Merge "TaskUpToDateValidator bringing back its own tracking of task inputs and outputs" into androidx-main
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
index bb33a40..7dbdbd4 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
@@ -89,12 +89,10 @@
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
 import androidx.lifecycle.enableSavedStateHandles
-import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import androidx.lifecycle.viewmodel.CreationExtras
 import androidx.lifecycle.viewmodel.MutableCreationExtras
-import androidx.lifecycle.whenCreated
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryController
 import androidx.savedstate.SavedStateRegistryOwner
@@ -103,7 +101,6 @@
 import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
-import kotlinx.coroutines.launch
 
 /**
  * Base class for activities that enables composition of higher level components.
@@ -640,20 +637,29 @@
                 }
             }
         }.also { dispatcher ->
-            lifecycleScope.launch {
-                whenCreated {
-                    if (Build.VERSION.SDK_INT >= 33) {
-                        dispatcher.setOnBackInvokedDispatcher(
-                            Api33Impl.getOnBackInvokedDispatcher(
-                                this@ComponentActivity
-                            )
-                        )
+            if (Build.VERSION.SDK_INT >= 33) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    Handler(Looper.getMainLooper()).post {
+                        addObserverForBackInvoker(dispatcher)
                     }
+                } else {
+                    addObserverForBackInvoker(dispatcher)
                 }
             }
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    private fun addObserverForBackInvoker(dispatcher: OnBackPressedDispatcher) {
+        lifecycle.addObserver(LifecycleEventObserver { _, event ->
+            if (event == Lifecycle.Event.ON_CREATE) {
+                dispatcher.setOnBackInvokedDispatcher(
+                    Api33Impl.getOnBackInvokedDispatcher(this@ComponentActivity)
+                )
+            }
+        })
+    }
+
     final override val savedStateRegistry: SavedStateRegistry
         get() = savedStateRegistryController.savedStateRegistry
 
diff --git a/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt b/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt
index 8132955..3b44d1a 100644
--- a/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt
+++ b/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt
@@ -286,7 +286,7 @@
 }
 
 @RequiresApi(28)
-private class EdgeToEdgeApi28 : EdgeToEdgeApi26() {
+private open class EdgeToEdgeApi28 : EdgeToEdgeApi26() {
 
     @DoNotInline
     override fun adjustLayoutInDisplayCutoutMode(window: Window) {
@@ -296,7 +296,7 @@
 }
 
 @RequiresApi(29)
-private open class EdgeToEdgeApi29 : EdgeToEdgeBase() {
+private open class EdgeToEdgeApi29 : EdgeToEdgeApi28() {
 
     @DoNotInline
     override fun setUp(
@@ -321,7 +321,7 @@
     }
 }
 
-@RequiresApi(29)
+@RequiresApi(30)
 private class EdgeToEdgeApi30 : EdgeToEdgeApi29() {
 
     @DoNotInline
diff --git a/activity/integration-tests/macrobenchmark-target/build.gradle b/activity/integration-tests/macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..a3b0aea
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark-target/build.gradle
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    namespace "androidx.activity.integration.macrobenchmark.target"
+
+    defaultConfig {
+        minSdkVersion 21
+    }
+}
+
+dependencies {
+    implementation(libs.kotlinStdlib)
+    implementation(project(":activity:activity"))
+}
diff --git a/activity/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/activity/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d486839
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<!--
+  ~ 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
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:label="Jetpack Activity Macrobenchmark Target"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <!-- Profileable to enable macrobenchmark profiling -->
+        <profileable android:shell="true"/>
+
+        <!--
+        Activities need to be exported so the macrobenchmark can discover them.
+
+        Feel free to add the launcher intent filter to enable easier profiling,
+        just be sure to add a consistent label.
+         -->
+        <activity
+            android:name=".OnBackPressedDispatcherInOnCreateActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.ON_BACK_PRESSED_DISPATCHER_IN_ON_CREATE_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/activity/integration-tests/macrobenchmark-target/src/main/java/androidx/activity/integration/macrobenchmark/target/OnBackPressedDispatcherInOnCreateActivity.kt b/activity/integration-tests/macrobenchmark-target/src/main/java/androidx/activity/integration/macrobenchmark/target/OnBackPressedDispatcherInOnCreateActivity.kt
new file mode 100644
index 0000000..8a38baa
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark-target/src/main/java/androidx/activity/integration/macrobenchmark/target/OnBackPressedDispatcherInOnCreateActivity.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 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.activity.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+
+class OnBackPressedDispatcherInOnCreateActivity :
+    ComponentActivity(R.layout.main_activity) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        onBackPressedDispatcher
+    }
+}
diff --git a/activity/integration-tests/macrobenchmark-target/src/main/res/layout/main_activity.xml b/activity/integration-tests/macrobenchmark-target/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..21951d4
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark-target/src/main/res/layout/main_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/activity/integration-tests/macrobenchmark/build.gradle b/activity/integration-tests/macrobenchmark/build.gradle
new file mode 100644
index 0000000..a1b080f
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.test")
+    id("kotlin-android")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 23
+    }
+    namespace "androidx.activity.integration.macrobenchmark"
+    targetProjectPath = ":activity:integration-tests:macrobenchmark-target"
+    experimentalProperties["android.experimental.self-instrumenting"] = true
+}
+
+dependencies {
+    implementation(project(":benchmark:benchmark-junit4"))
+    implementation(project(":benchmark:benchmark-macro-junit4"))
+    implementation(project(":internal-testutils-macrobenchmark"))
+    implementation(libs.testRules)
+    implementation(libs.testExtJunit)
+    implementation(libs.testCore)
+    implementation(libs.testRunner)
+    implementation(libs.testUiautomator)
+}
diff --git a/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt b/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt
new file mode 100644
index 0000000..2bf9360
--- /dev/null
+++ b/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 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.activity.integration.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ActivityStartMacroBenchmark {
+
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    @Test
+    fun startup() = benchmarkRule.measureStartup(
+        compilationMode = CompilationMode.Full(),
+        startupMode = StartupMode.COLD,
+        packageName = "androidx.activity.integration.macrobenchmark.target",
+        metrics = listOf(StartupTimingMetric()),
+        iterations = 10,
+    ) {
+        action = "androidx.compose.integration.macrobenchmark" +
+            ".target.ON_BACK_PRESSED_DISPATCHER_IN_ON_CREATE_ACTIVITY"
+    }
+}
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
index c2e7b39..192a3db 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
@@ -78,38 +78,50 @@
 
         val expectedFix = """
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -50 +50
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -50 +51
++     @OptIn(markerClass = ExperimentalLocation.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
 +     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -50 +50
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -50 +51
++     @OptIn(markerClass = ExperimentalLocation.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
 +     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -50 +50
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -50 +51
++     @OptIn(markerClass = ExperimentalLocation.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
 +     @ExperimentalLocation
@@ -193,38 +205,50 @@
 
         val expectedFix = """
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -28 +28
-+     @androidx.annotation.OptIn(ExperimentalDateTime::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -28 +29
++     @OptIn(ExperimentalDateTime::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -28 +28
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -28 +28
-+     @androidx.annotation.OptIn(ExperimentalDateTime::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -28 +29
++     @OptIn(ExperimentalDateTime::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -28 +28
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -28 +28
-+     @androidx.annotation.OptIn(ExperimentalDateTime::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -28 +29
++     @OptIn(ExperimentalDateTime::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -28 +28
 +     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-+     @androidx.annotation.OptIn(ExperimentalLocation::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -54 +55
++     @OptIn(ExperimentalLocation::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -54 +54
 +     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-+     @androidx.annotation.OptIn(ExperimentalLocation::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -54 +55
++     @OptIn(ExperimentalLocation::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -54 +54
 +     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-+     @androidx.annotation.OptIn(ExperimentalLocation::class)
+@@ -21 +21
++ import androidx.annotation.OptIn
+@@ -54 +55
++     @OptIn(ExperimentalLocation::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -54 +54
 +     @ExperimentalLocation
@@ -283,62 +307,82 @@
 
         val expectedFix = """
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
-@@ -24 +24
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -24 +25
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -51 +52
++     @OptIn(markerClass = ExperimentalLocationKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
 +     @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -51 +52
++     @OptIn(markerClass = ExperimentalLocationKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
 +     @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -51 +52
++     @OptIn(markerClass = ExperimentalLocationKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
 +     @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
-@@ -87 +87
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -87 +88
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
-@@ -87 +87
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -87 +88
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestInlineUsage':
-@@ -95 +95
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -95 +96
++     @OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 +     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'regressionTestInlineUsage':
-@@ -95 +95
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
+@@ -18 +18
++ import androidx.annotation.OptIn;
+@@ -95 +96
++     @OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 +     @ExperimentalDateTime
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index 99eb6f1..11d055a 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -490,8 +490,10 @@
 
         val expectedFix = """
 Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@androidx.annotation.OptIn(sample.optin.ExperimentalJavaAnnotation::class)' annotation to 'testMethod':
-@@ -21 +21
-+     @androidx.annotation.OptIn(ExperimentalJavaAnnotation::class)
+@@ -19 +19
++ import androidx.annotation.OptIn
+@@ -21 +22
++     @OptIn(ExperimentalJavaAnnotation::class)
 Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@sample.optin.ExperimentalJavaAnnotation' annotation to 'testMethod':
 @@ -21 +21
 +     @ExperimentalJavaAnnotation
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/ImageViewTintDetector.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/ImageViewTintDetector.kt
index 1efd15b..607f28c 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/ImageViewTintDetector.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/ImageViewTintDetector.kt
@@ -55,7 +55,7 @@
             element,
             context.getLocation(element.getAttributeNodeNS(SdkConstants.ANDROID_URI, "tint")),
             "Must use `app:tint` instead of `android:tint`",
-            LintFix.create().composite(
+            LintFix.create().name("Use `app:tint` instead of `android:tint`").composite(
                 LintFix.create().set(
                     SdkConstants.AUTO_URI, "tint",
                     element.getAttributeNS(SdkConstants.ANDROID_URI, "tint")
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/TextViewCompoundDrawablesXmlDetector.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/TextViewCompoundDrawablesXmlDetector.kt
index cc03549..fafaa93 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/TextViewCompoundDrawablesXmlDetector.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/TextViewCompoundDrawablesXmlDetector.kt
@@ -72,7 +72,7 @@
                 element,
                 context.getLocation(element.getAttributeNodeNS(SdkConstants.ANDROID_URI, from)),
                 "Use `app:$to` instead of `android:$from`",
-                LintFix.create().composite(
+                LintFix.create().name("Use app namespace instead of android").composite(
                     LintFix.create().set(
                         SdkConstants.AUTO_URI, to,
                         element.getAttributeNS(SdkConstants.ANDROID_URI, from)
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageButtonTintDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageButtonTintDetectorTest.kt
index 389d1a8..971003e 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageButtonTintDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageButtonTintDetectorTest.kt
@@ -58,7 +58,7 @@
             )
             .expectFixDiffs(
                 """
-Fix for res/layout/image_button.xml line 10: Set tint="#FF0000":
+Fix for res/layout/image_button.xml line 10: Use `app:tint` instead of `android:tint`:
 @@ -3 +3
 +     xmlns:app="http://schemas.android.com/apk/res-auto"
 @@ -11 +12
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageViewTintDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageViewTintDetectorTest.kt
index def09a8..73a3740 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageViewTintDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/ImageViewTintDetectorTest.kt
@@ -58,7 +58,7 @@
             )
             .expectFixDiffs(
                 """
-Fix for res/layout/image_view.xml line 10: Set tint="#FF0000":
+Fix for res/layout/image_view.xml line 10: Use `app:tint` instead of `android:tint`:
 @@ -3 +3
 +     xmlns:app="http://schemas.android.com/apk/res-auto"
 @@ -11 +12
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/TextViewCompoundDrawablesXmlDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/TextViewCompoundDrawablesXmlDetectorTest.kt
index be3fd6b..0508605 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/TextViewCompoundDrawablesXmlDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/TextViewCompoundDrawablesXmlDetectorTest.kt
@@ -107,7 +107,7 @@
             )
             .expectFixDiffs(
                 """
-Fix for res/layout-v23/text_view.xml line 9: Set $appAttrName="$attrValue":
+Fix for res/layout-v23/text_view.xml line 9: Use app namespace instead of android:
 @@ -3 +3
 +     xmlns:app="http://schemas.android.com/apk/res-auto"
 @@ -10 +11
diff --git a/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml b/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml
new file mode 100644
index 0000000..0c844d3
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        if (extensionBuildTypes.findByName(newBuildTypeName) != null) {"
+        errorLine2="                                ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/utils/BuildTypes.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    if (extensionBuildTypes.findByName(buildTypeName) != null) {"
+        errorLine2="                            ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/utils/BuildTypes.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                .findByName(fromVariantConfigurationName)"
+        errorLine2="                 ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/utils/Dependencies.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                .findByName(toVariantConfigurationName)"
+        errorLine2="                 ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/utils/Dependencies.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                .mapNotNull { ext.variants.findByName(it) }"
+        errorLine2="                                           ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                val variantConfig = ext.variants.findByName(it) ?: return@mapNotNull null"
+        errorLine2="                                                 ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt"/>
+    </issue>
+
+</issues>
diff --git a/benchmark/benchmark-common/lint-baseline.xml b/benchmark/benchmark-common/lint-baseline.xml
index 96cf036..01496cf7 100644
--- a/benchmark/benchmark-common/lint-baseline.xml
+++ b/benchmark/benchmark-common/lint-baseline.xml
@@ -1,77 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `stopAllPerfettoProcesses`"
-        errorLine1="        PerfettoHelper.stopAllPerfettoProcesses()"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 21 (current min is 19): `getPidsForProcess`"
-        errorLine1="        fun getPerfettoPids() = Shell.getPidsForProcess(if (unbundled) &quot;tracebox&quot; else &quot;perfetto&quot;)"
-        errorLine2="                                      ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `PerfettoCapture`"
-        errorLine1="        val capture = PerfettoCapture(unbundled)"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `start`"
-        errorLine1="        capture.start("
-        errorLine2="                ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `isRunning`"
-        errorLine1="        assertTrue(capture.isRunning())"
-        errorLine2="                           ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `stopAllPerfettoProcesses`"
-        errorLine1="        PerfettoHelper.stopAllPerfettoProcesses()"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `isRunning`"
-        errorLine1="        assertFalse(capture.isRunning())"
-        errorLine2="                            ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 19): `isAbiSupported`"
-        errorLine1="        Assume.assumeTrue(PerfettoHelper.isAbiSupported())"
-        errorLine2="                                         ~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="UnsafeOptInUsageError"
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index 0e64ea3..c99550b 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -43,7 +43,7 @@
     api(libs.kotlinStdlib)
     api("androidx.annotation:annotation:1.1.0")
     api(project(":benchmark:benchmark-macro"))
-    api("androidx.test.uiautomator:uiautomator:2.3.0-alpha04")
+    api("androidx.test.uiautomator:uiautomator:2.3.0-rc01")
     implementation(project(":benchmark:benchmark-common"))
     implementation("androidx.test:rules:1.5.0")
     implementation("androidx.test:runner:1.5.2")
diff --git a/benchmark/benchmark-macro/lint-baseline.xml b/benchmark/benchmark-macro/lint-baseline.xml
index 3743177..ad8812d 100644
--- a/benchmark/benchmark-macro/lint-baseline.xml
+++ b/benchmark/benchmark-macro/lint-baseline.xml
@@ -1,23 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 29 (current min is 23): `Power`"
-        errorLine1="            return if (type is PowerMetric.Type.Power) powerUw else energyUws"
-        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 29 (current min is 23): `Power`"
-        errorLine1="            return if (type is PowerMetric.Type.Power) powerUw else energyUws"
-        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanThreadSleep"
diff --git a/benchmark/gradle-plugin/lint-baseline.xml b/benchmark/gradle-plugin/lint-baseline.xml
new file mode 100644
index 0000000..e8cbd60
--- /dev/null
+++ b/benchmark/gradle-plugin/lint-baseline.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        if (project.rootProject.tasks.findByName(&quot;lockClocks&quot;) == null) {"
+        errorLine2="                                      ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        if (project.rootProject.tasks.findByName(&quot;unlockClocks&quot;) == null) {"
+        errorLine2="                                      ~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt"/>
+    </issue>
+
+</issues>
diff --git a/biometric/biometric/build.gradle b/biometric/biometric/build.gradle
index 99d096d..59ede8e 100644
--- a/biometric/biometric/build.gradle
+++ b/biometric/biometric/build.gradle
@@ -36,7 +36,7 @@
 
 dependencies {
     // Public API dependencies
-    api("androidx.annotation:annotation:1.1.0")
+    api("androidx.annotation:annotation:1.6.0")
     api("androidx.core:core:1.3.2")
     api("androidx.fragment:fragment:1.2.5")
 
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 0c4eff7..c29b0bb 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -28,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -1110,6 +1111,7 @@
          *                              {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          * @param allowedAuthenticators A bit field representing allowed authenticator types.
          */
+        @DoNotInline
         static void setAllowedAuthenticators(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 @BiometricManager.AuthenticatorTypes int allowedAuthenticators) {
@@ -1132,6 +1134,7 @@
          *                             {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          * @param confirmationRequired The value for the "confirmation required" option.
          */
+        @DoNotInline
         static void setConfirmationRequired(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 boolean confirmationRequired) {
@@ -1146,6 +1149,7 @@
          * @param deviceCredentialAllowed The value for the "device credential allowed" option.
          */
         @SuppressWarnings("deprecation")
+        @DoNotInline
         static void setDeviceCredentialAllowed(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 boolean deviceCredentialAllowed) {
@@ -1168,6 +1172,7 @@
          * @param context The application or activity context.
          * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          */
+        @DoNotInline
         @NonNull
         static android.hardware.biometrics.BiometricPrompt.Builder createPromptBuilder(
                 @NonNull Context context) {
@@ -1181,6 +1186,7 @@
          *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          * @param title   The title for the prompt.
          */
+        @DoNotInline
         static void setTitle(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 @NonNull CharSequence title) {
@@ -1194,6 +1200,7 @@
          *                 {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          * @param subtitle The subtitle for the prompt.
          */
+        @DoNotInline
         static void setSubtitle(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 @NonNull CharSequence subtitle) {
@@ -1207,6 +1214,7 @@
          *                    {@link android.hardware.biometrics.BiometricPrompt.Builder}.
          * @param description The description for the prompt.
          */
+        @DoNotInline
         static void setDescription(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 @NonNull CharSequence description) {
@@ -1222,6 +1230,7 @@
          * @param executor An executor for the negative button callback.
          * @param listener A listener for the negative button press event.
          */
+        @DoNotInline
         static void setNegativeButton(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
                 @NonNull CharSequence text,
@@ -1237,6 +1246,7 @@
          * @param builder The builder for the prompt.
          * @return An instance of {@link android.hardware.biometrics.BiometricPrompt}.
          */
+        @DoNotInline
         @NonNull
         static android.hardware.biometrics.BiometricPrompt buildPrompt(
                 @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder) {
@@ -1252,6 +1262,7 @@
          * @param executor           An executor for authentication callbacks.
          * @param callback           An object that will receive authentication events.
          */
+        @DoNotInline
         static void authenticate(
                 @NonNull android.hardware.biometrics.BiometricPrompt biometricPrompt,
                 @NonNull android.os.CancellationSignal cancellationSignal,
@@ -1271,6 +1282,7 @@
          * @param executor           An executor for authentication callbacks.
          * @param callback           An object that will receive authentication events.
          */
+        @DoNotInline
         static void authenticate(
                 @NonNull android.hardware.biometrics.BiometricPrompt biometricPrompt,
                 @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto,
@@ -1301,6 +1313,7 @@
          * @return An intent that can be used to launch the confirm device credential activity.
          */
         @SuppressWarnings("deprecation")
+        @DoNotInline
         @Nullable
         static Intent createConfirmDeviceCredentialIntent(
                 @NonNull KeyguardManager keyguardManager,
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 98d0cb8..5b3af52 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -17,9 +17,9 @@
 // This project contains tests for code contained in buildSrc
 // This project is stored outside of buildSrc/ so that waiting for these tests to complete doesn't delay the rest of the build
 
-import androidx.build.BuildServerConfigurationKt
 import androidx.build.LibraryType
-import androidx.build.SdkResourceGenerator
+import androidx.build.KotlinTarget
+import androidx.build.Publish
 
 plugins {
     id("AndroidXPlugin")
@@ -36,36 +36,28 @@
 //
 // :buildSrc-tests:jar: No valid plugin descriptors were found in META-INF/gradle-plugins
 // so we disable it.
-jar.enabled = false
 
 apply from: "../buildSrc/kotlin-dsl-dependency.gradle"
 
-def buildSrcJar(jarName) {
-    return project.files(
-            new File(
-                    BuildServerConfigurationKt.getRootOutDirectory(project),
-                    "buildSrc/$jarName/build/libs/${jarName}.jar"
-            )
-    )
+sourceSets {
+    main.kotlin.srcDirs += [
+            '../buildSrc/public/src/main/kotlin',
+            '../buildSrc/private/src/main/kotlin',
+            '../buildSrc/jetpad-integration/src/main/java'
+    ]
+    main.java.srcDirs += [
+            '../buildSrc/jetpad-integration/src/main/java',
+    ]
+    main.resources.srcDirs += ['../buildSrc/private/src/main/resources']
 }
 
-dependencies {
-    implementation(findGradleKotlinDsl())
-    implementation(gradleApi())
-    implementation(libs.androidGradlePluginz)
-    implementation(buildSrcJar("private"))
-    implementation(buildSrcJar("public"))
-    implementation(buildSrcJar("jetpad-integration"))
-    implementation("com.googlecode.json-simple:json-simple:1.1")
-    implementation(libs.gson)
-    implementation(libs.dom4j) {
-        // Optional dependency where Ivy fails to parse the POM file.
-        exclude(group:"net.java.dev.msv", module:"xsdlib")
-    }
-    // Required for dom4j to parse comments correctly.
-    implementation(libs.xerces)
-    implementation(libs.kotlinGradlePluginz)
+apply from: "${buildscript.sourceFile.parentFile.parent}/buildSrc/shared-dependencies.gradle"
 
+dependencies {
+    implementation(project(":benchmark:benchmark-gradle-plugin"))
+    implementation(project(":inspection:inspection-gradle-plugin"))
+    implementation(project(":stableaidl:stableaidl-gradle-plugin"))
+    implementation(findGradleKotlinDsl())
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(project(":internal-testutils-gradle-plugin"))
@@ -81,7 +73,9 @@
 }
 
 androidx {
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
+    type = LibraryType.GRADLE_PLUGIN
+    publish = Publish.NONE
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 // Also do style checking of the buildSrc project from within this project
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
new file mode 100644
index 0000000..b57c355
--- /dev/null
+++ b/buildSrc-tests/lint-baseline.xml
@@ -0,0 +1,589 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.plugins.all { plugin ->"
+        errorLine2="                        ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXComposeImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="            multiplatformExtension.sourceSets.all {"
+        errorLine2="                                              ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXComposeImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                if (multiplatformExtension.targets.findByName(&quot;jvm&quot;) != null) {"
+        errorLine2="                                                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXComposeImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                if (multiplatformExtension.targets.findByName(&quot;desktop&quot;) != null) {"
+        errorLine2="                                                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXComposeImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.plugins.all { plugin ->"
+        errorLine2="                        ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/docs/AndroidXDocsImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="                    libraryExtension.buildTypes.all { buildType ->"
+        errorLine2="                                                ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/docs/AndroidXDocsImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of whenTaskAdded"
+        errorLine1="        project.tasks.whenTaskAdded { task ->"
+        errorLine2="                      ~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/docs/AndroidXDocsImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.plugins.all { plugin ->"
+        errorLine2="                        ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        val allHostTests = project.tasks.register(&quot;allHostTests&quot;).get()"
+        errorLine2="                                                                  ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        configurations.all { configuration ->"
+        errorLine2="                       ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                target.compilations.findByName(&quot;test&quot;)?.compileKotlinTaskName == name"
+        errorLine2="                                    ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="            project.agpVariants.all { variant ->"
+        errorLine2="                                ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                    (sourceSets.findByName(&quot;main&quot;)?.kotlin"
+        errorLine2="                                ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.configurations.all { configuration ->"
+        errorLine2="                               ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        buildTypes.all { buildType ->"
+        errorLine2="                   ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.configurations.all { configuration ->"
+        errorLine2="                               ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.configurations.all { configuration ->"
+        errorLine2="                               ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        sourceSets.findByName(&quot;main&quot;)!!.manifest.srcFile(&quot;src/androidMain/AndroidManifest.xml&quot;)"
+        errorLine2="                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="            .findByName(&quot;androidTest&quot;)!!"
+        errorLine2="             ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        kmpExtension.targets.all { kotlinTarget ->"
+        errorLine2="                             ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="            kotlinTarget.compilations.all {"
+        errorLine2="                                      ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="            project.configurations.all { config ->"
+        errorLine2="                                   ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="            val androidConfiguration = project.configurations.findByName(&quot;implementation&quot;)"
+        errorLine2="                                                              ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    configurations.all { configuration ->"
+        errorLine2="                   ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        if (project.tasks.findByName(&quot;check&quot;) != null) {"
+        errorLine2="                          ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXMultiplatformExtension.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        project.configurations.all { configuration ->"
+        errorLine2="                               ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXPlaygroundRootImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use register instead of create"
+        errorLine1="        val buildOnServerTask = tasks.create(BUILD_ON_SERVER_TASK, BuildOnServerTask::class.java)"
+        errorLine2="                                      ~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXRootImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="                    subproject.configurations.all { configuration ->"
+        errorLine2="                                              ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXRootImplPlugin.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="                        .get() as ProcessLibraryManifest"
+        errorLine2="                         ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/checkapi/ApiTasks.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                    container.findByName(configName)"
+        errorLine2="                              ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/license/CheckExternalDependencyLicensesTask.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    variants.all { variant ->"
+        errorLine2="             ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/ErrorProneConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        val jvmJarTask = jvmJarTaskProvider.get()"
+        errorLine2="                                            ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/ErrorProneConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="                val compileTask = compileTaskProvider.get()"
+        errorLine2="                                                      ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/ErrorProneConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        this.configurations.all { c ->"
+        errorLine2="                            ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/GradleTransformWorkaround.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                checkNotNull(compilations.findByName(compilationName)) {"
+        errorLine2="                                          ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/java/JavaCompileInputs.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        configurations.findByName(&quot;ktlint&quot;)"
+        errorLine2="                       ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/Ktlint.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        if (project.tasks.findByName(&quot;check&quot;) != null) {"
+        errorLine2="                          ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/Ktlint.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    project.plugins.all { plugin ->"
+        errorLine2="                    ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                .mapNotNull { configName -> project.configurations.findByName(configName) }"
+        errorLine2="                                                                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                .mapNotNull { target -> target.compilations.findByName(kmpCompilationName) }"
+        errorLine2="                                                            ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    multiplatformExtension.targets.findByName(&quot;android&quot;) ?: return"
+        errorLine2="                                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        multiplatformExtension.sourceSets.findByName(&quot;androidMain&quot;)"
+        errorLine2="                                          ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        components.all { component ->"
+        errorLine2="                   ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    tasks.findByName(taskName) ?: throw GradleException("
+        errorLine2="          ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use named instead of getByName"
+        errorLine1="                tasks.getByName(&quot;publishPluginMavenPublicationToMavenRepository&quot;).doFirst {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use named instead of getByName"
+        errorLine1="                    tasks.getByName(&quot;publishMavenPublicationToMavenRepository&quot;).doFirst {"
+        errorLine2="                          ~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="        publications.withType(MavenPublication::class.java).all { publication ->"
+        errorLine2="                                                            ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    multiplatformExtension.targets.all { target ->"
+        errorLine2="                                   ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    val kotlinComponent = components.findByName(&quot;kotlin&quot;) as SoftwareComponentInternal"
+        errorLine2="                                     ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/MavenUploadHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        configurations.findByName(&quot;metalava&quot;)"
+        errorLine2="                       ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/metalava/MetalavaRunner.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    libraryExtension.libraryVariants.all { variant ->"
+        errorLine2="                                     ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/resources/PublicResourcesStubHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    val release = components.findByName(&quot;release&quot;)"
+        errorLine2="                             ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/PublishingHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    val javaComponent = components.findByName(&quot;java&quot;)"
+        errorLine2="                                   ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/PublishingHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="            val verifyOutputsTask = verifyOutputs.get()"
+        errorLine2="                                                  ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/Release.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of all"
+        errorLine1="    libraryVariants.all { variant ->"
+        errorLine2="                    ~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/Release.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    val samplesConfiguration = project.configurations.findByName(&quot;samples&quot;)"
+        errorLine2="                                                      ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/Samples.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="            appliesShadowPlugin() &amp;&amp; project.configurations.findByName(&quot;shadowed&quot;) == null"
+        errorLine2="                                                            ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/sbom/Sbom.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        val resolved = project.configurations.findByName(configurationName)"
+        errorLine2="                                              ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/sbom/Sbom.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of whenObjectAdded"
+        errorLine1="        configurations.whenObjectAdded {"
+        errorLine2="                       ~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/SourceJarTaskHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Use configureEach instead of whenObjectAdded"
+        errorLine1="                it.artifacts.whenObjectAdded { _ ->"
+        errorLine2="                             ~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/SourceJarTaskHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                javaExtension.sourceSets.findByName(&quot;main&quot;)?.let {"
+        errorLine2="                                         ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/SourceJarTaskHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="                    kmpExtension.sourceSets.findByName(sourceSetName)?.let { sourceSet ->"
+        errorLine2="                                            ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/SourceJarTaskHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="    compilations.findByName(MAIN_COMPILATION_NAME) ?: compilations.getByName(&quot;debug&quot;)"
+        errorLine2="                 ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/SourceJarTaskHelper.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        .findByName(FINALIZE_TEST_CONFIGS_WITH_APKS_TASK)!!"
+        errorLine2="         ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method findByName"
+        errorLine1="        project.rootProject.tasks.findByName(FINALIZE_TEST_CONFIGS_WITH_APKS_TASK)!!.dependsOn(task)"
+        errorLine2="                                  ~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
+    </issue>
+
+</issues>
diff --git a/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle b/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle
deleted file mode 100644
index ece0f79..0000000
--- a/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-import androidx.build.paparazzi.AndroidXPaparazziImplPlugin
-
-buildscript {
-    dependencies {
-        classpath(project.files("${project.rootProject.ext["buildSrcOut"]}/private/build/libs/private.jar"))
-    }
-}
-
-apply plugin: AndroidXPaparazziImplPlugin
diff --git a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
index 3458531..8dff2a9 100644
--- a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
+++ b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
@@ -38,7 +38,7 @@
  * @property dependencies a list of dependencies on other androidx libraries
  * @property checks arraylist of [Check]s that is used by Jetpad
  */
-public class LibraryBuildInfoFile {
+public final class LibraryBuildInfoFile {
     public String groupId;
     public String artifactId;
     public String version;
@@ -55,7 +55,7 @@
     /**
      * @property isTipOfTree boolean that specifies whether the dependency is tip-of-tree
      */
-    public static class Dependency implements Serializable {
+    public static final class Dependency implements Serializable {
         public String groupId;
         public String artifactId;
         public String version;
@@ -78,7 +78,7 @@
         }
     }
 
-    public class Check {
+    public static final class Check {
         public String name;
         public boolean passing;
     }
diff --git a/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt b/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt
deleted file mode 100644
index 090d9b6..0000000
--- a/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2022 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.build
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-/**
- * Configures screenshot testing using Paparazzi for AndroidX projects.
- *
- * The actual implementation is in AndroidXPaparazziImplPlugin.
- */
-class AndroidXPaparazziPlugin : Plugin<Project> {
-    override fun apply(project: Project) {
-        val supportRoot = project.getSupportRootFolder()
-        project.apply(
-            mapOf("from" to "$supportRoot/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle")
-        )
-    }
-}
diff --git a/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties b/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties
deleted file mode 100644
index 872551c..0000000
--- a/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright 2022 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.
-#
-
-implementation-class=androidx.build.AndroidXPaparazziPlugin
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AgpExtensions.kt b/buildSrc/private/src/main/kotlin/androidx/build/AgpExtensions.kt
deleted file mode 100644
index 357b46d..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/AgpExtensions.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2018 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.build
-
-import com.android.build.gradle.AppExtension
-import com.android.build.gradle.BaseExtension
-import com.android.build.gradle.LibraryExtension
-import com.android.build.gradle.TestExtension
-import org.gradle.api.DomainObjectSet
-import org.gradle.api.Project
-
-@Suppress("DEPRECATION") // BaseVariant
-val BaseExtension.variants: DomainObjectSet<out com.android.build.gradle.api.BaseVariant>
-    get() =
-        when (this) {
-            is AppExtension -> applicationVariants
-            is LibraryExtension -> libraryVariants
-            is TestExtension -> applicationVariants
-            else -> error("Unhandled plugin ${this::class.java}")
-        }
-
-@Suppress("DEPRECATION") // BaseVariant
-val Project.agpVariants: DomainObjectSet<out com.android.build.gradle.api.BaseVariant>
-    get() {
-        val extension =
-            checkNotNull(project.extensions.findByType(BaseExtension::class.java)) {
-                "${project.name} has no BaseExtension"
-            }
-        return extension.variants
-    }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index a0ac53e..9175c7d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -25,6 +25,7 @@
 import androidx.build.checkapi.KmpApiTaskConfig
 import androidx.build.checkapi.LibraryApiTaskConfig
 import androidx.build.checkapi.configureProjectForApiTasks
+import androidx.build.docs.CheckTipOfTreeDocsTask.Companion.setUpCheckDocsTask
 import androidx.build.gradle.isRoot
 import androidx.build.license.configureExternalDependencyLicenseCheck
 import androidx.build.resources.configurePublicResourcesStub
@@ -100,14 +101,12 @@
 import org.gradle.kotlin.dsl.withModule
 import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin
 import org.gradle.plugin.devel.tasks.ValidatePlugins
-import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
 import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
@@ -580,6 +579,7 @@
             AndroidMultiplatformApiTaskConfig,
             androidXExtension
         )
+        project.setUpCheckDocsTask(androidXExtension)
     }
 
     /**
@@ -628,18 +628,17 @@
 
     private fun Project.buildOnServerDependsOnLint() {
         if (!project.usingMaxDepVersions()) {
-            project.agpVariants.all { variant ->
-                // in AndroidX, release and debug variants are essentially the same,
-                // so we don't run the lintRelease task on the build server
+            val androidComponents = extensions.findByType(AndroidComponentsExtension::class.java)
+            androidComponents?.onVariants { variant ->
                 if (!variant.name.lowercase(Locale.getDefault()).contains("release")) {
                     val taskName =
                         "lint${variant.name.replaceFirstChar {
-                        if (it.isLowerCase()) {
-                            it.titlecase(Locale.getDefault())
-                        } else {
-                            it.toString()
-                        }
-                    }}"
+                            if (it.isLowerCase()) {
+                                it.titlecase(Locale.getDefault())
+                            } else {
+                                it.toString()
+                            }
+                        }}"
                     project.addToBuildOnServer(taskName)
                 }
             }
@@ -758,6 +757,7 @@
             LibraryApiTaskConfig(libraryExtension),
             androidXExtension
         )
+        project.setUpCheckDocsTask(androidXExtension)
 
         project.addToProjectMap(androidXExtension)
 
@@ -821,6 +821,7 @@
             }
 
         project.configureProjectForApiTasks(apiTaskConfig, androidXExtension)
+        project.setUpCheckDocsTask(androidXExtension)
 
         project.afterEvaluate {
             if (androidXExtension.shouldRelease()) {
@@ -931,7 +932,7 @@
             buildType.signingConfig = debugSigningConfig
         }
 
-        project.configureErrorProneForAndroid(variants)
+        project.configureErrorProneForAndroid()
 
         // workaround for b/120487939
         project.configurations.all { configuration ->
@@ -1421,56 +1422,6 @@
     project.tasks.named("check").configure { it.dependsOn(task) }
 }
 
-/** Expected to be called in afterEvaluate when all extensions are available */
-internal fun Project.hasAndroidTestSourceCode(): Boolean {
-    // com.android.test modules keep test code in main sourceset
-    extensions.findByType(TestExtension::class.java)?.let { testExtension ->
-        testExtension.sourceSets.findByName("main")?.let { sourceSet ->
-            if (!sourceSet.java.getSourceFiles().isEmpty) return true
-        }
-        // check kotlin-android main source set
-        extensions
-            .findByType(KotlinAndroidProjectExtension::class.java)
-            ?.sourceSets
-            ?.findByName("main")
-            ?.let { if (it.kotlin.files.isNotEmpty()) return true }
-        // Note, don't have to check for kotlin-multiplatform as it is not compatible with
-        // com.android.test modules
-    }
-
-    // check Java androidTest source set
-    extensions
-        .findByType(TestedExtension::class.java)
-        ?.sourceSets
-        ?.findByName("androidTest")
-        ?.let { sourceSet ->
-            // using getSourceFiles() instead of sourceFiles due to b/150800094
-            if (!sourceSet.java.getSourceFiles().isEmpty) return true
-        }
-
-    // check kotlin-android androidTest source set
-    extensions
-        .findByType(KotlinAndroidProjectExtension::class.java)
-        ?.sourceSets
-        ?.findByName("androidTest")
-        ?.let { if (it.kotlin.files.isNotEmpty()) return true }
-
-    // check kotlin-multiplatform androidInstrumentedTest target source sets
-    multiplatformExtension?.let { kmpExtension ->
-        val instrumentedTestSourceSets = kmpExtension
-            .targets
-            .filterIsInstance<KotlinAndroidTarget>()
-            .mapNotNull {
-                target -> target.compilations.findByName("debugAndroidTest")
-            }.flatMap { compilation -> compilation.allKotlinSourceSets }
-        if (instrumentedTestSourceSets.any { it.kotlin.files.isNotEmpty() }) {
-            return true
-        }
-    }
-
-    return false
-}
-
 fun Project.validateMultiplatformPluginHasNotBeenApplied() {
     if (plugins.hasPlugin(KotlinMultiplatformPluginWrapper::class.java)) {
         throw GradleException(
@@ -1602,4 +1553,8 @@
     }
 }
 
+internal fun String.camelCase() = replaceFirstChar {
+    if (it.isLowerCase()) it.titlecase() else it.toString()
+}
+
 const val PROJECT_OR_ARTIFACT_EXT_NAME = "projectOrArtifact"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 8e22f42..d9dd5cd 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -145,13 +145,7 @@
                         subproject.name != "docs-public" &&
                         subproject.name != "docs-tip-of-tree" &&
                         subproject.name != "camera-testapp-timing" &&
-                        subproject.name != "room-testapp" &&
-                        !(subproject.path.contains(
-                            "media2:media2-session:version-compat-tests:client-previous"
-                        )) &&
-                        !(subproject.path.contains(
-                            "media2:media2-session:version-compat-tests:service-previous"
-                        ))
+                        subproject.name != "room-testapp"
                 ) {
                     subproject.configurations.all { configuration ->
                         configuration.resolutionStrategy.dependencySubstitution.apply {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 8e90dc2..6808ccc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -18,13 +18,10 @@
 
 import androidx.build.java.JavaCompileInputs
 import com.android.build.api.variant.AndroidComponentsExtension
-import com.android.builder.core.BuilderConstants
-import org.gradle.api.DomainObjectSet
 import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.logging.Logging
 import org.gradle.api.plugins.JavaPlugin.COMPILE_JAVA_TASK_NAME
-import org.gradle.api.provider.MapProperty
 import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.SourceSetContainer
@@ -40,7 +37,6 @@
 const val ERROR_PRONE_TASK = "runErrorProne"
 
 private const val ERROR_PRONE_CONFIGURATION = "errorprone"
-private const val ERROR_PRONE_VERSION = "com.google.errorprone:error_prone_core:2.14.0"
 private val log = Logging.getLogger("ErrorProneConfiguration")
 
 fun Project.configureErrorProneForJava() {
@@ -56,43 +52,33 @@
         return
     }
 
-    val javaCompileProvider = project.tasks.named(COMPILE_JAVA_TASK_NAME, JavaCompile::class.java)
     log.info("Configuring error-prone for ${project.path}")
     if (kmpExtension != null) {
         val jvmJarProvider = tasks.named(kmpExtension.jvm().artifactsTaskName, Jar::class.java)
         makeKmpErrorProneTask(
-            javaCompileProvider,
+            COMPILE_JAVA_TASK_NAME,
             jvmJarProvider,
             JavaCompileInputs.fromKmpJvmTarget(project)
         )
     } else {
-        makeErrorProneTask(javaCompileProvider)
+        makeErrorProneTask(COMPILE_JAVA_TASK_NAME)
     }
 }
 
-@Suppress("DEPRECATION") // BaseVariant
-fun Project.configureErrorProneForAndroid(
-    variants: DomainObjectSet<out com.android.build.gradle.api.BaseVariant>
-) {
-    var annotationArgs: MapProperty<String, String>? = null
-    val extension = extensions.findByType(AndroidComponentsExtension::class.java)
-    extension?.onVariants { variant ->
-        @Suppress("UnstableApiUsage")
-        annotationArgs = variant.javaCompilation.annotationProcessor.arguments
-    }
-    val errorProneConfiguration = createErrorProneConfiguration()
-    variants.all { variant ->
-        if (variant.buildType.name == BuilderConstants.RELEASE) {
-            val task = variant.javaCompileProvider
-            (variant as com.android.build.gradle.api.BaseVariant)
-                .annotationProcessorConfiguration
+fun Project.configureErrorProneForAndroid() {
+    val androidComponents = extensions.findByType(AndroidComponentsExtension::class.java)
+    androidComponents?.onVariants { variant ->
+        if (variant.buildType == "release") {
+            val errorProneConfiguration = createErrorProneConfiguration()
+            configurations.getByName(variant.annotationProcessorConfiguration.name)
                 .extendsFrom(errorProneConfiguration)
 
             log.info("Configuring error-prone for ${variant.name}'s java compile")
-            makeErrorProneTask(task) { javaCompile ->
-                // Passing along annotation processor arguments to errorprone compile task
+            makeErrorProneTask("compile${variant.name.camelCase()}JavaWithJavac") { javaCompile ->
+                @Suppress("UnstableApiUsage")
+                val annotationArgs = variant.javaCompilation.annotationProcessor.arguments
                 javaCompile.options.compilerArgumentProviders.add(
-                    CommandLineArgumentProviderAdapter(annotationArgs!!)
+                    CommandLineArgumentProviderAdapter(annotationArgs)
                 )
             }
         }
@@ -110,17 +96,15 @@
     }
 }
 
-private fun Project.createErrorProneConfiguration(): Configuration {
-    val errorProneConfiguration =
-        configurations.create(ERROR_PRONE_CONFIGURATION) {
-            it.isVisible = false
-            it.isCanBeConsumed = false
-            it.isCanBeResolved = true
-            it.exclude(group = "com.google.errorprone", module = "javac")
-        }
-    dependencies.add(ERROR_PRONE_CONFIGURATION, getLibraryByName("errorProne"))
-    return errorProneConfiguration
-}
+private fun Project.createErrorProneConfiguration(): Configuration =
+    configurations.findByName(ERROR_PRONE_CONFIGURATION)
+        ?: configurations.create(ERROR_PRONE_CONFIGURATION).apply {
+            isVisible = false
+            isCanBeConsumed = false
+            isCanBeResolved = true
+            exclude(group = "com.google.errorprone", module = "javac")
+            project.dependencies.add(ERROR_PRONE_CONFIGURATION, getLibraryByName("errorProne"))
+    }
 
 // Given an existing JavaCompile task, reconfigures the task to use the ErrorProne compiler plugin
 private fun JavaCompile.configureWithErrorProne() {
@@ -281,11 +265,11 @@
  * @param jvmCompileInputs [JavaCompileInputs] that specifies jvm source including Kotlin sources.
  */
 private fun Project.makeKmpErrorProneTask(
-    compileTaskProvider: TaskProvider<JavaCompile>,
+    compileTaskName: String,
     jvmJarTaskProvider: TaskProvider<Jar>,
     jvmCompileInputs: JavaCompileInputs
 ) {
-    makeErrorProneTask(compileTaskProvider) { errorProneTask ->
+    makeErrorProneTask(compileTaskName) { errorProneTask ->
         // ErrorProne doesn't understand Kotlin source, so first let kotlinCompile finish, then
         // take the resulting jar and add it to the classpath.
         val jvmJarTask = jvmJarTaskProvider.get()
@@ -312,14 +296,16 @@
  *   any additional configuration such as overriding default settings.
  */
 private fun Project.makeErrorProneTask(
-    compileTaskProvider: TaskProvider<JavaCompile>,
+    compileTaskName: String,
     onConfigure: (errorProneTask: JavaCompile) -> Unit = {}
-) {
+) = afterEvaluate {
     val errorProneTaskProvider =
         maybeRegister<JavaCompile>(
             name = ERROR_PRONE_TASK,
             onConfigure = {
-                val compileTask = compileTaskProvider.get()
+                val compileTask = tasks.withType(JavaCompile::class.java)
+                    .named(compileTaskName)
+                    .get()
                 it.classpath = compileTask.classpath
                 it.source = compileTask.source
                 it.destinationDirectory.set(layout.buildDirectory.dir("errorProne"))
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 6ed3943..ffc24e5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -17,6 +17,7 @@
 package androidx.build
 
 import com.android.build.api.dsl.Lint
+import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.gradle.AppPlugin
 import com.android.build.gradle.LibraryPlugin
 import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
@@ -74,10 +75,7 @@
 
         // We already run lintDebug, we don't need to run lint on the release variant.
         tasks.named("lint").configure { task -> task.enabled = false }
-
-        afterEvaluate {
-            registerLintDebugIfNeededAfterEvaluate()
-        }
+        registerLintDebugIfNeeded()
     }
 
 /** Android Lint configuration entry point for non-Android projects. */
@@ -123,24 +121,27 @@
 
 /**
  * Registers the `lintDebug` task if there are debug variants present.
- *
- * This method *must* run after evaluation.
  */
-private fun Project.registerLintDebugIfNeededAfterEvaluate() {
-    val variantNames = project.agpVariants.map { it.name }
-    if (!variantNames.contains("debug")) {
-        tasks.register("lintDebug") { task ->
-            // The lintDebug tasks depends on lint tasks for all debug variants.
-            variantNames
-                .filter { it.contains("debug", ignoreCase = true) }
-                .map { tasks.named("lint${it.camelCase()}") }
-                .forEach { task.dependsOn(it) }
+private fun Project.registerLintDebugIfNeeded() {
+    val androidComponents = extensions.findByType(AndroidComponentsExtension::class.java)
+    val debugVariantLintTaskNames = mutableListOf<String>()
+
+    androidComponents?.onVariants { variant ->
+        if (variant.buildType == "debug") {
+            debugVariantLintTaskNames.add("lint${variant.name.camelCase()}")
         }
     }
-}
 
-private fun String.camelCase() = replaceFirstChar {
-    if (it.isLowerCase()) it.titlecase() else it.toString()
+    afterEvaluate {
+        val filteredTaskNames = debugVariantLintTaskNames.filterNot { it == "lintDebug" }
+        if (filteredTaskNames.isNotEmpty()) {
+            tasks.register("lintDebug") { task ->
+                filteredTaskNames.forEach { lintTaskName ->
+                    task.dependsOn(tasks.named(lintTaskName))
+                }
+            }
+        }
+    }
 }
 
 /**
@@ -342,6 +343,7 @@
             }
         } else {
             disable.add("BanUncheckedReflection")
+            disable.add("BanConcurrentHashMap")
         }
 
         // Only show ObsoleteCompatMethod in the IDE.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
index 79c3d60..f84fbe7 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
@@ -20,15 +20,11 @@
 import androidx.build.Release
 import androidx.build.RunApiTasks
 import androidx.build.Version
-import androidx.build.addToBuildOnServer
-import androidx.build.docs.CheckTipOfTreeDocsTask
-import androidx.build.getSupportRootFolder
 import androidx.build.isWriteVersionedApiFilesEnabled
 import androidx.build.java.JavaCompileInputs
 import androidx.build.metalava.MetalavaTasks
 import androidx.build.resources.ResourceTasks
 import androidx.build.stableaidl.setupWithStableAidlPlugin
-import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import androidx.build.version
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.tasks.ProcessLibraryManifest
@@ -146,21 +142,6 @@
             return@afterEvaluate
         }
 
-        // Require docs to be set up unless opted-out
-        if (extension.doNotDocumentReason == null) {
-            val checkDocs = project.tasks.register(
-                "checkDocsTipOfTree",
-                CheckTipOfTreeDocsTask::class.java
-            ) { task ->
-                task.tipOfTreeBuildFile.set(
-                    project.getSupportRootFolder().resolve("docs-tip-of-tree/build.gradle")
-                )
-                task.projectPathProvider.set(path)
-                task.cacheEvenIfNoOutputs()
-            }
-            project.addToBuildOnServer(checkDocs)
-        }
-
         val builtApiLocation = project.getBuiltApiLocation()
         val versionedApiLocation = project.getVersionedApiLocation()
         val currentApiLocation = project.getCurrentApiLocation()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/CheckApi.kt b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
index 222b5373..a130ea3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
@@ -68,10 +68,7 @@
                 "Did you mean $suggestedVersion?"
         )
     }
-    var extra = ""
-    if (version.patch == 0 && version.extra != null) {
-        extra = version.extra!!
-    }
+    val extra = if (version.patch != 0) "" else version.extra ?: ""
     return Version(version.major, version.minor, 0, extra)
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 2361992..6307291 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -521,20 +521,12 @@
                     // Making a change in :media:version-compat-tests makes
                     // mediaGenerateTestConfiguration run (an unfortunate but low priority bug). To
                     // prevent failures from missing apks, we make sure to build the
-                    // version-compat-tests projects in that case. Same with media2-session below.
+                    // version-compat-tests projects in that case.
                     ":media:version-compat-tests",
                     ":media:version-compat-tests:client",
                     ":media:version-compat-tests:service",
                     ":media:version-compat-tests:client-previous",
                     ":media:version-compat-tests:service-previous"
-                ),
-                setOf(
-                    ":media2:media2-session",
-                    ":media2:media2-session:version-compat-tests",
-                    ":media2:media2-session:version-compat-tests:client",
-                    ":media2:media2-session:version-compat-tests:service",
-                    ":media2:media2-session:version-compat-tests:client-previous",
-                    ":media2:media2-session:version-compat-tests:service-previous"
                 ), // Link material and material-ripple
                 setOf(":compose:material:material-ripple", ":compose:material:material"),
                 setOf(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index 0c2315e..a67c3d3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -733,7 +733,11 @@
 private val hiddenAnnotationsJava: List<String> = emptyList()
 
 // Annotations which mean the elements they are applied to should be hidden from the docs
-private val annotationsToHideApis: List<String> = listOf("androidx.annotation.RestrictTo")
+private val annotationsToHideApis: List<String> = listOf(
+    "androidx.annotation.RestrictTo",
+    // Appears in androidx.test sources
+    "dagger.internal.DaggerGenerated",
+)
 
 /** Data class that matches JSON structure of kotlin source set metadata */
 data class ProjectStructureMetadata(var sourceSets: List<SourceSetMetadata>)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt
index 4304dae..887bd7c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt
@@ -16,8 +16,16 @@
 
 package androidx.build.docs
 
+import androidx.build.AndroidXExtension
+import androidx.build.LibraryType
+import androidx.build.addToBuildOnServer
+import androidx.build.checkapi.shouldConfigureApiTasks
+import androidx.build.getSupportRootFolder
+import androidx.build.multiplatformExtension
+import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
+import org.gradle.api.Project
 import org.gradle.api.file.RegularFileProperty
 import org.gradle.api.provider.Property
 import org.gradle.api.tasks.CacheableTask
@@ -39,24 +47,84 @@
     @get:Input
     abstract val projectPathProvider: Property<String>
 
+    @get:Input
+    abstract val type: Property<DocsType>
+
     @TaskAction
     fun exec() {
+        val projectPath = projectPathProvider.get()
         // Make sure not to allow a partial project path match, e.g. ":activity:activity" shouldn't
         // match ":activity:activity-ktx", both need to be listed separately.
-        val projectPath = projectPathProvider.get()
-        val fullExpectedText = "project(\"$projectPath\")"
-        if (!tipOfTreeBuildFile.asFile.get().readText().contains(fullExpectedText)) {
-            val message = "Project $projectPath not found in docs-tip-of-tree/build.gradle\n\n" +
-                "Use the project creation script (development/project-creator/create_project.py) " +
-                "when setting up a project to make sure all required steps are complete.\n\n" +
-                "The project should be added to docs-tip-of-tree/build.gradle as " +
-                "\'docs(project(\"$projectPath\"))\' (use 'kmpDocs' instead of 'docs' for KMP " +
-                "projects).\n\n" +
-                "If this project should not have published refdocs, first check that the library " +
-                "type listed in its build.gradle file is accurate. If it is, opt out of refdoc " +
-                "generation using \'doNotDocumentReason = \"some reason\"\' in the 'androidx' " +
-                "configuration section (this is not common)."
+        val projectDependency = "project(\"$projectPath\")"
+
+        val prefix = type.get().prefix
+        // Check that projects are listed with the right configuration type (docs, kmpDocs, samples)
+        val fullExpectedText = "$prefix($projectDependency)"
+
+        val fileContents = tipOfTreeBuildFile.asFile.get().readText()
+        if (!fileContents.contains(fullExpectedText)) {
+            // If this is a KMP project, check if it is present but configured as non-KMP
+            val message = if (fileContents.contains(projectDependency)) {
+                "Project $projectPath has the wrong configuration type in " +
+                    "docs-tip-of-tree/build.gradle, should use $prefix\n\n" +
+                    "Update the entry for $projectPath in docs-tip-of-tree/build.gradle to " +
+                    "'$fullExpectedText'."
+            } else {
+                "Project $projectPath not found in docs-tip-of-tree/build.gradle\n\n" +
+                    "Use the project creation script (development/project-creator/" +
+                    "create_project.py) when setting up a project to make sure all required " +
+                    "steps are complete.\n\n" +
+                    "The project should be added to docs-tip-of-tree/build.gradle as " +
+                    "\'$fullExpectedText\'.\n\n" +
+                    "If this project should not have published refdocs, first check that the " +
+                    "library type listed in its build.gradle file is accurate. If it is, opt out " +
+                    "of refdoc generation using \'doNotDocumentReason = \"some reason\"\' in the " +
+                    "'androidx' configuration section (this is not common)."
+            }
             throw GradleException(message)
         }
     }
+
+    companion object {
+        fun Project.setUpCheckDocsTask(extension: AndroidXExtension) {
+            project.afterEvaluate {
+                if (!extension.requiresDocs()) return@afterEvaluate
+
+                val docsType = if (extension.type == LibraryType.Companion.SAMPLES) {
+                    DocsType.SAMPLES
+                } else if (multiplatformExtension != null) {
+                    DocsType.KMP
+                } else {
+                    DocsType.STANDARD
+                }
+
+                val checkDocs = project.tasks.register(
+                    "checkDocsTipOfTree",
+                    CheckTipOfTreeDocsTask::class.java
+                ) { task ->
+                    task.tipOfTreeBuildFile.set(
+                        project.getSupportRootFolder().resolve("docs-tip-of-tree/build.gradle")
+                    )
+                    task.projectPathProvider.set(path)
+                    task.type.set(docsType)
+                    task.cacheEvenIfNoOutputs()
+                }
+                project.addToBuildOnServer(checkDocs)
+            }
+        }
+
+        enum class DocsType(val prefix: String) {
+            STANDARD("docs"),
+            KMP("kmpDocs"),
+            SAMPLES("samples"),
+        }
+
+        /**
+         * Whether the project should have public docs. True for API-tracked projects and samples,
+         * unless opted-out with [AndroidXExtension.doNotDocumentReason]
+         */
+        fun AndroidXExtension.requiresDocs() =
+            (shouldConfigureApiTasks() || type == LibraryType.SAMPLES) &&
+                doNotDocumentReason == null
+    }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt
deleted file mode 100644
index 6c3b775..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright 2022 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.build.paparazzi
-
-import androidx.build.OperatingSystem
-import androidx.build.defaultAndroidConfig
-import androidx.build.getLibraryByName
-import androidx.build.getOperatingSystem
-import androidx.build.getSdkPath
-import androidx.build.getSupportRootFolder
-import com.android.build.gradle.BaseExtension
-import javax.inject.Inject
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE
-import org.gradle.api.artifacts.type.ArtifactTypeDefinition.JAR_TYPE
-import org.gradle.api.file.Directory
-import org.gradle.api.file.FileCollection
-import org.gradle.api.file.FileSystemOperations
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.Copy
-import org.gradle.api.tasks.PathSensitivity
-import org.gradle.api.tasks.testing.Test
-import org.gradle.kotlin.dsl.get
-import org.gradle.kotlin.dsl.register
-import org.gradle.kotlin.dsl.the
-import org.gradle.kotlin.dsl.withType
-import org.gradle.process.JavaForkOptions
-
-/** Configures screenshot testing using Paparazzi for AndroidX projects. */
-class AndroidXPaparazziImplPlugin
-@Inject
-constructor(private val fileSystemOperations: FileSystemOperations) : Plugin<Project> {
-    override fun apply(project: Project) {
-        val paparazziNative = project.createUnzippedPaparazziNativeDependency()
-        project.afterEvaluate { it.addTestUtilsDependency() }
-        project.tasks.register("updateGolden")
-        project.afterEvaluate {
-            // need to be inside of afterEvaluate because we read android.namespace
-            // ideally, we refactor to use a lazy API
-            project.tasks.withType<Test>().configureEach { it.configureTestTask(paparazziNative) }
-        }
-        project.tasks.withType<Test>().whenTaskAdded { project.registerUpdateGoldenTask(it) }
-    }
-
-    /**
-     * Add project's golden directory and the unzipped native Paparazzi location as task inputs, and
-     * set system properties for the test library to consume at runtime.
-     */
-    private fun Test.configureTestTask(paparazziNative: FileCollection) {
-        val compileSdkVersion = project.defaultAndroidConfig.compileSdk
-        val platformDirectory = project.getSdkPath().resolve("platforms/$compileSdkVersion")
-        val cachedGoldenRootDirectory = project.goldenRootDirectory
-        val cachedReportDirectory = reportDirectory
-        val android = project.the<BaseExtension>()
-        val packageName =
-            requireNotNull(android.namespace) { "android.namespace must be set for Paparazzi" }
-
-        // Attach unzipped Paparazzi native directory as a task input
-        inputs
-            .files(paparazziNative)
-            .withPathSensitivity(PathSensitivity.NONE)
-            .withPropertyName("paparazziNative")
-
-        // Attach golden directory to task inputs to invalidate tests when updating goldens
-        inputs
-            .dir(project.goldenDirectory)
-            .withPathSensitivity(PathSensitivity.RELATIVE)
-            .withPropertyName("goldenDirectory")
-
-        // Mark report directory as an output directory
-        outputs.dir(reportDirectory).withPropertyName("paparazziReportDir")
-
-        // Clean the contents of the report directory before each test run
-        doFirst {
-            fileSystemOperations.delete {
-                it.delete(cachedReportDirectory.get().asFile.listFiles())
-            }
-        }
-
-        // Set non-path system properties at configuration time, so that changes invalidate caching
-        prefixedSystemProperties(
-            "gradlePluginApplied" to "true",
-            "compileSdkVersion" to project.defaultAndroidConfig.targetSdk,
-            "resourcePackageNames" to packageName, // TODO: Transitive resource packages?
-            "modulePath" to project.modulePath,
-            "updateGoldenTask" to "${project.path}:${updateGoldenTaskName()}"
-        )
-
-        // Set the remaining system properties at execution time, after the snapshotting, so that
-        // the absolute paths don't affect caching
-        doFirst {
-            systemProperty("paparazzi.platform.data.root", paparazziNative.singleFile.canonicalPath)
-            prefixedSystemProperties(
-                "platformDir" to platformDirectory.canonicalPath,
-                "assetsDir" to ".", // TODO: Merged assets dirs? (needed for compose?)
-                "resDir" to ".", // TODO: Merged resource dirs? (needed for compose?)
-                "reportDir" to cachedReportDirectory.get().asFile.canonicalPath,
-                "goldenRootDir" to cachedGoldenRootDirectory.canonicalPath,
-            )
-        }
-    }
-
-    /** Register a copy task for moving new images to the golden directory. */
-    private fun Project.registerUpdateGoldenTask(testTask: Test) {
-        tasks.register<Copy>(testTask.updateGoldenTaskName()) {
-            dependsOn(testTask)
-
-            from(testTask.reportDirectory) {
-                include("**/*_actual.png")
-                into(goldenDirectory)
-                rename { it.removeSuffix("_actual.png") + "_paparazzi.png" }
-            }
-        }
-
-        tasks["updateGolden"].dependsOn(testTask.updateGoldenTaskName())
-    }
-
-    /** Derive updateGolden task name from a test task name. */
-    private fun Test.updateGoldenTaskName(): String {
-        return "updateGolden" + name.removePrefix("test").replaceFirstChar { it.titlecase() }
-    }
-
-    /**
-     * Configure [UnzipPaparazziNativeTransform] for the project, and add the platform-specific
-     * Paparazzi native layoutlib dependency, using the version in `libs.versions.toml`.
-     */
-    private fun Project.createUnzippedPaparazziNativeDependency(): FileCollection {
-        val platformSuffix =
-            when (val os = getOperatingSystem()) {
-                OperatingSystem.LINUX -> "LinuxX64"
-                OperatingSystem.MAC -> {
-                    val arch = System.getProperty("os.arch")
-                    if (arch.startsWith("x86", ignoreCase = true)) "MacOsX64" else "MacOsArm64"
-                }
-                else -> error("Unsupported operating system $os for Paparazzi")
-            }
-
-        dependencies.registerTransform(UnzipPaparazziNativeTransform::class.java) { spec ->
-            spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_TYPE)
-            spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, UNZIPPED_PAPARAZZI_NATIVE)
-        }
-
-        val configuration = configurations.create("paparazziNative")
-        configuration.isCanBeConsumed = false
-        configuration.dependencies.add(
-            dependencies.create(getLibraryByName("paparazziNative$platformSuffix"))
-        )
-
-        return configuration.incoming
-            .artifactView {
-                it.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, UNZIPPED_PAPARAZZI_NATIVE)
-            }
-            .files
-    }
-
-    /** The golden image directory for this project. */
-    private val Project.goldenDirectory
-        get() = goldenRootDirectory.resolve(modulePath)
-
-    /** The root of the golden image directory in a standard AndroidX checkout. */
-    private val Project.goldenRootDirectory
-        get() = getSupportRootFolder().resolve("../../golden")
-
-    /** Filesystem path for this module derived from Gradle project path. */
-    private val Project.modulePath
-        get() = path.replace(':', '/').trim('/')
-
-    /** Output directory for storing reports and images. */
-    private val Test.reportDirectory: Provider<Directory>
-        get() = project.layout.buildDirectory.dir("paparazzi/$name")
-
-    /** Add a testImplementation dependency on the wrapper test utils library. */
-    private fun Project.addTestUtilsDependency() {
-        configurations["testImplementation"]
-            .dependencies
-            .add(dependencies.create(project(TEST_UTILS_PROJECT)))
-    }
-
-    private companion object {
-        /** Package name of the test library, used to namespace system properties */
-        const val PACKAGE_NAME = "androidx.testutils.paparazzi"
-
-        /** Project path to the wrapper test utils project. */
-        const val TEST_UTILS_PROJECT = ":internal-testutils-paparazzi"
-
-        /** Artifact type attribute for unzipped Paparazzi layoutlib unzipped artifacts */
-        const val UNZIPPED_PAPARAZZI_NATIVE = "unzipped-paparazzi-native"
-
-        /** Set system properties with keys prefixed with [PACKAGE_NAME] */
-        fun JavaForkOptions.prefixedSystemProperties(vararg properties: Pair<String, Any>) {
-            properties.forEach { (name, value) -> systemProperty("$PACKAGE_NAME.$name", value) }
-        }
-    }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt b/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt
deleted file mode 100644
index 2d2c09a..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2022 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.build.paparazzi
-
-import java.util.zip.ZipInputStream
-import org.gradle.api.artifacts.transform.InputArtifact
-import org.gradle.api.artifacts.transform.TransformAction
-import org.gradle.api.artifacts.transform.TransformOutputs
-import org.gradle.api.artifacts.transform.TransformParameters.None
-import org.gradle.api.file.FileSystemLocation
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.PathSensitive
-import org.gradle.api.tasks.PathSensitivity
-import org.gradle.work.DisableCachingByDefault
-
-/**
- * Unzips one of Paparazzi's platform-specific layoutlib JAR artifacts so that Paparazzi can read
- * its contents at run time. These contain a native dynamic library and supporting resources
- * including ICU and fonts.
- */
-@DisableCachingByDefault(because = "Just an unzip task, faster to rerun locally")
-abstract class UnzipPaparazziNativeTransform : TransformAction<None> {
-    @get:PathSensitive(PathSensitivity.NAME_ONLY)
-    @get:InputArtifact
-    abstract val primaryInput: Provider<FileSystemLocation>
-
-    override fun transform(outputs: TransformOutputs) {
-        val inputFile = primaryInput.get().asFile
-        val outputDir = outputs.dir(inputFile.nameWithoutExtension).also { it.mkdirs() }
-
-        ZipInputStream(inputFile.inputStream().buffered()).use { zipInputStream ->
-            while (true) {
-                val entry = zipInputStream.nextEntry ?: break
-                val outputFile = outputDir.resolve(entry.name)
-
-                if (entry.isDirectory) {
-                    outputFile.mkdir()
-                } else {
-                    // This works because ZipInputStream resizes itself to the contents of the
-                    // last-returned entry
-                    outputFile.outputStream().buffered().use { zipInputStream.copyTo(it) }
-                }
-            }
-        }
-    }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index a0f33a6..a878dd0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -36,6 +36,7 @@
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.SkipWhenEmpty
 import org.gradle.api.tasks.TaskAction
 import org.gradle.work.DisableCachingByDefault
 
@@ -58,6 +59,14 @@
     @get:PathSensitive(PathSensitivity.RELATIVE)
     abstract val appFileCollection: ConfigurableFileCollection
 
+    /**
+     * File existence check to determine whether to run this task.
+     */
+    @get:InputFiles
+    @get:SkipWhenEmpty
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val androidTestSourceCodeCollection: ConfigurableFileCollection
+
     @get:Internal abstract val appLoader: Property<BuiltArtifactsLoader>
 
     /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index 8fc1428..d38bad3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -24,9 +24,9 @@
 import androidx.build.getFileInTestConfigDirectory
 import androidx.build.getPrivacySandboxApksDirectory
 import androidx.build.getSupportRootFolder
-import androidx.build.hasAndroidTestSourceCode
 import androidx.build.hasBenchmarkPlugin
 import androidx.build.isPresubmitBuild
+import androidx.build.multiplatformExtension
 import com.android.build.api.artifact.Artifacts
 import com.android.build.api.artifact.SingleArtifact
 import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
@@ -40,16 +40,20 @@
 import com.android.build.api.variant.Variant
 import com.android.build.gradle.BaseExtension
 import com.android.build.gradle.TestExtension
+import com.android.build.gradle.TestedExtension
 import com.android.build.gradle.internal.attributes.VariantAttr
 import com.android.build.gradle.internal.publishing.AndroidArtifacts
 import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType
 import org.gradle.api.Project
 import org.gradle.api.attributes.Usage
+import org.gradle.api.file.FileCollection
 import org.gradle.api.file.RegularFile
 import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.TaskProvider
 import org.gradle.kotlin.dsl.getByType
 import org.gradle.kotlin.dsl.named
+import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
 
 /**
  * Creates and configures the test config generation task for a project. Configuration includes
@@ -121,16 +125,12 @@
             task.hasBenchmarkPlugin.set(hasBenchmarkPlugin)
             task.testRunner.set(testRunner)
             task.testProjectPath.set(path)
+            // Skip task if getTestSourceSetsForAndroid is empty, even if
+            //  androidXExtension.deviceTests.enabled is set to true
+            task.androidTestSourceCodeCollection.from(getTestSourceSetsForAndroid())
+            task.enabled = androidXExtension.deviceTests.enabled
             AffectedModuleDetector.configureTaskGuard(task)
         }
-    // Disable xml generation for projects that have no test sources
-    // or explicitly don't want to run device tests
-    afterEvaluate {
-        val androidXExtension = extensions.getByType<AndroidXExtension>()
-        generateTestConfigurationTask.configure {
-            it.enabled = androidXExtension.deviceTests.enabled && hasAndroidTestSourceCode()
-        }
-    }
     rootProject.tasks
         .findByName(FINALIZE_TEST_CONFIGS_WITH_APKS_TASK)!!
         .dependsOn(generateTestConfigurationTask)
@@ -261,24 +261,18 @@
 }
 
 private fun getOrCreateMediaTestConfigTask(
-    project: Project,
-    isMedia2: Boolean
+    project: Project
 ): TaskProvider<GenerateMediaTestConfigurationTask> {
-    val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
     val parentProject = project.parent!!
     if (
         !parentProject.tasks
             .withType(GenerateMediaTestConfigurationTask::class.java)
             .names
-            .contains(
-                "support-$mediaPrefix-test${
-                    AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK
-                    }"
-            )
+            .contains("support-media-test${AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK}")
     ) {
         val task =
             parentProject.tasks.register(
-                "support-$mediaPrefix-test${AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK}",
+                "support-media-test${AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK}",
                 GenerateMediaTestConfigurationTask::class.java
             ) { task ->
                 AffectedModuleDetector.configureTaskGuard(task)
@@ -288,30 +282,20 @@
     } else {
         return parentProject.tasks
             .withType(GenerateMediaTestConfigurationTask::class.java)
-            .named(
-                "support-$mediaPrefix-test${
-                    AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK
-                    }"
-            )
+            .named("support-media-test${AndroidXImplPlugin.GENERATE_TEST_CONFIGURATION_TASK}")
     }
 }
 
-private fun getMediaConfigTaskPrefix(isMedia2: Boolean): String {
-    return if (isMedia2) "media2" else "media"
-}
-
 fun Project.createOrUpdateMediaTestConfigurationGenerationTask(
     variantName: String,
     artifacts: Artifacts,
     minSdk: Int,
     testRunner: String,
-    isMedia2: Boolean
 ) {
-    val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
-    val mediaTask = getOrCreateMediaTestConfigTask(this, isMedia2)
+    val mediaTask = getOrCreateMediaTestConfigTask(this)
 
     fun getJsonName(clientToT: Boolean, serviceToT: Boolean, clientTests: Boolean): String {
-        return "_${mediaPrefix}Client${
+        return "_mediaClient${
             if (clientToT) "ToT" else "Previous"
         }Service${
             if (serviceToT) "ToT" else "Previous"
@@ -403,15 +387,15 @@
                 getJsonName(clientToT = true, serviceToT = true, clientTests = false)
             )
         )
-        it.totClientApk.set(getFileInTestConfigDirectory("${mediaPrefix}ClientToT$variantName.apk"))
+        it.totClientApk.set(getFileInTestConfigDirectory("mediaClientToT$variantName.apk"))
         it.previousClientApk.set(
-            getFileInTestConfigDirectory("${mediaPrefix}ClientPrevious$variantName.apk")
+            getFileInTestConfigDirectory("mediaClientPrevious$variantName.apk")
         )
         it.totServiceApk.set(
-            getFileInTestConfigDirectory("${mediaPrefix}ServiceToT$variantName.apk")
+            getFileInTestConfigDirectory("mediaServiceToT$variantName.apk")
         )
         it.previousServiceApk.set(
-            getFileInTestConfigDirectory("${mediaPrefix}ServicePrevious$variantName.apk")
+            getFileInTestConfigDirectory("mediaServicePrevious$variantName.apk")
         )
         it.minSdk.set(minSdk)
         it.testRunner.set(testRunner)
@@ -439,22 +423,12 @@
                 return@onVariants
             }
             when {
-                path.contains("media2:media2-session:version-compat-tests:") -> {
-                    createOrUpdateMediaTestConfigurationGenerationTask(
-                        name,
-                        artifacts,
-                        baseExtension.defaultConfig.minSdk!!,
-                        baseExtension.defaultConfig.testInstrumentationRunner!!,
-                        isMedia2 = true
-                    )
-                }
                 path.contains("media:version-compat-tests:") -> {
                     createOrUpdateMediaTestConfigurationGenerationTask(
                         name,
                         artifacts,
                         baseExtension.defaultConfig.minSdk!!,
                         baseExtension.defaultConfig.testInstrumentationRunner!!,
-                        isMedia2 = false,
                     )
                 }
                 else -> {
@@ -491,3 +465,45 @@
         }
     }
 }
+
+private fun Project.getTestSourceSetsForAndroid(): List<FileCollection> {
+    val testSourceFileCollections = mutableListOf<FileCollection>()
+    // com.android.test modules keep test code in main sourceset
+    extensions.findByType(TestExtension::class.java)?.let { testExtension ->
+        testExtension.sourceSets.find { it.name == "main" }?.let { sourceSet ->
+            testSourceFileCollections.addAll(sourceSet.java.getSourceDirectoryTrees())
+        }
+        // Add kotlin-android main source set
+        extensions
+            .findByType(KotlinAndroidProjectExtension::class.java)
+            ?.sourceSets
+            ?.find { it.name == "main" }
+            ?.let { testSourceFileCollections.add(it.kotlin.sourceDirectories) }
+        // Note, don't have to add kotlin-multiplatform as it is not compatible with
+        // com.android.test modules
+    }
+
+    // Add Java androidTest source set
+    extensions
+        .findByType(TestedExtension::class.java)
+        ?.sourceSets
+        ?.find { it.name == "androidTest" }
+        ?.let { sourceSet ->
+            testSourceFileCollections.addAll(sourceSet.java.getSourceDirectoryTrees())
+        }
+
+    // Add kotlin-android androidTest source set
+    extensions
+        .findByType(KotlinAndroidProjectExtension::class.java)
+        ?.sourceSets
+        ?.find { it.name == "androidTest" }
+        ?.let { testSourceFileCollections.add(it.kotlin.sourceDirectories) }
+
+    // Add kotlin-multiplatform androidInstrumentedTest target source sets
+    multiplatformExtension?.targets
+        ?.filterIsInstance<KotlinAndroidTarget>()
+        ?.mapNotNull { it.compilations.find { it.name == "debugAndroidTest" } }
+        ?.flatMap { it.allKotlinSourceSets }
+        ?.mapTo(testSourceFileCollections) { it.kotlin.sourceDirectories }
+    return testSourceFileCollections
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt b/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
index b6c9910..877109a 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
@@ -63,8 +63,6 @@
         .dependencies
         .add(project.dependencies.create(aarAsJar))
 
-    // Added to allow the :external:paparazzi:paparazzi build to select the correct jar (not get
-    // confused by the mpp jars) when the mpp builds are enabled
     project.configurations
         .getByName(testAarsAsJars.name)
         .attributes
diff --git a/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties b/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties
deleted file mode 100644
index 0ea8b07..0000000
--- a/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright 2022 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.
-#
-
-implementation-class=androidx.build.paparazzi.AndroidXPaparazziImplPlugin
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
index 2c144b5..6890929 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
@@ -23,6 +23,7 @@
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.Usage
 import org.gradle.api.file.FileTreeElement
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.Optional
@@ -30,6 +31,7 @@
 import org.gradle.jvm.tasks.Jar
 import org.gradle.kotlin.dsl.findByType
 import org.gradle.kotlin.dsl.get
+import org.gradle.kotlin.dsl.named
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
@@ -38,6 +40,7 @@
 object BundleInsideHelper {
     val CONFIGURATION_NAME = "bundleInside"
     val REPACKAGE_TASK_NAME = "repackageBundledJars"
+
     /**
      * Creates a configuration for the users to use that will be used to bundle these dependency
      * jars inside of libs/ directory inside of the aar.
@@ -182,7 +185,7 @@
     }
 
     /**
-     * Creates a configuration for users to use that will be used bundle these dependency jars
+     * Creates a configuration for users to use that will bundle the dependency jars
      * inside of this lint check's jar. This is required because lintPublish does not currently
      * support dependencies, so instead we need to bundle any dependencies with the lint jar
      * manually. (b/182319899)
@@ -207,26 +210,17 @@
         val bundle = createBundleConfiguration()
         val compileOnly = configurations.getByName("compileOnly")
         val testImplementation = configurations.getByName("testImplementation")
-        // bundleInside dependencies should be included as compileOnly as well
-        compileOnly.setExtendsFrom(listOf(bundle))
-        testImplementation.setExtendsFrom(listOf(bundle))
 
-        tasks.named("jar").configure { jarTask ->
-            jarTask as Jar
-            jarTask.dependsOn(bundle)
-            jarTask.from({
-                bundle
-                    // The stdlib is already bundled with lint, so no need to include it manually
-                    // in the lint.jar if any dependencies here depend on it
-                    .filter { !it.name.contains("kotlin-stdlib") }
-                    .map { file ->
-                        if (file.isDirectory) {
-                            file
-                        } else {
-                            zipTree(file)
-                        }
-                    }
-            })
+        compileOnly.extendsFrom(bundle)
+        testImplementation.extendsFrom(bundle)
+
+        val extractTask = tasks.register("extractBundleJars", ExtractJarTask::class.java) { task ->
+            task.description = "Extracts all JARs from the bundle configuration."
+            task.jarFiles.setFrom(bundle.incoming.artifactView { }.files)
+            task.outputDir.set(layout.buildDirectory.dir("extractedJars"))
+        }
+        tasks.named("jar", Jar::class.java).configure {
+            it.from(extractTask.flatMap { it.outputDir })
         }
     }
 
@@ -254,8 +248,15 @@
     }
 
     private fun Project.createBundleConfiguration(): Configuration {
-        val bundle = configurations.create(CONFIGURATION_NAME)
-        bundle.isCanBeConsumed = false
+        val bundle = configurations.create(CONFIGURATION_NAME) {
+            it.attributes {
+                   it.attribute(
+                         Usage.USAGE_ATTRIBUTE,
+                        objects.named<Usage>(Usage.JAVA_RUNTIME)
+                   )
+            }
+            it.isCanBeConsumed = false
+        }
         return bundle
     }
 
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/ExtractJarTask.kt b/buildSrc/public/src/main/kotlin/androidx/build/ExtractJarTask.kt
new file mode 100644
index 0000000..a452843
--- /dev/null
+++ b/buildSrc/public/src/main/kotlin/androidx/build/ExtractJarTask.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.build
+
+import java.io.File
+import java.util.zip.ZipFile
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.work.DisableCachingByDefault
+
+/**
+ * A task designed to extract the contents of one or more JAR files. This task accepts a collection
+ * of JAR files as input and extracts their contents into a specified output directory.
+ * Each JAR file is processed individually, and its contents are placed directly into the output
+ * directory, maintaining the internal structure of the JAR files.
+ */
+@DisableCachingByDefault
+abstract class ExtractJarTask : DefaultTask() {
+
+    @get:InputFiles
+    abstract val jarFiles: ConfigurableFileCollection
+
+    @get:OutputDirectory
+    abstract val outputDir: DirectoryProperty
+
+    @TaskAction
+    fun extractJars() {
+        val outputDirectory = outputDir.get().asFile
+        if (!outputDirectory.exists()) {
+            outputDirectory.mkdirs()
+        }
+
+        jarFiles.forEach { jarFile ->
+            ZipFile(jarFile).use { zip ->
+                zip.entries().asSequence().forEach { entry ->
+                    val outputFile = File(outputDirectory, entry.name)
+                    if (!entry.isDirectory) {
+                        outputFile.parentFile.mkdirs()
+                        zip.getInputStream(entry).use { input ->
+                            outputFile.outputStream().use { output ->
+                                input.copyTo(output)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/buildSrc/settingsScripts/project-dependency-graph.groovy b/buildSrc/settingsScripts/project-dependency-graph.groovy
index d74e0d8..34e95d3 100644
--- a/buildSrc/settingsScripts/project-dependency-graph.groovy
+++ b/buildSrc/settingsScripts/project-dependency-graph.groovy
@@ -239,10 +239,6 @@
                     links.add(":compose:compiler:compiler")
                     links.add(":compose:lint:internal-lint-checks")
                 }
-                if (paparazziPlugin.matcher(line).find()) {
-                    links.add(":test:screenshot:screenshot-proto")
-                    links.add(":internal-testutils-paparazzi")
-                }
                 if (iconGenerator.matcher(line).find()) {
                     links.add(":compose:material:material:icons:generator")
                 }
@@ -267,7 +263,6 @@
     private static Pattern multilineProjectReference = Pattern.compile("project\\(\$")
     private static Pattern inspection = Pattern.compile("packageInspector\\(project, \"(.*)\"\\)")
     private static Pattern composePlugin = Pattern.compile("id\\(\"AndroidXComposePlugin\"\\)")
-    private static Pattern paparazziPlugin = Pattern.compile("id\\(\"AndroidXPaparazziPlugin\"\\)")
     private static Pattern iconGenerator = Pattern.compile("IconGenerationTask\\.register")
 }
 
diff --git a/busytown/androidx_multiplatform_mac.sh b/busytown/androidx_multiplatform_mac.sh
index 8efc3de..43e7427 100755
--- a/busytown/androidx_multiplatform_mac.sh
+++ b/busytown/androidx_multiplatform_mac.sh
@@ -1,28 +1,22 @@
 #!/bin/bash
 set -e
-cd "$(dirname $0)"
+cd "$(dirname "$0")"
 
-# Builds all projects that support KMP except for Compose-specific projects which are already
-# covered by androidx_compose_multiplatform.sh
-
+# Builds all projects that support KMP except for Compose-specific projects
 # Must be run on Mac
-
 export ANDROIDX_PROJECTS=INFRAROGUE   # TODO: Switch from `INFRAROGUE` to `KMP`
 
 # This target is for testing that clean builds work correctly
-# We disable the remote cache for this target unless it was already enabled
-if [ "$USE_ANDROIDX_REMOTE_BUILD_CACHE" == "" ]; then
-  export USE_ANDROIDX_REMOTE_BUILD_CACHE=false
-fi
+export USE_ANDROIDX_REMOTE_BUILD_CACHE=false
 
 sharedArgs="--no-configuration-cache -Pandroidx.constraints=true $*"
 # Setup simulators
 impl/androidx-native-mac-simulator-setup.sh
 
-impl/build.sh buildOnServer listTaskOutputs createAllArchives $sharedArgs
+impl/build.sh buildOnServer listTaskOutputs createAllArchives "$sharedArgs"
 
 # run a separate createAllArchives task to prepare a repository
 # folder in DIST.
 # This cannot be merged with the buildOnServer run because
 # snapshot version is not a proper release version.
-DIST_DIR=$DIST_DIR/snapshots SNAPSHOT=true impl/build.sh createAllArchives $sharedArgs
+DIST_DIR=$DIST_DIR/snapshots SNAPSHOT=true impl/build.sh createAllArchives "$sharedArgs"
diff --git a/busytown/androidx_multiplatform_mac_arm64.sh b/busytown/androidx_multiplatform_mac_arm64.sh
index 484cf900..f907a83 100755
--- a/busytown/androidx_multiplatform_mac_arm64.sh
+++ b/busytown/androidx_multiplatform_mac_arm64.sh
@@ -1,7 +1,15 @@
 #!/bin/bash
 set -e
+cd "$(dirname "$0")"
+
+# Builds all projects that support KMP except for Compose-specific projects
+# Must be run on Mac
+export ANDROIDX_PROJECTS=INFRAROGUE   # TODO: Switch from `INFRAROGUE` to `KMP`
 
 export USE_ANDROIDX_REMOTE_BUILD_CACHE=gcp
 
-SCRIPT_DIR="$(cd $(dirname $0) && pwd)"
-$SCRIPT_DIR/androidx_multiplatform_mac.sh -Pandroidx.lowMemory
+sharedArgs="--no-configuration-cache -Pandroidx.constraints=true -Pandroidx.lowMemory $*"
+# Setup simulators
+impl/androidx-native-mac-simulator-setup.sh
+
+impl/build.sh buildOnServer listTaskOutputs createAllArchives "$sharedArgs"
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
index 3a6ed4f..474fd2e 100644
--- a/camera/camera-camera2-pipe-integration/lint-baseline.xml
+++ b/camera/camera-camera2-pipe-integration/lint-baseline.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):&#xA;&#xA;"
+        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="class AspectRatioLegacyApi21Quirk : Quirk {"
         errorLine2="      ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -12,7 +12,7 @@
 
     <issue
         id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):&#xA;&#xA;"
+        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="class ExcludedSupportedSizesQuirk : Quirk {"
         errorLine2="      ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -21,7 +21,7 @@
 
     <issue
         id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):&#xA;&#xA;"
+        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="class ExtraCroppingQuirk : Quirk {"
         errorLine2="      ~~~~~~~~~~~~~~~~~~">
         <location
@@ -30,7 +30,7 @@
 
     <issue
         id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):&#xA;&#xA;"
+        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="class ExtraSupportedSurfaceCombinationsQuirk : Quirk {"
         errorLine2="      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -39,7 +39,7 @@
 
     <issue
         id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):&#xA;&#xA;"
+        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="class Nexus4AndroidLTargetAspectRatioQuirk : Quirk {"
         errorLine2="      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index dea04ab..da840e51 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -149,6 +149,7 @@
                 streams = useCaseCameraGraphConfig.getStreamIdsFromSurfaces(
                     sessionConfig.repeatingCaptureConfig.surfaces
                 ),
+                sessionConfig = sessionConfig,
             )
         }
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
index 86eda1a..8278652 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
@@ -44,12 +44,12 @@
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 class RequestProcessorAdapter(
     private val useCaseGraphConfig: UseCaseGraphConfig,
-    private val sessionConfig: SessionConfig?,
     private val processorSurfaces: List<SessionProcessorSurface>,
     private val scope: CoroutineScope,
 ) : RequestProcessor {
     private val coroutineMutex = CoroutineMutex()
     private val sequenceIds = atomic(0)
+    internal var sessionConfig: SessionConfig? = null
 
     private class RequestProcessorCallbackAdapter(
         private val callback: RequestProcessor.Callback,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
index 33074e4..9e7daeb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
@@ -20,10 +20,12 @@
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
 import android.view.Surface
+import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraStream
 import androidx.camera.camera2.pipe.compat.CameraPipeKeys
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
@@ -37,7 +39,6 @@
 import androidx.camera.core.impl.DeferrableSurfaces
 import androidx.camera.core.impl.OutputSurface
 import androidx.camera.core.impl.OutputSurfaceConfiguration
-import androidx.camera.core.impl.RequestProcessor
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
@@ -57,11 +58,32 @@
     private val cameraInfoInternal: CameraInfoInternal,
     private val scope: CoroutineScope,
 ) {
+    private val lock = Any()
+
+    @GuardedBy("lock")
+    private var captureSessionStarted = false
+
+    @GuardedBy("lock")
     private var sessionOptions = CaptureRequestOptions.Builder().build()
+
+    @GuardedBy("lock")
     private var stillCaptureOptions = CaptureRequestOptions.Builder().build()
+
+    @GuardedBy("lock")
+    private var requestProcessor: RequestProcessorAdapter? = null
+
+    @GuardedBy("lock")
+    private var pendingCaptureConfigs: List<CaptureConfig>? = null
+
+    @GuardedBy("lock")
+    private var pendingCaptureCallbacks: List<CaptureCallback>? = null
+
+    @GuardedBy("lock")
     internal var sessionConfig: SessionConfig? = null
-        set(value) {
+        set(value) = synchronized(lock) {
             field = checkNotNull(value)
+            if (!captureSessionStarted) return
+            checkNotNull(requestProcessor).sessionConfig = value
             sessionOptions =
                 CaptureRequestOptions.Builder.from(value.implementationOptions).build()
             updateOptions()
@@ -170,19 +192,56 @@
         useCaseManager.tryResumeUseCaseManager(useCaseManagerConfig)
     }
 
-    internal fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
+    internal fun onCaptureSessionStart(requestProcessor: RequestProcessorAdapter) {
+        var captureConfigsToIssue: List<CaptureConfig>?
+        var captureCallbacksToIssue: List<CaptureCallback>?
+        synchronized(lock) {
+            check(!captureSessionStarted)
+            requestProcessor.sessionConfig = sessionConfig
+            this.requestProcessor = requestProcessor
+            captureSessionStarted = true
+
+            captureConfigsToIssue = pendingCaptureConfigs
+            captureCallbacksToIssue = pendingCaptureCallbacks
+            pendingCaptureConfigs = null
+            pendingCaptureCallbacks = null
+        }
+        Log.debug { "Invoking SessionProcessor#onCaptureSessionStart" }
         sessionProcessor.onCaptureSessionStart(requestProcessor)
+        startRepeating(object : CaptureCallback {})
+        captureConfigsToIssue?.let { captureConfigs ->
+            submitCaptureConfigs(captureConfigs, checkNotNull(captureCallbacksToIssue))
+        }
     }
 
     internal fun startRepeating(captureCallback: CaptureCallback) {
+        synchronized(lock) {
+            if (!captureSessionStarted) return
+        }
+        Log.debug { "Invoking SessionProcessor#startRepeating" }
         sessionProcessor.startRepeating(captureCallback)
     }
 
+    internal fun stopRepeating() {
+        synchronized(lock) {
+            if (!captureSessionStarted) return
+        }
+        Log.debug { "Invoking SessionProcessor#stopRepeating" }
+        sessionProcessor.stopRepeating()
+    }
+
     internal fun submitCaptureConfigs(
         captureConfigs: List<CaptureConfig>,
         captureCallbacks: List<CaptureCallback>,
     ) {
         check(captureConfigs.size == captureCallbacks.size)
+        synchronized(lock) {
+            if (!captureSessionStarted) {
+                pendingCaptureConfigs = captureConfigs
+                pendingCaptureCallbacks = captureCallbacks
+                return
+            }
+        }
         for ((config, callback) in captureConfigs.zip(captureCallbacks)) {
             if (config.templateType == CameraDevice.TEMPLATE_STILL_CAPTURE) {
                 val builder = CaptureRequestOptions.Builder.from(config.implementationOptions)
@@ -203,8 +262,10 @@
                         )!!.toByte()
                     )
                 }
-                stillCaptureOptions = builder.build()
-                updateOptions()
+                synchronized(lock) {
+                    stillCaptureOptions = builder.build()
+                    updateOptions()
+                }
                 Log.debug { "Invoking SessionProcessor.startCapture()" }
                 sessionProcessor.startCapture(config.isPostviewEnabled, callback)
             } else {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index e796d3f..5710e9c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -22,15 +22,18 @@
 import android.hardware.camera2.CaptureRequest
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
 import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.core.Log.debug
+import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.SessionProcessorSurface
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -101,13 +104,6 @@
         set(value) {
             field = value
 
-            if (sessionProcessorManager != null) {
-                sessionConfigAdapter.getValidSessionConfigOrNull()?.let {
-                    requestControl.setSessionConfigAsync(it)
-                }
-                return
-            }
-
             // Note: This may be called with the same set of values that was previously set. This
             // is used as a signal to indicate the properties of the UseCase may have changed.
             SessionConfigAdapter(value).getValidSessionConfigOrNull()?.let {
@@ -140,6 +136,28 @@
                     if (closed.value && it is GraphStateStopped) {
                         cancel()
                     }
+
+                    // TODO: b/323614735: Technically our RequestProcessor implementation could be
+                    //   given to the SessionProcessor through onCaptureSessionStart after the
+                    //   new set of configurations (CameraGraph) is created. However, this seems to
+                    //   be causing occasional SIGBUS on the Android platform level. Delaying this
+                    //   seems to be mitigating the issue, but does result in overhead in startup
+                    //   latencies. Move this back to UseCaseManager once we understand more about
+                    //   the situation.
+                    if (sessionProcessorManager != null && it is GraphStateStarted) {
+                        val sessionProcessorSurfaces =
+                            sessionConfigAdapter.deferrableSurfaces.map {
+                                it as SessionProcessorSurface
+                            }
+                        val requestProcessorAdapter = RequestProcessorAdapter(
+                            useCaseGraphConfig,
+                            sessionProcessorSurfaces,
+                            threads.scope,
+                        )
+                        sessionProcessorManager.onCaptureSessionStart(
+                            requestProcessorAdapter
+                        )
+                    }
                 }
             }
         }
@@ -199,6 +217,7 @@
             streams = useCaseGraphConfig.getStreamIdsFromSurfaces(
                 sessionConfig.repeatingCaptureConfig.surfaces
             ),
+            sessionConfig = sessionConfig,
         )
     } ?: canceledResult
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index a0c8e87..abe5af1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -41,6 +41,7 @@
 import androidx.camera.core.impl.CaptureConfig.TEMPLATE_TYPE_NONE
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.MutableTagBundle
+import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.TagBundle
 import dagger.Binds
 import dagger.Module
@@ -125,7 +126,8 @@
         tags: Map<String, Any> = emptyMap(),
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
-        listeners: Set<Request.Listener> = emptySet()
+        listeners: Set<Request.Listener> = emptySet(),
+        sessionConfig: SessionConfig? = null,
     ): Deferred<Unit>
 
     // 3A
@@ -200,7 +202,8 @@
         tags: Map<String, Any>,
         streams: Set<StreamId>?,
         template: RequestTemplate?,
-        listeners: Set<Request.Listener>
+        listeners: Set<Request.Listener>,
+        sessionConfig: SessionConfig?,
     ): Deferred<Unit> = runIfNotClosed {
         synchronized(lock) {
             debug { "[$type] Set config: ${config?.toParameters()}" }
@@ -217,6 +220,7 @@
             infoBundleMap.merge()
         }.updateCameraStateAsync(
             streams = streams,
+            sessionConfig = sessionConfig,
         )
     } ?: canceledResult
 
@@ -352,7 +356,10 @@
             }
         }
 
-    private fun InfoBundle.updateCameraStateAsync(streams: Set<StreamId>? = null): Deferred<Unit> =
+    private fun InfoBundle.updateCameraStateAsync(
+        streams: Set<StreamId>? = null,
+        sessionConfig: SessionConfig? = null,
+    ): Deferred<Unit> =
         runIfNotClosed {
             capturePipeline.template =
                 if (template != null && template!!.value != TEMPLATE_TYPE_NONE) {
@@ -369,6 +376,7 @@
                 streams = streams,
                 template = template,
                 listeners = listeners,
+                sessionConfig = sessionConfig,
             )
         } ?: canceledResult
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index 0737345..2fe0e04 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -36,9 +36,11 @@
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
+import androidx.camera.core.Preview
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
 import androidx.camera.core.impl.TagBundle
+import androidx.camera.core.streamsharing.StreamSharing
 import javax.inject.Inject
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CancellationException
@@ -96,6 +98,9 @@
     @GuardedBy("lock")
     private var currentTemplate: RequestTemplate? = null
 
+    @GuardedBy("lock")
+    private var currentSessionConfig: SessionConfig? = null
+
     private val requestListener = RequestListener()
 
     /**
@@ -119,6 +124,7 @@
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
         listeners: Set<Request.Listener>? = null,
+        sessionConfig: SessionConfig? = null,
     ): Deferred<Unit> {
         val result: Deferred<Unit>
         synchronized(lock) {
@@ -139,7 +145,7 @@
             updateState(
                 parameters, appendParameters, internalParameters,
                 appendInternalParameters, streams, template,
-                listeners
+                listeners, sessionConfig
             )
 
             if (updateSignal == null) {
@@ -198,7 +204,8 @@
         appendInternalParameters: Boolean = true,
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
-        listeners: Set<Request.Listener>? = null
+        listeners: Set<Request.Listener>? = null,
+        sessionConfig: SessionConfig? = null,
     ) {
         // TODO: Consider if this should detect changes and only invoke an update if state has
         //  actually changed.
@@ -226,6 +233,9 @@
             currentListeners.clear()
             currentListeners.addAll(listeners)
         }
+        if (sessionConfig != null) {
+            currentSessionConfig = sessionConfig
+        }
     }
 
     /**
@@ -235,6 +245,11 @@
     fun tryStartRepeating() = submitLatest()
 
     private fun submitLatest() {
+        if (sessionProcessorManager != null) {
+            submitLatestWithSessionProcessor()
+            return
+        }
+
         // Update the cameraGraph with the most recent set of values.
         // Since acquireSession is a suspending function, it's possible that subsequent updates
         // can occur while waiting for the acquireSession call to complete. If this happens,
@@ -242,7 +257,6 @@
         // synchronously with the latest values. The startRepeating/stopRepeating call happens
         // outside of the synchronized block to avoid holding a lock while updating the camera
         // state.
-
         threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
             val result: CompletableDeferred<Unit>?
             val request: Request?
@@ -285,26 +299,7 @@
                             }
                         }
                         Log.debug { "Update RepeatingRequest: $request" }
-                        if (sessionProcessorManager != null) {
-                            val sessionConfig = SessionConfig.Builder().apply {
-                                request.template?.let { setTemplateType(it.value) }
-                                setImplementationOptions(Camera2ImplConfig.Builder().apply {
-                                    for ((key, value) in request.parameters) {
-                                        setCaptureRequestOptionWithType(key, value)
-                                    }
-                                }.build())
-                                currentInternalParameters[CAMERAX_TAG_BUNDLE]?.let {
-                                    val tagBundleMap = (it as TagBundle).toMap()
-                                    for ((tag, value) in tagBundleMap) {
-                                        addTag(tag, value)
-                                    }
-                                }
-                            }.build()
-                            sessionProcessorManager.sessionConfig = sessionConfig
-                            sessionProcessorManager.startRepeating(object : CaptureCallback {})
-                        } else {
-                            it.startRepeating(request)
-                        }
+                        it.startRepeating(request)
                         it.update3A(request.parameters)
                     }
                 }
@@ -320,6 +315,52 @@
         }
     }
 
+    private fun submitLatestWithSessionProcessor() {
+        checkNotNull(sessionProcessorManager)
+        synchronized(lock) {
+            updating = false
+            val signal = updateSignal
+            updateSignal = null
+
+            if (currentSessionConfig == null) {
+                signal?.complete(Unit)
+                return
+            }
+
+            // Here we're intentionally building a new SessionConfig. Various request parameters,
+            // such as zoom or 3A are directly translated to corresponding CameraPipe types and
+            // APIs. As such, we need to build a new, "combined" SessionConfig that has these
+            // updated request parameters set. Otherwise, certain settings like zoom would be
+            // disregarded.
+            SessionConfig.Builder().apply {
+                currentTemplate?.let { setTemplateType(it.value) }
+                setImplementationOptions(Camera2ImplConfig.Builder().apply {
+                    for ((key, value) in currentParameters) {
+                        setCaptureRequestOptionWithType(key, value)
+                    }
+                }.build())
+                currentInternalParameters[CAMERAX_TAG_BUNDLE]?.let {
+                    val tagBundleMap = (it as TagBundle).toMap()
+                    for ((tag, value) in tagBundleMap) {
+                        addTag(tag, value)
+                    }
+                }
+            }.build().also { sessionConfig ->
+                sessionProcessorManager.sessionConfig = sessionConfig
+            }
+
+            if (currentSessionConfig!!.repeatingCaptureConfig.surfaces.any {
+                    it.containerClass == Preview::class.java ||
+                        it.containerClass == StreamSharing::class.java
+                }) {
+                sessionProcessorManager.startRepeating(object : CaptureCallback {})
+            } else {
+                sessionProcessorManager.stopRepeating()
+            }
+            signal?.complete(Unit)
+        }
+    }
+
     private fun CameraGraph.Session.update3A(parameters: Map<CaptureRequest.Key<*>, Any>?) {
         val aeMode = parameters.getIntOrNull(CaptureRequest.CONTROL_AE_MODE)?.let {
             AeMode.fromIntOrNull(it)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 8da5485..a2447e6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -36,7 +36,6 @@
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.EncoderProfilesProviderAdapter
-import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SupportedSurfaceCombination
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
@@ -62,7 +61,6 @@
 import androidx.camera.core.impl.SessionConfig.OutputConfig.SURFACE_GROUP_ID_NONE
 import androidx.camera.core.impl.SessionConfig.ValidatingBuilder
 import androidx.camera.core.impl.SessionProcessor
-import androidx.camera.core.impl.SessionProcessorSurface
 import androidx.camera.core.impl.stabilization.StabilizationMode
 import javax.inject.Inject
 import javax.inject.Provider
@@ -125,14 +123,6 @@
         }
         set(value) = synchronized(lock) {
             field = value
-            // Only create the SessionProcessorManager when we have a SessionProcessor set.
-            if (field != null) {
-                sessionProcessorManager = SessionProcessorManager(
-                    field!!,
-                    cameraInfoInternal.get(),
-                    useCaseThreads.get().scope
-                )
-            }
         }
 
     @GuardedBy("lock")
@@ -339,6 +329,7 @@
                 }
             }
         }
+        sessionProcessorManager = null
 
         // Update list of active useCases
         if (useCases.isEmpty()) {
@@ -351,7 +342,13 @@
 
         if (sessionProcessor != null) {
             Log.debug { "Setting up UseCaseManager with SessionProcessorManager" }
-            checkNotNull(sessionProcessorManager).initialize(this, useCases)
+            sessionProcessorManager = SessionProcessorManager(
+                sessionProcessor!!,
+                cameraInfoInternal.get(),
+                useCaseThreads.get().scope,
+            ).also {
+                it.initialize(this, useCases)
+            }
             return
         } else {
             val sessionConfigAdapter = SessionConfigAdapter(useCases)
@@ -390,13 +387,7 @@
         val sessionProcessorEnabled =
             useCaseManagerConfig.sessionConfigAdapter.isSessionProcessorEnabled
         with(useCaseManagerConfig) {
-            var sessionProcessorManager: SessionProcessorManager? = null
             if (sessionProcessorEnabled) {
-                sessionProcessorManager = SessionProcessorManager(
-                    checkNotNull(sessionProcessor),
-                    cameraInfoInternal.get(),
-                    useCaseThreads.get().scope,
-                )
                 for ((streamConfig, deferrableSurface) in streamConfigMap) {
                     cameraGraph.streams[streamConfig]?.let {
                         cameraGraph.setSurface(it.id, deferrableSurface.surface.get())
@@ -421,19 +412,6 @@
                 control.useCaseCamera = camera
             }
 
-            if (sessionProcessorEnabled) {
-                val sessionProcessorSurfaces =
-                    sessionConfigAdapter.deferrableSurfaces.map {
-                        it as SessionProcessorSurface
-                    }
-                val requestProcessorAdapter = RequestProcessorAdapter(
-                    useCaseGraphConfig!!,
-                    sessionConfigAdapter.getValidSessionConfigOrNull(),
-                    sessionProcessorSurfaces,
-                    useCaseThreads.get().scope,
-                )
-                checkNotNull(sessionProcessorManager).onCaptureSessionStart(requestProcessorAdapter)
-            }
             camera?.setActiveResumeMode(activeResumeEnabled)
 
             refreshRunningUseCases()
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
index 4b86a82..bde75b0 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
@@ -132,10 +132,11 @@
 
         requestProcessorAdapter = RequestProcessorAdapter(
             useCaseGraphConfig,
-            fakeSessionConfig,
             sessionProcessorSurfaces,
             scope,
-        )
+        ).apply {
+            sessionConfig = fakeSessionConfig
+        }
         scope.advanceUntilIdle()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
index 1e506bc..db12111 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
@@ -23,6 +23,7 @@
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.FakeTestUseCase
+import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.TestDeferrableSurface
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
@@ -120,7 +121,7 @@
         }
 
         override fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
-            TODO("Not yet implemented")
+            Log.debug { "$this#onCaptureSessionStart" }
         }
 
         override fun onCaptureSessionEnd() {
@@ -128,11 +129,12 @@
         }
 
         override fun startRepeating(callback: CaptureCallback): Int {
-            TODO("Not yet implemented")
+            Log.debug { "$this#startRepeating" }
+            return 0
         }
 
         override fun stopRepeating() {
-            TODO("Not yet implemented")
+            Log.debug { "$this#stopRepeating" }
         }
 
         override fun startCapture(
@@ -229,6 +231,9 @@
         ).join()
         sessionProcessorManager.sessionConfig = SessionConfig.Builder().build()
 
+        val mockRequestProcessorAdapter: RequestProcessorAdapter = mock()
+        sessionProcessorManager.onCaptureSessionStart(mockRequestProcessorAdapter)
+
         val jpegRotation = 90
         val jpegQuality = 95
         val captureConfig = CaptureConfig.Builder().apply {
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index 3f60f25..bb21a18 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -40,6 +40,7 @@
 import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.SessionConfig
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
@@ -110,7 +111,8 @@
         tags: Map<String, Any>,
         streams: Set<StreamId>?,
         template: RequestTemplate?,
-        listeners: Set<Request.Listener>
+        listeners: Set<Request.Listener>,
+        sessionConfig: SessionConfig?,
     ): Deferred<Unit> {
         setConfigCalls.add(RequestParameters(type, config, tags))
         return CompletableDeferred(Unit)
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
index 52325db..433564a 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
@@ -86,6 +86,25 @@
         return cameraController
     }
 
+    override fun prewarm(cameraId: CameraId) {
+        _cameraControllers.find { it.cameraId == cameraId }?.simulateCameraStarted()
+    }
+
+    override fun disconnect(cameraId: CameraId) {
+        _cameraControllers.find { it.cameraId == cameraId }?.simulateCameraStopped()
+    }
+
+    override fun disconnectAsync(cameraId: CameraId): Deferred<Unit> {
+        _cameraControllers.find { it.cameraId == cameraId }?.simulateCameraStopped()
+        return CompletableDeferred(Unit)
+    }
+
+    override fun disconnectAll() {
+        _cameraControllers.forEach {
+            it.simulateCameraStopped()
+        }
+    }
+
     companion object {
         val FAKE_CAMERA_BACKEND_ID =
             CameraBackendId("androidx.camera.camera2.pipe.testing.FakeCameraBackend")
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraDevices.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraDevices.kt
index 171f072..224a224 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraDevices.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraDevices.kt
@@ -17,10 +17,12 @@
 package androidx.camera.camera2.pipe.testing
 
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraBackend
 import androidx.camera.camera2.pipe.CameraBackendId
 import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
+import kotlinx.coroutines.Deferred
 
 /**
  * This provides a fake implementation of [CameraDevices] for tests with a fixed list of Cameras.
@@ -31,10 +33,16 @@
     private val concurrentCameraBackendIds: Set<Set<CameraBackendId>>,
     private val cameraMetadataMap: Map<CameraBackendId, List<CameraMetadata>>
 ) : CameraDevices {
+    private val cameraBackends: Map<CameraBackendId, CameraBackend>
+
     init {
         check(cameraMetadataMap.containsKey(defaultCameraBackendId)) {
             "FakeCameraDevices must include $defaultCameraBackendId"
         }
+
+        cameraBackends = cameraMetadataMap.mapValues { entry ->
+            FakeCameraBackend(entry.value.associateBy { it.camera })
+        }
     }
 
     override suspend fun getCameraIds(cameraBackendId: CameraBackendId?): List<CameraId>? =
@@ -51,8 +59,8 @@
 
     override fun awaitConcurrentCameraIds(cameraBackendId: CameraBackendId?): Set<Set<CameraId>> {
         return concurrentCameraBackendIds.map { concurrentCameraIds ->
-            concurrentCameraIds.map {
-                    cameraId -> CameraId.fromCamera2Id(cameraId.value)
+            concurrentCameraIds.map { cameraId ->
+                CameraId.fromCamera2Id(cameraId.value)
             }.toSet()
         }.toSet()
     }
@@ -70,6 +78,34 @@
         return cameraMetadataMap[backendId]?.firstOrNull { it.camera == cameraId }
     }
 
+    override fun prewarm(cameraId: CameraId, cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.prewarm(cameraId)
+    }
+
+    override fun disconnect(cameraId: CameraId, cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.disconnect(cameraId)
+    }
+
+    override fun disconnectAsync(
+        cameraId: CameraId,
+        cameraBackendId: CameraBackendId?
+    ): Deferred<Unit> {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        return cameraBackend.disconnectAsync(cameraId)
+    }
+
+    override fun disconnectAll(cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.disconnectAll()
+    }
+
+    override fun disconnectAllAsync(cameraBackendId: CameraBackendId?): Deferred<Unit> {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        return cameraBackend.disconnectAllAsync()
+    }
+
     @Deprecated(
         "findAll() is not able to specify a specific CameraBackendId to query.",
         replaceWith = ReplaceWith("awaitCameraIds"),
@@ -99,4 +135,9 @@
     )
     override fun awaitMetadata(camera: CameraId): CameraMetadata =
         checkNotNull(awaitCameraMetadata(camera))
+
+    private fun getCameraBackend(cameraBackendId: CameraBackendId?): CameraBackend {
+        val backendId = cameraBackendId ?: defaultCameraBackendId
+        return checkNotNull(cameraBackends[backendId]) { "Failed to load CameraBackend $backendId" }
+    }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 699f614..997ad8f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -128,6 +128,23 @@
         graphListener: GraphListener,
         streamGraph: StreamGraph
     ): CameraController
+
+    /** Connects and starts the underlying camera */
+    fun prewarm(cameraId: CameraId)
+
+    /** Disconnects the underlying camera.*/
+    fun disconnect(cameraId: CameraId)
+
+    /**
+     * Disconnects the underlying camera. Once the connection is closed, the returned [Deferred]
+     * should be completed.
+     */
+    fun disconnectAsync(cameraId: CameraId): Deferred<Unit>
+
+    /**
+     * Disconnects all active Cameras.
+     */
+    fun disconnectAll()
 }
 
 /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
index 59c1012..76c367db 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
@@ -21,6 +21,7 @@
 
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
+import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flow
 
@@ -76,6 +77,39 @@
     ): CameraMetadata?
 
     /**
+     * Opens the camera device indicated by the cameraId, so that any subsequent open calls will
+     * potentially have a better latency.
+     */
+    fun prewarm(cameraId: CameraId, cameraBackendId: CameraBackendId? = null)
+
+    /**
+     * Non blocking operation that disconnects the underlying active Camera.
+     */
+    fun disconnect(cameraId: CameraId, cameraBackendId: CameraBackendId? = null)
+
+    /**
+     * Disconnects the underlying active Camera. Once fully closed,
+     * the returned [Deferred] should be completed. It is synchronous with the other operations
+     * within this class.
+     */
+    fun disconnectAsync(
+        cameraId: CameraId,
+        cameraBackendId: CameraBackendId? = null
+    ): Deferred<Unit>
+
+    /**
+     * Non blocking operation that disconnects all active Cameras.
+     */
+    fun disconnectAll(cameraBackendId: CameraBackendId? = null)
+
+    /**
+     * Non blocking operation that disconnects all active Cameras. Once all connections are fully
+     * closed, the returned [Deferred] should be completed. It is synchronous with the other
+     * operations within this class.
+     */
+    fun disconnectAllAsync(cameraBackendId: CameraBackendId? = null): Deferred<Unit>
+
+    /**
      * Iterate and return a list of CameraId's on the device that are capable of being opened. Some
      * camera devices may be hidden or un-openable if they are included as part of a logical camera
      * group.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
index 36d8450..34e1eaa 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
@@ -39,8 +39,7 @@
 /** This is the default [CameraBackend] implementation for CameraPipe based on Camera2. */
 @RequiresApi(21)
 internal class Camera2Backend
-@Inject
-constructor(
+@Inject constructor(
     private val threads: Threads,
     private val camera2DeviceCache: Camera2DeviceCache,
     private val camera2MetadataCache: Camera2MetadataCache,
@@ -65,11 +64,26 @@
     override fun awaitCameraMetadata(cameraId: CameraId): CameraMetadata =
         camera2MetadataCache.awaitCameraMetadata(cameraId)
 
+    override fun disconnect(cameraId: CameraId) {
+        virtualCameraManager.close(cameraId)
+    }
+
+    override fun disconnectAsync(cameraId: CameraId): Deferred<Unit> {
+        TODO(
+            "b/324142928 - Add support in VirtualCameraManager for closing a camera " +
+                "with a deferred result."
+        )
+    }
+
+    override fun disconnectAll() {
+        return virtualCameraManager.closeAll()
+    }
+
     override fun disconnectAllAsync(): Deferred<Unit> {
-        // TODO: VirtualCameraManager needs to be extended to support a suspendable future that can
-        //   be used to wait until close has been called on all camera devices.
-        virtualCameraManager.closeAll()
-        return CompletableDeferred(Unit)
+        TODO(
+            "b/324142928 - Add support in VirtualCameraManager for closing a camera " +
+                "with a deferred result."
+        )
     }
 
     override fun shutdownAsync(): Deferred<Unit> {
@@ -86,16 +100,17 @@
         streamGraph: StreamGraph
     ): CameraController {
         // Use Dagger to create the camera2 controller component, then create the CameraController.
-        val cameraControllerComponent =
-            camera2CameraControllerComponent
-                .camera2ControllerConfig(
-                    Camera2ControllerConfig(
-                        this, graphConfig, graphListener, streamGraph as StreamGraphImpl
-                    )
-                )
-                .build()
+        val cameraControllerComponent = camera2CameraControllerComponent.camera2ControllerConfig(
+            Camera2ControllerConfig(
+                this, graphConfig, graphListener, streamGraph as StreamGraphImpl
+            )
+        ).build()
 
         // Create and return a Camera2 CameraController object.
         return cameraControllerComponent.cameraController()
     }
+
+    override fun prewarm(cameraId: CameraId) {
+        virtualCameraManager.prewarm(cameraId)
+    }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
index 14dc332..8cf4b52 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
@@ -27,6 +27,7 @@
 import androidx.camera.camera2.pipe.core.Threads
 import androidx.camera.camera2.pipe.core.WakeLock
 import androidx.camera.camera2.pipe.graph.GraphListener
+import androidx.camera.camera2.pipe.graph.GraphRequestProcessor
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlinx.coroutines.CoroutineName
@@ -47,11 +48,29 @@
     val isForegroundObserver: (Unit) -> Boolean,
 ) : CameraRequest()
 
+/**
+ * Sends a request to close an active camera.
+ * Note: RequestOpen() & RequestClose() may not be executed sequentially,
+ * as the camera may take a while to be fully opened, and RequestClose() might execute in parallel.
+ */
 internal data class RequestClose(val activeCamera: VirtualCameraManager.ActiveCamera) :
     CameraRequest()
 
+internal data class RequestCloseById(val activeCameraId: CameraId) :
+    CameraRequest()
+
 internal object RequestCloseAll : CameraRequest()
 
+internal object NoOpGraphListener : GraphListener {
+    override fun onGraphStarted(requestProcessor: GraphRequestProcessor) {}
+
+    override fun onGraphStopped(requestProcessor: GraphRequestProcessor) {}
+
+    override fun onGraphModified(requestProcessor: GraphRequestProcessor) {}
+
+    override fun onGraphError(graphStateError: GraphState.GraphStateError) {}
+}
+
 // A queue depth of 32 was deemed necessary in b/276051078 where a flood of requests can cause the
 // queue depth to go over 8. In the long run, we can perhaps look into refactoring and
 // reimplementing the request queue in a more robust way.
@@ -98,6 +117,16 @@
         return result
     }
 
+    /** Connects and starts the underlying camera.*/
+    internal fun prewarm(cameraId: CameraId) {
+        open(cameraId, emptyList(), NoOpGraphListener) { _ -> false }
+    }
+
+    /** Submits a request to close the underlying camera */
+    internal fun close(cameraId: CameraId) {
+        offerChecked(RequestCloseById(cameraId))
+    }
+
     internal fun closeAll() {
         if (!offerChecked(RequestCloseAll)) {
             Log.warn { "Failed to close all cameras: Close request submission failed" }
@@ -133,6 +162,23 @@
                 continue
             }
 
+            // Ensures the closure of a camera device happens after any preceding RequestOpen().
+            val closeRequestById = requests.firstOrNull()
+            if (closeRequestById != null && closeRequestById is RequestCloseById) {
+                requests.remove(closeRequestById)
+                pendingRequestOpens.removeAll {
+                    it.virtualCamera.cameraId == closeRequestById.activeCameraId
+                }
+                val activeCamera =
+                    activeCameras.firstOrNull { it.cameraId == closeRequestById.activeCameraId }
+                if (activeCamera != null) {
+                    activeCameras.remove(activeCamera)
+                    launch { activeCamera.close() }
+                    activeCamera.awaitClosed()
+                }
+                continue
+            }
+
             // If we received a closeAll request, then close every request leading up to it.
             val closeAll = requests.indexOfLast { it is RequestCloseAll }
             if (closeAll >= 0) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
index 0759f3e..2a143f3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
@@ -97,6 +97,22 @@
             throwUnsupportedOperationException()
         }
 
+        override fun prewarm(cameraId: CameraId) {
+            throwUnsupportedOperationException()
+        }
+
+        override fun disconnect(cameraId: CameraId) {
+            throwUnsupportedOperationException()
+        }
+
+        override fun disconnectAsync(cameraId: CameraId): Deferred<Unit> {
+            throwUnsupportedOperationException()
+        }
+
+        override fun disconnectAll() {
+            throwUnsupportedOperationException()
+        }
+
         private fun throwUnsupportedOperationException(): Nothing =
             throw UnsupportedOperationException("External CameraPipe should not use backends")
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
index 4e035a2..3cfe9d85 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
@@ -24,7 +24,6 @@
 import androidx.camera.camera2.pipe.core.TokenLock.Token
 import java.util.ArrayDeque
 import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
 import kotlin.math.min
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CancellableContinuation
@@ -87,17 +86,17 @@
 }
 
 /** Shorthand for "acquire(value, value)" */
-internal suspend inline fun TokenLock.acquire(value: Long): TokenLock.Token =
+internal suspend inline fun TokenLock.acquire(value: Long): Token =
     this.acquire(value, value)
 
 /** Shorthand for "acquireOrNull(value, value)" */
-internal inline fun TokenLock.acquireOrNull(value: Long): TokenLock.Token? =
+internal inline fun TokenLock.acquireOrNull(value: Long): Token? =
     this.acquireOrNull(value, value)
 
 /** Executes the given action while holding a token. */
 internal suspend inline fun <T> TokenLock.withToken(
     value: Long,
-    crossinline action: (token: TokenLock.Token) -> T
+    crossinline action: (token: Token) -> T
 ): T {
     this.acquire(value).use {
         return action(it)
@@ -108,7 +107,7 @@
 internal suspend inline fun <T> TokenLock.withToken(
     min: Long,
     max: Long,
-    crossinline action: (token: TokenLock.Token) -> T
+    crossinline action: (token: Token) -> T
 ): T {
     this.acquire(min, max).use {
         return action(it)
@@ -116,10 +115,6 @@
 }
 
 internal class TokenLockImpl(override val capacity: Long) : TokenLock {
-    companion object {
-        val closedException = CancellationException()
-    }
-
     private val pending = ArrayDeque<TokenRequest>()
 
     @GuardedBy("pending")
@@ -148,8 +143,8 @@
                 }
             }
 
-    override fun acquireOrNull(min: Long, max: Long): TokenLock.Token? {
-        if (min > capacity) throw IllegalArgumentException("Attempted to acquire $min / $capacity")
+    override fun acquireOrNull(min: Long, max: Long): Token? {
+        require(min <= capacity) { "Cannot acquire more than $capacity (requested $min)" }
 
         synchronized(pending) {
             if (closed) return null
@@ -165,17 +160,14 @@
         return null
     }
 
-    override suspend fun acquire(min: Long, max: Long): TokenLock.Token =
+    override suspend fun acquire(min: Long, max: Long): Token =
         suspendCancellableCoroutine { continuation ->
-            if (min > capacity) {
-                continuation.resumeWithException(
-                    IllegalArgumentException("Attempted to acquire $min / $capacity")
-                )
-                return@suspendCancellableCoroutine
-            }
-
+            require(min <= capacity) { "Cannot acquire more than $capacity (requested $min)" }
             synchronized(pending) {
-                if (closed) throw closedException
+                if (closed) {
+                    continuation.cancel()
+                    return@suspendCancellableCoroutine
+                }
                 if (pending.isEmpty()) {
                     val value = min(_available, max)
                     if (value >= min) {
@@ -248,7 +240,7 @@
                 }
 
                 // If we fulfilled 1 or more requests, then create and pass tokens to the
-                // continuation outside of the syncronized block.
+                // continuation outside of the synchronized block.
                 if (requests.isNotEmpty()) {
                     requestsToComplete = requests
                 }
@@ -259,13 +251,13 @@
     }
 
     private class TokenRequest(
-        val continuation: CancellableContinuation<TokenLock.Token>,
+        val continuation: CancellableContinuation<Token>,
         val min: Long,
         val max: Long,
         var token: TokenImpl? = null
     )
 
-    inner class TokenImpl(override val value: Long) : TokenLock.Token {
+    inner class TokenImpl(override val value: Long) : Token {
         private val closed = atomic(false)
 
         override fun close() {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraDevicesImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraDevicesImpl.kt
index 6f0b2bd..d2798d62 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraDevicesImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraDevicesImpl.kt
@@ -27,6 +27,7 @@
 import androidx.camera.camera2.pipe.core.Log
 import javax.inject.Inject
 import javax.inject.Singleton
+import kotlinx.coroutines.Deferred
 
 /** Provides utilities for querying cameras and accessing metadata about those cameras. */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@@ -118,6 +119,34 @@
         return metadata
     }
 
+    override fun prewarm(cameraId: CameraId, cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.prewarm(cameraId)
+    }
+
+    override fun disconnect(cameraId: CameraId, cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.disconnect(cameraId)
+    }
+
+    override fun disconnectAsync(
+        cameraId: CameraId,
+        cameraBackendId: CameraBackendId?
+    ): Deferred<Unit> {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        return cameraBackend.disconnectAsync(cameraId)
+    }
+
+    override fun disconnectAll(cameraBackendId: CameraBackendId?) {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        cameraBackend.disconnectAll()
+    }
+
+    override fun disconnectAllAsync(cameraBackendId: CameraBackendId?): Deferred<Unit> {
+        val cameraBackend = getCameraBackend(cameraBackendId)
+        return cameraBackend.disconnectAllAsync()
+    }
+
     private fun getCameraBackend(cameraBackendId: CameraBackendId?): CameraBackend =
         Debug.trace("getCameraBackend") {
             val actualBackendId = cameraBackendId ?: cameraBackends.default.id
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt
index 91c4338..4d24319 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt
@@ -148,14 +148,9 @@
             val isOutOfOrder = isFrameNumberOutOfOrder || isOutputNumberOutOfOrder
 
             // onOutputStarted should only be invoked once. Check to see that there are no other
-            // duplicate events.
-            check(
-                !startedOutputs.any {
-                    it.cameraFrameNumber == cameraFrameNumber ||
-                        it.cameraTimestamp == cameraTimestamp ||
-                        it.outputNumber == outputNumber
-                }
-            ) {
+            // duplicate events. Note that on some platforms, non-compliant camera HALs may return
+            // frames with identical frame numbers and output numbers. See b/324320062 for context.
+            check(!startedOutputs.any { it.cameraFrameNumber == cameraFrameNumber }) {
                 "onOutputStarted was invoked multiple times with a previously started output!" +
                     "onOutputStarted with $cameraFrameNumber, $cameraTimestamp, $outputNumber. " +
                     "Previously started outputs: $startedOutputs"
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt
index 42bf6cc..3386f26 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt
@@ -22,7 +22,10 @@
 import androidx.camera.camera2.pipe.OutputStatus
 import androidx.camera.camera2.pipe.internal.OutputDistributor.OutputListener
 import androidx.camera.camera2.pipe.media.Finalizer
+import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
 import kotlinx.atomicfu.atomic
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -447,6 +450,57 @@
         assertThat(pendingOutput1.outputStatus).isEqualTo(OutputStatus.ERROR_OUTPUT_FAILED)
     }
 
+    @Test
+    fun outputDistributorThrowsOnIdenticalFrameNumber() {
+        val pendingOutput1 =
+            PendingOutput(FrameNumber(1), CameraTimestamp(11), outputNumber = 101)
+        val pendingOutput2 =
+            PendingOutput(FrameNumber(1), CameraTimestamp(12), outputNumber = 102)
+        outputDistributor.startWith(pendingOutput1)
+
+        assertThrows<IllegalStateException> {
+            outputDistributor.startWith(pendingOutput2)
+        }
+    }
+
+    @Test
+    fun pendingOutputCompletesOnIdenticalTimestamps() {
+        val pendingOutput1 =
+            PendingOutput(FrameNumber(1), CameraTimestamp(11), outputNumber = 101)
+        val pendingOutput2 =
+            PendingOutput(FrameNumber(2), CameraTimestamp(11), outputNumber = 102)
+        outputDistributor.startWith(pendingOutput1)
+        outputDistributor.startWith(pendingOutput2)
+
+        outputDistributor.onOutputResult(fakeOutput1.outputNumber, OutputResult.from(fakeOutput1))
+        assertTrue(pendingOutput1.isComplete)
+        assertFalse(pendingOutput2.isComplete)
+
+        outputDistributor.onOutputResult(fakeOutput2.outputNumber, OutputResult.from(fakeOutput2))
+        assertTrue(pendingOutput1.isComplete)
+        assertTrue(pendingOutput2.isComplete)
+    }
+
+    @Test
+    fun pendingOutputCompletesOnIdenticalOutputNumbers() {
+        val pendingOutput1 =
+            PendingOutput(FrameNumber(1), CameraTimestamp(11), outputNumber = 101)
+        val pendingOutput2 =
+            PendingOutput(FrameNumber(2), CameraTimestamp(12), outputNumber = 101)
+        outputDistributor.startWith(pendingOutput1)
+        outputDistributor.startWith(pendingOutput2)
+
+        val fakeOutput1 = FakeOutput(101)
+        outputDistributor.onOutputResult(fakeOutput1.outputNumber, OutputResult.from(fakeOutput1))
+        assertTrue(pendingOutput1.isComplete)
+        assertFalse(pendingOutput2.isComplete)
+
+        val fakeOutput2 = FakeOutput(101)
+        outputDistributor.onOutputResult(fakeOutput2.outputNumber, OutputResult.from(fakeOutput2))
+        assertTrue(pendingOutput1.isComplete)
+        assertTrue(pendingOutput2.isComplete)
+    }
+
     /**
      * Utility class that implements [OutputListener] and can be used to observe when an
      * output is complete and the callback is invoked.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
index bd3f5a3..816a790 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
@@ -119,6 +119,7 @@
                     }
                 });
             }
+
             @Override
             public void onCaptureProcessProgressed(int captureConfigId, int progress) {
                 mainThreadExecutor().execute(() -> {
@@ -397,7 +398,9 @@
         abstract boolean isVirtualCamera();
 
         /**
-         * Whether the pipeline is connected to a virtual camera.
+         * The {@link ImageReaderProxyProvider} associated with the node. When the value exists,
+         * the node will use it to create the {@link ImageReaderProxy} that connects to
+         * the camera.
          */
         @Nullable
         abstract ImageReaderProxyProvider getImageReaderProxyProvider();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
index c45fae4..af8283b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
@@ -48,7 +48,14 @@
      * Returns the area of the supplied size.
      */
     public static int getArea(@NonNull Size size) {
-        return size.getWidth() * size.getHeight();
+        return getArea(size.getWidth(), size.getHeight());
+    }
+
+    /**
+     * Returns the area of the supplied width and height.
+     */
+    public static int getArea(int width, int height) {
+        return width * height;
     }
 
     /**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
index 8549b34..0ac706e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
@@ -49,6 +49,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
@@ -376,6 +377,7 @@
         }
     }
 
+    @Ignore("Flaking in presubmit (b/323202283)")
     @Test(expected = IllegalArgumentException::class)
     fun cropSizeMismatchesOutputSize_throwsException() {
         createSurfaceProcessorNode()
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 1cc83d2..95a8346 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -60,6 +60,7 @@
     androidTestImplementation(libs.truth)
     androidTestImplementation(libs.multidex)
     androidTestImplementation(project(":camera:camera-camera2"))
+    androidTestImplementation(project(":camera:camera-camera2-pipe-integration"))
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing")) {
         // Ensure camera-testing does not pull in androidx.test dependencies
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 5eafbc8..18e7195 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -24,6 +24,7 @@
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.MutableStateObservable
@@ -34,7 +35,9 @@
 import androidx.camera.extensions.internal.VendorExtender
 import androidx.camera.extensions.internal.Version
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
 import androidx.camera.testing.impl.fakes.FakeUseCase
@@ -55,6 +58,7 @@
 import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -63,10 +67,16 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ExtensionsManagerTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig,
     private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
+    @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+    )
 
     private val context = InstrumentationRegistry.getInstrumentation().context
 
@@ -86,6 +96,7 @@
             )
         )
 
+        ProcessCameraProvider.configureInstance(cameraXConfig)
         cameraProvider =
             ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
 
@@ -112,10 +123,14 @@
 
     companion object {
         val context: Context = ApplicationProvider.getApplicationContext()
+
         @JvmStatic
-        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
-        val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, false)
+        @Parameterized.Parameters(
+            name = "cameraXConfig = {0}, implType = {2}, mode = {3}, facing = {4}"
+        )
+        fun data(): Collection<Array<Any>> {
+            return ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, false)
+        }
     }
 
     @Test
@@ -302,7 +317,7 @@
                 return true
             }
 
-            override fun getEstimatedCaptureLatencyRange(size: Size?): Range<Long>? {
+            override fun getEstimatedCaptureLatencyRange(size: Size?): Range<Long> {
                 return estimatedCaptureLatency
             }
         }
@@ -310,9 +325,11 @@
             fakeVendorExtender
         }
 
-        assertThat(extensionsManager.getEstimatedCaptureLatencyRange(
-            baseCameraSelector,
-            extensionMode)
+        assertThat(
+            extensionsManager.getEstimatedCaptureLatencyRange(
+                baseCameraSelector,
+                extensionMode
+            )
         ).isEqualTo(estimatedCaptureLatency)
     }
 
@@ -329,9 +346,11 @@
                 != ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
         )
 
-        assertThat(extensionsManager.getEstimatedCaptureLatencyRange(
+        assertThat(
+            extensionsManager.getEstimatedCaptureLatencyRange(
                 baseCameraSelector,
-                extensionMode)
+                extensionMode
+            )
         ).isNull()
     }
 
@@ -483,9 +502,11 @@
                 == ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
         )
 
-        assertThat(extensionsManager.isImageAnalysisSupported(
-            baseCameraSelector,
-            extensionMode)
+        assertThat(
+            extensionsManager.isImageAnalysisSupported(
+                baseCameraSelector,
+                extensionMode
+            )
         ).isFalse()
     }
 
@@ -517,11 +538,14 @@
                 == ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
         )
 
-        assertThat(extensionsManager.isImageAnalysisSupported(
-            baseCameraSelector,
-            extensionMode)
+        assertThat(
+            extensionsManager.isImageAnalysisSupported(
+                baseCameraSelector,
+                extensionMode
+            )
         ).isTrue()
     }
+
     @Test
     fun isImageAnalysisSupportedIsFalse_whenExtensionAvailabilityIsNotAvailable() {
         extensionsManager = ExtensionsManager.getInstanceAsync(
@@ -535,9 +559,11 @@
                 != ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
         )
 
-        assertThat(extensionsManager.isImageAnalysisSupported(
-            baseCameraSelector,
-            extensionMode)
+        assertThat(
+            extensionsManager.isImageAnalysisSupported(
+                baseCameraSelector,
+                extensionMode
+            )
         ).isFalse()
     }
 
@@ -569,7 +595,7 @@
             }
 
             override fun isPostviewAvailable(): Boolean {
-                return true;
+                return true
             }
         }
         extensionsManager.setVendorExtenderFactory {
@@ -598,7 +624,7 @@
             }
 
             override fun isCaptureProcessProgressAvailable(): Boolean {
-                return true;
+                return true
             }
         }
         extensionsManager.setVendorExtenderFactory {
@@ -637,8 +663,10 @@
         var vendorExtender = ExtensionsTestUtil.createVendorExtender(extensionMode)
         val cameraId = (cameraInfo as CameraInfoInternal).cameraId
 
-        return vendorExtender.isExtensionAvailable(cameraId,
-            ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo))
+        return vendorExtender.isExtensionAvailable(
+            cameraId,
+            ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo)
+        )
     }
 
     private fun createVideoCapture(): VideoCapture<TestVideoOutput> {
@@ -658,6 +686,7 @@
         override fun onSurfaceRequested(@NonNull request: SurfaceRequest) {
             surfaceRequest = request
         }
+
         override fun getMediaSpec() = mediaSpecObservable
         override fun onSourceStateChanged(@NonNull sourceState: VideoOutput.SourceState) {
             this.sourceState = sourceState
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
index db5d883..8ebfd99 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
@@ -21,9 +21,9 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.util.Pair
 import android.util.Size
-import androidx.camera.camera2.Camera2Config
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
@@ -33,7 +33,9 @@
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.internal.VendorExtender
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
@@ -59,21 +61,32 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ImageAnalysisTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig,
     private val implType: ExtensionsTestlibControl.ImplementationType,
     @ExtensionMode.Mode private val extensionMode: Int,
     @CameraSelector.LensFacing private val lensFacing: Int
 ) {
     companion object {
         val context: Context = ApplicationProvider.getApplicationContext()
+
         @JvmStatic
-        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
-        val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        @Parameterized.Parameters(
+            name = "cameraXConfig = {0}, implType = {2}, mode = {3}, facing = {4}"
+        )
+        fun data(): Collection<Array<Any>> {
+            return ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        }
     }
 
     @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+    )
+
+    @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
-        CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+        CameraUtil.PreTestCameraIdList(cameraXConfig)
     )
 
     private lateinit var cameraProvider: ProcessCameraProvider
@@ -92,6 +105,7 @@
             )
         )
 
+        ProcessCameraProvider.configureInstance(cameraXConfig)
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
@@ -128,8 +142,9 @@
             baseCameraSelector,
             extensionMode
         )
-        Assume.assumeTrue(extensionsManager
-            .isImageAnalysisSupported(extensionsCameraSelector, extensionMode))
+        Assume.assumeTrue(
+            extensionsManager.isImageAnalysisSupported(extensionsCameraSelector, extensionMode)
+        )
 
         val analysisLatch = CountDownLatch(2)
         withContext(Dispatchers.Main) {
@@ -185,16 +200,21 @@
 
                 override fun getSupportedPreviewOutputResolutions(): List<Pair<Int, Array<Size>>> {
                     return listOf(
-                        Pair(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                        Pair(
+                            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
                             getOutputSizes(
-                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE))
+                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+                            )
+                        )
                     )
                 }
 
                 override fun getSupportedCaptureOutputResolutions(): List<Pair<Int, Array<Size>>> {
                     return listOf(
-                        Pair(ImageFormat.JPEG,
-                            getOutputSizes(ImageFormat.JPEG))
+                        Pair(
+                            ImageFormat.JPEG,
+                            getOutputSizes(ImageFormat.JPEG)
+                        )
                     )
                 }
             }
@@ -204,8 +224,10 @@
             baseCameraSelector,
             extensionMode
         )
-        assertThat(extensionsManager
-            .isImageAnalysisSupported(baseCameraSelector, extensionMode)).isTrue()
+        assertThat(
+            extensionsManager
+                .isImageAnalysisSupported(baseCameraSelector, extensionMode)
+        ).isTrue()
         withContext(Dispatchers.Main) {
             val preview = Preview.Builder().build()
             val imageCapture = ImageCapture.Builder().build()
@@ -213,9 +235,9 @@
 
             // 2. Act
             cameraProvider.bindToLifecycle(
-                    fakeLifecycleOwner,
-                    extensionsCameraSelector,
-                    preview, imageCapture, imageAnalysis
+                fakeLifecycleOwner,
+                extensionsCameraSelector,
+                preview, imageCapture, imageAnalysis
             )
 
             // 3. Assert
@@ -241,16 +263,21 @@
 
                 override fun getSupportedPreviewOutputResolutions(): List<Pair<Int, Array<Size>>> {
                     return listOf(
-                        Pair(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                        Pair(
+                            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
                             getOutputSizes(
-                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE))
+                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+                            )
+                        )
                     )
                 }
 
                 override fun getSupportedCaptureOutputResolutions(): List<Pair<Int, Array<Size>>> {
                     return listOf(
-                        Pair(ImageFormat.JPEG,
-                            getOutputSizes(ImageFormat.JPEG))
+                        Pair(
+                            ImageFormat.JPEG,
+                            getOutputSizes(ImageFormat.JPEG)
+                        )
                     )
                 }
             }
@@ -260,8 +287,10 @@
             baseCameraSelector,
             extensionMode
         )
-        assertThat(extensionsManager
-            .isImageAnalysisSupported(baseCameraSelector, extensionMode)).isFalse()
+        assertThat(
+            extensionsManager
+                .isImageAnalysisSupported(baseCameraSelector, extensionMode)
+        ).isFalse()
         withContext(Dispatchers.Main) {
             val preview = Preview.Builder().build()
             val imageCapture = ImageCapture.Builder().build()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
index 3c14ce5..52c9a3d 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
@@ -23,9 +23,9 @@
 import android.util.Log
 import android.util.Size
 import android.view.Surface
-import androidx.camera.camera2.Camera2Config
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
@@ -34,7 +34,9 @@
 import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
 import androidx.camera.testing.impl.ExifUtil
@@ -70,14 +72,21 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ImageCaptureTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig,
     private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
 
     @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+    )
+
+    @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
-        PreTestCameraIdList(Camera2Config.defaultConfig())
+        PreTestCameraIdList(cameraXConfig)
     )
 
     @get:Rule
@@ -102,6 +111,7 @@
             )
         )
 
+        ProcessCameraProvider.configureInstance(cameraXConfig)
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
         ExtensionsTestlibControl.getInstance().setImplementationType(implType)
@@ -136,10 +146,14 @@
     companion object {
         val TAG = "ImageCaptureTest"
         val context: Context = ApplicationProvider.getApplicationContext()
+
         @JvmStatic
-        @get:Parameterized.Parameters(name = "impl= {0}, mode = {1}, facing = {2}")
-        val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        @Parameterized.Parameters(
+            name = "cameraXConfig = {0}, impl = {2}, mode = {3}, facing = {4}"
+        )
+        fun data(): Collection<Array<Any>> {
+            return ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        }
     }
 
     @Test
@@ -186,8 +200,10 @@
 
     // TODO(b/322416654): Enable test after it can pass on most devices
     fun canInterruptTakePictureAndResume_forLongCapture(): Unit = runBlocking {
-        val latency = extensionsManager.getEstimatedCaptureLatencyRange(extensionsCameraSelector,
-            extensionMode)
+        val latency = extensionsManager.getEstimatedCaptureLatencyRange(
+            extensionsCameraSelector,
+            extensionMode
+        )
         assumeTrue(latency != null && latency.lower >= 2000)
         canInterruptTakePictureAndResumeInternal(
             delayForStopLifecycle = latency!!.lower,
@@ -207,7 +223,7 @@
         delayForStopLifecycle: Long
     ): Unit = runBlocking {
         if (enablePostview) {
-            assumeTrue(isPostviewSupported());
+            assumeTrue(isPostviewSupported())
         }
         val imageCapturedCallback = FakeOnImageCaptureCallback()
         val imageCapture = ImageCapture.Builder()
@@ -265,8 +281,9 @@
                 Bitmap::class.java
             )
             Mockito.verify(mockOnImageCapturedCallback, Mockito.timeout(10000))
-                .onPostviewBitmapAvailable(bitmap.capture()
-            )
+                .onPostviewBitmapAvailable(
+                    bitmap.capture()
+                )
             assertThat(bitmap).isNotNull()
         }
 
@@ -555,12 +572,15 @@
             override fun onError(exception: ImageCaptureException) {
                 hasError = true
             }
+
             override fun onCaptureStarted() {
                 captureStartedDeferred.complete(true)
             }
+
             override fun onCaptureSuccess(image: ImageProxy) {
                 captureSuccessDeferred.complete(image)
             }
+
             override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
                 PostviewDeferred.complete(bitmap)
             }
@@ -605,6 +625,7 @@
             override fun onError(exception: ImageCaptureException) {
                 hasError = true
             }
+
             override fun onCaptureStarted() {
                 captureStartedDeferred.complete(true)
             }
@@ -612,6 +633,7 @@
             override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                 imageSavedDeferred.complete(outputFileResults)
             }
+
             override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
                 PostviewDeferred.complete(bitmap)
             }
@@ -643,7 +665,8 @@
             cameraProvider.bindToLifecycle(
                 fakeLifecycleOwner,
                 extensionsCameraSelector,
-                imageCapture)
+                imageCapture
+            )
         }
 
         assertThat(imageCapture.currentConfig.isHigResolutionDisabled(false)).isTrue()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
index 3d527c9..cea52da 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
@@ -21,12 +21,14 @@
 import android.os.Handler
 import android.os.HandlerThread
 import android.util.Size
-import androidx.camera.camera2.Camera2Config
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
 import androidx.camera.core.Preview
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
 import androidx.camera.testing.impl.GLUtil
@@ -54,14 +56,20 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class PreviewTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig,
     private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
+    @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+    )
 
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
-        PreTestCameraIdList(Camera2Config.defaultConfig())
+        PreTestCameraIdList(cameraXConfig)
     )
 
     private lateinit var cameraProvider: ProcessCameraProvider
@@ -118,6 +126,7 @@
             )
         )
 
+        ProcessCameraProvider.configureInstance(cameraXConfig)
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
@@ -151,10 +160,14 @@
 
     companion object {
         val context: Context = ApplicationProvider.getApplicationContext()
+
         @JvmStatic
-        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
-        val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        @Parameterized.Parameters(
+            name = "cameraXConfig = {0}, implType = {2}, mode = {3}, facing = {4}"
+        )
+        fun data(): Collection<Array<Any>> {
+            return ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        }
     }
 
     @UiThreadTest
@@ -186,10 +199,15 @@
         val preview = Preview.Builder().build()
 
         withContext(Dispatchers.Main) {
+            preview.setSurfaceProvider(
+                SurfaceTextureProvider.createSurfaceTextureProvider(createSurfaceTextureCallback())
+            )
+
             cameraProvider.bindToLifecycle(
                 fakeLifecycleOwner,
                 extensionsCameraSelector,
-                preview)
+                preview
+            )
         }
 
         assertThat(preview.currentConfig.isHigResolutionDisabled(false)).isTrue()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
index 81cfe97..6661554 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
@@ -23,16 +23,18 @@
 import android.net.Uri
 import android.util.Log
 import android.util.Size
-import androidx.camera.camera2.Camera2Config
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
 import androidx.camera.core.Preview.SurfaceProvider
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.impl.AndroidUtil.skipVideoRecordingTestIfNotSupportedByEmulator
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
@@ -67,13 +69,20 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class VideoCaptureTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig,
     private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
     @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+    )
+
+    @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
-        CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+        CameraUtil.PreTestCameraIdList(cameraXConfig)
     )
 
     @get:Rule
@@ -102,19 +111,23 @@
                 Log.d(TAG, "Recording start")
                 latchForVideoStarted.countDown()
             }
+
             is VideoRecordEvent.Finalize -> {
                 Log.d(TAG, "Recording finalize")
                 finalize = it
                 latchForVideoSaved.countDown()
             }
+
             is VideoRecordEvent.Status -> {
                 Log.d(TAG, "Recording Status")
                 latchForVideoRecording.countDown()
             }
+
             is VideoRecordEvent.Pause,
             is VideoRecordEvent.Resume -> {
                 // Do nothing.
             }
+
             else -> {
                 throw IllegalStateException()
             }
@@ -131,6 +144,7 @@
         )
         skipVideoRecordingTestIfNotSupportedByEmulator()
 
+        ProcessCameraProvider.configureInstance(cameraXConfig)
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
@@ -282,9 +296,13 @@
         private const val VIDEO_TIMEOUT_SEC = 10L
         private const val TAG = "VideoCaptureTest"
         val context: Context = ApplicationProvider.getApplicationContext()
+
         @JvmStatic
-        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
-        val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        @Parameterized.Parameters(
+            name = "cameraXConfig = {0}, implType = {2}, mode = {3}, facing = {4}"
+        )
+        fun data(): Collection<Array<Any>> {
+            return ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations(context, true)
+        }
     }
 }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
index 56b3bac..24baeae 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
@@ -32,7 +32,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CameraXConfig;
 import androidx.camera.core.ExtendableBuilder;
 import androidx.camera.core.impl.Config;
 import androidx.camera.extensions.ExtensionMode;
@@ -62,14 +65,18 @@
             SESSION_CAPTURE_CALLBACK_OPTION =
             Config.Option.create("camera2.cameraCaptureSession.captureCallback",
                     CameraCaptureSession.CaptureCallback.class);
+    public static final String CAMERA2_IMPLEMENTATION_OPTION = "camera2";
+    public static final String CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe";
 
     /**
-     * Returns the parameters which contains the combination of implementationType, extensions
-     * mode and lens facing.
+     * Returns the parameters which contains the combination of CameraXConfig
+     * name, CameraXConfig, implementationType, extensions mode and lens facing.
      */
     @NonNull
     public static Collection<Object[]> getAllImplExtensionsLensFacingCombinations(
-            @NonNull Context context, boolean excludeUnavailableModes) {
+            @NonNull Context context,
+            boolean excludeUnavailableModes
+    ) {
         ExtensionsTestlibControl.ImplementationType implType =
                 ExtensionsTestlibControl.getInstance().getImplementationType();
 
@@ -92,8 +99,9 @@
         });
 
         if (implType == OEM_IMPL) {
-            return excludeUnavailableModes ? filterOutUnavailableMode(context, basicOrOemImplList)
-                    : basicOrOemImplList;
+            List<Object[]> allList = excludeUnavailableModes ? filterOutUnavailableMode(context,
+                    basicOrOemImplList) : basicOrOemImplList;
+            return getConfigPrependedCombinations(allList);
         }
 
         List<Object[]> advancedList = Arrays.asList(new Object[][]{
@@ -119,7 +127,8 @@
 
         // Reset to basic in case advanced is used accidentally.
         ExtensionsTestlibControl.getInstance().setImplementationType(TESTLIB_BASIC);
-        return allList;
+
+        return getConfigPrependedCombinations(allList);
     }
 
     private static List<Object[]> filterOutUnavailableMode(Context context,
@@ -158,6 +167,24 @@
         }
     }
 
+    private static List<Object[]> getConfigPrependedCombinations(List<Object[]> combinations) {
+        CameraXConfig camera2Config = Camera2Config.defaultConfig();
+        CameraXConfig cameraPipeConfig = CameraPipeConfig.defaultConfig();
+        List<Object[]> combinationsWithConfig = new ArrayList<Object[]>();
+        for (Object[] combination: combinations) {
+            List<Object> combinationCamera2 = new ArrayList<Object>(
+                    Arrays.asList(CAMERA2_IMPLEMENTATION_OPTION, camera2Config));
+            combinationCamera2.addAll(Arrays.asList(combination));
+            combinationsWithConfig.add(combinationCamera2.toArray());
+
+            List<Object> combinationCameraPipe = new ArrayList<Object>(
+                    Arrays.asList(CAMERA_PIPE_IMPLEMENTATION_OPTION, cameraPipeConfig));
+            combinationCameraPipe.addAll(Arrays.asList(combination));
+            combinationsWithConfig.add(combinationCameraPipe.toArray());
+        }
+        return combinationsWithConfig;
+    }
+
     /**
      * Returns whether the target camera device can support the test for a specific extension mode.
      */
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 755624e..d780a8a 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -566,7 +566,7 @@
         recorder.streamInfo.addObserver(directExecutor(), streamInfoObserver)
 
         // Assert: Recorder should start in INACTIVE stream state before any recordings
-        inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT)).onNewData(
+        inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT).atLeastOnce()).onNewData(
             argThat {
                 it!!.streamState == StreamInfo.StreamState.INACTIVE
             }
@@ -577,7 +577,7 @@
         recording.start()
 
         // Assert: Starting recording should move Recorder to ACTIVE stream state
-        inOrder.verify(streamInfoObserver, timeout(5000L)).onNewData(
+        inOrder.verify(streamInfoObserver, timeout(5000L).atLeastOnce()).onNewData(
             argThat { it!!.streamState == StreamInfo.StreamState.ACTIVE }
         )
 
@@ -585,7 +585,7 @@
         recording.stop()
 
         // Assert: Stopping recording should eventually move to INACTIVE stream state
-        inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT)).onNewData(
+        inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT).atLeastOnce()).onNewData(
             argThat {
                 it!!.streamState == StreamInfo.StreamState.INACTIVE
             }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index b633a8f..aa18150 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -38,10 +38,12 @@
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_ZSL_DISABLED;
 import static androidx.camera.core.impl.utils.Threads.isMainThread;
 import static androidx.camera.core.impl.utils.TransformUtils.rectToString;
+import static androidx.camera.core.impl.utils.TransformUtils.within360;
 import static androidx.camera.core.internal.TargetConfig.OPTION_TARGET_CLASS;
 import static androidx.camera.core.internal.TargetConfig.OPTION_TARGET_NAME;
 import static androidx.camera.core.internal.ThreadConfig.OPTION_BACKGROUND_EXECUTOR;
 import static androidx.camera.core.internal.UseCaseEventConfig.OPTION_USE_CASE_EVENT_CALLBACK;
+import static androidx.camera.core.internal.utils.SizeUtil.getArea;
 import static androidx.camera.video.QualitySelector.getQualityToResolutionMap;
 import static androidx.camera.video.StreamInfo.STREAM_ID_ERROR;
 import static androidx.camera.video.impl.VideoCaptureConfig.OPTION_FORCE_ENABLE_SURFACE_PROCESSING;
@@ -49,6 +51,9 @@
 import static androidx.camera.video.impl.VideoCaptureConfig.OPTION_VIDEO_OUTPUT;
 import static androidx.camera.video.internal.config.VideoConfigUtil.resolveVideoEncoderConfig;
 import static androidx.camera.video.internal.config.VideoConfigUtil.resolveVideoMimeInfo;
+import static androidx.camera.video.internal.utils.DynamicRangeUtil.isHdrSettingsMatched;
+import static androidx.camera.video.internal.utils.DynamicRangeUtil.videoProfileBitDepthToDynamicRangeBitDepth;
+import static androidx.camera.video.internal.utils.DynamicRangeUtil.videoProfileHdrFormatsToDynamicRangeEncoding;
 import static androidx.core.util.Preconditions.checkState;
 
 import static java.util.Collections.singletonList;
@@ -81,6 +86,7 @@
 import androidx.camera.core.MirrorMode;
 import androidx.camera.core.Preview;
 import androidx.camera.core.SurfaceRequest;
+import androidx.camera.core.SurfaceRequest.TransformationInfo;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.CameraCaptureCallback;
@@ -91,6 +97,7 @@
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.ConfigProvider;
 import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.EncoderProfilesProxy;
 import androidx.camera.core.impl.ImageInputConfig;
 import androidx.camera.core.impl.ImageOutputConfig;
 import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
@@ -139,6 +146,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -211,8 +219,6 @@
     @Nullable
     private SurfaceProcessorNode mNode;
     @Nullable
-    private VideoEncoderInfo mVideoEncoderInfo;
-    @Nullable
     private Rect mCropRect;
     private int mRotationDegrees;
     private boolean mHasCompensatingTransformation = false;
@@ -452,7 +458,8 @@
     protected StreamSpec onSuggestedStreamSpecImplementationOptionsUpdated(@NonNull Config config) {
         mSessionConfigBuilder.addImplementationOptions(config);
         updateSessionConfig(mSessionConfigBuilder.build());
-        return getAttachedStreamSpec().toBuilder().setImplementationOptions(config).build();
+        return requireNonNull(getAttachedStreamSpec()).toBuilder()
+                .setImplementationOptions(config).build();
     }
 
     @NonNull
@@ -531,8 +538,10 @@
     private int adjustRotationWithInProgressTransformation(int rotationDegrees) {
         int adjustedRotationDegrees = rotationDegrees;
         if (shouldCompensateTransformation()) {
-            adjustedRotationDegrees = TransformUtils.within360((rotationDegrees
-                    - mStreamInfo.getInProgressTransformationInfo().getRotationDegrees()));
+            TransformationInfo transformationInfo =
+                    requireNonNull(mStreamInfo.getInProgressTransformationInfo());
+            adjustedRotationDegrees = within360(
+                    rotationDegrees - transformationInfo.getRotationDegrees());
         }
         return adjustedRotationDegrees;
     }
@@ -596,20 +605,16 @@
         // handleInvalidate() is not used. But if a different approach is asked in the future,
         // handleInvalidate() can be used as an alternative.
         Runnable onSurfaceInvalidated = this::notifyReset;
-
-        // If the expected frame rate range is unspecified, we need to give an educated estimate
-        // on what frame rate the camera will be operating at. For most devices this is a
-        // constant frame rate of 30fps, but in the future this could probably be queried from
-        // the camera.
-        Range<Integer> expectedFrameRate = streamSpec.getExpectedFrameRateRange();
-        if (Objects.equals(expectedFrameRate, StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
-            expectedFrameRate = Defaults.DEFAULT_FPS_RANGE;
-        }
+        Range<Integer> expectedFrameRate = resolveFrameRate(streamSpec);
         MediaSpec mediaSpec = requireNonNull(getMediaSpec());
         VideoCapabilities videoCapabilities = getVideoCapabilities(camera.getCameraInfo());
         DynamicRange dynamicRange = streamSpec.getDynamicRange();
-        VideoEncoderInfo videoEncoderInfo = getVideoEncoderInfo(config.getVideoEncoderInfoFinder(),
-                videoCapabilities, dynamicRange, mediaSpec, resolution, expectedFrameRate);
+        VideoValidatedEncoderProfilesProxy encoderProfiles =
+                videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
+                        dynamicRange);
+        VideoEncoderInfo videoEncoderInfo = resolveVideoEncoderInfo(
+                config.getVideoEncoderInfoFinder(), encoderProfiles, mediaSpec,  resolution,
+                dynamicRange, expectedFrameRate);
         mRotationDegrees = adjustRotationWithInProgressTransformation(getRelativeRotation(camera,
                 isMirroringRequired(camera)));
         Rect originalCropRect = calculateCropRect(resolution, videoEncoderInfo);
@@ -622,18 +627,7 @@
             mHasCompensatingTransformation = true;
         }
         mNode = createNodeIfNeeded(camera, config, mCropRect, resolution, dynamicRange);
-        // Choose Timebase based on the whether the buffer is copied.
-        Timebase timebase;
-        if (mNode != null || !camera.getHasTransform()) {
-            timebase = camera.getCameraInfoInternal().getTimebase();
-        } else {
-            // When camera buffers from a REALTIME device are passed directly to a video encoder
-            // from the camera, automatic compensation is done to account for differing timebases
-            // of the audio and camera subsystems. See the document of
-            // CameraMetadata#SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME. So the timebase is always
-            // UPTIME when encoder surface is directly sent to camera.
-            timebase = Timebase.UPTIME;
-        }
+        Timebase timebase = resolveTimebase(camera, mNode);
         Logger.d(TAG, "camera timebase = " + camera.getCameraInfoInternal().getTimebase()
                 + ", processing timebase = " + timebase);
         // Update the StreamSpec with new frame rate range and resolution.
@@ -732,7 +726,6 @@
             mCameraEdge.close();
             mCameraEdge = null;
         }
-        mVideoEncoderInfo = null;
         mCropRect = null;
         mSurfaceRequest = null;
         mStreamInfo = StreamInfo.STREAM_INFO_ANY_INACTIVE;
@@ -942,6 +935,7 @@
      * right, top, and bottom) is then calculated by extending or indenting from the center of
      * the original cropping rectangle.
      */
+    @SuppressWarnings("RedundantIfStatement")
     @NonNull
     private static Rect adjustCropRectToValidSize(@NonNull Rect cropRect, @NonNull Size resolution,
             @NonNull VideoEncoderInfo videoEncoderInfo) {
@@ -1133,44 +1127,35 @@
         return supportedRange.clamp(newLength);
     }
 
-    @MainThread
-    @Nullable
-    private VideoEncoderInfo getVideoEncoderInfo(
-            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder,
-            @NonNull VideoCapabilities videoCapabilities,
-            @NonNull DynamicRange dynamicRange,
-            @NonNull MediaSpec mediaSpec,
-            @NonNull Size resolution,
-            @NonNull Range<Integer> expectedFrameRate) {
-        if (mVideoEncoderInfo != null) {
-            return mVideoEncoderInfo;
+    @NonNull
+    private static Timebase resolveTimebase(@NonNull CameraInternal camera,
+            @Nullable SurfaceProcessorNode node) {
+        // Choose Timebase based on the whether the buffer is copied.
+        Timebase timebase;
+        if (node != null || !camera.getHasTransform()) {
+            timebase = camera.getCameraInfoInternal().getTimebase();
+        } else {
+            // When camera buffers from a REALTIME device are passed directly to a video encoder
+            // from the camera, automatic compensation is done to account for differing timebases
+            // of the audio and camera subsystems. See the document of
+            // CameraMetadata#SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME. So the timebase is always
+            // UPTIME when encoder surface is directly sent to camera.
+            timebase = Timebase.UPTIME;
         }
+        return timebase;
+    }
 
-        // Find the nearest EncoderProfiles
-        VideoValidatedEncoderProfilesProxy encoderProfiles =
-                videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
-                        dynamicRange);
-        VideoEncoderInfo videoEncoderInfo = resolveVideoEncoderInfo(videoEncoderInfoFinder,
-                encoderProfiles, mediaSpec, resolution, dynamicRange, expectedFrameRate);
-        if (videoEncoderInfo == null) {
-            // If VideoCapture cannot find videoEncoderInfo, it means that VideoOutput should
-            // also not be able to find the encoder. VideoCapture will not handle this situation
-            // and leave it to VideoOutput to respond.
-            Logger.w(TAG, "Can't find videoEncoderInfo");
-            return null;
+    @NonNull
+    private static Range<Integer> resolveFrameRate(@NonNull StreamSpec streamSpec) {
+        // If the expected frame rate range is unspecified, we need to give an educated estimate
+        // on what frame rate the camera will be operating at. For most devices this is a
+        // constant frame rate of 30fps, but in the future this could probably be queried from
+        // the camera.
+        Range<Integer> frameRate = streamSpec.getExpectedFrameRateRange();
+        if (Objects.equals(frameRate, StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
+            frameRate = Defaults.DEFAULT_FPS_RANGE;
         }
-
-        Size profileSize = encoderProfiles != null ? new Size(
-                encoderProfiles.getDefaultVideoProfile().getWidth(),
-                encoderProfiles.getDefaultVideoProfile().getHeight()) : null;
-        videoEncoderInfo = VideoEncoderInfoWrapper.from(videoEncoderInfo, profileSize);
-
-        // Cache the VideoEncoderInfo as it should be the same when recreating the pipeline.
-        // This avoids recreating the MediaCodec instance to get encoder information.
-        // Note: We should clear the cache if the MediaSpec changes at any time, especially when
-        // the Encoder-related content in the VideoSpec changes. i.e. when we need to observe the
-        // MediaSpec Observable.
-        return mVideoEncoderInfo = videoEncoderInfo;
+        return frameRate;
     }
 
     @Nullable
@@ -1193,7 +1178,19 @@
                 dynamicRange,
                 expectedFrameRate);
 
-        return videoEncoderInfoFinder.apply(videoEncoderConfig);
+        VideoEncoderInfo videoEncoderInfo = videoEncoderInfoFinder.apply(videoEncoderConfig);
+        if (videoEncoderInfo == null) {
+            // If VideoCapture cannot find videoEncoderInfo, it means that VideoOutput should
+            // also not be able to find the encoder. VideoCapture will not handle this situation
+            // and leave it to VideoOutput to respond.
+            Logger.w(TAG, "Can't find videoEncoderInfo");
+            return null;
+        }
+
+        Size profileSize = encoderProfiles != null ? new Size(
+                encoderProfiles.getDefaultVideoProfile().getWidth(),
+                encoderProfiles.getDefaultVideoProfile().getHeight()) : null;
+        return VideoEncoderInfoWrapper.from(videoEncoderInfo, profileSize);
     }
 
     @MainThread
@@ -1287,6 +1284,7 @@
      * @throws IllegalArgumentException if not able to find a resolution by the QualitySelector
      *                                  in VideoOutput.
      */
+    @SuppressWarnings("unchecked") // Cast to VideoCaptureConfig<T>
     private void updateCustomOrderedResolutionsByQuality(@NonNull CameraInfoInternal cameraInfo,
             @NonNull UseCaseConfig.Builder<?, ?, ?> builder) throws IllegalArgumentException {
         MediaSpec mediaSpec = getMediaSpec();
@@ -1332,9 +1330,93 @@
             customOrderedResolutions.addAll(
                     qualityRatioTable.getResolutions(selectedQuality, aspectRatio));
         }
-        Logger.d(TAG, "Set custom ordered resolutions = " + customOrderedResolutions);
+        List<Size> filteredCustomOrderedResolutions = filterOutEncoderUnsupportedResolutions(
+                (VideoCaptureConfig<T>) builder.getUseCaseConfig(), mediaSpec,
+                requestedDynamicRange, videoCapabilities, customOrderedResolutions);
+        Logger.d(TAG, "Set custom ordered resolutions = " + filteredCustomOrderedResolutions);
         builder.getMutableConfig().insertOption(OPTION_CUSTOM_ORDERED_RESOLUTIONS,
-                customOrderedResolutions);
+                filteredCustomOrderedResolutions);
+    }
+
+    @NonNull
+    private static List<Size> filterOutEncoderUnsupportedResolutions(
+            @NonNull VideoCaptureConfig<?> config,
+            @NonNull MediaSpec mediaSpec,
+            @NonNull DynamicRange dynamicRange,
+            @NonNull VideoCapabilities videoCapabilities,
+            @NonNull List<Size> resolutions
+    ) {
+        if (resolutions.isEmpty()) {
+            return resolutions;
+        }
+
+        Iterator<Size> iterator = resolutions.iterator();
+        while (iterator.hasNext()) {
+            Size resolution = iterator.next();
+            // We must find EncoderProfiles for each resolution because the EncoderProfiles found
+            // by resolution may contain different video mine type which leads to different codec.
+            VideoValidatedEncoderProfilesProxy encoderProfiles =
+                    videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
+                            dynamicRange);
+            if (encoderProfiles == null) {
+                continue;
+            }
+            // If the user set a non-fully specified target DynamicRange, there could be multiple
+            // videoProfiles that matches to the DynamicRange. Find the one with the largest
+            // supported size as a workaround.
+            // If the suggested StreamSpec(i.e. DynamicRange + resolution) is unfortunately over
+            // codec supported size, then rely on surface processing (OpenGL) to resize the
+            // camera stream.
+            VideoEncoderInfo videoEncoderInfo = findLargestSupportedSizeVideoEncoderInfo(
+                    config.getVideoEncoderInfoFinder(), encoderProfiles, dynamicRange,
+                    mediaSpec, resolution,
+                    requireNonNull(config.getTargetFrameRate(Defaults.DEFAULT_FPS_RANGE)));
+            if (videoEncoderInfo != null && !videoEncoderInfo.isSizeSupportedAllowSwapping(
+                    resolution.getWidth(), resolution.getHeight())) {
+                iterator.remove();
+            }
+        }
+        return resolutions;
+    }
+
+    @Nullable
+    private static VideoEncoderInfo findLargestSupportedSizeVideoEncoderInfo(
+            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder,
+            @NonNull VideoValidatedEncoderProfilesProxy encoderProfiles,
+            @NonNull DynamicRange dynamicRange,
+            @NonNull MediaSpec mediaSpec,
+            @NonNull Size resolution,
+            @NonNull Range<Integer> expectedFrameRate) {
+        if (dynamicRange.isFullySpecified()) {
+            return resolveVideoEncoderInfo(videoEncoderInfoFinder, encoderProfiles,
+                    mediaSpec, resolution, dynamicRange, expectedFrameRate);
+        }
+        // There could be multiple VideoProfiles that match the non-fully specified DynamicRange.
+        // The one with the largest supported size will be returned.
+        VideoEncoderInfo sizeLargestVideoEncoderInfo = null;
+        int largestArea = Integer.MIN_VALUE;
+        for (EncoderProfilesProxy.VideoProfileProxy videoProfile :
+                encoderProfiles.getVideoProfiles()) {
+            if (isHdrSettingsMatched(videoProfile, dynamicRange)) {
+                DynamicRange profileDynamicRange = new DynamicRange(
+                        videoProfileHdrFormatsToDynamicRangeEncoding(videoProfile.getHdrFormat()),
+                        videoProfileBitDepthToDynamicRangeBitDepth(videoProfile.getBitDepth()));
+                VideoEncoderInfo videoEncoderInfo =
+                        resolveVideoEncoderInfo(videoEncoderInfoFinder, encoderProfiles,
+                                mediaSpec, resolution, profileDynamicRange, expectedFrameRate);
+                if (videoEncoderInfo == null) {
+                    continue;
+                }
+                // Compare by area size.
+                int area = getArea(videoEncoderInfo.getSupportedWidths().getUpper(),
+                        videoEncoderInfo.getSupportedHeights().getUpper());
+                if (area > largestArea) {
+                    largestArea = area;
+                    sizeLargestVideoEncoderInfo = videoEncoderInfo;
+                }
+            }
+        }
+        return sizeLargestVideoEncoderInfo;
     }
 
     private static boolean hasVideoQualityQuirkAndWorkaroundBySurfaceProcessing() {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/DynamicRangeUtil.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/DynamicRangeUtil.java
index 63736fc..e475a6e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/DynamicRangeUtil.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/DynamicRangeUtil.java
@@ -54,9 +54,11 @@
 import static androidx.camera.core.DynamicRange.SDR;
 import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_10;
 import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_8;
+import static androidx.core.util.Preconditions.checkArgument;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
 
 import android.media.MediaFormat;
 
@@ -78,6 +80,7 @@
 public class DynamicRangeUtil {
     public static final Map<Integer, Set<Integer>> DR_TO_VP_BIT_DEPTH_MAP = new HashMap<>();
     public static final Map<Integer, Set<Integer>> DR_TO_VP_FORMAT_MAP = new HashMap<>();
+    public static final Map<Integer, Integer> VP_TO_DR_BIT_DEPTH = new HashMap<>();
     public static final Map<Integer, Integer> VP_TO_DR_FORMAT_MAP = new HashMap<>();
     private static final Map<String, Map<DynamicRange, Integer>> MIME_TO_DEFAULT_PROFILE_LEVEL_MAP =
             new HashMap<>();
@@ -104,6 +107,10 @@
         DR_TO_VP_FORMAT_MAP.put(ENCODING_DOLBY_VISION,
                 new HashSet<>(singletonList(HDR_DOLBY_VISION)));
 
+        // VideoProfile bit depth to DynamicRange bit depth.
+        VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_8, BIT_DEPTH_8_BIT);
+        VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_10, BIT_DEPTH_10_BIT);
+
         // VideoProfile HDR format to DynamicRange encoding.
         VP_TO_DR_FORMAT_MAP.put(HDR_NONE, ENCODING_SDR);
         VP_TO_DR_FORMAT_MAP.put(HDR_HLG, ENCODING_HLG);
@@ -214,6 +221,28 @@
     }
 
     /**
+     * Returns the encoding of {@link DynamicRange} for the given HDR format of
+     * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy}.
+     *
+     * @throws IllegalArgumentException if the input HDR format is not defined in VideoProfileProxy.
+     */
+    public static int videoProfileHdrFormatsToDynamicRangeEncoding(int hdrFormat) {
+        checkArgument(VP_TO_DR_FORMAT_MAP.containsKey(hdrFormat));
+        return requireNonNull(VP_TO_DR_FORMAT_MAP.get(hdrFormat));
+    }
+
+    /**
+     * Returns the bit depth of {@link DynamicRange} for the given bit depth of
+     * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy}.
+     *
+     * @throws IllegalArgumentException if the input bit depth is not defined in VideoProfileProxy.
+     */
+    public static int videoProfileBitDepthToDynamicRangeBitDepth(int vpBitDepth) {
+        checkArgument(VP_TO_DR_BIT_DEPTH.containsKey(vpBitDepth));
+        return requireNonNull(VP_TO_DR_BIT_DEPTH.get(vpBitDepth));
+    }
+
+    /**
      * Checks if the HDR settings match between a {@link EncoderProfilesProxy.VideoProfileProxy}
      * and a {@link DynamicRange}.
      *
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index 6cad54d..f6bc21a6 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -27,6 +27,11 @@
 import android.media.CamcorderProfile.QUALITY_720P
 import android.media.CamcorderProfile.QUALITY_HIGH
 import android.media.CamcorderProfile.QUALITY_LOW
+import android.media.EncoderProfiles
+import android.media.MediaFormat.MIMETYPE_VIDEO_AV1
+import android.media.MediaFormat.MIMETYPE_VIDEO_AVC
+import android.media.MediaFormat.MIMETYPE_VIDEO_HEVC
+import android.media.MediaRecorder
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
@@ -89,6 +94,8 @@
 import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_QHD
 import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_QVGA
 import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_VGA
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeAudioProfileProxy
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeVideoProfileProxy
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.impl.fakes.FakeCameraFactory
 import androidx.camera.testing.impl.fakes.FakeEncoderProfilesProvider
@@ -597,6 +604,182 @@
     }
 
     @Test
+    fun filterOutEncoderUnsupportedResolutions() {
+        // Arrange.
+        val profileMap = mapOf(
+            QUALITY_HIGH to PROFILES_720P,
+            QUALITY_720P to PROFILES_720P,
+            QUALITY_LOW to PROFILES_720P,
+        )
+        // Arrange: camera supported resolutions are
+        // 1360x1020(4:3), 1280x960(4:3), 1280x720(16:9), 960x720(4:3).
+        setupCamera(
+            profiles = profileMap,
+            supportedResolutions = mapOf(
+                INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE to listOf(
+                    /*HD:4:3*/Size(1360, 1020), Size(1280, 960), Size(960, 720),
+                    /*HD:16:9*/Size(1280, 720),
+                )
+            )
+        )
+        createCameraUseCaseAdapter()
+
+        // Arrange: set 4:3 aspect ratio.
+        // 1360x1020, 1280x960, 960x720 should be candidates of custom resolutions.
+        val videoOutput = createVideoOutput(
+            mediaSpec = MediaSpec.builder().configureVideo {
+                it.setQualitySelector(QualitySelector.from(HD))
+                it.setAspectRatio(RATIO_4_3)
+            }.build(),
+            videoCapabilities = createFakeVideoCapabilities(
+                mapOf(DynamicRange.SDR to profileMap)
+            )
+        )
+        // Arrange: encoder max supported size is 1280x720.
+        val videoCapture = createVideoCapture(
+            videoOutput,
+            dynamicRange = DynamicRange.SDR,
+            videoEncoderInfoFinder = { _ ->
+                FakeVideoEncoderInfo(
+                    supportedWidths = Range.create(2, 1280),
+                    supportedHeights = Range.create(2, 720)
+                )
+            })
+
+        // Act.
+        addAndAttachUseCases(videoCapture)
+
+        // Assert: the largest encoder supported size is 1280x720.
+        // 960x720, 1280x960, 1360x1020 are candidates of custom resolutions.
+        // 1280x960 and 1360x1020 should be filtered out.
+        assertCustomOrderedResolutions(
+            videoCapture,
+            Size(960, 720),
+        )
+    }
+
+    @Test
+    fun filterOutEncoderUnsupportedResolutions_dynamicRangeIsNotFullySpecified() {
+        // Arrange: create HD SDR VideoProfile.
+        val videoProfileHdSdr = createFakeVideoProfileProxy(
+            RESOLUTION_720P.width,
+            RESOLUTION_720P.height,
+            videoMediaType = MIMETYPE_VIDEO_AVC,
+            videoBitDepth = EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_8,
+            videoHdrFormat = EncoderProfiles.VideoProfile.HDR_NONE
+        )
+        // Arrange: create HD HLG10 VideoProfile.
+        val videoProfileHdHlg10 = createFakeVideoProfileProxy(
+            RESOLUTION_720P.width,
+            RESOLUTION_720P.height,
+            videoMediaType = MIMETYPE_VIDEO_HEVC,
+            videoBitDepth = EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_10,
+            videoHdrFormat = EncoderProfiles.VideoProfile.HDR_HLG
+        )
+        // Arrange: create HD HDR10 VideoProfile.
+        val videoProfileHdHdr10 = createFakeVideoProfileProxy(
+            RESOLUTION_720P.width,
+            RESOLUTION_720P.height,
+            videoMediaType = MIMETYPE_VIDEO_AV1,
+            videoBitDepth = EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_10,
+            videoHdrFormat = EncoderProfiles.VideoProfile.HDR_HDR10
+        )
+        // Arrange: create HD EncoderProfiles.
+        val audioProfile = createFakeAudioProfileProxy()
+        val durationSeconds = 20
+        val outputFormat = MediaRecorder.OutputFormat.WEBM
+        val profilesHd = EncoderProfilesProxy.ImmutableEncoderProfilesProxy.create(
+            durationSeconds,
+            outputFormat,
+            listOf(audioProfile),
+            listOf(videoProfileHdSdr, videoProfileHdHlg10, videoProfileHdHdr10)
+        )
+        // Arrange: create profile map.
+        val profileMap = mapOf(
+            QUALITY_HIGH to profilesHd,
+            QUALITY_720P to profilesHd,
+            QUALITY_LOW to profilesHd,
+        )
+        // Arrange: camera supported resolutions are
+        // 1360x1020(4:3), 1280x960(4:3), 1280x720(16:9), 960x720(4:3).
+        setupCamera(
+            profiles = profileMap,
+            supportedResolutions = mapOf(
+                INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE to listOf(
+                    /*HD:4:3*/Size(1360, 1020), Size(1280, 960), Size(960, 720),
+                    /*HD:16:9*/Size(1280, 720),
+                )
+            )
+        )
+        createCameraUseCaseAdapter()
+
+        // Arrange: set 4:3 aspect ratio.
+        // 1360x1020, 1280x960, 960x720 should be candidates of custom resolutions.
+        val videoOutput = createVideoOutput(
+            mediaSpec = MediaSpec.builder().configureVideo {
+                it.setQualitySelector(QualitySelector.from(HD))
+                it.setAspectRatio(RATIO_4_3)
+            }.build(),
+            videoCapabilities = createFakeVideoCapabilities(
+                mapOf(
+                    DynamicRange.HDR_UNSPECIFIED_10_BIT to mapOf(
+                        QUALITY_720P to EncoderProfilesProxy.ImmutableEncoderProfilesProxy.create(
+                            durationSeconds,
+                            outputFormat,
+                            listOf(audioProfile),
+                            listOf(videoProfileHdHlg10, videoProfileHdHdr10)
+                        )
+                    )
+                )
+            )
+        )
+        // Arrange: set HDR_UNSPECIFIED_10_BIT and HDR encoder max supported size 1280x960
+        val videoCapture = createVideoCapture(
+            videoOutput,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT,
+            videoEncoderInfoFinder = { config ->
+                when (config.mimeType) {
+                    MIMETYPE_VIDEO_AVC -> { // SDR: H263
+                        FakeVideoEncoderInfo(
+                            supportedWidths = Range.create(2, 1920),
+                            supportedHeights = Range.create(2, 1080)
+                        )
+                    }
+
+                    MIMETYPE_VIDEO_HEVC -> { // HLG10: H264
+                        FakeVideoEncoderInfo(
+                            supportedWidths = Range.create(2, 1280),
+                            supportedHeights = Range.create(2, 720)
+                        )
+                    }
+
+                    MIMETYPE_VIDEO_AV1 -> { // HDR10: HEVC
+                        FakeVideoEncoderInfo(
+                            supportedWidths = Range.create(2, 1280),
+                            supportedHeights = Range.create(2, 960)
+                        )
+                    }
+
+                    else -> {
+                        throw AssertionError("Unknown mimeType: " + config.mimeType)
+                    }
+                }
+            })
+
+        // Act.
+        addAndAttachUseCases(videoCapture)
+
+        // Assert: the largest encoder supported size is 1280x960.
+        // 960x720, 1280x960, 1360x1020 are candidates of custom resolutions.
+        // 1360x1020 should be filtered out.
+        assertCustomOrderedResolutions(
+            videoCapture,
+            Size(960, 720),
+            Size(1280, 960),
+        )
+    }
+
+    @Test
     fun adjustInvalidResolution() {
         // Arrange.
         setupCamera()
diff --git a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
index b0208c1..266c716a 100644
--- a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
+++ b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
@@ -27,24 +27,16 @@
 import androidx.compose.foundation.AndroidExternalSurface
 import androidx.compose.foundation.AndroidExternalSurfaceScope
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.setFrom
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
 import kotlin.coroutines.resume
 import kotlinx.coroutines.suspendCancellableCoroutine
 
@@ -73,19 +65,12 @@
     transformationInfo: TransformationInfo,
     modifier: Modifier = Modifier
 ) {
-    var parentViewSize by remember { mutableStateOf(IntSize.Zero) }
     val resolution = surfaceRequest.resolution
 
-    IntSize(transformationInfo.cropRectRight - transformationInfo.cropRectLeft,
-        transformationInfo.cropRectTop - transformationInfo.cropRectBottom)
-
     Box(
         modifier = modifier
-            .onSizeChanged {
-                parentViewSize = it
-            }
             .clipToBounds()
-            .wrapContentSize(unbounded = true, align = Alignment.Center)
+            .fillMaxSize()
     ) {
         key(surfaceRequest) {
             TransformedSurface(
@@ -100,7 +85,6 @@
 
                     // TODO(b/322420176): Properly handle onSurfaceChanged()
                 },
-                getParentSize = { parentViewSize },
             )
         }
     }
@@ -109,7 +93,7 @@
 // TODO(b/322420450): Properly release surface when this is cancelled
 private suspend fun ViewfinderSurfaceRequest.provideSurface(surface: Surface): Surface =
     suspendCancellableCoroutine {
-        this.provideSurface(surface, Runnable::run) { result ->
+        this.provideSurface(surface, Runnable::run) { result: ViewfinderSurfaceRequest.Result? ->
             it.resume(requireNotNull(result) {
                 "Expected non-null result from ViewfinderSurfaceRequest, but received null."
             }.surface)
@@ -123,7 +107,6 @@
     transformationInfo: TransformationInfo,
     implementationMode: ImplementationMode,
     onInit: AndroidExternalSurfaceScope.() -> Unit,
-    getParentSize: () -> IntSize
 ) {
     // For TextureView, correct the orientation to match the target rotation.
     val correctionMatrix = Matrix()
@@ -136,42 +119,51 @@
         )
     }
 
-    val getSurfaceRectInViewFinder = {
-        SurfaceTransformationUtil.getTransformedSurfaceRect(
-            resolution,
-            transformationInfo,
-            getParentSize().toSize()
-        )
-    }
+    val surfaceModifier = Modifier.layout { measurable, constraints ->
+            val placeable = measurable.measure(
+                Constraints.fixed(resolution.width, resolution.height)
+            )
 
-    val getViewFinderScaleX = { getSurfaceRectInViewFinder().width() / resolution.width }
-    val getViewFinderScaleY = { getSurfaceRectInViewFinder().height() / resolution.height }
+            // When the child placeable is larger than the parent's constraints, rather
+            // than the child overflowing through the right or bottom of the parent, it overflows
+            // evenly on all sides, as if it's placed exactly in the center of the parent.
+            // To compensate for this, we must offset the child by the amount it overflows
+            // so it is consistently placed in the top left corner of the parent before
+            // we apply scaling and translation in the graphics layer.
+            val widthOffset = 0.coerceAtLeast((placeable.width - constraints.maxWidth) / 2)
+            val heightOffset = 0.coerceAtLeast((placeable.height - constraints.maxHeight) / 2)
+            layout(placeable.width, placeable.height) {
+                placeable.placeWithLayer(widthOffset, heightOffset) {
+                    val surfaceRectInViewfinder =
+                        SurfaceTransformationUtil.getTransformedSurfaceRect(
+                            resolution,
+                            transformationInfo,
+                            Size(constraints.maxWidth, constraints.maxHeight)
+                        )
 
-    val heightDp = with(LocalDensity.current) { resolution.height.toDp() }
-    val widthDp = with(LocalDensity.current) { resolution.width.toDp() }
+                    transformOrigin = TransformOrigin(0f, 0f)
+                    scaleX = surfaceRectInViewfinder.width() / resolution.width
+                    scaleY = surfaceRectInViewfinder.height() / resolution.height
 
-    val getModifier: () -> Modifier = {
-        Modifier
-            .height(heightDp)
-            .width(widthDp)
-            .scale(getViewFinderScaleX(), getViewFinderScaleY())
-    }
+                    translationX = surfaceRectInViewfinder.left
+                    translationY = surfaceRectInViewfinder.top
+                }
+            }
+        }
 
     when (implementationMode) {
         ImplementationMode.PERFORMANCE -> {
             AndroidExternalSurface(
-                modifier = getModifier(),
+                modifier = surfaceModifier,
                 onInit = onInit
             )
         }
         ImplementationMode.COMPATIBLE -> {
             AndroidEmbeddedExternalSurface(
-                modifier = getModifier(),
+                modifier = surfaceModifier,
                 transform = correctionMatrix,
                 onInit = onInit
             )
         }
     }
 }
-
-private fun IntSize.toSize() = Size(this.width, this.height)
diff --git a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
index 33cfd27..f2a118d 100644
--- a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
+++ b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
@@ -17,13 +17,12 @@
 package androidx.camera.viewfinder.compose.internal
 
 import android.graphics.Matrix
-import android.graphics.Rect
 import android.graphics.RectF
 import android.util.Size
 import androidx.camera.viewfinder.surface.TransformationInfo
 
 /**
- * A util class with methods that transform the input viewFinder surface so that its preview fits
+ * A util class with methods that transform the input viewfinder surface so that its preview fits
  * the given aspect ratio of its parent view.
  *
  * The goal is to transform it in a way so that the entire area of
@@ -50,23 +49,23 @@
     ): Size {
         return if (TransformUtil.is90or270(transformationInfo.sourceRotation)) {
             Size(
-                transformationInfo.cropRectTop - transformationInfo.cropRectBottom,
+                transformationInfo.cropRectBottom - transformationInfo.cropRectTop,
                 transformationInfo.cropRectRight - transformationInfo.cropRectLeft)
         } else {
             Size(
                 transformationInfo.cropRectRight - transformationInfo.cropRectLeft,
-                transformationInfo.cropRectTop - transformationInfo.cropRectBottom)
+                transformationInfo.cropRectBottom - transformationInfo.cropRectTop)
         }
     }
 
     private fun isViewportAspectRatioMatchViewFinder(
         transformationInfo: TransformationInfo,
-        viewFinderSize: Size
+        viewfinderSize: Size
     ): Boolean {
         // Using viewport rect to check if the viewport is based on the view finder.
         val rotatedViewportSize: Size = getRotatedViewportSize(transformationInfo)
         return TransformUtil.isAspectRatioMatchingWithRoundingError(
-            viewFinderSize,
+            viewfinderSize,
             true,
             rotatedViewportSize,
             false
@@ -81,16 +80,16 @@
         matrix.invert(matrix)
     }
 
-    private fun getViewFinderViewportRectForMismatchedAspectRatios(
+    private fun getViewfinderViewportRectForMismatchedAspectRatios(
         transformationInfo: TransformationInfo,
-        viewFinderSize: Size
+        viewfinderSize: Size
     ): RectF {
-        val viewFinderRect =
+        val viewfinderRect =
             RectF(
                 0f,
                 0f,
-                viewFinderSize.width.toFloat(),
-                viewFinderSize.height.toFloat()
+                viewfinderSize.width.toFloat(),
+                viewfinderSize.height.toFloat()
             )
         val rotatedViewportSize = getRotatedViewportSize(transformationInfo)
         val rotatedViewportRect =
@@ -104,45 +103,46 @@
         setMatrixRectToRect(
             matrix,
             rotatedViewportRect,
-            viewFinderRect
+            viewfinderRect
         )
         matrix.mapRect(rotatedViewportRect)
         return rotatedViewportRect
     }
 
     private fun getSurfaceToViewFinderMatrix(
-        viewFinderSize: Size,
+        viewfinderSize: Size,
         transformationInfo: TransformationInfo,
     ): Matrix {
         // Get the target of the mapping, the coordinates of the crop rect in view finder.
-        val viewFinderCropRect: RectF =
-            if (isViewportAspectRatioMatchViewFinder(transformationInfo, viewFinderSize)) {
+        val viewfinderCropRect: RectF =
+            if (isViewportAspectRatioMatchViewFinder(transformationInfo, viewfinderSize)) {
                 // If crop rect has the same aspect ratio as view finder, scale the crop rect to
                 // fill the entire view finder. This happens if the scale type is FILL_* AND a
                 // view-finder-based viewport is used.
                 RectF(
                     0f,
                     0f,
-                    viewFinderSize.width.toFloat(),
-                    viewFinderSize.height.toFloat()
+                    viewfinderSize.width.toFloat(),
+                    viewfinderSize.height.toFloat()
                 )
             } else {
                 // If the aspect ratios don't match, it could be 1) scale type is FIT_*, 2) the
                 // Viewport is not based on the view finder or 3) both.
-                getViewFinderViewportRectForMismatchedAspectRatios(
+                getViewfinderViewportRectForMismatchedAspectRatios(
                     transformationInfo,
-                    viewFinderSize
+                    viewfinderSize
                 )
             }
 
-        val rect = Rect(transformationInfo.cropRectLeft,
-                        transformationInfo.cropRectTop,
-                        transformationInfo.cropRectRight,
-                        transformationInfo.cropRectBottom)
+        val surfaceCropRect = RectF(transformationInfo.cropRectLeft.toFloat(),
+                        transformationInfo.cropRectTop.toFloat(),
+                        transformationInfo.cropRectRight.toFloat(),
+                        transformationInfo.cropRectBottom.toFloat())
+
         val matrix =
             TransformUtil.getRectToRect(
-                RectF(),
-                viewFinderCropRect,
+                surfaceCropRect,
+                viewfinderCropRect,
                 transformationInfo.sourceRotation
             )
 
@@ -155,8 +155,8 @@
                 matrix.preScale(
                     1f,
                     -1f,
-                    rect.centerX().toFloat(),
-                    rect.centerY().toFloat()
+                    surfaceCropRect.centerX(),
+                    surfaceCropRect.centerY()
                 )
             } else {
                 // If the rotation is 0/180, the Surface should be flipped horizontally.
@@ -166,8 +166,8 @@
                 matrix.preScale(
                     -1f,
                     1f,
-                    rect.centerX().toFloat(),
-                    rect.centerY().toFloat()
+                    surfaceCropRect.centerX(),
+                    surfaceCropRect.centerY()
                 )
             }
         }
@@ -177,11 +177,11 @@
     fun getTransformedSurfaceRect(
         resolution: Size,
         transformationInfo: TransformationInfo,
-        viewFinderSize: Size,
+        viewfinderSize: Size,
     ): RectF {
         val surfaceToViewFinder: Matrix =
             getSurfaceToViewFinderMatrix(
-                viewFinderSize,
+                viewfinderSize,
                 transformationInfo,
             )
         val rect = RectF(0f, 0f, resolution.width.toFloat(), resolution.height.toFloat())
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 98a7d6a..1e7bafb5 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -60,7 +60,6 @@
 }
 
 dependencies {
-
     // Internal library
     implementation(libs.kotlinStdlib)
     implementation(project(":camera:camera-core"))
@@ -70,7 +69,7 @@
     implementation(project(":camera:camera-view"))
     implementation(project(":camera:camera-video"))
     implementation(project(":camera:camera-mlkit-vision"))
-    implementation 'com.google.mlkit:barcode-scanning:17.0.2'
+    implementation("com.google.mlkit:barcode-scanning:17.0.2")
 
     // Android Support Library
     implementation("androidx.appcompat:appcompat:1.2.0")
@@ -88,15 +87,14 @@
     implementation(libs.guavaAndroid)
 
     // Compose
-    implementation 'androidx.activity:activity-compose:1.4.0'
-    implementation project(':compose:material:material')
-    implementation 'androidx.compose.animation:animation:1.4.0'
-    implementation 'androidx.compose.runtime:runtime:1.4.0'
-    implementation 'androidx.compose.ui:ui-tooling:1.4.0'
-    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
-    implementation 'androidx.navigation:navigation-compose:2.4.2'
-    implementation 'androidx.compose.material:material-icons-extended:1.4.0'
-    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
+    implementation("androidx.activity:activity-compose:1.4.0")
+    implementation(project(":compose:material:material"))
+    implementation("androidx.compose.animation:animation:1.4.0")
+    implementation("androidx.compose.runtime:runtime:1.4.0")
+    implementation("androidx.compose.ui:ui-tooling:1.4.0")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1")
+    implementation("androidx.navigation:navigation-compose:2.4.2")
+    implementation("androidx.compose.material:material-icons-extended:1.4.0")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
     androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
@@ -125,16 +123,14 @@
 
     // Testing for Compose
     // Test rules and transitive dependencies:
-    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-    androidTestImplementation(project(":compose:runtime:runtime"))
     androidTestImplementation(project(":compose:ui:ui"))
-    androidTestImplementation(project(":compose:runtime:runtime-saveable"))
     androidTestImplementation(project(":compose:ui:ui-geometry"))
     androidTestImplementation(project(":compose:ui:ui-util"))
     androidTestImplementation(project(":compose:ui:ui-graphics"))
     androidTestImplementation(project(":compose:ui:ui-unit"))
     androidTestImplementation(project(":compose:ui:ui-text"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation("androidx.collection:collection:1.4.0")
     // Needed for createComposeRule, but not createAndroidComposeRule:
-    debugImplementation(project(":compose:ui:ui-test-manifest"))
+    debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0")
 }
diff --git a/car/app/app-samples/showcase/common/lint-baseline.xml b/car/app/app-samples/showcase/common/lint-baseline.xml
index af8abfd..0d0b951 100644
--- a/car/app/app-samples/showcase/common/lint-baseline.xml
+++ b/car/app/app-samples/showcase/common/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanThreadSleep"
@@ -12,7 +12,7 @@
 
     <issue
         id="UnspecifiedRegisterReceiverFlag"
-        message="`mBroadcastReceiver` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for androidx.car.app.sample.showcase.common.INTENT_ACTION_PRIMARY_PHONE, androidx.car.app.sample.showcase.common.INTENT_ACTION_SECONDARY_PHONE"
+        message="`mBroadcastReceiver` is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected broadcasts registered for androidx.car.app.sample.showcase.common.INTENT_ACTION_PRIMARY_PHONE, androidx.car.app.sample.showcase.common.INTENT_ACTION_SECONDARY_PHONE"
         errorLine1="        getCarContext().registerReceiver(mBroadcastReceiver, filter);"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/cardview/cardview/lint-baseline.xml b/cardview/cardview/lint-baseline.xml
deleted file mode 100644
index cc40871..0000000
--- a/cardview/cardview/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 21 (current min is 19): `CardViewApi21Impl`"
-        errorLine1="        if (!(IMPL instanceof CardViewApi21Impl)) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/cardview/widget/CardView.java"/>
-    </issue>
-
-</issues>
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 387bef3..9c394df 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -34,7 +34,7 @@
     method public boolean addAll(java.util.Collection<? extends E> elements);
     method public void clear();
     method public operator boolean contains(E element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends E> elements);
     method public void ensureCapacity(int minimumCapacity);
     method public int getSize();
     method public int indexOf(Object? key);
@@ -42,9 +42,9 @@
     method public java.util.Iterator<E> iterator();
     method public boolean remove(E element);
     method public boolean removeAll(androidx.collection.ArraySet<? extends E> array);
-    method public boolean removeAll(java.util.Collection<E> elements);
+    method public boolean removeAll(java.util.Collection<? extends E> elements);
     method public E removeAt(int index);
-    method public boolean retainAll(java.util.Collection<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
     method public Object?[] toArray();
     method public <T> T[] toArray(T[] array);
     method public E valueAt(int index);
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 8b4f85d..042ced8 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -34,7 +34,7 @@
     method public boolean addAll(java.util.Collection<? extends E> elements);
     method public void clear();
     method public operator boolean contains(E element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends E> elements);
     method public void ensureCapacity(int minimumCapacity);
     method public int getSize();
     method public int indexOf(Object? key);
@@ -42,9 +42,9 @@
     method public java.util.Iterator<E> iterator();
     method public boolean remove(E element);
     method public boolean removeAll(androidx.collection.ArraySet<? extends E> array);
-    method public boolean removeAll(java.util.Collection<E> elements);
+    method public boolean removeAll(java.util.Collection<? extends E> elements);
     method public E removeAt(int index);
-    method public boolean retainAll(java.util.Collection<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
     method public Object?[] toArray();
     method public <T> T[] toArray(T[] array);
     method public E valueAt(int index);
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 2c95ad2..dea379c 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -276,6 +276,15 @@
     method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> generateDecayAnimationSpec(androidx.compose.animation.core.FloatDecayAnimationSpec);
   }
 
+  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimatableApi public final class DeferredTargetAnimation<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public DeferredTargetAnimation(androidx.compose.animation.core.TwoWayConverter<T,V> vectorConverter);
+    method public T? getPendingTarget();
+    method public boolean isIdle();
+    method public T updateTarget(T target, kotlinx.coroutines.CoroutineScope coroutineScope, optional androidx.compose.animation.core.FiniteAnimationSpec<T> animationSpec);
+    property public final boolean isIdle;
+    property public final T? pendingTarget;
+  }
+
   public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.FiniteAnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -366,6 +375,9 @@
     property public static final androidx.compose.animation.core.Easing LinearOutSlowInEasing;
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalAnimatableApi {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental animation API for AnimationSpec. " + "It may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalAnimationSpecApi {
   }
 
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index d3a92c6..4824942 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -276,6 +276,15 @@
     method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> generateDecayAnimationSpec(androidx.compose.animation.core.FloatDecayAnimationSpec);
   }
 
+  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimatableApi public final class DeferredTargetAnimation<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public DeferredTargetAnimation(androidx.compose.animation.core.TwoWayConverter<T,V> vectorConverter);
+    method public T? getPendingTarget();
+    method public boolean isIdle();
+    method public T updateTarget(T target, kotlinx.coroutines.CoroutineScope coroutineScope, optional androidx.compose.animation.core.FiniteAnimationSpec<T> animationSpec);
+    property public final boolean isIdle;
+    property public final T? pendingTarget;
+  }
+
   public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.FiniteAnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -366,6 +375,9 @@
     property public static final androidx.compose.animation.core.Easing LinearOutSlowInEasing;
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalAnimatableApi {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental animation API for AnimationSpec. " + "It may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalAnimationSpecApi {
   }
 
diff --git a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/TransitionBenchmark.kt b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/TransitionBenchmark.kt
new file mode 100644
index 0000000..1abe89d
--- /dev/null
+++ b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/TransitionBenchmark.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.animation.core.benchmark
+
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TransitionBenchmark {
+
+    @get:Rule
+    val rule = ComposeBenchmarkRule()
+
+    @Test
+    fun createTransitionThroughUpdateTransition() {
+        rule.benchmarkFirstCompose(::TransitionBenchmarkCaseFactory)
+    }
+}
+
+private class TransitionBenchmarkCaseFactory : LayeredComposeTestCase() {
+    @Composable
+    override fun MeasuredContent() {
+        updateTransition(targetState = Unit, label = null)
+    }
+}
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index c175361..fbc84ec 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -40,9 +40,10 @@
     sourceSets {
         commonMain {
             dependencies {
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui"))
-                implementation(project(":compose:ui:ui-unit"))
+                implementation("androidx.compose.runtime:runtime:1.6.0")
+                implementation("androidx.compose.ui:ui:1.6.0")
+                implementation("androidx.compose.ui:ui-unit:1.6.0")
+                implementation(project(":compose:ui:ui-graphics"))
                 implementation(project(":compose:ui:ui-util"))
                 implementation("androidx.collection:collection:1.4.0")
                 implementation(libs.kotlinStdlibCommon)
@@ -75,11 +76,6 @@
         desktopMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation(libs.kotlinStdlib)
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui"))
-                implementation(project(":compose:ui:ui-unit"))
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
diff --git a/compose/animation/animation-core/lint-baseline.xml b/compose/animation/animation-core/lint-baseline.xml
index f95935a..82c2f30 100644
--- a/compose/animation/animation-core/lint-baseline.xml
+++ b/compose/animation/animation-core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanSuppressTag"
@@ -76,17 +76,8 @@
     <issue
         id="PrimitiveInCollection"
         message="constructor VectorizedKeyframesSpec has parameter keyframes with type Map&lt;Integer, ? extends Pair&lt;? extends V, ? extends Easing>>: replace with IntObjectMap"
-        errorLine1="    private val keyframes: Map&lt;Int, Pair&lt;V, Easing>>,"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field keyframes with type Map&lt;Integer, Pair&lt;V, Easing>>: replace with IntObjectMap"
-        errorLine1="    private val keyframes: Map&lt;Int, Pair&lt;V, Easing>>,"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        errorLine1="        keyframes: Map&lt;Int, Pair&lt;V, Easing>>,"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt"/>
     </issue>
diff --git a/compose/animation/animation-core/samples/build.gradle b/compose/animation/animation-core/samples/build.gradle
index dc3cfcbd..f566a97 100644
--- a/compose/animation/animation-core/samples/build.gradle
+++ b/compose/animation/animation-core/samples/build.gradle
@@ -36,7 +36,7 @@
     implementation(project(":compose:animation:animation"))
     implementation(project(":compose:animation:animation-core"))
     implementation(project(":compose:runtime:runtime"))
-    implementation("androidx.compose.ui:ui:1.2.1")
+    implementation(project(":compose:ui:ui"))
     implementation("androidx.compose.ui:ui-unit:1.2.1")
     implementation("androidx.compose.foundation:foundation:1.2.1")
     implementation("androidx.compose.foundation:foundation-layout:1.2.1")
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt
index 160a753..0ddab6d 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt
@@ -19,6 +19,9 @@
 import androidx.annotation.Sampled
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationEndReason
+import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.calculateTargetValue
@@ -27,18 +30,29 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.splineBasedDecay
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.verticalDrag
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
@@ -48,7 +62,10 @@
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
@@ -219,3 +236,69 @@
         this.graphicsLayer(alpha = alphaAnimation.value)
     }
 }
+
+@OptIn(ExperimentalAnimatableApi::class, ExperimentalComposeUiApi::class)
+@Sampled
+@Composable
+fun DeferredTargetAnimationSample() {
+    // Creates a custom modifier that animates the constraints and measures child with the
+    // animated constraints. This modifier is built on top of `Modifier.approachLayout` to approach
+    // th destination size determined by the lookahead pass. A resize animation will be kicked off
+    // whenever the lookahead size changes, to animate children from current size to destination
+    // size. Fixed constraints created based on the animation value will be used to measure
+    // child, so the child layout gradually changes its animated constraints until the approach
+    // completes.
+    fun Modifier.animateConstraints(
+        sizeAnimation: DeferredTargetAnimation<IntSize, AnimationVector2D>,
+        coroutineScope: CoroutineScope
+    ) = this.approachLayout(
+        isMeasurementApproachComplete = { lookaheadSize ->
+            // Update the target of the size animation.
+            sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
+            // Return true if the size animation has no pending target change and has finished
+            // running.
+            sizeAnimation.isIdle
+        }
+    ) { measurable, _ ->
+        // In the measurement approach, the goal is to gradually reach the destination size
+        // (i.e. lookahead size). To achieve that, we use an animation to track the current
+        // size, and animate to the destination size whenever it changes. Once the animation
+        // finishes, the approach is complete.
+
+        // First, update the target of the animation, and read the current animated size.
+        val (width, height) = sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
+        // Then create fixed size constraints using the animated size
+        val animatedConstraints = Constraints.fixed(width, height)
+        // Measure child with animated constraints.
+        val placeable = measurable.measure(animatedConstraints)
+        layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
+        }
+    }
+
+    var fullWidth by remember { mutableStateOf(false) }
+
+    // Creates a size animation with a target unknown at the time of instantiation.
+    val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
+    val coroutineScope = rememberCoroutineScope()
+    Row(
+        (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp))
+            .height(200.dp)
+            // Use the custom modifier created above to animate the constraints passed
+            // to the child, and therefore resize children in an animation.
+            .animateConstraints(sizeAnimation, coroutineScope)
+            .clickable { fullWidth = !fullWidth }) {
+        Box(
+            Modifier
+                .weight(1f)
+                .fillMaxHeight()
+                .background(Color(0xffff6f69)),
+        )
+        Box(
+            Modifier
+                .weight(2f)
+                .fillMaxHeight()
+                .background(Color(0xffffcc5c))
+        )
+    }
+}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt
deleted file mode 100644
index 62b6424..0000000
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Copyright 2023 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.compose.animation.core
-
-import androidx.collection.FloatFloatPair
-import androidx.compose.ui.graphics.PathSegment
-import androidx.compose.ui.util.fastCbrt
-import androidx.compose.ui.util.fastCoerceIn
-import kotlin.math.abs
-import kotlin.math.acos
-import kotlin.math.cos
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.sqrt
-
-private const val Tau = Math.PI * 2.0
-private const val Epsilon = 1e-7
-// We use a fairly high epsilon here because it's post double->float conversion
-// and because we use a fast approximation of cbrt(). The epsilon we use here is
-// the max error of fastCbrt() in the -1f..1f range.
-private const val FloatEpsilon = 8.3446500e-7f
-
-/**
- * Evaluate the specified [segment] at position [t] and returns the X
- * coordinate of the segment's curve at that position.
- */
-private fun evaluateX(
-    segment: PathSegment,
-    t: Float
-): Float {
-    val points = segment.points
-
-    return when (segment.type) {
-        PathSegment.Type.Move -> points[0]
-
-        PathSegment.Type.Line -> {
-            evaluateLine(
-                points[0],
-                points[2],
-                t
-            )
-        }
-
-        PathSegment.Type.Quadratic -> {
-            evaluateQuadratic(
-                points[0],
-                points[2],
-                points[4],
-                t
-            )
-        }
-
-        // We convert all conics to cubics, won't happen
-        PathSegment.Type.Conic -> Float.NaN
-
-        PathSegment.Type.Cubic -> {
-            evaluateCubic(
-                points[0],
-                points[2],
-                points[4],
-                points[6],
-                t
-            )
-        }
-
-        PathSegment.Type.Close -> Float.NaN
-        PathSegment.Type.Done -> Float.NaN
-    }
-}
-
-/**
- * Evaluate the specified [segment] at position [t] and returns the Y
- * coordinate of the segment's curve at that position.
- */
-internal fun evaluateY(
-    segment: PathSegment,
-    t: Float
-): Float {
-    val points = segment.points
-
-    return when (segment.type) {
-        PathSegment.Type.Move -> points[1]
-
-        PathSegment.Type.Line -> {
-            evaluateLine(
-                points[1],
-                points[3],
-                t
-            )
-        }
-
-        PathSegment.Type.Quadratic -> {
-            evaluateQuadratic(
-                points[1],
-                points[3],
-                points[5],
-                t
-            )
-        }
-
-        // We convert all conics to cubics, won't happen
-        PathSegment.Type.Conic -> Float.NaN
-
-        PathSegment.Type.Cubic -> {
-            evaluateCubic(
-                points[1],
-                points[3],
-                points[5],
-                points[7],
-                t
-            )
-        }
-
-        PathSegment.Type.Close -> Float.NaN
-        PathSegment.Type.Done -> Float.NaN
-    }
-}
-
-private fun evaluateLine(
-    p0y: Float,
-    p1y: Float,
-    t: Float
-) = (p1y - p0y) * t + p0y
-
-private fun evaluateQuadratic(
-    p0: Float,
-    p1: Float,
-    p2: Float,
-    t: Float
-): Float {
-    val by = 2.0f * (p1 - p0)
-    val ay = p2 - 2.0f * p1 + p0
-    return (ay * t + by) * t + p0
-}
-
-private fun evaluateCubic(
-    p0: Float,
-    p1: Float,
-    p2: Float,
-    p3: Float,
-    t: Float
-): Float {
-    val a = p3 + 3.0f * (p1 - p2) - p0
-    val b = 3.0f * (p2 - 2.0f * p1 + p0)
-    val c = 3.0f * (p1 - p0)
-    return ((a * t + b) * t + c) * t + p0
-}
-
-/**
- * Evaluates a cubic Bézier curve at position [t] along the curve. The curve is
- * defined by the start point (0, 0), the end point (0, 0) and two control points
- * of respective coordinates [p1] and [p2].
- */
-@Suppress("UnnecessaryVariable")
-internal fun evaluateCubic(
-    p1: Float,
-    p2: Float,
-    t: Float
-): Float {
-    val a = 1.0f / 3.0f + (p1 - p2)
-    val b = (p2 - 2.0f * p1)
-    val c = p1
-    return 3.0f * ((a * t + b) * t + c) * t
-}
-
-/**
- * Finds the first real root of the specified [segment].
- * If no root can be found, this method returns [Float.NaN].
- */
-internal fun findFirstRoot(
-    segment: PathSegment,
-    fraction: Float
-): Float {
-    val points = segment.points
-    return when (segment.type) {
-        PathSegment.Type.Move -> Float.NaN
-
-        PathSegment.Type.Line -> {
-            findFirstLineRoot(
-                points[0] - fraction,
-                points[2] - fraction,
-            )
-        }
-
-        PathSegment.Type.Quadratic -> findFirstQuadraticRoot(
-            points[0] - fraction,
-            points[2] - fraction,
-            points[4] - fraction
-        )
-
-        // We convert all conics to cubics, won't happen
-        PathSegment.Type.Conic -> Float.NaN
-
-        PathSegment.Type.Cubic -> findFirstCubicRoot(
-            points[0] - fraction,
-            points[2] - fraction,
-            points[4] - fraction,
-            points[6] - fraction
-        )
-
-        PathSegment.Type.Close -> Float.NaN
-        PathSegment.Type.Done -> Float.NaN
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-private inline fun findFirstLineRoot(p0: Float, p1: Float) =
-    clampValidRootInUnitRange(-p0 / (p1 - p0))
-
-/**
- * Finds the first real root of a quadratic Bézier curve:
- * - [p0]: coordinate of the start point
- * - [p1]: coordinate of the control point
- * - [p2]: coordinate of the end point
- *
- * If no root can be found, this method returns [Float.NaN].
- */
-private fun findFirstQuadraticRoot(
-    p0: Float,
-    p1: Float,
-    p2: Float
-): Float {
-    val a = p0.toDouble()
-    val b = p1.toDouble()
-    val c = p2.toDouble()
-    val d = a - 2.0 * b + c
-
-    if (d != 0.0) {
-        val v1 = -sqrt(b * b - a * c)
-        val v2 = -a + b
-
-        val root = clampValidRootInUnitRange((-(v1 + v2) / d).toFloat())
-        if (!root.isNaN()) return root
-
-        return clampValidRootInUnitRange(((v1 - v2) / d).toFloat())
-    } else if (b != c) {
-        return clampValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat())
-    }
-
-    return Float.NaN
-}
-
-/**
- * Finds the first real root of a cubic Bézier curve:
- * - [p0]: coordinate of the start point
- * - [p1]: coordinate of the first control point
- * - [p2]: coordinate of the second control point
- * - [p3]: coordinate of the end point
- *
- * If no root can be found, this method returns [Float.NaN].
- */
-internal fun findFirstCubicRoot(
-    p0: Float,
-    p1: Float,
-    p2: Float,
-    p3: Float
-): Float {
-    // This function implements Cardano's algorithm as described in "A Primer on Bézier Curves":
-    // https://pomax.github.io/bezierinfo/#yforx
-    //
-    // The math used to find the roots is explained in "Solving the Cubic Equation":
-    // http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
-
-    var a = 3.0 * (p0 - 2.0 * p1 + p2)
-    var b = 3.0 * (p1 - p0)
-    var c = p0.toDouble()
-    val d = -p0 + 3.0 * (p1 - p2) + p3
-
-    // Not a cubic
-    if (d.closeTo(0.0)) {
-        // Not a quadratic
-        if (a.closeTo(0.0)) {
-            // No solutions
-            if (b.closeTo(0.0)) {
-                return Float.NaN
-            }
-            return clampValidRootInUnitRange((-c / b).toFloat())
-        } else {
-            val q = sqrt(b * b - 4.0 * a * c)
-            val a2 = 2.0 * a
-
-            val root = clampValidRootInUnitRange(((q - b) / a2).toFloat())
-            if (!root.isNaN()) return root
-
-            return clampValidRootInUnitRange(((-b - q) / a2).toFloat())
-        }
-    }
-
-    a /= d
-    b /= d
-    c /= d
-
-    val o3 = (3.0 * b - a * a) / 9.0
-    val q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
-    val discriminant = q2 * q2 + o3 * o3 * o3
-    val a3 = a / 3.0
-
-    if (discriminant < 0.0) {
-        val mp33 = -(o3 * o3 * o3)
-        val r = sqrt(mp33)
-        val t = -q2 / r
-        val cosPhi = t.fastCoerceIn(-1.0, 1.0)
-        val phi = acos(cosPhi)
-        val t1 = 2.0f * fastCbrt(r.toFloat())
-
-        var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a3).toFloat())
-        if (!root.isNaN()) return root
-
-        root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a3).toFloat())
-        if (!root.isNaN()) return root
-
-        return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a3).toFloat())
-    } else if (discriminant == 0.0) { // TODO: closeTo(0.0)?
-        val u1 = -fastCbrt(q2.toFloat())
-
-        val root = clampValidRootInUnitRange(2.0f * u1 - a3.toFloat())
-        if (!root.isNaN()) return root
-
-        return clampValidRootInUnitRange(-u1 - a3.toFloat())
-    }
-
-    val sd = sqrt(discriminant)
-    val u1 = fastCbrt((-q2 + sd).toFloat())
-    val v1 = fastCbrt((q2 + sd).toFloat())
-
-    return clampValidRootInUnitRange((u1 - v1 - a3).toFloat())
-}
-
-/**
- * Finds the real root of a line defined by the X coordinates of its start ([p0])
- * and end ([p1]) points. The root, if any, is written in the [roots] array at
- * [index]. Returns 1 if a root was found, 0 otherwise.
- */
-@Suppress("NOTHING_TO_INLINE")
-private inline fun findLineRoot(p0: Float, p1: Float, roots: FloatArray, index: Int = 0) =
-    writeValidRootInUnitRange(-p0 / (p1 - p0), roots, index)
-
-/**
- * Finds the real roots of a quadratic Bézier curve. To find the roots, only the X
- * coordinates of the four points are required:
- * - [p0]: x coordinate of the start point
- * - [p1]: x coordinate of the control point
- * - [p2]: x coordinate of the end point
- *
- * Any root found is written in the [roots] array, starting at [index]. The
- * function returns the number of roots found and written to the array.
- */
-private fun findQuadraticRoots(
-    p0: Float,
-    p1: Float,
-    p2: Float,
-    roots: FloatArray,
-    index: Int = 0
-): Int {
-    val a = p0.toDouble()
-    val b = p1.toDouble()
-    val c = p2.toDouble()
-    val d = a - 2.0 * b + c
-
-    var rootCount = 0
-
-    if (d != 0.0) {
-        val v1 = -sqrt(b * b - a * c)
-        val v2 = -a + b
-
-        rootCount += writeValidRootInUnitRange(
-            (-(v1 + v2) / d).toFloat(), roots, index
-        )
-        rootCount += writeValidRootInUnitRange(
-            ((v1 - v2) / d).toFloat(), roots, index + rootCount
-        )
-    } else if (b != c) {
-        rootCount += writeValidRootInUnitRange(
-            ((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat(), roots, index
-        )
-    }
-
-    return rootCount
-}
-
-/**
- * Finds the roots of the derivative of the curve described by [segment].
- * The roots, if any, are written in the [roots] array starting at [index].
- * The function returns the number of roots founds and written into the array.
- * The [roots] array must be able to hold at least 5 floats starting at [index].
- */
-private fun findDerivativeRoots(
-    segment: PathSegment,
-    roots: FloatArray,
-    index: Int = 0,
-): Int {
-    val points = segment.points
-    return when (segment.type) {
-        PathSegment.Type.Move -> 0
-
-        PathSegment.Type.Line -> 0
-
-        PathSegment.Type.Quadratic -> {
-            // Line derivative of a quadratic function
-            // We do the computation inline to avoid using arrays of other data
-            // structures to return the result
-            val d0 = 2 * (points[2] - points[0])
-            val d1 = 2 * (points[4] - points[2])
-            findLineRoot(d0, d1, roots, index)
-        }
-
-        // We convert all conics to cubics, won't happen
-        PathSegment.Type.Conic -> 0
-
-        PathSegment.Type.Cubic -> {
-            // Quadratic derivative of a cubic function
-            // We do the computation inline to avoid using arrays of other data
-            // structures to return the result
-            val d0 = 3.0f * (points[2] - points[0])
-            val d1 = 3.0f * (points[4] - points[2])
-            val d2 = 3.0f * (points[6] - points[4])
-            val count = findQuadraticRoots(d0, d1, d2, roots, index)
-
-            // Compute the second derivative as a line
-            val dd0 = 2.0f * (d1 - d0)
-            val dd1 = 2.0f * (d2 - d1)
-            count + findLineRoot(dd0, dd1, roots, index + count)
-        }
-
-        PathSegment.Type.Close -> 0
-        PathSegment.Type.Done -> 0
-    }
-}
-
-/**
- * Computes the horizontal bounds of the specified [segment] and returns
- * a pair of floats containing the lowest bound as the first value, and
- * the highest bound as the second value.
- *
- * The [roots] array is used as a scratch array and must be able to hold
- * at least 5 floats.
- */
-internal fun computeHorizontalBounds(
-    segment: PathSegment,
-    roots: FloatArray,
-    index: Int = 0
-): FloatFloatPair {
-    val count = findDerivativeRoots(segment, roots, index)
-    var minX = min(segment.startX, segment.endX)
-    var maxX = max(segment.startX, segment.endX)
-
-    for (i in 0 until count) {
-        val t = roots[i]
-        val x = evaluateX(segment, t)
-        minX = min(minX, x)
-        maxX = max(maxX, x)
-    }
-
-    return FloatFloatPair(minX, maxX)
-}
-
-@Suppress("NOTHING_TO_INLINE")
-private inline fun Double.closeTo(b: Double, epsilon: Double = Epsilon) = abs(this - b) < epsilon
-
-/**
- * Returns [r] if it's in the [0..1] range, and [Float.NaN] otherwise. To account
- * for numerical imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon]
- * range are considered to be in the [0..1] range and clamped appropriately.
- */
-@Suppress("NOTHING_TO_INLINE")
-private inline fun clampValidRootInUnitRange(r: Float): Float = if (r < 0.0f) {
-    if (r >= -FloatEpsilon) 0.0f else Float.NaN
-} else if (r > 1.0f) {
-    if (r <= 1.0f + FloatEpsilon) 1.0f else Float.NaN
-} else {
-    r
-}
-
-/**
- * Writes [r] in the [roots] array at [index], if it's in the [0..1] range. To account
- * for numerical imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon]
- * range are considered to be in the [0..1] range and clamped appropriately. Returns 0 if
- * no value was written, 1 otherwise.
- */
-private fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
-    val v = clampValidRootInUnitRange(r)
-    roots[index] = v
-    return if (v.isNaN()) 0 else 1
-}
-
-private inline val PathSegment.startX: Float
-    get() = points[0]
-
-private val PathSegment.endX: Float
-    get() = points[when (type) {
-        PathSegment.Type.Move -> 0
-        PathSegment.Type.Line -> 2
-        PathSegment.Type.Quadratic -> 4
-        PathSegment.Type.Conic -> 4
-        PathSegment.Type.Cubic -> 6
-        PathSegment.Type.Close -> 0
-        PathSegment.Type.Done -> 0
-    }]
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DeferredTargetAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DeferredTargetAnimation.kt
new file mode 100644
index 0000000..1844447
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DeferredTargetAnimation.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.animation.core
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@RequiresOptIn(
+    message = "This is an experimental animation API for Transition. It may change in the future."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalAnimatableApi
+
+/**
+ * [DeferredTargetAnimation] is intended for animations where the target is unknown at the time
+ * of instantiation. Such use cases include, but are not limited to, size or position animations
+ * created during composition or the initialization of a Modifier.Node, yet the target size or
+ * position stays unknown until the later measure and placement phase.
+ *
+ * [DeferredTargetAnimation] offers a declarative [updateTarget] function, which requires a
+ * target to either set up the animation or update the animation, and to read the current value
+ * of the animation.
+ *
+ * @sample androidx.compose.animation.core.samples.DeferredTargetAnimationSample
+ */
+@ExperimentalAnimatableApi
+class DeferredTargetAnimation<T, V : AnimationVector>(
+    private val vectorConverter: TwoWayConverter<T, V>
+) {
+    /**
+     * Returns the target value from the most recent [updateTarget] call.
+     */
+    val pendingTarget: T?
+        get() = _pendingTarget
+
+    private var _pendingTarget: T? by mutableStateOf(null)
+    private val target: T?
+        get() = animatable?.targetValue
+    private var animatable: Animatable<T, V>? = null
+
+    /**
+     * [updateTarget] sets up an animation, or updates an already running animation, based on the
+     * [target] in the given [coroutineScope]. [pendingTarget] will be updated to track the last
+     * seen [target].
+     *
+     * [updateTarget] will return the current value of the animation after launching the animation
+     * in the given [coroutineScope].
+     *
+     * @return current value of the animation
+     */
+    fun updateTarget(
+        target: T,
+        coroutineScope: CoroutineScope,
+        animationSpec: FiniteAnimationSpec<T> = spring()
+    ): T {
+        _pendingTarget = target
+        val anim = animatable ?: Animatable(target, vectorConverter).also { animatable = it }
+        coroutineScope.launch {
+            if (anim.targetValue != _pendingTarget) {
+                anim.animateTo(target, animationSpec)
+            }
+        }
+        return anim.value
+    }
+
+    /**
+     * [isIdle] returns true when the animation has finished running and reached its
+     * [pendingTarget], or when the animation has not been set up (i.e. [updateTarget] has never
+     * been called).
+     */
+    val isIdle: Boolean
+        get() = _pendingTarget == target && animatable?.isRunning != true
+}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
index c62aaa6..0b6e7d6 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.evaluateCubic
+import androidx.compose.ui.graphics.findFirstCubicRoot
 import androidx.compose.ui.util.fastCoerceIn
 
 /**
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntervalTree.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntervalTree.kt
deleted file mode 100644
index 02b67d9..0000000
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntervalTree.kt
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright 2023 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.compose.animation.core
-import kotlin.math.max
-import kotlin.math.min
-
-// TODO: We should probably move this to androidx.collection
-
-/**
- * Interval in an [IntervalTree]. The interval is defined between a [start] and an [end]
- * coordinates, whose meanings are defined by the caller. An interval can also hold
- * arbitrary [data] to be used to looking at the result of queries with
- * [IntervalTree.findOverlaps].
- */
-internal class Interval<T>(val start: Float, val end: Float, val data: T? = null) {
-    /**
-     * Returns trues if this interval overlaps with another interval.
-     */
-    fun overlaps(other: Interval<T>) = start <= other.end && end >= other.start
-
-    /**
-     * Returns trues if this interval overlaps with the interval defined by [start]
-     * and [end]. [start] must be less than or equal to [end].
-     */
-    fun overlaps(start: Float, end: Float) = this.start <= end && this.end >= start
-
-    /**
-     * Returns true if this interval contains [value].
-     */
-    operator fun contains(value: Float) = value in start..end
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other == null || this::class != other::class) return false
-
-        other as Interval<*>
-
-        if (start != other.start) return false
-        if (end != other.end) return false
-        if (data != other.data) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = start.hashCode()
-        result = 31 * result + end.hashCode()
-        result = 31 * result + (data?.hashCode() ?: 0)
-        return result
-    }
-
-    override fun toString(): String {
-        return "Interval(start=$start, end=$end, data=$data)"
-    }
-}
-
-internal val EmptyInterval: Interval<Any?> = Interval(Float.MAX_VALUE, Float.MIN_VALUE, null)
-
-/**
- * An interval tree holds a list of intervals and allows for fast queries of intervals
- * that overlap any given interval. This can be used for instance to perform fast spatial
- * queries like finding all the segments in a path that overlap with a given vertical
- * interval.
- */
-internal class IntervalTree<T> {
-    // Note: this interval tree is implemented as a binary red/black tree that gets
-    // re-balanced on updates. There's nothing notable about this particular data
-    // structure beyond what can be found in various descriptions of binary search
-    // trees and red/black trees
-
-    private val terminator = Node(
-        Interval(Float.MAX_VALUE, Float.MIN_VALUE, null),
-        TreeColor.Black
-    )
-    private var root = terminator
-
-    /**
-     * Clears this tree and prepares it for reuse. After calling [clear], any call to
-     * [findOverlaps] returns false.
-     */
-    fun clear() {
-        root = terminator
-    }
-
-    /**
-     * Finds the first interval that overlaps with the specified [interval]. If no overlap can
-     * be found, return [EmptyInterval].
-     */
-    fun findFirstOverlap(interval: ClosedFloatingPointRange<Float>) =
-        findFirstOverlap(interval.start, interval.endInclusive)
-
-    /**
-     * Finds the first interval that overlaps with the interval defined by [start] and [end].
-     * If no overlap can be found, return [EmptyInterval]. [start] *must* be lesser than or
-     * equal to [end].
-     */
-    fun findFirstOverlap(
-        start: Float,
-        end: Float = start
-    ): Interval<T> {
-        if (root !== terminator) {
-            return findFirstOverlap(root, start, end)
-        }
-        @Suppress("UNCHECKED_CAST")
-        return EmptyInterval as Interval<T>
-    }
-
-    private fun findFirstOverlap(
-        node: Node,
-        start: Float,
-        end: Float
-    ): Interval<T> {
-        if (node.interval.overlaps(start, end)) return node.interval
-        if (node.left !== terminator && node.left.max >= start) {
-            return findFirstOverlap(node.left, start, end)
-        }
-        if (node.right !== terminator && node.right.min <= end) {
-            return findFirstOverlap(node.right, start, end)
-        }
-        @Suppress("UNCHECKED_CAST")
-        return EmptyInterval as Interval<T>
-    }
-
-    /**
-     * Finds all the intervals that overlap with the specified [interval]. If [results]
-     * is specified, [results] is returned, otherwise a new [MutableList] is returned.
-     */
-    fun findOverlaps(
-        interval: ClosedFloatingPointRange<Float>,
-        results: MutableList<Interval<T>> = mutableListOf()
-    ) = findOverlaps(interval.start, interval.endInclusive, results)
-
-    /**
-     * Finds all the intervals that overlap with the interval defined by [start] and [end].
-     * [start] *must* be lesser than or equal to [end]. If [results] is specified, [results]
-     * is returned, otherwise a new [MutableList] is returned.
-     */
-    fun findOverlaps(
-        start: Float,
-        end: Float = start,
-        results: MutableList<Interval<T>> = mutableListOf()
-    ): MutableList<Interval<T>> {
-        if (root !== terminator) {
-            findOverlaps(root, start, end, results)
-        }
-        return results
-    }
-
-    private fun findOverlaps(
-        node: Node,
-        start: Float,
-        end: Float,
-        results: MutableList<Interval<T>>
-    ) {
-        if (node.interval.overlaps(start, end)) results.add(node.interval)
-        if (node.left !== terminator && node.left.max >= start) {
-            findOverlaps(node.left, start, end, results)
-        }
-        if (node.right !== terminator && node.right.min <= end) {
-            findOverlaps(node.right, start, end, results)
-        }
-    }
-
-    /**
-     * Returns true if [value] is inside any of the intervals in this tree.
-     */
-    operator fun contains(value: Float) = findFirstOverlap(value, value) !== EmptyInterval
-
-    /**
-     * Returns true if the specified [interval] overlaps with any of the intervals
-     * in this tree.
-     */
-    operator fun contains(interval: ClosedFloatingPointRange<Float>) =
-        findFirstOverlap(interval.start, interval.endInclusive) !== EmptyInterval
-
-    operator fun iterator(): Iterator<Interval<T>> {
-        return object : Iterator<Interval<T>> {
-            var next = root.lowestNode()
-
-            override fun hasNext(): Boolean {
-                return next !== terminator
-            }
-
-            override fun next(): Interval<T> {
-                val node = next
-                next = next.next()
-                return node.interval
-            }
-        }
-    }
-
-    /**
-     * Adds the specified [Interval] to the interval tree.
-     */
-    operator fun plusAssign(interval: Interval<T>) {
-        val node = Node(interval)
-
-        // Update the tree without doing any balancing
-        var current = root
-        var parent = terminator
-
-        while (current !== terminator) {
-            parent = current
-            current = if (node.interval.start <= current.interval.start) {
-                current.left
-            } else {
-                current.right
-            }
-        }
-
-        node.parent = parent
-
-        if (parent === terminator) {
-            root = node
-        } else {
-            if (node.interval.start <= parent.interval.start) {
-                parent.left = node
-            } else {
-                parent.right = node
-            }
-        }
-
-        updateNodeData(node)
-
-        rebalance(node)
-    }
-
-    private fun rebalance(target: Node) {
-        var node = target
-
-        while (node !== root && node.parent.color == TreeColor.Red) {
-            val ancestor = node.parent.parent
-            if (node.parent === ancestor.left) {
-                val right = ancestor.right
-                if (right.color == TreeColor.Red) {
-                    right.color = TreeColor.Black
-                    node.parent.color = TreeColor.Black
-                    ancestor.color = TreeColor.Red
-                    node = ancestor
-                } else {
-                    if (node === node.parent.right) {
-                        node = node.parent
-                        rotateLeft(node)
-                    }
-                    node.parent.color = TreeColor.Black
-                    ancestor.color = TreeColor.Red
-                    rotateRight(ancestor)
-                }
-            } else {
-                val left = ancestor.left
-                if (left.color == TreeColor.Red) {
-                    left.color = TreeColor.Black
-                    node.parent.color = TreeColor.Black
-                    ancestor.color = TreeColor.Red
-                    node = ancestor
-                } else {
-                    if (node === node.parent.left) {
-                        node = node.parent
-                        rotateRight(node)
-                    }
-                    node.parent.color = TreeColor.Black
-                    ancestor.color = TreeColor.Red
-                    rotateLeft(ancestor)
-                }
-            }
-        }
-
-        root.color = TreeColor.Black
-    }
-
-    private fun rotateLeft(node: Node) {
-        val right = node.right
-        node.right = right.left
-
-        if (right.left !== terminator) {
-            right.left.parent = node
-        }
-
-        right.parent = node.parent
-
-        if (node.parent === terminator) {
-            root = right
-        } else {
-            if (node.parent.left === node) {
-                node.parent.left = right
-            } else {
-                node.parent.right = right
-            }
-        }
-
-        right.left = node
-        node.parent = right
-
-        updateNodeData(node)
-    }
-
-    private fun rotateRight(node: Node) {
-        val left = node.left
-        node.left = left.right
-
-        if (left.right !== terminator) {
-            left.right.parent = node
-        }
-
-        left.parent = node.parent
-
-        if (node.parent === terminator) {
-            root = left
-        } else {
-            if (node.parent.right === node) {
-                node.parent.right = left
-            } else {
-                node.parent.left = left
-            }
-        }
-
-        left.right = node
-        node.parent = left
-
-        updateNodeData(node)
-    }
-
-    private fun updateNodeData(node: Node) {
-        var current = node
-        while (current !== terminator) {
-            current.min = min(current.interval.start, min(current.left.min, current.right.min))
-            current.max = max(current.interval.end, max(current.left.max, current.right.max))
-            current = current.parent
-        }
-    }
-
-    private enum class TreeColor {
-        Red, Black
-    }
-
-    private inner class Node(val interval: Interval<T>, var color: TreeColor = TreeColor.Red) {
-        var min: Float = interval.start
-        var max: Float = interval.end
-
-        var left: Node = terminator
-        var right: Node = terminator
-        var parent: Node = terminator
-
-        fun lowestNode(): Node {
-            var node = this
-            while (node.left !== terminator) {
-                node = node.left
-            }
-            return node
-        }
-
-        fun next(): Node {
-            if (right !== terminator) {
-                return right.lowestNode()
-            }
-
-            var a = this
-            var b = parent
-            while (b !== terminator && a === b.right) {
-                a = b
-                b = b.parent
-            }
-
-            return b
-        }
-    }
-}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
index 887f492..43446776 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
@@ -17,9 +17,14 @@
 package androidx.compose.animation.core
 
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.IntervalTree
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.PathIterator
 import androidx.compose.ui.graphics.PathSegment
+import androidx.compose.ui.graphics.computeHorizontalBounds
+import androidx.compose.ui.graphics.evaluateY
+import androidx.compose.ui.graphics.findFirstRoot
+import androidx.compose.ui.util.fastCoerceIn
 
 /**
  * An easing function for an arbitrary [Path].
@@ -84,7 +89,7 @@
                         segment.type != PathSegment.Type.Done
                     ) {
                         val bounds = computeHorizontalBounds(segment, roots)
-                        this += Interval(bounds.first, bounds.second, segment)
+                        addInterval(bounds.first, bounds.second, segment)
                     }
                 }
             }
@@ -106,6 +111,6 @@
             "The easing path is invalid. Make sure it does not contain NaN/Infinity values."
         }
 
-        return evaluateY(segment, t).coerceAtLeast(0.0f).coerceAtMost(1.0f)
+        return evaluateY(segment, t).fastCoerceIn(0.0f, 1.0f)
     }
 }
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 582509b..150d58b 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -42,13 +42,12 @@
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:animation:animation"))
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-geometry"))
+                api("androidx.compose.foundation:foundation-layout:1.6.0")
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.ui:ui:1.6.0")
+                api("androidx.compose.ui:ui-geometry:1.6.0")
 
                 implementation(project(":compose:ui:ui-util"))
-
                 implementation("androidx.collection:collection:1.4.0")
             }
         }
@@ -69,7 +68,6 @@
             }
         }
 
-
         androidMain {
             dependsOn(jvmMain)
             dependencies {
@@ -80,11 +78,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation(libs.kotlinStdlib)
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-geometry"))
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index 6a28b1d..f8204bf 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -42,10 +42,10 @@
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:animation:animation-core"))
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-geometry"))
+                api("androidx.compose.foundation:foundation-layout:1.6.0")
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.ui:ui:1.6.0")
+                api("androidx.compose.ui:ui-geometry:1.6.0")
 
                 implementation(project(":compose:ui:ui-util"))
 
@@ -77,13 +77,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation(libs.kotlinStdlib)
-
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-geometry"))
-
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
diff --git a/compose/animation/animation/integration-tests/animation-demos/build.gradle b/compose/animation/animation/integration-tests/animation-demos/build.gradle
index 2eb0a79..855469a 100644
--- a/compose/animation/animation/integration-tests/animation-demos/build.gradle
+++ b/compose/animation/animation/integration-tests/animation-demos/build.gradle
@@ -30,6 +30,7 @@
     implementation(project(":compose:foundation:foundation"))
     implementation(project(":compose:material:material"))
     implementation(project(":compose:ui:ui-tooling-preview"))
+    implementation project(':compose:material3:material3')
     debugImplementation(project(":compose:ui:ui-tooling"))
 }
 
diff --git a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml b/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
index b3839ca..7ef4d21 100644
--- a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
+++ b/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="PrimitiveInCollection"
@@ -39,24 +39,6 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="variable sizeMap with type Map&lt;T, IntSize>: replace with ObjectLongMap"
-        errorLine1="    val sizeMap = remember { mutableMapOf&lt;T, IntSize>() }"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable offsetMap with type Map&lt;T, Offset>: replace with ObjectLongMap"
-        errorLine1="    val offsetMap = remember { mutableMapOf&lt;T, Offset>() }"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
         message="field colors with type List&lt;Color>: replace with LongList"
         errorLine1="private val colors = listOf("
         errorLine2="^">
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
index 707240c..62f03d5 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
@@ -18,36 +18,31 @@
 
 package androidx.compose.animation.demos.lookahead
 
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector
 import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.graphics.drawscope.translate
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
+context(LookaheadScope)
+@OptIn(ExperimentalAnimatableApi::class)
 fun Modifier.animateBounds(
     modifier: Modifier = Modifier,
     sizeAnimationSpec: FiniteAnimationSpec<IntSize> = spring(
@@ -59,44 +54,62 @@
         Spring.StiffnessMediumLow
     ),
     debug: Boolean = false,
-    lookaheadScope: (closestLookaheadScope: LookaheadScope) -> LookaheadScope = { it }
 ) = composed {
 
-    val outerOffsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
-    val outerSizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
+    val outerOffsetAnimation = remember { DeferredTargetAnimation(IntOffset.VectorConverter) }
+    val outerSizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
 
-    val offsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
-    val sizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
+    val offsetAnimation = remember { DeferredTargetAnimation(IntOffset.VectorConverter) }
+    val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
 
-    // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as
-    // intermediateLayout is expected to produce intermediate stages of a layout transform.
+    val coroutineScope = rememberCoroutineScope()
+
+    // The measure logic in `approachLayout` is skipped in the lookahead pass, as
+    // approachLayout is expected to produce intermediate stages of a layout transform.
     // When the measure block is invoked after lookahead pass, the lookahead size of the
     // child will be accessible as a parameter to the measure block.
     this
         .drawWithContent {
             drawContent()
             if (debug) {
-                val offset = outerOffsetAnimation.target!! - outerOffsetAnimation.value!!
-                translate(
-                    offset.x.toFloat(), offset.y.toFloat()
-                ) {
-                    drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f))
-                }
+//                val offset = outerOffsetAnimation.pendingTarget!! - outerOffsetAnimation.value!!
+//                translate(
+//                    offset.x.toFloat(), offset.y.toFloat()
+//                ) {
+//                    drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f))
+//                }
             }
         }
-        .intermediateLayout { measurable, constraints ->
+        .approachLayout(
+            isMeasurementApproachComplete = {
+                outerSizeAnimation.updateTarget(it, coroutineScope, sizeAnimationSpec)
+                outerSizeAnimation.isIdle
+            },
+            isPlacementApproachComplete = {
+                val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
+                outerOffsetAnimation.updateTarget(
+                    target.round(),
+                    coroutineScope,
+                    positionAnimationSpec
+                )
+                outerOffsetAnimation.isIdle
+            }
+        ) { measurable, constraints ->
             val (w, h) = outerSizeAnimation.updateTarget(
                 lookaheadSize,
+                coroutineScope,
                 sizeAnimationSpec,
             )
             measurable
                 .measure(constraints)
                 .run {
                     layout(w, h) {
-                        val (x, y) = outerOffsetAnimation.updateTargetBasedOnCoordinates(
-                            positionAnimationSpec
-                        )
-                        place(x, y)
+                        with(coroutineScope) {
+                            val (x, y) = outerOffsetAnimation.updateTargetBasedOnCoordinates(
+                                positionAnimationSpec
+                            )
+                            place(x, y)
+                        }
                     }
                 }
         }
@@ -104,21 +117,36 @@
         .drawWithContent {
             drawContent()
             if (debug) {
-                val offset = offsetAnimation.target!! - offsetAnimation.value!!
-                translate(
-                    offset.x.toFloat(), offset.y.toFloat()
-                ) {
-                    drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f))
-                }
+//                val offset = offsetAnimation.pendingTarget!! - offsetAnimation.value!!
+//                translate(
+//                    offset.x.toFloat(), offset.y.toFloat()
+//                ) {
+//                    drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f))
+//                }
             }
         }
-        .intermediateLayout { measurable, _ ->
+        .approachLayout(
+            isMeasurementApproachComplete = {
+                sizeAnimation.updateTarget(it, coroutineScope, sizeAnimationSpec)
+                sizeAnimation.isIdle
+            },
+            isPlacementApproachComplete = {
+                val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
+                offsetAnimation.updateTarget(
+                    target.round(),
+                    coroutineScope,
+                    positionAnimationSpec
+                )
+                offsetAnimation.isIdle
+            }
+        ) { measurable, _ ->
             // When layout changes, the lookahead pass will calculate a new final size for the
             // child modifier. This lookahead size can be used to animate the size
             // change, such that the animation starts from the current size and gradually
             // change towards `lookaheadSize`.
             val (width, height) = sizeAnimation.updateTarget(
                 lookaheadSize,
+                coroutineScope,
                 sizeAnimationSpec,
             )
             // Creates a fixed set of constraints using the animated size
@@ -126,7 +154,7 @@
             // Measure child/children with animated constraints.
             val placeable = measurable.measure(animatedConstraints)
             layout(placeable.width, placeable.height) {
-                val (x, y) = with(lookaheadScope(this@intermediateLayout)) {
+                val (x, y) = with(coroutineScope) {
                     offsetAnimation.updateTargetBasedOnCoordinates(
                         positionAnimationSpec,
                     )
@@ -137,7 +165,8 @@
 }
 
 context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
-    internal fun DeferredAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
+@OptIn(ExperimentalAnimatableApi::class)
+internal fun DeferredTargetAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
     animationSpec: FiniteAnimationSpec<IntOffset>,
 ): IntOffset {
     coordinates?.let { coordinates ->
@@ -145,6 +174,7 @@
             val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
             val animOffset = updateTarget(
                 targetOffset.round(),
+                this@CoroutineScope,
                 animationSpec,
             )
             val current = lookaheadScopeCoordinates.localPositionOf(
@@ -157,40 +187,3 @@
 
     return IntOffset.Zero
 }
-
-// Experimenting with a way to initialize animation during measurement && only take the last target
-// change in a frame (if the target was changed multiple times in the same frame) as the
-// animation target.
-internal class DeferredAnimation<T, V : AnimationVector>(
-    private val vectorConverter: TwoWayConverter<T, V>
-) {
-    val value: T?
-        get() = animatable?.value ?: target
-    var target: T? by mutableStateOf(null)
-        private set
-    private var animatable: Animatable<T, V>? = null
-
-    internal val isActive: Boolean
-        get() = target != animatable?.targetValue || animatable?.isRunning == true
-
-    context (CoroutineScope)
-    fun updateTarget(
-        targetValue: T,
-        animationSpec: FiniteAnimationSpec<T>,
-    ): T {
-        target = targetValue
-        if (target != null && target != animatable?.targetValue) {
-            animatable?.run {
-                launch {
-                    animateTo(
-                        targetValue,
-                        animationSpec
-                    )
-                }
-            } ?: Animatable(targetValue, vectorConverter).let {
-                animatable = it
-            }
-        }
-        return animatable?.value ?: targetValue
-    }
-}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt
index dba31d2..4f80c38 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/CraneDemo.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.animation.demos.lookahead
 
+import android.annotation.SuppressLint
 import androidx.compose.animation.animateContentSize
 import androidx.compose.animation.core.animate
 import androidx.compose.animation.core.tween
@@ -52,9 +53,10 @@
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.IntermediateMeasureScope
+import androidx.compose.ui.layout.ApproachMeasureScope
+import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -182,6 +184,8 @@
     val progress: Float
 }
 
+context(LookaheadScope)
+@SuppressLint("PrimitiveInCollection")
 @OptIn(ExperimentalComposeUiApi::class)
 fun <T> Modifier.sharedElementBasedOnProgress(provider: ProgressProvider<T>) = composed {
     val sizeMap = remember { mutableMapOf<T, IntSize>() }
@@ -196,7 +200,7 @@
             IntSize(width.roundToInt(), height.roundToInt())
         }
 
-    val calculateOffset: Placeable.PlacementScope.(IntermediateMeasureScope) -> IntOffset = {
+    val calculateOffset: Placeable.PlacementScope.(ApproachMeasureScope) -> IntOffset = {
         with(it) {
             coordinates?.let {
                 offsetMap[provider.targetState] =
@@ -213,12 +217,12 @@
             } ?: IntOffset(0, 0)
         }
     }
-    this.intermediateLayout { measurable, _ ->
+    this.approachLayout({ provider.progress == 1f }) { measurable, _ ->
         val (width, height) = calculateSize(lookaheadSize)
         val animatedConstraints = Constraints.fixed(width, height)
         val placeable = measurable.measure(animatedConstraints)
         layout(placeable.width, placeable.height) {
-            placeable.place(calculateOffset(this@intermediateLayout))
+            placeable.place(calculateOffset(this@approachLayout))
         }
     }
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
index 1fdf49b..f0375d7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
@@ -18,13 +18,15 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.samples.IntermediateLayoutSample
 import androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
+import androidx.compose.ui.samples.approachLayoutSample
+import androidx.compose.ui.tooling.preview.Preview
 
+@Preview
 @Composable
 fun LookaheadSamplesDemo() {
     Column {
-        IntermediateLayoutSample()
+        approachLayoutSample()
         LookaheadLayoutCoordinatesSample()
     }
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithDisappearingMoveableContentDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithDisappearingMoveableContentDemo.kt
index 4f66032..56fed32 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithDisappearingMoveableContentDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithDisappearingMoveableContentDemo.kt
@@ -21,6 +21,8 @@
 import android.annotation.SuppressLint
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
@@ -41,6 +43,7 @@
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -48,7 +51,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
@@ -147,22 +150,35 @@
 }
 
 context(LookaheadScope)
+@OptIn(ExperimentalAnimatableApi::class)
 @SuppressLint("UnnecessaryComposedModifier")
 fun Modifier.animatePosition(): Modifier = composed {
     val offsetAnimation = remember {
-        DeferredAnimation(IntOffset.VectorConverter)
+        DeferredTargetAnimation(IntOffset.VectorConverter)
     }
-    this.intermediateLayout { measurable, constraints ->
+    val coroutineScope = rememberCoroutineScope()
+    this.approachLayout(isMeasurementApproachComplete = { true },
+        isPlacementApproachComplete = {
+            offsetAnimation.updateTarget(
+                lookaheadScopeCoordinates.localLookaheadPositionOf(
+                    it
+                ).round(),
+                coroutineScope,
+                spring(stiffness = Spring.StiffnessMediumLow)
+            )
+            offsetAnimation.isIdle
+        }
+    ) { measurable, constraints ->
         measurable.measure(constraints).run {
             layout(width, height) {
                 val (x, y) =
                     coordinates?.let { coordinates ->
                         val origin = this.lookaheadScopeCoordinates
-                        offsetAnimation.updateTarget(
+                        val animOffset = offsetAnimation.updateTarget(
                             origin.localLookaheadPositionOf(
                                 coordinates
-                            )
-                                .round(),
+                            ).round(),
+                            coroutineScope,
                             spring(stiffness = Spring.StiffnessMediumLow),
                         )
                         val currentOffset =
@@ -170,8 +186,7 @@
                                 coordinates,
                                 Offset.Zero
                             )
-                        (offsetAnimation.value
-                            ?: offsetAnimation.target!!) - currentOffset.round()
+                        animOffset - currentOffset.round()
                     } ?: IntOffset.Zero
                 place(x, y)
             }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
index 35058df..484f77a 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
@@ -53,6 +53,7 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 
+@Preview
 @OptIn(ExperimentalComposeUiApi::class, ExperimentalLayoutApi::class)
 @Composable
 fun LookaheadWithFlowRowDemo() {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
index f25cb8a..d4e84bc 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.animation.demos.lookahead
 
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.demos.fancy.AnimatedDotsDemo
@@ -41,6 +43,7 @@
 import androidx.compose.runtime.movableContentWithReceiverOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -50,13 +53,15 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 
+@Preview
 @Composable
 fun LookaheadWithMovableContentDemo() {
     Column(
@@ -152,32 +157,40 @@
 }
 
 context (LookaheadScope)
+@OptIn(ExperimentalAnimatableApi::class)
 fun Modifier.animateBoundsInScope(): Modifier = composed {
-    val sizeAnim = remember { DeferredAnimation(IntSize.VectorConverter) }
-    val offsetAnim = remember { DeferredAnimation(IntOffset.VectorConverter) }
-    this.intermediateLayout { measurable, _ ->
-        sizeAnim.updateTarget(
+    val sizeAnim = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
+    val offsetAnim = remember { DeferredTargetAnimation(IntOffset.VectorConverter) }
+    val scope = rememberCoroutineScope()
+    this.approachLayout(
+        isMeasurementApproachComplete = {
+            sizeAnim.updateTarget(it, scope)
+            sizeAnim.isIdle
+        },
+        isPlacementApproachComplete = {
+            val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
+            offsetAnim.updateTarget(target.round(), scope, spring())
+            offsetAnim.isIdle
+        }
+    ) { measurable, _ ->
+        val (animWidth, animHeight) = sizeAnim.updateTarget(
             lookaheadSize,
+            scope,
             spring()
         )
-        measurable.measure(
-            Constraints.fixed(
-                sizeAnim.value!!.width,
-                sizeAnim.value!!.height
-            )
-        )
+        measurable.measure(Constraints.fixed(animWidth, animHeight))
             .run {
                 layout(width, height) {
                     coordinates?.let {
                         val target =
                             lookaheadScopeCoordinates.localLookaheadPositionOf(it)
                                 .round()
-                        offsetAnim.updateTarget(target, spring())
+                        val animOffset = offsetAnim.updateTarget(target, scope, spring())
                         val current = lookaheadScopeCoordinates.localPositionOf(
                             it,
                             Offset.Zero
                         ).round()
-                        val (x, y) = offsetAnim.value!! - current
+                        val (x, y) = animOffset - current
                         place(x, y)
                     } ?: place(0, 0)
                 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
index 412d8b3..15260bb 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
@@ -110,6 +110,7 @@
     }
 }
 
+context(LookaheadScope)
 @OptIn(ExperimentalComposeUiApi::class)
 private fun Modifier.conditionallyAnimateBounds(
     shouldAnimate: Boolean,
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/NestedSceneHostDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/NestedSceneHostDemo.kt
deleted file mode 100644
index c77dcd3..0000000
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/NestedSceneHostDemo.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-@file:OptIn(ExperimentalComposeUiApi::class)
-
-package androidx.compose.animation.demos.lookahead
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.intermediateLayout
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun NestedSceneHostDemo() {
-    SceneHost {
-        Box(
-            Modifier
-                .padding(top = 100.dp)
-                .fillMaxSize()
-                .intermediateLayout { measurable, constraints ->
-                    println("SPEC, actually measure parent")
-                    val placeable = measurable.measure(constraints)
-                    layout(placeable.width, placeable.height) {
-                        println("SPEC, actually place parent")
-                        placeable.place(0, 0)
-                    }
-                }) {
-            SceneHost {
-                Column {
-                    Box(
-                        Modifier
-                            .size(100.dp)
-                            .background(Color.Red)
-                            .intermediateLayout { measurable, constraints ->
-                                println("SPEC, actually measure child")
-                                val placeable = measurable.measure(constraints)
-                                layout(placeable.width, placeable.height) {
-                                    println("SPEC, actually place child")
-                                    placeable.place(0, 0)
-                                }
-                            })
-                    Box(
-                        Modifier
-                            .size(100.dp)
-                            .background(Color.Green)
-                    )
-                }
-            }
-        }
-    }
-}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
index 0a3b4a8..eeef14b 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
@@ -18,8 +18,9 @@
 
 package androidx.compose.animation.demos.lookahead
 
-import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
@@ -28,6 +29,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -37,14 +39,13 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
-import kotlinx.coroutines.launch
 
 @Composable
 fun SceneHost(modifier: Modifier = Modifier, content: @Composable SceneScope.() -> Unit) {
@@ -61,15 +62,17 @@
 class SceneScope internal constructor(
     lookaheadScope: LookaheadScope
 ) : LookaheadScope by lookaheadScope {
+    @OptIn(ExperimentalAnimatableApi::class)
     fun Modifier.sharedElement(): Modifier = composed {
-        val offsetAnimation: DeferredAnimation<IntOffset, AnimationVector2D> =
+        val offsetAnimation: DeferredTargetAnimation<IntOffset, AnimationVector2D> =
             remember {
-                DeferredAnimation(IntOffset.VectorConverter)
+                DeferredTargetAnimation(IntOffset.VectorConverter)
             }
-        val sizeAnimation: DeferredAnimation<IntSize, AnimationVector2D> =
-            remember { DeferredAnimation(IntSize.VectorConverter) }
+        val sizeAnimation: DeferredTargetAnimation<IntSize, AnimationVector2D> =
+            remember { DeferredTargetAnimation(IntSize.VectorConverter) }
 
         var placementOffset: IntOffset by remember { mutableStateOf(IntOffset.Zero) }
+        val coroutineScope = rememberCoroutineScope()
 
         this
             .drawBehind {
@@ -77,37 +80,53 @@
                     drawRect(
                         color = Color.Black,
                         style = Stroke(2f),
-                        topLeft = (offsetAnimation.target!! - placementOffset).toOffset(),
-                        size = sizeAnimation.target!!.toSize()
+                        topLeft = (offsetAnimation.pendingTarget!! - placementOffset).toOffset(),
+                        size = sizeAnimation.pendingTarget!!.toSize()
                     )
                 }
             }
-            .intermediateLayout { measurable, _ ->
-                val (width, height) = sizeAnimation.updateTarget(
-                    lookaheadSize, spring(stiffness = Spring.StiffnessMediumLow)
-                )
-                val animatedConstraints = Constraints.fixed(width, height)
-                val placeable = measurable.measure(animatedConstraints)
-                layout(placeable.width, placeable.height) {
-                    val (x, y) = offsetAnimation.updateTargetBasedOnCoordinates(
-                        spring(stiffness = Spring.StiffnessMediumLow),
+            .approachLayout(
+                isMeasurementApproachComplete = {
+                    sizeAnimation.updateTarget(it, coroutineScope)
+                    sizeAnimation.isIdle
+                },
+                isPlacementApproachComplete = {
+                    val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
+                    offsetAnimation.updateTarget(target.round(), coroutineScope, spring())
+                    offsetAnimation.isIdle
+                }
+            ) { measurable, _ ->
+                with(coroutineScope) {
+                    val (width, height) = sizeAnimation.updateTarget(
+                        lookaheadSize, coroutineScope, spring(stiffness = Spring.StiffnessMediumLow)
                     )
-                    coordinates?.let {
-                        placementOffset = lookaheadScopeCoordinates.localPositionOf(
-                            it, Offset.Zero
-                        ).round()
+                    val animatedConstraints = Constraints.fixed(width, height)
+                    val placeable = measurable.measure(animatedConstraints)
+                    layout(placeable.width, placeable.height) {
+                        val (x, y) = offsetAnimation.updateTargetBasedOnCoordinates(
+                            spring(stiffness = Spring.StiffnessMediumLow),
+                        )
+                        coordinates?.let {
+                            placementOffset = lookaheadScopeCoordinates
+                                .localPositionOf(
+                                    it, Offset.Zero
+                                )
+                                .round()
+                        }
+                        placeable.place(x, y)
                     }
-                    placeable.place(x, y)
                 }
             }
     }
 }
 
+@OptIn(ExperimentalAnimatableApi::class)
 fun Modifier.animateSizeAndSkipToFinalLayout() = composed {
-    var sizeAnimation: Animatable<IntSize, AnimationVector2D>? by remember {
-        mutableStateOf(null)
+    val sizeAnimation = remember {
+        DeferredTargetAnimation(IntSize.VectorConverter)
     }
     var targetSize: IntSize? by remember { mutableStateOf(null) }
+    val scope = rememberCoroutineScope()
     this
         .drawBehind {
             if (debugSharedElement) {
@@ -119,16 +138,14 @@
                 )
             }
         }
-        .intermediateLayout { measurable, constraints ->
-            targetSize = lookaheadSize
-            if (lookaheadSize != sizeAnimation?.targetValue) {
-                sizeAnimation?.run {
-                    launch { animateTo(lookaheadSize) }
-                } ?: Animatable(lookaheadSize, IntSize.VectorConverter).let {
-                    sizeAnimation = it
-                }
+        .approachLayout(
+            isMeasurementApproachComplete = {
+                sizeAnimation.updateTarget(it, scope)
+                sizeAnimation.isIdle
             }
-            val (width, height) = sizeAnimation?.value ?: lookaheadSize
+        ) { measurable, constraints ->
+            targetSize = lookaheadSize
+            val (width, height) = sizeAnimation.updateTarget(lookaheadSize, scope)
             val placeable = measurable.measure(
                 Constraints.fixed(lookaheadSize.width, lookaheadSize.height)
             )
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
index e0edcbe..750a320 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
@@ -46,6 +46,7 @@
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
@@ -55,6 +56,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.lerp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -723,4 +725,76 @@
             lookaheadSizes.clear()
         }
     }
+
+    @Test
+    fun interruptedExitAnimationUsesCorrectTransition() = with(rule.density) {
+        var visible by mutableStateOf(false)
+
+        val duration = 16 * 20 // 20 frames
+        val animation = { tween<IntOffset>(duration, easing = LinearEasing) }
+
+        val boxSizePx = 100
+        val enterDistance = 180
+        val undesiredExitDistance = -200
+        val expectedExitDistance = 200
+
+        var boxPosition = IntOffset.Zero
+
+        rule.setContent {
+            AnimatedVisibility(
+                visible = visible,
+                enter = slideInHorizontally(animation()) { enterDistance },
+                exit = if (visible) {
+                    slideOutHorizontally(animation()) { undesiredExitDistance }
+                } else {
+                    // Only this transition should apply
+                    slideOutHorizontally(animation()) { expectedExitDistance }
+                }
+            ) {
+                Box(
+                    Modifier
+                        .requiredSize(boxSizePx.toDp())
+                        .background(Color.Red)
+                        .onGloballyPositioned {
+                            boxPosition = it
+                                .positionInRoot()
+                                .round()
+                        }
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        rule.mainClock.autoAdvance = false
+        rule.runOnIdle {
+            visible = true
+        }
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        // Animate towards half of the transition and interrupt it by toggling the state
+        rule.mainClock.advanceTimeBy(duration / 2L)
+        rule.runOnIdle {
+            // Verify the position corresponds to half of the animation
+            assertEquals(IntOffset(enterDistance / 2, 0), boxPosition)
+            visible = false
+        }
+        val positionAtInterruption = boxPosition
+
+        // Run the animation for a few steps/frames to guarantee each frame is rendered, since a
+        // spring is used after interruption it's unknown how many more frames we need till the end
+        // of the animation, but we don't need to run the entire animation.
+        // We also need to run more than one step, since springs keep their initial momentum (in
+        // this case, momentum towards the left), so the next frame might still move the box towards
+        // the left.
+        repeat(3) {
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+        }
+
+        // After a few frames, we can check the position of the box. If it used the expected
+        // transition, its current position should be offset to the right.
+        assertEquals(0, boxPosition.y)
+        assert(boxPosition.x > positionAtInterruption.x)
+    }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index 9a1177e..c2a1136 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -107,10 +107,11 @@
     operator fun plus(enter: EnterTransition): EnterTransition {
         return EnterTransitionImpl(
             TransitionData(
-                fade = data.fade ?: enter.data.fade,
-                slide = data.slide ?: enter.data.slide,
-                changeSize = data.changeSize ?: enter.data.changeSize,
-                scale = data.scale ?: enter.data.scale,
+                fade = enter.data.fade ?: data.fade,
+                slide = enter.data.slide ?: data.slide,
+                changeSize = enter.data.changeSize ?: data.changeSize,
+                scale = enter.data.scale ?: data.scale,
+                // `enter` after plus operator to prioritize its values on the map
                 effectsMap = data.effectsMap + enter.data.effectsMap
             )
         )
@@ -192,11 +193,12 @@
     operator fun plus(exit: ExitTransition): ExitTransition {
         return ExitTransitionImpl(
             TransitionData(
-                fade = data.fade ?: exit.data.fade,
-                slide = data.slide ?: exit.data.slide,
-                changeSize = data.changeSize ?: exit.data.changeSize,
-                scale = data.scale ?: exit.data.scale,
-                hold = data.hold || exit.data.hold,
+                fade = exit.data.fade ?: data.fade,
+                slide = exit.data.slide ?: data.slide,
+                changeSize = exit.data.changeSize ?: data.changeSize,
+                scale = exit.data.scale ?: data.scale,
+                hold = exit.data.hold || data.hold,
+                // `exit` after plus operator to prioritize its values on the map
                 effectsMap = data.effectsMap + exit.data.effectsMap
             )
         )
diff --git a/compose/compiler/compiler-hosted/integration-tests/build.gradle b/compose/compiler/compiler-hosted/integration-tests/build.gradle
index 8199c23..1adcb8a 100644
--- a/compose/compiler/compiler-hosted/integration-tests/build.gradle
+++ b/compose/compiler/compiler-hosted/integration-tests/build.gradle
@@ -55,8 +55,8 @@
                 implementation(libs.protobufLite)
                 implementation(libs.guavaAndroid)
                 implementation(project(":compose:compiler:compiler-hosted"))
-                implementation("androidx.compose.foundation:foundation:1.6.0-beta02")
-                implementation("androidx.compose.ui:ui:1.6.0-beta02")
+                implementation("androidx.compose.foundation:foundation:1.6.0")
+                implementation("androidx.compose.ui:ui:1.6.0")
                 implementation(project(":compose:runtime:runtime"))
                 implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4")
                 implementation("com.google.dagger:dagger:2.40.1")
@@ -68,8 +68,8 @@
             dependencies {
                 implementation(libs.kotlinMetadataJvm)
                 implementation(libs.robolectric)
-                implementation("androidx.activity:activity-ktx:1.9.0-alpha01")
-                implementation("androidx.core:core-ktx:1.13.0-alpha03")
+                implementation("androidx.activity:activity-ktx:1.9.0-alpha03")
+                implementation("androidx.core:core-ktx:1.13.0-alpha05")
                 runtimeOnly(
                         project(":compose:compiler:compiler-hosted:integration-tests:kotlin-compiler-repackaged")
                 )
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
index 7e19a0f..3ba1cac 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
@@ -123,7 +123,8 @@
             // replace source keys for start group calls
             .replace(
                 Regex(
-                    "(%composer\\.start(Restart|Movable|Replaceable)Group\\()-?((0b)?[-\\d]+)"
+                    "(%composer\\.start(Restart|Movable|Replaceable|Replace)" +
+                        "Group\\()-?((0b)?[-\\d]+)"
                 )
             ) {
                 val stringKey = it.groupValues[3]
@@ -162,7 +163,7 @@
             // replace source information with source it references
             .replace(
                 Regex(
-                    "(%composer\\.start(Restart|Movable|Replaceable)Group\\" +
+                    "(%composer\\.start(Restart|Movable|Replaceable|Replace)Group\\" +
                         "([^\"\\n]*)\"(.*)\"\\)"
                 )
             ) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = false\135.txt"
index a1a28e8..07aa131 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = false\135.txt"
@@ -26,7 +26,7 @@
   object : C {
     @Composable
     override fun Render(%composer: Composer?, %changed: Int) {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C(Render)<B()>:Test.kt")
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
@@ -48,7 +48,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = true\135.txt"
index a1a28e8..07aa131 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composableLocalFunctionInsideLocalClass\133useFir = true\135.txt"
@@ -26,7 +26,7 @@
   object : C {
     @Composable
     override fun Render(%composer: Composer?, %changed: Int) {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C(Render)<B()>:Test.kt")
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
@@ -48,7 +48,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = false\135.txt"
index c95de31..3975f63 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = false\135.txt"
@@ -41,7 +41,7 @@
   @NonRestartableComposable
   @Composable
   override fun bar(%composer: Composer?, %changed: Int) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C(bar):Test.kt#2487m")
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
@@ -49,7 +49,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
   static val %stable: Int = 0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = true\135.txt"
index c95de31..3975f63 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/testAbstractComposable\133useFir = true\135.txt"
@@ -41,7 +41,7 @@
   @NonRestartableComposable
   @Composable
   override fun bar(%composer: Composer?, %changed: Int) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C(bar):Test.kt#2487m")
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
@@ -49,7 +49,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
   static val %stable: Int = 0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = false\135.txt"
index 65f9106..a65bb12 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = false\135.txt"
@@ -33,12 +33,12 @@
     }
     with(foo) {
       A(%this%with, %composer, 0)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<B()>")
       with(Bar()) {
         B(%this%with, %this%with, %composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = true\135.txt"
index 65f9106..a65bb12 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ContextReceiversTransformTests/testContextReceiversNestedWith\133useFir = true\135.txt"
@@ -33,12 +33,12 @@
     }
     with(foo) {
       A(%this%with, %composer, 0)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<B()>")
       with(Bar()) {
         B(%this%with, %this%with, %composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = false\135.txt"
index f051992..87b3736 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = false\135.txt"
@@ -25,10 +25,10 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B()>,<B()>")
   val tmp0_group = B(%composer, 0) && B(%composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = true\135.txt"
index f051992..87b3736 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testAND\133useFir = true\135.txt"
@@ -25,10 +25,10 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B()>,<B()>")
   val tmp0_group = B(%composer, 0) && B(%composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = false\135.txt"
index d400019..b3459ca 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = false\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -39,7 +39,7 @@
     }
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = true\135.txt"
index d400019..b3459ca 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsAfter\133useFir = true\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -39,7 +39,7 @@
     }
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = false\135.txt"
index 0945fc9..2547f95 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = false\135.txt"
@@ -33,7 +33,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>,<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -43,7 +43,7 @@
     }
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = true\135.txt"
index 0945fc9..2547f95 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfterAndCallAfter\133useFir = true\135.txt"
@@ -33,7 +33,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>,<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -43,7 +43,7 @@
     }
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = false\135.txt"
index bc9a8a3..3b2d8d9 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = false\135.txt"
@@ -33,7 +33,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>,<P(j)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -44,7 +44,7 @@
     }
     P(j, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = true\135.txt"
index bc9a8a3..3b2d8d9 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBeforeAndAfter\133useFir = true\135.txt"
@@ -33,7 +33,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>,<P(j)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -44,7 +44,7 @@
     }
     P(j, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = false\135.txt"
index 3efd13f..b2d254e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = false\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -39,7 +39,7 @@
       break
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = true\135.txt"
index 3efd13f..b2d254e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testBreakWithCallsBefore\133useFir = true\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   while (items.hasNext()) {
     val i = items.next()
@@ -39,7 +39,7 @@
       break
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = false\135.txt"
index 99f3e5e..aee5bdf 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = false\135.txt"
@@ -24,7 +24,7 @@
   val property: A
     @Composable @JvmName(name = "getProperty")
     get() {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
@@ -33,7 +33,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
 }
 @Composable
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = true\135.txt"
index 99f3e5e..aee5bdf 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposableInAnonymousObjectDelegate\133useFir = true\135.txt"
@@ -24,7 +24,7 @@
   val property: A
     @Composable @JvmName(name = "getProperty")
     get() {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
@@ -33,7 +33,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
 }
 @Composable
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = false\135.txt"
index aca099e..7f0f74b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = false\135.txt"
@@ -30,20 +30,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>")
     val i = items.next()
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     P(i, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = true\135.txt"
index aca099e..7f0f74b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsAfter\133useFir = true\135.txt"
@@ -30,20 +30,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>")
     val i = items.next()
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     P(i, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = false\135.txt"
index ee4b2ed..a679ce7 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = false\135.txt"
@@ -31,21 +31,21 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>,<P(i)>")
     val i = items.next()
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     P(i, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = true\135.txt"
index ee4b2ed..a679ce7 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBeforeAndAfter\133useFir = true\135.txt"
@@ -31,21 +31,21 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>,<P(i)>")
     val i = items.next()
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     P(i, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = false\135.txt"
index 867c78d..2525fd7 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = false\135.txt"
@@ -31,21 +31,21 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>")
     val i = items.next()
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     print(i)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = true\135.txt"
index 867c78d..2525fd7 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testContinueWithCallsBefore\133useFir = true\135.txt"
@@ -31,21 +31,21 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (items.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<P(i)>")
     val i = items.next()
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     print(i)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = false\135.txt"
index 335a543..8f91499 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = false\135.txt"
@@ -29,23 +29,23 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "")
     val tmp4_group = if (x > 0) {
-      val tmp3_group = if (%composer.startReplaceableGroup(<>)
+      val tmp3_group = if (%composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<B()>")
       val tmp1_group = B(%composer, 0)
-      %composer.endReplaceableGroup()
-      tmp1_group) 1 else if (%composer.startReplaceableGroup(<>)
+      %composer.endReplaceGroup()
+      tmp1_group) 1 else if (%composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<B()>")
       val tmp2_group = B(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp2_group) 2 else 3
       tmp3_group
     } else {
       4
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp4_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = true\135.txt"
index 335a543..8f91499 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testDynamicWrappingGroupWithReturnValue\133useFir = true\135.txt"
@@ -29,23 +29,23 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "")
     val tmp4_group = if (x > 0) {
-      val tmp3_group = if (%composer.startReplaceableGroup(<>)
+      val tmp3_group = if (%composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<B()>")
       val tmp1_group = B(%composer, 0)
-      %composer.endReplaceableGroup()
-      tmp1_group) 1 else if (%composer.startReplaceableGroup(<>)
+      %composer.endReplaceGroup()
+      tmp1_group) 1 else if (%composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<B()>")
       val tmp2_group = B(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp2_group) 2 else 3
       tmp3_group
     } else {
       4
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp4_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = false\135.txt"
index a8106b9..78cdf3e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = false\135.txt"
@@ -23,27 +23,27 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<R()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<R()>")
   if (x > 0) {
     val tmp1_return = R(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   val tmp0 = R(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = true\135.txt"
index a8106b9..78cdf3e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnCallValue\133useFir = true\135.txt"
@@ -23,27 +23,27 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<R()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<R()>")
   if (x > 0) {
     val tmp1_return = R(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   val tmp0 = R(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
index 6eb97a7..9874dcf 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
@@ -29,12 +29,12 @@
     }
     Dialog({ %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Test(p...>")
       if (false) {
         Test(param, %composer, 0b1110 and %dirty)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
index 6eb97a7..9874dcf 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
@@ -29,12 +29,12 @@
     }
     Dialog({ %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Test(p...>")
       if (false) {
         Test(param, %composer, 0b1110 and %dirty)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = false\135.txt"
index af9595d..2711f11 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = false\135.txt"
@@ -38,10 +38,10 @@
     val tmp0_subject = state.value
     when {
       tmp0_subject == true -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<Text(t...>")
         val tmp1_return = Text("true", %composer, 0b0110)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
@@ -51,10 +51,10 @@
         return tmp1_return
       }
       else -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<Text(t...>")
         Text("false", %composer, 0b0110)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }
     }
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = true\135.txt"
index af9595d..2711f11 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromWhenStatement\133useFir = true\135.txt"
@@ -38,10 +38,10 @@
     val tmp0_subject = state.value
     when {
       tmp0_subject == true -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<Text(t...>")
         val tmp1_return = Text("true", %composer, 0b0110)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
@@ -51,10 +51,10 @@
         return tmp1_return
       }
       else -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<Text(t...>")
         Text("false", %composer, 0b0110)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }
     }
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = false\135.txt"
index 6d045c2..3466566 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = false\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -34,7 +34,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   A(%composer, 0)
@@ -42,6 +42,6 @@
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = true\135.txt"
index 6d045c2..3466566 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValueWithCallsAfter\133useFir = true\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -34,7 +34,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   A(%composer, 0)
@@ -42,6 +42,6 @@
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = false\135.txt"
index 904ae2a..06474c2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = false\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -35,13 +35,13 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = 2
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = true\135.txt"
index 904ae2a..06474c2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnValue\133useFir = true\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -35,13 +35,13 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = 2
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = false\135.txt"
index 66cec40..aa2146c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = false\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -33,12 +33,12 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = true\135.txt"
index 66cec40..aa2146c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsAfterButNotBefore\133useFir = true\135.txt"
@@ -24,7 +24,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -33,12 +33,12 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = false\135.txt"
index e698b4b..4d0b5f4 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = false\135.txt"
@@ -25,7 +25,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -35,12 +35,12 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
   print("hello")
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = true\135.txt"
index e698b4b..4d0b5f4 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnWithCallsBeforeButNotAfter\133useFir = true\135.txt"
@@ -25,7 +25,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(x: Int, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -35,12 +35,12 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
   print("hello")
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = false\135.txt"
index 6b3b281..da5c0d3 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   }
   val y = <block>{
     val <elvis> = x
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R()>")
     val tmp0_group = when {
       <elvis> == null -> {
@@ -39,7 +39,7 @@
         <elvis>
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = true\135.txt"
index 6b3b281..da5c0d3 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testElvis\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   }
   val y = <block>{
     val <elvis> = x
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R()>")
     val tmp0_group = when {
       <elvis> == null -> {
@@ -39,7 +39,7 @@
         <elvis>
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = false\135.txt"
index abf76e6..33fa453 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = false\135.txt"
@@ -33,14 +33,14 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     IW({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>:Test.kt")
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@IW
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = true\135.txt"
index abf76e6..33fa453 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureEarlyExitInInline_Labeled\133useFir = true\135.txt"
@@ -33,14 +33,14 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     IW({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>:Test.kt")
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@IW
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = false\135.txt"
index ac7376c..0dc5b33 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = false\135.txt"
@@ -34,7 +34,7 @@
     }
     Text("Root - before", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("M1 - before", %composer, 0b0110)
       if (condition) {
@@ -48,7 +48,7 @@
         return
       }
       Text("M1 - after", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Root - after", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = true\135.txt"
index ac7376c..0dc5b33 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEnsureRuntimeTestWillCompile_CG\133useFir = true\135.txt"
@@ -34,7 +34,7 @@
     }
     Text("Root - before", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("M1 - before", %composer, 0b0110)
       if (condition) {
@@ -48,7 +48,7 @@
         return
       }
       Text("M1 - after", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Root - after", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
index ac862b1..e200d6a 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
@@ -29,14 +29,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val i = <iterator>.next()
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
index ac862b1..e200d6a 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
@@ -29,14 +29,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val i = <iterator>.next()
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = false\135.txt"
index 4155895..720eec5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = false\135.txt"
@@ -29,14 +29,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val i = <iterator>.next()
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = true\135.txt"
index 4155895..720eec5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testForLoopWithCallsInBody\133useFir = true\135.txt"
@@ -29,14 +29,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(i)>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val i = <iterator>.next()
     P(i, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = false\135.txt"
index 359608f..5799185 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = false\135.txt"
@@ -46,12 +46,12 @@
     while (<iterator>.hasNext()) {
       val i = <iterator>.next()
       val b = a.get(bKey, %composer, 0b00110110)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<get(cK...>")
       if (i == 0b0010) {
         a.get(cKey, %composer, 0b00110110)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = true\135.txt"
index 359608f..5799185 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupAroundExtensionFunctions\133useFir = true\135.txt"
@@ -46,12 +46,12 @@
     while (<iterator>.hasNext()) {
       val i = <iterator>.next()
       val b = a.get(bKey, %composer, 0b00110110)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<get(cK...>")
       if (i == 0b0010) {
         a.get(cKey, %composer, 0b00110110)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = false\135.txt"
index 70e51b9..2cc1338 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = false\135.txt"
@@ -47,7 +47,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     items.forEach { item: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "")
       if (item > -1) {
         %composer.startMovableGroup(<>, item)
@@ -63,7 +63,7 @@
         %composer.endMovableGroup()
         tmp0
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -90,7 +90,7 @@
     val <iterator> = items.iterator()
     while (<iterator>.hasNext()) {
       val item = <iterator>.next()
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "")
       if (item > -1) {
         %composer.startMovableGroup(<>, item)
@@ -106,7 +106,7 @@
         %composer.endMovableGroup()
         tmp0
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = true\135.txt"
index 5fbcf02..57e0568 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testGroupsInLoops\133useFir = true\135.txt"
@@ -47,7 +47,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     items.forEach { item: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "")
       if (item > -1) {
         %composer.startMovableGroup(<>, item)
@@ -59,7 +59,7 @@
         sourceInformationMarkerEnd(%composer)
         %composer.endMovableGroup()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -86,7 +86,7 @@
     val <iterator> = items.iterator()
     while (<iterator>.hasNext()) {
       val item = <iterator>.next()
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "")
       if (item > -1) {
         %composer.startMovableGroup(<>, item)
@@ -102,7 +102,7 @@
         %composer.endMovableGroup()
         tmp0
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = false\135.txt"
index 8b48bc4..d3cb440 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = false\135.txt"
@@ -32,15 +32,15 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   if (x > 0) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A(a)>")
     A(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   } else {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A(b)>")
     A(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = true\135.txt"
index 8b48bc4..d3cb440 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInBranch\133useFir = true\135.txt"
@@ -32,15 +32,15 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   if (x > 0) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A(a)>")
     A(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   } else {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A(b)>")
     A(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = false\135.txt"
index 44a9f0a..93b4d57 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = false\135.txt"
@@ -35,24 +35,24 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
-  if (%composer.startReplaceableGroup(<>)
+  if (%composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B(a)>")
   val tmp0_group = B(a, %composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group) {
     NA()
-  } else if (%composer.startReplaceableGroup(<>)
+  } else if (%composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B(b)>")
   val tmp1_group = B(b, %composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp1_group) {
     NA()
   } else {
     NA()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = true\135.txt"
index 44a9f0a..93b4d57 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfElseWithCallsInConditions\133useFir = true\135.txt"
@@ -35,24 +35,24 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
-  if (%composer.startReplaceableGroup(<>)
+  if (%composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B(a)>")
   val tmp0_group = B(a, %composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group) {
     NA()
-  } else if (%composer.startReplaceableGroup(<>)
+  } else if (%composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B(b)>")
   val tmp1_group = B(b, %composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp1_group) {
     NA()
   } else {
     NA()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = false\135.txt"
index 4673898..59f08b0 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = false\135.txt"
@@ -28,12 +28,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   if (x > 0) {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = true\135.txt"
index 4673898..59f08b0 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithCallsInBranch\133useFir = true\135.txt"
@@ -28,12 +28,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   if (x > 0) {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = false\135.txt"
index 40a50be..fe1b30e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = false\135.txt"
@@ -28,17 +28,17 @@
       traceEventStart(<>, %changed, -1, <>)
     }
     run {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Test()>")
       if (true) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@run
       } else {
         Test(%composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@run
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = true\135.txt"
index 40a50be..fe1b30e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testIfWithEarlyReturnInsideInlineLambda\133useFir = true\135.txt"
@@ -28,17 +28,17 @@
       traceEventStart(<>, %changed, -1, <>)
     }
     run {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Test()>")
       if (true) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@run
       } else {
         Test(%composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@run
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = false\135.txt"
index 1813dd9..5872e2c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = false\135.txt"
@@ -24,17 +24,17 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   InlineNonComposable {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<Test("...>")
     repeat(10) { it: Int ->
       Test("InsideInline", %composer, 0b0110)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   val tmp0 = Test("AfterInline", %composer, 0b0110)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = true\135.txt"
index 1813dd9..5872e2c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambdaBeforeACall\133useFir = true\135.txt"
@@ -24,17 +24,17 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   InlineNonComposable {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<Test("...>")
     repeat(10) { it: Int ->
       Test("InsideInline", %composer, 0b0110)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   val tmp0 = Test("AfterInline", %composer, 0b0110)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = false\135.txt"
index b5e8b9d..12bf825 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = false\135.txt"
@@ -27,18 +27,18 @@
     }
     Inline1({ %composer: Composer?, %changed: Int ->
       val tmp0_marker = %composer.currentMarker
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Inline...>:Test.kt")
       Inline2({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (true) {
           %composer.endToMarker(tmp0_marker)
           return@Inline1
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = true\135.txt"
index b5e8b9d..12bf825 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineLambda_nonLocalReturn\133useFir = true\135.txt"
@@ -27,18 +27,18 @@
     }
     Inline1({ %composer: Composer?, %changed: Int ->
       val tmp0_marker = %composer.currentMarker
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Inline...>:Test.kt")
       Inline2({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (true) {
           %composer.endToMarker(tmp0_marker)
           return@Inline1
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = false\135.txt"
index 75bd69e..5024195 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = false\135.txt"
@@ -35,14 +35,14 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   FakeBox({ %composer: Composer?, %changed: Int ->
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C<A()>:Test.kt")
     if (condition) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return@FakeBox
     }
     A(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = true\135.txt"
index 75bd69e..5024195 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInlineReturnLabel\133useFir = true\135.txt"
@@ -35,14 +35,14 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   FakeBox({ %composer: Composer?, %changed: Int ->
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C<A()>:Test.kt")
     if (condition) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return@FakeBox
     }
     A(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = false\135.txt"
index f4a59d1..97e6e7f 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = false\135.txt"
@@ -36,7 +36,7 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
@@ -50,7 +50,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = true\135.txt"
index f4a59d1..97e6e7f 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun\133useFir = true\135.txt"
@@ -36,7 +36,7 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
@@ -50,7 +50,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = false\135.txt"
index 039aae79..3a2c802 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = false\135.txt"
@@ -46,7 +46,7 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (a) {
@@ -60,10 +60,10 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (b) {
@@ -77,7 +77,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = true\135.txt"
index 039aae79..3a2c802 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RFun_CM3_RFun\133useFir = true\135.txt"
@@ -46,7 +46,7 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (a) {
@@ -60,10 +60,10 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (b) {
@@ -77,7 +77,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = false\135.txt"
index 5861274..8e9c30d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = false\135.txt"
@@ -35,15 +35,15 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = true\135.txt"
index 5861274..8e9c30d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_RM3\133useFir = true\135.txt"
@@ -35,15 +35,15 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = false\135.txt"
index 93faaff..3a855da 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = false\135.txt"
@@ -42,26 +42,26 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = true\135.txt"
index 93faaff..3a855da 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_CM3_Return_M3_CM3_Return_M3\133useFir = true\135.txt"
@@ -42,26 +42,26 @@
     }
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@M3
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = false\135.txt"
index ec150c6..66014f8 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = false\135.txt"
@@ -26,7 +26,7 @@
           traceEventStart(<>, %changed, -1, <>)
         }
         M1({ %composer: Composer?, %changed: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C:Test.kt")
           if (condition) {
             %composer.endToMarker(tmp0_marker)
@@ -35,7 +35,7 @@
             }
             return@composableLambdaInstance
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }, %composer, 0)
         if (isTraceInProgress()) {
           traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = true\135.txt"
index ec150c6..66014f8 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_Lambda\133useFir = true\135.txt"
@@ -26,7 +26,7 @@
           traceEventStart(<>, %changed, -1, <>)
         }
         M1({ %composer: Composer?, %changed: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C:Test.kt")
           if (condition) {
             %composer.endToMarker(tmp0_marker)
@@ -35,7 +35,7 @@
             }
             return@composableLambdaInstance
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }, %composer, 0)
         if (isTraceInProgress()) {
           traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = false\135.txt"
index 0ae15bf..ba1e034 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = false\135.txt"
@@ -40,10 +40,10 @@
     }
     A(%composer, 0)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<A()>,<A()>")
       while (true) {
         A(%composer, 0)
@@ -59,9 +59,9 @@
         }
         A(%composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = true\135.txt"
index 0ae15bf..ba1e034 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M1_W_Return_Func\133useFir = true\135.txt"
@@ -40,10 +40,10 @@
     }
     A(%composer, 0)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<A()>,<A()>")
       while (true) {
         A(%composer, 0)
@@ -59,9 +59,9 @@
         }
         A(%composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = false\135.txt"
index 4e5850e..906de43 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = false\135.txt"
@@ -40,13 +40,13 @@
       sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
       A(%composer, 0)
       M1({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (condition) {
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return@M1
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
       A(%composer, 0)
       sourceInformationMarkerEnd(%composer)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = true\135.txt"
index 4e5850e..906de43 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M1\133useFir = true\135.txt"
@@ -40,13 +40,13 @@
       sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
       A(%composer, 0)
       M1({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (condition) {
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return@M1
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
       A(%composer, 0)
       sourceInformationMarkerEnd(%composer)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = false\135.txt"
index 3fd2128..76d72b5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = false\135.txt"
@@ -38,20 +38,20 @@
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
       val tmp0_marker = %composer.currentMarker
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
       A(%composer, 0)
       M1({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (condition) {
           %composer.endToMarker(tmp0_marker)
           return@M3
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = true\135.txt"
index 3fd2128..76d72b5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testInline_M3_M1_Return_M3\133useFir = true\135.txt"
@@ -38,20 +38,20 @@
     A(%composer, 0)
     M3({ %composer: Composer?, %changed: Int ->
       val tmp0_marker = %composer.currentMarker
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
       A(%composer, 0)
       M1({ %composer: Composer?, %changed: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "C:Test.kt")
         if (condition) {
           %composer.endToMarker(tmp0_marker)
           return@M3
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }, %composer, 0)
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = false\135.txt"
index 5c95f9f..0141b32 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A(b)>")
   if (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -38,7 +38,7 @@
     %composer.endMovableGroup()
     A(b, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = true\135.txt"
index 5c95f9f..0141b32 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsAfter\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A(b)>")
   if (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -38,7 +38,7 @@
     %composer.endMovableGroup()
     A(b, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = false\135.txt"
index 4152631e..0f63c6b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A(a)>")
   if (x > 0) {
     A(a, %composer, 0)
@@ -38,7 +38,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = true\135.txt"
index 4152631e..0f63c6b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIfAndCallsBefore\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A(a)>")
   if (x > 0) {
     A(a, %composer, 0)
@@ -38,7 +38,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = false\135.txt"
index 03e5b2f..ccfb16c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = false\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   if (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = true\135.txt"
index 03e5b2f..ccfb16c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyInIf\133useFir = true\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   if (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = false\135.txt"
index 91509c0..37f70fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = false\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<R()>")
   while (x > 0) {
     %composer.startMovableGroup(<>, R(%composer, 0))
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = true\135.txt"
index 91509c0..37f70fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testKeyWithComposableValue\133useFir = true\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<R()>")
   while (x > 0) {
     %composer.startMovableGroup(<>, R(%composer, 0))
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = false\135.txt"
index 65deee7..9e1a7a6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = false\135.txt"
@@ -25,7 +25,7 @@
       traceEventStart(<>, %changed, -1, <>)
     }
     val factory = createFactory { %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
       }
@@ -33,7 +33,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp0
     }
     factory(%composer, 0)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = true\135.txt"
index 65deee7..9e1a7a6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLambdaWithNonUnitResult\133useFir = true\135.txt"
@@ -25,7 +25,7 @@
       traceEventStart(<>, %changed, -1, <>)
     }
     val factory = createFactory { %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
       }
@@ -33,7 +33,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp0
     }
     factory(%composer, 0)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = false\135.txt"
index 7471158..5c540b6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = false\135.txt"
@@ -36,7 +36,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     val tmp0_safe_receiver = x
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A(b)>")
     val tmp0_group = when {
       tmp0_safe_receiver == null -> {
@@ -44,17 +44,17 @@
       }
       else -> {
         tmp0_safe_receiver.let { it: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(a)>")
           if (it > 0) {
             A(a, %composer, 0)
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           A(b, %composer, 0)
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
     A(c, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = true\135.txt"
index 7471158..5c540b6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLetWithComposableCalls\133useFir = true\135.txt"
@@ -36,7 +36,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     val tmp0_safe_receiver = x
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A(b)>")
     val tmp0_group = when {
       tmp0_safe_receiver == null -> {
@@ -44,17 +44,17 @@
       }
       else -> {
         tmp0_safe_receiver.let { it: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(a)>")
           if (it > 0) {
             A(a, %composer, 0)
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           A(b, %composer, 0)
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
     A(c, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = false\135.txt"
index be60c3e..7556d53 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = false\135.txt"
@@ -34,24 +34,24 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   a@while (a.hasNext()) {
     val x = a.next()
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       val y = b.next()
       if (y == x) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         break@a
       }
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = true\135.txt"
index be60c3e..7556d53 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithBreak\133useFir = true\135.txt"
@@ -34,24 +34,24 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   a@while (a.hasNext()) {
     val x = a.next()
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       val y = b.next()
       if (y == x) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         break@a
       }
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = false\135.txt"
index 1d82d7b..aecf15c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = false\135.txt"
@@ -26,7 +26,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -37,7 +37,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return
     }
     A(%composer, 0)
@@ -45,5 +45,5 @@
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = true\135.txt"
index 1d82d7b..aecf15c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testLoopWithReturn\133useFir = true\135.txt"
@@ -26,7 +26,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -37,7 +37,7 @@
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return
     }
     A(%composer, 0)
@@ -45,5 +45,5 @@
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = false\135.txt"
index b048ac1..9b7a7fe 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = false\135.txt"
@@ -30,18 +30,18 @@
     }
     Wrapper({ %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<Leaf(0...>")
       repeat(1) { it: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "*<Leaf(0...>")
         repeat(1) { it: Int ->
           Leaf(0, %composer, 0b0110, 0)
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         Leaf(0, %composer, 0b0110, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = true\135.txt"
index b048ac1..9b7a7fe 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testMultipleNestedInlines\133useFir = true\135.txt"
@@ -30,18 +30,18 @@
     }
     Wrapper({ %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<Leaf(0...>")
       repeat(1) { it: Int ->
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "*<Leaf(0...>")
         repeat(1) { it: Int ->
           Leaf(0, %composer, 0b0110, 0)
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         Leaf(0, %composer, 0b0110, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = false\135.txt"
index 728f598..5713e7dc 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = false\135.txt"
@@ -39,7 +39,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -49,7 +49,7 @@
     if (x == 0) {
       break
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       val y = b.next()
@@ -57,24 +57,24 @@
         break
       }
       if (y == x) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         break@a
       }
       if (y > 100) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return
       }
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = true\135.txt"
index 9f4feab..9d3b5de 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoopsAndBreak\133useFir = true\135.txt"
@@ -39,7 +39,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -49,7 +49,7 @@
     if (x == 0) {
       break@a
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       val y = b.next()
@@ -57,24 +57,24 @@
         break@b
       }
       if (y == x) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         break@a
       }
       if (y > 100) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return
       }
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = false\135.txt"
index 7cedd68..8532169 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = false\135.txt"
@@ -30,18 +30,18 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   a@while (a.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = true\135.txt"
index 7cedd68..8532169 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testNestedLoops\133useFir = true\135.txt"
@@ -30,18 +30,18 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   a@while (a.hasNext()) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     b@while (b.hasNext()) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = false\135.txt"
index 0ee3819..0355a3b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = false\135.txt"
@@ -25,10 +25,10 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B()>,<B()>")
   val tmp0_group = B(%composer, 0) || B(%composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = true\135.txt"
index 0ee3819..0355a3b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOR\133useFir = true\135.txt"
@@ -25,10 +25,10 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<B()>,<B()>")
   val tmp0_group = B(%composer, 0) || B(%composer, 0)
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = false\135.txt"
index c4a2bf9..dbf6574 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = false\135.txt"
@@ -49,10 +49,10 @@
       val l = i
       P(i, %composer, 0)
       if (i == 0) {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<P(j)>")
         P(j, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
@@ -61,10 +61,10 @@
         }
         return
       } else {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<P(k)>")
         P(k, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }
       P(l, %composer, 0)
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = true\135.txt"
index c4a2bf9..dbf6574 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOrderingOfPushedEndCallsWithEarlyReturns\133useFir = true\135.txt"
@@ -49,10 +49,10 @@
       val l = i
       P(i, %composer, 0)
       if (i == 0) {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<P(j)>")
         P(j, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         if (isTraceInProgress()) {
           traceEventEnd()
         }
@@ -61,10 +61,10 @@
         }
         return
       } else {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<P(k)>")
         P(k, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
       }
       P(l, %composer, 0)
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = false\135.txt"
index 2458288..25a0cbd 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = false\135.txt"
@@ -17,7 +17,7 @@
 class SomeClassImpl : SomeClass {
   @Composable
   override fun SomeFunction(%composer: Composer?, %changed: Int): Int {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C(SomeFunction):Test.kt")
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
@@ -26,7 +26,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp0
   }
   static val %stable: Int = 0
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = true\135.txt"
index 2458288..25a0cbd 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testOverrideWithNonUnitResult\133useFir = true\135.txt"
@@ -17,7 +17,7 @@
 class SomeClassImpl : SomeClass {
   @Composable
   override fun SomeFunction(%composer: Composer?, %changed: Int): Int {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C(SomeFunction):Test.kt")
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
@@ -26,7 +26,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp0
   }
   static val %stable: Int = 0
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = false\135.txt"
index 31ea2f2..936c9e6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = false\135.txt"
@@ -32,7 +32,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     Test(<block>{
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<rememb...>")
       val tmp1_group = if (param == null) {
         sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -44,7 +44,7 @@
       } else {
         null
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp1_group
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = true\135.txt"
index 31ea2f2..936c9e6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInConditionalCallArgument\133useFir = true\135.txt"
@@ -32,7 +32,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     Test(<block>{
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<rememb...>")
       val tmp1_group = if (param == null) {
         sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -44,7 +44,7 @@
       } else {
         null
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp1_group
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = false\135.txt"
index 4d48d88..f07fc50 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = false\135.txt"
@@ -32,11 +32,11 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = Test(<block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<Test(>")
     val tmp3_group = if (param == null) {
       Test(<block>{
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<rememb...>")
         val tmp2_group = if (param == null) {
           sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -48,13 +48,13 @@
         } else {
           null
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp2_group
       }, %composer, 0)
     } else {
       null
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp3_group
   }, %composer, 0)
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = true\135.txt"
index 4d48d88..f07fc50 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRememberInNestedConditionalCallArgument\133useFir = true\135.txt"
@@ -32,11 +32,11 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = Test(<block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<Test(>")
     val tmp3_group = if (param == null) {
       Test(<block>{
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<rememb...>")
         val tmp2_group = if (param == null) {
           sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -48,13 +48,13 @@
         } else {
           null
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp2_group
       }, %composer, 0)
     } else {
       null
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp3_group
   }, %composer, 0)
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = false\135.txt"
index d6d5953..3dede05d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = false\135.txt"
@@ -44,14 +44,14 @@
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
       }
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<effect>")
       repeat(number) { it: Int ->
         effects[it] = effect({
           0
         }, %composer, 0b0110)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       outside = effect({
         "0"
       }, %composer, 0b0110)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = true\135.txt"
index d6d5953..3dede05d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testRepeatedCallsToEffects\133useFir = true\135.txt"
@@ -44,14 +44,14 @@
       if (isTraceInProgress()) {
         traceEventStart(<>, %changed, -1, <>)
       }
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "*<effect>")
       repeat(number) { it: Int ->
         effects[it] = effect({
           0
         }, %composer, 0b0110)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       outside = effect({
         "0"
       }, %composer, 0b0110)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = false\135.txt"
index 5219109..bd769f2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = false\135.txt"
@@ -33,7 +33,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -45,25 +45,25 @@
     val l = i
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<P(j)>")
       P(j, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return
     } else {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<P(k)>")
       P(k, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     P(l, %composer, 0)
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = true\135.txt"
index 5219109..bd769f2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnFromLoop\133useFir = true\135.txt"
@@ -33,7 +33,7 @@
 @NonRestartableComposable
 @Composable
 fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -45,25 +45,25 @@
     val l = i
     P(i, %composer, 0)
     if (i == 0) {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<P(j)>")
       P(j, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       if (isTraceInProgress()) {
         traceEventEnd()
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       return
     } else {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<P(k)>")
       P(k, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     P(l, %composer, 0)
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = false\135.txt"
index ed747d1..7d750cb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = false\135.txt"
@@ -31,14 +31,14 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     val tmp1_group = x.let { it: Int ->
       A(%composer, 0)
       val tmp0_return = 123
       tmp0_return
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = true\135.txt"
index ed747d1..7d750cb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnInlinedExpressionWithCall\133useFir = true\135.txt"
@@ -31,14 +31,14 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0 = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     val tmp1_group = x.let { it: Int ->
       A(%composer, 0)
       val tmp0_return = 123
       tmp0_return
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = false\135.txt"
index 406738b..c958359 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = false\135.txt"
@@ -82,7 +82,7 @@
 }
 @Composable
 fun Test2(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test2):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -92,19 +92,19 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = null
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
 @Composable
 fun Test3(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test3):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -114,20 +114,20 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp0_return
   } else {
     val tmp1_return = null
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
 @Composable
 fun Test4(b: Boolean, %composer: Composer?, %changed: Int): String? {
@@ -174,7 +174,7 @@
 }
 @Composable
 fun Test7(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test7):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -184,14 +184,14 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = "false"
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
 @Composable
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = true\135.txt"
index 2ef96cd..e461327 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testReturnNull\133useFir = true\135.txt"
@@ -82,7 +82,7 @@
 }
 @Composable
 fun Test2(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test2):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -92,19 +92,19 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = null
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
 @Composable
 fun Test3(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test3):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -114,20 +114,20 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp0_return
   } else {
     val tmp1_return = null
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
 @Composable
 fun Test4(b: Boolean, %composer: Composer?, %changed: Int): String? {
@@ -170,7 +170,7 @@
 }
 @Composable
 fun Test7(b: Boolean, %composer: Composer?, %changed: Int): String? {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(Test7):Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
@@ -180,14 +180,14 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return tmp1_return
   }
   val tmp0 = "false"
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   return tmp0
 }
 @Composable
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = false\135.txt"
index 33b11fc2..eed4dc6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = false\135.txt"
@@ -28,7 +28,7 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0_safe_receiver = x
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   val tmp0_group = when {
     tmp0_safe_receiver == null -> {
@@ -38,7 +38,7 @@
       tmp0_safe_receiver.A(%composer, 0b1110 and %changed)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = true\135.txt"
index 33b11fc2..eed4dc6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSafeCall\133useFir = true\135.txt"
@@ -28,7 +28,7 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val tmp0_safe_receiver = x
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   val tmp0_group = when {
     tmp0_safe_receiver == null -> {
@@ -38,7 +38,7 @@
       tmp0_safe_receiver.A(%composer, 0b1110 and %changed)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   tmp0_group
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = false\135.txt"
index 4e5f7ed..a38afd2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = false\135.txt"
@@ -50,12 +50,12 @@
       IW({ %composer: Composer?, %changed: Int ->
         sourceInformationMarkerStart(%composer, <>, "C<T(2)>,<T(4)>:Test.kt")
         T(2, %composer, 0b0110)
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "*<T(3)>")
         repeat(3) { it: Int ->
           T(3, %composer, 0b0110)
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         T(4, %composer, 0b0110)
         sourceInformationMarkerEnd(%composer)
       }, %composer, 0)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = true\135.txt"
index 4e5f7ed..a38afd2 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testSourceLineInformationForNormalInline\133useFir = true\135.txt"
@@ -50,12 +50,12 @@
       IW({ %composer: Composer?, %changed: Int ->
         sourceInformationMarkerStart(%composer, <>, "C<T(2)>,<T(4)>:Test.kt")
         T(2, %composer, 0b0110)
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "*<T(3)>")
         repeat(3) { it: Int ->
           T(3, %composer, 0b0110)
         }
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         T(4, %composer, 0b0110)
         sourceInformationMarkerEnd(%composer)
       }, %composer, 0)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = false\135.txt"
index 619a9f7..4963e87 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = false\135.txt"
@@ -61,12 +61,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
@@ -76,28 +76,28 @@
 @NonRestartableComposable
 @Composable
 fun WithReturn(%composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(WithReturn)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
 @NonRestartableComposable
 @Composable
@@ -122,12 +122,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = true\135.txt"
index 619a9f7..4963e87 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testTheThing\133useFir = true\135.txt"
@@ -61,12 +61,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
@@ -76,28 +76,28 @@
 @NonRestartableComposable
 @Composable
 fun WithReturn(%composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "C(WithReturn)<A()>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     return
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
 @NonRestartableComposable
 @Composable
@@ -122,12 +122,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   run {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = false\135.txt"
index af46314..a6f29e1 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = false\135.txt"
@@ -34,7 +34,7 @@
     }
     Text("Some text", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       Identity {
         if (condition) {
@@ -48,7 +48,7 @@
           return
         }
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Some more text", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = true\135.txt"
index af46314..a6f29e1 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1\133useFir = true\135.txt"
@@ -34,7 +34,7 @@
     }
     Text("Some text", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       Identity {
         if (condition) {
@@ -48,7 +48,7 @@
           return
         }
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Some more text", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = false\135.txt"
index d924718..042447b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = false\135.txt"
@@ -33,15 +33,15 @@
     }
     Text("Some text", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       Identity {
         if (condition) {
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return@M1
         }
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Some more text", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = true\135.txt"
index d924718..042447b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testVerifyEarlyExitFromNonComposable_M1_RM1\133useFir = true\135.txt"
@@ -33,15 +33,15 @@
     }
     Text("Some text", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C:Test.kt")
       Identity {
         if (condition) {
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return@M1
         }
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Some more text", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = false\135.txt"
index 9681ed7..abdb1fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = false\135.txt"
@@ -32,20 +32,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   when {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(a)>")
     val tmp0_group = x == R(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group -> {
       NA()
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(b)>")
     val tmp1_group = x > R(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group -> {
       NA()
     }
@@ -53,7 +53,7 @@
       NA()
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = true\135.txt"
index 9681ed7..abdb1fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditionsAndCallAfter\133useFir = true\135.txt"
@@ -32,20 +32,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   when {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(a)>")
     val tmp0_group = x == R(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group -> {
       NA()
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(b)>")
     val tmp1_group = x > R(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group -> {
       NA()
     }
@@ -53,7 +53,7 @@
       NA()
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = false\135.txt"
index 2458736..13e1ae3 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = false\135.txt"
@@ -32,20 +32,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   when {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(a)>")
     val tmp0_group = x == R(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group -> {
       NA()
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(b)>")
     val tmp1_group = x > R(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group -> {
       NA()
     }
@@ -53,7 +53,7 @@
       NA()
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = true\135.txt"
index 2458736..13e1ae3 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInConditions\133useFir = true\135.txt"
@@ -32,20 +32,20 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   when {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(a)>")
     val tmp0_group = x == R(a, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group -> {
       NA()
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<R(b)>")
     val tmp1_group = x > R(b, %composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group -> {
       NA()
     }
@@ -53,7 +53,7 @@
       NA()
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = false\135.txt"
index 2cee4f4..bc3f736 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = false\135.txt"
@@ -33,21 +33,21 @@
   }
   when {
     x < 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     x > 30 -> {
-      %composer.startReplaceableGroup(<>)
-      %composer.endReplaceableGroup()
+      %composer.startReplaceGroup(<>)
+      %composer.endReplaceGroup()
       NA()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = true\135.txt"
index 2cee4f4..bc3f736 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCallsInSomeResults\133useFir = true\135.txt"
@@ -33,21 +33,21 @@
   }
   when {
     x < 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     x > 30 -> {
-      %composer.startReplaceableGroup(<>)
-      %composer.endReplaceableGroup()
+      %composer.startReplaceGroup(<>)
+      %composer.endReplaceGroup()
       NA()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = false\135.txt"
index 6951232..5f0305a 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = false\135.txt"
@@ -33,22 +33,22 @@
   }
   when {
     x < 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     x > 30 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(c)>")
       A(c, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = true\135.txt"
index 6951232..5f0305a 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithCalls\133useFir = true\135.txt"
@@ -33,22 +33,22 @@
   }
   when {
     x < 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     x > 30 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(c)>")
       A(c, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = false\135.txt"
index ec19619..cfff1ad 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = false\135.txt"
@@ -35,24 +35,24 @@
     val tmp0_subject = x
     when {
       tmp0_subject == 0 -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(a)>")
         val tmp0_group = R(a, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp0_group
       }
       tmp0_subject == 0b0001 -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(b)>")
         val tmp1_group = R(b, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp1_group
       }
       else -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(c)>")
         val tmp2_group = R(c, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp2_group
       }
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = true\135.txt"
index ec19619..cfff1ad 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCallsWithResult\133useFir = true\135.txt"
@@ -35,24 +35,24 @@
     val tmp0_subject = x
     when {
       tmp0_subject == 0 -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(a)>")
         val tmp0_group = R(a, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp0_group
       }
       tmp0_subject == 0b0001 -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(b)>")
         val tmp1_group = R(b, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp1_group
       }
       else -> {
-        %composer.startReplaceableGroup(<>)
+        %composer.startReplaceGroup(<>)
         sourceInformation(%composer, "<R(c)>")
         val tmp2_group = R(c, %composer, 0)
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         tmp2_group
       }
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = false\135.txt"
index bad6af4..0a70d3e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = false\135.txt"
@@ -34,22 +34,22 @@
   val tmp0_subject = x
   when {
     tmp0_subject == 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     tmp0_subject == 0b0001 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(c)>")
       A(c, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = true\135.txt"
index bad6af4..0a70d3e 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhenWithSubjectAndCalls\133useFir = true\135.txt"
@@ -34,22 +34,22 @@
   val tmp0_subject = x
   when {
     tmp0_subject == 0 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(a)>")
       A(a, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     tmp0_subject == 0b0001 -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(b)>")
       A(b, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
     else -> {
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<A(c)>")
       A(c, %composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }
   }
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = false\135.txt"
index b15c231..ff05c971 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = false\135.txt"
@@ -29,18 +29,18 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   if (x > 0) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     while (x > 0) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = true\135.txt"
index b15c231..ff05c971 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallAfter\133useFir = true\135.txt"
@@ -29,18 +29,18 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>")
   if (x > 0) {
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<A()>")
     while (x > 0) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = false\135.txt"
index 69b1c1a..7f98ae5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>,*<A()>")
   if (x > 0) {
     A(%composer, 0)
@@ -37,7 +37,7 @@
       A(%composer, 0)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = true\135.txt"
index 69b1c1a..7f98ae5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIfAndCallBefore\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "<A()>,*<A()>")
   if (x > 0) {
     A(%composer, 0)
@@ -37,7 +37,7 @@
       A(%composer, 0)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = false\135.txt"
index 07965c2..4cf7057 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = false\135.txt"
@@ -28,14 +28,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   if (x > 0) {
     while (x > 0) {
       A(%composer, 0)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = true\135.txt"
index 07965c2..4cf7057 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileInsideIf\133useFir = true\135.txt"
@@ -28,14 +28,14 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A()>")
   if (x > 0) {
     while (x > 0) {
       A(%composer, 0)
     }
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
index d5bd839..d44e03b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = false\135.txt"
@@ -31,13 +31,13 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(item...>")
   while (items.isNotEmpty()) {
     val item = items.removeAt(items.size - 1)
     P(item, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
index d5bd839..d44e03b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBodyAndCallsAfter\133useFir = true\135.txt"
@@ -31,13 +31,13 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(item...>")
   while (items.isNotEmpty()) {
     val item = items.removeAt(items.size - 1)
     P(item, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = false\135.txt"
index 96a283c..6d5c7fc 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = false\135.txt"
@@ -31,13 +31,13 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(item...>")
   while (items.isNotEmpty()) {
     val item = items.removeAt(items.size - 1)
     P(item, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = true\135.txt"
index 96a283c..6d5c7fc 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInBody\133useFir = true\135.txt"
@@ -31,13 +31,13 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<P(item...>")
   while (items.isNotEmpty()) {
     val item = items.removeAt(items.size - 1)
     P(item, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = false\135.txt"
index 6d83f67..4719516 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = false\135.txt"
@@ -30,12 +30,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>,<A(a)>")
   while (B(%composer, 0)) {
     A(a, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(b, %composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = true\135.txt"
index 6d83f67..4719516 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBodyAndCallsAfter\133useFir = true\135.txt"
@@ -30,12 +30,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>,<A(a)>")
   while (B(%composer, 0)) {
     A(a, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(b, %composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = false\135.txt"
index eb47a3d..0b7ffbc 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = false\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>,<A()>")
   while (B(%composer, 0)) {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = true\135.txt"
index eb47a3d..0b7ffbc 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndBody\133useFir = true\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>,<A()>")
   while (B(%composer, 0)) {
     A(%composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = false\135.txt"
index 836920e..db5ea62 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = false\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>")
   while (B(%composer, 0)) {
     print("hello world")
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = true\135.txt"
index 836920e..db5ea62 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInConditionAndCallsAfter\133useFir = true\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>")
   while (B(%composer, 0)) {
     print("hello world")
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   A(%composer, 0)
   if (isTraceInProgress()) {
     traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = false\135.txt"
index c84f9e7..116ac66 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = false\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>")
   while (B(%composer, 0)) {
     print("hello world")
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = true\135.txt"
index c84f9e7..116ac66 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileLoopWithCallsInCondition\133useFir = true\135.txt"
@@ -29,12 +29,12 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<B()>")
   while (B(%composer, 0)) {
     print("hello world")
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = false\135.txt"
index 822c943..4061c76 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(b)>")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -38,7 +38,7 @@
     %composer.endMovableGroup()
     A(b, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = true\135.txt"
index 822c943..4061c76 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallAfter\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(b)>")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -38,7 +38,7 @@
     %composer.endMovableGroup()
     A(b, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = false\135.txt"
index 8feac5a..37404e1 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = false\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(a)>,<A(c)>")
   while (x > 0) {
     A(a, %composer, 0)
@@ -40,7 +40,7 @@
     %composer.endMovableGroup()
     A(c, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = true\135.txt"
index 8feac5a..37404e1 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBeforeAndAfter\133useFir = true\135.txt"
@@ -30,7 +30,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(a)>,<A(c)>")
   while (x > 0) {
     A(a, %composer, 0)
@@ -40,7 +40,7 @@
     %composer.endMovableGroup()
     A(c, %composer, 0)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = false\135.txt"
index d2dae96..42cda15 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = false\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(a)>")
   while (x > 0) {
     A(a, %composer, 0)
@@ -38,7 +38,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = true\135.txt"
index d2dae96..42cda15 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKeyAndCallBefore\133useFir = true\135.txt"
@@ -29,7 +29,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<A(a)>")
   while (x > 0) {
     A(a, %composer, 0)
@@ -38,7 +38,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = false\135.txt"
index 6aa8167..2df0578 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = false\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = true\135.txt"
index 6aa8167..2df0578 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithKey\133useFir = true\135.txt"
@@ -28,7 +28,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -36,7 +36,7 @@
     A(%composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = false\135.txt"
index 938e6cf..95fd7ed 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = false\135.txt"
@@ -31,7 +31,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -43,7 +43,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = true\135.txt"
index 938e6cf..95fd7ed 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testWhileWithTwoKeys\133useFir = true\135.txt"
@@ -31,7 +31,7 @@
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   while (x > 0) {
     %composer.startMovableGroup(<>, x)
@@ -43,7 +43,7 @@
     A(b, %composer, 0)
     %composer.endMovableGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = false\135.txt"
index 468d6f3..f895e8b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = false\135.txt"
@@ -40,15 +40,15 @@
     }
     Text("Root - before", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("M1 - begin", %composer, 0b0110)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Text("...>,<M1>")
       if (condition) {
         Text("if - begin", %composer, 0b0110)
         M1({ %composer: Composer?, %changed: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C<Text("...>:Test.kt")
           Text("In CCM1", %composer, 0b0110)
           %composer.endToMarker(tmp0_marker)
@@ -59,12 +59,12 @@
             test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
           }
           return
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }, %composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       Text("M1 - end", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Root - end", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = true\135.txt"
index 468d6f3..f895e8b 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/test_CM1_CCM1_RetFun\133useFir = true\135.txt"
@@ -40,15 +40,15 @@
     }
     Text("Root - before", %composer, 0b0110)
     M1({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("M1 - begin", %composer, 0b0110)
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<Text("...>,<M1>")
       if (condition) {
         Text("if - begin", %composer, 0b0110)
         M1({ %composer: Composer?, %changed: Int ->
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C<Text("...>:Test.kt")
           Text("In CCM1", %composer, 0b0110)
           %composer.endToMarker(tmp0_marker)
@@ -59,12 +59,12 @@
             test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
           }
           return
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }, %composer, 0)
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       Text("M1 - end", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("Root - end", %composer, 0b0110)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
index e3171f7..ee4e5ea 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
@@ -34,11 +34,11 @@
   Text("Before outer", %composer, 0b0110)
   InlineLinearA({ %composer: Composer?, %changed: Int ->
     val tmp0_marker = %composer.currentMarker
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("Before return", %composer, 0b0110)
       if (condition) {
@@ -46,10 +46,10 @@
         return@InlineLinearA
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   Text("Before outer", %composer, 0b0110)
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
index e3171f7..ee4e5ea 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
@@ -34,11 +34,11 @@
   Text("Before outer", %composer, 0b0110)
   InlineLinearA({ %composer: Composer?, %changed: Int ->
     val tmp0_marker = %composer.currentMarker
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("Before return", %composer, 0b0110)
       if (condition) {
@@ -46,10 +46,10 @@
         return@InlineLinearA
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   Text("Before outer", %composer, 0b0110)
   if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = false\135.txt"
index c804418..f622ab0 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = false\135.txt"
@@ -36,15 +36,15 @@
     sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("Before return", %composer, 0b0110)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@InlineLinearB
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
     sourceInformationMarkerEnd(%composer)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = true\135.txt"
index c804418..f622ab0 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/verifyEarlyExitFromNestedInlineFunction\133useFir = true\135.txt"
@@ -36,15 +36,15 @@
     sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
       Text("Before return", %composer, 0b0110)
       if (condition) {
-        %composer.endReplaceableGroup()
+        %composer.endReplaceGroup()
         return@InlineLinearB
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
     sourceInformationMarkerEnd(%composer)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
index 68a2b0f..c8788dd 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = false\135.txt"
@@ -30,20 +30,20 @@
   Text("Before outer", %composer, 0b0110)
   InlineLinearA({ %composer: Composer?, %changed: Int ->
     val tmp0_marker = %composer.currentMarker
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       Text("Before return", %composer, 0b0110)
       if (condition) {
         %composer.endToMarker(tmp0_marker)
         return@InlineLinearA
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   Text("Before outer", %composer, 0b0110)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
index 68a2b0f..c8788dd 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTestsNoSource/verifyEarlyExitFromMultiLevelNestedInlineFunction\133useFir = true\135.txt"
@@ -30,20 +30,20 @@
   Text("Before outer", %composer, 0b0110)
   InlineLinearA({ %composer: Composer?, %changed: Int ->
     val tmp0_marker = %composer.currentMarker
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     Text("Before inner", %composer, 0b0110)
     InlineLinearB({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       Text("Before return", %composer, 0b0110)
       if (condition) {
         %composer.endToMarker(tmp0_marker)
         return@InlineLinearA
       }
       Text("After return", %composer, 0b0110)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     Text("After inner", %composer, 0b0110)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }, %composer, 0)
   Text("Before outer", %composer, 0b0110)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = false\135.txt"
index 173da83..7ced598 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = false\135.txt"
@@ -17,7 +17,7 @@
 fun A(factory: Function2<Composer, Int, Int>) { }
 fun B() {
   return A { %composer: Composer?, %changed: Int ->
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
     }
@@ -25,7 +25,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0
   }
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = true\135.txt"
index 173da83..7ced598 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testComposableLambdasWithReturnGetGroups\133useFir = true\135.txt"
@@ -17,7 +17,7 @@
 fun A(factory: Function2<Composer, Int, Int>) { }
 fun B() {
   return A { %composer: Composer?, %changed: Int ->
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
     }
@@ -25,7 +25,7 @@
     if (isTraceInProgress()) {
       traceEventEnd()
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0
   }
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
index 1e95edb..6de1c6d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
@@ -63,7 +63,7 @@
       class <no name provided> : ButtonColors {
         @Composable
         override fun getColor(%composer: Composer?, %changed: Int): Color {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C(getColor)<condit...>:Test.kt")
           if (isTraceInProgress()) {
             traceEventStart(<>, %changed, -1, <>)
@@ -76,7 +76,7 @@
           if (isTraceInProgress()) {
             traceEventEnd()
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return tmp0
         }
       }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
index 1e95edb..6de1c6d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
@@ -63,7 +63,7 @@
       class <no name provided> : ButtonColors {
         @Composable
         override fun getColor(%composer: Composer?, %changed: Int): Color {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "C(getColor)<condit...>:Test.kt")
           if (isTraceInProgress()) {
             traceEventStart(<>, %changed, -1, <>)
@@ -76,7 +76,7 @@
           if (isTraceInProgress()) {
             traceEventEnd()
           }
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
           return tmp0
         }
       }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = false\135.txt"
index 0d68784..3a0bc0c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = false\135.txt"
@@ -56,15 +56,15 @@
           traceEventStart(<>, %changed, -1, <>)
         }
         if (x > 0) {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(x)>")
           A(x, 0, %composer, 0, 0b0010)
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         } else {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(x)>")
           A(x, 0, %composer, 0, 0b0010)
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }
         if (isTraceInProgress()) {
           traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = true\135.txt"
index 0d68784..3a0bc0c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testIfInLambda\133useFir = true\135.txt"
@@ -56,15 +56,15 @@
           traceEventStart(<>, %changed, -1, <>)
         }
         if (x > 0) {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(x)>")
           A(x, 0, %composer, 0, 0b0010)
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         } else {
-          %composer.startReplaceableGroup(<>)
+          %composer.startReplaceGroup(<>)
           sourceInformation(%composer, "<A(x)>")
           A(x, 0, %composer, 0, 0b0010)
-          %composer.endReplaceableGroup()
+          %composer.endReplaceGroup()
         }
         if (isTraceInProgress()) {
           traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = false\135.txt"
index 18102ab..c976626 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = false\135.txt"
@@ -31,22 +31,22 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   Call(%composer, 0)
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   val <iterator> = 0 .. 1.iterator()
   while (<iterator>.hasNext()) {
     val index = <iterator>.next()
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<Call()>,<Call()>")
     Call(%composer, 0)
     if (condition()) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     Call(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = true\135.txt"
index 18102ab..c976626 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testLoopWithContinueAndCallAfter\133useFir = true\135.txt"
@@ -31,22 +31,22 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   Call(%composer, 0)
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "")
   val <iterator> = 0 .. 1.iterator()
   while (<iterator>.hasNext()) {
     val index = <iterator>.next()
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<Call()>,<Call()>")
     Call(%composer, 0)
     if (condition()) {
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       continue
     }
     Call(%composer, 0)
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = false\135.txt"
index f3a5067..0c0f571 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = false\135.txt"
@@ -33,12 +33,12 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %dirty, -1, <>)
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A()>")
     if (cond) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (cond) {
       B(%composer, 0)
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = true\135.txt"
index f3a5067..0c0f571 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testSiblingIfsWithoutElseHaveUniqueKeys\133useFir = true\135.txt"
@@ -33,12 +33,12 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %dirty, -1, <>)
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<A()>")
     if (cond) {
       A(%composer, 0)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     if (cond) {
       B(%composer, 0)
     }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = false\135.txt"
index 8eedb86..3b8cb66 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = false\135.txt"
@@ -47,13 +47,13 @@
 @ComposableInferredTarget(scheme = "[0[0]]")
 fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
   sourceInformationMarkerStart(%composer, <>, "CC(Bug)P(1):Test.kt")
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<conten...>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val item = <iterator>.next()
     content(item, %composer, 0b01110000 and %changed)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   sourceInformationMarkerEnd(%composer)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = true\135.txt"
index 587adfa..2381eaa 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/test_InlineForLoop\133useFir = true\135.txt"
@@ -47,13 +47,13 @@
 @ComposableInferredTarget(scheme = "[0[0]]")
 fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
   sourceInformationMarkerStart(%composer, <>, "CC(Bug)P(1):Test.kt")
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   sourceInformation(%composer, "*<conten...>")
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val item = <iterator>.next()
     content(item, %composer, 0b01110000 and %changed)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
   sourceInformationMarkerEnd(%composer)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = false\135.txt"
index ed7d170..0d63bde 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = false\135.txt"
@@ -37,11 +37,11 @@
 @Composable
 @ComposableInferredTarget(scheme = "[0[0]]")
 private fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val item = <iterator>.next()
     content(item, %composer, 0b01110000 and %changed)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = true\135.txt"
index 2076a4f..0d5f79d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/test_InlineForLoop_no_source_info\133useFir = true\135.txt"
@@ -37,11 +37,11 @@
 @Composable
 @ComposableInferredTarget(scheme = "[0[0]]")
 private fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-  %composer.startReplaceableGroup(<>)
+  %composer.startReplaceGroup(<>)
   val <iterator> = items.iterator()
   while (<iterator>.hasNext()) {
     val item = <iterator>.next()
     content(item, %composer, 0b01110000 and %changed)
   }
-  %composer.endReplaceableGroup()
+  %composer.endReplaceGroup()
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = false\135.txt"
index 1f82a6f..2fa88ce 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = false\135.txt"
@@ -32,7 +32,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     Something(<block>{
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<{>")
       val tmp1_group = if (param != null) {
         sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -46,7 +46,7 @@
       } else {
         null
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp1_group
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = true\135.txt"
index 1f82a6f..2fa88ce 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testMemoizingFunctionInIf\133useFir = true\135.txt"
@@ -32,7 +32,7 @@
       traceEventStart(<>, %dirty, -1, <>)
     }
     Something(<block>{
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "<{>")
       val tmp1_group = if (param != null) {
         sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -46,7 +46,7 @@
       } else {
         null
       }
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
       tmp1_group
     }, %composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = false\135.txt"
index 5127fc0..4df31fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = false\135.txt"
@@ -27,7 +27,7 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val a = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<rememb...>")
     val tmp1_group = if (x) {
       sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -39,7 +39,7 @@
     } else {
       2
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
   }
   val b = <block>{
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = true\135.txt"
index 5127fc0..4df31fb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testElidedRememberInsideIfDeoptsRememberAfterIf\133useFir = true\135.txt"
@@ -27,7 +27,7 @@
     traceEventStart(<>, %changed, -1, <>)
   }
   val a = <block>{
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<rememb...>")
     val tmp1_group = if (x) {
       sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
@@ -39,7 +39,7 @@
     } else {
       2
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
   }
   val b = <block>{
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = false\135.txt"
index ed9888e..50a53e1 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = false\135.txt"
@@ -22,7 +22,7 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<rememb...>")
     val <iterator> = 0 until count.iterator()
     while (<iterator>.hasNext()) {
@@ -36,7 +36,7 @@
         tmp0_group
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     val a = <block>{
       sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
       val tmp1_group = %composer.cache(false) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = true\135.txt"
index eeb8395..d2f0f67c 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberInALoop\133useFir = true\135.txt"
@@ -22,7 +22,7 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %changed, -1, <>)
     }
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<rememb...>")
     val <iterator> = 0 until count.iterator()
     while (<iterator>.hasNext()) {
@@ -36,7 +36,7 @@
         tmp0_group
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     val a = <block>{
       sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp")
       val tmp1_group = %composer.cache(false) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = false\135.txt"
index fad6262..0f427c9 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = false\135.txt"
@@ -161,7 +161,7 @@
     }
     one(%composer, 0b1110 and %dirty)
     val tmp0_safe_receiver = two
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<invoke...>")
     val tmp0_group = when {
       tmp0_safe_receiver == null -> {
@@ -171,10 +171,10 @@
         tmp0_safe_receiver(%composer, 0b1110 and %dirty shr 0b0011)
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
     val tmp1_safe_receiver = three
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<it()>")
     val tmp1_group = when {
       tmp1_safe_receiver == null -> {
@@ -186,10 +186,10 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
     val tmp2_safe_receiver = four
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<four()>")
     val tmp2_group = when {
       tmp2_safe_receiver == null -> {
@@ -201,16 +201,16 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp2_group
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<five()>")
     if (five != null) {
       five(%composer, 0b1110 and %dirty shr 0b1100)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     val tmp3_safe_receiver = six
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<Wrappe...>")
     val tmp3_group = when {
       tmp3_safe_receiver == null -> {
@@ -222,7 +222,7 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp3_group
     content(%composer, 0b1110 and %dirty shr 0b00010010)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = true\135.txt"
index fad6262..0f427c9 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TargetAnnotationsTransformTests/testOptionalParameters\133useFir = true\135.txt"
@@ -161,7 +161,7 @@
     }
     one(%composer, 0b1110 and %dirty)
     val tmp0_safe_receiver = two
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<invoke...>")
     val tmp0_group = when {
       tmp0_safe_receiver == null -> {
@@ -171,10 +171,10 @@
         tmp0_safe_receiver(%composer, 0b1110 and %dirty shr 0b0011)
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp0_group
     val tmp1_safe_receiver = three
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<it()>")
     val tmp1_group = when {
       tmp1_safe_receiver == null -> {
@@ -186,10 +186,10 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp1_group
     val tmp2_safe_receiver = four
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<four()>")
     val tmp2_group = when {
       tmp2_safe_receiver == null -> {
@@ -201,16 +201,16 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp2_group
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "<five()>")
     if (five != null) {
       five(%composer, 0b1110 and %dirty shr 0b1100)
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     val tmp3_safe_receiver = six
-    %composer.startReplaceableGroup(<>)
+    %composer.startReplaceGroup(<>)
     sourceInformation(%composer, "*<Wrappe...>")
     val tmp3_group = when {
       tmp3_safe_receiver == null -> {
@@ -222,7 +222,7 @@
         }
       }
     }
-    %composer.endReplaceableGroup()
+    %composer.endReplaceGroup()
     tmp3_group
     content(%composer, 0b1110 and %dirty shr 0b00010010)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = false\135.txt"
index 73bb99b..626f0e42 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = false\135.txt"
@@ -44,7 +44,7 @@
     }
     A(%composer, 0)
     Wrapper({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (!condition) {
@@ -58,7 +58,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = true\135.txt"
index 73bb99b..626f0e42 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.TraceInformationTest/testInlineFunctionsDonotGenerateTraceMarkers\133useFir = true\135.txt"
@@ -44,7 +44,7 @@
     }
     A(%composer, 0)
     Wrapper({ %composer: Composer?, %changed: Int ->
-      %composer.startReplaceableGroup(<>)
+      %composer.startReplaceGroup(<>)
       sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
       A(%composer, 0)
       if (!condition) {
@@ -58,7 +58,7 @@
         return
       }
       A(%composer, 0)
-      %composer.endReplaceableGroup()
+      %composer.endReplaceGroup()
     }, %composer, 0)
     A(%composer, 0)
     if (isTraceInProgress()) {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index 8a9f216..dfe91af 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -131,6 +131,7 @@
             11800 to "1.6.0-beta01",
             12000 to "1.7.0-alpha01",
             12100 to "1.7.0-alpha02",
+            12200 to "1.7.0-alpha03",
         )
 
         /**
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
index 74d139b..acc6fd3 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
@@ -1254,7 +1254,7 @@
         }
     }
 
-    fun irStartReplaceableGroup(
+    fun irStartReplaceGroup(
         currentComposer: IrExpression,
         key: IrExpression,
         startOffset: Int = UNDEFINED_OFFSET,
@@ -1262,7 +1262,7 @@
     ): IrExpression {
         return irMethodCall(
             currentComposer,
-            startReplaceableFunction,
+            startReplaceFunction,
             startOffset,
             endOffset
         ).also {
@@ -1270,14 +1270,14 @@
         }
     }
 
-    fun irEndReplaceableGroup(
+    fun irEndReplaceGroup(
         currentComposer: IrExpression,
         startOffset: Int = UNDEFINED_OFFSET,
         endOffset: Int = UNDEFINED_OFFSET,
     ): IrExpression {
         return irMethodCall(
             currentComposer,
-            endReplaceableFunction,
+            endReplaceFunction,
             startOffset,
             endOffset
         )
@@ -1310,17 +1310,21 @@
                 it.valueParameters.first().type.isNullableAny()
         } ?: changedFunction
 
-    val startReplaceableFunction by guardedLazy {
-        composerIrClass.functions
+    private val startReplaceFunction by guardedLazy {
+        composerIrClass.functions.firstOrNull {
+            it.name.identifier == "startReplaceGroup" && it.valueParameters.size == 1
+        } ?: composerIrClass.functions
             .first {
                 it.name.identifier == "startReplaceableGroup" && it.valueParameters.size == 1
             }
     }
 
-    val endReplaceableFunction by guardedLazy {
-        composerIrClass.functions
+    private val endReplaceFunction by guardedLazy {
+        composerIrClass.functions.firstOrNull {
+            it.name.identifier == "endReplaceGroup" && it.valueParameters.isEmpty()
+        } ?: composerIrClass.functions
             .first {
-                it.name.identifier == "endReplaceableGroup" && it.valueParameters.size == 0
+                it.name.identifier == "endReplaceableGroup" && it.valueParameters.isEmpty()
             }
     }
 
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index c960c63..4647e77 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -312,7 +312,7 @@
  *
  * There are 3 types of groups in Compose:
  *
- * 1. Replaceable Groups
+ * 1. Replace Groups
  * 2. Movable Groups
  * 3. Restart Groups
  *
@@ -322,7 +322,7 @@
  *
  * 1. If a block executes exactly 1 time always, no groups are needed
  * 2. If a set of blocks are such that exactly one of them is executed exactly once (for example,
- * the result blocks of a when clause), then we insert a replaceable group around each block.
+ * the result blocks of a when clause), then we insert a replace group around each block.
  * 3. A movable group is only needed if the immediate composable call in the group has a Pivotal
  * property.
  *
@@ -788,12 +788,12 @@
 
     // At a high level, without useNonSkippingGroupOptimization, a non-restartable composable
     // function
-    // 1. gets a replaceable group placed around the body
+    // 1. gets a replace group placed around the body
     // 2. never calls `$composer.changed(...)` with its parameters
     // 3. can have default parameters, so needs to add the defaults preamble if defaults present
     // 4. proper groups around control flow structures in the body
     // If supported by the runtime and useNonSkippingGroupOptimization is enabled then the
-    // replaceable group is not necessary so the above list is changed to,
+    // replace group is not necessary so the above list is changed to,
     // 1. never calls `$composer.changed(...)` with its parameters
     // 2. can have default parameters, so needs to add the defaults preamble if defaults present
     // 3. never elides groups around control flow structures in the body
@@ -860,7 +860,7 @@
             scope.realizeGroup {
                 irComposite(statements = listOfNotNull(
                     if (emitTraceMarkers) irTraceEventEnd() else null,
-                    irEndReplaceableGroup(scope = scope)
+                    irEndReplaceGroup(scope = scope)
                 ))
             }
         } else if (useNonSkippingGroupOptimization) {
@@ -874,7 +874,7 @@
             listOfNotNull(
                 when {
                     outerGroupRequired ->
-                        irStartReplaceableGroup(
+                        irStartReplaceGroup(
                             body,
                             scope,
                             irFunctionSourceKey()
@@ -891,7 +891,7 @@
                 *bodyPreamble.statements.toTypedArray(),
                 *transformed.statements.toTypedArray(),
                 when {
-                    outerGroupRequired -> irEndReplaceableGroup(scope = scope)
+                    outerGroupRequired -> irEndReplaceGroup(scope = scope)
                     collectSourceInformation && !hasExplicitGroups ->
                         irSourceInformationMarkerEnd(body, scope)
                     else -> null
@@ -2020,7 +2020,7 @@
             functionSourceKey()
         )
 
-    private fun irStartReplaceableGroup(
+    private fun irStartReplaceGroup(
         element: IrElement,
         scope: Scope.BlockScope,
         key: IrExpression = element.irSourceKey(),
@@ -2028,7 +2028,7 @@
         endOffset: Int = UNDEFINED_OFFSET
     ): IrExpression {
         return irWithSourceInformation(
-            irStartReplaceableGroup(
+            irStartReplaceGroup(
                 scope.irCurrentComposer(startOffset, endOffset),
                 key,
                 startOffset,
@@ -2199,12 +2199,12 @@
         )
     }
 
-    private fun irEndReplaceableGroup(
+    private fun irEndReplaceGroup(
         startOffset: Int = UNDEFINED_OFFSET,
         endOffset: Int = UNDEFINED_OFFSET,
         scope: Scope.BlockScope
     ): IrExpression {
-        return irEndReplaceableGroup(
+        return irEndReplaceGroup(
             scope.irCurrentComposer(startOffset, endOffset),
             startOffset,
             endOffset
@@ -2299,13 +2299,13 @@
         }
     }
 
-    private fun IrBlock.withReplaceableGroupStatements(
+    private fun IrBlock.withReplaceGroupStatements(
         scope: Scope.BlockScope,
         insertAt: Int = 0
     ): IrExpression {
         currentFunctionScope.metrics.recordGroup()
         scope.realizeGroup {
-            irEndReplaceableGroup(scope = scope)
+            irEndReplaceGroup(scope = scope)
         }
 
         val prefix = statements.subList(0, insertAt)
@@ -2319,7 +2319,7 @@
                 endOffset,
                 type,
                 origin,
-                prefix + listOf(irStartReplaceableGroup(this, scope)) + suffix
+                prefix + listOf(irStartReplaceGroup(this, scope)) + suffix
             )
             // otherwise, we want to push an end call for any early returns/jumps, but also add
             // an end call to the end of the group
@@ -2329,18 +2329,18 @@
                 type,
                 origin,
                 prefix + listOf(
-                    irStartReplaceableGroup(
+                    irStartReplaceGroup(
                         this,
                         scope,
                         startOffset = startOffset,
                         endOffset = endOffset
                     )
-                ) + suffix + listOf(irEndReplaceableGroup(startOffset, endOffset, scope))
+                ) + suffix + listOf(irEndReplaceGroup(startOffset, endOffset, scope))
             )
         }
     }
 
-    private fun IrExpression.asReplaceableGroup(scope: Scope.BlockScope): IrExpression {
+    private fun IrExpression.asReplaceGroup(scope: Scope.BlockScope): IrExpression {
         currentFunctionScope.metrics.recordGroup()
         // if the scope has no composable calls, then the only important thing is that a
         // start/end call gets executed. as a result, we can just put them both at the top of
@@ -2349,39 +2349,39 @@
         if (!scope.hasComposableCalls && !scope.hasReturn && !scope.hasJump) {
             return wrap(
                 before = listOf(
-                    irStartReplaceableGroup(
+                    irStartReplaceGroup(
                         this,
                         scope,
                         startOffset = startOffset,
                         endOffset = endOffset,
                     ),
-                    irEndReplaceableGroup(startOffset, endOffset, scope)
+                    irEndReplaceGroup(startOffset, endOffset, scope)
                 )
             )
         }
         scope.realizeGroup {
-            irEndReplaceableGroup(scope = scope)
+            irEndReplaceGroup(scope = scope)
         }
         return when {
             // if the scope ends with a return call, then it will get properly ended if we
             // just push the end call on the scope because of the way returns get transformed in
             // this class. As a result, here we can safely just "prepend" the start call
             endsWithReturnOrJump() -> {
-                wrap(before = listOf(irStartReplaceableGroup(this, scope)))
+                wrap(before = listOf(irStartReplaceGroup(this, scope)))
             }
             // otherwise, we want to push an end call for any early returns/jumps, but also add
             // an end call to the end of the group
             else -> {
                 wrap(
                     before = listOf(
-                        irStartReplaceableGroup(
+                        irStartReplaceGroup(
                             this,
                             scope,
                             startOffset = startOffset,
                             endOffset = endOffset
                         )
                     ),
-                    after = listOf(irEndReplaceableGroup(startOffset, endOffset, scope))
+                    after = listOf(irEndReplaceGroup(startOffset, endOffset, scope))
                 )
             }
         }
@@ -2427,12 +2427,12 @@
             realizeGroup = {
                 if (before.statements.isEmpty()) {
                     metrics.recordGroup()
-                    before.statements.add(irStartReplaceableGroup(this, scope))
-                    after.statements.add(irEndReplaceableGroup(scope = scope))
+                    before.statements.add(irStartReplaceGroup(this, scope))
+                    after.statements.add(irEndReplaceGroup(scope = scope))
                 }
             },
             makeEnd = {
-                irEndReplaceableGroup(scope = scope)
+                irEndReplaceGroup(scope = scope)
             }
         )
         return wrap(
@@ -2456,7 +2456,7 @@
         // the group, and we don't have to deal with any of the complicated jump logic that
         // could be inside of the block
         val makeStart = {
-            if (scope.hasInlineEarlyReturn) irStartReplaceableGroup(
+            if (scope.hasInlineEarlyReturn) irStartReplaceGroup(
                 this,
                 scope,
                 startOffset = startOffset,
@@ -2465,7 +2465,7 @@
             else irSourceInformationMarkerStart(this, scope)
         }
         val makeEnd = {
-            if (scope.hasInlineEarlyReturn) irEndReplaceableGroup(scope = scope)
+            if (scope.hasInlineEarlyReturn) irEndReplaceGroup(scope = scope)
             else irSourceInformationMarkerEnd(this, scope)
         }
         if (!scope.hasComposableCalls && !scope.hasReturn && !scope.hasJump) {
@@ -3237,9 +3237,9 @@
             else
                 cacheCall.wrap(
                     before = inputVals.filterNotNull() + listOf(
-                        irStartReplaceableGroup(expression, blockScope)
+                        irStartReplaceGroup(expression, blockScope)
                     ),
-                    after = listOf(irEndReplaceableGroup(scope = blockScope))
+                    after = listOf(irEndReplaceGroup(scope = blockScope))
                 )
         }.also { block ->
             if (
@@ -3701,7 +3701,7 @@
         withScope(loopScope) {
             loop.condition = loop.condition.transform(this, null)
             if (loopScope.needsGroupPerIteration && loopScope.hasComposableCalls) {
-                loop.condition = loop.condition.asReplaceableGroup(loopScope)
+                loop.condition = loop.condition.asReplaceGroup(loopScope)
             }
 
             loop.body = loop.body?.transform(this, null)
@@ -3725,12 +3725,12 @@
                     val forLoopVariableIndex = current.statements.indexOfFirst {
                         (it as? IrVariable)?.origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE
                     }
-                    loop.body = current.withReplaceableGroupStatements(
+                    loop.body = current.withReplaceGroupStatements(
                         loopScope,
                         insertAt = forLoopVariableIndex + 1
                     )
                 } else {
-                    loop.body = current?.asReplaceableGroup(loopScope)
+                    loop.body = current?.asReplaceGroup(loopScope)
                 }
             }
         }
@@ -3759,7 +3759,7 @@
         // result branches of the when clause. This is because if we have N branches of a when
         // clause, we will always execute exactly 1 result branch, but we will execute 0-N of the
         // conditions. This means that if only the results have composable calls, we can use
-        // replaceable groups to represent the entire expression. If a condition has a composable
+        // replace groups to represent the entire expression. If a condition has a composable
         // call in it, we need to place the whole expression in a Container group, since a variable
         // number of them will be created. The exception here is the first branch's condition,
         // since it will *always* be executed. As a result, if only the first conditional has a
@@ -3863,7 +3863,7 @@
             // to generate a group around it because we will be generating one around the entire
             // if statement
             if (needsWrappingGroup && condScope.hasComposableCalls) {
-                it.condition = it.condition.asReplaceableGroup(condScope)
+                it.condition = it.condition.asReplaceGroup(condScope)
             }
             if (
                 // if no wrapping group but more than one result have calls, we have to have every
@@ -3873,7 +3873,7 @@
                 // the block has composable calls
                 (needsWrappingGroup && resultScope.hasComposableCalls)
             ) {
-                it.result = it.result.asReplaceableGroup(resultScope)
+                it.result = it.result.asReplaceGroup(resultScope)
             }
 
             if (resultsWithCalls == 1 && resultScope.hasComposableCalls) {
@@ -4396,21 +4396,6 @@
                     else -> super.sourceLocationOf(call)
                 }
         }
-
-        class ComposableLambdaScope : BlockScope("composableLambda") {
-            override fun calculateHasSourceInformation(sourceInformationEnabled: Boolean): Boolean {
-                return sourceInformationEnabled
-            }
-
-            override fun calculateSourceInfo(sourceInformationEnabled: Boolean): String? =
-                if (sourceInformationEnabled) {
-                    "C${
-                    super.calculateSourceInfo(sourceInformationEnabled) ?: ""
-                    }:${functionScope?.function?.sourceFileInformation() ?: ""}"
-                } else {
-                    null
-                }
-        }
     }
 
     inner class IrDefaultBitMaskValueImpl(
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 1ecc29f..ef24c14 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -1035,9 +1035,9 @@
             val cacheTmpVar = irTemporary(cache, "tmpCache")
             cacheTmpVar.wrap(
                 type = expression.type,
-                before = listOf(irStartReplaceableGroup(irCurrentComposer(), irConst(key))),
+                before = listOf(irStartReplaceGroup(irCurrentComposer(), irConst(key))),
                 after = listOf(
-                    irEndReplaceableGroup(irCurrentComposer()),
+                    irEndReplaceGroup(irCurrentComposer()),
                     irGet(cacheTmpVar)
                 )
             )
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index ca66cb3..4009002 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -111,16 +111,102 @@
     method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class ContextualFlowColumnOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.ContextualFlowColumnOverflow.Companion Companion;
+  }
+
+  public static final class ContextualFlowColumnOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.ContextualFlowColumnOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> collapseIndicator, optional int minColumnsToShowCollapse, optional float minWidthToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowColumnOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowColumnOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnOverflowScope extends androidx.compose.foundation.layout.FlowColumnOverflowScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnScope extends androidx.compose.foundation.layout.FlowColumnScope {
+  }
+
+  public final class ContextualFlowLayoutKt {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void ContextualFlowColumn(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, optional int maxLines, optional androidx.compose.foundation.layout.ContextualFlowColumnOverflow overflow, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ContextualFlowColumnScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void ContextualFlowRow(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, optional int maxLines, optional androidx.compose.foundation.layout.ContextualFlowRowOverflow overflow, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ContextualFlowRowScope,? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class ContextualFlowRowOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion Companion;
+  }
+
+  public static final class ContextualFlowRowOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.ContextualFlowRowOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> collapseIndicator, optional int minRowsToShowCollapse, optional float minHeightToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowRowOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowRowOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowOverflowScope extends androidx.compose.foundation.layout.FlowRowOverflowScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowScope extends androidx.compose.foundation.layout.FlowRowScope {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi {
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class FlowColumnOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.FlowColumnOverflow.Companion Companion;
+  }
+
+  public static final class FlowColumnOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.FlowColumnOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> collapseIndicator, optional int minColumnsToShowCollapse, optional float minWidthToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowColumnOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowColumnOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnOverflowScope extends androidx.compose.foundation.layout.FlowColumnScope {
+    method public int getShownItemCount();
+    method public int getTotalItemCount();
+    property public abstract int shownItemCount;
+    property public abstract int totalItemCount;
+  }
+
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnScope extends androidx.compose.foundation.layout.ColumnScope {
     method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxColumnWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class FlowLayoutKt {
-    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static inline void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static inline void FlowRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, optional int maxLines, optional androidx.compose.foundation.layout.FlowColumnOverflow overflow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, optional int maxLines, optional androidx.compose.foundation.layout.FlowRowOverflow overflow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowScope,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public abstract sealed class FlowLayoutOverflow {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class FlowRowOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.FlowRowOverflow.Companion Companion;
+  }
+
+  public static final class FlowRowOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.FlowRowOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> collapseIndicator, optional int minRowsToShowCollapse, optional float minHeightToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowRowOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowRowOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowOverflowScope extends androidx.compose.foundation.layout.FlowRowScope {
+    method public int getShownItemCount();
+    method public int getTotalItemCount();
+    property public abstract int shownItemCount;
+    property public abstract int totalItemCount;
   }
 
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowScope extends androidx.compose.foundation.layout.RowScope {
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index a39ff50..672b14f 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -114,20 +114,106 @@
     method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class ContextualFlowColumnOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.ContextualFlowColumnOverflow.Companion Companion;
+  }
+
+  public static final class ContextualFlowColumnOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.ContextualFlowColumnOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope,kotlin.Unit> collapseIndicator, optional int minColumnsToShowCollapse, optional float minWidthToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowColumnOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowColumnOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowColumnOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnOverflowScope extends androidx.compose.foundation.layout.FlowColumnOverflowScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnScope extends androidx.compose.foundation.layout.FlowColumnScope {
+  }
+
+  public final class ContextualFlowLayoutKt {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void ContextualFlowColumn(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, optional int maxLines, optional androidx.compose.foundation.layout.ContextualFlowColumnOverflow overflow, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ContextualFlowColumnScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void ContextualFlowRow(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, optional int maxLines, optional androidx.compose.foundation.layout.ContextualFlowRowOverflow overflow, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ContextualFlowRowScope,? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class ContextualFlowRowOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion Companion;
+  }
+
+  public static final class ContextualFlowRowOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.ContextualFlowRowOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ContextualFlowRowOverflowScope,kotlin.Unit> collapseIndicator, optional int minRowsToShowCollapse, optional float minHeightToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.ContextualFlowRowOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowRowOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.ContextualFlowRowOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowOverflowScope extends androidx.compose.foundation.layout.FlowRowOverflowScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowScope extends androidx.compose.foundation.layout.FlowRowScope {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi {
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class FlowColumnOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.FlowColumnOverflow.Companion Companion;
+  }
+
+  public static final class FlowColumnOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.FlowColumnOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnOverflowScope,kotlin.Unit> collapseIndicator, optional int minColumnsToShowCollapse, optional float minWidthToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowColumnOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowColumnOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowColumnOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnOverflowScope extends androidx.compose.foundation.layout.FlowColumnScope {
+    method public int getShownItemCount();
+    method public int getTotalItemCount();
+    property public abstract int shownItemCount;
+    property public abstract int totalItemCount;
+  }
+
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnScope extends androidx.compose.foundation.layout.ColumnScope {
     method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxColumnWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class FlowLayoutKt {
-    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static inline void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static inline void FlowRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional int maxItemsInEachColumn, optional int maxLines, optional androidx.compose.foundation.layout.FlowColumnOverflow overflow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional int maxItemsInEachRow, optional int maxLines, optional androidx.compose.foundation.layout.FlowRowOverflow overflow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy columnMeasurementHelper(androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, int maxItemsInMainAxis);
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy rowMeasurementHelper(androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, int maxItemsInMainAxis);
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public abstract sealed class FlowLayoutOverflow {
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final class FlowRowOverflow extends androidx.compose.foundation.layout.FlowLayoutOverflow {
+    field public static final androidx.compose.foundation.layout.FlowRowOverflow.Companion Companion;
+  }
+
+  public static final class FlowRowOverflow.Companion {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow expandIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.FlowRowOverflow expandOrCollapseIndicator(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> expandIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.FlowRowOverflowScope,kotlin.Unit> collapseIndicator, optional int minRowsToShowCollapse, optional float minHeightToShowCollapse);
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow getClip();
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.foundation.layout.FlowRowOverflow getVisible();
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowRowOverflow Clip;
+    property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public final androidx.compose.foundation.layout.FlowRowOverflow Visible;
+  }
+
+  @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowOverflowScope extends androidx.compose.foundation.layout.FlowRowScope {
+    method public int getShownItemCount();
+    method public int getTotalItemCount();
+    property public abstract int shownItemCount;
+    property public abstract int totalItemCount;
+  }
+
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowScope extends androidx.compose.foundation.layout.RowScope {
     method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxRowHeight(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 062412b..14a7610 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -41,9 +41,11 @@
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
-                api(project(":compose:ui:ui"))
-                implementation(project(":compose:runtime:runtime"))
+                api("androidx.compose.ui:ui:1.6.0")
+                implementation("androidx.compose.runtime:runtime:1.6.0")
                 implementation(project(":compose:ui:ui-util"))
+                implementation("androidx.collection:collection:1.4.0")
+                implementation(project(":compose:ui:ui-unit"))
             }
         }
 
@@ -58,7 +60,6 @@
             }
         }
 
-
         androidMain {
             dependsOn(jvmMain)
             dependencies {
@@ -73,9 +74,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation(libs.kotlinStdlib)
-
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt
new file mode 100644
index 0000000..07401d3
--- /dev/null
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.compose.foundation.layout.demos
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+fun ContextualFlowColumnDemo() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        ContextualFlowColMaxLineDynamicSeeMore()
+    }
+}
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt
new file mode 100644
index 0000000..be0237b
--- /dev/null
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.compose.foundation.layout.demos
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+fun ContextualFlowRowDemo() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        ContextualFlowRowMaxLineDynamicSeeMore()
+    }
+}
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
index 104d27c..b96504f 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
@@ -25,6 +25,8 @@
         ComposableDemo("Row and column") { SimpleLayoutDemo() },
         ComposableDemo("Flow Column") { SimpleFlowColumnDemo() },
         ComposableDemo("Flow Row") { SimpleFlowRowDemo() },
+        ComposableDemo("Contextual Flow Row") { ContextualFlowRowDemo() },
+        ComposableDemo("Contextual FlowColumn") { ContextualFlowColumnDemo() },
         ComposableDemo("Rtl support") { RtlDemo() }
     )
 )
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
index eb0867f..ec497a3 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
@@ -17,21 +17,23 @@
 package androidx.compose.foundation.layout.demos
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.samples.SimpleFlowColumn
-import androidx.compose.foundation.layout.samples.SimpleFlowColumnWithWeights
+import androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesDynamicSeeMore
+import androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesWithSeeMore
+import androidx.compose.foundation.layout.samples.SimpleFlowColumnWithMaxWidth
 import androidx.compose.foundation.layout.samples.SimpleFlowColumn_EqualWidth
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
-@OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun SimpleFlowColumnDemo() {
     Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
         SimpleFlowColumn()
-        SimpleFlowColumnWithWeights()
+        SimpleFlowColumnMaxLinesWithSeeMore()
+        SimpleFlowColumnMaxLinesDynamicSeeMore()
+        SimpleFlowColumnWithMaxWidth()
         SimpleFlowColumn_EqualWidth()
     }
 }
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
index abc3b38..1940de2 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
@@ -17,21 +17,23 @@
 package androidx.compose.foundation.layout.demos
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.samples.SimpleFlowRow
-import androidx.compose.foundation.layout.samples.SimpleFlowRowWithWeights
+import androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesDynamicSeeMore
+import androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesWithSeeMore
+import androidx.compose.foundation.layout.samples.SimpleFlowRowWithMaxHeight
 import androidx.compose.foundation.layout.samples.SimpleFlowRow_EqualHeight
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
-@OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun SimpleFlowRowDemo() {
     Column(Modifier.verticalScroll(rememberScrollState())) {
         SimpleFlowRow()
-        SimpleFlowRowWithWeights()
+        SimpleFlowRowMaxLinesWithSeeMore()
+        SimpleFlowRowMaxLinesDynamicSeeMore()
+        SimpleFlowRowWithMaxHeight()
         SimpleFlowRow_EqualHeight()
     }
 }
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt
new file mode 100644
index 0000000..3d6bdeb
--- /dev/null
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 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.compose.foundation.layout.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ContextualFlowColumn
+import androidx.compose.foundation.layout.ContextualFlowColumnOverflow
+import androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun ContextualFlowColMaxLineDynamicSeeMore() {
+    val totalCount = 300
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(
+        modifier = Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp),
+        text = "ContextualFlowColumn (based on Subcompose)" +
+            " is great for Large Items & +N dynamic labels",
+        fontWeight = FontWeight.Bold
+    )
+
+    val moreOrCollapseIndicator = @Composable { scope: ContextualFlowColumnOverflowScope ->
+        val remainingItems = totalCount - scope.shownItemCount
+        DynamicSeeMore(
+            isHorizontal = true,
+            remainingItems = remainingItems
+        ) {
+            if (remainingItems == 0) {
+                maxLines = 2
+            } else {
+                maxLines += 2
+            }
+        }
+    }
+    ContextualFlowColumn(
+        modifier = Modifier
+            .fillMaxWidth(1f)
+            .horizontalScroll(rememberScrollState())
+            .padding(20.dp)
+            .height(200.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        verticalArrangement = Arrangement.spacedBy(10.dp),
+        horizontalArrangement = Arrangement.spacedBy(20.dp),
+        maxLines = maxLines,
+        overflow = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+            minColumnsToShowCollapse = 4,
+            expandIndicator = moreOrCollapseIndicator,
+            collapseIndicator = moreOrCollapseIndicator
+        ),
+        itemCount = totalCount
+    ) { index ->
+        Box(
+            modifier = Modifier
+                .align(Alignment.CenterHorizontally)
+                .height(50.dp)
+                .width(50.dp)
+                .background(Color.Green)
+        ) {
+            Text(text = index.toString(), fontSize = 18.sp, modifier =
+            Modifier
+                .padding(3.dp)
+                .align(Alignment.Center))
+        }
+    }
+}
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt
new file mode 100644
index 0000000..0410a1f
--- /dev/null
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 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.compose.foundation.layout.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ContextualFlowRow
+import androidx.compose.foundation.layout.ContextualFlowRowOverflow
+import androidx.compose.foundation.layout.ContextualFlowRowOverflowScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun ContextualFlowRowMaxLineDynamicSeeMore() {
+    val totalCount = 300
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "ContextualFlowRow (based on Subcompose)" +
+            " is great for Large Items & +N dynamic labels",
+        fontWeight = FontWeight.Bold
+    )
+    val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
+        val remainingItems = totalCount - scope.shownItemCount
+        DynamicSeeMore(
+            isHorizontal = true,
+            remainingItems = remainingItems
+        ) {
+            if (remainingItems == 0) {
+                maxLines = 2
+            } else {
+                maxLines += 5
+            }
+        }
+    }
+    ContextualFlowRow(
+        modifier = Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        horizontalArrangement = Arrangement.spacedBy(10.dp),
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        maxLines = maxLines,
+        overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+            minRowsToShowCollapse = 4,
+            expandIndicator = moreOrCollapseIndicator,
+            collapseIndicator = moreOrCollapseIndicator
+        ),
+        itemCount = totalCount
+    ) {
+        Box(
+            Modifier
+                .align(Alignment.CenterVertically)
+                .width(50.dp)
+                .height(50.dp)
+                .background(Color.Green)
+        ) {
+            Text(text = it.toString(), fontSize = 18.sp, modifier =
+            Modifier
+                .padding(3.dp)
+                .align(Alignment.Center))
+        }
+    }
+}
+
+@Composable
+internal fun DynamicSeeMore(
+    isHorizontal: Boolean,
+    remainingItems: Int,
+    onClick: () -> Unit
+) {
+    Box(
+        Modifier
+            .clickable(onClick = onClick)
+            .wrapContentWidth()
+            .height(50.dp)
+            .background(Color.Green)
+    ) {
+        val collapseText = if (isHorizontal) "^" else "<"
+        Text(
+            modifier = Modifier
+                .align(Alignment.Center)
+                .padding(3.dp),
+            text = if (remainingItems == 0) collapseText else "+$remainingItems",
+            fontSize = 18.sp
+        )
+    }
+}
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
index cce4c34..a0308b7 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
@@ -20,22 +20,30 @@
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.horizontalScroll
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowColumn
+import androidx.compose.foundation.layout.FlowColumnOverflow
+import androidx.compose.foundation.layout.FlowColumnOverflowScope
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
@@ -43,24 +51,35 @@
 @Sampled
 @Composable
 fun SimpleFlowColumn() {
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "FlowColumn with weights",
+        fontWeight = FontWeight.Bold
+    )
+
     FlowColumn(
         Modifier
             .padding(20.dp)
             .fillMaxWidth()
+            .padding(20.dp)
             .wrapContentHeight(align = Alignment.Top)
-            .requiredHeight(200.dp)
+            .height(200.dp)
             .border(BorderStroke(2.dp, Color.Gray)),
         horizontalArrangement = Arrangement.spacedBy(10.dp),
-        verticalArrangement = Arrangement.Center,
-        maxItemsInEachColumn = 3
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        maxItemsInEachColumn = 3,
     ) {
-        repeat(10) { index ->
+
+        repeat(17) { index ->
             Box(
                 Modifier
                     .align(Alignment.CenterHorizontally)
-                    .padding(top = 10.dp)
                     .width(50.dp)
                     .height(50.dp)
+                    .weight(1f, true)
                     .background(color = Color.Green)
             ) {
                 Text(text = index.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
@@ -72,28 +91,157 @@
 @OptIn(ExperimentalLayoutApi::class)
 @Sampled
 @Composable
-fun SimpleFlowColumnWithWeights() {
+fun SimpleFlowColumnMaxLinesWithSeeMore() {
+    val totalCount = 20
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "Flow Column with Max Lines and See More",
+        fontWeight = FontWeight.Bold
+    )
+
     FlowColumn(
-        Modifier
+        modifier = Modifier
+            .height(200.dp)
+            .horizontalScroll(rememberScrollState())
             .padding(20.dp)
-            .fillMaxWidth()
-            .wrapContentHeight(align = Alignment.Top)
-            .requiredHeight(200.dp)
-            .border(BorderStroke(2.dp, Color.Gray)),
-        horizontalArrangement = Arrangement.spacedBy(10.dp),
-        verticalArrangement = Arrangement.Center,
-        maxItemsInEachColumn = 3
+            .wrapContentWidth(align = Alignment.Start),
+        verticalArrangement = Arrangement.spacedBy(10.dp),
+        horizontalArrangement = Arrangement.spacedBy(20.dp),
+        maxLines = maxLines,
+        overflow = FlowColumnOverflow.expandIndicator {
+            Ellipsis(text = "...") {
+                maxLines += 2
+            }
+        }
     ) {
-        repeat(10) { index ->
+        repeat(totalCount) {
             Box(
-                Modifier
+                modifier = Modifier
                     .align(Alignment.CenterHorizontally)
-                    .padding(top = 10.dp)
-                    .width(50.dp)
                     .height(50.dp)
-                    .weight(if (index % 2 == 0) 1f else 2f, fill = true)
-                    .background(color = Color.Green)
-            )
+                    .width(50.dp)
+                    .background(Color.Green)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier =
+                Modifier
+                    .padding(3.dp)
+                    .align(Alignment.Center))
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun SimpleFlowColumnWithMaxWidth() {
+    var initialWidth = 200.dp // Reversed from initialHeight
+    var width by remember { mutableStateOf(initialWidth) } // Reversed from height
+
+    Text(
+        modifier = Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        text = "FlowColumn with MaxWidth and See More or collapse",
+        fontWeight = FontWeight.Bold
+    )
+
+    FlowColumn(
+        modifier = Modifier
+            .height(200.dp)
+            .padding(20.dp)
+            .horizontalScroll(rememberScrollState())
+            .width(width)
+            .wrapContentWidth(align = Alignment.Start),
+        verticalArrangement = Arrangement.spacedBy(10.dp),
+        horizontalArrangement = Arrangement.spacedBy(20.dp),
+        overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+            minWidthToShowCollapse = 200.dp,
+            expandIndicator = {
+                Ellipsis(text = "...") {
+                    width += 200.dp
+                }
+            },
+            collapseIndicator = {
+                Ellipsis(text = "<") {
+                    width = 100.dp
+                }
+            })
+    ) {
+        repeat(40) {
+            Box(
+                modifier = Modifier
+                    .align(Alignment.CenterHorizontally)
+                    .height(50.dp)
+                    .width(50.dp)
+                    .background(Color.Green)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun SimpleFlowColumnMaxLinesDynamicSeeMore() {
+    val totalCount = 20
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(
+        modifier = Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        text = "FlowColumn with MaxLines and +N button",
+        fontWeight = FontWeight.Bold
+    )
+    val moreOrCollapseIndicator = @Composable { scope: FlowColumnOverflowScope ->
+        DynamicSeeMoreForDrawText(
+            isHorizontal = false,
+            totalCount = totalCount,
+            { scope.shownItemCount },
+            onExpand = { maxLines += 2 },
+            onShrink = { maxLines = 2 }
+        )
+    }
+    FlowColumn(
+        modifier = Modifier
+            .height(200.dp)
+            .padding(20.dp)
+            .horizontalScroll(rememberScrollState())
+            .wrapContentWidth(align = Alignment.Start),
+        verticalArrangement = Arrangement.spacedBy(10.dp),
+        horizontalArrangement = Arrangement.spacedBy(20.dp),
+        maxLines = maxLines,
+        overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+            minColumnsToShowCollapse = 4,
+            expandIndicator = moreOrCollapseIndicator,
+            collapseIndicator = moreOrCollapseIndicator
+        )
+    ) {
+        repeat(totalCount) {
+            Box(
+                modifier = Modifier
+                    .align(Alignment.CenterHorizontally)
+                    .height(50.dp)
+                    .width(50.dp)
+                    .background(Color.Green)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier =
+                Modifier
+                    .padding(3.dp)
+                    .align(Alignment.Center))
+            }
         }
     }
 }
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
index 3529ff8..0b510c8 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
@@ -17,11 +17,16 @@
 package androidx.compose.foundation.layout.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.FlowRowOverflow
+import androidx.compose.foundation.layout.FlowRowOverflowScope
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
@@ -29,9 +34,21 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.drawText
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import kotlin.random.Random
@@ -40,6 +57,14 @@
 @Sampled
 @Composable
 fun SimpleFlowRow() {
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "Flow Row with weights",
+        fontWeight = FontWeight.Bold
+    )
+
     FlowRow(
         Modifier
             .fillMaxWidth(1f)
@@ -47,9 +72,110 @@
             .wrapContentHeight(align = Alignment.Top),
         horizontalArrangement = Arrangement.spacedBy(10.dp),
         verticalArrangement = Arrangement.spacedBy(20.dp),
-        maxItemsInEachRow = 3
+        maxItemsInEachRow = 3,
     ) {
-        repeat(10) {
+        repeat(20) {
+            Box(
+                Modifier
+                    .align(Alignment.CenterVertically)
+                    .width(50.dp)
+                    .height(50.dp)
+                    .weight(1f, true)
+                    .background(Color.Green)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun SimpleFlowRowMaxLinesWithSeeMore() {
+    val totalCount = 20
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "Flow Row with total items of 40, " +
+            "with Max Lines of 2 and See More, " +
+            "which when clicked, increases max lines by two",
+        fontWeight = FontWeight.Bold
+    )
+    FlowRow(
+        Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        horizontalArrangement = Arrangement.spacedBy(10.dp),
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        maxLines = maxLines,
+        overflow = FlowRowOverflow.expandIndicator {
+            Ellipsis(text = "...") {
+                maxLines += 2
+            }
+        }
+    ) {
+        repeat(totalCount) {
+            Box(
+                Modifier
+                    .align(Alignment.CenterVertically)
+                    .width(50.dp)
+                    .height(50.dp)
+                    .background(Color.Green)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier =
+                Modifier
+                    .padding(3.dp)
+                    .align(Alignment.Center))
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun SimpleFlowRowWithMaxHeight() {
+    var initialHeight = 200.dp
+    var height by remember { mutableStateOf(initialHeight) }
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "Flow Row with total items of 40, " +
+            "with Max height of 200.dp and See More/collapse button, " +
+            "which when clicked, increases height by 200.dp",
+        fontWeight = FontWeight.Bold
+    )
+
+    FlowRow(
+        Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .height(height)
+            .wrapContentHeight(align = Alignment.Top),
+        horizontalArrangement = Arrangement.spacedBy(10.dp),
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        overflow = FlowRowOverflow.expandOrCollapseIndicator(
+            minHeightToShowCollapse = 200.dp,
+            expandIndicator = {
+                Ellipsis(text = "...") {
+                    height += 200.dp
+                }
+            },
+            collapseIndicator = {
+                Ellipsis(text = "^") {
+                    height = 100.dp
+                }
+            })
+    ) {
+        repeat(40) {
             Box(
                 Modifier
                     .align(Alignment.CenterVertically)
@@ -66,29 +192,150 @@
 @OptIn(ExperimentalLayoutApi::class)
 @Sampled
 @Composable
-fun SimpleFlowRowWithWeights() {
+fun SimpleFlowRowMaxLinesDynamicSeeMore() {
+    val totalCount = 40
+    var maxLines by remember {
+        mutableStateOf(2)
+    }
+
+    Text(modifier = Modifier
+        .fillMaxWidth(1f)
+        .padding(20.dp)
+        .wrapContentHeight(align = Alignment.Top),
+        text = "Flow Row with total items of 40, " +
+            "with Max Lines of 2 and See More/collapse button, " +
+            "which when clicked, increases max lines by two",
+        fontWeight = FontWeight.Bold
+    )
+    val moreOrLessIndicator = @Composable { scope: FlowRowOverflowScope ->
+        DynamicSeeMoreForDrawText(
+            isHorizontal = true,
+            totalCount = totalCount,
+            { scope.shownItemCount },
+            onExpand = { maxLines += 2 },
+            onShrink = { maxLines = 2 }
+        )
+    }
     FlowRow(
         Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
             .wrapContentHeight()
             .padding(20.dp)
             .fillMaxWidth(1f),
         horizontalArrangement = Arrangement.spacedBy(10.dp),
         verticalArrangement = Arrangement.spacedBy(20.dp),
-        maxItemsInEachRow = 3
+        maxLines = maxLines,
+        overflow = FlowRowOverflow.expandOrCollapseIndicator(
+            minRowsToShowCollapse = 4,
+            expandIndicator = moreOrLessIndicator,
+            collapseIndicator = moreOrLessIndicator
+        )
     ) {
-        repeat(6) { index ->
+        repeat(totalCount) {
             Box(
                 Modifier
                     .align(Alignment.CenterVertically)
                     .width(50.dp)
                     .height(50.dp)
                     .background(Color.Green)
-                    .weight(if (index % 2 == 0) 1f else 2f, fill = true)
+            ) {
+                Text(text = it.toString(), fontSize = 18.sp, modifier =
+                Modifier
+                    .padding(3.dp)
+                    .align(Alignment.Center))
+            }
+        }
+    }
+}
+
+@Composable
+internal fun DynamicSeeMoreForDrawText(
+    isHorizontal: Boolean,
+    totalCount: Int,
+    noOfItemsShown: () -> Int?,
+    onExpand: () -> Unit,
+    onShrink: () -> Unit,
+) {
+    Box(
+        Modifier
+            .clickable(onClick = {
+                val remainingItems = noOfItemsShown()?.let { totalCount - it }
+                if (remainingItems == 0) {
+                    onShrink()
+                } else {
+                    onExpand()
+                }
+            }
+            )
+            .width(50.dp)
+            .height(50.dp)
+            .background(Color.Green)
+    ) {
+
+        val textMeasurer = rememberTextMeasurer()
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .layout { measurable, constraints ->
+                    // TextLayout can be done any time prior to its use in draw, including in a
+                    // background thread.
+                    // In this sample, text layout is measured in layout modifier. This way the layout
+                    // call can be restarted when async font loading completes due to the fact that
+                    // `.measure` call is executed in `.layout`.
+                    val result = textMeasurer.measure(
+                        text = "+2000",
+                        style = TextStyle(fontSize = 24.sp),
+                        constraints = constraints
+                    )
+                    val placeable = measurable.measure(
+                        Constraints
+                            (
+                            minWidth = result.size.width,
+                            maxWidth = result.size.width,
+                            minHeight = result.size.height,
+                            maxHeight = result.size.height
+                        )
+                    )
+                    layout(placeable.width, placeable.height) {
+                        placeable.placeRelative(0, 0)
+                    }
+                }) {
+            // This happens during draw phase.
+            val collapseText = if (isHorizontal) "^" else "<"
+            val remainingItems = noOfItemsShown()?.let { totalCount - it }
+            var textLayoutResult: TextLayoutResult = textMeasurer.measure(
+                text = if (remainingItems == 0) collapseText else "+$remainingItems",
+                style = TextStyle(fontSize = 18.sp)
+            )
+            drawText(textLayoutResult,
+                topLeft = Offset((size.width - textLayoutResult.size.width) / 2,
+                    (size.height - textLayoutResult.size.height) / 2,
+                )
             )
         }
     }
 }
 
+@Composable
+internal fun Ellipsis(text: String, onClick: () -> Unit) {
+    Box(
+        Modifier
+            .clickable(onClick = onClick)
+            .width(50.dp)
+            .height(50.dp)
+            .background(Color.Green)
+    ) {
+        Text(
+            modifier = Modifier
+                .align(Alignment.Center)
+                .padding(3.dp),
+            text = text,
+            fontSize = 18.sp
+        )
+    }
+}
+
 @OptIn(ExperimentalLayoutApi::class)
 @Sampled
 @Composable
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
new file mode 100644
index 0000000..6ff0af0
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
@@ -0,0 +1,3932 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.layout
+
+import androidx.compose.foundation.clickable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.random.Random
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalLayoutApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ContextualFlowRowColumnTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLine() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        itemCount = 6
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLine_MultipleContentPerIndex() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        itemCount = 3
+                    ) {
+                        repeat(2) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLine_NoContentPlusMultiplePerIndex() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        itemCount = 3
+                    ) { index ->
+                        if (index == 0) {
+                            repeat(5) {
+                                Box(Modifier.size(20.toDp()))
+                            }
+                        } else if (index == 1) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsToTheNextLine() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        itemCount = 6
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLine_withExactSpaceNeeded() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        itemCount = 10
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsToTheNextLine_withExactSpaceNeeded() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        itemCount = 10
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLineMultipleTimes() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        itemCount = 6
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_doesNotCrashOnEmpty() {
+        var itemShown = 0
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .fillMaxWidth(1f)
+                            .wrapContentHeight(),
+                        horizontalArrangement = Arrangement.spacedBy(20.toDp()),
+                        itemCount = 10
+                    ) {
+                        if (it in 2..5 || it == 9) {
+                        } else {
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        itemShown++
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemShown).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsToTheNextLineMultipleTimes() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowColumn(
+                        itemCount = 6,
+                        Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            }
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsWithMaxItems() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowRow(
+                        itemCount = 6,
+                        Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            }, maxItemsInEachRow = 2
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(60)
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsWithMaxItems() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowColumn(
+                        itemCount = 6,
+                        Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            }, maxItemsInEachColumn = 2
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(60)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsWithWeights() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowRow(
+                        itemCount = 6,
+                        Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            }, maxItemsInEachRow = 2
+                    ) {
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .weight(1f, true)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(60)
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsWithWeights() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    ContextualFlowColumn(
+                        itemCount = 6,
+                        Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            }, maxItemsInEachColumn = 2
+                    ) {
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .weight(1f, true)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(60)
+    }
+
+    @Test
+    fun testContextualFlowRow_staysInOneRow() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    ContextualFlowRow(
+                        itemCount = 2,
+                        Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            }
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(20)
+    }
+
+    @Test
+    fun testContextualFlowRow_equalHeight() {
+        val listOfHeights = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = 9,
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(10.dp),
+                    verticalArrangement = Arrangement.spacedBy(20.dp),
+                    maxItemsInEachRow = 3,
+                ) {
+                    Box(
+                        Modifier
+                            .onSizeChanged {
+                                listOfHeights.add(it.height)
+                            }
+                            .width(100.dp)
+                            .background(Color.Green)
+                            .fillMaxRowHeight()
+                    ) {
+                        val height = it * 20
+                        Box(modifier = Modifier.height(height.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights[0]).isEqualTo(listOfHeights[1])
+        Truth.assertThat(listOfHeights[1]).isEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo(listOfHeights[4])
+        Truth.assertThat(listOfHeights[4]).isEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo(listOfHeights[7])
+        Truth.assertThat(listOfHeights[7]).isEqualTo(listOfHeights[8])
+    }
+
+    @Test
+    fun testContextualFlowRow_equalHeight_worksWithWeight() {
+        val listOfHeights = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = 9,
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(10.dp),
+                    verticalArrangement = Arrangement.spacedBy(20.dp),
+                    maxItemsInEachRow = 3,
+                ) {
+                    Box(
+                        Modifier
+                            .onSizeChanged {
+                                listOfHeights.add(it.height)
+                            }
+                            .width(100.dp)
+                            .weight(1f, true)
+                            .background(Color.Green)
+                            .fillMaxRowHeight()
+                    ) {
+                        val height = Random.Default.nextInt(1, 200) - it
+                        Box(modifier = Modifier.height(height.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights[0]).isEqualTo(listOfHeights[1])
+        Truth.assertThat(listOfHeights[1]).isEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo(listOfHeights[4])
+        Truth.assertThat(listOfHeights[4]).isEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo(listOfHeights[7])
+        Truth.assertThat(listOfHeights[7]).isEqualTo(listOfHeights[8])
+    }
+
+    @Test
+    fun testContextualFlowRow_equalHeight_WithFraction() {
+        val listOfHeights = mutableMapOf<Int, Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                with(LocalDensity.current) {
+                    ContextualFlowRow(
+                        itemCount = 9,
+                        Modifier
+                            .fillMaxWidth(1f)
+                            .padding(20.dp)
+                            .wrapContentHeight(align = Alignment.Top, unbounded = true),
+                        horizontalArrangement = Arrangement.spacedBy(10.dp),
+                        verticalArrangement = Arrangement.spacedBy(20.dp),
+                        maxItemsInEachRow = 3,
+                    ) {
+                        Box(
+                            Modifier
+                                .onSizeChanged { item ->
+                                    listOfHeights[it] = item.height
+                                }
+                                .width(100.dp)
+                                .background(Color.Green)
+                                .run {
+                                    if (it == 0 || it == 3 || it == 6) {
+                                        fillMaxRowHeight(0.5f)
+                                    } else {
+                                        this
+                                    }
+                                }
+                        ) {
+                            val height = it * 400
+                            Box(modifier = Modifier.height(height.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8)
+        Truth.assertThat(listOfHeights[0]).isEqualTo((.5 * listOfHeights[2]!!).roundToInt())
+        Truth.assertThat(listOfHeights[1]).isNotEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isEqualTo(800)
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo((.5 * listOfHeights[5]!!).roundToInt())
+        Truth.assertThat(listOfHeights[4]).isNotEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isEqualTo(2000)
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo((.5 * listOfHeights[8]!!).roundToInt())
+        Truth.assertThat(listOfHeights[7]).isNotEqualTo(listOfHeights[8])
+        Truth.assertThat(listOfHeights[8]).isEqualTo(3200)
+    }
+
+    @Test
+    fun testContextualFlowColumn_equalWidth() {
+        val listOfWidths = mutableMapOf<Int, Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = 9,
+                    Modifier
+                        .wrapContentWidth(align = Alignment.Start)
+                        .padding(20.dp)
+                        .fillMaxHeight(1f),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) {
+                    Box(
+                        Modifier
+                            .onSizeChanged { item ->
+                                listOfWidths[it] = item.width
+                            }
+                            .height(100.dp)
+                            .background(Color.Green)
+                            .fillMaxColumnWidth()
+                    ) {
+                        val width = it * 20
+                        Box(modifier = Modifier.width(width.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8)
+        Truth.assertThat(listOfWidths[0]).isEqualTo(listOfWidths[1])
+        Truth.assertThat(listOfWidths[1]).isEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo(listOfWidths[4])
+        Truth.assertThat(listOfWidths[4]).isEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo(listOfWidths[7])
+        Truth.assertThat(listOfWidths[7]).isEqualTo(listOfWidths[8])
+    }
+
+    @Test
+    fun testContextualFlowColumn_equalWidth_worksWithWeight() {
+        val listOfWidths = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = 9,
+                    Modifier
+                        .wrapContentWidth(align = Alignment.Start)
+                        .fillMaxHeight(1f)
+                        .padding(20.dp),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) {
+                    Box(
+                        Modifier
+                            .onSizeChanged {
+                                listOfWidths.add(it.width)
+                            }
+                            .height(100.dp)
+                            .weight(1f, true)
+                            .background(Color.Green)
+                            .fillMaxColumnWidth()
+                    ) {
+                        val width = it * 20
+                        Box(modifier = Modifier.width(width.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths[0]).isEqualTo(listOfWidths[1])
+        Truth.assertThat(listOfWidths[1]).isEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo(listOfWidths[4])
+        Truth.assertThat(listOfWidths[4]).isEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo(listOfWidths[7])
+        Truth.assertThat(listOfWidths[7]).isEqualTo(listOfWidths[8])
+    }
+
+    @Test
+    fun testContextualFlowColumn_equalWidth_fraction() {
+        val listOfWidths = mutableMapOf<Int, Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = 9,
+                    Modifier
+                        .wrapContentWidth(align = Alignment.Start, unbounded = true)
+                        .padding(20.dp)
+                        .fillMaxWidth(1f),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) { index ->
+                    Box(
+                        Modifier
+                            .onSizeChanged {
+                                listOfWidths[index] = it.width
+                            }
+                            .height(100.dp)
+                            .background(Color.Green)
+                            .run {
+                                if (index == 0 || index == 3 || index == 6) {
+                                    fillMaxColumnWidth(0.5f)
+                                } else {
+                                    this
+                                }
+                            }
+                    ) {
+                        val width = index * 400
+                        Box(modifier = Modifier.width(width.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8)
+        Truth.assertThat(listOfWidths[0]).isEqualTo((.5 * listOfWidths[2]!!).roundToInt())
+        Truth.assertThat(listOfWidths[1]).isNotEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isEqualTo(800)
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo((.5 * listOfWidths[5]!!).roundToInt())
+        Truth.assertThat(listOfWidths[4]).isNotEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isEqualTo(2000)
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo((.5 * listOfWidths[8]!!).roundToInt())
+        Truth.assertThat(listOfWidths[7]).isNotEqualTo(listOfWidths[8])
+        Truth.assertThat(listOfWidths[8]).isEqualTo(3200)
+    }
+
+    @Test
+    fun testContextualFlowColumn_staysInOneRow() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    ContextualFlowColumn(
+                        itemCount = 2,
+                        Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            }
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(20)
+    }
+
+    @Test
+    fun testContextualFlowRow_wrapsToTheNextLine_Rounding() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    ContextualFlowRow(
+                        itemCount = 3,
+                        Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            }
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowColumn_wrapsToTheNextLine_Rounding() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    ContextualFlowColumn(
+                        itemCount = 3,
+                        Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            }
+                    ) {
+                        Box(Modifier.size(20.toDp()))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testContextualFlowRow_empty() {
+        var height = 0
+        var width = 0
+
+        rule.setContent {
+            Box(Modifier.size(100.dp)) {
+                ContextualFlowRow(
+                    itemCount = 0,
+                    Modifier
+                        .onSizeChanged {
+                            height = it.height
+                            width = it.width
+                        }
+                ) {}
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(0)
+        Truth.assertThat(width).isEqualTo(0)
+    }
+
+    @Test
+    fun testContextualFlowColumn_empty() {
+        var height = 0
+        var width = 0
+
+        rule.setContent {
+            Box(Modifier.size(100.dp)) {
+                ContextualFlowColumn(
+                    itemCount = 0,
+                    Modifier
+                        .onSizeChanged {
+                            height = it.height
+                            width = it.width
+                        }
+                ) {}
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(0)
+        Truth.assertThat(width).isEqualTo(0)
+    }
+
+    @Test
+    fun testContextualFlowRow_alignItemsDefaultsToLeft() {
+        val shorterHeight = 10
+        val expectedResult = 0f
+        var positionInParentY = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(itemCount = 5) {
+                        Box(
+                            Modifier
+                                .size(
+                                    20.toDp(),
+                                    shorterHeight.toDp()
+                                )
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    positionInParentY = positionInParent.y
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentY).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testContextualFlowRow_alignItemsCenterVertically() {
+        val totalRowHeight = 20
+        val shorterHeight = 10
+        val expectedResult = (totalRowHeight - shorterHeight) / 2
+        var positionInParentY = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(itemCount = 5) { index ->
+                        Box(
+                            Modifier
+                                .align(Alignment.CenterVertically)
+                                .size(
+                                    20.toDp(),
+                                    if (index == 4) {
+                                        shorterHeight.toDp()
+                                    } else {
+                                        totalRowHeight.toDp()
+                                    }
+                                )
+                                .onPlaced {
+                                    if (index == 4) {
+                                        val positionInParent = it.positionInParent()
+                                        positionInParentY = positionInParent.y
+                                    }
+                                })
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentY).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testContextualFlowColumn_alignItemsDefaultsToTop() {
+        val shorterWidth = 10
+        val expectedResult = 0f
+        var positionInParentX = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(itemCount = 5) {
+                        Box(
+                            Modifier
+                                .size(
+                                    shorterWidth.toDp(),
+                                    20.toDp()
+                                )
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    positionInParentX = positionInParent.x
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentX).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testContextualFlowColumn_alignItemsCenterHorizontally() {
+        val totalColumnWidth = 20
+        val shorterWidth = 10
+        val expectedResult = (totalColumnWidth - shorterWidth) / 2
+        var positionInParentX = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(itemCount = 5) { index ->
+                        Box(
+                            Modifier
+                                .align(Alignment.CenterHorizontally)
+                                .size(
+                                    if (index == 4) {
+                                        shorterWidth.toDp()
+                                    } else {
+                                        totalColumnWidth.toDp()
+                                    },
+                                    20.toDp()
+                                )
+                                .onPlaced {
+                                    if (index == 4) {
+                                        val positionInParent = it.positionInParent()
+                                        positionInParentX = positionInParent.x
+                                    }
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentX).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementSpaceAround() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+        val xPositions = FloatArray(noOfItemsPerRow)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        itemCount = 5,
+                        Modifier.fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.SpaceAround
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEach {
+            val xPosition = it
+            expectedXPosition += gapSize
+            Truth.assertThat(xPosition).isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+            expectedXPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxLinesVisible() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.Visible
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxLinesVisible() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.Visible
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxHeightVisible() {
+        val itemSize = 50f
+        val maxHeight = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowRowOverflow.Visible
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxWidthVisible() {
+        val itemSize = 50f
+        val maxWidth = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowColumnOverflow.Visible
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxLinesClipped() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.Clip
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxLinesClipped() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.Clip
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxHeightClipped() {
+        val itemSize = 50f
+        val maxHeight = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowRowOverflow.Clip
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxWidthClipped() {
+        val itemSize = 50f
+        val maxWidth = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowColumnOverflow.Clip
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxLinesSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var finalMaxLines = 2
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.expandIndicator {
+                        seeMoreTag = "seeMoreTag$shownItemCount"
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxLines += 2
+                                    finalMaxLines = maxLines
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onPlaced {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxLinesSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var finalMaxLines = 2
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.expandIndicator {
+                        seeMoreTag = "SeeMoreTag$shownItemCount"
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxLines += 2
+                                    finalMaxLines = maxLines
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onPlaced {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxHeightSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var finalMaxHeight = 120.dp
+
+        rule.setContent {
+            var maxHeight by remember { mutableStateOf(120.dp) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowRowOverflow.expandIndicator {
+                        seeMoreTag = "seeMoreTag$shownItemCount"
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxHeight += 100.dp + (spacing.dp * 2)
+                                    finalMaxHeight = maxHeight
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onGloballyPositioned {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onGloballyPositioned {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxHeight).isEqualTo(260.dp)
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxHeight).isEqualTo(400.dp)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxWidthSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var finalMaxWidth = 120.dp
+
+        rule.setContent {
+            var maxWidth by remember { mutableStateOf(120.dp) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = ContextualFlowColumnOverflow.expandIndicator {
+                        seeMoreTag = "seeMoreTag$shownItemCount"
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxWidth += 100.dp + (spacing.dp * 2)
+                                    finalMaxWidth = maxWidth
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onGloballyPositioned {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onGloballyPositioned {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxWidth).isEqualTo(260.dp)
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxWidth).isEqualTo(400.dp)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_DoesNotThrowExceptionWhenSeeMoreCalledDuringComposition() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            val remainingItems = totalItems - shownItemCount
+                            if (remainingItems > 0) {
+                                Box(
+                                    modifier = Modifier
+                                        .clickable {
+                                            maxLines += 2
+                                        }
+                                        .size(itemSize.dp)
+                                )
+                            }
+                        },
+                        collapseIndicator = {
+                            val remainingItems = totalItems - shownItemCount
+                            if (remainingItems > 0) {
+                                Box(
+                                    modifier = Modifier
+                                        .clickable {
+                                            maxLines += 2
+                                        }
+                                        .size(itemSize.dp)
+                                )
+                            }
+                        }
+                    )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_DoesNotThrowExceptionWhenSeeMoreCalledDuringComposition() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            val remainingItems = totalItems - shownItemCount
+                            if (remainingItems > 0) {
+                                Box(
+                                    modifier = Modifier
+                                        .clickable {
+                                            maxLines += 2
+                                        }
+                                        .size(itemSize.dp)
+                                )
+                            }
+                        },
+                        collapseIndicator = {
+                            val remainingItems = totalItems - shownItemCount
+                            if (remainingItems > 0) {
+                                Box(
+                                    modifier = Modifier
+                                        .clickable {
+                                            maxLines += 2
+                                        }
+                                        .size(itemSize.dp)
+                                )
+                            }
+                        }
+                    )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxLinesexpandOrCollapseIndicator() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        lateinit var expandOnScope: ContextualFlowRowOverflowScope
+        lateinit var collapseOnScope: ContextualFlowRowOverflowScope
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    itemCount = totalItems,
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            expandOnScope = this
+                            seeMoreTag = "seeMoreTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onGloballyPositioned {
+                                        seeMoreShown = true
+                                    }
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            collapseOnScope = this
+                            collapseTag = "collapseTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(collapseTag)
+                                    .onGloballyPositioned {
+                                        collapseShown = true
+                                    }
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(collapseOnScope.shownItemCount)
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(collapseOnScope.shownItemCount)
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(collapseOnScope.shownItemCount)
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(collapseOnScope.shownItemCount)
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(collapseOnScope.shownItemCount)
+            Truth.assertThat(expandOnScope.shownItemCount).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxLinesexpandOrCollapseIndicator() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        var itemsShownOnExpand = 0
+        var itemsShownOnCollapse = 0
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    itemCount = totalItems,
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            itemsShownOnExpand = shownItemCount
+                            seeMoreTag = "seeMoreTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onGloballyPositioned {
+                                        seeMoreShown = true
+                                    }
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            itemsShownOnCollapse = shownItemCount
+                            collapseTag = "collapseTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(collapseTag)
+                                    .onGloballyPositioned {
+                                        collapseShown = true
+                                    }
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnCollapse).isEqualTo(0)
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(itemsShownOnCollapse).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_MaxLines_DifferentCollapseSize() {
+        val itemSize = 50f
+        val collapseSize = 100f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var collapseTag = "CollapseTag"
+
+        var finalMaxLines = 2
+        var itemsShownOnExpand = 0
+        var itemsShownOnCollapse = 0
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            seeMoreTag = "seeMoreTag$shownItemCount"
+                            itemsShownOnExpand = shownItemCount
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            collapseTag = "collapseTag$shownItemCount"
+                            itemsShownOnCollapse = shownItemCount
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(collapseSize.dp)
+                                    .testTag(collapseTag)
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }),
+                    itemCount = totalItems
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(itemsShownOnCollapse).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_MaxLines_DifferentCollapseSize() {
+        val itemSize = 50f
+        val collapseSize = 100f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        var seeMoreTag = "SeeMoreTag"
+        var collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        var itemsShownOnExpand = 0
+        var itemsShownOnCollapse = 0
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                ContextualFlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            itemsShownOnExpand = shownItemCount
+                            seeMoreTag = "seeMoreTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            itemsShownOnCollapse = shownItemCount
+                            collapseTag = "collapseTag$shownItemCount"
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(collapseSize.dp)
+                                    .testTag(collapseTag)
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    ),
+                    itemCount = totalItems
+                ) { index ->
+                    Box(
+                        modifier = Modifier
+                            .size(itemSize.dp)
+                            .onPlaced {
+                                itemsShownCount = index + 1
+                            }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(itemsShownOnCollapse).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(itemsShownOnExpand).isEqualTo(5)
+        }
+    }
+
+    private fun advanceClock() {
+        rule.mainClock.advanceTimeBy(100_000L)
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementSpaceAround() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+
+        val yPositions = FloatArray(noOfItemsPerRow)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.SpaceAround,
+                        itemCount = 5
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEach {
+            val yPosition = it
+            expectedYPosition += gapSize
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+            expectedYPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementSpaceAround_withTwoRows() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+        //  ----
+        //      * Visually: #1##2##3# for LTR and #3##2##1# for RTL
+        // --(front) - (back) --
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.SpaceAround,
+                        maxItemsInEachRow = 5,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, xPosition ->
+            if (index % 5 == 0) {
+                expectedXPosition = 0
+            }
+            expectedXPosition += gapSize
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+            expectedXPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementSpaceAround_withTwoColumns() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.SpaceAround,
+                        maxItemsInEachColumn = 5,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            expectedYPosition += gapSize
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+            expectedYPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementEnd() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val gapSize = spaceAvailable.roundToInt()
+        //  * Visually: ####123
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.End,
+                        maxItemsInEachRow = 5,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = gapSize
+        xPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedXPosition = gapSize
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementBottom() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val gapSize = spaceAvailable.roundToInt()
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.Bottom,
+                        maxItemsInEachColumn = 5,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = gapSize
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = gapSize
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementStart() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        //  * Visually: 123####
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementStart_MaxLines() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val xPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(ContextualFlowRowOverflow.Clip)
+        var seeMoreOrCollapse: ContextualFlowRowOverflow? = null
+        var seeMoreXPosition: Float? = null
+        var collapseXPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxLines by remember { maxLinesState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreXPosition = it.positionInParent().x
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseXPosition = it.positionInParent().x
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(Modifier.size(200.dp)) {
+                    ContextualFlowRow(
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow,
+                        itemCount = total
+                    ) {
+                        Box(
+                            Modifier
+                                .size(eachSize.dp)
+                                .onGloballyPositioned {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions.add(xPosition)
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(xPositions.size).isEqualTo(
+                maxItemsInMainAxis * maxLinesState.value
+            )
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            xPositions.clear()
+            overflowState.value = ContextualFlowRowOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreXPosition = positionInParent.x
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxLinesState.value = 4
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxLinesState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+            xPositions.clear()
+            collapseXPosition = null
+            minLinesToShowCollapseState.value = maxLinesState.value + 1
+        }
+        advanceClock()
+        rule.runOnIdle {
+            xPositions.clear()
+            collapseXPosition = null
+            seeMoreXPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value), total
+            )
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementStart_MaxLines() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val yPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(ContextualFlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: ContextualFlowColumnOverflow? = null
+        var seeMoreYPosition: Float? = null
+        var collapseYPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxLines by remember { maxLinesState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreYPosition = it.positionInParent().y
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseYPosition = it.positionInParent().y
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(Modifier.size(200.dp)) {
+                    ContextualFlowColumn(
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow,
+                        itemCount = total
+                    ) {
+                        Box(
+                            Modifier
+                                .size(eachSize.dp)
+                                .onGloballyPositioned {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions.add(yPosition)
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        // Assertions and interaction logic
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(yPositions.size).isEqualTo(
+                maxItemsInMainAxis * maxLinesState.value
+            )
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            yPositions.clear()
+            overflowState.value = ContextualFlowColumnOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreYPosition = positionInParent.y
+                        }
+                )
+            }
+        }
+        // Continuing from the previous logic
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxLinesState.value = 4
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxLinesState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total
+            )
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+            yPositions.clear()
+            collapseYPosition = null
+            minLinesToShowCollapseState.value = maxLinesState.value + 1
+        }
+        advanceClock()
+        rule.runOnIdle {
+            yPositions.clear()
+            collapseYPosition = null
+            seeMoreYPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value), total
+            )
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementStart_MaxHeight() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxHeightState = mutableStateOf(40.dp)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val xPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(ContextualFlowRowOverflow.Clip)
+        var seeMoreOrCollapse: ContextualFlowRowOverflow? = null
+        var seeMoreXPosition: Float? = null
+        var collapseXPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxHeight by remember { maxHeightState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreXPosition = it.positionInParent().x
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseXPosition = it.positionInParent().x
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(
+                    Modifier
+                        .width(200.dp)
+                        .height(maxHeight)
+                ) {
+                    ContextualFlowRow(
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        overflow = overflow,
+                        itemCount = total
+                    ) {
+                        Box(
+                            Modifier
+                                .size(eachSize.dp)
+                                .onGloballyPositioned {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions.add(xPosition)
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(xPositions.size).isEqualTo(10)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            xPositions.clear()
+            overflowState.value = ContextualFlowRowOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreXPosition = positionInParent.x
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 9
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxHeightState.value = 80.dp
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 19
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxHeightState.value = 220.dp
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+            xPositions.clear()
+            collapseXPosition = null
+            minLinesToShowCollapseState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            xPositions.clear()
+            collapseXPosition = null
+            seeMoreXPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_SpaceAligned() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        horizontalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            if (index % maxItemsInMainAxis == 0) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+                expectedXPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementStart_MaxWidth() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxWidthState = mutableStateOf(40.dp)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val yPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(ContextualFlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: ContextualFlowColumnOverflow? = null
+        var seeMoreYPosition: Float? = null
+        var collapseYPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxWidth by remember { maxWidthState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = ContextualFlowColumnOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreYPosition = it.positionInParent().y
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseYPosition = it.positionInParent().y
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .width(maxWidth)
+                ) {
+                    ContextualFlowColumn(
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        overflow = overflow,
+                        itemCount = total
+                    ) {
+                        Box(
+                            Modifier
+                                .size(eachSize.dp)
+                                .onGloballyPositioned {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions.add(yPosition)
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        // Continuing from the previous logic
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(yPositions.size).isEqualTo(10)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            yPositions.clear()
+            overflowState.value = ContextualFlowColumnOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreYPosition = positionInParent.y
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 9
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxWidthState.value = 80.dp
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 19
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxWidthState.value = 220.dp
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+            yPositions.clear()
+            collapseYPosition = null
+            minLinesToShowCollapseState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            yPositions.clear()
+            collapseYPosition = null
+            seeMoreYPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+        }
+    }
+
+    /**
+     * Should space something like this:
+     * 1 2 3
+     * # SpaceAligned
+     * 4 5 6
+     * No Space here
+     */
+    @Test
+    fun testContextualFlowRow_crossAxisSpacedBy() {
+        val eachSize = 20
+        val spaceAligned = 20
+        val noOfItems = 3
+        val expectedHeight = 100
+        var heightResult = 0
+
+        val yPositions = FloatArray(noOfItems)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                heightResult = it.height
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachRow = 1,
+                        itemCount = noOfItems
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth
+            .assertThat(heightResult)
+            .isEqualTo(expectedHeight)
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+            if (index < (noOfItems - 1)) {
+                expectedYPosition += spaceAligned
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_crossAxisSpacedBy() {
+        val eachSize = 20
+        val spaceAligned = 20
+        val noOfItems = 3
+        val expectedWidth = 100
+        var widthResult = 0
+
+        val xPositions = FloatArray(noOfItems)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                widthResult = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachColumn = 1,
+                        itemCount = noOfItems
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        Truth
+            .assertThat(widthResult)
+            .isEqualTo(expectedWidth)
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+            if (index < (noOfItems - 1)) {
+                expectedXPosition += spaceAligned
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_SpaceAligned() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    ContextualFlowColumn(
+                        verticalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val position = positionInParent.y
+                                    yPositions[index] = position
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % maxItemsInMainAxis == 0) {
+                expectedYPosition = 0
+            } else {
+                expectedYPosition += eachSize
+                expectedYPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_SpaceAligned_notExact() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+        val noOfItemsThatCanFit = 2
+
+        var width = 0
+        val expectedWidth = 30
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .widthIn(30.toDp(), 40.toDp())
+                ) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val xPosition = positionInParent.x
+                                    xPositions[index] = xPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(expectedWidth)
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            if (index % noOfItemsThatCanFit == 0) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+                expectedXPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_SpaceAligned_notExact() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+        val noOfItemsThatCanFit = 2
+
+        var height = 0
+        val expectedHeight = 30
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .heightIn(30.toDp(), 40.toDp())
+
+                ) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(eachSize.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(expectedHeight)
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % noOfItemsThatCanFit == 0) {
+                expectedYPosition = 0
+            } else {
+                expectedYPosition += eachSize
+                expectedYPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementTop() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(size.toDp())) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        itemCount = 10
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .size(20.toDp())
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    val yPosition = positionInParent.y
+                                    yPositions[index] = yPosition
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementStart_rtl_fillMaxWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        //  * Visually:
+        //  #54321
+        //  ####6
+
+        val xPositions = FloatArray(6)
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        ContextualFlowRow(
+                            modifier = Modifier
+                                .fillMaxWidth(1f),
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = maxItemsInMainAxis,
+                            itemCount = 6
+                        ) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = size.toInt() - eachSize
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = size.toInt() - eachSize
+            } else {
+                expectedXPosition -= eachSize
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementTop_rtl_fillMaxWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        val xYPositions = Array(10) { Pair(0f, 0f) }
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        ContextualFlowColumn(
+                            modifier = Modifier
+                                .fillMaxHeight(1f)
+                                .fillMaxWidth(1f),
+                            verticalArrangement = Arrangement.Top,
+                            maxItemsInEachColumn = maxItemsInMainAxis,
+                            itemCount = 10
+                        ) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        val xPosition = positionInParent.x
+                                        xYPositions[index] = Pair(xPosition, yPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        var expectedYPosition = 0
+        var expectedXPosition = size.toInt() - eachSize
+        for (index in xYPositions.indices) {
+            val xPosition = xYPositions[index].first
+            val yPosition = xYPositions[index].second
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition -= eachSize
+            }
+            expectedYPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementTop_rtl_wrapContentWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        var itemsThatCanFit = 0
+        var width = 0
+        val xYPositions = Array(10) { Pair(0f, 0f) }
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        ContextualFlowColumn(
+                            modifier = Modifier
+                                .fillMaxHeight(1f)
+                                .onSizeChanged {
+                                    width = it.width
+                                    itemsThatCanFit = it.height / eachSize
+                                },
+                            verticalArrangement = Arrangement.Top,
+                            maxItemsInEachColumn = maxItemsInMainAxis,
+                            itemCount = 10
+                        ) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        val yPosition = positionInParent.y
+                                        xYPositions[index] = Pair(xPosition, yPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        var expectedXPosition = width
+        var fittedItems = 0
+        for (index in xYPositions.indices) {
+            val pair = xYPositions[index]
+            val xPosition = pair.first
+            val yPosition = pair.second
+            if (index % maxItemsInMainAxis == 0 ||
+                fittedItems == itemsThatCanFit
+            ) {
+                expectedYPosition = 0
+                expectedXPosition -= eachSize
+                fittedItems = 0
+            }
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            expectedYPosition += eachSize
+            fittedItems++
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_horizontalArrangementStart_rtl_wrap() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxMainAxisSize = 100
+        //  * Visually:
+        //  #54321
+        //  ####6
+
+        val xPositions = FloatArray(6)
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(200.toDp())) {
+                        ContextualFlowRow(
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = 5,
+                            itemCount = 6
+                        ) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        var expectedXPosition = maxMainAxisSize - eachSize
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = maxMainAxisSize - eachSize
+            } else {
+                expectedXPosition -= eachSize
+            }
+        }
+    }
+
+    @Test
+    fun testContextualFlowRow_constrainsOverflow() {
+        var width = 0
+        var noOfItemsPlaced = 0
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    ContextualFlowRow(
+                        modifier = Modifier
+                            .fillMaxWidth(1f)
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        verticalArrangement = Arrangement.spacedBy(20.dp),
+                        itemCount = 2,
+                        overflow = ContextualFlowRowOverflow.Clip
+                    ) { index ->
+                        Layout(
+                            modifier = Modifier
+                                .requiredSize(250.dp)
+                                .onPlaced {
+                                    noOfItemsPlaced = index + 1
+                                }
+                        ) { _, _ ->
+                            layout(250, 250) {} }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(200)
+        Truth.assertThat(noOfItemsPlaced).isEqualTo(0)
+    }
+
+    @Test
+    fun testContextualFlowColumn_constrainsOverflow() {
+        var height = 0
+        var noOfItemsPlaced = 0
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    ContextualFlowColumn(
+                        modifier = Modifier
+                            .fillMaxHeight(1f)
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(20.dp),
+                        itemCount = 2,
+                        overflow = ContextualFlowColumnOverflow.Clip
+                    ) { index ->
+                        Layout(
+                            modifier = Modifier
+                                .requiredSize(250.dp)
+                                .onPlaced {
+                                    noOfItemsPlaced = index + 1
+                                }
+                        ) { _, _ ->
+                            layout(250, 250) {} }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(200)
+        Truth.assertThat(noOfItemsPlaced).isEqualTo(0)
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
index d3acbbb..8a860f4 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
@@ -16,26 +16,39 @@
 
 package androidx.compose.foundation.layout
 
-import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.MultiContentMeasurePolicy
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInParent
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.DeviceConfigurationOverride
 import androidx.compose.ui.test.LayoutDirection
+import androidx.compose.ui.test.click
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
+import kotlin.math.min
 import kotlin.math.roundToInt
-import kotlin.random.Random
+import org.junit.Assert
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -329,7 +342,9 @@
         val listOfHeights = mutableListOf<Int>()
 
         rule.setContent {
-            with(LocalDensity.current) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
                 FlowRow(
                     Modifier
                         .fillMaxWidth(1f)
@@ -349,7 +364,7 @@
                                 .background(Color.Green)
                                 .fillMaxRowHeight()
                         ) {
-                            val height = it * Random.Default.nextInt(0, 200)
+                            val height = it * 20
                             Box(modifier = Modifier.height(height.dp))
                         }
                     }
@@ -380,7 +395,7 @@
                         .fillMaxWidth(1f)
                         .wrapContentHeight(align = Alignment.Top)
                         .onSizeChanged {
-                                  finalHeight = it.height
+                            finalHeight = it.height
                         },
                     horizontalArrangement = Arrangement.spacedBy(10.dp),
                     maxItemsInEachRow = 3,
@@ -468,7 +483,9 @@
         val listOfHeights = mutableListOf<Int>()
 
         rule.setContent {
-            with(LocalDensity.current) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
                 FlowRow(
                     Modifier
                         .fillMaxWidth(1f)
@@ -489,7 +506,7 @@
                                 .background(Color.Green)
                                 .fillMaxRowHeight()
                         ) {
-                            val height = (it * Random.Default.nextInt(0, 200)) + it
+                            val height = it
                             Box(modifier = Modifier.height(height.dp))
                         }
                     }
@@ -565,12 +582,14 @@
         val listOfWidths = mutableListOf<Int>()
 
         rule.setContent {
-            with(LocalDensity.current) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
                 FlowColumn(
                     Modifier
-                        .fillMaxWidth(1f)
+                        .wrapContentWidth(align = Alignment.Start)
                         .padding(20.dp)
-                        .wrapContentHeight(align = Alignment.Top),
+                        .fillMaxHeight(1f),
                     horizontalArrangement = Arrangement.spacedBy(20.dp),
                     verticalArrangement = Arrangement.spacedBy(10.dp),
                     maxItemsInEachColumn = 3,
@@ -585,7 +604,7 @@
                                 .background(Color.Green)
                                 .fillMaxColumnWidth()
                         ) {
-                            val width = it * Random.Default.nextInt(0, 500)
+                            val width = 20 * it
                             Box(modifier = Modifier.width(width.dp))
                         }
                     }
@@ -609,9 +628,12 @@
         val listOfWidths = mutableListOf<Int>()
 
         rule.setContent {
-            with(LocalDensity.current) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
                 FlowColumn(
                     Modifier
+                        .wrapContentWidth(align = Alignment.Start)
                         .fillMaxHeight(1f)
                         .padding(20.dp),
                     horizontalArrangement = Arrangement.spacedBy(20.dp),
@@ -629,7 +651,7 @@
                                 .background(Color.Green)
                                 .fillMaxColumnWidth()
                         ) {
-                            val width = it * Random.Default.nextInt(0, 500)
+                            val width = 20 * it
                             Box(modifier = Modifier.width(width.dp))
                         }
                     }
@@ -658,9 +680,9 @@
             ) {
                 FlowColumn(
                     Modifier
-                        .wrapContentWidth(Alignment.Start, unbounded = true)
+                        .wrapContentWidth(align = Alignment.Start, unbounded = true)
                         .padding(20.dp)
-                        .fillMaxHeight(1f),
+                        .fillMaxWidth(1f),
                     horizontalArrangement = Arrangement.spacedBy(20.dp),
                     verticalArrangement = Arrangement.spacedBy(10.dp),
                     maxItemsInEachColumn = 3,
@@ -1021,6 +1043,1290 @@
     }
 
     @Test
+    fun testFlowRow_MaxLinesVisible() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.Visible
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testFlowColumn_MaxLinesVisible() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.Visible
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testFlowRow_MaxHeightVisible() {
+        val itemSize = 50f
+        val maxHeight = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowRowOverflow.Visible
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testFlowColumn_MaxWidthVisible() {
+        val itemSize = 50f
+        val maxWidth = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowColumnOverflow.Visible
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(10)
+    }
+
+    @Test
+    fun testFlowRow_MaxLinesClipped() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.Clip
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testFlowColumn_MaxLinesClipped() {
+        val itemSize = 50f
+        val maxLines = 2
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.Clip
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testFlowRow_MaxHeightClipped() {
+        val itemSize = 50f
+        val maxHeight = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowRowOverflow.Clip
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testFlowColumn_MaxWidthClipped() {
+        val itemSize = 50f
+        val maxWidth = 120
+        val totalItems = 10
+        val spacing = 20
+        var itemsShownCount = 0
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowColumnOverflow.Clip
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // Assert that the number of items shown is as expected
+        Truth.assertThat(itemsShownCount).isEqualTo(6)
+    }
+
+    @Test
+    fun testFlowRow_MaxLinesSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        val seeMoreTag = "SeeMoreTag"
+        var finalMaxLines = 2
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.expandIndicator {
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxLines += 2
+                                    finalMaxLines = maxLines
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onPlaced {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testFlowColumn_MaxLinesSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        val seeMoreTag = "SeeMoreTag"
+        var finalMaxLines = 2
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.expandIndicator {
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxLines += 2
+                                    finalMaxLines = maxLines
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onPlaced {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testFlowRow_MaxHeightSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        val seeMoreTag = "SeeMoreTag"
+        var finalMaxHeight = 120.dp
+
+        rule.setContent {
+            var maxHeight by remember { mutableStateOf(120.dp) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier
+                        .width(200.dp)
+                        .height(maxHeight),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowRowOverflow.expandIndicator {
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxHeight += 100.dp + (spacing.dp * 2)
+                                    finalMaxHeight = maxHeight
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onGloballyPositioned {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onGloballyPositioned {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(finalMaxHeight).isEqualTo(260.dp)
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxHeight).isEqualTo(400.dp)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testFlowColumn_MaxWidthSeeMore() {
+        val itemSize = 50f
+        val totalItems = 15
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = false
+        val seeMoreTag = "SeeMoreTag"
+        var finalMaxWidth = 120.dp
+
+        rule.setContent {
+            var maxWidth by remember { mutableStateOf(120.dp) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier
+                        .height(200.dp)
+                        .width(maxWidth),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    overflow = FlowColumnOverflow.expandIndicator {
+                        Box(
+                            modifier = Modifier
+                                .clickable {
+                                    itemsShownCount = 0
+                                    seeMoreShown = false
+                                    maxWidth += 100.dp + (spacing.dp * 2)
+                                    finalMaxWidth = maxWidth
+                                }
+                                .size(itemSize.dp)
+                                .testTag(seeMoreTag)
+                                .onGloballyPositioned {
+                                    seeMoreShown = true
+                                }
+                        )
+                    }
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onGloballyPositioned {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(finalMaxWidth).isEqualTo(260.dp)
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(seeMoreShown).isTrue()
+            itemsShownCount = 0
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(15)
+            Truth.assertThat(finalMaxWidth).isEqualTo(400.dp)
+            Truth.assertThat(seeMoreShown).isFalse()
+        }
+    }
+
+    @Test
+    fun testFlowRow_ThrowsExceptionWhenSeeMoreCalledDuringComposition() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            Assert.assertThrows(RuntimeException::class.java) {
+                                totalItems - shownItemCount
+                            }
+                        },
+                        collapseIndicator = {
+                            Assert.assertThrows(RuntimeException::class.java) {
+                                totalItems - shownItemCount
+                            }
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { _ ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testFlowColumn_ThrowsExceptionWhenSeeMoreCalledDuringComposition() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            Assert.assertThrows(RuntimeException::class.java) {
+                                totalItems - shownItemCount
+                            }
+                        },
+                        collapseIndicator = {
+                            Assert.assertThrows(RuntimeException::class.java) {
+                                totalItems - shownItemCount
+                            }
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { _ ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testFlowRow_MaxLinesSeeMoreOrCollapse() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        val seeMoreTag = "SeeMoreTag"
+        val collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        lateinit var scopeOnExpand: FlowRowOverflowScope
+        lateinit var scopeOnCollapse: FlowRowOverflowScope
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            scopeOnExpand = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onGloballyPositioned {
+                                        seeMoreShown = true
+                                    }
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            scopeOnCollapse = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(collapseTag)
+                                    .onGloballyPositioned {
+                                        collapseShown = true
+                                    }
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_MaxLinesSeeMoreOrCollapse() {
+        val itemSize = 50f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        val seeMoreTag = "SeeMoreTag"
+        val collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        lateinit var scopeOnExpand: FlowColumnOverflowScope
+        lateinit var scopeOnCollapse: FlowColumnOverflowScope
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            scopeOnExpand = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onGloballyPositioned {
+                                        seeMoreShown = true
+                                    }
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            scopeOnCollapse = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(collapseTag)
+                                    .onGloballyPositioned {
+                                        collapseShown = true
+                                    }
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testFlowRow_MaxLines_DifferentCollapseSize() {
+        val itemSize = 50f
+        val collapseSize = 100f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        val seeMoreTag = "SeeMoreTag"
+        val collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        lateinit var scopeOnExpand: FlowRowOverflowScope
+        lateinit var scopeOnCollapse: FlowRowOverflowScope
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowRow(
+                    modifier = Modifier.width(200.dp),
+                    horizontalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowRowOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            scopeOnExpand = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            scopeOnCollapse = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(collapseSize.dp)
+                                    .testTag(collapseTag)
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnCollapse.shownItemCount)
+            Truth.assertThat(scopeOnCollapse.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_MaxLines_DifferentCollapseSize() {
+        val itemSize = 50f
+        val collapseSize = 100f
+        val totalItems = 18
+        val spacing = 20
+        var itemsShownCount = 0
+        var seeMoreShown = true
+        var collapseShown = false
+        val seeMoreTag = "SeeMoreTag"
+        val collapseTag = "CollapseTag"
+        var finalMaxLines = 2
+        lateinit var scopeOnExpand: FlowColumnOverflowScope
+        lateinit var scopeOnCollapse: FlowColumnOverflowScope
+
+        rule.setContent {
+            var maxLines by remember { mutableStateOf(2) }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    modifier = Modifier.height(200.dp),
+                    verticalArrangement = Arrangement.spacedBy(spacing.dp),
+                    maxLines = maxLines,
+                    overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+                        expandIndicator = {
+                            scopeOnExpand = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines += 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(itemSize.dp)
+                                    .testTag(seeMoreTag)
+                                    .onPlaced {
+                                        seeMoreShown = true
+                                    }
+                            )
+                        },
+                        collapseIndicator = {
+                            scopeOnCollapse = this
+                            Box(
+                                modifier = Modifier
+                                    .clickable {
+                                        itemsShownCount = 0
+                                        seeMoreShown = false
+                                        collapseShown = false
+                                        maxLines = 2
+                                        finalMaxLines = maxLines
+                                    }
+                                    .size(collapseSize.dp)
+                                    .testTag(collapseTag)
+                                    .onPlaced {
+                                        collapseShown = true
+                                    }
+                            )
+                        }
+                    )
+                ) {
+                    repeat(totalItems) { index ->
+                        Box(
+                            modifier = Modifier
+                                .size(itemSize.dp)
+                                .onPlaced {
+                                    itemsShownCount = index + 1
+                                }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(itemsShownCount).isEqualTo(5)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.totalItemCount).isEqualTo(scopeOnExpand.totalItemCount)
+            Truth.assertThat(scopeOnExpand.totalItemCount).isEqualTo(totalItems)
+            Truth.assertThat(scopeOnCollapse.totalItemCount).isEqualTo(totalItems)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(11)
+            Truth.assertThat(finalMaxLines).isEqualTo(4)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(17)
+            Truth.assertThat(finalMaxLines).isEqualTo(6)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+
+        rule.onNodeWithTag(seeMoreTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+        rule.runOnIdle {
+            // Assert that the number of items shown is as expected
+            Truth.assertThat(itemsShownCount).isEqualTo(18)
+            Truth.assertThat(finalMaxLines).isEqualTo(8)
+            Truth.assertThat(collapseShown).isTrue()
+            Truth.assertThat(seeMoreShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(itemsShownCount)
+        }
+        rule.onNodeWithTag(collapseTag)
+            .performTouchInput { click() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            Truth.assertThat(finalMaxLines).isEqualTo(2)
+            Truth.assertThat(seeMoreShown).isTrue()
+            Truth.assertThat(collapseShown).isFalse()
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(scopeOnExpand.shownItemCount)
+            Truth.assertThat(scopeOnExpand.shownItemCount).isEqualTo(5)
+        }
+    }
+
+    private fun advanceClock() {
+        rule.mainClock.advanceTimeBy(100_000L)
+    }
+
+    @Test
     fun testFlowColumn_verticalArrangementSpaceAround() {
         val size = 200f
         val noOfItemsPerRow = 5
@@ -1310,6 +2616,559 @@
     }
 
     @Test
+    fun testFlowRow_horizontalArrangementStart_MaxLines() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val xPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var seeMoreOrCollapse: FlowRowOverflow? = null
+        var seeMoreXPosition: Float? = null
+        var collapseXPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxLines by remember { maxLinesState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreXPosition = it.positionInParent().x
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseXPosition = it.positionInParent().x
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(Modifier.size(200.dp)) {
+                    FlowRow(
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(total) { _ ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.dp)
+                                    .onGloballyPositioned {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions.add(xPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(xPositions.size).isEqualTo(
+                maxItemsInMainAxis * maxLinesState.value
+            )
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            xPositions.clear()
+            overflowState.value = FlowRowOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreXPosition = positionInParent.x
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxLinesState.value = 4
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxLinesState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+            xPositions.clear()
+            collapseXPosition = null
+            minLinesToShowCollapseState.value = maxLinesState.value + 1
+        }
+        advanceClock()
+        rule.runOnIdle {
+            xPositions.clear()
+            collapseXPosition = null
+            seeMoreXPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value), total)
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementStart_MaxLines() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val yPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: FlowColumnOverflow? = null
+        var seeMoreYPosition: Float? = null
+        var collapseYPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxLines by remember { maxLinesState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreYPosition = it.positionInParent().y
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseYPosition = it.positionInParent().y
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(Modifier.size(200.dp)) {
+                    FlowColumn(
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(total) { _ ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.dp)
+                                    .onGloballyPositioned {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions.add(yPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        // Assertions and interaction logic
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(yPositions.size).isEqualTo(
+                maxItemsInMainAxis * maxLinesState.value
+            )
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            yPositions.clear()
+            overflowState.value = FlowColumnOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreYPosition = positionInParent.y
+                        }
+                )
+            }
+        }
+        // Continuing from the previous logic
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxLinesState.value = 4
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxLinesState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value) - 1, total)
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+            yPositions.clear()
+            collapseYPosition = null
+            minLinesToShowCollapseState.value = maxLinesState.value + 1
+        }
+        advanceClock()
+        rule.runOnIdle {
+            yPositions.clear()
+            collapseYPosition = null
+            seeMoreYPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = min(
+                (maxItemsInMainAxis * maxLinesState.value), total)
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementStart_MaxHeight() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxHeightState = mutableStateOf(40.dp)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val xPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var seeMoreOrCollapse: FlowRowOverflow? = null
+        var seeMoreXPosition: Float? = null
+        var collapseXPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxHeight by remember { maxHeightState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreXPosition = it.positionInParent().x
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseXPosition = it.positionInParent().x
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(
+                    Modifier
+                        .width(200.dp)
+                        .height(maxHeight)) {
+                    FlowRow(
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        overflow = overflow
+                    ) {
+                        repeat(total) { _ ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.dp)
+                                    .onGloballyPositioned {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions.add(xPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(xPositions.size).isEqualTo(10)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            xPositions.clear()
+            overflowState.value = FlowRowOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreXPosition = positionInParent.x
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 9
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxHeightState.value = 80.dp
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 19
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            xPositions.clear()
+            seeMoreXPosition = null
+            maxHeightState.value = 220.dp
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(expectedXPosition)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+            xPositions.clear()
+            collapseXPosition = null
+            minLinesToShowCollapseState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            xPositions.clear()
+            collapseXPosition = null
+            seeMoreXPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(xPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedXPosition = 0
+            xPositions.forEachIndexed { index, position ->
+                Truth
+                    .assertThat(position)
+                    .isEqualTo(expectedXPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedXPosition = 0
+                } else {
+                    expectedXPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseXPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreXPosition).isEqualTo(null)
+        }
+    }
+
+    @Test
     fun testFlowRow_SpaceAligned() {
         val eachSize = 10
         val maxItemsInMainAxis = 5
@@ -1355,6 +3214,181 @@
         }
     }
 
+    @Test
+    fun testFlowColumn_verticalArrangementStart_MaxWidth() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxWidthState = mutableStateOf(40.dp)
+        val minLinesToShowCollapseState = mutableStateOf(1)
+        val minHeightToShowCollapseState = mutableStateOf(0.dp)
+        val total = 20
+        //  * Visually: 123####
+
+        val yPositions = mutableListOf<Float>()
+        var overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: FlowColumnOverflow? = null
+        var seeMoreYPosition: Float? = null
+        var collapseYPosition: Float? = null
+        rule.setContent {
+            var overflow by remember { overflowState }
+            var maxWidth by remember { maxWidthState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                    expandIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    seeMoreYPosition = it.positionInParent().y
+                                })
+                    },
+                    collapseIndicator = {
+                        Box(
+                            Modifier
+                                .size(20.dp)
+                                .onGloballyPositioned {
+                                    collapseYPosition = it.positionInParent().y
+                                })
+                    },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .width(maxWidth)) {
+                    FlowColumn(
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        overflow = overflow
+                    ) {
+                        repeat(total) { _ ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.dp)
+                                    .onGloballyPositioned {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions.add(yPosition)
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        // Continuing from the previous logic
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(yPositions.size).isEqualTo(10)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            yPositions.clear()
+            overflowState.value = FlowColumnOverflow.expandIndicator {
+                Box(
+                    Modifier
+                        .size(20.dp)
+                        .onGloballyPositioned {
+                            val positionInParent = it.positionInParent()
+                            seeMoreYPosition = positionInParent.y
+                        })
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 9
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxWidthState.value = 80.dp
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 19
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(seeMoreYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            yPositions.clear()
+            seeMoreYPosition = null
+            maxWidthState.value = 220.dp
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(expectedYPosition)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+            yPositions.clear()
+            collapseYPosition = null
+            minLinesToShowCollapseState.value = 5
+        }
+        advanceClock()
+        rule.runOnIdle {
+            yPositions.clear()
+            collapseYPosition = null
+            seeMoreYPosition = null
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            val maxItemsThatCanFit = 20
+            Truth.assertThat(yPositions.size).isEqualTo(maxItemsThatCanFit)
+            var expectedYPosition = 0
+            yPositions.forEachIndexed { index, position ->
+                Truth.assertThat(position).isEqualTo(expectedYPosition)
+                if ((index + 1) % maxItemsInMainAxis == 0) {
+                    expectedYPosition = 0
+                } else {
+                    expectedYPosition += eachSize
+                }
+            }
+            Truth.assertThat(collapseYPosition).isEqualTo(null)
+            Truth.assertThat(seeMoreYPosition).isEqualTo(null)
+        }
+    }
+
     /**
      * Should space something like this:
      * 1 2 3
@@ -1939,6 +3973,885 @@
     }
 
     @Test
+    fun testFlowRow_minIntrinsicWidth_MaxItemsInRow_MaxLines() {
+        var width = 0
+        var height = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var seeMoreOrCollapse: FlowRowOverflow = FlowRowOverflow.Clip
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                expandIndicator = {
+                    Box(Modifier.size(20.dp))
+                },
+                collapseIndicator = {
+                    Box(Modifier.size(20.dp))
+                }
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .width(200.dp)
+                        .wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                                height = it.height
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowRowOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(140)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicHeight_MaxItemsInColumn_MaxLines() {
+        var height = 0
+        var width = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: FlowColumnOverflow = FlowColumnOverflow.Clip
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                expandIndicator = {
+                    Box(Modifier.size(20.dp))
+                },
+                collapseIndicator = {
+                    Box(Modifier.size(20.dp)) {}
+                }
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                                width = it.width
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowColumnOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(140)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLines_SeeMoreOrCollapse_MinToShow() {
+        var width = 0
+        var height = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(4)
+        val overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var minLinesToShowCollapseState = mutableStateOf(4)
+        var minHeightToShowCollapseState = mutableStateOf(0.dp)
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minHeightToShowCollapse by remember { minHeightToShowCollapseState }
+            var spacedBy by remember { spacingState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                overflowState.value = FlowRowOverflow.expandOrCollapseIndicator(
+                    expandIndicator = { Box(Modifier.size(20.dp)) },
+                    collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+                    minLinesToShowCollapse,
+                    minHeightToShowCollapse
+                )
+                var overflow by remember { overflowState }
+                Box(
+                    Modifier
+                        .width(200.dp)
+                        .wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                                height = it.height
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minLinesToShowCollapseState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minHeightToShowCollapseState.value = 100.dp
+            maxLinesState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            minHeightToShowCollapseState.value = 120.dp
+        }
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minHeightToShowCollapseState.value = 100.dp
+            minLinesToShowCollapseState.value = 4
+        }
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLines() {
+        var width = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                with(LocalDensity.current) {
+                    Box(
+                        Modifier
+                            .width(200.dp)
+                            .wrapContentHeight()) {
+                        FlowRow(
+                            Modifier
+                                .width(IntrinsicSize.Min)
+                                .onSizeChanged {
+                                    width = it.width
+                                },
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = 6,
+                            maxLines = maxLines,
+                            overflow = FlowRowOverflow.Clip
+                        ) {
+                            repeat(6) {
+                                Box(
+                                    Modifier
+                                        .size(20.toDp())
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(120)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicHeight_MaxLines_SeeMoreOrCollapse_MinToShow() {
+        var height = 0
+        var width = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(4)
+        val overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var minLinesToShowCollapseState = mutableStateOf(4)
+        var minWidthToShowCollapseState = mutableStateOf(0.dp)
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var minLinesToShowCollapse by remember { minLinesToShowCollapseState }
+            var minWidthToShowCollapse by remember { minWidthToShowCollapseState }
+            var spacedBy by remember { spacingState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                overflowState.value = FlowColumnOverflow.expandOrCollapseIndicator(
+                    expandIndicator = { Box(Modifier.size(20.dp)) },
+                    collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+                    minLinesToShowCollapse,
+                    minWidthToShowCollapse
+                )
+                var overflow by remember { overflowState }
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                                width = it.width
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minLinesToShowCollapseState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minWidthToShowCollapseState.value = 100.dp
+            maxLinesState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            minWidthToShowCollapseState.value = 120.dp
+        }
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            minWidthToShowCollapseState.value = 100.dp
+            minLinesToShowCollapseState.value = 4
+        }
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLinesWithSpacedBy() {
+        var width = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .width(250.dp)
+                        .wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(20.dp),
+                        maxItemsInEachRow = 6,
+                        maxLines = maxLines,
+                        overflow = FlowRowOverflow.Clip
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(220)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(100)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLines_SeeMore() {
+        var width = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .width(200.dp)
+                        .wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = 6,
+                        maxLines = maxLines,
+                        overflow = FlowRowOverflow.expandIndicator {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(120)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicHeight_MaxLines_SeeMore() {
+        var height = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = 6,
+                        maxLines = maxLines,
+                        overflow = FlowColumnOverflow.expandIndicator {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(120)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLines_SeeMore_SpacedBy() {
+        var width = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .width(250.dp)
+                        .wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(20.dp),
+                        maxItemsInEachRow = 6,
+                        maxLines = maxLines,
+                        overflow = FlowRowOverflow.expandIndicator {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(220)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(100)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicHeight_MaxLines_SeeMore_SpacedBy() {
+        var height = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .height(250.dp)
+                        .wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                            },
+                        verticalArrangement = Arrangement.spacedBy(20.dp),
+                        maxItemsInEachColumn = 6,
+                        maxLines = maxLines,
+                        overflow = FlowColumnOverflow.expandIndicator {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(220)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(100)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(20)
+        }
+    }
+
+    @Test
+    fun testFlowRow_minIntrinsicWidth_MaxLines_SeeMoreOrCollapse() {
+        var width = 0
+        var height = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl,
+                LocalDensity provides NoOpDensity
+            ) {
+                with(LocalDensity.current) {
+                    Box(
+                        Modifier
+                            .width(200.dp)
+                            .wrapContentHeight()) {
+                        FlowRow(
+                            Modifier
+                                .width(IntrinsicSize.Min)
+                                .onSizeChanged {
+                                    width = it.width
+                                    height = it.height
+                                },
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = 6,
+                            maxLines = maxLines,
+                            overflow = FlowRowOverflow.expandOrCollapseIndicator(
+                                expandIndicator = { Box(Modifier.size(20.dp)) },
+                                collapseIndicator = { Box(Modifier.size(20.dp)) },
+                                minRowsToShowCollapse = 2
+                            )
+                        ) {
+                            repeat(6) {
+                                Box(
+                                    Modifier
+                                        .size(20.dp)
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(120)
+            Truth.assertThat(height).isEqualTo(20)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(80)
+            Truth.assertThat(height).isEqualTo(40)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(80)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(80)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicHeight_MaxLines_SeeMoreOrCollapse() {
+        var height = 0
+        var width = 0
+        val maxLinesState = mutableStateOf(1)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl,
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                                width = it.width
+                            },
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = 6,
+                        maxLines = maxLines,
+                        overflow = FlowColumnOverflow.expandOrCollapseIndicator(
+                            expandIndicator = { Box(Modifier.size(20.dp)) },
+                            collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+                            minColumnsToShowCollapse = 2
+                        )
+                    ) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(120)
+            Truth.assertThat(width).isEqualTo(20)
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(80)
+            Truth.assertThat(width).isEqualTo(40)
+            maxLinesState.value = 4
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(80)
+            maxLinesState.value = 6
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(80)
+        }
+    }
+
+    @Test
     fun testFlowColumn_minIntrinsicWidth() {
         var width = 0
         rule.setContent {
@@ -2566,7 +5479,7 @@
             }
         }
         rule.waitForIdle()
-        Truth.assertThat(width).isEqualTo(101)
+        Truth.assertThat(width).isEqualTo(100)
         Truth.assertThat(height).isEqualTo(120)
     }
 
@@ -2638,6 +5551,184 @@
     }
 
     @Test
+    fun testFlowRow_minIntrinsicHeight_MaxItemsInRow_MaxLines() {
+        var width = 0
+        var height = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var seeMoreOrCollapse: FlowRowOverflow? = null
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                expandIndicator = { Box(Modifier.size(20.dp)) },
+                collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    FlowRow(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                height = it.height
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowRowOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(100)
+            Truth.assertThat(height).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_minIntrinsicWidth_MaxItemsInColumn_MaxLines() {
+        var height = 0
+        var width = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: FlowColumnOverflow? = null
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                expandIndicator = { Box(Modifier.size(20.dp)) },
+                collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    FlowColumn(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .onSizeChanged {
+                                width = it.width
+                                height = it.height
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowColumnOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(100)
+            Truth.assertThat(width).isEqualTo(100)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
     fun testFlowRow_maxIntrinsicHeight() {
         var height = 0
         rule.setContent {
@@ -2670,6 +5761,186 @@
     }
 
     @Test
+    fun testFlowRow_maxIntrinsicHeight_MaxItemsInRow_MaxLines() {
+        var width = 0
+        var height = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowRowOverflow.Clip)
+        var seeMoreOrCollapse: FlowRowOverflow? = null
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                expandIndicator = { Box(Modifier.size(20.dp)) },
+                collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    FlowRow(
+                        Modifier
+                            .width(IntrinsicSize.Min)
+                            .height(IntrinsicSize.Max)
+                            .onSizeChanged {
+                                height = it.height
+                                width = it.width
+                            },
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        maxItemsInEachRow = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowRowOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(40)
+            Truth.assertThat(height).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(height).isEqualTo(140)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_maxIntrinsicWidth_MaxItemsInColumn_MaxLines() {
+        var height = 0
+        var width = 0
+        var itemShown = 0
+        val maxItemsInMainAxisState = mutableStateOf(2)
+        val maxLinesState = mutableStateOf(1)
+        val overflowState = mutableStateOf(FlowColumnOverflow.Clip)
+        var seeMoreOrCollapse: FlowColumnOverflow? = null
+        var spacingState = mutableStateOf(0)
+        rule.setContent {
+            var maxLines by remember { maxLinesState }
+            var overflow by remember { overflowState }
+            var maxItemsInMainAxis by remember { maxItemsInMainAxisState }
+            var spacedBy by remember { spacingState }
+            seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                expandIndicator = { Box(Modifier.size(20.dp)) },
+                collapseIndicator = { Box(Modifier.size(20.dp)) {} },
+            )
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
+                    FlowColumn(
+                        Modifier
+                            .height(IntrinsicSize.Min)
+                            .width(IntrinsicSize.Max)
+                            .onSizeChanged {
+                                width = it.width
+                                height = it.height
+                            },
+                        verticalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(spacedBy.dp, Alignment.Start),
+                        maxItemsInEachColumn = maxItemsInMainAxis,
+                        maxLines = maxLines,
+                        overflow = overflow
+                    ) {
+                        repeat(6) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.dp)
+                                    .onPlaced {
+                                        itemShown = index + 1
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(2)
+            overflowState.value = FlowColumnOverflow.expandIndicator {
+                Box(Modifier.size(20.dp)) {}
+            }
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(20)
+            Truth.assertThat(itemShown).isEqualTo(1)
+            maxLinesState.value = 3
+            overflowState.value = seeMoreOrCollapse!!
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(60)
+            Truth.assertThat(itemShown).isEqualTo(5)
+            maxLinesState.value = 4
+            maxItemsInMainAxisState.value = 3
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(40)
+            Truth.assertThat(width).isEqualTo(80)
+            Truth.assertThat(itemShown).isEqualTo(6)
+            spacingState.value = 20
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(height).isEqualTo(60)
+            Truth.assertThat(width).isEqualTo(140)
+            Truth.assertThat(itemShown).isEqualTo(6)
+        }
+    }
+
+    @Test
     fun testFlowRow_maxIntrinsicHeight_withSpacedBy() {
         var height = 0
         rule.setContent {
@@ -2868,22 +6139,30 @@
     @Test
     fun testFlowRow_constrainsOverflow() {
         var width = 0
+        var noOfItemsPlaced = 0
         rule.setContent {
-            with(LocalDensity.current) {
-                Box(Modifier.size(200.toDp())) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
                     FlowRow(
                         Modifier
                             .fillMaxWidth(1f)
                             .onSizeChanged {
                                 width = it.width
                             },
-                        verticalArrangement = Arrangement.spacedBy(20.toDp()),
+                        verticalArrangement = Arrangement.spacedBy(20.dp),
+                        overflow = FlowRowOverflow.Clip
                     ) {
-                        repeat(2) {
-                            Box(
-                                Modifier
-                                    .size(250.toDp())
-                            )
+                        repeat(2) { index ->
+                            Layout(
+                                modifier = Modifier
+                                    .requiredSize(250.dp)
+                                    .onPlaced {
+                                        noOfItemsPlaced = index + 1
+                                    }
+                            ) { _, _ ->
+                                layout(250, 250) {} }
                         }
                     }
                 }
@@ -2891,27 +6170,166 @@
         }
         rule.waitForIdle()
         Truth.assertThat(width).isEqualTo(200)
+        Truth.assertThat(noOfItemsPlaced).isEqualTo(0)
+    }
+
+    @Test
+    fun testFlowRow_reuseMeasurePolicy() {
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+
+        var overflow = mutableStateOf(FlowRowOverflow.expandIndicator { })
+        var seeMoreOrCollapse: FlowRowOverflow? = null
+        var seeMoreTwo: FlowRowOverflow? = null
+        var measurePolicy: MultiContentMeasurePolicy? = null
+        var previousMeasurePolicy: MultiContentMeasurePolicy? = null
+        rule.setContent {
+            previousMeasurePolicy = measurePolicy
+            val minLinesToShowCollapseState = 1
+            val minHeightToShowCollapseState = 0.dp
+            seeMoreOrCollapse = FlowRowOverflow.expandOrCollapseIndicator(
+                {},
+                {},
+                minLinesToShowCollapseState,
+                minHeightToShowCollapseState)
+            seeMoreTwo = FlowRowOverflow.expandOrCollapseIndicator(
+                {},
+                {},
+                minLinesToShowCollapseState,
+                minHeightToShowCollapseState)
+            var overflowState = remember(overflow.value) { overflow.value.createOverflowState() }
+            var maxLines by remember { maxLinesState }
+            measurePolicy = rowMeasurementMultiContentHelper(
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                maxLines = maxLines,
+                overflowState = overflowState
+            )
+        }
+
+        rule.runOnIdle {
+            overflow.value = seeMoreOrCollapse!!
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(previousMeasurePolicy).isNotEqualTo(measurePolicy)
+            overflow.value = seeMoreTwo!!
+            maxLinesState.value = 2
+        }
+        rule.waitForIdle()
+        Truth.assertThat(previousMeasurePolicy).isSameInstanceAs(measurePolicy)
+    }
+
+    @Test
+    fun testFlowRow_measurePolicy_Identical() {
+        val maxItemsInMainAxis = 5
+        val maxLines = 2
+
+        var measurePolicy: MultiContentMeasurePolicy? = null
+        var previousMeasurePolicy: MultiContentMeasurePolicy? = null
+        rule.setContent {
+            previousMeasurePolicy = rowMeasurementMultiContentHelper(
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                maxLines = maxLines,
+                overflowState = FlowRowOverflow.expandIndicator {}.createOverflowState()
+            )
+
+            measurePolicy = rowMeasurementMultiContentHelper(
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                maxLines = maxLines,
+                overflowState = FlowRowOverflow.expandIndicator {}.createOverflowState()
+            )
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(previousMeasurePolicy).isNotSameInstanceAs(measurePolicy)
+        Truth.assertThat(previousMeasurePolicy).isEqualTo(measurePolicy)
+    }
+
+    @Test
+    fun testFlowColumn_reuseMeasurePolicy() {
+        val maxItemsInMainAxis = 5
+        val maxLinesState = mutableStateOf(2)
+
+        var overflow = mutableStateOf(FlowColumnOverflow.expandIndicator {})
+        var seeMoreOrCollapse: FlowColumnOverflow? = null
+        var seeMoreTwo: FlowColumnOverflow? = null
+        var measurePolicy: MultiContentMeasurePolicy? = null
+        var previousMeasurePolicy: MultiContentMeasurePolicy? = null
+        rule.setContent {
+            previousMeasurePolicy = measurePolicy
+            val minLinesToShowCollapseState = 1
+            val minWidthToShowCollapseState = 0.dp
+            seeMoreOrCollapse = FlowColumnOverflow.expandOrCollapseIndicator(
+                {},
+                {},
+                minLinesToShowCollapseState,
+                minWidthToShowCollapseState
+            )
+            seeMoreTwo = FlowColumnOverflow.expandOrCollapseIndicator(
+                {},
+                {},
+                minLinesToShowCollapseState,
+                minWidthToShowCollapseState
+            )
+            var overflowState = remember(overflow.value) { overflow.value.createOverflowState() }
+            var maxLines by remember { maxLinesState }
+            measurePolicy = columnMeasurementMultiContentHelper(
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                maxLines = maxLines,
+                overflowState = overflowState
+            )
+        }
+
+        rule.runOnIdle {
+            overflow.value = seeMoreOrCollapse!!
+            maxLinesState.value = 2
+        }
+        advanceClock()
+        rule.runOnIdle {
+            Truth.assertThat(previousMeasurePolicy).isNotEqualTo(measurePolicy)
+            overflow.value = seeMoreTwo!!
+            maxLinesState.value = 2
+        }
+        rule.waitForIdle()
+        Truth.assertThat(previousMeasurePolicy).isSameInstanceAs(measurePolicy)
     }
 
     @Test
     fun testFlowColumn_constrainsOverflow() {
         var height = 0
+        var noOfItemsPlaced = 0
         rule.setContent {
-            with(LocalDensity.current) {
-                Box(Modifier.size(200.toDp())) {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                Box(Modifier.size(200.dp)) {
                     FlowColumn(
                         Modifier
-                            .fillMaxWidth(1f)
+                            .fillMaxHeight(1f)
                             .onSizeChanged {
                                 height = it.height
                             },
-                        horizontalArrangement = Arrangement.spacedBy(20.toDp()),
+                        horizontalArrangement = Arrangement.spacedBy(20.dp),
+                        overflow = FlowColumnOverflow.Clip
                     ) {
-                        repeat(2) {
-                            Box(
-                                Modifier
-                                    .size(250.toDp())
-                            )
+                        repeat(2) { index ->
+                            Layout(
+                                modifier = Modifier
+                                    .requiredSize(250.dp)
+                                    .onPlaced {
+                                        noOfItemsPlaced = index + 1
+                                    }
+                            ) { _, _ ->
+                                layout(250, 250) {} }
                         }
                     }
                 }
@@ -2919,10 +6337,11 @@
         }
         rule.waitForIdle()
         Truth.assertThat(height).isEqualTo(200)
+        Truth.assertThat(noOfItemsPlaced).isEqualTo(0)
     }
 }
 
-private val NoOpDensity = object : Density {
+internal val NoOpDensity = object : Density {
     override val density = 1f
     override val fontScale = 1f
 }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
new file mode 100644
index 0000000..ce07967
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2023 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.compose.foundation.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.SubcomposeMeasureScope
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+
+/**
+ * [ContextualFlowRow] is a specialized version of the [FlowRow] layout. It is designed to
+ * enable users to make contextual decisions during the construction of [FlowRow] layouts.
+ *
+ * This component is particularly advantageous when dealing with
+ * a large collection of items, allowing for efficient management and display. Unlike traditional
+ * [FlowRow] that composes all items regardless of their visibility, ContextualFlowRow smartly
+ * limits composition to only those items that are visible within its constraints, such as
+ * [maxLines] or `maxHeight`. This approach ensures optimal performance and resource utilization
+ * by composing fewer items than the total number available, based on the current context and
+ * display parameters.
+ *
+ * While maintaining the core functionality of the standard [FlowRow], [ContextualFlowRow]
+ * operates on an index-based system and composes items sequentially, one after another.
+ * This approach provides a perfect way to make contextual decisions and can be an easier way
+ * to handle problems such as dynamic see more buttons such as (N+ buttons).
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
+ *
+ * @param modifier The modifier to be applied to the Row.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children.
+ * @param verticalArrangement The vertical arrangement of the layout's virtual rows.
+ * @param maxItemsInEachRow The maximum number of items per row
+ * @param maxLines The maximum number of rows
+ * @param overflow The strategy to handle overflowing items
+ * @param itemCount The total number of item composable
+ * @param content The indexed-based content of [ContextualFlowRowScope]
+ *
+ * @see FlowRow
+ * @see ContextualFlowColumn
+ */
+@Composable
+@ExperimentalLayoutApi
+fun ContextualFlowRow(
+    itemCount: Int,
+    modifier: Modifier = Modifier,
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+    maxItemsInEachRow: Int = Int.MAX_VALUE,
+    maxLines: Int = Int.MAX_VALUE,
+    overflow: ContextualFlowRowOverflow = ContextualFlowRowOverflow.Clip,
+    content: @Composable ContextualFlowRowScope.(index: Int) -> Unit,
+) {
+    val overflowState = remember(overflow) {
+        overflow.createOverflowState()
+    }
+    val list: List<@Composable () -> Unit> = remember(overflow) {
+        val mutableList: MutableList<@Composable () -> Unit> = mutableListOf()
+        overflow.addOverflowComposables(overflowState, mutableList)
+        mutableList
+    }
+    val measurePolicy = contextualRowMeasurementHelper(
+        horizontalArrangement,
+        verticalArrangement,
+        maxItemsInEachRow,
+        maxLines,
+        overflowState,
+        itemCount,
+        list
+    ) { index ->
+        ContextualFlowRowScopeInstance.content(index)
+    }
+    SubcomposeLayout(
+        modifier = modifier,
+        measurePolicy = measurePolicy
+    )
+}
+
+/**
+ * [ContextualFlowColumn] is a specialized version of the [FlowColumn] layout. It is designed to
+ * enable users to make contextual decisions during the construction of [FlowColumn] layouts.
+ *
+ * This component is particularly advantageous when dealing with
+ * a large collection of items, allowing for efficient management and display. Unlike traditional
+ * [FlowColumn] that composes all items regardless of their visibility, ContextualFlowColumn smartly
+ * limits composition to only those items that are visible within its constraints, such as
+ * [maxLines] or `maxWidth`. This approach ensures optimal performance and resource utilization
+ * by composing fewer items than the total number available, based on the current context and
+ * display parameters.
+ *
+ * While maintaining the core functionality of the standard [FlowColumn], [ContextualFlowColumn]
+ * operates on an index-based system and composes items sequentially, one after another.
+ * This approach provides a perfect way to make contextual decisions and can be an easier way
+ * to handle problems such as dynamic see more buttons such as (N+ buttons).
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
+ *
+ * @param modifier The modifier to be applied to the Row.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children.
+ * @param verticalArrangement The vertical arrangement of the layout's virtual column.
+ * @param maxItemsInEachColumn The maximum number of items per column
+ * @param maxLines The maximum number of columns
+ * @param overflow The strategy to handle overflowing items
+ * @param itemCount The total number of item composable
+ * @param content The indexed-based content of [ContextualFlowColumnScope]
+ *
+ * @see FlowColumn
+ * @see ContextualFlowRow
+ */
+@Composable
+@ExperimentalLayoutApi
+fun ContextualFlowColumn(
+    itemCount: Int,
+    modifier: Modifier = Modifier,
+    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+    maxItemsInEachColumn: Int = Int.MAX_VALUE,
+    maxLines: Int = Int.MAX_VALUE,
+    overflow: ContextualFlowColumnOverflow = ContextualFlowColumnOverflow.Clip,
+    content: @Composable ContextualFlowColumnScope.(index: Int) -> Unit,
+) {
+    val overflowState = remember(overflow) {
+        overflow.createOverflowState()
+    }
+    val list: List<@Composable () -> Unit> = remember(overflow) {
+        val mutableList: MutableList<@Composable () -> Unit> = mutableListOf()
+        overflow.addOverflowComposables(overflowState, mutableList)
+        mutableList
+    }
+    val measurePolicy = contextualColumnMeasureHelper(
+        verticalArrangement,
+        horizontalArrangement,
+        maxItemsInEachColumn,
+        maxLines,
+        overflowState,
+        itemCount,
+        list,
+    ) { index ->
+        ContextualFlowColumnScopeInstance.content(index)
+    }
+
+    SubcomposeLayout(
+        modifier = modifier,
+        measurePolicy = measurePolicy
+    )
+}
+
+/**
+ * Scope for the children of [ContextualFlowRow].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface ContextualFlowRowScope : FlowRowScope
+
+/**
+ * Scope for the overflow [ContextualFlowRow].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface ContextualFlowRowOverflowScope : FlowRowOverflowScope
+
+/**
+ * Scope for the overflow [ContextualFlowColumn].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface ContextualFlowColumnOverflowScope : FlowColumnOverflowScope
+
+/**
+ * Scope for the children of [ContextualFlowColumn].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface ContextualFlowColumnScope : FlowColumnScope
+
+@OptIn(ExperimentalLayoutApi::class)
+internal object ContextualFlowRowScopeInstance :
+    FlowRowScope by FlowRowScopeInstance, ContextualFlowRowScope
+
+@OptIn(ExperimentalLayoutApi::class)
+internal object ContextualFlowColumnScopeInstance : FlowColumnScope by FlowColumnScopeInstance,
+    ContextualFlowColumnScope
+
+@ExperimentalLayoutApi
+internal class ContextualFlowRowOverflowScopeImpl(
+    private val state: FlowLayoutOverflowState
+) : FlowRowOverflowScope by FlowRowOverflowScopeImpl(state), ContextualFlowRowOverflowScope
+
+@ExperimentalLayoutApi
+internal class ContextualFlowColumnOverflowScopeImpl(
+    private val state: FlowLayoutOverflowState
+) : FlowColumnOverflowScope by FlowColumnOverflowScopeImpl(state), ContextualFlowColumnOverflowScope
+
+@Composable
+internal fun contextualRowMeasurementHelper(
+    horizontalArrangement: Arrangement.Horizontal,
+    verticalArrangement: Arrangement.Vertical,
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflowState: FlowLayoutOverflowState,
+    itemCount: Int,
+    overflowComposables: List<@Composable () -> Unit>,
+    getComposable: @Composable (
+        index: Int
+    ) -> Unit
+): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
+    return remember(
+        horizontalArrangement,
+        verticalArrangement,
+        maxItemsInMainAxis,
+        maxLines,
+        overflowState,
+        itemCount,
+        getComposable
+    ) {
+        FlowMeasureLazyPolicy(
+            orientation = LayoutOrientation.Horizontal,
+            horizontalArrangement = horizontalArrangement,
+            mainAxisArrangementSpacing = horizontalArrangement.spacing,
+            crossAxisSize = SizeMode.Wrap,
+            crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
+            verticalArrangement = verticalArrangement,
+            crossAxisArrangementSpacing = verticalArrangement.spacing,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            itemCount = itemCount,
+            overflow = overflowState,
+            maxLines = maxLines,
+            getComposable = getComposable,
+            overflowComposables = overflowComposables
+        ).getMeasurePolicy()
+    }
+}
+
+@Composable
+internal fun contextualColumnMeasureHelper(
+    verticalArrangement: Arrangement.Vertical,
+    horizontalArrangement: Arrangement.Horizontal,
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflowState: FlowLayoutOverflowState,
+    itemCount: Int,
+    overflowComposables: List<@Composable () -> Unit>,
+    getComposable: @Composable (
+        index: Int
+    ) -> Unit
+): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
+    return remember(
+        verticalArrangement,
+        horizontalArrangement,
+        maxItemsInMainAxis,
+        maxLines,
+        overflowState,
+        itemCount,
+        getComposable
+    ) {
+        FlowMeasureLazyPolicy(
+            orientation = LayoutOrientation.Vertical,
+            verticalArrangement = verticalArrangement,
+            mainAxisArrangementSpacing = verticalArrangement.spacing,
+            crossAxisSize = SizeMode.Wrap,
+            crossAxisAlignment = CROSS_AXIS_ALIGNMENT_START,
+            horizontalArrangement = horizontalArrangement,
+            crossAxisArrangementSpacing = horizontalArrangement.spacing,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            itemCount = itemCount,
+            overflow = overflowState,
+            maxLines = maxLines,
+            overflowComposables = overflowComposables,
+            getComposable = getComposable
+        ).getMeasurePolicy()
+    }
+}
+
+/**
+ * Returns a Flow Measure Policy
+ */
+@OptIn(ExperimentalLayoutApi::class)
+private data class FlowMeasureLazyPolicy(
+    private val orientation: LayoutOrientation,
+    private val horizontalArrangement: Arrangement.Horizontal,
+    private val verticalArrangement: Arrangement.Vertical,
+    private val mainAxisArrangementSpacing: Dp,
+    private val crossAxisSize: SizeMode,
+    private val crossAxisAlignment: CrossAxisAlignment,
+    private val crossAxisArrangementSpacing: Dp,
+    private val itemCount: Int,
+    private val maxLines: Int,
+    private val maxItemsInMainAxis: Int,
+    private val overflow: FlowLayoutOverflowState,
+    private val overflowComposables: List<@Composable () -> Unit>,
+    private val getComposable: @Composable (
+        index: Int
+    ) -> Unit
+) {
+    fun getMeasurePolicy(): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
+        return { measureScope, constraints ->
+            measureScope.measure(constraints)
+        }
+    }
+
+    private fun SubcomposeMeasureScope.measure(
+        constraints: Constraints
+    ): MeasureResult {
+        if (itemCount <= 0 ||
+            (maxLines == 0 ||
+            maxItemsInMainAxis == 0 ||
+            constraints.maxHeight == 0 &&
+            overflow.type != FlowLayoutOverflow.OverflowType.Visible
+                )) {
+            return layout(0, 0) {}
+        }
+        val measurablesIterator = ContextualFlowItemIterator(
+            itemCount
+        ) { index ->
+            this.subcompose(index) {
+                getComposable(index)
+            }
+        }
+        overflow.itemCount = itemCount
+        overflow.setOverflowMeasurables(
+            orientation,
+            constraints
+        ) { canExpand, noOfItemsShown ->
+            val composableIndex = if (canExpand) 0 else 1
+            overflowComposables.getOrNull(composableIndex)?.run {
+                [email protected]("$canExpand$itemCount$noOfItemsShown",
+                    this
+                ).getOrNull(0)
+            }
+        }
+        return breakDownItems(
+            orientation,
+            horizontalArrangement,
+            verticalArrangement,
+            crossAxisSize,
+            crossAxisAlignment,
+            measurablesIterator,
+            constraints,
+            maxItemsInMainAxis,
+            maxLines,
+            overflow
+        )
+    }
+}
+
+internal class ContextualFlowItemIterator(
+    private val itemCount: Int,
+    private val getMeasurables: (
+        index: Int
+    ) -> List<Measurable>
+) : Iterator<Measurable> {
+    private val _list: MutableList<Measurable> = mutableListOf()
+    private var itemIndex: Int = 0
+    private var listIndex = 0
+    val list: List<Measurable> get() = _list
+
+    override fun hasNext(): Boolean {
+        return listIndex < list.size || itemIndex < itemCount
+    }
+
+    override fun next(): Measurable {
+        // when we are at the end of the list, we fetch a new item from getMeasurables
+        // and add to the list.
+        // otherwise, we continue through the list.
+        return if (listIndex < list.size) {
+            val measurable = list[listIndex]
+            listIndex++
+            measurable
+        } else if (itemIndex < itemCount) {
+            val measurables = getMeasurables(itemIndex)
+            itemIndex++
+            if (measurables.isEmpty()) {
+                next()
+            } else {
+                val measurable = measurables.first()
+                _list.addAll(measurables)
+                listIndex++
+                measurable
+            }
+        } else {
+            throw ArrayIndexOutOfBoundsException(
+                "No item returned at index call. Index: $itemIndex"
+            )
+        }
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index b876953..c4b653f 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -1,6 +1,25 @@
+/*
+ * Copyright 2023 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.compose.foundation.layout
 
 import androidx.annotation.FloatRange
+import androidx.collection.IntIntPair
+import androidx.collection.mutableIntListOf
+import androidx.collection.mutableIntObjectMapOf
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.collection.MutableVector
@@ -15,6 +34,7 @@
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.MultiContentMeasurePolicy
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ParentDataModifierNode
@@ -27,6 +47,7 @@
 import androidx.compose.ui.util.fastForEachIndexed
 import kotlin.math.ceil
 import kotlin.math.max
+import kotlin.math.min
 
 /**
  * [FlowRow] is a layout that fills items from left to right (ltr) in LTR layouts
@@ -40,9 +61,6 @@
  * When a Modifier [RowScope.weight] is provided, it scales the item
  * based on the number items that fall on the row it was placed in.
  *
- * Example:
- * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowWithWeights
- *
  * Note that if two or more Text components are placed in a [Row], normally they should be aligned
  * by their first baselines. [FlowRow] as a general purpose container does not do it automatically
  * so developers need to handle this manually. This is achieved by adding a
@@ -55,27 +73,44 @@
  * @param horizontalArrangement The horizontal arrangement of the layout's children.
  * @param verticalArrangement The vertical arrangement of the layout's virtual rows.
  * @param maxItemsInEachRow The maximum number of items per row
+ * @param maxLines The max number of rows
+ * @param overflow The strategy to handle overflowing items
  * @param content The content as a [RowScope]
  *
  * @see FlowColumn
+ * @see ContextualFlowRow
  * @see [androidx.compose.foundation.layout.Row]
  */
 @Composable
 @ExperimentalLayoutApi
-inline fun FlowRow(
+fun FlowRow(
     modifier: Modifier = Modifier,
     horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
     verticalArrangement: Arrangement.Vertical = Arrangement.Top,
     maxItemsInEachRow: Int = Int.MAX_VALUE,
+    maxLines: Int = Int.MAX_VALUE,
+    overflow: FlowRowOverflow = FlowRowOverflow.Clip,
     content: @Composable FlowRowScope.() -> Unit
 ) {
-    val measurePolicy = rowMeasurementHelper(
+    val overflowState = remember(overflow) {
+        overflow.createOverflowState()
+    }
+    val measurePolicy = rowMeasurementMultiContentHelper(
         horizontalArrangement,
         verticalArrangement,
-        maxItemsInEachRow
+        maxItemsInEachRow,
+        maxLines,
+        overflowState
     )
+    val list: List<@Composable () -> Unit> = remember(overflow, content) {
+        val mutableList: MutableList<@Composable () -> Unit> = mutableListOf()
+        mutableList.add { FlowRowScopeInstance.content() }
+        overflow.addOverflowComposables(overflowState, mutableList)
+        mutableList
+    }
+
     Layout(
-        content = { FlowRowScopeInstance.content() },
+        contents = list,
         measurePolicy = measurePolicy,
         modifier = modifier
     )
@@ -98,34 +133,47 @@
  * When a Modifier [ColumnScope.weight] is provided, it scales the item
  * based on the number items that fall on the column it was placed in.
  *
- * Example:
- * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnWithWeights
- *
  * @param modifier The modifier to be applied to the Row.
  * @param verticalArrangement The vertical arrangement of the layout's children.
  * @param horizontalArrangement The horizontal arrangement of the layout's virtual columns
  * @param maxItemsInEachColumn The maximum number of items per column
+ * @param maxLines The max number of rows
+ * @param overflow The strategy to handle overflowing items
  * @param content The content as a [ColumnScope]
  *
  * @see FlowRow
+ * @see ContextualFlowColumn
  * @see [androidx.compose.foundation.layout.Column]
  */
 @Composable
 @ExperimentalLayoutApi
-inline fun FlowColumn(
+fun FlowColumn(
     modifier: Modifier = Modifier,
     verticalArrangement: Arrangement.Vertical = Arrangement.Top,
     horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
     maxItemsInEachColumn: Int = Int.MAX_VALUE,
+    maxLines: Int = Int.MAX_VALUE,
+    overflow: FlowColumnOverflow = FlowColumnOverflow.Clip,
     content: @Composable FlowColumnScope.() -> Unit
 ) {
-    val measurePolicy = columnMeasurementHelper(
+    val overflowState = remember(overflow) {
+        overflow.createOverflowState()
+    }
+    val measurePolicy = columnMeasurementMultiContentHelper(
         verticalArrangement,
         horizontalArrangement,
-        maxItemsInEachColumn
+        maxItemsInEachColumn,
+        maxLines,
+        overflowState
     )
+    val list: List<@Composable () -> Unit> = remember(overflow, content) {
+        val mutableList: MutableList<@Composable () -> Unit> = mutableListOf()
+        mutableList.add { FlowColumnScopeInstance.content() }
+        overflow.addOverflowComposables(overflowState, mutableList)
+        mutableList
+    }
     Layout(
-        content = { FlowColumnScopeInstance.content() },
+        contents = list,
         measurePolicy = measurePolicy,
         modifier = modifier
     )
@@ -156,6 +204,30 @@
 }
 
 /**
+ * Scope for the overflow [FlowRow].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface FlowRowOverflowScope : FlowRowScope {
+    /**
+    * Total Number of Items available to show in [FlowRow]
+    * This includes items that may not be displayed.
+    *
+    * In [ContextualFlowRow], this matches the
+    * [ContextualFlowRow]'s `itemCount` parameter
+    */
+    @ExperimentalLayoutApi
+    val totalItemCount: Int
+
+    /**
+     * Total Number of Items displayed in the [FlowRow]
+     */
+    @ExperimentalLayoutApi
+    val shownItemCount: Int
+}
+
+/**
  * Scope for the children of [FlowColumn].
  */
 @LayoutScopeMarker
@@ -179,6 +251,30 @@
     ): Modifier
 }
 
+/**
+ * Scope for the overflow [FlowColumn].
+ */
+@LayoutScopeMarker
+@Immutable
+@ExperimentalLayoutApi
+interface FlowColumnOverflowScope : FlowColumnScope {
+    /**
+     * Total Number of Items available to show in [FlowColumn]
+     * This includes items that may not be displayed.
+     *
+     * In [ContextualFlowColumn], this matches the
+     * [ContextualFlowColumn]'s `itemCount` parameter
+     */
+    @ExperimentalLayoutApi
+    val totalItemCount: Int
+
+    /**
+     * Total Number of Items displayed in the [FlowColumn]
+     */
+    @ExperimentalLayoutApi
+    val shownItemCount: Int
+}
+
 @OptIn(ExperimentalLayoutApi::class)
 internal object FlowRowScopeInstance : RowScope by RowScopeInstance, FlowRowScope {
     override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
@@ -195,6 +291,28 @@
 }
 
 @OptIn(ExperimentalLayoutApi::class)
+internal class FlowRowOverflowScopeImpl(
+    private val state: FlowLayoutOverflowState
+) : FlowRowScope by FlowRowScopeInstance, FlowRowOverflowScope {
+    override val totalItemCount: Int
+        get() = state.itemCount
+
+    override val shownItemCount: Int
+        get() = state.noOfItemsShown
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+internal class FlowColumnOverflowScopeImpl(
+    private val state: FlowLayoutOverflowState
+) : FlowColumnScope by FlowColumnScopeInstance, FlowColumnOverflowScope {
+    override val totalItemCount: Int
+        get() = state.itemCount
+
+    override val shownItemCount: Int
+        get() = state.noOfItemsShown
+}
+
+@OptIn(ExperimentalLayoutApi::class)
 internal object FlowColumnScopeInstance : ColumnScope by ColumnScopeInstance, FlowColumnScope {
     override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
         require(fraction >= 0.0) { "invalid fraction $fraction; must be greater than or " +
@@ -253,6 +371,7 @@
     }
 }
 
+@OptIn(ExperimentalLayoutApi::class)
 @PublishedApi
 @Composable
 internal fun rowMeasurementHelper(
@@ -260,7 +379,48 @@
     verticalArrangement: Arrangement.Vertical,
     maxItemsInMainAxis: Int,
 ): MeasurePolicy {
-    return remember(horizontalArrangement, verticalArrangement, maxItemsInMainAxis) {
+    return remember(
+        horizontalArrangement,
+        verticalArrangement,
+        maxItemsInMainAxis,
+    ) {
+        val measurePolicy = FlowMeasurePolicy(
+            orientation = LayoutOrientation.Horizontal,
+            horizontalArrangement = horizontalArrangement,
+            mainAxisArrangementSpacing = horizontalArrangement.spacing,
+            crossAxisSize = SizeMode.Wrap,
+            crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
+            verticalArrangement = verticalArrangement,
+            crossAxisArrangementSpacing = verticalArrangement.spacing,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            maxLines = Int.MAX_VALUE,
+            overflow = FlowRowOverflow.Visible.createOverflowState()
+        ) as MultiContentMeasurePolicy
+
+        MeasurePolicy { measurables, constraints ->
+            with(measurePolicy) {
+                [email protected](listOf(measurables), constraints)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+internal fun rowMeasurementMultiContentHelper(
+    horizontalArrangement: Arrangement.Horizontal,
+    verticalArrangement: Arrangement.Vertical,
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflowState: FlowLayoutOverflowState,
+): MultiContentMeasurePolicy {
+    return remember(
+        horizontalArrangement,
+        verticalArrangement,
+        maxItemsInMainAxis,
+        maxLines,
+        overflowState
+    ) {
         FlowMeasurePolicy(
             orientation = LayoutOrientation.Horizontal,
             horizontalArrangement = horizontalArrangement,
@@ -269,11 +429,14 @@
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
             verticalArrangement = verticalArrangement,
             crossAxisArrangementSpacing = verticalArrangement.spacing,
-            maxItemsInMainAxis = maxItemsInMainAxis
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            maxLines = maxLines,
+            overflow = overflowState
         )
     }
 }
 
+@OptIn(ExperimentalLayoutApi::class)
 @PublishedApi
 @Composable
 internal fun columnMeasurementHelper(
@@ -281,7 +444,47 @@
     horizontalArrangement: Arrangement.Horizontal,
     maxItemsInMainAxis: Int,
 ): MeasurePolicy {
-    return remember(verticalArrangement, horizontalArrangement, maxItemsInMainAxis) {
+    return remember(
+        verticalArrangement,
+        horizontalArrangement,
+        maxItemsInMainAxis,
+    ) {
+        val measurePolicy = FlowMeasurePolicy(
+            orientation = LayoutOrientation.Vertical,
+            verticalArrangement = verticalArrangement,
+            mainAxisArrangementSpacing = verticalArrangement.spacing,
+            crossAxisSize = SizeMode.Wrap,
+            crossAxisAlignment = CROSS_AXIS_ALIGNMENT_START,
+            horizontalArrangement = horizontalArrangement,
+            crossAxisArrangementSpacing = horizontalArrangement.spacing,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            maxLines = Int.MAX_VALUE,
+            overflow = FlowRowOverflow.Visible.createOverflowState()
+        )
+        MeasurePolicy { measurables, constraints ->
+            with(measurePolicy) {
+                [email protected](listOf(measurables), constraints)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+internal fun columnMeasurementMultiContentHelper(
+    verticalArrangement: Arrangement.Vertical,
+    horizontalArrangement: Arrangement.Horizontal,
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflowState: FlowLayoutOverflowState
+): MultiContentMeasurePolicy {
+    return remember(
+        verticalArrangement,
+        horizontalArrangement,
+        maxItemsInMainAxis,
+        maxLines,
+        overflowState
+    ) {
         FlowMeasurePolicy(
             orientation = LayoutOrientation.Vertical,
             verticalArrangement = verticalArrangement,
@@ -291,6 +494,8 @@
             horizontalArrangement = horizontalArrangement,
             crossAxisArrangementSpacing = horizontalArrangement.spacing,
             maxItemsInMainAxis = maxItemsInMainAxis,
+            maxLines = maxLines,
+            overflow = overflowState
         )
     }
 }
@@ -298,170 +503,178 @@
 /**
  * Returns a Flow Measure Policy
  */
+@OptIn(ExperimentalLayoutApi::class)
 private data class FlowMeasurePolicy(
     private val orientation: LayoutOrientation,
-    private val horizontalArrangement: Arrangement.Horizontal?,
-    private val verticalArrangement: Arrangement.Vertical?,
+    private val horizontalArrangement: Arrangement.Horizontal,
+    private val verticalArrangement: Arrangement.Vertical,
     private val mainAxisArrangementSpacing: Dp,
     private val crossAxisSize: SizeMode,
     private val crossAxisAlignment: CrossAxisAlignment,
     private val crossAxisArrangementSpacing: Dp,
     private val maxItemsInMainAxis: Int,
-) : MeasurePolicy {
+    private val maxLines: Int,
+    private val overflow: FlowLayoutOverflowState,
+) : MultiContentMeasurePolicy {
 
     override fun MeasureScope.measure(
-        measurables: List<Measurable>,
+        measurables: List<List<Measurable>>,
         constraints: Constraints
     ): MeasureResult {
-        if (measurables.isEmpty()) {
+        if (maxLines == 0 || maxItemsInMainAxis == 0 || measurables.isEmpty() ||
+            constraints.maxHeight == 0 && overflow.type != FlowLayoutOverflow.OverflowType.Visible
+        ) {
             return layout(0, 0) {}
         }
-        val placeables: Array<Placeable?> = arrayOfNulls(measurables.size)
-        val measureHelper = RowColumnMeasurementHelper(
+        val list = measurables.first()
+        if (list.isEmpty()) {
+            return layout(0, 0) {}
+        }
+        val seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull()
+        val collapseMeasurable = measurables.getOrNull(2)?.firstOrNull()
+        overflow.itemCount = list.size
+        overflow.setOverflowMeasurables(
+            seeMoreMeasurable,
+            collapseMeasurable,
+            orientation,
+            constraints,
+        )
+        return breakDownItems(
             orientation,
             horizontalArrangement,
             verticalArrangement,
-            mainAxisArrangementSpacing,
             crossAxisSize,
             crossAxisAlignment,
-            measurables,
-            placeables,
-        )
-        val orientationIndependentConstraints =
-            OrientationIndependentConstraints(constraints, orientation)
-        val flowResult = breakDownItems(
-            measureHelper,
-            orientation,
-            orientationIndependentConstraints,
+            list.iterator(),
+            constraints,
             maxItemsInMainAxis,
+            maxLines,
+            overflow
         )
-        val items = flowResult.items
-        val crossAxisSizes = IntArray(items.size) { index ->
-            items[index].crossAxisSize
-        }
-        // space in between children, except for the last child
-        val outPosition = IntArray(crossAxisSizes.size)
-        var totalCrossAxisSize = flowResult.crossAxisTotalSize
-        val totalCrossAxisSpacing =
-            crossAxisArrangementSpacing.roundToPx() * (items.size - 1)
-        totalCrossAxisSize += totalCrossAxisSpacing
-        // cross axis arrangement
-        if (orientation == LayoutOrientation.Horizontal) {
-            with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
-                arrange(
-                    totalCrossAxisSize,
-                    crossAxisSizes,
-                    outPosition
-                )
-            }
-        } else {
-            with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
-                arrange(
-                    totalCrossAxisSize,
-                    crossAxisSizes,
-                    layoutDirection,
-                    outPosition
-                )
-            }
-        }
-
-        var layoutWidth: Int
-        var layoutHeight: Int
-        if (orientation == LayoutOrientation.Horizontal) {
-            layoutWidth = flowResult.mainAxisTotalSize
-            layoutHeight = totalCrossAxisSize
-        } else {
-            layoutWidth = totalCrossAxisSize
-            layoutHeight = flowResult.mainAxisTotalSize
-        }
-        layoutWidth = constraints.constrainWidth(layoutWidth)
-        layoutHeight = constraints.constrainHeight(layoutHeight)
-
-        return layout(layoutWidth, layoutHeight) {
-            flowResult.items.forEachIndexed { currentRowOrColumnIndex,
-                measureResult ->
-                measureHelper.placeHelper(
-                    this,
-                    measureResult,
-                    outPosition[currentRowOrColumnIndex],
-                    [email protected]
-                )
-            }
-        }
     }
 
     override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurables: List<IntrinsicMeasurable>,
+        measurables: List<List<IntrinsicMeasurable>>,
         height: Int
-    ) = if (orientation == LayoutOrientation.Horizontal) {
-        minIntrinsicMainAxisSize(
-            measurables,
-            height,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx()
+    ): Int {
+        overflow.setOverflowMeasurables(
+            seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
+            collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
+            orientation = orientation,
+            constraints = Constraints(maxHeight = height)
         )
-    } else {
-        intrinsicCrossAxisSize(
-            measurables,
-            height,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx()
-        )
+        return if (orientation == LayoutOrientation.Horizontal) {
+            minIntrinsicMainAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                height,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        } else {
+            intrinsicCrossAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                height,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        }
     }
 
     override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurables: List<IntrinsicMeasurable>,
+        measurables: List<List<IntrinsicMeasurable>>,
         width: Int
-    ) = if (orientation == LayoutOrientation.Horizontal) {
-        intrinsicCrossAxisSize(
-            measurables,
-            width,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx()
+    ): Int {
+        overflow.setOverflowMeasurables(
+            seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
+            collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
+            orientation = orientation,
+            constraints = Constraints(maxWidth = width)
         )
-    } else {
-        minIntrinsicMainAxisSize(
-            measurables,
-            width,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx(),
-        )
+        return if (orientation == LayoutOrientation.Horizontal) {
+            intrinsicCrossAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                width,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        } else {
+            minIntrinsicMainAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                width,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        }
     }
 
     override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurables: List<IntrinsicMeasurable>,
+        measurables: List<List<IntrinsicMeasurable>>,
         width: Int
-    ) = if (orientation == LayoutOrientation.Horizontal) {
-        intrinsicCrossAxisSize(
-            measurables,
-            width,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx()
+    ): Int {
+        overflow.setOverflowMeasurables(
+            seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
+            collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
+            orientation = orientation,
+            constraints = Constraints(maxWidth = width)
         )
-    } else {
-        maxIntrinsicMainAxisSize(
-            measurables,
-            width,
-            mainAxisArrangementSpacing.roundToPx(),
-        )
+        return if (orientation == LayoutOrientation.Horizontal) {
+            intrinsicCrossAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                width,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        } else {
+            maxIntrinsicMainAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                width,
+                mainAxisArrangementSpacing.roundToPx(),
+            )
+        }
     }
 
     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurables: List<IntrinsicMeasurable>,
+        measurables: List<List<IntrinsicMeasurable>>,
         height: Int
-    ) = if (orientation == LayoutOrientation.Horizontal) {
-        maxIntrinsicMainAxisSize(
-            measurables,
-            height,
-            mainAxisArrangementSpacing.roundToPx(),
+    ): Int {
+        overflow.setOverflowMeasurables(
+            seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
+            collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
+            orientation = orientation,
+            constraints = Constraints(maxHeight = height)
         )
-    } else {
-        intrinsicCrossAxisSize(
-            measurables,
-            height,
-            mainAxisArrangementSpacing.roundToPx(),
-            crossAxisArrangementSpacing.roundToPx()
-        )
+        return if (orientation == LayoutOrientation.Horizontal) {
+            maxIntrinsicMainAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                height,
+                mainAxisArrangementSpacing.roundToPx(),
+            )
+        } else {
+            intrinsicCrossAxisSize(
+                measurables.firstOrNull() ?: listOf(),
+                height,
+                mainAxisArrangementSpacing.roundToPx(),
+                crossAxisArrangementSpacing.roundToPx(),
+                maxLines = maxLines,
+                maxItemsInMainAxis = maxItemsInMainAxis,
+                overflow = overflow
+            )
+        }
     }
 
     fun minIntrinsicMainAxisSize(
@@ -469,6 +682,9 @@
         crossAxisAvailable: Int,
         mainAxisSpacing: Int,
         crossAxisSpacing: Int,
+        maxItemsInMainAxis: Int,
+        maxLines: Int,
+        overflow: FlowLayoutOverflowState
     ) = minIntrinsicMainAxisSize(
         measurables,
         mainAxisSize = minMainAxisIntrinsicItemSize,
@@ -476,7 +692,9 @@
         crossAxisAvailable,
         mainAxisSpacing,
         crossAxisSpacing,
-        maxItemsInMainAxis
+        maxItemsInMainAxis,
+        maxLines,
+        overflow
     )
 
     fun maxIntrinsicMainAxisSize(
@@ -495,7 +713,10 @@
         measurables: List<IntrinsicMeasurable>,
         mainAxisAvailable: Int,
         mainAxisSpacing: Int,
-        crossAxisSpacing: Int
+        crossAxisSpacing: Int,
+        maxItemsInMainAxis: Int,
+        maxLines: Int,
+        overflow: FlowLayoutOverflowState
     ) = intrinsicCrossAxisSize(
         measurables,
         mainAxisSize = minMainAxisIntrinsicItemSize,
@@ -503,8 +724,10 @@
         mainAxisAvailable,
         mainAxisSpacing,
         crossAxisSpacing,
-        maxItemsInMainAxis
-    )
+        maxItemsInMainAxis = maxItemsInMainAxis,
+        overflow = overflow,
+        maxLines = maxLines
+    ).first
 
     val maxMainAxisIntrinsicItemSize: IntrinsicMeasurable.(Int, Int) -> Int =
         if (orientation == LayoutOrientation.Horizontal) { _, h ->
@@ -568,6 +791,7 @@
  * Slower algorithm but needed to determine the minimum main axis size
  * Uses a binary search to search different scenarios to see the minimum main axis size
  */
+@OptIn(ExperimentalLayoutApi::class)
 private fun minIntrinsicMainAxisSize(
     children: List<IntrinsicMeasurable>,
     mainAxisSize: IntrinsicMeasurable.(Int, Int) -> Int,
@@ -575,8 +799,13 @@
     crossAxisAvailable: Int,
     mainAxisSpacing: Int,
     crossAxisSpacing: Int,
-    maxItemsInMainAxis: Int
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflow: FlowLayoutOverflowState
 ): Int {
+    if (children.isEmpty()) {
+        return 0
+    }
     val mainAxisSizes = IntArray(children.size) { 0 }
     val crossAxisSizes = IntArray(children.size) { 0 }
 
@@ -587,35 +816,62 @@
         crossAxisSizes[index] = child.crossAxisSize(index, mainAxisItemSize)
     }
 
-    val maxMainAxisSize = mainAxisSizes.sum()
+    var maxItemsThatCanBeShown = if (
+        maxLines != Int.MAX_VALUE &&
+        maxItemsInMainAxis != Int.MAX_VALUE) {
+        maxItemsInMainAxis * maxLines
+    } else {
+        Int.MAX_VALUE
+    }
+    val mustHaveEllipsis = when {
+        maxItemsThatCanBeShown < children.size &&
+            (overflow.type == FlowLayoutOverflow.OverflowType.ExpandIndicator ||
+                overflow.type == FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator)
+        -> true
+        maxItemsThatCanBeShown >= children.size &&
+            maxLines >= overflow.minLinesToShowCollapse &&
+            overflow.type == FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator ->
+            true
+        else -> false
+    }
+    maxItemsThatCanBeShown -= if (mustHaveEllipsis) 1 else 0
+    maxItemsThatCanBeShown = min(maxItemsThatCanBeShown, children.size)
+    val maxMainAxisSize = mainAxisSizes.sum().run { this + ((children.size - 1) * mainAxisSpacing) }
     var mainAxisUsed = maxMainAxisSize
     var crossAxisUsed = crossAxisSizes.maxOf { it }
 
     val minimumItemSize = mainAxisSizes.maxOf { it }
     var low = minimumItemSize
     var high = maxMainAxisSize
-    while (low < high) {
+    while (low <= high) {
         if (crossAxisUsed == crossAxisAvailable) {
             return mainAxisUsed
         }
         val mid = (low + high) / 2
         mainAxisUsed = mid
-        crossAxisUsed = intrinsicCrossAxisSize(
+        val pair = intrinsicCrossAxisSize(
             children,
             mainAxisSizes,
             crossAxisSizes,
             mainAxisUsed,
             mainAxisSpacing,
             crossAxisSpacing,
-            maxItemsInMainAxis
+            maxItemsInMainAxis,
+            maxLines,
+            overflow
         )
+        crossAxisUsed = pair.first
+        val itemShown = pair.second
 
-        if (crossAxisUsed == crossAxisAvailable) {
-            return mainAxisUsed
-        } else if (crossAxisUsed > crossAxisAvailable) {
+        if (crossAxisUsed > crossAxisAvailable || itemShown < maxItemsThatCanBeShown) {
             low = mid + 1
-        } else {
+            if (low > high) {
+                return low
+            }
+        } else if (crossAxisUsed < crossAxisAvailable) {
             high = mid - 1
+        } else {
+            return mainAxisUsed
         }
     }
 
@@ -633,8 +889,10 @@
     mainAxisAvailable: Int,
     mainAxisSpacing: Int,
     crossAxisSpacing: Int,
-    maxItemsInMainAxis: Int
-): Int {
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflow: FlowLayoutOverflowState
+): IntIntPair {
     return intrinsicCrossAxisSize(
         children,
         { index, _ -> mainAxisSizes[index] },
@@ -642,7 +900,9 @@
         mainAxisAvailable,
         mainAxisSpacing,
         crossAxisSpacing,
-        maxItemsInMainAxis
+        maxItemsInMainAxis,
+        maxLines,
+        overflow
     )
 }
 
@@ -656,11 +916,26 @@
     mainAxisAvailable: Int,
     mainAxisSpacing: Int,
     crossAxisSpacing: Int,
-    maxItemsInMainAxis: Int
-): Int {
+    maxItemsInMainAxis: Int,
+    maxLines: Int,
+    overflow: FlowLayoutOverflowState
+): IntIntPair {
     if (children.isEmpty()) {
-        return 0
+        return IntIntPair(0, 0)
     }
+    val buildingBlocks = FlowLayoutBuildingBlocks(
+        maxItemsInMainAxis = maxItemsInMainAxis,
+        overflow = overflow,
+        maxLines = maxLines,
+        constraints = OrientationIndependentConstraints(
+            mainAxisMin = 0,
+            mainAxisMax = mainAxisAvailable,
+            crossAxisMin = 0,
+            crossAxisMax = Constraints.Infinity
+        ),
+        mainAxisSpacing = mainAxisSpacing,
+        crossAxisSpacing = crossAxisSpacing,
+    )
     var nextChild = children.getOrNull(0)
     var nextCrossAxisSize = nextChild?.crossAxisSize(0, mainAxisAvailable) ?: 0
     var nextMainAxisSize = nextChild?.mainAxisSize(0, nextCrossAxisSize) ?: 0
@@ -669,12 +944,36 @@
     var currentCrossAxisSize = 0
     var totalCrossAxisSize = 0
     var lastBreak = 0
+    var lineIndex = 0
 
-    children.fastForEachIndexed { index, _ ->
-        nextChild!!
+    var wrapInfo = buildingBlocks.getWrapInfo(
+        nextItemHasNext = children.size > 1,
+        nextIndexInLine = 0,
+        leftOver = IntIntPair(remaining, Constraints.Infinity),
+        nextSize = if (nextChild == null) null else IntIntPair(nextMainAxisSize, nextCrossAxisSize),
+        lineIndex = lineIndex,
+        totalCrossAxisSize = totalCrossAxisSize,
+        currentLineCrossAxisSize = currentCrossAxisSize,
+        isWrappingRound = false,
+        isEllipsisWrap = false
+    )
+
+    if (wrapInfo.isLastItemInContainer) {
+        val size = overflow.ellipsisSize(
+            hasNext = nextChild != null,
+            lineIndex = 0,
+            totalCrossAxisSize = 0,
+        )?.second ?: 0
+        val noOfItemsShown = 0
+        return IntIntPair(size, noOfItemsShown)
+    }
+
+    var noOfItemsShown = 0
+    for (index in children.indices) {
         val childCrossAxisSize = nextCrossAxisSize
         val childMainAxisSize = nextMainAxisSize
         remaining -= childMainAxisSize
+        noOfItemsShown = index + 1
         currentCrossAxisSize = maxOf(currentCrossAxisSize, childCrossAxisSize)
 
         // look ahead to simplify logic
@@ -683,20 +982,49 @@
         nextMainAxisSize = nextChild?.mainAxisSize(index + 1, nextCrossAxisSize)
             ?.plus(mainAxisSpacing) ?: 0
 
-        if (remaining < 0 || index + 1 == children.size ||
-            (index + 1) - lastBreak == maxItemsInMainAxis ||
-            remaining - nextMainAxisSize < 0
-        ) {
+        wrapInfo = buildingBlocks.getWrapInfo(
+            nextItemHasNext = index + 2 < children.size,
+            nextIndexInLine = (index + 1) - lastBreak,
+            leftOver = IntIntPair(remaining, Constraints.Infinity),
+            nextSize = if (nextChild == null) {
+                null
+            } else {
+                IntIntPair(nextMainAxisSize, nextCrossAxisSize)
+            },
+            lineIndex = lineIndex,
+            totalCrossAxisSize = totalCrossAxisSize,
+            currentLineCrossAxisSize = currentCrossAxisSize,
+            isWrappingRound = false,
+            isEllipsisWrap = false
+        )
+        if (wrapInfo.isLastItemInLine) {
             totalCrossAxisSize += currentCrossAxisSize + crossAxisSpacing
+            val ellipsisWrapInfo = buildingBlocks.getWrapEllipsisInfo(
+                wrapInfo,
+                hasNext = nextChild != null,
+                leftOverMainAxis = remaining,
+                lastContentLineIndex = lineIndex,
+                totalCrossAxisSize = totalCrossAxisSize,
+                nextIndexInLine = (index + 1) - lastBreak,
+            )
             currentCrossAxisSize = 0
             remaining = mainAxisAvailable
             lastBreak = index + 1
             nextMainAxisSize -= mainAxisSpacing
+            lineIndex++
+            if (wrapInfo.isLastItemInContainer) {
+                ellipsisWrapInfo?.ellipsisSize?.let {
+                    if (!ellipsisWrapInfo.placeEllipsisOnLastContentLine) {
+                        totalCrossAxisSize += it.second + crossAxisSpacing
+                    }
+                }
+                break
+            }
         }
     }
     // remove the last spacing for the last row or column
     totalCrossAxisSize -= crossAxisSpacing
-    return totalCrossAxisSize
+    return IntIntPair(totalCrossAxisSize, noOfItemsShown)
 }
 
 /**
@@ -705,19 +1033,38 @@
  * it moves to the next "line" and moves the next batch of items to a new list of items
  */
 internal fun MeasureScope.breakDownItems(
-    measureHelper: RowColumnMeasurementHelper,
     orientation: LayoutOrientation,
-    constraints: OrientationIndependentConstraints,
+    horizontalArrangement: Arrangement.Horizontal,
+    verticalArrangement: Arrangement.Vertical,
+    sizeMode: SizeMode,
+    crossAxisAlignment: CrossAxisAlignment,
+    measurablesIterator: Iterator<Measurable>,
+    constraints: Constraints,
     maxItemsInMainAxis: Int,
-): FlowResult {
+    maxLines: Int,
+    overflow: FlowLayoutOverflowState,
+): MeasureResult {
     val items = mutableVectorOf<RowColumnMeasureHelperResult>()
-    val mainAxisMax = constraints.mainAxisMax
-    val mainAxisMin = constraints.mainAxisMin
-    val crossAxisMax = constraints.crossAxisMax
-    val measurables = measureHelper.measurables
-    val placeables = measureHelper.placeables
+    val independentConstraints = OrientationIndependentConstraints(constraints, orientation)
+    val mainAxisMax = independentConstraints.mainAxisMax
+    val mainAxisMin = independentConstraints.mainAxisMin
+    val crossAxisMax = independentConstraints.crossAxisMax
+    val placeables = mutableIntObjectMapOf<Placeable?>()
+    val measurables = mutableListOf<Measurable>()
 
-    val spacing = ceil(measureHelper.arrangementSpacing.toPx()).toInt()
+    val mainAxisSpacingDp = if (orientation == LayoutOrientation.Horizontal) {
+        horizontalArrangement.spacing
+    } else {
+        verticalArrangement.spacing
+    }
+    val crossAxisSpacingDp = if (orientation == LayoutOrientation.Horizontal) {
+        verticalArrangement.spacing
+    } else {
+        horizontalArrangement.spacing
+    }
+
+    val spacing = ceil(mainAxisSpacingDp.toPx()).toInt()
+    val crossAxisSpacing = ceil(crossAxisSpacingDp.toPx()).toInt()
     val subsetConstraints = OrientationIndependentConstraints(
         mainAxisMin,
         mainAxisMax,
@@ -725,52 +1072,138 @@
         crossAxisMax
     )
     // nextSize of the list, pre-calculated
-    var nextSize: Pair<Int, Int>? = measurables.getOrNull(0)?.measureAndCache(
-        subsetConstraints, orientation
-    ) { placeable ->
-        placeables[0] = placeable
+    var index = 0
+    var measurable: Measurable?
+    var nextSize = measurablesIterator.hasNext().run {
+        measurable = if (!this) null else measurablesIterator.safeNext()
+        measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
+            placeables[0] = placeable
+        }
     }
     var nextMainAxisSize: Int? = nextSize?.first
     var nextCrossAxisSize: Int? = nextSize?.second
 
     var startBreakLineIndex = 0
-    val endBreakLineList = arrayOfNulls<Int>(measurables.size)
-    val crossAxisSizes = arrayOfNulls<Int>(measurables.size)
-    var endBreakLineIndex = 0
+    val endBreakLineList = mutableIntListOf()
+    val crossAxisSizes = mutableIntListOf()
+    var lineIndex = 0
 
     var leftOver = mainAxisMax
+    var leftOverCrossAxis = crossAxisMax
+    val buildingBlocks = FlowLayoutBuildingBlocks(
+        maxItemsInMainAxis = maxItemsInMainAxis,
+        mainAxisSpacing = spacing,
+        crossAxisSpacing = crossAxisSpacing,
+        constraints = independentConstraints,
+        maxLines = maxLines,
+        overflow = overflow
+    )
+    var ellipsisWrapInfo: FlowLayoutBuildingBlocks.WrapEllipsisInfo? = null
+    var wrapInfo = buildingBlocks.getWrapInfo(
+        nextItemHasNext = measurablesIterator.hasNext(),
+        leftOver = IntIntPair(leftOver, leftOverCrossAxis),
+        totalCrossAxisSize = 0,
+        nextSize = nextSize,
+        currentLineCrossAxisSize = 0,
+        nextIndexInLine = 0,
+        isWrappingRound = false,
+        isEllipsisWrap = false,
+        lineIndex = 0
+    ).also { wrapInfo ->
+        if (wrapInfo.isLastItemInContainer) {
+            ellipsisWrapInfo = buildingBlocks.getWrapEllipsisInfo(
+                wrapInfo,
+                nextSize != null,
+                lastContentLineIndex = -1,
+                totalCrossAxisSize = 0,
+                leftOver,
+                nextIndexInLine = 0
+            )
+        }
+    }
+
     // figure out the mainAxisTotalSize which will be minMainAxis when measuring the row/column
     var mainAxisTotalSize = mainAxisMin
+    var crossAxisTotalSize = 0
     var currentLineMainAxisSize = 0
     var currentLineCrossAxisSize = 0
-    for (index in measurables.indices) {
+    while (!wrapInfo.isLastItemInContainer && measurable != null) {
         val itemMainAxisSize = nextMainAxisSize!!
         val itemCrossAxisSize = nextCrossAxisSize!!
         currentLineMainAxisSize += itemMainAxisSize
         currentLineCrossAxisSize = maxOf(currentLineCrossAxisSize, itemCrossAxisSize)
         leftOver -= itemMainAxisSize
-        nextSize = measurables.getOrNull(index + 1)?.measureAndCache(
-            subsetConstraints, orientation
-        ) { placeable ->
-            placeables[index + 1] = placeable
+        overflow.itemShown = index + 1
+        measurables.add(measurable!!)
+        nextSize = measurablesIterator.hasNext().run {
+            measurable = if (!this) null else measurablesIterator.safeNext()
+            measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
+                placeables[index + 1] = placeable
+            }
         }
         nextMainAxisSize = nextSize?.first?.plus(spacing)
-        nextCrossAxisSize = nextSize?.second ?: 0
-        if (index + 1 >= measurables.size ||
-            (index + 1) - startBreakLineIndex >= maxItemsInMainAxis ||
-            leftOver - (nextMainAxisSize ?: 0) < 0
-        ) {
+        nextCrossAxisSize = nextSize?.second
+
+        wrapInfo = buildingBlocks.getWrapInfo(
+            nextItemHasNext = measurablesIterator.hasNext(),
+            leftOver = IntIntPair(leftOver, leftOverCrossAxis),
+            totalCrossAxisSize = crossAxisTotalSize,
+            nextSize = if (nextSize == null) null else
+                IntIntPair(nextMainAxisSize!!, nextCrossAxisSize!!),
+            currentLineCrossAxisSize = currentLineCrossAxisSize,
+            nextIndexInLine = (index + 1) - startBreakLineIndex,
+            isWrappingRound = false,
+            isEllipsisWrap = false,
+            lineIndex = lineIndex
+        )
+        if (wrapInfo.isLastItemInLine) {
             mainAxisTotalSize = maxOf(mainAxisTotalSize, currentLineMainAxisSize)
             mainAxisTotalSize = minOf(mainAxisTotalSize, mainAxisMax)
+            crossAxisTotalSize += currentLineCrossAxisSize
+            ellipsisWrapInfo = buildingBlocks.getWrapEllipsisInfo(
+                wrapInfo,
+                nextSize != null,
+                lastContentLineIndex = lineIndex,
+                totalCrossAxisSize = crossAxisTotalSize,
+                leftOver,
+                (index + 1) - startBreakLineIndex
+            )
+            crossAxisSizes.add(currentLineCrossAxisSize)
+            leftOver = mainAxisMax
+            leftOverCrossAxis = crossAxisMax - crossAxisTotalSize - crossAxisSpacing
             startBreakLineIndex = index + 1
-            endBreakLineList[endBreakLineIndex] = index + 1
-            crossAxisSizes[endBreakLineIndex] = currentLineCrossAxisSize
-            endBreakLineIndex++
+            endBreakLineList.add(index + 1)
             currentLineMainAxisSize = 0
             currentLineCrossAxisSize = 0
-            leftOver = mainAxisMax
             // only add spacing for next items in the row or column, not the starting indexes
             nextMainAxisSize = nextMainAxisSize?.minus(spacing)
+            lineIndex++
+            crossAxisTotalSize += crossAxisSpacing
+        }
+        index++
+    }
+
+    val measureHelper = RowColumnMeasurementHelper(
+        orientation,
+        horizontalArrangement,
+        verticalArrangement,
+        sizeMode,
+        crossAxisAlignment,
+        RowColumnMeasurablesWrapper(
+            measurables,
+            placeables,
+            ellipsisWrapInfo?.ellipsis,
+            ellipsisWrapInfo?.placeEllipsisOnLastContentLine,
+        ),
+    )
+
+    ellipsisWrapInfo?.let {
+        lineIndex = endBreakLineList.lastIndex
+        if (it.placeEllipsisOnLastContentLine) {
+            val lastLineCrossAxis = crossAxisSizes[lineIndex]
+            crossAxisSizes[lineIndex] = max(lastLineCrossAxis, it.ellipsisSize.second)
+        } else {
+            crossAxisSizes.add(it.ellipsisSize.second)
         }
     }
 
@@ -778,19 +1211,17 @@
         mainAxisMin = mainAxisTotalSize
     )
 
-    startBreakLineIndex = 0
-    var crossAxisTotalSize = 0
-
-    endBreakLineIndex = 0
-    var endIndex = endBreakLineList.getOrNull(endBreakLineIndex)
-    while (endIndex != null) {
-        var crossAxisSize = crossAxisSizes[endBreakLineIndex]
+    crossAxisTotalSize = 0
+    measureHelper.listWrapper.forEachLine(
+        endBreakLineList
+    ) { currentLineIndex, startIndex, endIndex ->
+        val crossAxisSize = crossAxisSizes[currentLineIndex]
         val result = measureHelper.measureWithoutPlacing(
             this,
             subsetBoxConstraints.copy(
-                crossAxisMax = crossAxisSize!!
+                crossAxisMax = crossAxisSize
             ).toBoxConstraints(orientation),
-            startBreakLineIndex,
+            startIndex,
             endIndex
         )
         crossAxisTotalSize += result.crossAxisSize
@@ -798,28 +1229,41 @@
         items.add(
             result
         )
-        startBreakLineIndex = endIndex
-        endBreakLineIndex++
-        endIndex = endBreakLineList.getOrNull(endBreakLineIndex)
     }
 
-    crossAxisTotalSize = maxOf(crossAxisTotalSize, constraints.crossAxisMin)
-    mainAxisTotalSize = maxOf(mainAxisTotalSize, constraints.mainAxisMin)
-    return FlowResult(
+    crossAxisTotalSize = maxOf(crossAxisTotalSize, independentConstraints.crossAxisMin)
+    mainAxisTotalSize = maxOf(mainAxisTotalSize, independentConstraints.mainAxisMin)
+    val flowResult = FlowResult(
         mainAxisTotalSize,
         crossAxisTotalSize,
         items,
     )
+
+    if (flowResult.items.isEmpty()) {
+        return layout(
+        constraints.constrainWidth(0),
+        constraints.constrainHeight(0)) {}
+    }
+
+    return handleFlowResult(flowResult, constraints, measureHelper)
 }
 
-internal fun Measurable.mainAxisMin(orientation: LayoutOrientation, crossAxisSize: Int) =
+private fun Iterator<Measurable>.safeNext(): Measurable? {
+    return try {
+        next()
+    } catch (e: ArrayIndexOutOfBoundsException) {
+        null
+    }
+}
+
+internal fun IntrinsicMeasurable.mainAxisMin(orientation: LayoutOrientation, crossAxisSize: Int) =
     if (orientation == LayoutOrientation.Horizontal) {
         minIntrinsicWidth(crossAxisSize)
     } else {
         minIntrinsicHeight(crossAxisSize)
     }
 
-internal fun Measurable.crossAxisMin(orientation: LayoutOrientation, mainAxisSize: Int) =
+internal fun IntrinsicMeasurable.crossAxisMin(orientation: LayoutOrientation, mainAxisSize: Int) =
     if (orientation == LayoutOrientation.Horizontal) {
         minIntrinsicHeight(mainAxisSize)
     } else {
@@ -827,13 +1271,13 @@
     }
 
 internal fun Placeable.mainAxisSize(orientation: LayoutOrientation) =
-    if (orientation == LayoutOrientation.Horizontal) width else height
+    if (orientation == LayoutOrientation.Horizontal) measuredWidth else measuredHeight
 
 internal fun Placeable.crossAxisSize(orientation: LayoutOrientation) =
-    if (orientation == LayoutOrientation.Horizontal) height else width
+    if (orientation == LayoutOrientation.Horizontal) measuredHeight else measuredWidth
 
-private val CROSS_AXIS_ALIGNMENT_TOP = CrossAxisAlignment.vertical(Alignment.Top)
-private val CROSS_AXIS_ALIGNMENT_START = CrossAxisAlignment.horizontal(Alignment.Start)
+internal val CROSS_AXIS_ALIGNMENT_TOP = CrossAxisAlignment.vertical(Alignment.Top)
+internal val CROSS_AXIS_ALIGNMENT_START = CrossAxisAlignment.horizontal(Alignment.Start)
 
 // We measure and cache to improve performance dramatically, instead of using intrinsics
 // This only works so far for fixed size items.
@@ -844,8 +1288,8 @@
     constraints: OrientationIndependentConstraints,
     orientation: LayoutOrientation,
     storePlaceable: (Placeable?) -> Unit
-): Pair<Int, Int> {
-    val itemSize: Pair<Int, Int> = if (
+): IntIntPair {
+    return if (
         rowColumnParentData.weight == 0f &&
         rowColumnParentData?.flowLayoutData?.fillCrossAxisFraction == null
     ) {
@@ -857,13 +1301,76 @@
         ).also(storePlaceable)
         val mainAxis = placeable.mainAxisSize(orientation)
         val crossAxis = placeable.crossAxisSize(orientation)
-        Pair(mainAxis, crossAxis)
+        IntIntPair(mainAxis, crossAxis)
     } else {
         val mainAxis = mainAxisMin(orientation, Constraints.Infinity)
         val crossAxis = crossAxisMin(orientation, mainAxis)
-        Pair(mainAxis, crossAxis)
+        IntIntPair(mainAxis, crossAxis)
     }
-    return itemSize
+}
+
+internal fun MeasureScope.handleFlowResult(
+    flowResult: FlowResult,
+    constraints: Constraints,
+    measureHelper: RowColumnMeasurementHelper
+): MeasureResult {
+    val orientation = measureHelper.orientation
+    val verticalArrangement = measureHelper.verticalArrangement
+    val horizontalArrangement = measureHelper.horizontalArrangement
+    val items = flowResult.items
+    val crossAxisSizes = IntArray(items.size) { index ->
+        items[index].crossAxisSize
+    }
+    // space in between children, except for the last child
+    val outPosition = IntArray(crossAxisSizes.size)
+    var totalCrossAxisSize = flowResult.crossAxisTotalSize
+    // cross axis arrangement
+    if (orientation == LayoutOrientation.Horizontal) {
+        with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
+            val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
+            totalCrossAxisSize += totalCrossAxisSpacing
+            arrange(
+                totalCrossAxisSize,
+                crossAxisSizes,
+                outPosition
+            )
+        }
+    } else {
+        with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
+            val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
+            totalCrossAxisSize += totalCrossAxisSpacing
+            arrange(
+                totalCrossAxisSize,
+                crossAxisSizes,
+                layoutDirection,
+                outPosition
+            )
+        }
+    }
+
+    var layoutWidth: Int
+    var layoutHeight: Int
+    if (orientation == LayoutOrientation.Horizontal) {
+        layoutWidth = flowResult.mainAxisTotalSize
+        layoutHeight = totalCrossAxisSize
+    } else {
+        layoutWidth = totalCrossAxisSize
+        layoutHeight = flowResult.mainAxisTotalSize
+    }
+    layoutWidth = constraints.constrainWidth(layoutWidth)
+    layoutHeight = constraints.constrainHeight(layoutHeight)
+
+    return layout(layoutWidth, layoutHeight) {
+        flowResult.items.forEachIndexed { currentRowOrColumnIndex,
+            measureResult ->
+            measureHelper.placeHelper(
+                this,
+                measureResult,
+                outPosition[currentRowOrColumnIndex],
+                [email protected]
+            )
+        }
+    }
 }
 
 /**
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt
new file mode 100644
index 0000000..3dfa4ecc
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 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.
+ */
+package androidx.compose.foundation.layout
+
+import androidx.collection.IntIntPair
+import androidx.compose.ui.layout.Measurable
+import kotlin.math.max
+
+@OptIn(ExperimentalLayoutApi::class)
+internal class FlowLayoutBuildingBlocks(
+    private val maxItemsInMainAxis: Int,
+    private val overflow: FlowLayoutOverflowState,
+    private val constraints: OrientationIndependentConstraints,
+    private val maxLines: Int,
+    private val mainAxisSpacing: Int,
+    private val crossAxisSpacing: Int,
+) {
+    class WrapInfo(
+        val isLastItemInLine: Boolean = false,
+        val isLastItemInContainer: Boolean = false
+    )
+
+    class WrapEllipsisInfo(
+        val ellipsis: Measurable,
+        val ellipsisSize: IntIntPair,
+        val placeEllipsisOnLastContentLine: Boolean,
+    )
+
+    fun getWrapEllipsisInfo(
+        wrapInfo: WrapInfo,
+        hasNext: Boolean,
+        lastContentLineIndex: Int,
+        totalCrossAxisSize: Int,
+        leftOverMainAxis: Int,
+        nextIndexInLine: Int
+    ): WrapEllipsisInfo? {
+        if (!wrapInfo.isLastItemInContainer) return null
+
+        val ellipsis = overflow.ellipsis(
+            hasNext,
+            lastContentLineIndex,
+            getFinalMeasurable = true,
+            totalCrossAxisSize
+        ) ?: return null
+
+        val ellipsisSize = ellipsis.let {
+            overflow.ellipsisSize(
+                hasNext,
+                lastContentLineIndex,
+                totalCrossAxisSize
+            )
+        } ?: return null
+
+        val canFitLine = lastContentLineIndex >= 0 && (nextIndexInLine == 0 ||
+            !(leftOverMainAxis - ellipsisSize.first < 0 || nextIndexInLine >= maxItemsInMainAxis))
+
+        return WrapEllipsisInfo(
+            ellipsis,
+            ellipsisSize,
+            canFitLine
+        )
+    }
+
+    fun getWrapInfo(
+        nextItemHasNext: Boolean,
+        nextIndexInLine: Int,
+        leftOver: IntIntPair,
+        nextSize: IntIntPair?,
+        lineIndex: Int,
+        totalCrossAxisSize: Int,
+        currentLineCrossAxisSize: Int,
+        isWrappingRound: Boolean,
+        isEllipsisWrap: Boolean,
+    ): WrapInfo {
+        var totalContainerCrossAxisSize = totalCrossAxisSize + currentLineCrossAxisSize
+        if (nextSize == null) {
+            return WrapInfo(
+                isLastItemInLine = true,
+                isLastItemInContainer = true
+            )
+        }
+
+        val willOverflowCrossAxis = when {
+            overflow.type == FlowLayoutOverflow.OverflowType.Visible -> false
+            lineIndex >= maxLines -> true
+            leftOver.second - nextSize.second < 0 -> true
+            else -> false
+        }
+
+        if (willOverflowCrossAxis) {
+            return WrapInfo(
+                isLastItemInLine = true,
+                isLastItemInContainer = true
+            )
+        }
+
+        val shouldWrapItem = when {
+            nextIndexInLine == 0 -> false
+            nextIndexInLine >= maxItemsInMainAxis -> true
+            leftOver.first - nextSize.first < 0 -> true
+            else -> false
+        }
+
+        if (shouldWrapItem) {
+            if (isWrappingRound) {
+                return WrapInfo(
+                    isLastItemInLine = true,
+                    isLastItemInContainer = true
+                )
+            }
+            val wrapInfo = getWrapInfo(
+                nextItemHasNext,
+                nextIndexInLine = 0,
+                leftOver = IntIntPair(
+                    constraints.mainAxisMax,
+                    leftOver.second -
+                    crossAxisSpacing -
+                    currentLineCrossAxisSize
+                ),
+                // remove the mainAxisSpacing added to 2nd position or more indexed items.
+                IntIntPair(
+                    first = nextSize.first.minus(mainAxisSpacing),
+                    second = nextSize.second
+                ),
+                lineIndex = lineIndex + 1,
+                totalCrossAxisSize = totalContainerCrossAxisSize,
+                currentLineCrossAxisSize = 0,
+                isWrappingRound = true,
+                isEllipsisWrap = false
+            )
+            return WrapInfo(
+                isLastItemInLine = true,
+                isLastItemInContainer = wrapInfo.isLastItemInContainer
+            )
+        }
+
+        totalContainerCrossAxisSize = totalCrossAxisSize + max(
+            currentLineCrossAxisSize,
+            nextSize.second)
+
+        val ellipsis = if (isEllipsisWrap) {
+            null
+        } else {
+            overflow.ellipsisSize(
+                nextItemHasNext,
+                lineIndex,
+                totalContainerCrossAxisSize
+            )
+        }
+        val shouldWrapEllipsis = ellipsis?.run {
+            when {
+                nextIndexInLine + 1 >= maxItemsInMainAxis -> true
+                leftOver.first - nextSize.first - mainAxisSpacing - ellipsis.first < 0 -> true
+                else -> false
+            }
+        } ?: false
+
+        if (shouldWrapEllipsis) {
+            if (isEllipsisWrap) {
+                return WrapInfo(
+                    isLastItemInLine = true,
+                    isLastItemInContainer = true
+                )
+            }
+            val wrapInfo = getWrapInfo(
+                nextItemHasNext = false,
+                nextIndexInLine = 0,
+                IntIntPair(
+                    constraints.mainAxisMax,
+                    leftOver.second -
+                    crossAxisSpacing - max(currentLineCrossAxisSize, nextSize.second)
+                ),
+                ellipsis,
+                lineIndex = lineIndex + 1,
+                totalCrossAxisSize = totalContainerCrossAxisSize,
+                currentLineCrossAxisSize = 0,
+                isWrappingRound = true,
+                isEllipsisWrap = true
+            )
+
+            return WrapInfo(
+                isLastItemInLine = wrapInfo.isLastItemInContainer,
+                isLastItemInContainer = wrapInfo.isLastItemInContainer
+            )
+        }
+
+        return WrapInfo(
+            isLastItemInLine = false,
+            isLastItemInContainer = false
+        )
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt
new file mode 100644
index 0000000..8d19b8d
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt
@@ -0,0 +1,843 @@
+/*
+ * Copyright 2023 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.compose.foundation.layout
+
+import androidx.collection.IntIntPair
+import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Clip
+import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Visible
+import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Clip
+import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Visible
+import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandIndicator
+import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandOrCollapseIndicator
+import androidx.compose.foundation.layout.FlowRowOverflow.Companion.Clip
+import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandIndicator
+import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandOrCollapseIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Overflow Handling Options
+ *
+ * This enumeration defines the available options for handling content that exceeds
+ * the boundaries of its container for [FlowRow].
+ *
+ * Options:
+ *
+ * - [Visible]: The overflowing content remains visible outside its container.
+ * This can lead to overlapping with other elements. Use this option when it's crucial to
+ * display all content, regardless of the container's size.
+ *
+ * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of
+ * its container. Ideal for maintaining a clean and uncluttered UI,
+ * where overlapping elements are undesirable.
+ *
+ * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button
+ * indicating that more items can be loaded.
+ *
+ * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality
+ * by adding a 'Collapse' option. After expanding the content, users can
+ * choose to collapse it back to the summary view.
+ */
+@ExperimentalLayoutApi
+class FlowRowOverflow private constructor(
+    type: OverflowType,
+    minLinesToShowCollapse: Int = 0,
+    minCrossAxisSizeToShowCollapse: Int = 0,
+    seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
+    collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
+) : FlowLayoutOverflow(
+    type,
+    minLinesToShowCollapse,
+    minCrossAxisSizeToShowCollapse,
+    seeMoreGetter,
+    collapseGetter
+) {
+
+    companion object {
+        /**
+         * Display all content, even if there is not enough space in the specified bounds.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Visible = FlowRowOverflow(OverflowType.Visible)
+
+        /**
+         * Clip the overflowing content to fix its container.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Clip = FlowRowOverflow(OverflowType.Clip)
+
+        /**
+         * Registers an "expand indicator" composable for handling overflow in a [FlowRow].
+         *
+         * This function allows the creation of a custom composable that can be displayed
+         * when there are more items in the [FlowRow] than can be displayed in the available
+         * space. The "expandable indicator" composable can be tailored to show a summary, a button,
+         * or any other composable element that indicates the presence of additional items.
+         *
+         * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesWithSeeMore
+         *
+         * @param content composable that visually indicates more items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        fun expandIndicator(
+            content: @Composable FlowRowOverflowScope.() -> Unit
+        ): FlowRowOverflow {
+            val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                @Composable {
+                    val scope = FlowRowOverflowScopeImpl(state)
+                    scope.content()
+                }
+            }
+            return FlowRowOverflow(
+                OverflowType.ExpandIndicator,
+                seeMoreGetter = seeMoreGetter
+            )
+        }
+
+        /**
+         * Registers an "expand or collapse indicator" composable for handling
+         * overflow in a [FlowRow].
+         *
+         * This function is designed to switch between two states: a "Expandable" state when there
+         * are more items to show, and a "Collapsable" state when all items are being displayed and
+         * can be collapsed.
+         *
+         * Prior to layout, the function evaluates both composables indicators to determine
+         * their maximum intrinsic size. Depending on the space available and the number
+         * of items, either the [expandIndicator] or the [collapseIndicator] is rendered.
+         *
+         * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesDynamicSeeMore
+         *
+         * @param minRowsToShowCollapse Specifies the minimum number of rows that
+         *        should be visible before showing the collapse option. This parameter is useful
+         *        when the number of rows is too small to be reduced further.
+         *
+         * @param minHeightToShowCollapse Specifies the minimum height at
+         *        which the collapse option should be displayed.
+         *        This parameter is useful for preventing the collapse
+         *        option from appearing when the height is too narrow.
+         *
+         * @param expandIndicator composable that visually indicates more items can be loaded.
+         * @param collapseIndicator composable that visually indicates less items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        @Composable
+        fun expandOrCollapseIndicator(
+            expandIndicator: @Composable FlowRowOverflowScope.() -> Unit,
+            collapseIndicator: @Composable FlowRowOverflowScope.() -> Unit,
+            minRowsToShowCollapse: Int = 1,
+            minHeightToShowCollapse: Dp = 0.dp,
+        ): FlowRowOverflow {
+            val minHeightToShowCollapsePx = with(LocalDensity.current) {
+                minHeightToShowCollapse.roundToPx()
+            }
+            return remember(
+                minRowsToShowCollapse,
+                minHeightToShowCollapsePx,
+                expandIndicator,
+                collapseIndicator
+            ) {
+                val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = FlowRowOverflowScopeImpl(state)
+                        scope.expandIndicator()
+                    }
+                }
+
+                val collapseGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = FlowRowOverflowScopeImpl(state)
+                        scope.collapseIndicator()
+                    }
+                }
+
+                FlowRowOverflow(
+                    OverflowType.ExpandOrCollapseIndicator,
+                    minLinesToShowCollapse = minRowsToShowCollapse,
+                    minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
+                    seeMoreGetter = seeMoreGetter,
+                    collapseGetter = collapseGetter
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Overflow Handling Options
+ *
+ * This enumeration defines the available options for handling content that exceeds
+ * the boundaries of its container for [FlowColumn] and [ContextualFlowColumn].
+ *
+ * Options:
+ *
+ * - [Visible]: The overflowing content remains visible outside its container.
+ * This can lead to overlapping with other elements. Use this option when it's crucial to
+ * display all content, regardless of the container's size.
+ *
+ * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of
+ * its container. Ideal for maintaining a clean and uncluttered UI,
+ * where overlapping elements are undesirable.
+ *
+ * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button
+ * indicating that more items can be loaded.
+ *
+ * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality
+ * by adding a 'Collapse' option. After expanding the content, users can
+ * choose to collapse it back to the summary view.
+ */
+@ExperimentalLayoutApi
+class FlowColumnOverflow private constructor(
+    type: OverflowType,
+    minLinesToShowCollapse: Int = 0,
+    minCrossAxisSizeToShowCollapse: Int = 0,
+    seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
+    collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
+) : FlowLayoutOverflow(
+    type,
+    minLinesToShowCollapse,
+    minCrossAxisSizeToShowCollapse,
+    seeMoreGetter,
+    collapseGetter
+) {
+    companion object {
+        /**
+         * Display all content, even if there is not enough space in the specified bounds.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Visible = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
+
+        /**
+         * Clip the overflowing content to fix its container.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Clip = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
+
+        /**
+         * Registers an "expand indicator" composable for handling overflow in a [FlowColumn].
+         *
+         * This function allows the creation of a custom composable that can be displayed
+         * when there are more items in the [FlowColumn] than can be displayed in the available
+         * space. The "expandable indicator" composable can be tailored to show a summary, a button,
+         * or any other composable element that indicates the presence of additional items.
+         *
+         * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesWithSeeMore
+         *
+         * @param content composable that visually indicates more items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        fun expandIndicator(
+            content: @Composable FlowColumnOverflowScope.() -> Unit
+        ): FlowColumnOverflow {
+            val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                @Composable {
+                    val scope = FlowColumnOverflowScopeImpl(state)
+                    scope.content()
+                }
+            }
+            return FlowColumnOverflow(
+                OverflowType.ExpandIndicator,
+                seeMoreGetter = seeMoreGetter
+            )
+        }
+
+        /**
+         * Registers an "expand or collapse indicator" composable for handling
+         * overflow in a [FlowColumn].
+         *
+         * This function is designed to switch between two states: a "Expandable" state when there
+         * are more items to show, and a "Collapsable" state when all items are being displayed and
+         * can be collapsed.
+         *
+         * Prior to layout, the function evaluates both composables indicators to determine
+         * their maximum intrinsic size. Depending on the space available and the number
+         * of items, either the [expandIndicator] or the [collapseIndicator] is rendered.
+         *
+         * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesDynamicSeeMore
+         *
+         * @param minColumnsToShowCollapse Specifies the minimum number of columns that
+         *        should be visible before showing the collapse option. This parameter is useful
+         *        when the number of columns is too small to be reduced further.
+         *
+         * @param minWidthToShowCollapse Specifies the minimum width at which the collapse
+         *        option should be displayed. This parameter is useful for preventing the collapse
+         *        option from appearing when the width is too narrow.
+         *
+         * @param expandIndicator composable that visually indicates more items can be loaded.
+         * @param collapseIndicator composable that visually indicates less items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        @Composable
+        fun expandOrCollapseIndicator(
+            expandIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
+            collapseIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
+            minColumnsToShowCollapse: Int = 1,
+            minWidthToShowCollapse: Dp = 0.dp,
+        ): FlowColumnOverflow {
+            val minWidthToShowCollapsePx = with(LocalDensity.current) {
+                minWidthToShowCollapse.roundToPx()
+            }
+            return remember(
+                minColumnsToShowCollapse,
+                minWidthToShowCollapsePx,
+                expandIndicator,
+                collapseIndicator
+            ) {
+                val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = FlowColumnOverflowScopeImpl(state)
+                        scope.expandIndicator()
+                    }
+                }
+
+                val collapseGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = FlowColumnOverflowScopeImpl(state)
+                        scope.collapseIndicator()
+                    }
+                }
+
+                FlowColumnOverflow(
+                    OverflowType.ExpandOrCollapseIndicator,
+                    minLinesToShowCollapse = minColumnsToShowCollapse,
+                    minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
+                    seeMoreGetter = seeMoreGetter,
+                    collapseGetter = collapseGetter
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Overflow Handling Options
+ *
+ * This enumeration defines the available options for handling content that exceeds
+ * the boundaries of its container for [ContextualFlowRow].
+ *
+ *
+ * Options:
+ *
+ * - [Visible]: The overflowing content remains visible outside its container.
+ * This can lead to overlapping with other elements. Use this option when it's crucial to
+ * display all content, regardless of the container's size.
+ *
+ * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of
+ * its container. Ideal for maintaining a clean and uncluttered UI,
+ * where overlapping elements are undesirable.
+ *
+ * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button
+ * indicating that more items can be loaded.
+ *
+ * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality
+ * by adding a 'Collapse' option. After expanding the content, users can
+ * choose to collapse it back to the summary view.
+ */
+@ExperimentalLayoutApi
+class ContextualFlowRowOverflow private constructor(
+    type: OverflowType,
+    minLinesToShowCollapse: Int = 0,
+    minCrossAxisSizeToShowCollapse: Int = 0,
+    seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
+    collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
+) : FlowLayoutOverflow(
+    type,
+    minLinesToShowCollapse,
+    minCrossAxisSizeToShowCollapse,
+    seeMoreGetter,
+    collapseGetter
+) {
+
+    companion object {
+        /**
+         * Display all content, even if there is not enough space in the specified bounds.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Visible = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Visible)
+
+        /**
+         * Clip the overflowing content to fix its container.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Clip = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Clip)
+
+        /**
+         * Registers an "expand indicator" composable for handling overflow
+         * in a [ContextualFlowRow].
+         *
+         * This function allows the creation of a custom composable that can be displayed
+         * when there are more items in the [ContextualFlowRow] than can be displayed
+         * in the available space. The "expandable indicator" composable can be tailored to
+         * show a summary, a button, or any other composable element that indicates
+         * the presence of additional items.
+         *
+         * @param content composable that visually indicates more items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        fun expandIndicator(
+            content: @Composable ContextualFlowRowOverflowScope.() -> Unit
+        ): ContextualFlowRowOverflow {
+            val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                @Composable {
+                    val scope = ContextualFlowRowOverflowScopeImpl(state)
+                    scope.content()
+                }
+            }
+            return ContextualFlowRowOverflow(
+                OverflowType.ExpandIndicator,
+                seeMoreGetter = seeMoreGetter
+            )
+        }
+
+        /**
+         * Registers an "expand or collapse indicator" composable for handling
+         * overflow in a [ContextualFlowRow].
+         *
+         * This function is designed to switch between two states: a "Expandable" state when there
+         * are more items to show, and a "Collapsable" state when all items are being displayed and
+         * can be collapsed.
+         *
+         * Prior to layout, the function evaluates both composables indicators to determine
+         * their maximum intrinsic size. Depending on the space available and the number
+         * of items, either the [expandIndicator] or the [collapseIndicator] is rendered.
+         *
+         * @sample androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
+         *
+         * @param minRowsToShowCollapse Specifies the minimum number of rows that
+         *        should be visible before showing the collapse option. This parameter is useful
+         *        when the number of rows is too small to be reduced further.
+         *
+         * @param minHeightToShowCollapse Specifies the minimum height at
+         *        which the collapse option should be displayed.
+         *        This parameter is useful for preventing the collapse
+         *        option from appearing when the height is too narrow.
+         *
+         * @param expandIndicator composable that visually indicates more items can be loaded.
+         * @param collapseIndicator composable that visually indicates less items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        @Composable
+        fun expandOrCollapseIndicator(
+            expandIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
+            collapseIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
+            minRowsToShowCollapse: Int = 1,
+            minHeightToShowCollapse: Dp = 0.dp,
+        ): ContextualFlowRowOverflow {
+            val minHeightToShowCollapsePx = with(LocalDensity.current) {
+                minHeightToShowCollapse.roundToPx()
+            }
+            return remember(
+                minRowsToShowCollapse,
+                minHeightToShowCollapsePx,
+                expandIndicator,
+                collapseIndicator
+            ) {
+                val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = ContextualFlowRowOverflowScopeImpl(state)
+                        scope.expandIndicator()
+                    }
+                }
+
+                val collapseGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = ContextualFlowRowOverflowScopeImpl(state)
+                        scope.collapseIndicator()
+                    }
+                }
+
+                ContextualFlowRowOverflow(
+                    OverflowType.ExpandOrCollapseIndicator,
+                    minLinesToShowCollapse = minRowsToShowCollapse,
+                    minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
+                    seeMoreGetter = seeMoreGetter,
+                    collapseGetter = collapseGetter
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Overflow Handling Options
+ *
+ * This enumeration defines the available options for handling content that exceeds
+ * the boundaries of its container for [ContextualFlowColumn].
+ *
+ * Options:
+ *
+ * - [Visible]: The overflowing content remains visible outside its container.
+ * This can lead to overlapping with other elements. Use this option when it's crucial to
+ * display all content, regardless of the container's size.
+ *
+ * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of
+ * its container. Ideal for maintaining a clean and uncluttered UI,
+ * where overlapping elements are undesirable.
+ *
+ * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button
+ * indicating that more items can be loaded.
+ *
+ * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality
+ * by adding a 'Collapse' option. After expanding the content, users can
+ * choose to collapse it back to the summary view.
+ */
+@ExperimentalLayoutApi
+class ContextualFlowColumnOverflow private constructor(
+    type: OverflowType,
+    minLinesToShowCollapse: Int = 0,
+    minCrossAxisSizeToShowCollapse: Int = 0,
+    seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
+    collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
+) : FlowLayoutOverflow(
+    type,
+    minLinesToShowCollapse,
+    minCrossAxisSizeToShowCollapse,
+    seeMoreGetter,
+    collapseGetter
+) {
+
+    companion object {
+        /**
+         * Display all content, even if there is not enough space in the specified bounds.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Visible = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
+
+        /**
+         * Clip the overflowing content to fix its container.
+         */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalLayoutApi
+        @get:ExperimentalLayoutApi
+        val Clip = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
+
+        /**
+         * Registers an "expand indicator" composable for handling overflow
+         * in a [ContextualFlowColumn].
+         *
+         * This function allows the creation of a custom composable that can be displayed
+         * when there are more items in the [ContextualFlowColumn] than can be displayed
+         * in the available space. The "expandable indicator" composable can be tailored to
+         * show a summary, a button, or any other composable element that indicates
+         * the presence of additional items.
+         *
+         * @param content composable that visually indicates more items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        fun expandIndicator(
+            content: @Composable ContextualFlowColumnOverflowScope.() -> Unit
+        ): ContextualFlowColumnOverflow {
+            val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                @Composable {
+                    val scope = ContextualFlowColumnOverflowScopeImpl(state)
+                    scope.content()
+                }
+            }
+            return ContextualFlowColumnOverflow(
+                OverflowType.ExpandIndicator,
+                seeMoreGetter = seeMoreGetter
+            )
+        }
+
+        /**
+         * Registers an "expand or collapse indicator" composable for handling
+         * overflow in a [ContextualFlowColumn].
+         *
+         * This function is designed to switch between two states: a "Expandable" state when there
+         * are more items to show, and a "Collapsable" state when all items are being displayed and
+         * can be collapsed.
+         *
+         * Prior to layout, the function evaluates both composables indicators to determine
+         * their maximum intrinsic size. Depending on the space available and the number
+         * of items, either the [expandIndicator] or the [collapseIndicator] is rendered.
+         *
+         * @sample androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
+         *
+         * @param minColumnsToShowCollapse Specifies the minimum number of columns that
+         *        should be visible before showing the collapse option. This parameter is useful
+         *        when the number of columns is too small to be reduced further.
+         *
+         * @param minWidthToShowCollapse Specifies the minimum width at which the collapse
+         *        option should be displayed. This parameter is useful for preventing the collapse
+         *        option from appearing when the width is too narrow.
+         *
+         * @param expandIndicator composable that visually indicates more items can be loaded.
+         * @param collapseIndicator composable that visually indicates less items can be loaded.
+         */
+        @ExperimentalLayoutApi
+        @Composable
+        fun expandOrCollapseIndicator(
+            expandIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
+            collapseIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
+            minColumnsToShowCollapse: Int = 1,
+            minWidthToShowCollapse: Dp = 0.dp,
+        ): ContextualFlowColumnOverflow {
+            val minWidthToShowCollapsePx = with(LocalDensity.current) {
+                minWidthToShowCollapse.roundToPx()
+            }
+            return remember(
+                minColumnsToShowCollapse,
+                minWidthToShowCollapsePx,
+                expandIndicator,
+                collapseIndicator
+            ) {
+                val seeMoreGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = ContextualFlowColumnOverflowScopeImpl(state)
+                        scope.expandIndicator()
+                    }
+                }
+
+                val collapseGetter = { state: FlowLayoutOverflowState ->
+                    @Composable {
+                        val scope = ContextualFlowColumnOverflowScopeImpl(state)
+                        scope.collapseIndicator()
+                    }
+                }
+
+                ContextualFlowColumnOverflow(
+                    OverflowType.ExpandOrCollapseIndicator,
+                    minLinesToShowCollapse = minColumnsToShowCollapse,
+                    minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
+                    seeMoreGetter = seeMoreGetter,
+                    collapseGetter = collapseGetter
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Overflow Handling Options
+ *
+ * This enumeration defines the available options for handling content that exceeds
+ * the boundaries of its container.
+ *
+ * Please check out the children classes on ways to initialize a FlowLayout overflow
+ *
+ * @see [FlowRowOverflow]
+ * @see [FlowColumnOverflow]
+ * @see [ContextualFlowRowOverflow]
+ * @see [ContextualFlowColumnOverflow]
+ **/
+@ExperimentalLayoutApi
+sealed class FlowLayoutOverflow(
+    internal val type: OverflowType,
+    private val minLinesToShowCollapse: Int = 0,
+    private val minCrossAxisSizeToShowCollapse: Int = 0,
+    private val seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
+    private val collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
+) {
+    internal fun createOverflowState() =
+        FlowLayoutOverflowState(
+            type,
+            minLinesToShowCollapse,
+            minCrossAxisSizeToShowCollapse
+        )
+
+    internal fun addOverflowComposables(
+        state: FlowLayoutOverflowState,
+        list: MutableList<@Composable () -> Unit>
+    ) {
+        val expandIndicator = seeMoreGetter?.let { getter -> getter(state) }
+        val collapseIndicator = collapseGetter?.let { getter -> getter(state) }
+
+        when (type) {
+            OverflowType.ExpandIndicator -> expandIndicator?.let { list.add(expandIndicator) }
+            OverflowType.ExpandOrCollapseIndicator -> {
+                expandIndicator?.let { list.add(expandIndicator) }
+                collapseIndicator?.let { list.add(collapseIndicator) }
+            }
+            else -> {}
+        }
+    }
+
+    internal enum class OverflowType {
+        Visible,
+        Clip,
+        ExpandIndicator,
+        ExpandOrCollapseIndicator,
+    }
+}
+
+/** Overflow State for managing overflow state within FlowLayouts. */
+@OptIn(ExperimentalLayoutApi::class)
+internal data class FlowLayoutOverflowState internal constructor(
+    internal val type: FlowLayoutOverflow.OverflowType,
+    internal val minLinesToShowCollapse: Int,
+    internal val minCrossAxisSizeToShowCollapse: Int
+) {
+    internal val noOfItemsShown: Int
+        get() {
+            if (itemShown == -1) {
+                throw IllegalStateException(
+                    "Accessing noOfItemsShown before it is set. " +
+                        "Are you calling this in the Composition phase, " +
+                        "rather than in the draw phase? " +
+                        "Consider our samples on how to use it during the draw phase " +
+                        "or consider using ContextualFlowRow/ContextualFlowColumn " +
+                        "which initializes this method in the composition phase.")
+            }
+            return itemShown
+        }
+
+    internal var itemShown: Int = -1
+    internal var itemCount = 0
+    private var seeMoreMeasurable: Measurable? = null
+    private var collapseMeasurable: Measurable? = null
+    private var seeMoreSize: IntIntPair? = null
+    private var collapseSize: IntIntPair? = null
+    // for contextual flow row
+    private var getOverflowMeasurable: ((
+        isExpandable: Boolean,
+        noOfItemsShown: Int
+    ) -> Measurable?)? = null
+
+    internal fun ellipsisSize(
+        hasNext: Boolean,
+        lineIndex: Int,
+        totalCrossAxisSize: Int
+    ): IntIntPair? {
+        return when (type) {
+            FlowLayoutOverflow.OverflowType.Visible -> null
+            FlowLayoutOverflow.OverflowType.Clip -> null
+            FlowLayoutOverflow.OverflowType.ExpandIndicator -> if (hasNext) {
+                seeMoreSize
+            } else {
+                null
+            }
+            FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
+                if (hasNext) {
+                    seeMoreSize
+                } else if (lineIndex + 1 >= minLinesToShowCollapse &&
+                    totalCrossAxisSize >= minCrossAxisSizeToShowCollapse
+                ) { collapseSize } else { null }
+            }
+        }
+    }
+
+    internal fun ellipsis(
+        hasNext: Boolean,
+        lineIndex: Int,
+        getFinalMeasurable: Boolean,
+        totalCrossAxisSize: Int
+    ): Measurable? {
+        return when (type) {
+            FlowLayoutOverflow.OverflowType.Visible -> null
+            FlowLayoutOverflow.OverflowType.Clip -> null
+            FlowLayoutOverflow.OverflowType.ExpandIndicator,
+            FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
+                if (hasNext) {
+                    if (getFinalMeasurable) {
+                        getOverflowMeasurable?.invoke(
+                            /* isExpandable */ true,
+                            noOfItemsShown
+                        ) ?: seeMoreMeasurable
+                    } else {
+                        seeMoreMeasurable
+                    }
+                } else {
+                    if (lineIndex < (minLinesToShowCollapse - 1) ||
+                        totalCrossAxisSize < (minCrossAxisSizeToShowCollapse)
+                    ) {
+                        null
+                    } else {
+                        if (getFinalMeasurable) {
+                            getOverflowMeasurable?.invoke(
+                                /* isExpandable */ false,
+                                noOfItemsShown
+                            ) ?: collapseMeasurable
+                        } else {
+                            collapseMeasurable
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    internal fun setOverflowMeasurables(
+        seeMoreMeasurable: IntrinsicMeasurable?,
+        collapseMeasurable: IntrinsicMeasurable?,
+        orientation: LayoutOrientation,
+        constraints: Constraints,
+    ) {
+        val orientationIndependentConstraints =
+            OrientationIndependentConstraints(constraints, orientation)
+        seeMoreMeasurable?.let { item ->
+            val mainAxisSize = item.mainAxisMin(
+                orientation,
+                orientationIndependentConstraints.crossAxisMax
+            )
+            val crossAxisSize = item.crossAxisMin(orientation,
+                mainAxisSize
+            )
+            this.seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
+            this.seeMoreMeasurable = item as? Measurable
+        }
+        collapseMeasurable?.let { item ->
+            val mainAxisSize = item.mainAxisMin(
+                orientation,
+                orientationIndependentConstraints.crossAxisMax
+            )
+            val crossAxisSize = item.crossAxisMin(orientation, mainAxisSize)
+            this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
+            this.collapseMeasurable = item as? Measurable
+        }
+    }
+
+    internal fun setOverflowMeasurables(
+        orientation: LayoutOrientation,
+        constraints: Constraints,
+        getOverflowMeasurable: ((isExpandable: Boolean, numberOfItemsShown: Int) -> Measurable?)
+    ) {
+        this.itemShown = 0
+        this.getOverflowMeasurable = getOverflowMeasurable
+        val seeMoreMeasurable = getOverflowMeasurable(
+            /* isExpandable */ true, /* numberOfItemsShown */ 0
+        )
+        val collapseMeasurable = getOverflowMeasurable(
+            /* isExpandable */ false, /* numberOfItemsShown */ 0
+        )
+        setOverflowMeasurables(
+            seeMoreMeasurable,
+            collapseMeasurable,
+            orientation,
+            constraints
+        )
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index cf7d6d1..6a74dde 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -55,17 +55,14 @@
         measurables: List<Measurable>,
         constraints: Constraints
     ): MeasureResult {
-        val placeables = arrayOfNulls<Placeable?>(measurables.size)
         val rowColumnMeasureHelper =
             RowColumnMeasurementHelper(
                 orientation,
                 horizontalArrangement,
                 verticalArrangement,
-                arrangementSpacing,
                 crossAxisSize,
                 crossAxisAlignment,
-                measurables,
-                placeables
+                RowColumnMeasurablesWrapper(measurables)
             )
 
         val measureResult = rowColumnMeasureHelper
@@ -393,6 +390,9 @@
 internal val IntrinsicMeasurable.rowColumnParentData: RowColumnParentData?
     get() = parentData as? RowColumnParentData
 
+internal val Placeable.rowColumnParentData: RowColumnParentData?
+    get() = parentData as? RowColumnParentData
+
 internal val RowColumnParentData?.weight: Float
     get() = this?.weight ?: 0f
 
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
index ddd28bb..e0e743a 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.collection.IntList
+import androidx.collection.MutableIntObjectMap
+import androidx.collection.mutableIntObjectMapOf
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastRoundToInt
 import kotlin.math.max
@@ -49,15 +51,17 @@
     val orientation: LayoutOrientation,
     val horizontalArrangement: Arrangement.Horizontal?,
     val verticalArrangement: Arrangement.Vertical?,
-    val arrangementSpacing: Dp,
     val crossAxisSize: SizeMode,
     val crossAxisAlignment: CrossAxisAlignment,
-    val measurables: List<Measurable>,
-    val placeables: Array<Placeable?>
+    val listWrapper: RowColumnMeasurablesWrapper
 ) {
 
-    private val rowColumnParentData = Array(measurables.size) {
-        measurables[it].rowColumnParentData
+    val mainAxisSpacing = if (orientation == LayoutOrientation.Horizontal) {
+        requireNotNull(horizontalArrangement) { "null horizontalArrangement in Row/FlowRow" }
+        horizontalArrangement.spacing
+    } else {
+        requireNotNull(verticalArrangement) { "null verticalArrangement in Column/FlowColumn" }
+        verticalArrangement.spacing
     }
 
     fun Placeable.mainAxisSize() =
@@ -86,7 +90,7 @@
         @Suppress("NAME_SHADOWING")
         val constraints = OrientationIndependentConstraints(constraints, orientation)
         val arrangementSpacingPx = with(measureScope) {
-            arrangementSpacing.roundToPx().toLong()
+            mainAxisSpacing.roundToPx().toLong()
         }
 
         var totalWeight = 0f
@@ -95,13 +99,12 @@
         var weightChildrenCount = 0
 
         var anyAlignBy = false
-        val subSize = endIndex - startIndex
+        val subSize = listWrapper.subSize(startIndex, endIndex)
 
         // First measure children with zero weight.
         var spaceAfterLastNoWeight = 0
-        for (i in startIndex until endIndex) {
-            val child = measurables[i]
-            val parentData = rowColumnParentData[i]
+        listWrapper.forEachIndexed(startIndex, endIndex) { i, child, placeableCache ->
+            val parentData = child.rowColumnParentData
             val weight = parentData.weight
 
             if (weight > 0f) {
@@ -114,7 +117,7 @@
                     parentData?.flowLayoutData?.let {
                         (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
                     }
-                val placeable = placeables[i] ?: child.measure(
+                val placeable = placeableCache ?: child.measure(
                     // Ask for preferred main axis size.
                     constraints.copy(
                         mainAxisMin = 0,
@@ -135,7 +138,7 @@
                 fixedSpace += placeable.mainAxisSize() + spaceAfterLastNoWeight
                 crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
                 anyAlignBy = anyAlignBy || parentData.isRelative
-                placeables[i] = placeable
+                listWrapper.setPlaceable(i, placeable)
             }
         }
 
@@ -156,14 +159,15 @@
                 (targetSpace - fixedSpace - arrangementSpacingTotal).coerceAtLeast(0)
 
             val weightUnitSpace = if (totalWeight > 0) remainingToTarget / totalWeight else 0f
-            var remainder = remainingToTarget - (startIndex until endIndex).sumOf {
-                (weightUnitSpace * rowColumnParentData[it].weight).fastRoundToInt()
+            var remainder = remainingToTarget
+            listWrapper.forEachIndexed(startIndex, endIndex) { _, measurable, _ ->
+                remainder -=
+                    (weightUnitSpace * measurable.rowColumnParentData.weight).fastRoundToInt()
             }
 
-            for (i in startIndex until endIndex) {
-                if (placeables[i] == null) {
-                    val child = measurables[i]
-                    val parentData = rowColumnParentData[i]
+            listWrapper.forEachIndexed(startIndex, endIndex) { i, child, placeableCache ->
+                if (placeableCache == null) {
+                    val parentData = child.rowColumnParentData
                     val weight = parentData.weight
                     val crossAxisMax = constraints.crossAxisMax
                     val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) null else
@@ -206,7 +210,7 @@
                     weightedSpace += placeable.mainAxisSize()
                     crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
                     anyAlignBy = anyAlignBy || parentData.isRelative
-                    placeables[i] = placeable
+                    listWrapper.setPlaceable(i, placeable)
                 }
             }
             weightedSpace = (weightedSpace + arrangementSpacingTotal)
@@ -217,9 +221,8 @@
         var beforeCrossAxisAlignmentLine = 0
         var afterCrossAxisAlignmentLine = 0
         if (anyAlignBy) {
-            for (i in startIndex until endIndex) {
-                val placeable = placeables[i]!!
-                val parentData = rowColumnParentData[i]
+            listWrapper.forEachIndexed(startIndex, endIndex) { _, _, placeable ->
+                val parentData = placeable!!.rowColumnParentData
                 val alignmentLinePosition = parentData.crossAxisAlignment
                     ?.calculateAlignmentLinePosition(placeable)
                 if (alignmentLinePosition != null) {
@@ -265,8 +268,9 @@
             )
         }
         val mainAxisPositions = IntArray(subSize) { 0 }
-        val childrenMainAxisSize = IntArray(subSize) { index ->
-            placeables[index + startIndex]!!.mainAxisSize()
+        val childrenMainAxisSize = IntArray(subSize)
+        listWrapper.forEachIndexed(startIndex, endIndex) { i, _, placeable ->
+            childrenMainAxisSize[i - startIndex] = placeable!!.mainAxisSize()
         }
 
         return RowColumnMeasureHelperResult(
@@ -336,13 +340,14 @@
         layoutDirection: LayoutDirection,
     ) {
         with(placeableScope) {
-            for (i in measureResult.startIndex until measureResult.endIndex) {
-                val placeable = placeables[i]
+            listWrapper.forEachIndexed(
+                measureResult.startIndex, measureResult.endIndex
+            ) { i, _, placeable ->
                 placeable!!
                 val mainAxisPositions = measureResult.mainAxisPositions
                 val crossAxisPosition = getCrossAxisPosition(
                     placeable,
-                    (measurables[i].parentData as? RowColumnParentData),
+                    placeable.rowColumnParentData,
                     measureResult.crossAxisSize,
                     layoutDirection,
                     measureResult.beforeCrossAxisAlignmentLine
@@ -362,3 +367,113 @@
         }
     }
 }
+
+/**
+ * The wrapper allows setting a range for processing its contents,
+ * defined by startIndex and endIndex
+ * and also considers the ellipsis handling as well.
+ */
+internal class RowColumnMeasurablesWrapper(
+    private val measurables: List<Measurable>,
+    private val placeables: MutableIntObjectMap<Placeable?> = mutableIntObjectMapOf(),
+    private val ellipsis: Measurable? = null,
+    private val ellipsisOnLineContent: Boolean? = null,
+) {
+
+    private val contentStart: Int = 0
+    private val contentEnd: Int = measurables.size
+    private var ellipsisPlaceable: Placeable? = null
+
+    /**
+     * Breaks the loop into multiple lines, taking into considering the ellipsis
+     */
+    inline fun forEachLine(
+        breakLineIndices: IntList,
+        action: (lineNo: Int, startIndex: Int, endIndex: Int) -> Unit
+    ) {
+        var start = contentStart
+        var lineNo = 0
+        breakLineIndices.forEach { end ->
+            require(end in (start + 1)..contentEnd) {
+                "For each line, contentStartIndex must be less than or equal to contentEndIndex "
+            }
+            action(lineNo, start, end)
+            start = end
+            lineNo++
+        }
+        val end = start
+        if (end == contentEnd && ellipsis != null && ellipsisOnLineContent == false) {
+            // create a new line where no content is included, but just used for the ellipsis
+            action(lineNo, end, end)
+        }
+    }
+
+    /**
+     * ForEach loop that iterates through the range of the
+     * current [contentStart] and [contentEnd] to iterate through.
+     * If the [contentEnd] aligns with our [contentEnd], we also provide the ellipsis measurable
+     * as the last for each return.
+     */
+    inline fun forEachIndexed(
+        contentStart: Int,
+        contentEnd: Int,
+        action: (index: Int, measurable: Measurable, placeable: Placeable?) -> Unit
+    ) {
+        require(contentStart <= contentEnd && contentEnd <= measurables.size) {
+            "contentStartIndex must be less than or equal to contentEndIndex " +
+                "and contentEndIndex must be less than or equal to list size"
+        }
+        var i = contentStart
+        while (i < contentEnd) {
+            action(i, measurables[i], placeables[i])
+            i++
+        }
+        val hasEllipsisOnContentLine =
+            ellipsis != null &&
+                contentEnd == this.contentEnd &&
+                ellipsisOnLineContent == true
+        val isEllipsisLineOnly =
+            ellipsis != null &&
+                contentStart == contentEnd &&
+                contentEnd == this.contentEnd
+        if (hasEllipsisOnContentLine || isEllipsisLineOnly) {
+            ellipsis?.let { action(i, it, ellipsisPlaceable) }
+        }
+    }
+
+    fun subSize(
+        contentStart: Int,
+        contentEnd: Int
+    ): Int {
+        require(contentStart <= contentEnd && contentEnd <= measurables.size) {
+            "contentStartIndex must be less than or equal to contentEndIndex " +
+                "and contentEndIndex must be less than or equal to list size"
+        }
+        val subSize = contentEnd - contentStart
+
+        val hasEllipsisOnContentLine =
+            ellipsis != null &&
+                contentEnd == this.contentEnd &&
+                ellipsisOnLineContent == true
+        // lines with only ellipsis have content starts that
+        // have reached the end of the list allowed.
+        val isEllipsisLineOnly =
+            ellipsis != null &&
+                contentStart == contentEnd &&
+                contentEnd == this.contentEnd
+
+        return if ((hasEllipsisOnContentLine || isEllipsisLineOnly)) {
+            subSize + 1
+        } else {
+            subSize
+        }
+    }
+
+    /**
+     * setPlaceable based on the index provided by [forEachIndexed]
+     * If the index goes over the content end, the ellipsis placeable is set
+     */
+    fun setPlaceable(i: Int, placeable: Placeable) {
+        if (i < contentEnd) { placeables[i] = placeable } else ellipsisPlaceable = placeable
+    }
+}
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index fba6623..d7309fe 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -17,13 +17,15 @@
 
 RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutItemProviderKt:
     Removed class androidx.compose.foundation.lazy.layout.LazyLayoutItemProviderKt
-RemovedClass: androidx.compose.foundation.text2.input.AllCapsTransformationKt:
-    Removed class androidx.compose.foundation.text2.input.AllCapsTransformationKt
-RemovedClass: androidx.compose.foundation.text2.input.MaxLengthTransformationKt:
-    Removed class androidx.compose.foundation.text2.input.MaxLengthTransformationKt
 
 
 RemovedDeprecatedMethod: androidx.compose.foundation.gestures.TapGestureDetectorKt#awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, boolean):
     Removed deprecated method androidx.compose.foundation.gestures.TapGestureDetectorKt.awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope,boolean)
 RemovedDeprecatedMethod: androidx.compose.foundation.gestures.TapGestureDetectorKt#waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope):
     Removed deprecated method androidx.compose.foundation.gestures.TapGestureDetectorKt.waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope)
+
+
+RemovedPackage: androidx.compose.foundation.text2:
+    Removed package androidx.compose.foundation.text2
+RemovedPackage: androidx.compose.foundation.text2.input:
+    Removed package androidx.compose.foundation.text2.input
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 3328399..6e67e8f 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -127,7 +127,7 @@
 
   public final class FocusableKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
-    method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class FocusedBoundsKt {
@@ -135,7 +135,7 @@
   }
 
   public final class HoverableKt {
-    method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
   }
 
   public final class ImageKt {
@@ -474,7 +474,7 @@
 
   public final class Draggable2DKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.gestures.Draggable2DState Draggable2DState(kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDelta);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier draggable2D(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Draggable2DState state, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStarted, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.unit.Velocity,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStopped, optional boolean reverseDirection);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier draggable2D(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Draggable2DState state, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStarted, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.unit.Velocity,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStopped, optional boolean reverseDirection);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.gestures.Draggable2DState rememberDraggable2DState(kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDelta);
   }
 
@@ -1524,6 +1524,16 @@
 
 package androidx.compose.foundation.text {
 
+  public final class BasicSecureTextFieldKt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+  }
+
+  public final class BasicTextField2Kt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+  }
+
   public final class BasicTextFieldKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function1<? super kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.Unit> decorationBox);
     method @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function1<? super kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.Unit> decorationBox);
@@ -1625,55 +1635,7 @@
 
 }
 
-package androidx.compose.foundation.text.selection {
-
-  public final class SelectionContainerKt {
-    method @androidx.compose.runtime.Composable public static void DisableSelection(kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void SelectionContainer(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-  }
-
-  @androidx.compose.runtime.Immutable public final class TextSelectionColors {
-    ctor public TextSelectionColors(long handleColor, long backgroundColor);
-    method public long getBackgroundColor();
-    method public long getHandleColor();
-    property public final long backgroundColor;
-    property public final long handleColor;
-  }
-
-  public final class TextSelectionColorsKt {
-    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> getLocalTextSelectionColors();
-    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> LocalTextSelectionColors;
-  }
-
-}
-
-package androidx.compose.foundation.text2 {
-
-  public final class BasicSecureTextFieldKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text2.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text2.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text2.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-  }
-
-  public final class BasicTextField2Kt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text2.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text2.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text2.input.CodepointTransformation? codepointTransformation, optional androidx.compose.foundation.text2.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text2.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text2.input.CodepointTransformation? codepointTransformation, optional androidx.compose.foundation.text2.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-  }
-
-}
-
-package androidx.compose.foundation.text2.input {
-
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface CodepointTransformation {
-    method public int transform(int codepointIndex, int codepoint);
-    field public static final androidx.compose.foundation.text2.input.CodepointTransformation.Companion Companion;
-  }
-
-  public static final class CodepointTransformation.Companion {
-  }
-
-  public final class CodepointTransformationKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.CodepointTransformation mask(androidx.compose.foundation.text2.input.CodepointTransformation.Companion, char character);
-  }
+package androidx.compose.foundation.text.input {
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface ImeActionHandler {
     method public boolean onImeAction(int action);
@@ -1681,26 +1643,26 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface InputTransformation {
     method public default androidx.compose.foundation.text.KeyboardOptions? getKeyboardOptions();
-    method public void transformInput(androidx.compose.foundation.text2.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text2.input.TextFieldBuffer valueWithChanges);
+    method public void transformInput(androidx.compose.foundation.text.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text.input.TextFieldBuffer valueWithChanges);
     property public default androidx.compose.foundation.text.KeyboardOptions? keyboardOptions;
-    field public static final androidx.compose.foundation.text2.input.InputTransformation.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.InputTransformation.Companion Companion;
   }
 
-  public static final class InputTransformation.Companion implements androidx.compose.foundation.text2.input.InputTransformation {
-    method public void transformInput(androidx.compose.foundation.text2.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text2.input.TextFieldBuffer valueWithChanges);
+  public static final class InputTransformation.Companion implements androidx.compose.foundation.text.input.InputTransformation {
+    method public void transformInput(androidx.compose.foundation.text.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text.input.TextFieldBuffer valueWithChanges);
   }
 
   public final class InputTransformationKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation allCaps(androidx.compose.foundation.text2.input.InputTransformation, androidx.compose.ui.text.intl.Locale locale);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation byValue(androidx.compose.foundation.text2.input.InputTransformation, kotlin.jvm.functions.Function2<? super java.lang.CharSequence,? super java.lang.CharSequence,? extends java.lang.CharSequence> transformation);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation maxLengthInChars(androidx.compose.foundation.text2.input.InputTransformation, int maxLength);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation maxLengthInCodepoints(androidx.compose.foundation.text2.input.InputTransformation, int maxLength);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation then(androidx.compose.foundation.text2.input.InputTransformation, androidx.compose.foundation.text2.input.InputTransformation next);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation? thenOrNull(androidx.compose.foundation.text2.input.InputTransformation?, androidx.compose.foundation.text2.input.InputTransformation? next);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation allCaps(androidx.compose.foundation.text.input.InputTransformation, androidx.compose.ui.text.intl.Locale locale);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation byValue(androidx.compose.foundation.text.input.InputTransformation, kotlin.jvm.functions.Function2<? super java.lang.CharSequence,? super java.lang.CharSequence,? extends java.lang.CharSequence> transformation);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation maxLengthInChars(androidx.compose.foundation.text.input.InputTransformation, int maxLength);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation maxLengthInCodepoints(androidx.compose.foundation.text.input.InputTransformation, int maxLength);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation then(androidx.compose.foundation.text.input.InputTransformation, androidx.compose.foundation.text.input.InputTransformation next);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation? thenOrNull(androidx.compose.foundation.text.input.InputTransformation?, androidx.compose.foundation.text.input.InputTransformation? next);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface OutputTransformation {
-    method public void transformOutput(androidx.compose.foundation.text2.input.TextFieldBuffer);
+    method public void transformOutput(androidx.compose.foundation.text.input.TextFieldBuffer);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class TextFieldBuffer implements java.lang.Appendable {
@@ -1709,7 +1671,7 @@
     method public Appendable append(CharSequence? text, int start, int end);
     method public CharSequence asCharSequence();
     method public char charAt(int index);
-    method public androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList getChanges();
+    method public androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList getChanges();
     method public int getCodepointLength();
     method public int getLength();
     method public long getSelectionInChars();
@@ -1723,7 +1685,7 @@
     method public void revertAllChanges();
     method public void selectCharsIn(long range);
     method public void selectCodepointsIn(long range);
-    property public final androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList changes;
+    property public final androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList changes;
     property public final int codepointLength;
     property public final boolean hasSelection;
     property public final int length;
@@ -1739,12 +1701,12 @@
   }
 
   public final class TextFieldBufferKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void delete(androidx.compose.foundation.text2.input.TextFieldBuffer, int start, int end);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChange(androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChangeReversed(androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void insert(androidx.compose.foundation.text2.input.TextFieldBuffer, int index, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void placeCursorAtEnd(androidx.compose.foundation.text2.input.TextFieldBuffer);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void selectAll(androidx.compose.foundation.text2.input.TextFieldBuffer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void delete(androidx.compose.foundation.text.input.TextFieldBuffer, int start, int end);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChange(androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChangeReversed(androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void insert(androidx.compose.foundation.text.input.TextFieldBuffer, int index, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void placeCursorAtEnd(androidx.compose.foundation.text.input.TextFieldBuffer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void selectAll(androidx.compose.foundation.text.input.TextFieldBuffer);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface TextFieldCharSequence extends java.lang.CharSequence {
@@ -1758,7 +1720,7 @@
   }
 
   public final class TextFieldCharSequenceKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.text2.input.TextFieldCharSequence TextFieldCharSequence(optional String text, optional long selection);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.text.input.TextFieldCharSequence TextFieldCharSequence(optional String text, optional long selection);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface TextFieldDecorator {
@@ -1766,15 +1728,15 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public sealed interface TextFieldLineLimits {
-    field public static final androidx.compose.foundation.text2.input.TextFieldLineLimits.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.TextFieldLineLimits.Companion Companion;
   }
 
   public static final class TextFieldLineLimits.Companion {
-    method public androidx.compose.foundation.text2.input.TextFieldLineLimits getDefault();
-    property public final androidx.compose.foundation.text2.input.TextFieldLineLimits Default;
+    method public androidx.compose.foundation.text.input.TextFieldLineLimits getDefault();
+    property public final androidx.compose.foundation.text.input.TextFieldLineLimits Default;
   }
 
-  @androidx.compose.runtime.Immutable public static final class TextFieldLineLimits.MultiLine implements androidx.compose.foundation.text2.input.TextFieldLineLimits {
+  @androidx.compose.runtime.Immutable public static final class TextFieldLineLimits.MultiLine implements androidx.compose.foundation.text.input.TextFieldLineLimits {
     ctor public TextFieldLineLimits.MultiLine(optional int minHeightInLines, optional int maxHeightInLines);
     method public int getMaxHeightInLines();
     method public int getMinHeightInLines();
@@ -1782,38 +1744,38 @@
     property public final int minHeightInLines;
   }
 
-  public static final class TextFieldLineLimits.SingleLine implements androidx.compose.foundation.text2.input.TextFieldLineLimits {
-    field public static final androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine INSTANCE;
+  public static final class TextFieldLineLimits.SingleLine implements androidx.compose.foundation.text.input.TextFieldLineLimits {
+    field public static final androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine INSTANCE;
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class TextFieldState {
     ctor public TextFieldState(optional String initialText, optional long initialSelectionInChars);
-    method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text2.input.TextFieldBuffer,kotlin.Unit> block);
-    method public androidx.compose.foundation.text2.input.TextFieldCharSequence getText();
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text2.input.UndoState getUndoState();
-    property public final androidx.compose.foundation.text2.input.TextFieldCharSequence text;
-    property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text2.input.UndoState undoState;
+    method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text.input.TextFieldBuffer,kotlin.Unit> block);
+    method public androidx.compose.foundation.text.input.TextFieldCharSequence getText();
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text.input.UndoState getUndoState();
+    property public final androidx.compose.foundation.text.input.TextFieldCharSequence text;
+    property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text.input.UndoState undoState;
   }
 
-  public static final class TextFieldState.Saver implements androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.text2.input.TextFieldState,java.lang.Object> {
-    method public androidx.compose.foundation.text2.input.TextFieldState? restore(Object value);
-    method public Object? save(androidx.compose.runtime.saveable.SaverScope, androidx.compose.foundation.text2.input.TextFieldState value);
-    field public static final androidx.compose.foundation.text2.input.TextFieldState.Saver INSTANCE;
+  public static final class TextFieldState.Saver implements androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.text.input.TextFieldState,java.lang.Object> {
+    method public androidx.compose.foundation.text.input.TextFieldState? restore(Object value);
+    method public Object? save(androidx.compose.runtime.saveable.SaverScope, androidx.compose.foundation.text.input.TextFieldState value);
+    field public static final androidx.compose.foundation.text.input.TextFieldState.Saver INSTANCE;
   }
 
   public final class TextFieldStateKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text2.input.TextFieldState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text2.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text2.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text2.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelectionInChars);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text2.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text2.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text2.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text2.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelectionInChars);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text.input.TextFieldState, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text.input.TextFieldState, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text.input.TextFieldState);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class TextObfuscationMode {
     method public int getValue();
     property public final int value;
-    field public static final androidx.compose.foundation.text2.input.TextObfuscationMode.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.TextObfuscationMode.Companion Companion;
   }
 
   public static final class TextObfuscationMode.Companion {
@@ -1837,3 +1799,25 @@
 
 }
 
+package androidx.compose.foundation.text.selection {
+
+  public final class SelectionContainerKt {
+    method @androidx.compose.runtime.Composable public static void DisableSelection(kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void SelectionContainer(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.compose.runtime.Immutable public final class TextSelectionColors {
+    ctor public TextSelectionColors(long handleColor, long backgroundColor);
+    method public long getBackgroundColor();
+    method public long getHandleColor();
+    property public final long backgroundColor;
+    property public final long handleColor;
+  }
+
+  public final class TextSelectionColorsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> getLocalTextSelectionColors();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> LocalTextSelectionColors;
+  }
+
+}
+
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index fba6623..d7309fe 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -17,13 +17,15 @@
 
 RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutItemProviderKt:
     Removed class androidx.compose.foundation.lazy.layout.LazyLayoutItemProviderKt
-RemovedClass: androidx.compose.foundation.text2.input.AllCapsTransformationKt:
-    Removed class androidx.compose.foundation.text2.input.AllCapsTransformationKt
-RemovedClass: androidx.compose.foundation.text2.input.MaxLengthTransformationKt:
-    Removed class androidx.compose.foundation.text2.input.MaxLengthTransformationKt
 
 
 RemovedDeprecatedMethod: androidx.compose.foundation.gestures.TapGestureDetectorKt#awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, boolean):
     Removed deprecated method androidx.compose.foundation.gestures.TapGestureDetectorKt.awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope,boolean)
 RemovedDeprecatedMethod: androidx.compose.foundation.gestures.TapGestureDetectorKt#waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope):
     Removed deprecated method androidx.compose.foundation.gestures.TapGestureDetectorKt.waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope)
+
+
+RemovedPackage: androidx.compose.foundation.text2:
+    Removed package androidx.compose.foundation.text2
+RemovedPackage: androidx.compose.foundation.text2.input:
+    Removed package androidx.compose.foundation.text2.input
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index e0a9478..4be36a7 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -127,7 +127,7 @@
 
   public final class FocusableKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
-    method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class FocusedBoundsKt {
@@ -135,7 +135,7 @@
   }
 
   public final class HoverableKt {
-    method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
   }
 
   public final class ImageKt {
@@ -476,7 +476,7 @@
 
   public final class Draggable2DKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.gestures.Draggable2DState Draggable2DState(kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDelta);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier draggable2D(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Draggable2DState state, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStarted, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.unit.Velocity,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStopped, optional boolean reverseDirection);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier draggable2D(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Draggable2DState state, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStarted, optional kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.ui.unit.Velocity,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDragStopped, optional boolean reverseDirection);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.gestures.Draggable2DState rememberDraggable2DState(kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDelta);
   }
 
@@ -1526,6 +1526,16 @@
 
 package androidx.compose.foundation.text {
 
+  public final class BasicSecureTextFieldKt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+  }
+
+  public final class BasicTextField2Kt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+  }
+
   public final class BasicTextFieldKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function1<? super kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.Unit> decorationBox);
     method @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function1<? super kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.Unit> decorationBox);
@@ -1627,55 +1637,7 @@
 
 }
 
-package androidx.compose.foundation.text.selection {
-
-  public final class SelectionContainerKt {
-    method @androidx.compose.runtime.Composable public static void DisableSelection(kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void SelectionContainer(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-  }
-
-  @androidx.compose.runtime.Immutable public final class TextSelectionColors {
-    ctor public TextSelectionColors(long handleColor, long backgroundColor);
-    method public long getBackgroundColor();
-    method public long getHandleColor();
-    property public final long backgroundColor;
-    property public final long handleColor;
-  }
-
-  public final class TextSelectionColorsKt {
-    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> getLocalTextSelectionColors();
-    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> LocalTextSelectionColors;
-  }
-
-}
-
-package androidx.compose.foundation.text2 {
-
-  public final class BasicSecureTextFieldKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text2.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text2.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text2.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-  }
-
-  public final class BasicTextField2Kt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text2.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text2.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text2.input.CodepointTransformation? codepointTransformation, optional androidx.compose.foundation.text2.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text2.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text2.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text2.input.CodepointTransformation? codepointTransformation, optional androidx.compose.foundation.text2.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text2.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
-  }
-
-}
-
-package androidx.compose.foundation.text2.input {
-
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface CodepointTransformation {
-    method public int transform(int codepointIndex, int codepoint);
-    field public static final androidx.compose.foundation.text2.input.CodepointTransformation.Companion Companion;
-  }
-
-  public static final class CodepointTransformation.Companion {
-  }
-
-  public final class CodepointTransformationKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.CodepointTransformation mask(androidx.compose.foundation.text2.input.CodepointTransformation.Companion, char character);
-  }
+package androidx.compose.foundation.text.input {
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface ImeActionHandler {
     method public boolean onImeAction(int action);
@@ -1683,26 +1645,26 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface InputTransformation {
     method public default androidx.compose.foundation.text.KeyboardOptions? getKeyboardOptions();
-    method public void transformInput(androidx.compose.foundation.text2.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text2.input.TextFieldBuffer valueWithChanges);
+    method public void transformInput(androidx.compose.foundation.text.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text.input.TextFieldBuffer valueWithChanges);
     property public default androidx.compose.foundation.text.KeyboardOptions? keyboardOptions;
-    field public static final androidx.compose.foundation.text2.input.InputTransformation.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.InputTransformation.Companion Companion;
   }
 
-  public static final class InputTransformation.Companion implements androidx.compose.foundation.text2.input.InputTransformation {
-    method public void transformInput(androidx.compose.foundation.text2.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text2.input.TextFieldBuffer valueWithChanges);
+  public static final class InputTransformation.Companion implements androidx.compose.foundation.text.input.InputTransformation {
+    method public void transformInput(androidx.compose.foundation.text.input.TextFieldCharSequence originalValue, androidx.compose.foundation.text.input.TextFieldBuffer valueWithChanges);
   }
 
   public final class InputTransformationKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation allCaps(androidx.compose.foundation.text2.input.InputTransformation, androidx.compose.ui.text.intl.Locale locale);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation byValue(androidx.compose.foundation.text2.input.InputTransformation, kotlin.jvm.functions.Function2<? super java.lang.CharSequence,? super java.lang.CharSequence,? extends java.lang.CharSequence> transformation);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation maxLengthInChars(androidx.compose.foundation.text2.input.InputTransformation, int maxLength);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation maxLengthInCodepoints(androidx.compose.foundation.text2.input.InputTransformation, int maxLength);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation then(androidx.compose.foundation.text2.input.InputTransformation, androidx.compose.foundation.text2.input.InputTransformation next);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text2.input.InputTransformation? thenOrNull(androidx.compose.foundation.text2.input.InputTransformation?, androidx.compose.foundation.text2.input.InputTransformation? next);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation allCaps(androidx.compose.foundation.text.input.InputTransformation, androidx.compose.ui.text.intl.Locale locale);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation byValue(androidx.compose.foundation.text.input.InputTransformation, kotlin.jvm.functions.Function2<? super java.lang.CharSequence,? super java.lang.CharSequence,? extends java.lang.CharSequence> transformation);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation maxLengthInChars(androidx.compose.foundation.text.input.InputTransformation, int maxLength);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation maxLengthInCodepoints(androidx.compose.foundation.text.input.InputTransformation, int maxLength);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation then(androidx.compose.foundation.text.input.InputTransformation, androidx.compose.foundation.text.input.InputTransformation next);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.text.input.InputTransformation? thenOrNull(androidx.compose.foundation.text.input.InputTransformation?, androidx.compose.foundation.text.input.InputTransformation? next);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface OutputTransformation {
-    method public void transformOutput(androidx.compose.foundation.text2.input.TextFieldBuffer);
+    method public void transformOutput(androidx.compose.foundation.text.input.TextFieldBuffer);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class TextFieldBuffer implements java.lang.Appendable {
@@ -1711,7 +1673,7 @@
     method public Appendable append(CharSequence? text, int start, int end);
     method public CharSequence asCharSequence();
     method public char charAt(int index);
-    method public androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList getChanges();
+    method public androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList getChanges();
     method public int getCodepointLength();
     method public int getLength();
     method public long getSelectionInChars();
@@ -1725,7 +1687,7 @@
     method public void revertAllChanges();
     method public void selectCharsIn(long range);
     method public void selectCodepointsIn(long range);
-    property public final androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList changes;
+    property public final androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList changes;
     property public final int codepointLength;
     property public final boolean hasSelection;
     property public final int length;
@@ -1741,12 +1703,12 @@
   }
 
   public final class TextFieldBufferKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void delete(androidx.compose.foundation.text2.input.TextFieldBuffer, int start, int end);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChange(androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChangeReversed(androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void insert(androidx.compose.foundation.text2.input.TextFieldBuffer, int index, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void placeCursorAtEnd(androidx.compose.foundation.text2.input.TextFieldBuffer);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void selectAll(androidx.compose.foundation.text2.input.TextFieldBuffer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void delete(androidx.compose.foundation.text.input.TextFieldBuffer, int start, int end);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChange(androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static inline void forEachChangeReversed(androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.TextRange,? super androidx.compose.ui.text.TextRange,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void insert(androidx.compose.foundation.text.input.TextFieldBuffer, int index, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void placeCursorAtEnd(androidx.compose.foundation.text.input.TextFieldBuffer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void selectAll(androidx.compose.foundation.text.input.TextFieldBuffer);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface TextFieldCharSequence extends java.lang.CharSequence {
@@ -1760,7 +1722,7 @@
   }
 
   public final class TextFieldCharSequenceKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.text2.input.TextFieldCharSequence TextFieldCharSequence(optional String text, optional long selection);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.text.input.TextFieldCharSequence TextFieldCharSequence(optional String text, optional long selection);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface TextFieldDecorator {
@@ -1768,15 +1730,15 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public sealed interface TextFieldLineLimits {
-    field public static final androidx.compose.foundation.text2.input.TextFieldLineLimits.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.TextFieldLineLimits.Companion Companion;
   }
 
   public static final class TextFieldLineLimits.Companion {
-    method public androidx.compose.foundation.text2.input.TextFieldLineLimits getDefault();
-    property public final androidx.compose.foundation.text2.input.TextFieldLineLimits Default;
+    method public androidx.compose.foundation.text.input.TextFieldLineLimits getDefault();
+    property public final androidx.compose.foundation.text.input.TextFieldLineLimits Default;
   }
 
-  @androidx.compose.runtime.Immutable public static final class TextFieldLineLimits.MultiLine implements androidx.compose.foundation.text2.input.TextFieldLineLimits {
+  @androidx.compose.runtime.Immutable public static final class TextFieldLineLimits.MultiLine implements androidx.compose.foundation.text.input.TextFieldLineLimits {
     ctor public TextFieldLineLimits.MultiLine(optional int minHeightInLines, optional int maxHeightInLines);
     method public int getMaxHeightInLines();
     method public int getMinHeightInLines();
@@ -1784,40 +1746,40 @@
     property public final int minHeightInLines;
   }
 
-  public static final class TextFieldLineLimits.SingleLine implements androidx.compose.foundation.text2.input.TextFieldLineLimits {
-    field public static final androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine INSTANCE;
+  public static final class TextFieldLineLimits.SingleLine implements androidx.compose.foundation.text.input.TextFieldLineLimits {
+    field public static final androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine INSTANCE;
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class TextFieldState {
     ctor public TextFieldState(optional String initialText, optional long initialSelectionInChars);
-    method @kotlin.PublishedApi internal void commitEdit(androidx.compose.foundation.text2.input.TextFieldBuffer newValue);
-    method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text2.input.TextFieldBuffer,kotlin.Unit> block);
-    method public androidx.compose.foundation.text2.input.TextFieldCharSequence getText();
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text2.input.UndoState getUndoState();
-    method @kotlin.PublishedApi internal androidx.compose.foundation.text2.input.TextFieldBuffer startEdit(androidx.compose.foundation.text2.input.TextFieldCharSequence value);
-    property public final androidx.compose.foundation.text2.input.TextFieldCharSequence text;
-    property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text2.input.UndoState undoState;
+    method @kotlin.PublishedApi internal void commitEdit(androidx.compose.foundation.text.input.TextFieldBuffer newValue);
+    method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text.input.TextFieldBuffer,kotlin.Unit> block);
+    method public androidx.compose.foundation.text.input.TextFieldCharSequence getText();
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text.input.UndoState getUndoState();
+    method @kotlin.PublishedApi internal androidx.compose.foundation.text.input.TextFieldBuffer startEdit(androidx.compose.foundation.text.input.TextFieldCharSequence value);
+    property public final androidx.compose.foundation.text.input.TextFieldCharSequence text;
+    property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text.input.UndoState undoState;
   }
 
-  public static final class TextFieldState.Saver implements androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.text2.input.TextFieldState,java.lang.Object> {
-    method public androidx.compose.foundation.text2.input.TextFieldState? restore(Object value);
-    method public Object? save(androidx.compose.runtime.saveable.SaverScope, androidx.compose.foundation.text2.input.TextFieldState value);
-    field public static final androidx.compose.foundation.text2.input.TextFieldState.Saver INSTANCE;
+  public static final class TextFieldState.Saver implements androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.text.input.TextFieldState,java.lang.Object> {
+    method public androidx.compose.foundation.text.input.TextFieldState? restore(Object value);
+    method public Object? save(androidx.compose.runtime.saveable.SaverScope, androidx.compose.foundation.text.input.TextFieldState value);
+    field public static final androidx.compose.foundation.text.input.TextFieldState.Saver INSTANCE;
   }
 
   public final class TextFieldStateKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text2.input.TextFieldState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text2.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text2.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text2.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelectionInChars);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text2.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text2.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text2.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text2.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelectionInChars);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text.input.TextFieldState, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text.input.TextFieldState, String text);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text.input.TextFieldState);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class TextObfuscationMode {
     method public int getValue();
     property public final int value;
-    field public static final androidx.compose.foundation.text2.input.TextObfuscationMode.Companion Companion;
+    field public static final androidx.compose.foundation.text.input.TextObfuscationMode.Companion Companion;
   }
 
   public static final class TextObfuscationMode.Companion {
@@ -1841,3 +1803,25 @@
 
 }
 
+package androidx.compose.foundation.text.selection {
+
+  public final class SelectionContainerKt {
+    method @androidx.compose.runtime.Composable public static void DisableSelection(kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void SelectionContainer(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.compose.runtime.Immutable public final class TextSelectionColors {
+    ctor public TextSelectionColors(long handleColor, long backgroundColor);
+    method public long getBackgroundColor();
+    method public long getHandleColor();
+    property public final long backgroundColor;
+    property public final long handleColor;
+  }
+
+  public final class TextSelectionColorsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> getLocalTextSelectionColors();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.foundation.text.selection.TextSelectionColors> LocalTextSelectionColors;
+  }
+
+}
+
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/NestedScrollerTestCase.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/NestedScrollerTestCase.kt
index 4176188..99232e1 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/NestedScrollerTestCase.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/NestedScrollerTestCase.kt
@@ -26,8 +26,8 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -50,8 +50,8 @@
     @Composable
     override fun MeasuredContent() {
         scrollState = rememberScrollState()
-        LazyColumn {
-            items(5) { index ->
+        Column(modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
+            repeat(5) { index ->
                 SquareRow(index == 0)
             }
         }
@@ -74,7 +74,10 @@
                             val blue = Random.nextInt(256)
                             Color(red = red, green = green, blue = blue)
                         }
-                        Box(Modifier.size(350f.toDp()).background(color = color))
+                        Box(
+                            Modifier
+                                .size(350f.toDp())
+                                .background(color = color))
                         Text(
                             text = "Some title",
                             color = Color.Black,
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/TrailingLambdaBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/TrailingLambdaBenchmark.kt
deleted file mode 100644
index 3cc15b9..0000000
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/TrailingLambdaBenchmark.kt
+++ /dev/null
@@ -1,115 +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.compose.foundation.benchmark
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.testutils.LayeredComposeTestCase
-import androidx.compose.testutils.ToggleableTestCase
-import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.benchmark.benchmarkFirstCompose
-import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class TrailingLambdaBenchmark {
-    @get:Rule
-    val benchmarkRule = ComposeBenchmarkRule()
-
-    @Test
-    fun withTrailingLambdas_compose() {
-        benchmarkRule.benchmarkFirstCompose { WithTrailingLambdas() }
-    }
-
-    @Test
-    fun withTrailingLambdas_recompose() {
-        benchmarkRule.toggleStateBenchmarkRecompose({ WithTrailingLambdas() })
-    }
-
-    @Test
-    fun withoutTrailingLambdas_compose() {
-        benchmarkRule.benchmarkFirstCompose { WithoutTrailingLambdas() }
-    }
-
-    @Test
-    fun withoutTrailingLambdas_recompose() {
-        benchmarkRule.toggleStateBenchmarkRecompose({ WithoutTrailingLambdas() })
-    }
-}
-
-private sealed class TrailingLambdaTestCase : LayeredComposeTestCase(), ToggleableTestCase {
-
-    var numberState: MutableState<Int>? = null
-
-    @Composable
-    override fun MeasuredContent() {
-        val number = remember { mutableStateOf(5) }
-        numberState = number
-
-        val content = @Composable {
-            Box(Modifier.width(10.dp))
-        }
-
-        Column {
-            repeat(10) {
-                Content(number = number.value, content = content)
-            }
-        }
-    }
-
-    override fun toggleState() {
-        with(numberState!!) {
-            value = if (value == 5) 10 else 5
-        }
-    }
-
-    @Composable
-    abstract fun Content(number: Int, content: @Composable () -> Unit)
-}
-
-private class WithTrailingLambdas : TrailingLambdaTestCase() {
-    @Composable
-    override fun Content(number: Int, content: @Composable () -> Unit) {
-        EmptyComposable(number = number) {
-            content()
-        }
-    }
-}
-
-private class WithoutTrailingLambdas : TrailingLambdaTestCase() {
-    @Composable
-    override fun Content(number: Int, content: @Composable () -> Unit) {
-        EmptyComposable(number = number, content = content)
-    }
-}
-
-@Suppress("UNUSED_PARAMETER")
-@Composable
-private fun EmptyComposable(number: Int, content: @Composable () -> Unit) {
-}
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 8afc1b1..ab1f2c9 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 
@@ -43,11 +42,11 @@
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api("androidx.collection:collection:1.4.0")
-                api(project(':compose:animation:animation'))
-                api(project(':compose:runtime:runtime'))
-                api(project(':compose:ui:ui'))
-                implementation(project(":compose:ui:ui-text"))
-                implementation(project(":compose:ui:ui-util"))
+                api(project(":compose:animation:animation"))
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api(project(":compose:ui:ui"))
+                implementation("androidx.compose.ui:ui-text:1.6.0")
+                implementation("androidx.compose.ui:ui-util:1.6.0")
                 implementation(project(':compose:foundation:foundation-layout'))
             }
         }
@@ -79,8 +78,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation(libs.kotlinStdlib)
-
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
@@ -124,8 +121,8 @@
                 implementation(libs.junit)
                 implementation(libs.truth)
                 implementation(libs.kotlinReflect)
-                implementation(libs.mockitoCore)
-                implementation(libs.mockitoKotlin)
+                implementation(libs.mockitoCore4)
+                implementation(libs.mockitoKotlin4)
                 implementation(project(":constraintlayout:constraintlayout-compose"))
             }
         }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
index 37ecac6..1e28470 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
@@ -26,9 +26,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text2.BasicSecureTextField
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.TextObfuscationMode
+import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.TextObfuscationMode
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Icon
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt
index 72e172f..48cb7eb 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt
@@ -28,15 +28,15 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField2
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldBuffer
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.clearText
-import androidx.compose.foundation.text2.input.maxLengthInChars
-import androidx.compose.foundation.text2.input.then
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.clearText
+import androidx.compose.foundation.text.input.maxLengthInChars
+import androidx.compose.foundation.text.input.then
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.LocalContentAlpha
 import androidx.compose.material.LocalContentColor
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
index 6f01f8a..c4dc19a 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
@@ -28,9 +28,9 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Checkbox
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
index fc5d40d2..1951d83 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
@@ -31,14 +31,14 @@
 import androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
 import androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueChooseSample
 import androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueReplaceSample
+import androidx.compose.foundation.text.BasicTextField2
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldBuffer
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.allCaps
-import androidx.compose.foundation.text2.input.maxLengthInChars
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.allCaps
+import androidx.compose.foundation.text.input.maxLengthInChars
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Switch
 import androidx.compose.material.Text
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt
index 516076b..a664fcb 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt
@@ -33,8 +33,8 @@
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.LocalTextStyle
 import androidx.compose.material.RadioButton
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt
index 5541557..a3ba95c 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt
@@ -20,9 +20,9 @@
 import androidx.compose.foundation.demos.text.loremIpsumWords
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt
index 03abef0..4252b9e 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt
@@ -24,16 +24,16 @@
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.OutputTransformation
-import androidx.compose.foundation.text2.input.TextFieldBuffer
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldDecorator
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.delete
-import androidx.compose.foundation.text2.input.insert
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldDecorator
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.delete
+import androidx.compose.foundation.text.input.insert
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Checkbox
 import androidx.compose.material.Icon
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
index 84967c1..167383f6 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
@@ -25,8 +25,8 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.material.Button
 import androidx.compose.material.Surface
 import androidx.compose.material.Text
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
index fffc487..641cc59 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
@@ -27,8 +27,8 @@
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.LocalContentColor
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
index 83470a3..bdeaf20 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
@@ -24,12 +24,12 @@
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicTextField2
 import androidx.compose.foundation.text.KeyboardActionScope
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.material.Checkbox
 import androidx.compose.material.Snackbar
 import androidx.compose.material.SnackbarDefaults
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
index 5d7286b..22599fe 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
@@ -29,9 +29,9 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.BasicTextField2
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.material.Button
 import androidx.compose.material.LocalTextStyle
 import androidx.compose.runtime.Composable
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
index 7faad46..434b903 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
@@ -46,9 +46,9 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Card
 import androidx.compose.material.Divider
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
index 0888591..f1bb452 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
@@ -27,10 +27,10 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.material.Slider
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
index 5754efb..859a1c0 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
@@ -23,9 +23,9 @@
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.LocalTextStyle
 import androidx.compose.material.Slider
diff --git a/compose/foundation/foundation/lint-baseline.xml b/compose/foundation/foundation/lint-baseline.xml
index 876590d..749cb82 100644
--- a/compose/foundation/foundation/lint-baseline.xml
+++ b/compose/foundation/foundation/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanSuppressTag"
@@ -94,17 +94,8 @@
     <issue
         id="PrimitiveInCollection"
         message="field currentKeyPressInteractions with type Map&lt;Key, Press>: replace with LongObjectMap"
-        errorLine1="        val currentKeyPressInteractions = mutableMapOf&lt;Key, PressInteraction.Press>()"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type Map&lt;Key, Press> of getCurrentKeyPressInteractions: replace with LongObjectMap"
-        errorLine1="        val currentKeyPressInteractions = mutableMapOf&lt;Key, PressInteraction.Press>()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        errorLine1="    private val currentKeyPressInteractions = mutableMapOf&lt;Key, PressInteraction.Press>()"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt"/>
     </issue>
@@ -616,15 +607,6 @@
     <issue
         id="PrimitiveInCollection"
         message="field baselineCache with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
-        errorLine1="    private var baselineCache: Map&lt;AlignmentLine, Int>? = null"
-        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field baselineCache with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
         errorLine1="    private var baselineCache: MutableMap&lt;AlignmentLine, Int>? = null"
         errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt
index 55db6d4..50710b7 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt
@@ -33,19 +33,19 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.byValue
-import androidx.compose.foundation.text2.input.delete
-import androidx.compose.foundation.text2.input.forEachChange
-import androidx.compose.foundation.text2.input.forEachChangeReversed
-import androidx.compose.foundation.text2.input.forEachTextValue
-import androidx.compose.foundation.text2.input.insert
-import androidx.compose.foundation.text2.input.rememberTextFieldState
-import androidx.compose.foundation.text2.input.setTextAndPlaceCursorAtEnd
-import androidx.compose.foundation.text2.input.textAsFlow
-import androidx.compose.foundation.text2.input.then
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.byValue
+import androidx.compose.foundation.text.input.delete
+import androidx.compose.foundation.text.input.forEachChange
+import androidx.compose.foundation.text.input.forEachChangeReversed
+import androidx.compose.foundation.text.input.forEachTextValue
+import androidx.compose.foundation.text.input.insert
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.text.input.textAsFlow
+import androidx.compose.foundation.text.input.then
 import androidx.compose.material.Button
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
index 3aeff3c..5ecc110 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
@@ -29,8 +29,8 @@
 import androidx.compose.foundation.content.receiveContent
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BackgroundTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
index 574672e..56dc5a4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2019 The Android Open Source Project
  *
@@ -27,10 +26,12 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
@@ -93,10 +94,15 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40f.toDp()).background(Color.Magenta),
+                    Modifier
+                        .size(40f.toDp())
+                        .background(Color.Magenta),
                     contentAlignment = Alignment.Center
                 ) {
-                    Box(Modifier.size(20f.toDp()).background(Color.White))
+                    Box(
+                        Modifier
+                            .size(20f.toDp())
+                            .background(Color.White))
                 }
             }
         }
@@ -116,11 +122,14 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40f.toDp()).background(Color.Magenta),
+                    Modifier
+                        .size(40f.toDp())
+                        .background(Color.Magenta),
                     contentAlignment = Alignment.Center
                 ) {
                     Box(
-                        Modifier.size(20f.toDp())
+                        Modifier
+                            .size(20f.toDp())
                             .background(SolidColor(Color.White))
                     )
                 }
@@ -142,7 +151,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40f.toDp())
+                    Modifier
+                        .size(40f.toDp())
                         .background(Color.Magenta)
                         .background(color = Color.White, shape = CircleShape)
                 )
@@ -163,7 +173,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40f.toDp())
+                    Modifier
+                        .size(40f.toDp())
                         .background(Color.Magenta)
                         .background(
                             brush = SolidColor(Color.White),
@@ -189,7 +200,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40f.toDp())
+                    Modifier
+                        .size(40f.toDp())
                         .background(Color.Magenta)
                         .background(color = Color.White, shape = shape)
                 )
@@ -224,7 +236,8 @@
             SemanticParent {
                 CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                     Box(
-                        Modifier.size(40f.toDp())
+                        Modifier
+                            .size(40f.toDp())
                             .background(Color.Magenta)
                             .background(
                                 brush = SolidColor(Color.White),
@@ -251,7 +264,8 @@
             SemanticParent {
                 CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
                     Box(
-                        Modifier.size(40f.toDp())
+                        Modifier
+                            .size(40f.toDp())
                             .background(Color.Magenta)
                             .background(
                                 brush = SolidColor(Color.White),
@@ -298,9 +312,28 @@
     }
 
     @Test
-    fun testEquals() {
-        assertThat(Modifier.background(SolidColor(Color.Red)))
-            .isEqualTo(Modifier.background(SolidColor(Color.Red)))
+    fun equalInputs_shouldResolveToEquals_withColor() {
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.background(Color.Red)
+            } else {
+                Modifier.background(Color.Gray)
+            }
+        }
+    }
+
+    @Test
+    fun equalInputs_shouldResolveToEquals_withBrush() {
+        val brush1 = Brush.horizontalGradient()
+        val brush2 = Brush.verticalGradient()
+
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.background(brush1)
+            } else {
+                Modifier.background(brush2)
+            }
+        }
     }
 
     @Composable
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BorderTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BorderTest.kt
index 8eb3ab4..084cd3e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BorderTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BorderTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -102,7 +103,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
                         .background(color = Color.Blue)
                         .border(BorderStroke(10.0f.toDp(), Color.Red), shape)
 
@@ -127,7 +129,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
                         .background(color = Color.Blue)
                         .border(
                             BorderStroke(10.0f.toDp(), SolidColor(Color.Red)),
@@ -154,7 +157,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
                         .background(color = Color.Blue)
                         .border(BorderStroke(1500.0f.toDp(), Color.Red), shape)
                 ) {}
@@ -176,7 +180,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
                         .background(color = Color.Blue)
                         .border(BorderStroke(-5.0f.toDp(), Color.Red), shape)
                 ) {}
@@ -198,10 +203,13 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp()).background(Color.White)
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
+                        .background(Color.White)
                 ) {
                     Box(
-                        Modifier.size(0.0f.toDp(), 40.0f.toDp())
+                        Modifier
+                            .size(0.0f.toDp(), 40.0f.toDp())
                             .border(BorderStroke(4.0f.toDp(), Color.Red), shape)
                     ) {}
                 }
@@ -233,7 +241,8 @@
                 borderWidthDp = (10f / density).dp
             }
             Box(
-                Modifier.testTag(testTag)
+                Modifier
+                    .testTag(testTag)
                     .requiredSize(triangleSizeDp, triangleSizeDp)
                     .background(Color.White)
                     .border(BorderStroke(borderWidthDp, Color.Red), triangle)
@@ -271,7 +280,8 @@
                 borderPx = border.toPx()
             }
             Box(
-                Modifier.testTag(roundRectTag)
+                Modifier
+                    .testTag(roundRectTag)
                     .size(size)
                     .background(Color.White)
                     .border(
@@ -333,7 +343,8 @@
         rule.setContent {
             SemanticParent {
                 Box(
-                    Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                    Modifier
+                        .size(40.0f.toDp(), 40.0f.toDp())
                         .background(color = Color.Blue)
                         .border(BorderStroke(10.0f.toDp(), Color.Red), rtlAwareShape)
                 ) {}
@@ -358,7 +369,8 @@
             SemanticParent {
                 CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
                     Box(
-                        Modifier.size(40.0f.toDp(), 40.0f.toDp())
+                        Modifier
+                            .size(40.0f.toDp(), 40.0f.toDp())
                             .background(color = Color.Blue)
                             .border(BorderStroke(10.0f.toDp(), Color.Red), rtlAwareShape)
                     ) {}
@@ -403,7 +415,8 @@
                 borderStrokePx = borderStrokeDp.toPx()
             }
             Box(
-                Modifier.testTag(testTag)
+                Modifier
+                    .testTag(testTag)
                     .requiredSize(20.dp, 20.dp)
                     .background(Color.White)
                     .border(
@@ -437,7 +450,7 @@
             )
             assertEquals(
                 Color.White,
-                pixelMap[ width / 2, height / 2]
+                pixelMap[width / 2, height / 2]
             )
         }
 
@@ -465,7 +478,7 @@
             )
             assertEquals(
                 Color.White,
-                pixelMap[ width / 2, height / 2]
+                pixelMap[width / 2, height / 2]
             )
         }
     }
@@ -510,7 +523,8 @@
                 borderStrokePx = borderWidthDp.toPx()
             }
             Box(
-                Modifier.testTag(testTag)
+                Modifier
+                    .testTag(testTag)
                     .requiredSize(bubbleWidthDp, bubbleHeightDp)
                     .background(Color.White)
                     .padding(top = arrowLengthDp)
@@ -582,6 +596,43 @@
         }
     }
 
+    @Test
+    fun equalInputs_shouldResolveToEquals_withColor() {
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.border(10.dp, Color.Red)
+            } else {
+                Modifier.border(5.dp, Color.Red)
+            }
+        }
+    }
+
+    @Test
+    fun equalInputs_shouldResolveToEquals_withBorderStroke() {
+        val borderStroke1 = BorderStroke(10.dp, Color.Blue)
+        val borderStroke2 = BorderStroke(5.dp, Color.Blue)
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.border(borderStroke1)
+            } else {
+                Modifier.border(borderStroke2)
+            }
+        }
+    }
+
+    @Test
+    fun equalInputs_shouldResolveToEquals_withBrush() {
+        val brush1 = Brush.horizontalGradient()
+        val brush2 = Brush.verticalGradient()
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.border(10.dp, brush1, shape)
+            } else {
+                Modifier.border(10.dp, brush2, shape)
+            }
+        }
+    }
+
     private fun calculateContainerShape(
         density: Density,
         arrowBaseWidthDp: Dp,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 4ad872c..38b2b99 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -40,6 +40,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.first
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -1520,7 +1521,7 @@
                 "onClickLabel",
                 "onClick",
                 "role",
-                "indication",
+                "indicationNodeFactory",
                 "interactionSource"
             )
         }
@@ -2627,6 +2628,111 @@
             assertThat(interactions.first()).isInstanceOf(PressInteraction.Press::class.java)
         }
     }
+
+    @Test
+    fun composedOverload_nonEquality() {
+        val onClick = {}
+        val modifier1 = Modifier.clickable(onClick = onClick)
+        val modifier2 = Modifier.clickable(onClick = onClick)
+
+        // The composed overload can never compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun nullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        assertModifierIsPure { toggleInput ->
+            Modifier.clickable(
+                interactionSource = null,
+                indication = null,
+                enabled = toggleInput,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nonNullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.clickable(
+                interactionSource = interactionSource,
+                indication = null,
+                enabled = toggleInput,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.clickable(
+                interactionSource = null,
+                indication = indication,
+                enabled = toggleInput,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val indication = TestIndication {}
+        val modifier1 = Modifier.clickable(
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.clickable(
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun nonNullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.clickable(
+                interactionSource = interactionSource,
+                indication = indication,
+                enabled = toggleInput,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nonNullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndication {}
+        val modifier1 = Modifier.clickable(
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.clickable(
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
 }
 
 /**
@@ -2635,7 +2741,7 @@
  * @param onCreate lambda executed when the instance is created with [rememberUpdatedInstance]
  */
 @Suppress("DEPRECATION_ERROR")
-private class TestIndication(val onCreate: (InteractionSource) -> Unit) : Indication {
+internal class TestIndication(val onCreate: (InteractionSource) -> Unit) : Indication {
     @Deprecated("Super method is deprecated")
     @Composable
     override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 5e233f4..4b0debf 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -1967,7 +1967,7 @@
                 "onDoubleClick",
                 "onLongClick",
                 "onLongClickLabel",
-                "indication",
+                "indicationNodeFactory",
                 "interactionSource"
             )
         }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
index b87f6d5..b813eef 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -888,7 +889,6 @@
             assertThat(modifier.valueOverride).isNull()
             assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
                 "enabled",
-                "canDrag",
                 "reverseDirection",
                 "interactionSource",
                 "startDragImmediately",
@@ -899,6 +899,19 @@
         }
     }
 
+    @Test
+    fun equalInputs_shouldResolveToEquals() {
+        val state = Draggable2DState { }
+
+        assertModifierIsPure { toggleInput ->
+            if (toggleInput) {
+                Modifier.draggable2D(state, enabled = false)
+            } else {
+                Modifier.draggable2D(state, enabled = true)
+            }
+        }
+    }
+
     private fun setDraggable2DContent(draggable2DFactory: @Composable () -> Modifier) {
         rule.setContent {
             Box {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
index d1ebf73..38724ad 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.first
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -482,6 +483,17 @@
     }
 
     @Test
+    fun focusable_equality() {
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.focusable(
+                enabled = toggleInput,
+                interactionSource = interactionSource,
+            )
+        }
+    }
+
+    @Test
     fun focusable_requestsBringIntoView_whenFocused() {
         // Arrange.
         val requestedRects = mutableListOf<Rect?>()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/HoverableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/HoverableTest.kt
index 5f0322e..cc2a51c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/HoverableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/HoverableTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -69,7 +70,7 @@
     }
 
     @Test
-    fun hoverableText_testInspectorValue() {
+    fun hoverableTest_testInspectorValue() {
         rule.setContent {
             val interactionSource = remember { MutableInteractionSource() }
             val modifier = Modifier.hoverable(interactionSource) as InspectableValue
@@ -83,6 +84,17 @@
         }
     }
 
+    @Test
+    fun hoverableTest_equality() {
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.hoverable(
+                interactionSource = interactionSource,
+                enabled = toggleInput,
+            )
+        }
+    }
+
     @OptIn(ExperimentalTestApi::class)
     @ExperimentalComposeUiApi
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
index c19d2d2..c1da808 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.InspectableModifier
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.ValueElement
@@ -96,39 +95,11 @@
 
     @SdkSuppress(maxSdkVersion = 27)
     @Test
-    fun magnifier_inspectorValue_whenNotSupported() {
-        val sourceCenterLambda: Density.() -> Offset = { Offset(42f, 42f) }
-        val magnifierCenterLambda: Density.() -> Offset = { Offset(42f, 42f) }
-        val modifier = Modifier.magnifier(
-            sourceCenter = sourceCenterLambda,
-            magnifierCenter = magnifierCenterLambda
-        ).findInspectableValue()!!
-        assertThat(modifier.nameFallback).isEqualTo("magnifier (not supported)")
-        assertThat(modifier.valueOverride).isNull()
-        assertThat(modifier.inspectableElements.toList()).containsExactly(
-            ValueElement("sourceCenter", sourceCenterLambda),
-            ValueElement("magnifierCenter", magnifierCenterLambda),
-            ValueElement("zoom", Float.NaN),
-            ValueElement("size", DpSize.Unspecified),
-            ValueElement("cornerRadius", Dp.Unspecified),
-            ValueElement("elevation", Dp.Unspecified),
-            ValueElement("clippingEnabled", true),
-        )
-    }
-
-    @SdkSuppress(maxSdkVersion = 27)
-    @Test
     fun magnifier_returnsEmptyModifier_whenNotSupported() {
         val modifier = Modifier.magnifier(sourceCenter = { Offset.Zero })
         val elements: List<Modifier.Element> =
             modifier.foldIn(emptyList()) { elements, element -> elements + element }
-
-        // Modifier.magnifier doesn't have its own modifier class, so instead of checking for the
-        // absence of the actual modifier we just check that the only modifier returned is the
-        // InspectableValue (which actually has two elements).
-        assertThat(elements).hasSize(2)
-        assertThat(elements.first()).isInstanceOf(InspectableValue::class.java)
-        assertThat(elements.last()).isInstanceOf(InspectableModifier.End::class.java)
+        assertThat(elements).hasSize(0)
     }
 
     @SdkSuppress(minSdkVersion = 28)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 58e9233..0fb5fd03 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -46,6 +46,7 @@
 import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
@@ -89,6 +90,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.ScrollWheel
+import androidx.compose.ui.test.click
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -130,6 +132,7 @@
 import org.junit.After
 import org.junit.Assert
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -2355,6 +2358,7 @@
     }
 
     @Test
+    @Ignore("b/175010956") // re-enable when we come back to fling continuation fix
     fun nestedScrollable_shouldImmediateScrollIfChildIsFlinging() {
         var innerDelta = 0f
         var middleDelta = 0f
@@ -2437,6 +2441,48 @@
         }
     }
 
+    @Test
+    fun nestedScrollable_noFlingContinuationInCrossAxis_shouldAllowClicksOnCrossAxis_scrollable() {
+        var clicked = 0
+        rule.setContentAndGetScope {
+            LazyColumn(Modifier.testTag("column")) {
+                item {
+                    Box(modifier = Modifier
+                        .size(20.dp)
+                        .background(Color.Red)
+                        .clickable { clicked++ })
+                }
+                item {
+                    LazyRow(Modifier.testTag("list")) {
+                        items(100) {
+                            Box(modifier = Modifier
+                                .size(20.dp)
+                                .background(Color.Blue))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag("list", useUnmergedTree = true).performTouchInput {
+            swipeLeft()
+        }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNodeWithTag("column").performTouchInput {
+            click(Offset(10f, 10f))
+        }
+
+        rule.mainClock.autoAdvance = true
+
+        rule.runOnIdle {
+            assertThat(clicked).isEqualTo(1)
+        }
+    }
+
     // b/179417109 Double checks that in a nested scroll cycle, the parent post scroll
     // consumption is taken into consideration.
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TestDragAndDrop.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TestDragAndDrop.kt
index ed8a71a..71883a6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TestDragAndDrop.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TestDragAndDrop.kt
@@ -20,7 +20,7 @@
 import android.net.Uri
 import android.view.DragEvent
 import android.view.View
-import androidx.compose.foundation.text2.input.internal.DragAndDropTestUtils
+import androidx.compose.foundation.text.input.internal.DragAndDropTestUtils
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Density
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermissionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermissionTest.kt
index 38a4e7a..368e64c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermissionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermissionTest.kt
@@ -20,7 +20,7 @@
 import android.view.DragEvent
 import androidx.compose.foundation.TestActivity
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.text2.input.internal.DragAndDropTestUtils
+import androidx.compose.foundation.text.input.internal.DragAndDropTestUtils
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -51,7 +51,8 @@
         rule.setContent {
             Box(Modifier.then(TestElement { testNode = it }))
         }
-        val event = DragAndDropEvent(DragAndDropTestUtils.makeImageDragEvent(
+        val event = DragAndDropEvent(
+            DragAndDropTestUtils.makeImageDragEvent(
             DragEvent.ACTION_DROP,
             Uri.parse("content://com.example/content.png")
         ))
@@ -70,7 +71,8 @@
         rule.setContent {
             Box(Modifier.then(TestElement { testNode = it }))
         }
-        val event = DragAndDropEvent(DragAndDropTestUtils.makeImageDragEvent(
+        val event = DragAndDropEvent(
+            DragAndDropTestUtils.makeImageDragEvent(
             DragEvent.ACTION_DROP,
             Uri.parse("file://com.example/content.png")
         ))
@@ -92,7 +94,8 @@
                 Box(Modifier.then(TestElement { testNode = it }))
             }
         }
-        val event = DragAndDropEvent(DragAndDropTestUtils.makeImageDragEvent(
+        val event = DragAndDropEvent(
+            DragAndDropTestUtils.makeImageDragEvent(
             DragEvent.ACTION_DROP,
             Uri.parse("file://com.example/content.png")
         ))
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
index 98952e5..8addd6e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
@@ -27,8 +27,11 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.ParameterizedComposeTestRule
+import androidx.compose.testutils.createParameterizedComposeTestRule
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
@@ -46,8 +49,6 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.LayoutDirection.Ltr
@@ -65,83 +66,101 @@
 @OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(Parameterized::class)
-class LazyListFocusMoveTest(param: Param) {
+class LazyListFocusMoveTest(param: FocusDirectionWrapper) {
+
     @get:Rule
-    val rule = createComposeRule()
+    val rule = createParameterizedComposeTestRule<Param>()
 
     // We need to wrap the inline class parameter in another class because Java can't instantiate
     // the inline class.
+    data class FocusDirectionWrapper(val direction: FocusDirection)
+
     class Param(
-        val focusDirection: FocusDirection,
         val reverseLayout: Boolean,
         val layoutDirection: LayoutDirection
     ) {
-        override fun toString() = "focusDirection=$focusDirection " +
+        override fun toString() =
             "reverseLayout=$reverseLayout " +
-            "layoutDirection=$layoutDirection"
+                "layoutDirection=$layoutDirection"
     }
 
-    private val focusDirection = param.focusDirection
-    private val reverseLayout = param.reverseLayout
-    private val layoutDirection = param.layoutDirection
-    private val initiallyFocused: FocusRequester = FocusRequester()
+    private val focusDirection = param.direction
+    private var initiallyFocused: FocusRequester = FocusRequester()
     private var isLazyListFocused by mutableStateOf(false)
     private val isFocused = mutableMapOf<Int, Boolean>()
     private lateinit var lazyListState: LazyListState
     private lateinit var focusManager: FocusManager
 
     companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun initParameters() = buildList {
-            for (direction in listOf(Previous, Next, Left, Right, Up, Down, Enter, Exit)) {
-                for (reverseLayout in listOf(true, false)) {
-                    for (layoutDirection in listOf(Ltr, Rtl)) {
-                        add(Param(direction, reverseLayout, layoutDirection))
-                    }
+        val ParamsToRun = buildList {
+            for (reverseLayout in listOf(true, false)) {
+                for (layoutDirection in listOf(Ltr, Rtl)) {
+                    add(Param(reverseLayout, layoutDirection))
                 }
             }
         }
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = buildList {
+            for (direction in arrayOf(Previous, Next, Left, Right, Up, Down, Enter, Exit)) {
+                add(FocusDirectionWrapper(direction))
+            }
+        }
+    }
+
+    private fun resetTestCase() {
+        isLazyListFocused = false
+        isFocused.clear()
+        initiallyFocused = FocusRequester()
     }
 
     @Test
     fun moveFocusAmongVisibleItems() {
         // Arrange.
         rule.setTestContent {
-            lazyList(50.dp, lazyListState) {
+            lazyList(50.dp, it, lazyListState) {
                 item { FocusableBox(0) }
                 item { FocusableBox(1, initiallyFocused) }
                 item { FocusableBox(2) }
             }
         }
-        rule.runOnIdle { initiallyFocused.requestFocus() }
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle { initiallyFocused.requestFocus() }
 
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                        }
 
-                Up -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
-                Previous -> assertThat(isFocused[0]).isTrue()
-                Next -> assertThat(isFocused[2]).isTrue()
-                Enter -> assertThat(isFocused[1]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                        Right -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                        Previous -> assertThat(isFocused[0]).isTrue()
+                        Next -> assertThat(isFocused[2]).isTrue()
+                        Enter -> assertThat(isFocused[1]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -150,40 +169,49 @@
     fun moveFocusAmongVisibleItems_userScrollIsOff() {
         // Arrange.
         rule.setTestContent {
-            lazyList(50.dp, lazyListState, userScrollEnabled = false) {
+            lazyList(50.dp, it, lazyListState, userScrollEnabled = false) {
                 item { FocusableBox(0) }
                 item { FocusableBox(1, initiallyFocused) }
                 item { FocusableBox(2) }
             }
         }
-        rule.runOnIdle { initiallyFocused.requestFocus() }
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle { initiallyFocused.requestFocus() }
 
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                        }
 
-                Up -> assertThat(isFocused[if (reverseLayout) 2 else 0]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 0 else 2]).isTrue()
-                Previous -> assertThat(isFocused[0]).isTrue()
-                Next -> assertThat(isFocused[2]).isTrue()
-                Enter -> assertThat(isFocused[1]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                        Right -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 2 else 0]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 0 else 2]).isTrue()
+                        Previous -> assertThat(isFocused[0]).isTrue()
+                        Next -> assertThat(isFocused[2]).isTrue()
+                        Enter -> assertThat(isFocused[1]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
+                rule.waitForIdle()
             }
         }
     }
@@ -192,54 +220,65 @@
     fun moveFocusToItemThatIsJustBeyondBounds() {
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState) {
+            lazyList(30.dp, it, lazyListState) {
                 items(5) { FocusableBox(it) }
                 item { FocusableBox(5, initiallyFocused) }
                 items(5) { FocusableBox(it + 6) }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(4) }
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(4) }
 
-            // Move focus to the last visible item.
-            initiallyFocused.requestFocus()
-            when (focusDirection) {
-                Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(focusDirection)
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    initiallyFocused.requestFocus()
+                    when (focusDirection) {
+                        Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(
+                            focusDirection
+                        )
+
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
                 }
 
-                else -> unsupportedDirection()
-            }
-        }
-
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 7 else 3]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 3 else 7]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 3 else 7]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 7 else 3]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 7 else 3]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 3 else 7]).isTrue()
+                        }
 
-                Up -> assertThat(isFocused[if (reverseLayout) 7 else 3]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 3 else 7]).isTrue()
-                Previous -> assertThat(isFocused[3]).isTrue()
-                Next -> assertThat(isFocused[7]).isTrue()
-                Enter -> assertThat(isFocused[5]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                        Right -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 3 else 7]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 7 else 3]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 7 else 3]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 3 else 7]).isTrue()
+                        Previous -> assertThat(isFocused[3]).isTrue()
+                        Next -> assertThat(isFocused[7]).isTrue()
+                        Enter -> assertThat(isFocused[5]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -248,44 +287,53 @@
     fun moveFocusToItemThatIsJustBeyondBounds_userScrollIsOff() {
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState, userScrollEnabled = false) {
+            lazyList(30.dp, it, lazyListState, userScrollEnabled = false) {
                 items(5) { FocusableBox(it) }
                 item { FocusableBox(5, initiallyFocused) }
                 items(5) { FocusableBox(it + 6) }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(4) }
+        with(rule) {
+            forEachParameter(ParamsToRun) {
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(4) }
 
-            // Move focus to the last visible item.
-            initiallyFocused.requestFocus()
-            when (focusDirection) {
-                Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(focusDirection)
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    initiallyFocused.requestFocus()
+                    when (focusDirection) {
+                        Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(
+                            focusDirection
+                        )
+
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
+                }
+                val firstVisibleItemIndex = lazyListState.firstVisibleItemIndex
+                // Act.
+                runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                else -> unsupportedDirection()
+                // Assert We Did Not Move
+                runOnIdle {
+                    assertThat(lazyListState.firstVisibleItemIndex).isEqualTo(firstVisibleItemIndex)
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
-        val firstVisibleItemIndex = lazyListState.firstVisibleItemIndex
-        // Act.
-        rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert We Did Not Move
-        rule.runOnIdle {
-            assertThat(lazyListState.firstVisibleItemIndex).isEqualTo(firstVisibleItemIndex)
-        }
     }
 
     @Test
     fun moveFocusToItemThatIsFarBeyondBounds() {
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState) {
+            lazyList(30.dp, it, lazyListState) {
                 items(5) { FocusableBox(it) }
                 items(100) { Box(Modifier.size(10.dp)) }
                 item { FocusableBox(105) }
@@ -295,48 +343,65 @@
                 items(5) { FocusableBox(it + 208) }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(105) }
-            initiallyFocused.requestFocus()
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(105) }
+                    initiallyFocused.requestFocus()
 
-            // Move focus to the last visible item.
-            when (focusDirection) {
-                Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(focusDirection)
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    when (focusDirection) {
+                        Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(
+                            focusDirection
+                        )
+
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
                 }
 
-                else -> unsupportedDirection()
-            }
-        }
-
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 208 else 4]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 4 else 208]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 4 else 208]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 208 else 4]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr ->
+                                assertThat(isFocused[if (param.reverseLayout) 208 else 4]).isTrue()
 
-                Up -> assertThat(isFocused[if (reverseLayout) 208 else 4]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 4 else 208]).isTrue()
-                Previous -> assertThat(isFocused[4]).isTrue()
-                Next -> assertThat(isFocused[208]).isTrue()
-                Enter -> assertThat(isFocused[106]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                            Rtl ->
+                                assertThat(isFocused[if (param.reverseLayout) 4 else 208]).isTrue()
+                        }
+
+                        Right -> when (param.layoutDirection) {
+                            Ltr ->
+                                assertThat(isFocused[if (param.reverseLayout) 4 else 208]).isTrue()
+
+                            Rtl ->
+                                assertThat(isFocused[if (param.reverseLayout) 208 else 4]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 208 else 4]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 4 else 208]).isTrue()
+                        Previous -> assertThat(isFocused[4]).isTrue()
+                        Next -> assertThat(isFocused[208]).isTrue()
+                        Enter -> assertThat(isFocused[106]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -345,56 +410,77 @@
     fun moveFocusToItemThatIsBeyondBoundsAndInANestedLazyList() {
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState) {
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 0) } } }
+            lazyList(30.dp, it, lazyListState) {
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 0) } }
+                }
                 item { FocusableBox(3) }
                 item { FocusableBox(4, initiallyFocused) }
                 item { FocusableBox(5) }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 6) } } }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 6) } }
+                }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(1) }
-            initiallyFocused.requestFocus()
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(1) }
+                    initiallyFocused.requestFocus()
 
-            // Move focus to the last visible item.
-            when (focusDirection) {
-                Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(focusDirection)
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    when (focusDirection) {
+                        Left, Right, Up, Down, Previous, Next -> focusManager.moveFocus(
+                            focusDirection
+                        )
+
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
                 }
 
-                else -> unsupportedDirection()
-            }
-        }
-
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 2 else 6]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 2 else 6]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 2 else 6]).isTrue()
+                        }
 
-                Up -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 2 else 6]).isTrue()
-                Previous -> assertThat(isFocused[2]).isTrue()
-                Next -> assertThat(isFocused[6]).isTrue()
-                Enter -> assertThat(isFocused[4]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                        Right -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 2 else 6]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 2 else 6]).isTrue()
+                        Previous -> assertThat(isFocused[2]).isTrue()
+                        Next -> assertThat(isFocused[6]).isTrue()
+                        Enter -> assertThat(isFocused[4]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -407,57 +493,75 @@
 
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState) {
+            lazyList(30.dp, it, lazyListState) {
                 item { FocusableBox(0) }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 1) } } }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 1) } }
+                }
                 item { FocusableBox(4, initiallyFocused) }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 5) } } }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 5) } }
+                }
                 item { FocusableBox(8) }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(1, 10) }
-            initiallyFocused.requestFocus()
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(1, 10) }
+                    initiallyFocused.requestFocus()
 
-            // Move focus to the last visible item.
-            when (focusDirection) {
-                Left, Right, Up, Down -> focusManager.moveFocus(focusDirection)
-                Previous, Next -> repeat(3) { focusManager.moveFocus(focusDirection) }
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    when (focusDirection) {
+                        Left, Right, Up, Down -> focusManager.moveFocus(focusDirection)
+                        Previous, Next -> repeat(3) { focusManager.moveFocus(focusDirection) }
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
                 }
 
-                else -> unsupportedDirection()
-            }
-        }
-
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 0 else 8]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 0 else 8]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 0 else 8]).isTrue()
+                        }
 
-                Up -> assertThat(isFocused[if (reverseLayout) 8 else 0]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 0 else 8]).isTrue()
-                Previous -> assertThat(isFocused[0]).isTrue()
-                Next -> assertThat(isFocused[8]).isTrue()
-                Enter -> assertThat(isFocused[4]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                        Right -> when (param.layoutDirection) {
+                            Ltr -> assertThat(isFocused[if (param.reverseLayout) 0 else 8]).isTrue()
+                            Rtl -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 8 else 0]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 0 else 8]).isTrue()
+                        Previous -> assertThat(isFocused[0]).isTrue()
+                        Next -> assertThat(isFocused[8]).isTrue()
+                        Enter -> assertThat(isFocused[4]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -470,57 +574,91 @@
 
         // Arrange.
         rule.setTestContent {
-            lazyList(30.dp, lazyListState) {
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 0) } } }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 3) } } }
+            lazyList(30.dp, it, lazyListState) {
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 0) } }
+                }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 3) } }
+                }
                 item { FocusableBox(6, initiallyFocused) }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 7) } } }
-                item { lazyListCrossAxis(30.dp) { items(3) { FocusableBox(it + 10) } } }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 7) } }
+                }
+                item {
+                    lazyListCrossAxis(
+                        30.dp,
+                        it
+                    ) { items(3) { FocusableBox(it + 10) } }
+                }
             }
         }
-        rule.runOnIdle {
-            // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(2, 0) }
-            initiallyFocused.requestFocus()
+        with(rule) {
+            forEachParameter(ParamsToRun) { param ->
+                runOnIdle {
+                    // Scroll so that the focused item is in the middle.
+                    runBlocking { lazyListState.scrollToItem(2, 0) }
+                    initiallyFocused.requestFocus()
 
-            // Move focus to the last visible item.
-            when (focusDirection) {
-                Left, Right, Up, Down -> focusManager.moveFocus(focusDirection)
-                Previous, Next -> repeat(3) { focusManager.moveFocus(focusDirection) }
-                Enter, Exit -> {
-                    // Do nothing
+                    // Move focus to the last visible item.
+                    when (focusDirection) {
+                        Left, Right, Up, Down -> focusManager.moveFocus(focusDirection)
+                        Previous, Next -> repeat(3) { focusManager.moveFocus(focusDirection) }
+                        Enter, Exit -> {
+                            // Do nothing
+                        }
+
+                        else -> unsupportedDirection()
+                    }
                 }
 
-                else -> unsupportedDirection()
-            }
-        }
-
-        // Act.
-        val success = rule.runOnIdle {
-            focusManager.moveFocus(focusDirection)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(success).apply { if (focusDirection == Enter) isFalse() else isTrue() }
-            when (focusDirection) {
-                Left -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 12 else 0]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 2 else 10]).isTrue()
+                // Act.
+                val success = runOnIdle {
+                    focusManager.moveFocus(focusDirection)
                 }
 
-                Right -> when (layoutDirection) {
-                    Ltr -> assertThat(isFocused[if (reverseLayout) 2 else 10]).isTrue()
-                    Rtl -> assertThat(isFocused[if (reverseLayout) 12 else 0]).isTrue()
-                }
+                // Assert.
+                runOnIdle {
+                    assertThat(success).apply {
+                        if (focusDirection == Enter) isFalse() else isTrue()
+                    }
+                    when (focusDirection) {
+                        Left -> when (param.layoutDirection) {
+                            Ltr ->
+                                assertThat(isFocused[if (param.reverseLayout) 12 else 0]).isTrue()
 
-                Up -> assertThat(isFocused[if (reverseLayout) 12 else 0]).isTrue()
-                Down -> assertThat(isFocused[if (reverseLayout) 2 else 10]).isTrue()
-                Previous -> assertThat(isFocused[2]).isTrue()
-                Next -> assertThat(isFocused[10]).isTrue()
-                Enter -> assertThat(isFocused[6]).isTrue()
-                Exit -> assertThat(isLazyListFocused).isTrue()
-                else -> unsupportedDirection()
+                            Rtl ->
+                                assertThat(isFocused[if (param.reverseLayout) 2 else 10]).isTrue()
+                        }
+
+                        Right -> when (param.layoutDirection) {
+                            Ltr ->
+                                assertThat(isFocused[if (param.reverseLayout) 2 else 10]).isTrue()
+
+                            Rtl ->
+                                assertThat(isFocused[if (param.reverseLayout) 12 else 0]).isTrue()
+                        }
+
+                        Up -> assertThat(isFocused[if (param.reverseLayout) 12 else 0]).isTrue()
+                        Down -> assertThat(isFocused[if (param.reverseLayout) 2 else 10]).isTrue()
+                        Previous -> assertThat(isFocused[2]).isTrue()
+                        Next -> assertThat(isFocused[10]).isTrue()
+                        Enter -> assertThat(isFocused[6]).isTrue()
+                        Exit -> assertThat(isLazyListFocused).isTrue()
+                        else -> unsupportedDirection()
+                    }
+                }
+                runOnIdle { runBlocking { lazyListState.scrollToItem(0) } }
+                resetTestCase()
             }
         }
     }
@@ -536,12 +674,16 @@
         )
     }
 
-    private fun ComposeContentTestRule.setTestContent(composable: @Composable () -> Unit) {
+    private fun ParameterizedComposeTestRule<Param>.setTestContent(
+        composable: @Composable (param: Param) -> Unit
+    ) {
         setContent {
-            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
-                focusManager = LocalFocusManager.current
-                lazyListState = rememberLazyListState()
-                composable()
+            key(it) {
+                CompositionLocalProvider(LocalLayoutDirection provides it.layoutDirection) {
+                    focusManager = LocalFocusManager.current
+                    lazyListState = rememberLazyListState()
+                    composable(it)
+                }
             }
         }
     }
@@ -549,9 +691,10 @@
     @Composable
     private fun lazyList(
         size: Dp,
+        param: Param,
         state: LazyListState = rememberLazyListState(),
         userScrollEnabled: Boolean = true,
-        content: LazyListScope.() -> Unit
+        content: LazyListScope.() -> Unit,
     ) {
         when (focusDirection) {
             Left, Right, Enter, Exit, Next, Previous -> LazyRow(
@@ -560,7 +703,7 @@
                     .onFocusChanged { isLazyListFocused = it.isFocused }
                     .focusable(),
                 state = state,
-                reverseLayout = reverseLayout,
+                reverseLayout = param.reverseLayout,
                 content = content,
                 userScrollEnabled = userScrollEnabled
             )
@@ -571,7 +714,7 @@
                     .onFocusChanged { isLazyListFocused = it.isFocused }
                     .focusable(),
                 state = state,
-                reverseLayout = reverseLayout,
+                reverseLayout = param.reverseLayout,
                 content = content,
                 userScrollEnabled = userScrollEnabled
             )
@@ -583,6 +726,7 @@
     @Composable
     private fun lazyListCrossAxis(
         size: Dp,
+        param: Param,
         state: LazyListState = rememberLazyListState(),
         content: LazyListScope.() -> Unit
     ) {
@@ -590,14 +734,14 @@
             Left, Right, Enter, Exit, Next, Previous -> LazyColumn(
                 modifier = Modifier.size(size),
                 state = state,
-                reverseLayout = reverseLayout,
+                reverseLayout = param.reverseLayout,
                 content = content
             )
 
             Up, Down -> LazyRow(
                 modifier = Modifier.size(size),
                 state = state,
-                reverseLayout = reverseLayout,
+                reverseLayout = param.reverseLayout,
                 content = content
             )
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
index fda112e..5eccf0b 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
@@ -22,8 +22,11 @@
 import androidx.compose.foundation.lazy.list.TrackPlacedElement
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.ParameterizedComposeTestRule
+import androidx.compose.testutils.createParameterizedComposeTestRule
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
@@ -37,8 +40,6 @@
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.LayoutDirection.Ltr
@@ -47,16 +48,13 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
 
 @OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
-@RunWith(Parameterized::class)
-class LazyStaggeredGridBeyondBoundsTest(param: Param) {
+class LazyStaggeredGridBeyondBoundsTest {
 
     @get:Rule
-    val rule = createComposeRule()
+    val rule = createParameterizedComposeTestRule<Param>()
 
     // We need to wrap the inline class parameter in another class because Java can't instantiate
     // the inline class.
@@ -68,21 +66,18 @@
         override fun toString() = "beyondBoundsLayoutDirection=$beyondBoundsLayoutDirection " +
             "reverseLayout=$reverseLayout " +
             "layoutDirection=$layoutDirection"
+
+        internal fun placementComparator(): PlacementComparator {
+            return PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
+        }
     }
 
-    private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
-    private val reverseLayout = param.reverseLayout
-    private val layoutDirection = param.layoutDirection
     private val placedItems = sortedMapOf<Int, Rect>()
     private var beyondBoundsLayout: BeyondBoundsLayout? = null
     private lateinit var lazyStaggeredGridState: LazyStaggeredGridState
-    private val placementComparator =
-        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun initParameters() = buildList {
+        val ParamsToTest = buildList {
             for (beyondBoundsLayoutDirection in listOf(Left, Right, Above, Below, Before, After)) {
                 for (reverseLayout in listOf(false, true)) {
                     for (layoutDirection in listOf(Ltr, Rtl)) {
@@ -93,6 +88,11 @@
         }
     }
 
+    private fun resetTestCase() {
+        placedItems.clear()
+        beyondBoundsLayout = null
+    }
+
     @Test
     fun onlyOneVisibleItemIsPlaced() {
         // Arrange.
@@ -107,9 +107,14 @@
         }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(0)
-            assertThat(visibleItems).containsExactly(0)
+        with(rule) {
+            forEachParameter(ParamsToTest) {
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(0)
+                    assertThat(visibleItems).containsExactly(0)
+                }
+                resetTestCase()
+            }
         }
     }
 
@@ -127,9 +132,14 @@
         }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(0, 1)
-            assertThat(visibleItems).containsExactly(0, 1)
+        with(rule) {
+            forEachParameter(ParamsToTest) {
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(0, 1)
+                    assertThat(visibleItems).containsExactly(0, 1)
+                }
+                resetTestCase()
+            }
         }
     }
 
@@ -147,9 +157,14 @@
         }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(0, 1, 2)
-            assertThat(visibleItems).containsExactly(0, 1, 2)
+        with(rule) {
+            forEachParameter(ParamsToTest) {
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(0, 1, 2)
+                    assertThat(visibleItems).containsExactly(0, 1, 2)
+                }
+                resetTestCase()
+            }
         }
     }
 
@@ -169,22 +184,29 @@
                 }
             }
         }
-        rule.runOnIdle {
-            beyondBoundsLayoutRef = beyondBoundsLayout!!
-            addItems = false
-        }
 
-        // Act.
-        val hasMoreContent = rule.runOnIdle {
-            beyondBoundsLayoutRef.layout(beyondBoundsLayoutDirection) {
-                hasMoreContent
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                runOnIdle {
+                    beyondBoundsLayoutRef = beyondBoundsLayout!!
+                    addItems = false
+                }
+
+                // Act.
+                val hasMoreContent = rule.runOnIdle {
+                    beyondBoundsLayoutRef.layout(param.beyondBoundsLayoutDirection) {
+                        hasMoreContent
+                    }
+                }
+
+                // Assert.
+                runOnIdle {
+                    assertThat(hasMoreContent).isFalse()
+                }
+                resetTestCase()
+                addItems = true
             }
         }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(hasMoreContent).isFalse()
-        }
     }
 
     @Test
@@ -199,188 +221,6 @@
                 )
             }
             item {
-                Box(Modifier
-                    .size(10.toDp())
-                    .trackPlaced(5)
-                    .modifierLocalConsumer {
-                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                    }
-                )
-            }
-            items(5) { index ->
-                Box(
-                    Modifier
-                        .size(10.toDp())
-                        .trackPlaced(index + 6)
-                )
-            }
-        }
-
-        // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                // Assert that the beyond bounds items are present.
-                if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
-                } else {
-                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
-                }
-                assertThat(visibleItems).containsExactly(5, 6, 7)
-
-                assertThat(placedItems.values).isInOrder(placementComparator)
-
-                // Just return true so that we stop as soon as we run this once.
-                // This should result in one extra item being added.
-                true
-            }
-        }
-
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(5, 6, 7)
-            assertThat(visibleItems).containsExactly(5, 6, 7)
-        }
-    }
-
-    @Test
-    fun oneExtraItemBeyondVisibleBounds_multipleCells() {
-        val itemSize = 50
-        val itemSizeDp = itemSize.toDp()
-        // Arrange.
-        rule.setLazyContent(cells = 2, size = itemSizeDp * 3, firstVisibleItem = 10) {
-            // item | item  | x5
-            // item | local | x1
-            // item | item  | x5
-            items(11) { index ->
-                Box(
-                    Modifier
-                        .size(itemSizeDp)
-                        .trackPlaced(index)
-                )
-            }
-            item {
-                Box(Modifier
-                    .size(itemSizeDp)
-                    .trackPlaced(11)
-                    .modifierLocalConsumer {
-                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                    }
-                )
-            }
-            items(10) { index ->
-                Box(
-                    Modifier
-                        .size(itemSizeDp)
-                        .trackPlaced(index + 12)
-                )
-            }
-        }
-
-        // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                // Assert that the beyond bounds items are present.
-                if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems.keys).containsExactly(9, 10, 11, 12, 13, 14, 15)
-                } else {
-                    assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15, 16)
-                }
-                assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
-
-                assertThat(placedItems.values).isInOrder(placementComparator)
-
-                // Just return true so that we stop as soon as we run this once.
-                // This should result in one extra item being added.
-                true
-            }
-        }
-
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15)
-            assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
-        }
-    }
-
-    @Test
-    fun oneExtraItemBeyondVisibleBounds_multipleCells_staggered() {
-        val itemSize = 50
-        val itemSizeDp = itemSize.toDp()
-        // Arrange.
-        rule.setLazyContent(cells = 3, size = itemSizeDp * 2, firstVisibleItem = 4) {
-            // -------------
-            // |   | 1 |   |
-            // | 0 |---| 2 |
-            // |   | 3 |   |
-            // |-----------|
-            // |     4     |
-            // |-----------|
-            // |   | 6 |   |
-            // | 5 |---| 7 |
-            // |   | 8 |   |
-            // -------------
-            items(4) { index ->
-                Box(
-                    Modifier
-                        .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
-                        .trackPlaced(index)
-                )
-            }
-            item(span = StaggeredGridItemSpan.FullLine) {
-                Box(Modifier
-                    .size(itemSizeDp)
-                    .trackPlaced(4)
-                    .modifierLocalConsumer {
-                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                    }
-                )
-            }
-            items(4) { index ->
-                Box(
-                    Modifier
-                        .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
-                        .trackPlaced(index + 5)
-                )
-            }
-        }
-
-        // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                // Assert that the beyond bounds items are present.
-                if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
-                    assertThat(visibleItems).containsExactly(4, 5, 6, 7)
-                } else {
-                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7, 8)
-                    assertThat(visibleItems).containsExactly(4, 5, 6, 7)
-                }
-                // Just return true so that we stop as soon as we run this once.
-                // This should result in one extra item being added.
-                true
-            }
-        }
-
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
-            assertThat(visibleItems).containsExactly(4, 5, 6, 7)
-        }
-    }
-
-    @Test
-    fun twoExtraItemsBeyondVisibleBounds() {
-        // Arrange.
-        var extraItemCount = 2
-        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
-            items(5) { index ->
-                Box(
-                    Modifier
-                        .size(10.toDp())
-                        .trackPlaced(index)
-                )
-            }
-            item {
                 Box(
                     Modifier
                         .size(10.toDp())
@@ -400,32 +240,237 @@
         }
 
         // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                if (--extraItemCount > 0) {
-                    // Return null to continue the search.
-                    null
-                } else {
-                    // Assert that the beyond bounds items are present.
-                    if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
-                    } else {
-                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        // Assert that the beyond bounds items are present.
+                        if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
+                        } else {
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
+                        }
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                        assertThat(placedItems.values).isInOrder(param.placementComparator())
+
+                        // Just return true so that we stop as soon as we run this once.
+                        // This should result in one extra item being added.
+                        true
                     }
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
-
-                    assertThat(placedItems.values).isInOrder(placementComparator)
-
-                    // Return true to stop the search.
-                    true
                 }
+
+                // Assert that the beyond bounds items are removed.
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                }
+                resetTestCase()
+            }
+        }
+    }
+
+    @Test
+    fun oneExtraItemBeyondVisibleBounds_multipleCells() {
+        val itemSize = 50
+        val itemSizeDp = itemSize.toDp()
+        // Arrange.
+        rule.setLazyContent(cells = 2, size = itemSizeDp * 3, firstVisibleItem = 10) {
+            // item | item  | x5
+            // item | local | x1
+            // item | item  | x5
+            items(11) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(11)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                )
+            }
+            items(10) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(index + 12)
+                )
             }
         }
 
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(5, 6, 7)
-            assertThat(visibleItems).containsExactly(5, 6, 7)
+        // Act.
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        // Assert that the beyond bounds items are present.
+                        if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                            assertThat(placedItems.keys).containsExactly(9, 10, 11, 12, 13, 14, 15)
+                        } else {
+                            assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15, 16)
+                        }
+                        assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+
+                        assertThat(placedItems.values).isInOrder(param.placementComparator())
+
+                        // Just return true so that we stop as soon as we run this once.
+                        // This should result in one extra item being added.
+                        true
+                    }
+                }
+
+                // Assert that the beyond bounds items are removed.
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15)
+                    assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+                }
+                resetTestCase()
+            }
+        }
+    }
+
+    @Test
+    fun oneExtraItemBeyondVisibleBounds_multipleCells_staggered() {
+        val itemSize = 50
+        val itemSizeDp = itemSize.toDp()
+        // Arrange.
+        rule.setLazyContent(cells = 3, size = itemSizeDp * 2, firstVisibleItem = 4) {
+            // -------------
+            // |   | 1 |   |
+            // | 0 |---| 2 |
+            // |   | 3 |   |
+            // |-----------|
+            // |     4     |
+            // |-----------|
+            // |   | 6 |   |
+            // | 5 |---| 7 |
+            // |   | 8 |   |
+            // -------------
+            items(4) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
+                        .trackPlaced(index)
+                )
+            }
+            item(span = StaggeredGridItemSpan.FullLine) {
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(4)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                )
+            }
+            items(4) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
+                        .trackPlaced(index + 5)
+                )
+            }
+        }
+
+        // Act.
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        // Assert that the beyond bounds items are present.
+                        if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                            assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
+                            assertThat(visibleItems).containsExactly(4, 5, 6, 7)
+                        } else {
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7, 8)
+                            assertThat(visibleItems).containsExactly(4, 5, 6, 7)
+                        }
+                        // Just return true so that we stop as soon as we run this once.
+                        // This should result in one extra item being added.
+                        true
+                    }
+                }
+
+                // Assert that the beyond bounds items are removed.
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
+                    assertThat(visibleItems).containsExactly(4, 5, 6, 7)
+                }
+                resetTestCase()
+            }
+        }
+    }
+
+    @Test
+    fun twoExtraItemsBeyondVisibleBounds() {
+        // Arrange.
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(5)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+
+        // Act.
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                var extraItemCount = 2
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        if (--extraItemCount > 0) {
+                            // Return null to continue the search.
+                            null
+                        } else {
+                            // Assert that the beyond bounds items are present.
+                            if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                                assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
+                            } else {
+                                assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
+                            }
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                            assertThat(placedItems.values).isInOrder(param.placementComparator())
+
+                            // Return true to stop the search.
+                            true
+                        }
+                    }
+                }
+
+                // Assert that the beyond bounds items are removed.
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                }
+                resetTestCase()
+            }
         }
     }
 
@@ -460,39 +505,45 @@
         }
 
         // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                if (hasMoreContent) {
-                    // Just return null so that we keep adding more items till we reach the end.
-                    null
-                } else {
-                    // Assert that the beyond bounds items are present.
-                    if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
-                    } else {
-                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        if (hasMoreContent) {
+                            // Just return null so that we keep adding more items till we reach the end.
+                            null
+                        } else {
+                            // Assert that the beyond bounds items are present.
+                            if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                                assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
+                            } else {
+                                assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
+                            }
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                            // Verify if the placed item offsets are in order.
+                            assertThat(
+                                placedItems.toSortedMap().values
+                            ).isInOrder(param.placementComparator())
+
+                            // Return true to end the search.
+                            true
+                        }
                     }
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
-
-                    // Verify if the placed item offsets are in order.
-                    assertThat(placedItems.toSortedMap().values).isInOrder(placementComparator)
-
-                    // Return true to end the search.
-                    true
                 }
-            }
-        }
 
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                // Assert that the beyond bounds items are removed.
+                runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                }
+                resetTestCase()
+            }
         }
     }
 
     @Test
     fun beyondBoundsLayoutRequest_inDirectionPerpendicularToLazyListOrientation() {
         // Arrange.
-        var beyondBoundsLayoutCount = 0
         rule.setLazyContentInPerpendicularDirection(size = 30.toDp(), firstVisibleItem = 5) {
             items(5) { index ->
                 Box(
@@ -519,49 +570,58 @@
                 )
             }
         }
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(5, 6, 7)
-            assertThat(visibleItems).containsExactly(5, 6, 7)
-        }
-
-        // Act.
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                beyondBoundsLayoutCount++
-                when (beyondBoundsLayoutDirection) {
-                    Left, Right, Above, Below -> {
-                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
-                    }
-                    Before, After -> {
-                        if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
-                            assertThat(visibleItems).containsExactly(5, 6, 7)
-                        } else {
-                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
-                            assertThat(visibleItems).containsExactly(5, 6, 7)
-                        }
-                    }
-                }
-                // Just return true so that we stop as soon as we run this once.
-                // This should result in one extra item being added.
-                true
-            }
-        }
-
-        rule.runOnIdle {
-            when (beyondBoundsLayoutDirection) {
-                Left, Right, Above, Below -> {
-                    assertThat(beyondBoundsLayoutCount).isEqualTo(0)
-                }
-                Before, After -> {
-                    assertThat(beyondBoundsLayoutCount).isEqualTo(1)
-
-                    // Assert that the beyond bounds items are removed.
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                var beyondBoundsLayoutCount = 0
+                runOnIdle {
                     assertThat(placedItems.keys).containsExactly(5, 6, 7)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
-                else -> error("Unsupported BeyondBoundsLayoutDirection")
+
+                // Act.
+                runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        beyondBoundsLayoutCount++
+                        when (param.beyondBoundsLayoutDirection) {
+                            Left, Right, Above, Below -> {
+                                assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                                assertThat(visibleItems).containsExactly(5, 6, 7)
+                            }
+
+                            Before, After -> {
+                                if (param.expectedExtraItemsBeforeVisibleBounds()) {
+                                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
+                                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                                } else {
+                                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
+                                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                                }
+                            }
+                        }
+                        // Just return true so that we stop as soon as we run this once.
+                        // This should result in one extra item being added.
+                        true
+                    }
+                }
+
+                runOnIdle {
+                    when (param.beyondBoundsLayoutDirection) {
+                        Left, Right, Above, Below -> {
+                            assertThat(beyondBoundsLayoutCount).isEqualTo(0)
+                        }
+
+                        Before, After -> {
+                            assertThat(beyondBoundsLayoutCount).isEqualTo(1)
+
+                            // Assert that the beyond bounds items are removed.
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
+                        }
+
+                        else -> error("Unsupported BeyondBoundsLayoutDirection")
+                    }
+                }
+                resetTestCase()
             }
         }
     }
@@ -597,81 +657,95 @@
         }
 
         // Act.
-        var count = 0
-        rule.runOnUiThread {
-            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
-                // Assert that we don't keep iterating when there is no ending condition.
-                assertThat(count++).isLessThan(lazyStaggeredGridState.layoutInfo.totalItemsCount)
-                // Always return null to continue the search.
-                null
-            }
-        }
+        with(rule) {
+            forEachParameter(ParamsToTest) { param ->
+                var count = 0
+                rule.runOnUiThread {
+                    beyondBoundsLayout!!.layout(param.beyondBoundsLayoutDirection) {
+                        // Assert that we don't keep iterating when there is no ending condition.
+                        assertThat(count++)
+                            .isLessThan(lazyStaggeredGridState.layoutInfo.totalItemsCount)
+                        // Always return null to continue the search.
+                        null
+                    }
+                }
 
-        // Assert that the beyond bounds items are removed.
-        rule.runOnIdle {
-            assertThat(placedItems.keys).containsExactly(5, 6, 7)
-            assertThat(visibleItems).containsExactly(5, 6, 7)
+                // Assert that the beyond bounds items are removed.
+                rule.runOnIdle {
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                }
+                resetTestCase()
+            }
         }
     }
 
-    private fun ComposeContentTestRule.setLazyContent(
+    private fun ParameterizedComposeTestRule<Param>.setLazyContent(
         size: Dp,
         firstVisibleItem: Int,
         cells: Int = 1,
         content: LazyStaggeredGridScope.() -> Unit
     ) {
         setContent {
-            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
-                lazyStaggeredGridState = rememberLazyStaggeredGridState(firstVisibleItem)
-                when (beyondBoundsLayoutDirection) {
-                    Left, Right, Before, After ->
-                        LazyHorizontalStaggeredGrid(
-                            rows = StaggeredGridCells.Fixed(cells),
-                            modifier = Modifier.size(size),
-                            state = lazyStaggeredGridState,
-                            reverseLayout = reverseLayout,
-                            content = content
-                        )
-                    Above, Below ->
-                        LazyVerticalStaggeredGrid(
-                            columns = StaggeredGridCells.Fixed(cells),
-                            modifier = Modifier.size(size),
-                            state = lazyStaggeredGridState,
-                            reverseLayout = reverseLayout,
-                            content = content
-                        )
-                    else -> unsupportedDirection()
+            key(it) {
+                CompositionLocalProvider(LocalLayoutDirection provides it.layoutDirection) {
+                    lazyStaggeredGridState = rememberLazyStaggeredGridState(firstVisibleItem)
+                    when (it.beyondBoundsLayoutDirection) {
+                        Left, Right, Before, After ->
+                            LazyHorizontalStaggeredGrid(
+                                rows = StaggeredGridCells.Fixed(cells),
+                                modifier = Modifier.size(size),
+                                state = lazyStaggeredGridState,
+                                reverseLayout = it.reverseLayout,
+                                content = content
+                            )
+
+                        Above, Below ->
+                            LazyVerticalStaggeredGrid(
+                                columns = StaggeredGridCells.Fixed(cells),
+                                modifier = Modifier.size(size),
+                                state = lazyStaggeredGridState,
+                                reverseLayout = it.reverseLayout,
+                                content = content
+                            )
+
+                        else -> unsupportedDirection()
+                    }
                 }
             }
         }
     }
 
-    private fun ComposeContentTestRule.setLazyContentInPerpendicularDirection(
+    private fun ParameterizedComposeTestRule<Param>.setLazyContentInPerpendicularDirection(
         size: Dp,
         firstVisibleItem: Int,
         content: LazyStaggeredGridScope.() -> Unit
     ) {
         setContent {
-            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
-                lazyStaggeredGridState = rememberLazyStaggeredGridState(firstVisibleItem)
-                when (beyondBoundsLayoutDirection) {
-                    Left, Right, Before, After ->
-                        LazyVerticalStaggeredGrid(
-                            columns = StaggeredGridCells.Fixed(1),
-                            modifier = Modifier.size(size),
-                            state = lazyStaggeredGridState,
-                            reverseLayout = reverseLayout,
-                            content = content
-                        )
-                    Above, Below ->
-                        LazyHorizontalStaggeredGrid(
-                            rows = StaggeredGridCells.Fixed(1),
-                            modifier = Modifier.size(size),
-                            state = lazyStaggeredGridState,
-                            reverseLayout = reverseLayout,
-                            content = content
-                        )
-                    else -> unsupportedDirection()
+            key(it) {
+                CompositionLocalProvider(LocalLayoutDirection provides it.layoutDirection) {
+                    lazyStaggeredGridState = rememberLazyStaggeredGridState(firstVisibleItem)
+                    when (it.beyondBoundsLayoutDirection) {
+                        Left, Right, Before, After ->
+                            LazyVerticalStaggeredGrid(
+                                columns = StaggeredGridCells.Fixed(1),
+                                modifier = Modifier.size(size),
+                                state = lazyStaggeredGridState,
+                                reverseLayout = it.reverseLayout,
+                                content = content
+                            )
+
+                        Above, Below ->
+                            LazyHorizontalStaggeredGrid(
+                                rows = StaggeredGridCells.Fixed(1),
+                                modifier = Modifier.size(size),
+                                state = lazyStaggeredGridState,
+                                reverseLayout = it.reverseLayout,
+                                content = content
+                            )
+
+                        else -> unsupportedDirection()
+                    }
                 }
             }
         }
@@ -682,7 +756,7 @@
     private val visibleItems: List<Int>
         get() = lazyStaggeredGridState.layoutInfo.visibleItemsInfo.map { it.index }
 
-    private fun expectedExtraItemsBeforeVisibleBounds() = when (beyondBoundsLayoutDirection) {
+    private fun Param.expectedExtraItemsBeforeVisibleBounds() = when (beyondBoundsLayoutDirection) {
         Right -> if (layoutDirection == Ltr) reverseLayout else !reverseLayout
         Left -> if (layoutDirection == Ltr) !reverseLayout else reverseLayout
         Above -> !reverseLayout
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
index 3b27ebd..effaf84 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build.VERSION.SDK_INT
 import androidx.compose.foundation.TapIndicationDelay
+import androidx.compose.foundation.TestIndication
 import androidx.compose.foundation.TestIndicationNodeFactory
 import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
@@ -36,6 +37,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.first
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -638,7 +640,7 @@
             assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
                 "selected",
                 "interactionSource",
-                "indication",
+                "indicationNodeFactory",
                 "enabled",
                 "role",
                 "onClick"
@@ -1191,4 +1193,113 @@
             assertThat(interactions.first()).isInstanceOf(PressInteraction.Press::class.java)
         }
     }
+
+    @Test
+    fun composedOverload_nonEquality() {
+        val onClick = {}
+        val modifier1 = Modifier.selectable(selected = true, onClick = onClick)
+        val modifier2 = Modifier.selectable(selected = true, onClick = onClick)
+
+        // The composed overload can never compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun nullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        assertModifierIsPure { toggleInput ->
+            Modifier.selectable(
+                selected = toggleInput,
+                interactionSource = null,
+                indication = null,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nonNullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.selectable(
+                selected = toggleInput,
+                interactionSource = interactionSource,
+                indication = null,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.selectable(
+                selected = toggleInput,
+                interactionSource = null,
+                indication = indication,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val indication = TestIndication {}
+        val modifier1 = Modifier.selectable(
+            selected = true,
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.selectable(
+            selected = true,
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun nonNullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.selectable(
+                selected = toggleInput,
+                interactionSource = interactionSource,
+                indication = indication,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun nonNullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndication {}
+        val modifier1 = Modifier.selectable(
+            selected = true,
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.selectable(
+            selected = true,
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
index 9ab9743..e95c2bd9 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build.VERSION.SDK_INT
 import androidx.compose.foundation.TapIndicationDelay
+import androidx.compose.foundation.TestIndication
 import androidx.compose.foundation.TestIndicationNodeFactory
 import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
@@ -39,6 +40,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.first
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -730,7 +732,7 @@
             assertThat(modifier.valueOverride).isNull()
             assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
                 "value",
-                "indication",
+                "indicationNodeFactory",
                 "interactionSource",
                 "enabled",
                 "role",
@@ -768,7 +770,7 @@
             assertThat(modifier.valueOverride).isNull()
             assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
                 "state",
-                "indication",
+                "indicationNodeFactory",
                 "interactionSource",
                 "enabled",
                 "role",
@@ -1410,7 +1412,7 @@
     }
 
     @Test
-    fun triStateToggleableTest_noInteractionSource_lazilyCreated_pointerInput() {
+    fun triStateToggleable_noInteractionSource_lazilyCreated_pointerInput() {
         var created = false
         val state = ToggleableState(value = false)
         lateinit var interactionSource: InteractionSource
@@ -1453,4 +1455,222 @@
             assertThat(interactions.first()).isInstanceOf(PressInteraction.Press::class.java)
         }
     }
+
+    @Test
+    fun toggleable_composedOverload_nonEquality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val modifier1 = Modifier.toggleable(value = true, onValueChange = onValueChange)
+        val modifier2 = Modifier.toggleable(value = true, onValueChange = onValueChange)
+
+        // The composed overload can never compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun toggleable_nullInteractionSourceNullIndication_equality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        assertModifierIsPure { toggleInput ->
+            Modifier.toggleable(
+                value = toggleInput,
+                interactionSource = null,
+                indication = null,
+                onValueChange = onValueChange
+            )
+        }
+    }
+
+    @Test
+    fun toggleable_nonNullInteractionSourceNullIndication_equality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.toggleable(
+                value = toggleInput,
+                interactionSource = interactionSource,
+                indication = null,
+                onValueChange = onValueChange
+            )
+        }
+    }
+
+    @Test
+    fun toggleable_nullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.toggleable(
+                value = toggleInput,
+                interactionSource = null,
+                indication = indication,
+                onValueChange = onValueChange
+            )
+        }
+    }
+
+    @Test
+    fun toggleable_nullInteractionSourceNonNullIndication_nonEquality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val indication = TestIndication {}
+        val modifier1 = Modifier.toggleable(
+            value = true,
+            interactionSource = null,
+            indication = indication,
+            onValueChange = onValueChange
+        )
+        val modifier2 = Modifier.toggleable(
+            value = true,
+            interactionSource = null,
+            indication = indication,
+            onValueChange = onValueChange
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun toggleable_nonNullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.toggleable(
+                value = toggleInput,
+                interactionSource = interactionSource,
+                indication = indication,
+                onValueChange = onValueChange
+            )
+        }
+    }
+
+    @Test
+    fun toggleable_nonNullInteractionSourceNonNullIndication_nonEquality() {
+        val onValueChange: (Boolean) -> Unit = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndication {}
+        val modifier1 = Modifier.toggleable(
+            value = true,
+            interactionSource = interactionSource,
+            indication = indication,
+            onValueChange = onValueChange
+        )
+        val modifier2 = Modifier.toggleable(
+            value = true,
+            interactionSource = interactionSource,
+            indication = indication,
+            onValueChange = onValueChange
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun triStateToggleable_composedOverload_nonEquality() {
+        val onClick = {}
+        val modifier1 = Modifier.triStateToggleable(state = ToggleableState.On, onClick = onClick)
+        val modifier2 = Modifier.triStateToggleable(state = ToggleableState.On, onClick = onClick)
+
+        // The composed overload can never compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun triStateToggleable_nullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        assertModifierIsPure { toggleInput ->
+            Modifier.triStateToggleable(
+                state = ToggleableState(toggleInput),
+                interactionSource = null,
+                indication = null,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun triStateToggleable_nonNullInteractionSourceNullIndication_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        assertModifierIsPure { toggleInput ->
+            Modifier.triStateToggleable(
+                state = ToggleableState(toggleInput),
+                interactionSource = interactionSource,
+                indication = null,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun triStateToggleable_nullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.triStateToggleable(
+                state = ToggleableState(toggleInput),
+                interactionSource = null,
+                indication = indication,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun triStateToggleable_nullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val indication = TestIndication {}
+        val modifier1 = Modifier.triStateToggleable(
+            state = ToggleableState.On,
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.triStateToggleable(
+            state = ToggleableState.On,
+            interactionSource = null,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
+
+    @Test
+    fun triStateToggleable_nonNullInteractionSourceNonNullIndicationNodeFactory_equality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndicationNodeFactory({}, { _, _ -> })
+        assertModifierIsPure { toggleInput ->
+            Modifier.triStateToggleable(
+                state = ToggleableState(toggleInput),
+                interactionSource = interactionSource,
+                indication = indication,
+                onClick = onClick
+            )
+        }
+    }
+
+    @Test
+    fun triStateToggleable_nonNullInteractionSourceNonNullIndication_nonEquality() {
+        val onClick = {}
+        val interactionSource = MutableInteractionSource()
+        val indication = TestIndication {}
+        val modifier1 = Modifier.triStateToggleable(
+            state = ToggleableState.On,
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+        val modifier2 = Modifier.triStateToggleable(
+            state = ToggleableState.On,
+            interactionSource = interactionSource,
+            indication = indication,
+            onClick = onClick
+        )
+
+        // Indication requires composed, so cannot compare equal
+        assertThat(modifier1).isNotEqualTo(modifier2)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
index bc5a7fb..cc4fccf 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
@@ -24,11 +24,11 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.setFocusableContent
+import androidx.compose.foundation.text.input.InputMethodInterceptor
 import androidx.compose.foundation.text.input.internal.InputMethodManager
 import androidx.compose.foundation.text.input.internal.LegacyTextInputMethodRequest
 import androidx.compose.foundation.text.input.internal.inputMethodManagerFactory
 import androidx.compose.foundation.text.input.internal.update
-import androidx.compose.foundation.text2.input.InputMethodInterceptor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt
new file mode 100644
index 0000000..d47bf31
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt
@@ -0,0 +1,593 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.foundation.text.selection.FakeTextToolbar
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.performTextReplacement
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicSecureTextFieldTest {
+
+    // Keyboard shortcut tests for BasicSecureTextField are in TextFieldKeyEventTest
+
+    @get:Rule
+    val rule = createComposeRule().apply {
+        mainClock.autoAdvance = false
+    }
+
+    @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicSecureTextField"
+    private val imm = FakeInputMethodManager()
+
+    @Before
+    fun setUp() {
+        immRule.setFactory { imm }
+    }
+
+    @Test
+    fun passwordSemanticsAreSet() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = remember {
+                    TextFieldState("Hello", initialSelectionInChars = TextRange(0, 1))
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Password))
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
+        // temporarily define copy and cut actions on BasicSecureTextField but make them no-op
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CopyText))
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CutText))
+    }
+
+    @Test
+    fun lastTypedCharacterIsRevealedTemporarily() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("a")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
+            rule.mainClock.advanceTimeBy(1500)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022")
+        }
+    }
+
+    @Test
+    fun lastTypedCharacterIsRevealed_hidesAfterAnotherCharacterIsTyped() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("a")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
+            performTextInput("b")
+            rule.mainClock.advanceTimeBy(50)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022b")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun lastTypedCharacterIsRevealed_whenInsertedInMiddle() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022")
+            performTextInputSelection(TextRange(1))
+            performTextInput("d")
+            rule.mainClock.advanceTimeBy(50)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022d\u2022\u2022")
+        }
+    }
+
+    @Test
+    fun lastTypedCharacterIsRevealed_hidesAfterFocusIsLost() {
+        inputMethodInterceptor.setContent {
+            Column {
+                BasicSecureTextField(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier.testTag(Tag)
+                )
+                Box(
+                    modifier = Modifier
+                        .size(1.dp)
+                        .testTag("otherFocusable")
+                        .focusable()
+                )
+            }
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("a")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
+            rule.onNodeWithTag("otherFocusable")
+                .requestFocus()
+            rule.mainClock.advanceTimeBy(50)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022")
+        }
+    }
+
+    @Test
+    fun lastTypedCharacterIsRevealed_hidesAfterAnotherCharacterRemoved() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeBy(200)
+            performTextInput("d")
+            rule.mainClock.advanceTimeBy(50)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022d")
+            performTextReplacement("bcd")
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022")
+        }
+    }
+
+    @Test
+    fun obfuscationMethodVisible_doesNotHideAnything() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                textObfuscationMode = TextObfuscationMode.Visible,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("abc")
+            rule.mainClock.advanceTimeBy(1500)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("abc")
+        }
+    }
+
+    @Test
+    fun obfuscationMethodVisible_revealsEverythingWhenSwitchedTo() {
+        var obfuscationMode by mutableStateOf(TextObfuscationMode.Hidden)
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                textObfuscationMode = obfuscationMode,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeBy(200)
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022")
+            obfuscationMode = TextObfuscationMode.Visible
+            rule.mainClock.advanceTimeByFrame()
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("abc")
+        }
+    }
+
+    @Test
+    fun obfuscationMethodHidden_hidesEverything() {
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                textObfuscationMode = TextObfuscationMode.Hidden,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeByFrame()
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022")
+            performTextInput("d")
+            rule.mainClock.advanceTimeByFrame()
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022\u2022")
+        }
+    }
+
+    @Test
+    fun obfuscationMethodHidden_hidesEverythingWhenSwitchedTo() {
+        var obfuscationMode by mutableStateOf(TextObfuscationMode.Visible)
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = rememberTextFieldState(),
+                textObfuscationMode = obfuscationMode,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("abc")
+            rule.mainClock.advanceTimeByFrame()
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("abc")
+            obfuscationMode = TextObfuscationMode.Hidden
+            rule.mainClock.advanceTimeByFrame()
+            assertThat(fetchTextLayoutResult().layoutInput.text.text)
+                .isEqualTo("\u2022\u2022\u2022")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_copy() {
+        val state = TextFieldState("Hello World!")
+        val clipboardManager = FakeClipboardManager("initial")
+        inputMethodInterceptor.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicSecureTextField(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("initial")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_cut() {
+        val state = TextFieldState("Hello World!")
+        val clipboardManager = FakeClipboardManager("initial")
+        inputMethodInterceptor.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicSecureTextField(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("initial")
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowCopyOrCut() {
+        var copyOptionAvailable = false
+        var cutOptionAvailable = false
+        var showMenuRequested = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+                showMenuRequested = true
+                copyOptionAvailable = onCopyRequested != null
+                cutOptionAvailable = onCutRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        inputMethodInterceptor.setContent {
+            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+                BasicSecureTextField(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        // We need to disable the traversalMode to show the toolbar.
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.SetSelection) {
+            it(0, 5, false)
+        }
+
+        rule.runOnIdle {
+            assertThat(showMenuRequested).isTrue()
+            assertThat(copyOptionAvailable).isFalse()
+            assertThat(cutOptionAvailable).isFalse()
+        }
+    }
+
+    @Test
+    fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
+        var text by mutableStateOf("hello")
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = { text = it },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.runOnIdle {
+            text = "world"
+        }
+        // Auto-advance is disabled.
+        rule.mainClock.advanceTimeByFrame()
+
+        assertThat(
+            rule.onNodeWithTag(Tag).fetchSemanticsNode().config[SemanticsProperties.EditableText]
+                .text
+        ).isEqualTo("world")
+    }
+
+    @Test
+    fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
+        var text by mutableStateOf("hello")
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = { text = it },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            text = "world"
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_onFocus() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        // Act: wiggle the cursor around a bit.
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        // Act: wiggle the composition around a bit
+        inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
+        inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        rule.runOnIdle {
+            text = "hello"
+        }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            text = "hello"
+        }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun inputMethod_doesNotRestart_inResponseToKeyEvents() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
+        inputMethodInterceptor.setContent {
+            BasicSecureTextField(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            requestFocus()
+            imm.resetCalls()
+
+            performKeyInput { pressKey(Key.Backspace) }
+            performTextInputSelection(TextRange.Zero)
+            performKeyInput { pressKey(Key.Delete) }
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(4, 4, -1, -1)")
+            imm.expectCall("updateSelection(0, 0, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    private fun requestFocus(tag: String) =
+        rule.onNodeWithTag(tag).requestFocus()
+
+    private fun assertTextSelection(expected: TextRange) {
+        val selection = rule.onNodeWithTag(Tag).fetchSemanticsNode()
+            .config.getOrNull(SemanticsProperties.TextSelectionRange)
+        assertWithMessage("Expected selection to be $expected")
+            .that(selection).isEqualTo(expected)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt
new file mode 100644
index 0000000..9d2205a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.Build
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+class BasicTextField2DrawPhaseToggleTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var state: TextFieldState
+
+    private val fontSize = 20.sp
+    private val textStyle = TextStyle(
+        fontSize = fontSize,
+        fontFamily = TEST_FONT_FAMILY
+    )
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun colorChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var color by mutableStateOf(Color.Red)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle.copy(color = color),
+                modifier = Modifier.background(Color.White)
+            )
+        }
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(Color.Red)
+            .assertContainsColor(Color.White)
+            .assertDoesNotContainColor(Color.Blue)
+
+        color = Color.Blue
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(Color.Blue)
+            .assertContainsColor(Color.White)
+            .assertDoesNotContainColor(Color.Red)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun brushChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var brush by mutableStateOf(
+            Brush.linearGradient(listOf(Color.Red, Color.Blue), end = Offset(20f, 20f))
+        )
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle.copy(brush = brush),
+                // use brush also for background to get rid of weird antialiasing edges
+                modifier = Modifier.background(brush)
+            )
+        }
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertPixelConsistency { color ->
+                // gradient should not contain any discernible level of green channel
+                color.green <= 0.02f
+            }
+
+        brush = Brush.linearGradient(listOf(Color.Red, Color.Green), end = Offset(20f, 20f))
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertPixelConsistency { color ->
+                // gradient should not contain any discernible level of blue channel
+                color.blue <= 0.02f
+            }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun shadowChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var shadow by mutableStateOf<Shadow?>(null)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle.copy(color = Color.White, shadow = shadow),
+                modifier = Modifier.background(Color.White)
+            )
+        }
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertPixelConsistency { color ->
+                color == Color.White
+            }
+
+        shadow = Shadow(blurRadius = 8f)
+
+        val pixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
+        for (x in 0 until pixelMap.width) {
+            for (y in 0 until pixelMap.height) {
+                if (pixelMap[x, y] != Color.White) return // everything is fine, end the test
+            }
+        }
+        throw AssertionError("Could not detect a Shadow in the view")
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textDecorationChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var textDecoration by mutableStateOf(TextDecoration.None)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle.copy(
+                    textDecoration = textDecoration
+                ),
+                modifier = Modifier.background(Color.White)
+            )
+        }
+
+        val initialPixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
+
+        textDecoration = TextDecoration.Underline
+
+        val underlinedPixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
+
+        assertThat(initialPixelMap.width to initialPixelMap.height)
+            .isEqualTo(underlinedPixelMap.width to underlinedPixelMap.height)
+
+        // They should not be the same due to underline.
+        assertThat(initialPixelMap.buffer).isNotEqualTo(underlinedPixelMap.buffer)
+    }
+}
+
+/**
+ * Instead of looking for an exact match of pixel values, this assertion provides the ability to
+ * judge each pixel individually to whether it fits a predefined filter.
+ */
+private inline fun ImageBitmap.assertPixelConsistency(
+    filter: (color: Color) -> Boolean
+) {
+    val pixel = toPixelMap()
+    for (x in 0 until width) {
+        for (y in 0 until height) {
+            val pxColor = pixel[x, y]
+            if (!filter(pxColor)) {
+                throw AssertionError(
+                    "Pixel at [$x, $y] with the value of [$pxColor] is unexpected!"
+                )
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt
new file mode 100644
index 0000000..5e68e3b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt
@@ -0,0 +1,420 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalFoundationApi::class,
+    ExperimentalTestApi::class,
+)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicTextField2ImmIntegrationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicTextField2"
+    private val imm = FakeInputMethodManager()
+
+    @Before
+    fun setUp() {
+        immRule.setFactory { imm }
+    }
+
+    @Test
+    fun becomesTextEditor_whenFocusGained() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            commitText("hello", 0)
+            assertThat(state.text.toString()).isEqualTo("hello")
+        }
+    }
+
+    @Test
+    fun stopsBeingTextEditor_whenFocusLost() {
+        val state = TextFieldState()
+        lateinit var focusManager: FocusManager
+        inputMethodInterceptor.setTextFieldTestContent {
+            focusManager = LocalFocusManager.current
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+        requestFocus(Tag)
+        rule.runOnIdle {
+            focusManager.clearFocus()
+        }
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun stopsBeingTextEditor_whenChangedToReadOnly() {
+        val state = TextFieldState()
+        var readOnly by mutableStateOf(false)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(state, Modifier.testTag(Tag), readOnly = readOnly)
+        }
+        requestFocus(Tag)
+        inputMethodInterceptor.assertSessionActive()
+
+        readOnly = true
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun stopsBeingTextEditor_whenChangedToDisabled() {
+        val state = TextFieldState()
+        var enabled by mutableStateOf(true)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(state, Modifier.testTag(Tag), enabled = enabled)
+        }
+        requestFocus(Tag)
+        inputMethodInterceptor.assertSessionActive()
+
+        enabled = false
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun staysTextEditor_whenFocusTransferred() {
+        val state1 = TextFieldState()
+        val state2 = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(state1, Modifier.testTag(Tag + 1))
+            BasicTextField2(state2, Modifier.testTag(Tag + 2))
+        }
+
+        requestFocus(Tag + 1)
+        requestFocus(Tag + 2)
+
+        inputMethodInterceptor.withInputConnection {
+            commitText("hello", 0)
+            endBatchEdit()
+            assertThat(state2.text.toString()).isEqualTo("hello")
+            assertThat(state1.text.toString()).isEmpty()
+        }
+    }
+
+    @Test
+    fun stopsBeingTextEditor_whenRemovedFromCompositionWhileFocused() {
+        val state = TextFieldState()
+        var compose by mutableStateOf(true)
+        inputMethodInterceptor.setTextFieldTestContent {
+            if (compose) {
+                BasicTextField2(state, Modifier.testTag(Tag))
+            }
+        }
+        requestFocus(Tag)
+        rule.runOnIdle {
+            compose = false
+        }
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun inputRestarted_whenStateInstanceChanged() {
+        val state1 = TextFieldState()
+        val state2 = TextFieldState()
+        var state by mutableStateOf(state1)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+        requestFocus(Tag)
+
+        state = state2
+
+        inputMethodInterceptor.withInputConnection {
+            commitText("hello", 0)
+            assertThat(state2.text.toString()).isEqualTo("hello")
+            assertThat(state1.text.toString()).isEmpty()
+        }
+    }
+
+    @Test
+    fun immUpdated_whenFilterChangesText_fromInputConnection() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, new ->
+                    // Force the selection not to change.
+                    val initialSelection = new.selectionInChars
+                    new.append("world")
+                    new.selectCharsIn(initialSelection)
+                }
+            )
+        }
+        requestFocus(Tag)
+        inputMethodInterceptor.withInputConnection {
+            // TODO move this before withInputConnection?
+            imm.resetCalls()
+
+            commitText("hello", 1)
+
+            @Suppress("SpellCheckingInspection")
+            assertThat(state.text.toString()).isEqualTo("helloworld")
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("restartInput")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immUpdated_whenFilterChangesText_fromKeyEvent() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, new ->
+                    val initialSelection = new.selectionInChars
+                    new.append("world")
+                    new.selectCharsIn(initialSelection)
+                }
+            )
+        }
+        requestFocus(Tag)
+        rule.runOnIdle { imm.resetCalls() }
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+
+        rule.runOnIdle {
+            imm.expectCall("restartInput")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @FlakyTest(bugId = 290927588)
+    @Test
+    fun immUpdated_whenFilterChangesSelection_fromInputConnection() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, new -> new.selectAll() }
+            )
+        }
+        requestFocus(Tag)
+        inputMethodInterceptor.withInputConnection {
+            imm.resetCalls()
+            setComposingText("hello", 1)
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(0, 5, 0, 5)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immUpdated_whenEditChangesText() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+        requestFocus(Tag)
+        rule.runOnIdle {
+            imm.resetCalls()
+
+            state.edit {
+                append("hello")
+                placeCursorBeforeCharAt(0)
+            }
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("restartInput")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immUpdated_whenEditChangesSelection() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+        requestFocus(Tag)
+        rule.runOnIdle {
+            imm.resetCalls()
+
+            state.edit {
+                placeCursorAtEnd()
+            }
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(5, 5, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immUpdated_whenEditChangesTextAndSelection() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state, Modifier.testTag(Tag))
+        }
+        requestFocus(Tag)
+        rule.runOnIdle {
+            imm.resetCalls()
+
+            state.edit {
+                append("hello")
+                placeCursorAtEnd()
+            }
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(5, 5, -1, -1)")
+            imm.expectCall("restartInput")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
+            )
+        }
+        requestFocus(Tag)
+        rule.runOnIdle { imm.resetCalls() }
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(1, 1, -1, -1)")
+            imm.expectCall("updateSelection(0, 0, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    @Test
+    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword_fromTransformation() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = object : InputTransformation {
+                    override val keyboardOptions: KeyboardOptions =
+                        KeyboardOptions(keyboardType = KeyboardType.Password)
+
+                    override fun transformInput(
+                        originalValue: TextFieldCharSequence,
+                        valueWithChanges: TextFieldBuffer
+                    ) {
+                        valueWithChanges.append('A')
+                    }
+                }
+            )
+        }
+        requestFocus(Tag)
+        rule.runOnIdle { imm.resetCalls() }
+
+        // "" -key-> "A" -filter> "AA"
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+        // "AA" -key-> "A" -filter> "AA"
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(2, 2, -1, -1)")
+            imm.expectCall("updateSelection(2, 2, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    private fun requestFocus(tag: String) =
+        rule.onNodeWithTag(tag).requestFocus()
+}
+
+// sets the WindowInfo with isWindowFocused is true
+internal fun InputMethodInterceptor.setTextFieldTestContent(
+    content: @Composable () -> Unit
+) {
+    val windowInfo = object : WindowInfo {
+        override val isWindowFocused = true
+    }
+    this.setContent {
+        CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
+            Row {
+                // Extra focusable that takes initial focus when focus is cleared.
+                Box(Modifier.size(10.dp).focusable())
+                Box { content() }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt
new file mode 100644
index 0000000..cd6f9db
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt
@@ -0,0 +1,742 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasImeAction
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.isEditable
+import androidx.compose.ui.test.isEnabled
+import androidx.compose.ui.test.isFocused
+import androidx.compose.ui.test.isNotEnabled
+import androidx.compose.ui.test.isNotFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.performTextReplacement
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class BasicTextField2SemanticsTest : FocusedWindowTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val Tag = "TextField"
+
+    @Test
+    fun defaultSemantics() {
+        rule.setContent {
+            BasicTextField2(
+                modifier = Modifier.testTag(Tag),
+                state = remember { TextFieldState() },
+                decorator = {
+                    Column {
+                        BasicText("label")
+                        it()
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assertEditableTextEquals("")
+            .assertTextEquals("label", includeEditableText = false)
+            .assert(isEditable())
+            .assertHasClickAction()
+            .assert(hasSetTextAction())
+            .assert(hasImeAction(ImeAction.Default))
+            .assert(isNotFocused())
+            .assert(
+                SemanticsMatcher.expectValue(
+                    SemanticsProperties.TextSelectionRange,
+                    TextRange.Zero
+                )
+            )
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetText))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Password))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetSelection))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.GetTextLayoutResult))
+
+        val textLayoutResults = mutableListOf<TextLayoutResult>()
+        rule.onNodeWithTag(Tag)
+            .performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(textLayoutResults) }
+        assert(textLayoutResults.size == 1) { "TextLayoutResult is null" }
+    }
+
+    @Test
+    fun semantics_enabledStatus() {
+        var enabled by mutableStateOf(true)
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                enabled = enabled
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isEnabled())
+
+        enabled = false
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotEnabled())
+    }
+
+    @Test
+    fun semantics_setTextAction() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotFocused())
+            .performTextReplacement("Hello")
+        rule.onNodeWithTag(Tag)
+            .assert(isFocused())
+            .assertTextEquals("Hello")
+
+        assertThat(state.text.toString()).isEqualTo("Hello")
+    }
+
+    @Test
+    fun semantics_performSetTextAction_whenReadOnly() {
+        val state = TextFieldState("", initialSelectionInChars = TextRange(1))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                readOnly = true
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .performTextReplacement("hello")
+
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun semantics_setTextAction_appliesFilter() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, changes ->
+                    if (changes.length > 1) {
+                        val newText = changes.asCharSequence().asSequence().joinToString("-")
+                        changes.replace(0, changes.length, newText)
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotFocused())
+            .performTextReplacement("Hello")
+        rule.onNodeWithTag(Tag)
+            .assert(isFocused())
+            .assertTextEquals("H-e-l-l-o")
+
+        assertThat(state.text.toString()).isEqualTo("H-e-l-l-o")
+    }
+
+    @Test
+    fun semantics_performTextInputAction() {
+        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotFocused())
+            .performTextInput("a")
+        rule.onNodeWithTag(Tag)
+            .assert(isFocused())
+            .assertTextEquals("Haello")
+
+        assertThat(state.text.toString()).isEqualTo("Haello")
+    }
+
+    @Test
+    fun semantics_performTextInputAction_whenReadOnly() {
+        val state = TextFieldState("", initialSelectionInChars = TextRange(1))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                readOnly = true
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .performTextInput("hello")
+
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun semantics_performTextInputAction_appliesFilter() {
+        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, changes ->
+                    val newChange = changes.asCharSequence().replace(Regex("a"), "")
+                    changes.replace(0, changes.length, newChange)
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotFocused())
+            .performTextInput("abc")
+        rule.onNodeWithTag(Tag)
+            .assert(isFocused())
+            .assertTextEquals("Hbcello")
+
+        assertThat(state.text.toString()).isEqualTo("Hbcello")
+    }
+
+    @Test
+    fun semantics_clickAction() {
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assert(isNotFocused())
+            .performSemanticsAction(SemanticsActions.OnClick)
+        rule.onNodeWithTag(Tag)
+            .assert(isFocused())
+    }
+
+    @Test
+    fun semantics_imeOption() {
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assert(hasImeAction(ImeAction.Search))
+    }
+
+    @Test
+    fun contentSemanticsAreSet_inTheFirstComposition() {
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+    }
+
+    @Test
+    fun contentSemanticsAreSet_afterRecomposition() {
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        state.setTextAndPlaceCursorAtEnd("hello2")
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello2")
+    }
+
+    @Test
+    fun selectionSemanticsAreSet_inTheFirstComposition() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    @Test
+    fun selectionSemanticsAreSet_afterRecomposition() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange.Zero)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange.Zero)
+        }
+
+        state.edit {
+            selectCharsIn(TextRange(2))
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun inputSelection_changesSelectionState() {
+        val state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2, 3))
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2, 3))
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun inputSelection_changesSelectionState_appliesFilter() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = { _, changes ->
+                    changes.revertAllChanges()
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2))
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+        }
+    }
+
+    @Test
+    fun textLayoutResultSemanticsAreSet_inTheFirstComposition() {
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .isEqualTo("hello")
+    }
+
+    @Test
+    fun textLayoutResultSemanticsAreUpdated_afterRecomposition() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("")
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .isEqualTo("hello")
+    }
+
+    @Test
+    fun semanticsAreSet_afterStateObjectChanges() {
+        val state1 = TextFieldState("hello", initialSelectionInChars = TextRange.Zero)
+        val state2 = TextFieldState("world", initialSelectionInChars = TextRange(2))
+        var chosenState by mutableStateOf(true)
+        rule.setContent {
+            BasicTextField2(
+                state = if (chosenState) state1 else state2,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange.Zero)
+        }
+
+        chosenState = false
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("world")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    @Test
+    fun semantics_paste_notAvailable_whenDisabledOrReadOnly() {
+        val state = TextFieldState("World!", initialSelectionInChars = TextRange(0))
+        var enabled by mutableStateOf(false)
+        var readOnly by mutableStateOf(false)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                enabled = enabled,
+                readOnly = readOnly
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.PasteText))
+
+        enabled = true
+        readOnly = true
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.PasteText))
+
+        enabled = true
+        readOnly = false
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_paste() {
+        val state = TextFieldState("Here World!")
+        val clipboardManager = FakeClipboardManager("Hello")
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 4))
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_paste_appliesFilter() {
+        val state = TextFieldState("Here World!")
+        val clipboardManager = FakeClipboardManager("Hello")
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag),
+                    inputTransformation = { _, changes ->
+                        // remove all 'l' characters
+                        if (changes.changes.changeCount != 0) {
+                            val newChange = changes.asCharSequence().replace(Regex("l"), "")
+                            changes.replace(0, changes.length, newChange)
+                            changes.placeCursorAtEnd()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 4))
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(9))
+            assertThat(state.text.toString()).isEqualTo("Heo Word!")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_copy() {
+        val state = TextFieldState("Hello World!")
+        val clipboardManager = FakeClipboardManager()
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
+        }
+    }
+
+    @Test
+    fun semantics_copy_disabled_whenSelectionCollapsed() {
+        val state = TextFieldState("Hello World!")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CopyText))
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_copy_appliesFilter() {
+        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
+        val clipboardManager = FakeClipboardManager()
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag),
+                    inputTransformation = { original, changes ->
+                        // reject copy action collapsing the selection
+                        if (changes.selectionInChars != original.selectionInChars) {
+                            changes.revertAllChanges()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_cut() {
+        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
+        val clipboardManager = FakeClipboardManager()
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo(" World!")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun semantics_cut_appliesFilter() {
+        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
+        val clipboardManager = FakeClipboardManager()
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag),
+                    inputTransformation = { _, changes ->
+                        changes.revertAllChanges()
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
+        }
+    }
+
+    @Test
+    fun semantics_cut_notAvailable_whenDisabledOrReadOnly() {
+        val state = TextFieldState("World!", initialSelectionInChars = TextRange(0, 1))
+        var enabled by mutableStateOf(false)
+        var readOnly by mutableStateOf(false)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                enabled = enabled,
+                readOnly = readOnly
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CutText))
+
+        enabled = true
+        readOnly = true
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CutText))
+
+        enabled = true
+        readOnly = false
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CutText))
+    }
+
+    @Test
+    fun semantics_isNotEditable_whenDisabledOrReadOnly() {
+        val state = TextFieldState()
+        var enabled by mutableStateOf(true)
+        var readOnly by mutableStateOf(false)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                enabled = enabled,
+                readOnly = readOnly
+            )
+        }
+        rule.onNodeWithTag(Tag).assert(isEditable())
+
+        enabled = true
+        readOnly = true
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
+
+        enabled = false
+        readOnly = false
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
+
+        enabled = false
+        readOnly = true
+        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
+
+        // Make editable again.
+        enabled = true
+        readOnly = false
+        rule.onNodeWithTag(Tag).assert(isEditable())
+    }
+
+    private fun SemanticsNodeInteraction.assertSelection(expected: TextRange) {
+        val selection = fetchSemanticsNode().config
+            .getOrNull(SemanticsProperties.TextSelectionRange)
+        assertThat(selection).isEqualTo(expected)
+    }
+
+    private fun SemanticsNodeInteraction.assertEditableTextEquals(
+        value: String
+    ): SemanticsNodeInteraction =
+        assert(
+            SemanticsMatcher("${SemanticsProperties.EditableText.name} = '$value'") {
+                it.config.getOrNull(SemanticsProperties.EditableText)?.text.equals(value)
+            }
+        )
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt
new file mode 100644
index 0000000..10548f9
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt
@@ -0,0 +1,1566 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.Build
+import android.text.InputType
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.KeyboardHelper
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.computeSizeForDefaultText
+import androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.testutils.assertPixelColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties.TextSelectionRange
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotFocused
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.performTextReplacement
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.drop
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicTextField2Test {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicTextField2"
+
+    private val imm = FakeInputMethodManager()
+
+    @Test
+    fun textField_rendersEmptyContent() {
+        var textLayoutResult: (() -> TextLayoutResult?)? = null
+        inputMethodInterceptor.setTextFieldTestContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.fillMaxSize(),
+                onTextLayout = { textLayoutResult = it }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(textLayoutResult).isNotNull()
+            assertThat(textLayoutResult?.invoke()?.layoutInput?.text).isEqualTo(AnnotatedString(""))
+        }
+    }
+
+    @Test
+    fun textFieldState_textChange_updatesState() {
+        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+        }
+    }
+
+    @Test
+    fun textFieldState_textChange_updatesSemantics() {
+        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.onNodeWithTag(Tag).assertTextEquals("Hello World!")
+        assertTextSelection(TextRange("Hello World!".length))
+    }
+
+    @Test
+    fun stringValue_textChange_updatesState() {
+        var state by mutableStateOf("Hello ")
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = state,
+                onValueChange = { state = it },
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.runOnIdle {
+            assertThat(state).isEqualTo("Hello World!")
+        }
+    }
+
+    /**
+     * This is a goal that we set for ourselves. Only updating the editing buffer should not cause
+     * BasicTextField to recompose.
+     */
+    @Test
+    fun textField_imeUpdatesDontCauseRecomposition() {
+        val state = TextFieldState()
+        var compositionCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            compositionCount++
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        rule.onNodeWithTag(Tag).performTextInput("world")
+
+        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
+        rule.runOnIdle {
+            assertThat(compositionCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun textField_textStyleFontSizeChange_relayouts() {
+        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
+        var style by mutableStateOf(TextStyle(fontSize = 20.sp))
+        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
+        val textLayoutResults = mutableListOf<TextLayoutResult?>()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                textStyle = style,
+                onTextLayout = { textLayoutResultState = it }
+            )
+
+            LaunchedEffect(Unit) {
+                snapshotFlow { textLayoutResultState?.invoke() }
+                    .drop(1)
+                    .collect { textLayoutResults += it }
+            }
+        }
+
+        style = TextStyle(fontSize = 30.sp)
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.size).isEqualTo(2)
+            assertThat(textLayoutResults.map { it?.layoutInput?.style?.fontSize })
+                .containsExactly(20.sp, 30.sp)
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun textField_textStyleColorChange_doesNotRelayout() {
+        val state = TextFieldState("Hello")
+        var style by mutableStateOf(TextStyle(color = Color.Red))
+        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
+        val textLayoutResults = mutableListOf<TextLayoutResult?>()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                textStyle = style,
+                onTextLayout = { textLayoutResultState = it }
+            )
+
+            LaunchedEffect(Unit) {
+                snapshotFlow { textLayoutResultState?.invoke() }
+                    .drop(1)
+                    .collect { textLayoutResults += it }
+            }
+        }
+
+        style = TextStyle(color = Color.Blue)
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.size).isEqualTo(2)
+            assertThat(textLayoutResults[0]?.multiParagraph)
+                .isSameInstanceAs(textLayoutResults[1]?.multiParagraph)
+            assertThat(textLayoutResults[0]?.layoutInput?.style?.color).isEqualTo(Color.Red)
+            assertThat(textLayoutResults[1]?.layoutInput?.style?.color).isEqualTo(Color.Blue)
+        }
+    }
+
+    @Test
+    fun textField_contentChange_relayouts() {
+        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
+        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
+        val textLayoutResults = mutableListOf<TextLayoutResult?>()
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
+                override val isWindowFocused = true
+            }) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .testTag(Tag),
+                    onTextLayout = { textLayoutResultState = it }
+                )
+            }
+
+            LaunchedEffect(Unit) {
+                snapshotFlow { textLayoutResultState?.invoke() }
+                    .drop(1)
+                    .collect { textLayoutResults += it }
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.map { it?.layoutInput?.text?.text })
+                .containsExactly("Hello ", "Hello World!")
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun textField_focus_showsSoftwareKeyboard() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performClick()
+        rule.onNodeWithTag(Tag).assertIsFocused()
+
+        inputMethodInterceptor.assertSessionActive()
+    }
+
+    @Test
+    fun textField_focus_doesNotShowSoftwareKeyboard_ifDisabled() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                enabled = false,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertIsNotEnabled()
+        rule.onNodeWithTag(Tag).performClick()
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun textField_focus_doesNotShowSoftwareKeyboard_ifReadOnly() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                readOnly = true,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performClick()
+        rule.onNodeWithTag(Tag).assertIsFocused()
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun textField_focus_doesNotShowSoftwareKeyboard_whenNotShowSoftwareKeyboard() {
+        val state = TextFieldState()
+        val focusRequester = FocusRequester()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+                    .focusRequester(focusRequester)
+            )
+        }
+        rule.runOnUiThread {
+            focusRequester.requestFocus()
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).assertIsFocused()
+
+        inputMethodInterceptor.assertNoSessionActive()
+    }
+
+    @Test
+    fun textField_tap_showSoftwareKeyboard_whenNotShowSoftwareKeyboard() {
+        val state = TextFieldState()
+        val focusRequester = FocusRequester()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+                    .focusRequester(focusRequester)
+            )
+        }
+        rule.runOnUiThread {
+            focusRequester.requestFocus()
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).assertIsFocused()
+        rule.onNodeWithTag(Tag).performClick()
+
+        inputMethodInterceptor.assertSessionActive()
+    }
+
+    @Test
+    fun hideKeyboardWhenDisposed() {
+        val keyboardHelper = KeyboardHelper(rule)
+        val state = TextFieldState("initial text")
+        var toggle by mutableStateOf(true)
+        rule.setContent {
+            keyboardHelper.initialize()
+
+            if (toggle) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag("TextField")
+                )
+            }
+        }
+
+        rule.onNodeWithTag("TextField").requestFocus()
+        keyboardHelper.waitForKeyboardVisibility(true)
+        assertTrue(keyboardHelper.isSoftwareKeyboardShown())
+
+        toggle = false
+        rule.waitForIdle()
+
+        keyboardHelper.waitForKeyboardVisibility(false)
+        assertFalse(keyboardHelper.isSoftwareKeyboardShown())
+    }
+
+    @Test
+    fun hideKeyboardWhenFocusCleared() {
+        val keyboardHelper = KeyboardHelper(rule)
+        val state = TextFieldState("initial text")
+        lateinit var focusManager: FocusManager
+        rule.setContent {
+            keyboardHelper.initialize()
+            focusManager = LocalFocusManager.current
+            Row {
+                // Extra focusable that takes initial focus when focus is cleared.
+                Box(
+                    Modifier
+                        .size(10.dp)
+                        .focusable())
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag("TextField")
+                )
+            }
+        }
+
+        rule.onNodeWithTag("TextField").requestFocus()
+        keyboardHelper.waitForKeyboardVisibility(true)
+        assertTrue(keyboardHelper.isSoftwareKeyboardShown())
+
+        rule.runOnIdle {
+            focusManager.clearFocus()
+        }
+
+        keyboardHelper.waitForKeyboardVisibility(false)
+        assertFalse(keyboardHelper.isSoftwareKeyboardShown())
+    }
+
+    @Test
+    fun textField_whenStateObjectChanges_newTextIsRendered() {
+        val state1 = TextFieldState("Hello")
+        val state2 = TextFieldState("World")
+        var toggleState by mutableStateOf(true)
+        val state by derivedStateOf { if (toggleState) state1 else state2 }
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                enabled = true,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("Hello")
+        toggleState = !toggleState
+        rule.onNodeWithTag(Tag).assertTextEquals("World")
+    }
+
+    @Test
+    fun textField_whenStateObjectChanges_restartsInput() {
+        val state1 = TextFieldState("Hello")
+        val state2 = TextFieldState("World")
+        var toggleState by mutableStateOf(true)
+        val state by derivedStateOf { if (toggleState) state1 else state2 }
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                enabled = true,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextReplacement("Compose")
+            assertTextEquals("Compose")
+        }
+        toggleState = !toggleState
+        with(rule.onNodeWithTag(Tag)) {
+            performTextReplacement("Compose2")
+            assertTextEquals("Compose2")
+        }
+        assertThat(state1.text.toString()).isEqualTo("Compose")
+        assertThat(state2.text.toString()).isEqualTo("Compose2")
+    }
+
+    @Test
+    fun textField_passesKeyboardOptionsThrough() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                // We don't need to test all combinations here, that is tested in EditorInfoTest.
+                keyboardOptions = KeyboardOptions(
+                    capitalization = KeyboardCapitalization.Characters,
+                    keyboardType = KeyboardType.Email,
+                    imeAction = ImeAction.Previous
+                )
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
+            assertThat(inputType and EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
+            assertThat(inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textField_appliesFilter_toInputConnection() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = RejectAllTextFilter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection { commitText("hello") }
+        rule.onNodeWithTag(Tag).assertTextEquals("")
+    }
+
+    @Test
+    fun textField_appliesFilter_toSetTextSemanticsAction() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = RejectAllTextFilter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextReplacement("hello")
+        rule.onNodeWithTag(Tag).assertTextEquals("")
+    }
+
+    @Test
+    fun textField_appliesFilter_toInsertTextSemanticsAction() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = RejectAllTextFilter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        rule.onNodeWithTag(Tag).assertTextEquals("")
+    }
+
+    @Test
+    fun textField_appliesFilter_toKeyEvents() {
+        val state = TextFieldState()
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = RejectAllTextFilter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+        rule.onNodeWithTag(Tag).assertTextEquals("")
+    }
+
+    @Test
+    fun textField_appliesFilter_toInputConnection_afterChanging() {
+        val state = TextFieldState()
+        var filter by mutableStateOf<InputTransformation?>(null)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = filter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection { commitText("hello") }
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = RejectAllTextFilter
+
+        inputMethodInterceptor.withInputConnection { commitText("world") }
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = null
+
+        inputMethodInterceptor.withInputConnection { commitText("world") }
+        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
+    }
+
+    @Test
+    fun textField_appliesFilter_toSetTextSemanticsAction_afterChanging() {
+        val state = TextFieldState()
+        var filter by mutableStateOf<InputTransformation?>(null)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = filter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = RejectAllTextFilter
+
+        rule.onNodeWithTag(Tag).performTextReplacement("world")
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = null
+
+        rule.onNodeWithTag(Tag).performTextReplacement("world")
+        rule.onNodeWithTag(Tag).assertTextEquals("world")
+    }
+
+    @Test
+    fun textField_appliesFilter_toInsertTextSemanticsAction_afterChanging() {
+        val state = TextFieldState()
+        var filter by mutableStateOf<InputTransformation?>(null)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = filter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = RejectAllTextFilter
+
+        rule.onNodeWithTag(Tag).performTextInput("world")
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = null
+
+        rule.onNodeWithTag(Tag).performTextInput("world")
+        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
+    }
+
+    @Test
+    fun textField_appliesFilter_toKeyEvents_afterChanging() {
+        val state = TextFieldState()
+        var filter by mutableStateOf<InputTransformation?>(null)
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = filter,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = RejectAllTextFilter
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Spacebar) }
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        filter = null
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Spacebar) }
+        rule.onNodeWithTag(Tag).assertTextEquals("hello ")
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenInputConnectionCommits() {
+        val state = TextFieldState()
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection { commitText("hello") }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0, 0))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenInputConnectionComposes() {
+        val state = TextFieldState()
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection { setComposingText("hello", 1) }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenInputConnectionDeletes() {
+        val state = TextFieldState("hello")
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            beginBatchEdit()
+            finishComposingText()
+            setSelection(5, 5)
+            deleteSurroundingText(1, 0)
+            endBatchEdit()
+        }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(4, 4))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenInputConnectionDeletesViaComposition() {
+        val state = TextFieldState("hello")
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            beginBatchEdit()
+            setComposingRegion(0, 5)
+            setComposingText("h", 1)
+            endBatchEdit()
+        }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(1, 1))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(1, 5))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenKeyEventInserts() {
+        val state = TextFieldState()
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+        requestFocus(Tag)
+
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 1))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenKeyEventDeletes() {
+        val state = TextFieldState("hello")
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(4, 4))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
+        }
+    }
+
+    @Test
+    fun textField_changesAreTracked_whenSemanticsActionInserts() {
+        val state = TextFieldState()
+        lateinit var changes: ChangeList
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                inputTransformation = { _, new ->
+                    if (new.changes.changeCount > 0) {
+                        changes = new.changes
+                    }
+                },
+                modifier = Modifier.testTag(Tag),
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("hello")
+
+        rule.runOnIdle {
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+        }
+    }
+
+    @Test
+    fun textField_filterKeyboardOptions_sentToIme() {
+        val filter = KeyboardOptionsFilter(
+            KeyboardOptions(
+                keyboardType = KeyboardType.Email,
+                imeAction = ImeAction.Previous
+            )
+        )
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = filter,
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
+            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textField_filterKeyboardOptions_mergedWithParams() {
+        val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = filter,
+                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
+            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textField_filterKeyboardOptions_overriddenByParams() {
+        val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = filter,
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
+            )
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_SEARCH).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textField_filterKeyboardOptions_applyWhenFilterChanged() {
+        var filter by mutableStateOf(
+            KeyboardOptionsFilter(
+                KeyboardOptions(
+                    keyboardType = KeyboardType.Email,
+                    imeAction = ImeAction.Previous
+                )
+            )
+        )
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
+                override val isWindowFocused = true
+            }) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier.testTag(Tag),
+                    inputTransformation = filter,
+                )
+            }
+        }
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
+            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
+        }
+
+        filter = KeyboardOptionsFilter(
+            KeyboardOptions(
+                keyboardType = KeyboardType.Decimal,
+                imeAction = ImeAction.Search
+            )
+        )
+
+        inputMethodInterceptor.withEditorInfo {
+            assertThat(imeOptions and EditorInfo.IME_ACTION_SEARCH).isNotEqualTo(0)
+            assertThat(inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotEqualTo(0)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun textField_showsKeyboardAgainWhenTapped_ifFocused() {
+        val testKeyboardController = TestSoftwareKeyboardController(rule)
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(
+                LocalSoftwareKeyboardController provides testKeyboardController
+            ) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+        // Focusing the field will show the keyboard without using the SoftwareKeyboardController.
+        rule.onNodeWithTag(Tag).requestFocus()
+        testKeyboardController.hide()
+
+        // This will go through the SoftwareKeyboardController to show the keyboard, since a session
+        // is already active.
+        rule.onNodeWithTag(Tag).performClick()
+
+        testKeyboardController.assertShown()
+    }
+
+    @Test
+    fun swipingThroughTextField_doesNotGainFocus() {
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // swipe through
+            swipeRight(endX = right + 200, durationMillis = 100)
+        }
+        rule.onNodeWithTag(Tag).assertIsNotFocused()
+    }
+
+    @Test
+    fun swipingTextFieldInScrollableContainer_doesNotGainFocus() {
+        val scrollState = ScrollState(0)
+        inputMethodInterceptor.setTextFieldTestContent {
+            Column(
+                Modifier
+                    .height(100.dp)
+                    .verticalScroll(scrollState)
+            ) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier.testTag(Tag)
+                )
+                Box(Modifier.height(200.dp))
+            }
+        }
+
+        rule.onNodeWithTag(Tag).performTouchInput { swipeUp() }
+        rule.onNodeWithTag(Tag).assertIsNotFocused()
+        assertThat(scrollState.value).isNotEqualTo(0)
+    }
+
+    @Test
+    fun densityChanges_causesRelayout() {
+        val state = TextFieldState("Hello")
+        var density by mutableStateOf(Density(1f))
+        val fontSize = 20.sp
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalDensity provides density) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = TextStyle(
+                        fontFamily = TEST_FONT_FAMILY,
+                        fontSize = fontSize
+                    ),
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        val firstSize = rule.onNodeWithTag(Tag).fetchTextLayoutResult().size
+
+        density = Density(2f)
+
+        val secondSize = rule.onNodeWithTag(Tag).fetchTextLayoutResult().size
+
+        assertThat(secondSize.width).isEqualTo(firstSize.width * 2)
+        assertThat(secondSize.height).isEqualTo(firstSize.height * 2)
+    }
+
+    @Test
+    fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
+        var text by mutableStateOf("hello")
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = { text = it },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        rule.runOnIdle {
+            text = "world"
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("world")
+    }
+
+    @Test
+    fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
+        var text by mutableStateOf("hello")
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = { text = it },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            text = "world"
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_onFocus() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        // Act: wiggle the cursor around a bit.
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        requestFocus(Tag)
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        // Act: wiggle the composition around a bit
+        inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
+        inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+
+        rule.runOnIdle {
+            text = "hello"
+        }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
+        var text by mutableStateOf("")
+        var onValueChangedCount = 0
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                value = text,
+                onValueChange = {
+                    text = it
+                    onValueChangedCount++
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+        assertThat(onValueChangedCount).isEqualTo(0)
+        requestFocus(Tag)
+
+        rule.runOnIdle {
+            text = "hello"
+        }
+
+        rule.runOnIdle {
+            assertThat(onValueChangedCount).isEqualTo(0)
+        }
+    }
+
+    // Regression test for b/311834126
+    @Test
+    fun whenPastingTextThatIncreasesEndOffset_noCrashAndCursorAtEndOfPastedText() {
+        val longText = "Text".repeat(4)
+        val shortText = "Text".repeat(2)
+
+        lateinit var tfs: TextFieldState
+        val clipboardManager = object : ClipboardManager {
+            var contents: AnnotatedString? = null
+
+            override fun setText(annotatedString: AnnotatedString) {
+                contents = annotatedString
+            }
+
+            override fun getText(): AnnotatedString? {
+                return contents
+            }
+        }
+        inputMethodInterceptor.setTextFieldTestContent {
+            tfs = rememberTextFieldState(shortText)
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = tfs,
+                    modifier = Modifier.testTag(Tag),
+                )
+            }
+        }
+        clipboardManager.setText(AnnotatedString(longText))
+        rule.waitForIdle()
+
+        val node = rule.onNodeWithTag(Tag)
+        node.performTouchInput { longClick(center) }
+        rule.waitForIdle()
+
+        node.performSemanticsAction(SemanticsActions.PasteText) { it() }
+        rule.waitForIdle()
+
+        assertThat(tfs.text.toString()).isEqualTo(longText)
+        assertThat(tfs.text.selectionInChars).isEqualTo(TextRange(longText.length))
+    }
+
+    @Test
+    fun selectAll_contextMenuAction_informsImeOfSelectionChange() {
+        immRule.setFactory { imm }
+        val state = TextFieldState("Hello")
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            performContextMenuAction(android.R.id.selectAll)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(imm.expectCall("updateSelection(0, 5, -1, -1)"))
+        }
+    }
+
+    @Test
+    fun cut_contextMenuAction_cutsIntoClipboard() {
+        val clipboardManager = FakeClipboardManager("World")
+        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            performContextMenuAction(android.R.id.cut)
+        }
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.text).isEqualTo("He")
+            assertThat(state.text.toString()).isEqualTo("llo")
+        }
+    }
+
+    @Test
+    fun copy_contextMenuAction_copiesIntoClipboard() {
+        val clipboardManager = FakeClipboardManager("World")
+        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            performContextMenuAction(android.R.id.copy)
+        }
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.text).isEqualTo("He")
+        }
+    }
+
+    @Test
+    fun paste_contextMenuAction_pastesFromClipboard() {
+        val clipboardManager = FakeClipboardManager("World")
+        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 4))
+        inputMethodInterceptor.setTextFieldTestContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.testTag(Tag)
+                )
+            }
+        }
+
+        requestFocus(Tag)
+
+        inputMethodInterceptor.withInputConnection {
+            performContextMenuAction(android.R.id.paste)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("Worldo")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textAlignCenter_defaultWidth() {
+        val fontSize = 50
+        val density = Density(1f, 1f)
+        val textStyle = TextStyle(
+            textAlign = TextAlign.Center,
+            color = Color.Black,
+            fontFamily = TEST_FONT_FAMILY,
+            fontSize = fontSize.sp
+        )
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides density) {
+                BasicTextField2(
+                    modifier = Modifier.testTag(Tag),
+                    state = rememberTextFieldState("A"),
+                    textStyle = textStyle,
+                    lineLimits = TextFieldLineLimits.SingleLine
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).captureToImage().assertHorizontallySymmetrical(fontSize)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textAlignCenter_widthSmallerThanDefaultWidth() {
+        val fontSize = 50
+        val density = Density(1f, 1f)
+        val textStyle = TextStyle(
+            textAlign = TextAlign.Center,
+            color = Color.Black,
+            fontFamily = TEST_FONT_FAMILY,
+            fontSize = fontSize.sp
+        )
+        rule.setContent {
+            val fontFamilyResolver = LocalFontFamilyResolver.current
+            val defaultWidth = computeSizeForDefaultText(
+                style = textStyle,
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                maxLines = 1
+            ).width
+
+            CompositionLocalProvider(LocalDensity provides density) {
+                BasicTextField2(
+                    modifier = Modifier
+                        .testTag(Tag)
+                        .width(defaultWidth.dp / 2),
+                    state = rememberTextFieldState("A"),
+                    textStyle = textStyle,
+                    lineLimits = TextFieldLineLimits.SingleLine
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).captureToImage().assertHorizontallySymmetrical(fontSize)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textAlignCenter_widthLargerThanDefaultWidth() {
+        val fontSize = 50
+        val density = Density(1f, 1f)
+        val textStyle = TextStyle(
+            textAlign = TextAlign.Center,
+            color = Color.Black,
+            fontFamily = TEST_FONT_FAMILY,
+            fontSize = fontSize.sp
+        )
+        rule.setContent {
+            val fontFamilyResolver = LocalFontFamilyResolver.current
+            val defaultWidth = computeSizeForDefaultText(
+                style = textStyle,
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                maxLines = 1
+            ).width
+
+            CompositionLocalProvider(LocalDensity provides density) {
+                BasicTextField2(
+                    modifier = Modifier
+                        .testTag(Tag)
+                        .width(defaultWidth.dp * 2),
+                    state = rememberTextFieldState("A"),
+                    textStyle = textStyle,
+                    lineLimits = TextFieldLineLimits.SingleLine
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).captureToImage().assertHorizontallySymmetrical(fontSize)
+    }
+
+    private fun requestFocus(tag: String) =
+        rule.onNodeWithTag(tag).requestFocus()
+
+    private fun assertTextSelection(expected: TextRange) {
+        val selection = rule.onNodeWithTag(Tag).fetchSemanticsNode()
+            .config.getOrNull(TextSelectionRange)
+        assertThat(selection).isEqualTo(expected)
+    }
+
+    private fun InputConnection.commitText(text: String) {
+        beginBatchEdit()
+        finishComposingText()
+        commitText(text, 1)
+        endBatchEdit()
+    }
+
+    private object RejectAllTextFilter : InputTransformation {
+        override fun transformInput(
+            originalValue: TextFieldCharSequence,
+            valueWithChanges: TextFieldBuffer
+        ) {
+            valueWithChanges.revertAllChanges()
+        }
+    }
+
+    private class KeyboardOptionsFilter(override val keyboardOptions: KeyboardOptions) :
+        InputTransformation {
+        override fun transformInput(
+            originalValue: TextFieldCharSequence,
+            valueWithChanges: TextFieldBuffer
+        ) {
+            // Noop
+        }
+    }
+}
+
+/**
+ * Checks whether the given image is horizontally symmetrical where a region that has the width
+ * of [excludedWidth] around the center is excluded.
+ */
+private fun ImageBitmap.assertHorizontallySymmetrical(excludedWidth: Int) {
+    val pixel = toPixelMap()
+    for (y in 0 until height) {
+        for (x in 0 until (width - excludedWidth) / 2) {
+            val leftPixel = pixel[x, y]
+            pixel.assertPixelColor(leftPixel, width - 1 - x, y)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/ComposeInputMethodManagerTestRule.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/ComposeInputMethodManagerTestRule.kt
new file mode 100644
index 0000000..6e6655c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/ComposeInputMethodManagerTestRule.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import android.view.View
+import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
+import androidx.compose.foundation.text.input.internal.overrideComposeInputMethodManagerFactoryForTests
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule to help setting the factory used to create [ComposeInputMethodManager] instances for tests.
+ * Restores the previous factory after the test finishes.
+ */
+internal class ComposeInputMethodManagerTestRule : TestRule {
+    private var initialFactory: ((View) -> ComposeInputMethodManager)? = null
+
+    fun setFactory(factory: (View) -> ComposeInputMethodManager) {
+        val previousFactory = overrideComposeInputMethodManagerFactoryForTests(factory)
+        if (initialFactory == null) {
+            initialFactory = previousFactory
+        }
+    }
+
+    override fun apply(base: Statement, description: Description): Statement =
+        object : Statement() {
+            override fun evaluate() {
+                try {
+                    base.evaluate()
+                } finally {
+                    // Reset the factory if it was set during the test so the next test gets the
+                    // default behavior.
+                    initialFactory?.let(::overrideComposeInputMethodManagerFactoryForTests)
+                }
+            }
+        }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
new file mode 100644
index 0000000..a223de4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class DecorationBoxTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val Tag = "BasicTextField2"
+    private val DecorationTag = "DecorationBox"
+
+    @Test
+    fun focusIsAppliedOnDecoratedComposable() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = { innerTextField ->
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                            .testTag(DecorationTag)
+                    ) {
+                        innerTextField()
+                    }
+                }
+            )
+        }
+
+        // requestFocus on node
+        rule.onNodeWithTag(Tag).performClick()
+
+        // assertThat decoration modifier has a focused parent.
+        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).assert(hasParent(isFocused()))
+    }
+
+    @Test
+    fun semanticsAreAppliedOnDecoratedComposable() {
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = { innerTextField ->
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                            .testTag(DecorationTag)
+                    ) {
+                        innerTextField()
+                    }
+                }
+            )
+        }
+
+        // assertThat decoration modifier has a focused parent.
+        with(rule.onNodeWithTag(DecorationTag, useUnmergedTree = true)) {
+            assert(hasParent(hasText("hello")))
+            assert(hasParent(hasSetTextAction()))
+        }
+    }
+
+    @Test
+    fun clickGestureIsAppliedOnDecoratedComposable() {
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = { innerTextField ->
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                            .testTag(DecorationTag)
+                    ) {
+                        innerTextField()
+                    }
+                }
+            )
+        }
+
+        // click on decoration box
+        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).performTouchInput {
+            // should be on the box not on inner text field since there is a padding
+            click(Offset(1f, 1f))
+        }
+
+        // assertThat textfield has focus
+        rule.onNodeWithTag(Tag).assertIsFocused()
+    }
+
+    @Test
+    fun nonPlacedInnerTextField_stillAcceptsTextInput() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = {
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                    )
+                }
+            )
+        }
+
+        // requestFocus on node
+        with(rule.onNodeWithTag(Tag)) {
+            performClick()
+            performTextInput("hello")
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("hello")
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun nonPlacedInnerTextField_stillAcceptsKeyInput() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = {
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                    )
+                }
+            )
+        }
+
+        // requestFocus on node
+        with(rule.onNodeWithTag(Tag)) {
+            performClick()
+            performKeyInput {
+                pressKey(Key.H)
+                pressKey(Key.E)
+                pressKey(Key.L)
+                pressKey(Key.L)
+                pressKey(Key.O)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("hello")
+        }
+    }
+
+    @Test
+    fun minConstraintsArePropagated() {
+        val state = TextFieldState()
+        var decorationBoxConstraints: Constraints? = null
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.fillMaxSize().testTag(Tag),
+                decorator = {
+                    Layout { _, constraints ->
+                        decorationBoxConstraints = constraints
+                        layout(0, 0) {}
+                    }
+                }
+            )
+        }
+
+        rule.waitForIdle()
+
+        assertThat(decorationBoxConstraints?.minWidth)
+            .isNotEqualTo(0)
+        assertThat(decorationBoxConstraints?.minWidth)
+            .isEqualTo(decorationBoxConstraints?.maxWidth)
+
+        assertThat(decorationBoxConstraints?.minHeight)
+            .isNotEqualTo(0)
+        assertThat(decorationBoxConstraints?.minHeight)
+            .isEqualTo(decorationBoxConstraints?.maxHeight)
+    }
+
+    @Ignore // TODO(halilibo): enable when pointerInput gestures are enabled
+    @Test
+    fun longClickGestureIsAppliedOnDecoratedComposable() {
+        // create a decorated BasicTextField2
+        val state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                decorator = { innerTextField ->
+                    Box(
+                        modifier = Modifier
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .padding(16.dp)
+                            .testTag(DecorationTag)
+                    ) {
+                        innerTextField()
+                    }
+                }
+            )
+        }
+
+        // click on decoration box
+        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).performTouchInput {
+            // should be on the box not on inner text field since there is a padding
+            longClick(Offset(1f, 1f))
+        }
+
+        // assertThat selection happened
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt
new file mode 100644
index 0000000..0b50dfb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import android.view.KeyEvent
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.ExtractedText
+import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
+import com.google.common.truth.Truth.assertThat
+
+internal class FakeInputMethodManager : ComposeInputMethodManager {
+    private val calls = mutableListOf<String>()
+
+    fun expectCall(description: String) {
+        assertThat(calls.removeFirst()).isEqualTo(description)
+    }
+
+    fun expectNoMoreCalls() {
+        assertThat(calls).isEmpty()
+    }
+
+    fun resetCalls() {
+        calls.clear()
+    }
+
+    override fun restartInput() {
+        calls += "restartInput"
+    }
+
+    override fun showSoftInput() {
+        calls += "showSoftInput"
+    }
+
+    override fun hideSoftInput() {
+        calls += "hideSoftInput"
+    }
+
+    override fun updateExtractedText(token: Int, extractedText: ExtractedText) {
+        calls += "updateExtractedText"
+    }
+
+    override fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    ) {
+        calls += "updateSelection($selectionStart, $selectionEnd, " +
+            "$compositionStart, $compositionEnd)"
+    }
+
+    override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
+        calls += "updateCursorAnchorInfo"
+    }
+
+    override fun sendKeyEvent(event: KeyEvent) {
+        calls += "sendKeyEvent"
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt
new file mode 100644
index 0000000..7d021df
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text.input
+
+import android.content.Context
+import android.graphics.Typeface
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.TEST_FONT
+import androidx.compose.foundation.text.heightInLines
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.AndroidFont
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontLoadingStrategy
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontVariation
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class HeightInLinesModifierTest {
+
+    private val longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+        " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+        "fugiat nulla pariatur."
+
+    private val context = InstrumentationRegistry.getInstrumentation().context
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun minLines_shortInputText() {
+        var subjectLayout: (() -> TextLayoutResult?)? = null
+        var subjectHeight: Int? = null
+        var twoLineHeight: Int? = null
+        val positionedLatch = CountDownLatch(1)
+        val twoLinePositionedLatch = CountDownLatch(1)
+
+        rule.setContent {
+            HeightObservingText(
+                onGlobalHeightPositioned = {
+                    subjectHeight = it
+                    positionedLatch.countDown()
+                },
+                onTextLayoutResult = {
+                    subjectLayout = it
+                },
+                text = "abc",
+                lineLimits = MultiLine(minHeightInLines = 2)
+            )
+            HeightObservingText(
+                onGlobalHeightPositioned = {
+                    twoLineHeight = it
+                    twoLinePositionedLatch.countDown()
+                },
+                onTextLayoutResult = {},
+                text = "1\n2",
+                lineLimits = MultiLine(minHeightInLines = 2)
+            )
+        }
+        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+        assertThat(twoLinePositionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+
+        rule.runOnIdle {
+            assertThat(subjectLayout).isNotNull()
+            assertThat(subjectLayout!!.invoke()?.lineCount).isEqualTo(1)
+            assertThat(subjectHeight!!).isEqualTo(twoLineHeight)
+        }
+    }
+
+    @Test
+    fun maxLines_shortInputText() {
+        val (textLayoutResult, height) = setTextFieldWithMaxLines(
+            text = "abc",
+            lines = MultiLine(maxHeightInLines = 5)
+        )
+
+        rule.runOnIdle {
+            assertThat(textLayoutResult).isNotNull()
+            assertThat(textLayoutResult!!.invoke()?.lineCount).isEqualTo(1)
+            assertThat(textLayoutResult()?.size?.height).isEqualTo(height)
+        }
+    }
+
+    @Test
+    fun maxLines_notApplied_infiniteMaxLines() {
+        val (textLayoutResult, height) =
+            setTextFieldWithMaxLines(longText, MultiLine(minHeightInLines = Int.MAX_VALUE))
+
+        rule.runOnIdle {
+            assertThat(textLayoutResult).isNotNull()
+            assertThat(textLayoutResult!!.invoke()?.size?.height).isEqualTo(height)
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun minLines_invalidValue() {
+        rule.setContent {
+            Box(
+                modifier = Modifier.heightInLines(textStyle = TextStyle.Default, minLines = 0)
+            )
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun maxLines_invalidValue() {
+        rule.setContent {
+            Box(
+                modifier = Modifier.heightInLines(textStyle = TextStyle.Default, maxLines = 0)
+            )
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun minLines_greaterThan_maxLines_invalidValue() {
+        rule.setContent {
+            Box(
+                modifier = Modifier.heightInLines(
+                    textStyle = TextStyle.Default,
+                    minLines = 2,
+                    maxLines = 1
+                )
+            )
+        }
+    }
+
+    @Test
+    fun minLines_longInputText() {
+        val (textLayoutResult, height) = setTextFieldWithMaxLines(
+            text = longText,
+            MultiLine(minHeightInLines = 2)
+        )
+
+        rule.runOnIdle {
+            assertThat(textLayoutResult).isNotNull()
+            // should be in the 20s, but use this to create invariant for the next assertion
+            assertThat(textLayoutResult!!.invoke()?.lineCount).isGreaterThan(2)
+            assertThat(textLayoutResult()?.size?.height).isEqualTo(height)
+        }
+    }
+
+    @Test
+    fun maxLines_longInputText() {
+        var subjectLayout: (() -> TextLayoutResult?)? = null
+        var subjectHeight: Int? = null
+        var twoLineHeight: Int? = null
+        val positionedLatch = CountDownLatch(1)
+        val twoLinePositionedLatch = CountDownLatch(1)
+
+        rule.setContent {
+            HeightObservingText(
+                onGlobalHeightPositioned = {
+                    subjectHeight = it
+                    positionedLatch.countDown()
+                },
+                onTextLayoutResult = {
+                    subjectLayout = it
+                },
+                text = longText,
+                lineLimits = MultiLine(maxHeightInLines = 2)
+            )
+            HeightObservingText(
+                onGlobalHeightPositioned = {
+                    twoLineHeight = it
+                    twoLinePositionedLatch.countDown()
+                },
+                onTextLayoutResult = {},
+                text = "1\n2",
+                lineLimits = MultiLine(maxHeightInLines = 2)
+            )
+        }
+        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+        assertThat(twoLinePositionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+
+        rule.runOnIdle {
+            assertThat(subjectLayout).isNotNull()
+            // should be in the 20s, but use this to create invariant for the next assertion
+            assertThat(subjectLayout!!.invoke()?.lineCount).isGreaterThan(2)
+            assertThat(subjectHeight!!).isEqualTo(twoLineHeight)
+        }
+    }
+
+    @OptIn(ExperimentalTextApi::class, ExperimentalCoroutinesApi::class)
+    @Test
+    fun asyncFontLoad_changesLineHeight() {
+        val testDispatcher = UnconfinedTestDispatcher()
+        val resolver = createFontFamilyResolver(context, testDispatcher)
+
+        val typefaceDeferred = CompletableDeferred<Typeface>()
+        val asyncLoader = object : AndroidFont.TypefaceLoader {
+            override fun loadBlocking(context: Context, font: AndroidFont): Typeface =
+                TODO("Not yet implemented")
+
+            override suspend fun awaitLoad(context: Context, font: AndroidFont): Typeface {
+                return typefaceDeferred.await()
+            }
+        }
+        val fontFamily = FontFamily(
+            object : AndroidFont(FontLoadingStrategy.Async, asyncLoader, FontVariation.Settings()) {
+                override val weight: FontWeight = FontWeight.Normal
+                override val style: FontStyle = FontStyle.Normal
+            },
+            TEST_FONT
+        )
+
+        val heights = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalFontFamilyResolver provides resolver,
+                LocalDensity provides Density(1.0f, 1f)
+            ) {
+                HeightObservingText(
+                    onGlobalHeightPositioned = {
+                        heights.add(it)
+                    },
+                    onTextLayoutResult = {},
+                    text = longText,
+                    lineLimits = MultiLine(maxHeightInLines = 10),
+                    textStyle = TextStyle.Default.copy(
+                        fontFamily = fontFamily,
+                        fontSize = 80.sp
+                    )
+                )
+            }
+        }
+
+        val before = heights.toList()
+        typefaceDeferred.complete(Typeface.create("cursive", Typeface.BOLD_ITALIC))
+
+        rule.runOnIdle {
+            assertThat(heights.size).isGreaterThan(before.size)
+            assertThat(heights.distinct().size).isGreaterThan(before.distinct().size)
+        }
+    }
+
+    @Test
+    fun testInspectableValue() {
+        isDebugInspectorInfoEnabled = true
+
+        val modifier = Modifier.heightInLines(
+            textStyle = TextStyle.Default,
+            minLines = 5,
+            maxLines = 10
+        ) as InspectableValue
+        assertThat(modifier.nameFallback).isEqualTo("heightInLines")
+        assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+            ValueElement("minLines", 5),
+            ValueElement("maxLines", 10),
+            ValueElement("textStyle", TextStyle.Default)
+        )
+
+        isDebugInspectorInfoEnabled = false
+    }
+
+    private fun setTextFieldWithMaxLines(
+        text: String,
+        lines: MultiLine
+    ): Pair<(() -> TextLayoutResult?)?, Int?> {
+        var textLayoutResult: (() -> TextLayoutResult?)? = null
+        var height: Int? = null
+        val positionedLatch = CountDownLatch(1)
+
+        rule.setContent {
+            HeightObservingText(
+                onGlobalHeightPositioned = {
+                    height = it
+                    positionedLatch.countDown()
+                },
+                onTextLayoutResult = {
+                    textLayoutResult = it
+                },
+                text = text,
+                lineLimits = lines
+            )
+        }
+        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+
+        return Pair(textLayoutResult, height)
+    }
+
+    @Composable
+    private fun HeightObservingText(
+        onGlobalHeightPositioned: (Int) -> Unit,
+        onTextLayoutResult: Density.(getResult: () -> TextLayoutResult?) -> Unit,
+        text: String,
+        lineLimits: MultiLine,
+        textStyle: TextStyle = TextStyle.Default
+    ) {
+        Box(
+            Modifier.onGloballyPositioned {
+                onGlobalHeightPositioned(it.size.height)
+            }
+        ) {
+            BasicTextField2(
+                state = remember { TextFieldState(text) },
+                textStyle = textStyle,
+                lineLimits = lineLimits,
+                modifier = Modifier.requiredWidth(100.dp),
+                onTextLayout = onTextLayoutResult
+            )
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
new file mode 100644
index 0000000..558b4ff
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import android.os.Looper
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.PlatformTextInputMethodTestOverride
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import com.google.common.truth.IntegerSubject
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.KClass
+import kotlin.test.assertNotNull
+import kotlinx.coroutines.awaitCancellation
+
+/**
+ * Helper class for testing integration of BasicTextField and BasicTextField2 with the platform IME.
+ */
+class InputMethodInterceptor(private val rule: ComposeContentTestRule) {
+
+    private var currentRequest: PlatformTextInputMethodRequest? = null
+    private val editorInfo = EditorInfo()
+    private var inputConnection: InputConnection? = null
+
+    /**
+     * The total number of sessions that have been requested on this interceptor, including the
+     * current one if active.
+     */
+    private var sessionCount = 0
+
+    /**
+     * Asserts that there is an active session.
+     *
+     * Can be called from any thread, including main and test runner.
+     */
+    fun assertSessionActive() {
+        runOnIdle {
+            assertWithMessage("Expected a text input session to be active")
+                .that(currentRequest).isNotNull()
+        }
+    }
+
+    /**
+     * Asserts that there is no active session.
+     *
+     * Can be called from any thread, including main and test runner.
+     */
+    fun assertNoSessionActive() {
+        runOnIdle {
+            assertWithMessage("Expected no text input session to be active")
+                .that(currentRequest).isNull()
+        }
+    }
+
+    /**
+     * Returns a subject that will assert on the total number of sessions requested on this
+     * interceptor, including the current one if active.
+     */
+    fun assertThatSessionCount(): IntegerSubject = assertThat(runOnIdle { sessionCount })
+
+    /**
+     * Runs [block] on the main thread and passes it the [PlatformTextInputMethodRequest]
+     * for the current input session.
+     *
+     * @throws AssertionError if no session is active.
+     */
+    inline fun <reified T : PlatformTextInputMethodRequest> withCurrentRequest(
+        noinline block: T.() -> Unit
+    ) {
+        withCurrentRequest(T::class, block)
+    }
+
+    /**
+     * Runs [block] on the main thread and passes it the [PlatformTextInputMethodRequest]
+     * for the current input session.
+     *
+     * @throws AssertionError if no session is active.
+     */
+    fun <T : PlatformTextInputMethodRequest> withCurrentRequest(
+        asClass: KClass<T>,
+        block: T.() -> Unit
+    ) {
+        runOnIdle {
+            val currentRequest =
+                assertNotNull(currentRequest, "Expected a text input session to be active")
+            assertThat(currentRequest).isInstanceOf(asClass.java)
+            @Suppress("UNCHECKED_CAST")
+            block(currentRequest as T)
+        }
+    }
+
+    /**
+     * Runs [block] on the main thread and passes it the [EditorInfo] configured by the current
+     * input session.
+     *
+     * @throws AssertionError if no session is active.
+     */
+    fun withEditorInfo(block: EditorInfo.() -> Unit) {
+        runOnIdle {
+            assertWithMessage("Expected a text input session to be active")
+                .that(currentRequest).isNotNull()
+            block(editorInfo)
+        }
+    }
+
+    /**
+     * Runs [block] on the main thread and passes it the [InputConnection] created by the current
+     * input session.
+     *
+     * @throws AssertionError if no session is active.
+     */
+    fun withInputConnection(block: InputConnection.() -> Unit) {
+        runOnIdle {
+            val inputConnection = checkNotNull(inputConnection) {
+                "Tried to read inputConnection while no session was active"
+            }
+            block(inputConnection)
+        }
+    }
+
+    /**
+     * Sets the content of the test, overriding the [PlatformTextInputSession] handler.
+     *
+     * This is just a convenience method for calling `rule.setContent` and then calling this class's
+     * [Content] method yourself.
+     */
+    fun setContent(content: @Composable () -> Unit) {
+        rule.setContent {
+            Content(content)
+        }
+    }
+
+    /**
+     * Wraps the content of the test to override the [PlatformTextInputSession] handler.
+     *
+     * @see setContent
+     */
+    @OptIn(ExperimentalTestApi::class)
+    @Composable
+    fun Content(content: @Composable () -> Unit) {
+        val view = LocalView.current
+        val sessionHandler = remember { SessionHandler(view) }
+        PlatformTextInputMethodTestOverride(
+            sessionHandler = sessionHandler,
+            content = content
+        )
+    }
+
+    private fun <T> runOnIdle(block: () -> T): T {
+        return if (Looper.myLooper() != Looper.getMainLooper()) {
+            rule.runOnIdle(block)
+        } else {
+            block()
+        }
+    }
+
+    private inner class SessionHandler(override val view: View) : PlatformTextInputSession {
+        override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing {
+            currentRequest = request
+            sessionCount++
+            try {
+                inputConnection = request.createInputConnection(editorInfo)
+                awaitCancellation()
+            } finally {
+                currentRequest = null
+                inputConnection = null
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt
new file mode 100644
index 0000000..e219afa
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RememberTextFieldStateTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val restorationTester = StateRestorationTester(rule)
+
+    @Test
+    fun rememberTextFieldState_withInitialTextAndSelection() {
+        lateinit var state: TextFieldState
+        rule.setContent {
+            state = rememberTextFieldState(
+                initialText = "hello",
+                initialSelectionInChars = TextRange(2)
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        }
+    }
+
+    @Test
+    fun rememberTextFieldState_restoresTextAndSelection() {
+        lateinit var originalState: TextFieldState
+        lateinit var restoredState: TextFieldState
+        var rememberCount = 0
+        restorationTester.setContent {
+            val state = rememberTextFieldState()
+            if (remember { rememberCount++ } == 0) {
+                originalState = state
+            } else {
+                restoredState = state
+            }
+        }
+        rule.runOnIdle {
+            originalState.edit {
+                append("hello, world")
+                selectAll()
+            }
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        rule.runOnIdle {
+            assertThat(restoredState.text.toString()).isEqualTo("hello, world")
+            assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 12))
+        }
+    }
+
+    @Test
+    fun rememberTextFieldState_withInitialTextAndSelection_restoresTextAndSelection() {
+        lateinit var originalState: TextFieldState
+        lateinit var restoredState: TextFieldState
+        var rememberCount = 0
+        restorationTester.setContent {
+            val state = rememberTextFieldState(
+                initialText = "this should be ignored",
+                initialSelectionInChars = TextRange.Zero
+            )
+            if (remember { rememberCount++ } == 0) {
+                originalState = state
+            } else {
+                restoredState = state
+            }
+        }
+        rule.runOnIdle {
+            originalState.edit {
+                replace(0, length, "hello, world")
+                selectAll()
+            }
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        rule.runOnIdle {
+            assertThat(restoredState.text.toString()).isEqualTo("hello, world")
+            assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 12))
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TestSoftwareKeyboardController.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TestSoftwareKeyboardController.kt
new file mode 100644
index 0000000..b84b1ca
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TestSoftwareKeyboardController.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import com.google.common.truth.Truth.assertWithMessage
+
+class TestSoftwareKeyboardController(
+    private val rule: ComposeTestRule
+) : SoftwareKeyboardController {
+    private var shown = false
+
+    override fun show() {
+        shown = true
+    }
+
+    override fun hide() {
+        shown = false
+    }
+
+    fun assertShown() {
+        rule.runOnIdle {
+            assertWithMessage("Expected last call on SoftwareKeyboardController to be show")
+                .that(shown).isTrue()
+        }
+    }
+
+    fun assertHidden() {
+        rule.runOnIdle {
+            assertWithMessage("Expected last call on SoftwareKeyboardController to be hide")
+                .that(shown).isFalse()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
new file mode 100644
index 0000000..e05003a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
@@ -0,0 +1,845 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.internal.CodepointTransformation
+import androidx.compose.foundation.text.input.internal.mask
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.withKeyDown
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldCodepointTransformationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicTextField2"
+
+    @Test
+    fun textField_rendersTheResultOf_codepointTransformation() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = { _, codepoint -> codepoint + 1 },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Ifmmp") // one character after in lexical order
+    }
+
+    @Test
+    fun textField_rendersTheResultOf_codepointTransformation_codepointIndex() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = { index, codepoint ->
+                    if (index % 2 == 0) codepoint + 1 else codepoint - 1
+                },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Idmkp") // one character after and before in lexical order
+    }
+
+    @Test
+    fun textField_toggleCodepointTransformation_affectsNextFrame() {
+        rule.mainClock.autoAdvance = false
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        var codepointTransformation: CodepointTransformation? by mutableStateOf(null)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = codepointTransformation,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Hello") // no change
+        codepointTransformation = CodepointTransformation.mask('c')
+
+        rule.mainClock.advanceTimeByFrame()
+        assertLayoutText("ccccc") // all characters turn to c
+    }
+
+    @Test
+    fun textField_statefulCodepointTransformation_reactsToStateChange() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        var mask by mutableStateOf('-')
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = CodepointTransformation.mask(mask),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("-----")
+        mask = '@'
+
+        rule.waitForIdle()
+        assertLayoutText("@@@@@")
+    }
+
+    @Test
+    fun textField_removingCodepointTransformation_rendersTextNormally() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        var codepointTransformation by mutableStateOf<CodepointTransformation?>(
+            CodepointTransformation.mask('*')
+        )
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = codepointTransformation,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("*****")
+        codepointTransformation = null
+
+        rule.waitForIdle()
+        assertLayoutText("Hello")
+    }
+
+    @Test
+    fun textField_codepointTransformation_continuesToRenderUpdatedText() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                codepointTransformation = CodepointTransformation.mask('*'),
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("*****")
+        rule.waitForIdle()
+        rule.onNodeWithTag(Tag).performTextInput(", World!")
+        assertLayoutText("*".repeat("Hello, World!".length))
+    }
+
+    @Test
+    fun textField_singleLine_removesLineFeedViaCodepointTransformation() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Hello World")
+        rule.onNodeWithTag(Tag).performTextInput("\n")
+        assertLayoutText("Hello World ")
+    }
+
+    @Test
+    fun textField_singleLine_removesCarriageReturnViaCodepointTransformation() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello\rWorld")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Hello\uFEFFWorld")
+    }
+
+    @Test
+    fun textField_singleLine_doesNotOverrideGivenCodepointTransformation() {
+        val state = TextFieldState()
+        state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                codepointTransformation = { _, codepoint -> codepoint },
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        assertLayoutText("Hello\nWorld")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_singleCodepoint_isTransformed() {
+        val state = TextFieldState(SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText(".")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_multipleCodepoints_areTransformed() {
+        val state = TextFieldState(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText("..")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_withNonSurrogates_areTransformed() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}b")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText("...")
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_singleCodepoint_isTransformed() {
+        val state = TextFieldState("a")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertLayoutText(SingleSurrogateCodepointString)
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_multipleCodepoints_areTransformed() {
+        val state = TextFieldState("ab")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertLayoutText(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_withNonSurrogates_areTransformed() {
+        val state = TextFieldState("abc")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    if (i == 1) SurrogateCodepoint else codepoint
+                }
+            )
+        }
+
+        assertLayoutText("a${SingleSurrogateCodepointString}c")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
+        val state = TextFieldState(SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertVisualTextLength(1)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 2),
+            TextRange(1, 0) to TextRange(2, 0),
+            TextRange(1) to TextRange(2),
+        )
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
+        val state = TextFieldState("a")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertVisualTextLength(2)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 1),
+            TextRange(0, 2) to TextRange(0, 1),
+            TextRange(1, 0) to TextRange(1, 0),
+            TextRange(1) to TextRange(0, 1),
+            TextRange(1, 2) to TextRange(0, 1),
+            TextRange(2, 0) to TextRange(1, 0),
+            TextRange(2, 1) to TextRange(1, 0),
+            TextRange(2) to TextRange(1),
+        )
+    }
+
+    @FlakyTest(bugId = 317749301)
+    @Test
+    fun multipleCodepoints_selectionIsMappedAroundCodepoints() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code, 'c'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        assertVisualTextLength(5)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 1),
+            TextRange(0, 2) to TextRange(0, 1),
+            TextRange(0, 3) to TextRange(0, 3),
+            TextRange(0, 4) to TextRange(0, 4),
+            TextRange(0, 5) to TextRange(0, 4),
+            TextRange(1, 0) to TextRange(1, 0),
+            TextRange(1) to TextRange(0, 1),
+            TextRange(1, 2) to TextRange(0, 1),
+            TextRange(1, 3) to TextRange(0, 3),
+            TextRange(1, 4) to TextRange(0, 4),
+            TextRange(1, 5) to TextRange(0, 4),
+            TextRange(2, 0) to TextRange(1, 0),
+            TextRange(2, 1) to TextRange(1, 0),
+            TextRange(2) to TextRange(1),
+            TextRange(2, 3) to TextRange(1, 3),
+            TextRange(2, 4) to TextRange(1, 4),
+            TextRange(2, 5) to TextRange(1, 4),
+            TextRange(3, 0) to TextRange(3, 0),
+            TextRange(3, 1) to TextRange(3, 0),
+            TextRange(3, 2) to TextRange(3, 1),
+            TextRange(3) to TextRange(3),
+            TextRange(3, 4) to TextRange(3, 4),
+            TextRange(3, 5) to TextRange(3, 4),
+            TextRange(4, 0) to TextRange(4, 0),
+            TextRange(4, 1) to TextRange(4, 0),
+            TextRange(4, 2) to TextRange(4, 1),
+            TextRange(4, 3) to TextRange(4, 3),
+            TextRange(4) to TextRange(3, 4),
+            TextRange(4, 5) to TextRange(3, 4),
+        )
+    }
+
+    @Test
+    fun cursorTraversal_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        listOf(0, 1, 3, 4).forEachIndexed { i, expectedCursor ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing right arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(TextRange(expectedCursor))
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                pressKey(Key.DirectionRight)
+            }
+        }
+    }
+
+    @Test
+    fun expandSelectionForward_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        listOf(
+            TextRange(0),
+            TextRange(0, 1),
+            TextRange(0, 3),
+            TextRange(0, 4)
+        ).forEachIndexed { i, expectedSelection ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing shift+right arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                withKeyDown(Key.ShiftLeft) {
+                    pressKey(Key.DirectionRight)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun expandSelectionBackward_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(4))
+
+        listOf(
+            TextRange(4),
+            TextRange(4, 3),
+            TextRange(4, 1),
+            TextRange(4, 0)
+        ).forEachIndexed { i, expectedSelection ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing shift+left arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                withKeyDown(Key.ShiftLeft) {
+                    pressKey(Key.DirectionLeft)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun insertNonSurrogates_intoSurrogateMask_fromKeyEvents() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.X)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Y)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Z)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertNonSurrogates_intoNonSurrogateMask_fromKeyEvents() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.X)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Y)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Z)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
+
+    @Test
+    fun insertText_intoSurrogateMask_fromSemantics() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        // Use semantics to actually input the text, just use key events to move the cursor.
+        rule.onNodeWithTag(Tag).performTextInput("x")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("y")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("z")
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertNonSurrogates_intoNonSurrogateMask_fromSemantics() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        // Use semantics to actually input the text, just use key events to move the cursor.
+        rule.onNodeWithTag(Tag).performTextInput("x")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("y")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("z")
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
+
+    @Test
+    fun insertText_intoSurrogateMask_fromIme() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        inputMethodInterceptor.withInputConnection {
+            beginBatchEdit()
+            finishComposingText()
+            setSelection(0, 0)
+            endBatchEdit()
+        }
+
+        inputMethodInterceptor.withInputConnection { commitText("x", 1) }
+        pressKey(Key.DirectionRight)
+        inputMethodInterceptor.withInputConnection { commitText("y", 1) }
+        pressKey(Key.DirectionRight)
+        inputMethodInterceptor.withInputConnection { commitText("z", 1) }
+        pressKey(Key.DirectionRight)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertText_intoNonSurrogateMask_fromIme() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        inputMethodInterceptor.withInputConnection {
+            beginBatchEdit()
+            finishComposingText()
+            setSelection(0, 0)
+            endBatchEdit()
+        }
+
+        inputMethodInterceptor.withInputConnection { commitText("x", 1) }
+        pressKey(Key.DirectionRight)
+        inputMethodInterceptor.withInputConnection { commitText("y", 1) }
+        pressKey(Key.DirectionRight)
+        inputMethodInterceptor.withInputConnection { commitText("z", 1) }
+        pressKey(Key.DirectionRight)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
+
+    @Test
+    fun removeNonSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
+        val state = TextFieldState("${SingleSurrogateCodepointString.repeat(2)}aa")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.Backspace)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString())
+                .isEqualTo("${SingleSurrogateCodepointString.repeat(2)}a")
+        }
+        assertVisualTextLength(3)
+    }
+
+    @Test
+    fun removeSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
+        val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.Backspace)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString())
+                .isEqualTo("aa$SingleSurrogateCodepointString")
+        }
+        assertVisualTextLength(3)
+    }
+
+    @Test
+    fun removeNonSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString.repeat(2)}a")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.Backspace)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString())
+                .isEqualTo("a${SingleSurrogateCodepointString.repeat(2)}")
+        }
+        assertVisualTextLength(6)
+    }
+
+    @Test
+    fun removeSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
+        val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.Backspace)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString())
+                .isEqualTo("aa$SingleSurrogateCodepointString")
+        }
+        assertVisualTextLength(6)
+    }
+
+    private fun assertLayoutText(text: String) {
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .isEqualTo(text)
+    }
+
+    private fun assertVisualTextLength(expectedLength: Int) {
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .hasLength(expectedLength)
+    }
+
+    private fun TextFieldState.assertSelectionMappings(
+        vararg mappings: Pair<TextRange, TextRange>
+    ) {
+        mappings.forEach { (write, expected) ->
+            val existingSelection = rule.onNodeWithTag(Tag)
+                .fetchSemanticsNode().config[SemanticsProperties.TextSelectionRange]
+            // Setting the selection to the current selection will return false.
+            if (existingSelection != write) {
+                assertWithMessage("Expected to be able to select $write")
+                    .that(performSelectionOnVisualText(write)).isTrue()
+                rule.runOnIdle {
+                    assertWithMessage("Visual selection $write to mapped")
+                        .that(text.selectionInChars).isEqualTo(expected)
+                }
+            }
+        }
+    }
+
+    private fun performSelectionOnVisualText(selection: TextRange): Boolean {
+        rule.onNodeWithTag(Tag).requestFocus()
+        var actionSucceeded = false
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.SetSelection) {
+            actionSucceeded = it(selection.start, selection.end, /* relativeToOriginal= */ false)
+        }
+        return actionSucceeded
+    }
+
+    private fun pressKey(key: Key) {
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(key) }
+    }
+
+    private companion object {
+        /** This is "𐐷", a surrogate codepoint. */
+        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
+        const val SingleSurrogateCodepointString = "\uD801\uDC37"
+
+        val MaskWithSurrogate = CodepointTransformation { _, _ -> SurrogateCodepoint }
+        val MaskWithNonSurrogate = CodepointTransformation { _, _ -> '.'.code }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
new file mode 100644
index 0000000..fbb0eab
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
@@ -0,0 +1,1104 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.Build
+import android.view.DragEvent
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.DefaultCursorThickness
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.internal.DragAndDropTestUtils.makeTextDragEvent
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.testutils.assertPixelColor
+import androidx.compose.testutils.assertPixels
+import androidx.compose.testutils.assertShape
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.toOffset
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import kotlin.math.floor
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@LargeTest
+class TextFieldCursorTest : FocusedWindowTest {
+
+    private val motionDurationScale = object : MotionDurationScale {
+        override var scaleFactor: Float by mutableStateOf(1f)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @get:Rule
+    val rule = createComposeRule(effectContext = motionDurationScale).also {
+        it.mainClock.autoAdvance = false
+    }
+
+    private lateinit var state: TextFieldState
+
+    private val boxPadding = 8.dp
+
+    // Both TextField background and font color should be the same to make sure that only
+    // cursor is visible
+    private val contentColor = Color.White
+    private val cursorColor = Color.Red
+    private val fontSize = 10.sp
+    private val textStyle = TextStyle(
+        color = contentColor,
+        background = contentColor,
+        fontSize = fontSize,
+        fontFamily = TEST_FONT_FAMILY
+    )
+
+    private var isFocused = false
+    private var textLayoutResult: (() -> TextLayoutResult?)? = null
+    private val cursorRect: Rect
+        // assume selection is collapsed
+        get() = textLayoutResult?.invoke()?.getCursorRect(state.text.selectionInChars.start)
+            ?: Rect.Zero
+
+    private val cursorSize: DpSize by lazy {
+        with(rule.density) {
+            DpSize(DefaultCursorThickness, fontSize.toDp())
+        }
+    }
+
+    private val cursorSizePx: Size by lazy {
+        with(rule.density) { cursorSize.toSize() }
+    }
+
+    private val cursorTopCenterInLtr: Offset
+        // assume selection is collapsed
+        get() = cursorRect.topCenter + Offset(cursorSizePx.width / 2f, 0f)
+
+    private val cursorTopCenterInRtl: Offset
+        // assume selection is collapsed
+        get() = cursorRect.topCenter - Offset(cursorSizePx.width / 2f, 0f)
+
+    private val backgroundModifier = Modifier.background(contentColor)
+    private val focusModifier = Modifier.onFocusChanged { if (it.isFocused) isFocused = true }
+
+    // default TextFieldModifier
+    private val textFieldModifier = Modifier
+        .then(backgroundModifier)
+        .then(focusModifier)
+
+    // default onTextLayout to capture cursor boundaries.
+    private val onTextLayout: Density.(() -> TextLayoutResult?) -> Unit = { textLayoutResult = it }
+
+    private fun ComposeContentTestRule.setTestContent(
+        content: @Composable () -> Unit
+    ) {
+        this.setTextFieldTestContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a
+            // bit different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                content()
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorRendered() {
+        state = TextFieldState()
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorRendered_rtlLayout() {
+        state = TextFieldState()
+        rule.setTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier.width(30.dp),
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        // an empty text layout will be placed on the right side of 30.dp-width area
+        // cursor will be at the most right side
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInRtl)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorRendered_rtlText_ltrLayout() {
+        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorRendered_rtlTextLayout() {
+        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
+        rule.setTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier.width(50.dp),
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            // 20 - 2(cursor)
+            .assertCursor(cursorTopCenterInRtl)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldCursorAtTheEnd_coercedIntoView() {
+        state = TextFieldState("hello", TextRange(5))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier.width(50.dp),
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr - Offset(cursorSizePx.width, 0f))
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldCursorAtTheEnd_coercedIntoView_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
+        rule.setTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier.width(30.dp),
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInRtl + Offset(cursorSizePx.width, 0f))
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorWithBrush() {
+        state = TextFieldState()
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle.copy(fontSize = textStyle.fontSize * 2),
+                modifier = Modifier
+                    .then(backgroundModifier)
+                    .then(focusModifier),
+                cursorBrush = Brush.verticalGradient(
+                    // make a brush double/triple color at the beginning and end so we have
+                    // stable colors at the ends.
+                    // Without triple bottom, the bottom color never hits to the provided color.
+                    listOf(
+                        Color.Blue,
+                        Color.Blue,
+                        Color.Green,
+                        Color.Green,
+                        Color.Green
+                    )
+                ),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        val bitmap = rule.onNode(hasSetTextAction())
+            .captureToImage().toPixelMap()
+
+        val cursorLeft = ceil(cursorRect.left).toInt() + 1
+        val cursorTop = ceil(cursorRect.top).toInt() + 1
+        val cursorBottom = floor(cursorRect.bottom).toInt() - 1
+        bitmap.assertPixelColor(Color.Blue, x = cursorLeft, y = cursorTop)
+        bitmap.assertPixelColor(Color.Green, x = cursorLeft, y = cursorBottom)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorBlinkingAnimation() {
+        state = TextFieldState()
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(100)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+
+        // cursor invisible during next 500 ms
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Suppress("UnnecessaryOptInAnnotation")
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorBlinkingAnimation_whenSystemDisablesAnimations() {
+        motionDurationScale.scaleFactor = 0f
+        state = TextFieldState()
+
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(100)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+
+        // cursor invisible during next 500 ms
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorUnsetColor_noCursor() {
+        state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(Color.Unspecified)
+            )
+        }
+
+        focusAndWait()
+
+        // no cursor when usually shown
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+
+        // no cursor when should be no cursor
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @FlakyTest(bugId = 303503435)
+    fun cursorNotBlinking_whileTyping() {
+        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier.width(100.dp),
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(500)
+        // TODO(b/170298051) check here that cursor is visible when we have a way to control
+        //  cursor position when sending a text
+
+        // change text field value
+        rule.onNode(hasSetTextAction())
+            .performTextInput("s")
+
+        // cursor would have been invisible during next 500 ms if cursor blinks while typing.
+        // To prevent blinking while typing we restart animation when new symbol is typed.
+        rule.mainClock.advanceTimeBy(300)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @FlakyTest(bugId = 303903824)
+    fun selectionChanges_cursorNotBlinking() {
+        state = TextFieldState("test", initialSelectionInChars = TextRange(2))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        // hide the cursor
+        rule.mainClock.advanceTimeBy(500)
+        rule.mainClock.advanceTimeByFrame()
+
+        // TODO(b/170298051) check here that cursor is visible when we have a way to control
+        //  cursor position when sending a text
+
+        rule.onNode(hasSetTextAction())
+            .performTextInputSelection(TextRange(0))
+
+        // necessary for animation to start (shows cursor again)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun brushChanged_doesntResetTimer() {
+        var cursorBrush by mutableStateOf(SolidColor(cursorColor))
+        state = TextFieldState()
+        rule.setTestContent {
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = cursorBrush,
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(800)
+        cursorBrush = SolidColor(Color.Green)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun selectionNotCollapsed_cursorNotDrawn() {
+        state = TextFieldState("test", initialSelectionInChars = TextRange(2, 3))
+        rule.setTestContent {
+            // set selection highlight to a known color
+            CompositionLocalProvider(
+                LocalTextSelectionColors provides TextSelectionColors(Color.Blue, Color.Blue)
+            ) {
+                BasicTextField2(
+                    state = state,
+                    // make sure that background is not obstructing selection
+                    textStyle = textStyle.copy(
+                        background = Color.Unspecified
+                    ),
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // cursor should still be visible if there wasn't a selection
+        rule.mainClock.advanceTimeBy(300)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertDoesNotContainColor(cursorColor)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun focusLost_cursorHidesImmediately() {
+        state = TextFieldState("test")
+        rule.setTestContent {
+            Column {
+                BasicTextField2(
+                    state = state,
+                    // make sure that background is not obstructing selection
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+                Box(
+                    modifier = Modifier
+                        .focusable(true)
+                        .testTag("box")
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+
+        rule.onNodeWithTag("box").requestFocus()
+        rule.mainClock.advanceTimeByFrame()
+
+        // cursor should hide immediately.
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun readOnly_cursorIsNotDrawn() {
+        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                readOnly = true,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertDoesNotContainColor(cursorColor)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun toggling_readOnly_drawsCursorAgain() {
+        var readOnly by mutableStateOf(true)
+        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
+        rule.setTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                readOnly = readOnly,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertDoesNotContainColor(cursorColor)
+
+        readOnly = false
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorNotBlinking_whenWindowLostFocus() {
+        state = TextFieldState()
+        val focusWindow = mutableStateOf(true)
+        fun createWindowInfo(focused: Boolean) = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focused
+        }
+
+        rule.setTestContent {
+            CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
+                Box(Modifier.padding(boxPadding)) {
+                    BasicTextField2(
+                        state = state,
+                        textStyle = textStyle,
+                        modifier = textFieldModifier,
+                        cursorBrush = SolidColor(cursorColor),
+                        onTextLayout = onTextLayout
+                    )
+                }
+            }
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500ms
+        rule.mainClock.advanceTimeBy(100)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(cursorColor)
+
+        // window loses focus
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        // check that text field cursor disappeared even within visible 500ms
+        rule.mainClock.advanceTimeBy(300)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertDoesNotContainColor(cursorColor)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun focusedTextField_resumeBlinking_whenWindowRegainsFocus() {
+        state = TextFieldState()
+        val focusWindow = mutableStateOf(true)
+        fun createWindowInfo(focused: Boolean) = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focused
+        }
+
+        rule.setTestContent {
+            CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
+                Box(Modifier.padding(boxPadding)) {
+                    BasicTextField2(
+                        state = state,
+                        textStyle = textStyle,
+                        modifier = textFieldModifier,
+                        cursorBrush = SolidColor(cursorColor),
+                        onTextLayout = onTextLayout
+                    )
+                }
+            }
+        }
+
+        focusAndWait()
+
+        // window loses focus
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        // check that text field cursor disappeared even within visible 500ms
+        rule.mainClock.advanceTimeBy(100)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertDoesNotContainColor(cursorColor)
+
+        // window regains focus within 500ms
+        focusWindow.value = true
+        rule.waitForIdle()
+
+        rule.mainClock.advanceTimeBy(100)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(cursorColor)
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_keepsSelection_whenWindowLosesFocus() {
+        state = TextFieldState("hello", initialSelectionInChars = TextRange(0, 5))
+        val selectionColor = Color.Blue
+        val focusWindow = mutableStateOf(true)
+        val windowInfo = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focusWindow.value
+        }
+
+        rule.setTestContent {
+            CompositionLocalProvider(
+                LocalWindowInfo provides windowInfo,
+                LocalTextSelectionColors provides TextSelectionColors(
+                    selectionColor,
+                    selectionColor
+                )
+            ) {
+                BasicTextField2(
+                    state = state,
+                    // make sure that background is not obstructing selection
+                    textStyle = textStyle.copy(background = Color.Unspecified),
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(selectionColor)
+
+        // window lost focus, make sure selection still drawn
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertContainsColor(selectionColor)
+    }
+
+    @Ignore("b/305799612")
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textDragging_cursorRendered() {
+        state = TextFieldState("Hello World")
+        var view: View? = null
+        rule.setTestContent {
+            view = LocalView.current
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
+            )
+
+            view?.dispatchDragEvent(startEvent)
+            view?.dispatchDragEvent(enterEvent)
+            view?.dispatchDragEvent(moveEvent)
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Ignore("b/305799612")
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textDragging_cursorDisappearsAfterTimeout() {
+        state = TextFieldState("Hello World")
+        var view: View? = null
+        rule.setTestContent {
+            view = LocalView.current
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
+            )
+
+            view?.dispatchDragEvent(startEvent)
+            view?.dispatchDragEvent(enterEvent)
+            view?.dispatchDragEvent(moveEvent)
+        }
+
+        rule.mainClock.advanceTimeBy(500)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = contentColor,
+                backgroundColor = contentColor,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Ignore("b/305799612")
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textDragging_cursorDoesNotDisappearWhileMoving() {
+        state = TextFieldState("Hello World")
+        var view: View? = null
+        rule.setTestContent {
+            view = LocalView.current
+            BasicTextField2(
+                state = state,
+                textStyle = textStyle,
+                modifier = textFieldModifier,
+                cursorBrush = SolidColor(cursorColor),
+                onTextLayout = onTextLayout
+            )
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
+            )
+
+            view?.dispatchDragEvent(startEvent)
+            view?.dispatchDragEvent(enterEvent)
+            view?.dispatchDragEvent(moveEvent)
+        }
+
+        rule.mainClock.advanceTimeBy(300)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+
+        val moveEvent2 = makeTextDragEvent(
+            action = DragEvent.ACTION_DRAG_LOCATION,
+            offset = Offset(with(rule.density) { fontSize.toPx() * 4 }, 5f)
+        )
+        view?.dispatchDragEvent(moveEvent2)
+        rule.mainClock.advanceTimeBy(400)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    @Ignore("b/305799612")
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_textDragging_noWindowFocus_cursorRendered() {
+        state = TextFieldState("Hello World")
+        var view: View? = null
+        rule.setContent {
+            Box(Modifier.padding(boxPadding)) {
+                view = LocalView.current
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
+            )
+
+            view?.dispatchDragEvent(startEvent)
+            view?.dispatchDragEvent(enterEvent)
+            view?.dispatchDragEvent(moveEvent)
+        }
+
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertCursor(cursorTopCenterInLtr)
+    }
+
+    private fun focusAndWait() {
+        rule.onNode(hasSetTextAction()).requestFocus()
+        rule.mainClock.advanceTimeUntil { isFocused }
+    }
+
+    /**
+     * @param cursorPosition Top center of cursor rectangle
+     */
+    private fun ImageBitmap.assertCursor(cursorPosition: Offset) {
+        assertThat(cursorPosition.x).isAtLeast(0f)
+        assertThat(cursorPosition.y).isAtLeast(0f)
+
+        // assert cursor width is greater than 2 since we will shrink the check area by 1 on each
+        // side
+        assertThat(cursorSizePx.width).isGreaterThan(2)
+
+        // shrink the check are by 1px for left, top, right, bottom
+        val checkRect = Rect(
+            ceil(cursorPosition.x - cursorSizePx.width / 2) + 1,
+            ceil(cursorPosition.y) + 1,
+            floor(cursorPosition.x + cursorSizePx.width / 2) - 1,
+            floor(cursorPosition.y + cursorSizePx.height) - 1
+        )
+
+        // skip an expanded rectangle that is 1px larger than cursor rectangle due to antialiasing
+        val skipRect = Rect(
+            floor(cursorPosition.x - cursorSizePx.width / 2) - 1,
+            floor(cursorPosition.y) - 1,
+            ceil(cursorPosition.x + cursorSizePx.width / 2) + 1,
+            ceil(cursorPosition.y + cursorSizePx.height) + 1
+        )
+
+        val width = width
+        val height = height
+        this.assertPixels(
+            IntSize(width, height)
+        ) { position ->
+            if (checkRect.contains(position.toOffset())) {
+                // cursor
+                cursorColor
+            } else if (skipRect.contains(position.toOffset())) {
+                // skip some pixels around cursor
+                null
+            } else {
+                // text field background
+                contentColor
+            }
+        }
+    }
+
+    @Test
+    fun textFieldCursor_alwaysReadLatestState_duringDraw() {
+        state = TextFieldState("hello world", TextRange(5))
+        rule.setTestContent {
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier.layout { measurable, constraints ->
+                        // change the state during layout so draw can read the new state
+                        val currValue = state.text
+                        if (currValue.isNotEmpty()) {
+                            val newText = currValue.dropLast(1)
+                            state.setTextAndPlaceCursorAtEnd(newText.toString())
+                        }
+
+                        val p = measurable.measure(constraints)
+                        layout(p.width, p.height) {
+                            p.place(0, 0)
+                        }
+                    },
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        rule.waitForIdle()
+
+        rule.onNode(hasSetTextAction()).assertTextEquals("")
+        // this test just needs to finish without crashing. There is no other assertion
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
new file mode 100644
index 0000000..bdb9a23
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
@@ -0,0 +1,529 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.net.Uri
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.TestActivity
+import androidx.compose.foundation.content.DragAndDropScope
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.foundation.content.ReceiveContentListener
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.createClipData
+import androidx.compose.foundation.content.receiveContent
+import androidx.compose.foundation.content.testDragAndDrop
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.firstUriOrNull
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldDragAndDropTest {
+
+    @get:Rule
+    val rule = createAndroidComposeRule<TestActivity>()
+
+    @Test
+    fun nonTextContent_isNotAccepted() {
+        rule.setContentAndTestDragAndDrop {
+            val startSelection = state.text.selectionInChars
+            drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
+            assertThat(state.text.selectionInChars).isEqualTo(startSelection)
+        }
+    }
+
+    @Test
+    fun nonTextContent_isAcceptedIfReceiveContentDefined() {
+        rule.setContentAndTestDragAndDrop(
+            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+                null
+            }
+        ) {
+            val accepted = drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
+            assertThat(accepted).isTrue()
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        }
+    }
+
+    @Test
+    fun textContent_isAccepted() {
+        rule.setContentAndTestDragAndDrop {
+            drag(Offset(fontSize.toPx() * 2, 10f), "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        }
+    }
+
+    @Test
+    fun draggingText_updatesSelection() {
+        rule.setContentAndTestDragAndDrop {
+            drag(Offset(fontSize.toPx() * 1, 10f), "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+            drag(Offset(fontSize.toPx() * 2, 10f), "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+            drag(Offset(fontSize.toPx() * 3, 10f), "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+        }
+    }
+
+    @Test
+    fun draggingNonText_updatesSelection_withReceiveContent() {
+        rule.setContentAndTestDragAndDrop(
+            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+                null
+            }
+        ) {
+            drag(Offset(fontSize.toPx() * 1, 10f), defaultUri)
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+            drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+            drag(Offset(fontSize.toPx() * 3, 10f), defaultUri)
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+        }
+    }
+
+    @Test
+    fun draggingText_toEndPadding_updatesSelection() {
+        rule.setContentAndTestDragAndDrop(
+            style = TextStyle(textAlign = TextAlign.Center),
+            modifier = Modifier.width(300.dp)
+        ) {
+            drag(Offset.Zero, "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            drag(Offset(295.dp.toPx(), 10f), "hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+        }
+    }
+
+    @Test
+    fun interactionSource_receivesHoverEnter_whenDraggingTextEnters() {
+        val interactionSource = MutableInteractionSource()
+        rule.setContentAndTestDragAndDrop(
+            style = TextStyle(textAlign = TextAlign.Center),
+            interactionSource = interactionSource,
+            modifier = Modifier.width(200.dp)
+        ) {
+            drag(Offset(1f, 1f), "hello")
+            assertThat(isHovered).isTrue()
+        }
+    }
+
+    @Test
+    fun interactionSource_receivesHoverExit_whenDraggingTextExits() {
+        val interactionSource = MutableInteractionSource()
+        rule.setContentAndTestDragAndDrop(
+            style = TextStyle(textAlign = TextAlign.Center),
+            interactionSource = interactionSource,
+            modifier = Modifier.width(200.dp)
+        ) {
+            drag(Offset(1f, 1f), "hello")
+            assertThat(isHovered).isTrue()
+
+            drag(Offset(1000f, 1f), "hello")
+            assertThat(isHovered).isFalse()
+        }
+    }
+
+    @Test
+    fun interactionSource_receivesHoverExit_whenDraggingTextEnds() {
+        val interactionSource = MutableInteractionSource()
+        rule.setContentAndTestDragAndDrop(
+            style = TextStyle(textAlign = TextAlign.Center),
+            interactionSource = interactionSource,
+            modifier = Modifier.width(200.dp)
+        ) {
+            drag(Offset(1f, 1f), "hello")
+            assertThat(isHovered).isTrue()
+
+            cancelDrag()
+            assertThat(isHovered).isFalse()
+        }
+    }
+
+    @Test
+    fun interactionSource_receivesHoverExit_whenDraggingTextDrops() {
+        val interactionSource = MutableInteractionSource()
+        rule.setContentAndTestDragAndDrop(
+            style = TextStyle(textAlign = TextAlign.Center),
+            interactionSource = interactionSource,
+            modifier = Modifier.width(200.dp)
+        ) {
+            drag(Offset(1f, 1f), "hello")
+            assertThat(isHovered).isTrue()
+
+            drop()
+            assertThat(isHovered).isFalse()
+        }
+    }
+
+    @Test
+    fun draggingOntoTextField_keepsWrapperReceiveContentEntered() {
+        // this is a nested scenario where moving a dragging item from receiveContent to
+        // BTF2 area does not send an exit event to receiveContent drag listener
+        lateinit var view: View
+        val density = Density(1f, 1f)
+        val calls = mutableListOf<String>()
+        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
+            view = LocalView.current
+            CompositionLocalProvider(
+                LocalDensity provides density,
+                LocalWindowInfo provides object : WindowInfo {
+                    override val isWindowFocused = false
+                }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(200.dp)
+                        .receiveContent(emptySet(), object : ReceiveContentListener {
+                            override fun onDragStart() {
+                                calls += "start"
+                            }
+
+                            override fun onDragEnd() {
+                                calls += "end"
+                            }
+
+                            override fun onDragEnter() {
+                                calls += "enter"
+                            }
+
+                            override fun onDragExit() {
+                                calls += "exit"
+                            }
+
+                            override fun onReceive(c: TransferableContent): TransferableContent? {
+                                calls += "receive"
+                                return null
+                            }
+                        })
+                ) {
+                    BasicTextField2(
+                        state = rememberTextFieldState(),
+                        textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                        lineLimits = TextFieldLineLimits.SingleLine,
+                        modifier = Modifier
+                            .width(100.dp)
+                            .height(40.dp)
+                            .align(Alignment.Center)
+                    )
+                }
+            }
+        }
+
+        testDragAndDrop(view, density) {
+            drag(Offset(1f, 1f), defaultUri)
+            assertThat(calls).isEqualTo(listOf("start", "enter"))
+
+            cancelDrag()
+            assertThat(calls).isEqualTo(listOf("start", "enter", "end"))
+            calls.clear()
+
+            drag(Offset(1f, 1f), defaultUri)
+            drag(Offset(100f, 100f), defaultUri) // should be inside TextField's area
+
+            // expect no extra enter/exit calls
+            assertThat(calls).isEqualTo(listOf("start", "enter"))
+            drop()
+
+            assertThat(calls).isEqualTo(listOf("start", "enter", "receive"))
+        }
+    }
+
+    @Test
+    fun draggingOutOfTextField_keepsWrapperReceiveContentEntered() {
+        // this is a nested scenario where moving a dragging item from receiveContent to
+        // BTF2 area does not send an exit event to receiveContent drag listener
+        lateinit var view: View
+        val density = Density(1f, 1f)
+        val calls = mutableListOf<String>()
+        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
+            view = LocalView.current
+            CompositionLocalProvider(
+                LocalDensity provides density,
+                LocalWindowInfo provides object : WindowInfo {
+                    override val isWindowFocused = false
+                }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(200.dp)
+                        .receiveContent(emptySet(), object : ReceiveContentListener {
+                            override fun onDragStart() {
+                                calls += "start"
+                            }
+
+                            override fun onDragEnd() {
+                                calls += "end"
+                            }
+
+                            override fun onDragEnter() {
+                                calls += "enter"
+                            }
+
+                            override fun onDragExit() {
+                                calls += "exit"
+                            }
+
+                            override fun onReceive(c: TransferableContent): TransferableContent? {
+                                calls += "receive"
+                                return null
+                            }
+                        })
+                ) {
+                    BasicTextField2(
+                        state = rememberTextFieldState(),
+                        textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                        lineLimits = TextFieldLineLimits.SingleLine,
+                        modifier = Modifier
+                            .width(100.dp)
+                            .height(40.dp)
+                            .align(Alignment.Center)
+                    )
+                }
+            }
+        }
+
+        testDragAndDrop(view, density) {
+            drag(Offset(100f, 100f), defaultUri) // should be inside TextField's area
+            assertThat(calls).isEqualTo(listOf("start", "enter"))
+
+            drag(Offset(199f, 199f), defaultUri)
+            assertThat(calls).isEqualTo(listOf("start", "enter")) // no exit event
+
+            drag(Offset(201f, 201f), defaultUri)
+            assertThat(calls).isEqualTo(listOf("start", "enter", "exit")) // no exit event
+        }
+    }
+
+    @Test
+    fun droppedText_insertsAtCursor() {
+        rule.setContentAndTestDragAndDrop("Hello World!") {
+            drag(
+                Offset(fontSize.toPx() * 5, 10f),
+                " Awesome"
+            )
+            drop()
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange("Hello Awesome".length))
+            assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
+        }
+    }
+
+    @Test
+    fun dropped_textAndNonTextCombined_insertsAtCursor() {
+        lateinit var receivedContent: TransferableContent
+        rule.setContentAndTestDragAndDrop(
+            "Hello World!",
+            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+                receivedContent = it
+                receivedContent.consumeEach {
+                    // do not consume text
+                    it.uri != null
+                }
+            }
+        ) {
+            val clipData = createClipData {
+                addText(" Awesome")
+                addUri(defaultUri)
+            }
+            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
+            drop()
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange("Hello Awesome".length))
+            assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
+            assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
+            assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
+        }
+    }
+
+    @Test
+    fun dropped_textAndNonTextCombined_consumedEverything_doesNotInsert() {
+        lateinit var receivedContent: TransferableContent
+        rule.setContentAndTestDragAndDrop(
+            "Hello World!",
+            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+                receivedContent = it
+                // consume everything
+                null
+            }
+        ) {
+            val clipData = createClipData {
+                addText(" Awesome")
+                addUri(defaultUri)
+            }
+            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
+            drop()
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+            assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
+            assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
+        }
+    }
+
+    @Test
+    fun dropped_consumedAndReplaced_insertsAtCursor() {
+        lateinit var receivedContent: TransferableContent
+        rule.setContentAndTestDragAndDrop(
+            "Hello World!",
+            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+                receivedContent = it
+                val uri = receivedContent.clipEntry.firstUriOrNull()
+                // replace the content
+                val clipData = createClipData { addText(uri.toString()) }
+                TransferableContent(clipData)
+            }
+        ) {
+            val clipData = createClipData {
+                addUri(defaultUri)
+            }
+            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
+            drop()
+            assertThat(state.text.toString()).isEqualTo("Hello$defaultUri World!")
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    fun droppedItem_requestsPermission_ifReceiveContent() {
+        rule.setContentAndTestDragAndDrop(
+            "Hello World!",
+            modifier = Modifier.receiveContent(emptySet()) { null }
+        ) {
+            drag(Offset(fontSize.toPx() * 5, 10f), defaultUri)
+            drop()
+            assertThat(rule.activity.requestedDragAndDropPermissions).isNotEmpty()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    fun droppedItem_doesNotRequestPermission_ifNoReceiveContent() {
+        rule.setContentAndTestDragAndDrop("Hello World!") {
+            drag(Offset(fontSize.toPx() * 5, 10f), createClipData {
+                addText()
+                addUri()
+            })
+            drop()
+            assertThat(rule.activity.requestedDragAndDropPermissions).isEmpty()
+        }
+    }
+
+    @Test
+    fun multipleClipDataItems_concatsByNewLine() {
+        rule.setContentAndTestDragAndDrop("aaaa") {
+            drag(
+                Offset(fontSize.toPx() * 2, 10f),
+                createClipData {
+                    addText("Hello")
+                    addText("World")
+                }
+            )
+            drop()
+            assertThat(state.text.toString()).isEqualTo("aaHello\nWorldaa")
+        }
+    }
+
+    private fun ComposeContentTestRule.setContentAndTestDragAndDrop(
+        textContent: String = "aaaa",
+        isWindowFocused: Boolean = false,
+        style: TextStyle = TextStyle.Default,
+        interactionSource: MutableInteractionSource? = null,
+        modifier: Modifier = Modifier,
+        block: DragAndDropTestScope.() -> Unit
+    ) {
+        val state = TextFieldState(
+            textContent,
+            initialSelectionInChars = TextRange.Zero
+        )
+        var view: View? = null
+        val density = Density(1f, 1f)
+        val mergedStyle = TextStyle(
+            fontFamily = TEST_FONT_FAMILY,
+            fontSize = 20.sp
+        ).merge(style)
+        var isHovered: State<Boolean>? = null
+        setContent { // Do not use setTextFieldTestContent for DnD tests.
+            view = LocalView.current
+            CompositionLocalProvider(
+                LocalDensity provides density,
+                LocalWindowInfo provides object : WindowInfo {
+                    override val isWindowFocused = isWindowFocused
+                }
+            ) {
+                isHovered = interactionSource?.collectIsHoveredAsState()
+                BasicTextField2(
+                    state = state,
+                    textStyle = mergedStyle,
+                    lineLimits = TextFieldLineLimits.SingleLine,
+                    interactionSource = interactionSource,
+                    modifier = modifier
+                )
+            }
+        }
+
+        testDragAndDrop(view!!, density) {
+            DragAndDropTestScope(state, mergedStyle.fontSize, isHovered, this).block()
+        }
+    }
+
+    @OptIn(ExperimentalFoundationApi::class)
+    private class DragAndDropTestScope(
+        val state: TextFieldState,
+        val fontSize: TextUnit,
+        isHovered: State<Boolean>?,
+        dragAndDropScopeImpl: DragAndDropScope,
+    ) : DragAndDropScope by dragAndDropScopeImpl {
+        val isHovered: Boolean by (isHovered ?: mutableStateOf(false))
+    }
+}
+
+private val defaultUri = Uri.parse("content://com.example/content.jpg")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt
new file mode 100644
index 0000000..fbb40e8
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt
@@ -0,0 +1,501 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class TextFieldFocusTest {
+    @get:Rule
+    val rule = createComposeRule()
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val testKeyboardController = TestSoftwareKeyboardController(rule)
+
+    @Composable
+    private fun TextFieldApp(dataList: List<FocusTestData>) {
+        for (data in dataList) {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .focusRequester(data.focusRequester)
+                    .onFocusChanged { data.focused = it.isFocused }
+                    .requiredWidth(10.dp)
+            )
+        }
+    }
+
+    data class FocusTestData(val focusRequester: FocusRequester, var focused: Boolean = false)
+
+    @Test
+    fun requestFocus() {
+        lateinit var testDataList: List<FocusTestData>
+
+        rule.setContent {
+            testDataList = listOf(
+                FocusTestData(FocusRequester()),
+                FocusTestData(FocusRequester()),
+                FocusTestData(FocusRequester())
+            )
+
+            TextFieldApp(testDataList)
+        }
+
+        rule.runOnIdle { testDataList[0].focusRequester.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isTrue()
+            assertThat(testDataList[1].focused).isFalse()
+            assertThat(testDataList[2].focused).isFalse()
+        }
+
+        rule.runOnIdle { testDataList[1].focusRequester.requestFocus() }
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isFalse()
+            assertThat(testDataList[1].focused).isTrue()
+            assertThat(testDataList[2].focused).isFalse()
+        }
+
+        rule.runOnIdle { testDataList[2].focusRequester.requestFocus() }
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isFalse()
+            assertThat(testDataList[1].focused).isFalse()
+            assertThat(testDataList[2].focused).isTrue()
+        }
+    }
+
+    @Test
+    fun noCrashWhenSwitchingBetweenEnabledFocusedAndDisabledTextField() {
+        val enabled = mutableStateOf(true)
+        var focused = false
+        val tag = "textField"
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                enabled = enabled.value,
+                modifier = Modifier
+                    .testTag(tag)
+                    .onFocusChanged {
+                        focused = it.isFocused
+                    }
+                    .requiredWidth(10.dp)
+            )
+        }
+        // bring enabled text field into focus
+        rule.onNodeWithTag(tag).performClick()
+        rule.runOnIdle {
+            assertThat(focused).isTrue()
+        }
+
+        // make text field disabled
+        enabled.value = false
+        rule.runOnIdle {
+            assertThat(focused).isFalse()
+        }
+
+        // make text field enabled again, it must not crash
+        enabled.value = true
+        rule.runOnIdle {
+            assertThat(focused).isFalse()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun textInputStarted_forFieldInActivity_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        textInputStarted_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            }
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun textInputStarted_forFieldInActivity_whenFocusRequestedImmediately_fromDisposableEffect() {
+        textInputStarted_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun textInputStarted_forFieldInDialog_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        textInputStarted_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun textInputStarted_forFieldInDialog_whenFocusRequestedImmediately_fromDisposableEffect() {
+        textInputStarted_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    private fun textInputStarted_whenFocusRequestedImmediately_fromEffect(
+        runEffect: @Composable (body: () -> Unit) -> Unit,
+        wrapContent: @Composable (@Composable () -> Unit) -> Unit = { it() }
+    ) {
+        val focusRequester = FocusRequester()
+        val state = TextFieldState()
+
+        inputMethodInterceptor.setContent {
+            wrapContent {
+                runEffect {
+                    focusRequester.requestFocus()
+                }
+
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier.focusRequester(focusRequester)
+                )
+            }
+        }
+
+        inputMethodInterceptor.assertSessionActive()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun basicTextField_checkFocusNavigation_onDPadLeft() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on left
+        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_LEFT)) return
+
+        // Check if the element to the left of text field gains focus
+        rule.onNodeWithTag("test-button-left").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun basicTextField_checkFocusNavigation_onDPadRight() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on right
+        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_RIGHT)) return
+
+        // Check if the element to the right of text field gains focus
+        rule.onNodeWithTag("test-button-right").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun basicTextField_checkFocusNavigation_onDPadUp() {
+        setupAndEnableBasicTextField()
+        inputMultilineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on top
+        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_UP)) return
+
+        // Check if the element on the top of text field gains focus
+        rule.onNodeWithTag("test-button-top").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun basicTextField_checkFocusNavigation_onDPadDown() {
+        setupAndEnableBasicTextField()
+        inputMultilineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on bottom
+        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_DOWN)) return
+
+        // Check if the element to the bottom of text field gains focus
+        rule.onNodeWithTag("test-button-bottom").assertIsFocused()
+    }
+
+    @FlakyTest(bugId = 305087008)
+    @Test
+    fun basicTextField_checkKeyboardShown_onDPadCenter() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        testKeyboardController.assertHidden()
+
+        // Check if keyboard is enabled on Dpad center key press
+        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_CENTER)) return
+        testKeyboardController.assertShown()
+    }
+
+    @Test
+    fun basicTextField_handlesInvalidDevice() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // -2 shouldn't be a valid device – we verify this below by asserting the device in the
+        // event is actually null.
+        val invalidDeviceId = -2
+        val keyCode = NativeKeyEvent.KEYCODE_DPAD_CENTER
+        val keyEventDown = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_DOWN, keyCode, 0, 0, invalidDeviceId, 0
+        )
+        assertThat(keyEventDown.device).isNull()
+        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
+        rule.waitForIdle()
+        val keyEventUp = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_UP, keyCode, 0, 0, invalidDeviceId, 0
+        )
+        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
+        rule.waitForIdle()
+    }
+
+    private fun setupAndEnableBasicTextField() {
+        setupContent()
+
+        rule.onNodeWithTag("test-text-field-1").assertIsFocused()
+    }
+
+    private fun inputSingleLineTextInBasicTextField() {
+        // Input "abc"
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
+        rule.waitForIdle()
+    }
+
+    private fun inputMultilineTextInBasicTextField() {
+        // Input "a\nb\nc"
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
+        rule.waitForIdle()
+    }
+
+    private fun setupContent() {
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalSoftwareKeyboardController provides testKeyboardController
+            ) {
+                Column {
+                    Row(horizontalArrangement = Arrangement.Center) {
+                        TestFocusableElement(id = "top")
+                    }
+                    Row {
+                        TestFocusableElement(id = "left")
+                        TestBasicTextField2(id = "1", requestFocus = true)
+                        TestFocusableElement(id = "right")
+                    }
+                    Row(horizontalArrangement = Arrangement.Center) {
+                        TestFocusableElement(id = "bottom")
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+    }
+
+    @Composable
+    private fun TestFocusableElement(id: String) {
+        var isFocused by remember {
+            mutableStateOf(false)
+        }
+        BasicText(
+            text = "test-button-$id",
+            modifier = Modifier
+                .testTag("test-button-$id")
+                .padding(10.dp)
+                .onFocusChanged {
+                    isFocused = it.hasFocus
+                }
+                .focusable()
+                .border(2.dp, if (isFocused) Color.Green else Color.Cyan)
+        )
+    }
+
+    @Composable
+    private fun TestBasicTextField2(
+        id: String,
+        requestFocus: Boolean = false
+    ) {
+        val state = rememberTextFieldState()
+        var isFocused by remember {
+            mutableStateOf(false)
+        }
+        val focusRequester = remember {
+            FocusRequester()
+        }
+        val modifier = if (requestFocus) Modifier.focusRequester(focusRequester) else Modifier
+
+        BasicTextField2(
+            state = state,
+            modifier = modifier
+                .testTag("test-text-field-$id")
+                .padding(10.dp)
+                .onFocusChanged {
+                    isFocused = it.isFocused || it.hasFocus
+                }
+                .border(2.dp, if (isFocused) Color.Red else Color.Transparent)
+        )
+
+        LaunchedEffect(requestFocus, focusRequester) {
+            if (requestFocus) focusRequester.requestFocus()
+        }
+    }
+
+    /** Triggers a key press on the root node from a non-virtual dpad device (if supported). */
+    private fun keyPressOnDpadInputDevice(
+        rule: ComposeContentTestRule,
+        keyCode: Int,
+        count: Int = 1
+    ) = keyPressOnPhysicalDevice(rule, keyCode, InputDevice.SOURCE_DPAD, count)
+
+    private fun keyPressOnPhysicalDevice(
+        rule: ComposeContentTestRule,
+        keyCode: Int,
+        source: Int,
+        count: Int = 1,
+        metaState: Int = 0,
+    ): Boolean {
+        val deviceId = InputDevice.getDeviceIds().firstOrNull { id ->
+            InputDevice.getDevice(id)?.isVirtual?.not() ?: false &&
+                InputDevice.getDevice(id)?.supportsSource(source) ?: false
+        } ?: return false
+        val keyEventDown = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_DOWN, keyCode, 0, metaState,
+            deviceId, 0, 0, InputDevice.SOURCE_DPAD
+        )
+        val keyEventUp = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_UP, keyCode, 0, metaState,
+            deviceId, 0, 0, InputDevice.SOURCE_DPAD
+        )
+
+        repeat(count) {
+            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
+            rule.waitForIdle()
+            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
+        }
+        return true
+    }
+
+    /** Triggers a key press on the virtual keyboard. */
+    private fun keyPressOnVirtualKeyboard(keyCode: Int, count: Int = 1) {
+        repeat(count) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
new file mode 100644
index 0000000..89dc380
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
@@ -0,0 +1,755 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.KeyInjectionScope
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.withKeyDown
+import androidx.compose.ui.test.withKeysDown
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(
+    ExperimentalFoundationApi::class,
+    ExperimentalTestApi::class
+)
+class TextFieldKeyEventTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val tag = "TextFieldTestTag"
+
+    private var defaultDensity = Density(1f)
+
+    @Test
+    fun textField_typedEvents() {
+        keysSequenceTest {
+            pressKey(Key.H)
+            press(Key.ShiftLeft + Key.I)
+            expectedText("hI")
+        }
+    }
+
+    @Test
+    fun textField_copyPaste() {
+        keysSequenceTest("hello") {
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.A)
+                pressKey(Key.C)
+            }
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            press(Key.CtrlLeft + Key.V)
+            expectedText("hello hello")
+        }
+    }
+
+    @Test
+    fun secureTextField_doesNotAllowCopy() {
+        keysSequenceTest("hello", secure = true) {
+            clipboardManager.setText(AnnotatedString("world"))
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.A)
+                pressKey(Key.C)
+            }
+            pressKey(Key.Copy) // also attempt direct copy
+            expectedClipboardText("world")
+        }
+    }
+
+    @Test
+    fun textField_directCopyPaste() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.A)
+            pressKey(Key.Copy)
+            expectedText("hello")
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            pressKey(Key.Paste)
+            expectedText("hello hello")
+        }
+    }
+
+    @Test
+    fun textField_directCutPaste() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.A)
+            pressKey(Key.Cut)
+            expectedText("")
+            pressKey(Key.Paste)
+            expectedText("hello")
+        }
+    }
+
+    @Test
+    fun secureTextField_doesNotAllowCut() {
+        keysSequenceTest("hello", secure = true) {
+            clipboardManager.setText(AnnotatedString("world"))
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.A)
+                pressKey(Key.X)
+            }
+            pressKey(Key.Cut) // Also attempts direct cut
+            expectedText("hello")
+            expectedClipboardText("world")
+        }
+    }
+
+    @Test
+    fun textField_linesNavigation() {
+        keysSequenceTest("hello\nworld") {
+            pressKey(Key.DirectionDown)
+            pressKey(Key.A)
+            pressKey(Key.DirectionUp)
+            pressKey(Key.A)
+            expectedText("haello\naworld")
+            pressKey(Key.DirectionUp)
+            pressKey(Key.A)
+            expectedText("ahaello\naworld")
+        }
+    }
+
+    @Test
+    fun textField_linesNavigation_cache() {
+        keysSequenceTest("hello\n\nworld") {
+            pressKey(Key.DirectionRight)
+            pressKey(Key.DirectionDown)
+            pressKey(Key.DirectionDown)
+            pressKey(Key.Zero)
+            expectedText("hello\n\nw0orld")
+        }
+    }
+
+    @Test
+    fun textField_newLine() {
+        keysSequenceTest("hello") {
+            pressKey(Key.Enter)
+            expectedText("\nhello")
+        }
+    }
+
+    @Test
+    fun textField_backspace() {
+        keysSequenceTest("hello") {
+            pressKey(Key.DirectionRight)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Backspace)
+            expectedText("hllo")
+        }
+    }
+
+    @Test
+    fun textField_delete() {
+        keysSequenceTest("hello") {
+            pressKey(Key.Delete)
+            expectedText("ello")
+        }
+    }
+
+    @Test
+    fun textField_delete_atEnd() {
+        keysSequenceTest("hello", TextRange(5)) {
+            pressKey(Key.Delete)
+            expectedText("hello")
+        }
+    }
+
+    @Test
+    fun textField_delete_whenEmpty() {
+        keysSequenceTest {
+            pressKey(Key.Delete)
+            expectedText("")
+        }
+    }
+
+    @Test
+    fun textField_nextWord() {
+        keysSequenceTest("hello world") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Zero)
+            expectedText("hello0 world")
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Zero)
+            expectedText("hello0 world0")
+        }
+    }
+
+    @Test
+    fun textField_nextWord_doubleSpace() {
+        keysSequenceTest("hello  world") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.DirectionRight)
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Zero)
+            expectedText("hello  world0")
+        }
+    }
+
+    @Test
+    fun textField_prevWord() {
+        keysSequenceTest("hello world") {
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionLeft)
+            }
+            pressKey(Key.Zero)
+            expectedText("hello 0world")
+        }
+    }
+
+    @Test
+    fun textField_HomeAndEnd() {
+        keysSequenceTest("hello world") {
+            pressKey(Key.MoveEnd)
+            pressKey(Key.Zero)
+            pressKey(Key.MoveHome)
+            pressKey(Key.Zero)
+            expectedText("0hello world0")
+        }
+    }
+
+    @Test
+    fun textField_byWordSelection() {
+        keysSequenceTest("hello  world\nhi") {
+            withKeysDown(listOf(Key.ShiftLeft, Key.CtrlLeft)) {
+                pressKey(Key.DirectionRight)
+                expectedSelection(TextRange(0, 5))
+                pressKey(Key.DirectionRight)
+                expectedSelection(TextRange(0, 12))
+                pressKey(Key.DirectionRight)
+                expectedSelection(TextRange(0, 15))
+                pressKey(Key.DirectionLeft)
+                expectedSelection(TextRange(0, 13))
+            }
+        }
+    }
+
+    @Test
+    fun textField_lineEndStart() {
+        keysSequenceTest(initText = "hi\nhello world\nhi") {
+            pressKey(Key.MoveEnd)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Zero)
+            expectedText("hi\n0hello world\nhi")
+            pressKey(Key.MoveEnd)
+            pressKey(Key.Zero)
+            expectedText("hi\n0hello world0\nhi")
+            withKeyDown(Key.ShiftLeft) { pressKey(Key.MoveHome) }
+            expectedSelection(TextRange(16, 3))
+            pressKey(Key.MoveHome)
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) { pressKey(Key.MoveEnd) }
+            expectedSelection(TextRange(4, 16))
+            expectedText("hi\n0hello world0\nhi")
+        }
+    }
+
+    @Test
+    fun textField_altLineLeftRight() {
+        keysSequenceTest(initText = "hi\nhello world\nhi") {
+            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionRight) }
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Zero)
+            expectedText("hi\n0hello world\nhi")
+            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionRight) }
+            pressKey(Key.Zero)
+            expectedText("hi\n0hello world0\nhi")
+            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionLeft) }
+            expectedSelection(TextRange(16, 3))
+            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionLeft) }
+            pressKey(Key.DirectionRight)
+            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionRight) }
+            expectedSelection(TextRange(4, 16))
+            expectedText("hi\n0hello world0\nhi")
+        }
+    }
+
+    @Test
+    fun textField_altTop() {
+        keysSequenceTest(initText = "hi\nhello world\nhi") {
+            pressKey(Key.MoveEnd)
+            repeat(3) { pressKey(Key.DirectionRight) }
+            pressKey(Key.Zero)
+            expectedText("hi\nhe0llo world\nhi")
+            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionUp) }
+            pressKey(Key.Zero)
+            expectedText("0hi\nhe0llo world\nhi")
+            pressKey(Key.MoveEnd)
+            repeat(3) { pressKey(Key.DirectionRight) }
+            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionUp) }
+            expectedSelection(TextRange(6, 0))
+            expectedText("0hi\nhe0llo world\nhi")
+        }
+    }
+
+    @Test
+    fun textField_altBottom() {
+        keysSequenceTest(initText = "hi\nhello world\nhi") {
+            pressKey(Key.MoveEnd)
+            repeat(3) { pressKey(Key.DirectionRight) }
+            pressKey(Key.Zero)
+            expectedText("hi\nhe0llo world\nhi")
+            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionDown) }
+            expectedSelection(TextRange(6, 18))
+            pressKey(Key.DirectionLeft)
+            pressKey(Key.Zero)
+            expectedText("hi\nhe00llo world\nhi")
+            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionDown) }
+            pressKey(Key.Zero)
+            expectedText("hi\nhe00llo world\nhi0")
+        }
+    }
+
+    @Ignore("b/305692638")
+    @Test
+    fun textField_deleteWords() {
+        keysSequenceTest("hello world\nhi world") {
+            pressKey(Key.MoveEnd)
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.Backspace)
+                expectedText("hello \nhi world")
+                pressKey(Key.Delete)
+            }
+            expectedText("hello  world")
+        }
+    }
+
+    @Test
+    fun textField_deleteToBeginningOfLine() {
+        keysSequenceTest("hello world\nhi world") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+
+            withKeyDown(Key.AltLeft) {
+                pressKey(Key.Backspace)
+                expectedText(" world\nhi world")
+                pressKey(Key.Backspace)
+                expectedText(" world\nhi world")
+            }
+
+            repeat(3) { pressKey(Key.DirectionRight) }
+
+            press(Key.AltLeft + Key.Backspace)
+            expectedText("rld\nhi world")
+            pressKey(Key.DirectionDown)
+            pressKey(Key.MoveEnd)
+
+            withKeyDown(Key.AltLeft) {
+                pressKey(Key.Backspace)
+                expectedText("rld\n")
+                pressKey(Key.Backspace)
+                expectedText("rld\n")
+            }
+        }
+    }
+
+    @Test
+    fun textField_deleteToEndOfLine() {
+        keysSequenceTest("hello world\nhi world") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            withKeyDown(Key.AltLeft) {
+                pressKey(Key.Delete)
+                expectedText("hello\nhi world")
+                pressKey(Key.Delete)
+                expectedText("hello\nhi world")
+            }
+
+            repeat(3) { pressKey(Key.DirectionRight) }
+
+            press(Key.AltLeft + Key.Delete)
+            expectedText("hello\nhi")
+
+            pressKey(Key.MoveHome)
+            withKeyDown(Key.AltLeft) {
+                pressKey(Key.Delete)
+                expectedText("hello\n")
+                pressKey(Key.Delete)
+                expectedText("hello\n")
+            }
+        }
+    }
+
+    @Test
+    fun textField_paragraphNavigation() {
+        keysSequenceTest("hello world\nhi") {
+            press(Key.CtrlLeft + Key.DirectionDown)
+            pressKey(Key.Zero)
+            expectedText("hello world0\nhi")
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.DirectionDown)
+                pressKey(Key.DirectionUp)
+            }
+            pressKey(Key.Zero)
+            expectedText("hello world0\n0hi")
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.DirectionUp)
+                pressKey(Key.DirectionUp)
+            }
+            pressKey(Key.Zero)
+            expectedText("0hello world0\n0hi")
+        }
+    }
+
+    @Test
+    fun textField_selectionCaret() {
+        keysSequenceTest("hello world") {
+            press(Key.CtrlLeft + Key.ShiftLeft + Key.DirectionRight)
+            expectedSelection(TextRange(0, 5))
+            press(Key.ShiftLeft + Key.DirectionRight)
+            expectedSelection(TextRange(0, 6))
+            press(Key.CtrlLeft + Key.Backslash)
+            expectedSelection(TextRange(6, 6))
+            press(Key.CtrlLeft + Key.ShiftLeft + Key.DirectionLeft)
+            expectedSelection(TextRange(6, 0))
+            press(Key.ShiftLeft + Key.DirectionRight)
+            expectedSelection(TextRange(6, 1))
+        }
+    }
+
+    @Test
+    fun textField_pageNavigationDown() {
+        keysSequenceTest(
+            initText = "A\nB\nC\nD\nE",
+            modifier = Modifier.requiredSize(73.dp)
+        ) {
+            pressKey(Key.PageDown)
+            expectedSelection(TextRange(4))
+        }
+    }
+
+    @Test
+    fun textField_pageNavigationDown_exactFit() {
+        keysSequenceTest(
+            initText = "A\nB\nC\nD\nE",
+            modifier = Modifier.requiredSize(90.dp) // exactly 3 lines fit
+        ) {
+            pressKey(Key.PageDown)
+            expectedSelection(TextRange(6))
+        }
+    }
+
+    @Test
+    fun textField_pageNavigationUp() {
+        keysSequenceTest(
+            initText = "A\nB\nC\nD\nE",
+            initSelection = TextRange(8), // just before 5
+            modifier = Modifier.requiredSize(73.dp)
+        ) {
+            pressKey(Key.PageUp)
+            expectedSelection(TextRange(4))
+        }
+    }
+
+    @Test
+    fun textField_pageNavigationUp_exactFit() {
+        keysSequenceTest(
+            initText = "A\nB\nC\nD\nE",
+            initSelection = TextRange(8), // just before 5
+            modifier = Modifier.requiredSize(90.dp) // exactly 3 lines fit
+        ) {
+            pressKey(Key.PageUp)
+            expectedSelection(TextRange(2))
+        }
+    }
+
+    @Test
+    fun textField_pageNavigationUp_cantGoUp() {
+        keysSequenceTest(
+            initText = "1\n2\n3\n4\n5",
+            initSelection = TextRange(0),
+            modifier = Modifier.requiredSize(90.dp)
+        ) {
+            pressKey(Key.PageUp)
+            expectedSelection(TextRange(0))
+        }
+    }
+
+    @Test
+    fun textField_tabSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            pressKey(Key.Tab)
+            expectedText("text") // no change, should try focus change instead
+        }
+    }
+
+    @Test
+    fun textField_tabMultiLine() {
+        keysSequenceTest("text") {
+            pressKey(Key.Tab)
+            expectedText("\ttext")
+        }
+    }
+
+    @Test
+    fun textField_shiftTabSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            press(Key.ShiftLeft + Key.Tab)
+            expectedText("text") // no change, should try focus change instead
+        }
+    }
+
+    @Test
+    fun textField_enterSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            pressKey(Key.Enter)
+            expectedText("text") // no change, should do ime action instead
+        }
+    }
+
+    @Test
+    fun textField_enterMultiLine() {
+        keysSequenceTest("text") {
+            pressKey(Key.Enter)
+            expectedText("\ntext")
+        }
+    }
+
+    @Test
+    fun textField_withActiveSelection_tabSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+            }
+            pressKey(Key.Tab)
+            expectedText("text") // no change, should try focus change instead
+        }
+    }
+
+    @Test
+    fun textField_withActiveSelection_tabMultiLine() {
+        keysSequenceTest("text") {
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+            }
+            pressKey(Key.Tab)
+            expectedText("t\tt")
+        }
+    }
+
+    @Test
+    fun textField_selectToLeft() {
+        keysSequenceTest("hello world hello") {
+            pressKey(Key.MoveEnd)
+            expectedSelection(TextRange(17))
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionLeft)
+                pressKey(Key.DirectionLeft)
+                pressKey(Key.DirectionLeft)
+            }
+            expectedSelection(TextRange(17, 14))
+        }
+    }
+
+    @Test
+    fun textField_withActiveSelection_shiftTabSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+                pressKey(Key.Tab)
+            }
+            expectedText("text") // no change, should try focus change instead
+        }
+    }
+
+    @Test
+    fun textField_withActiveSelection_enterSingleLine() {
+        keysSequenceTest("text", singleLine = true) {
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+            }
+            pressKey(Key.Enter)
+            expectedText("text") // no change, should do ime action instead
+        }
+    }
+
+    @Test
+    fun textField_withActiveSelection_enterMultiLine() {
+        keysSequenceTest("text") {
+            pressKey(Key.DirectionRight)
+            withKeyDown(Key.ShiftLeft) {
+                pressKey(Key.DirectionRight)
+                pressKey(Key.DirectionRight)
+            }
+            pressKey(Key.Enter)
+            expectedText("t\nt")
+        }
+    }
+
+    @Test
+    fun textField_simpleUndo() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            pressKey(Key.A)
+            pressKey(Key.B)
+            pressKey(Key.C)
+            expectedText("hello abc")
+            press(Key.CtrlLeft + Key.Z)
+            expectedText("hello")
+        }
+    }
+
+    @Test
+    fun textField_simpleRedo() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            pressKey(Key.A)
+            pressKey(Key.B)
+            pressKey(Key.C)
+            expectedText("hello abc")
+            press(Key.CtrlLeft + Key.Z)
+            expectedText("hello")
+            press(Key.CtrlLeft + Key.ShiftLeft + Key.Z)
+            expectedText("hello abc")
+        }
+    }
+
+    private inner class SequenceScope(
+        val state: TextFieldState,
+        val clipboardManager: ClipboardManager,
+        private val keyInjectionScope: KeyInjectionScope
+    ) : KeyInjectionScope by keyInjectionScope {
+
+        fun press(keys: List<Key>) {
+            require(keys.isNotEmpty()) { "At least one key must be specified for press action" }
+            if (keys.size == 1) {
+                pressKey(keys.first())
+            } else {
+                withKeysDown(keys.dropLast(1)) { pressKey(keys.last()) }
+            }
+        }
+
+        infix operator fun Key.plus(other: Key): MutableList<Key> {
+            return mutableListOf(this, other)
+        }
+
+        fun expectedText(text: String) {
+            rule.runOnIdle {
+                assertThat(state.text.toString()).isEqualTo(text)
+            }
+        }
+
+        fun expectedSelection(selection: TextRange) {
+            rule.runOnIdle {
+                assertThat(state.text.selectionInChars).isEqualTo(selection)
+            }
+        }
+
+        fun expectedClipboardText(text: String) {
+            rule.runOnIdle {
+                assertThat(clipboardManager.getText()?.text).isEqualTo(text)
+            }
+        }
+    }
+
+    private fun keysSequenceTest(
+        initText: String = "",
+        initSelection: TextRange = TextRange.Zero,
+        modifier: Modifier = Modifier.fillMaxSize(),
+        singleLine: Boolean = false,
+        secure: Boolean = false,
+        sequence: SequenceScope.() -> Unit,
+    ) {
+        val state = TextFieldState(initText, initSelection)
+        val focusRequester = FocusRequester()
+        val clipboardManager = FakeClipboardManager("InitialTestText")
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides defaultDensity,
+                LocalClipboardManager provides clipboardManager,
+            ) {
+                if (!secure) {
+                    BasicTextField2(
+                        state = state,
+                        textStyle = TextStyle(
+                            fontFamily = TEST_FONT_FAMILY,
+                            fontSize = 30.sp
+                        ),
+                        modifier = modifier
+                            .focusRequester(focusRequester)
+                            .testTag(tag),
+                        lineLimits = if (singleLine) SingleLine else MultiLine(),
+                    )
+                } else {
+                    BasicSecureTextField(
+                        state = state,
+                        textStyle = TextStyle(
+                            fontFamily = TEST_FONT_FAMILY,
+                            fontSize = 30.sp
+                        ),
+                        modifier = modifier
+                            .focusRequester(focusRequester)
+                            .testTag(tag)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        rule.waitForIdle()
+        rule.mainClock.advanceTimeBy(1000)
+
+        rule.onNodeWithTag(tag).performKeyInput {
+            sequence(SequenceScope(state, clipboardManager, this@performKeyInput))
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt
new file mode 100644
index 0000000..a0463d2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt
@@ -0,0 +1,414 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.view.inputmethod.EditorInfo
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.KeyboardActionScope
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.assertIsNotFocused
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performImeAction
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldKeyboardActionsTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    @Test
+    fun textField_performsImeAction_viaSemantics() {
+        var called = false
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
+                keyboardActions = KeyboardActions {
+                    called = true
+                }
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).performImeAction()
+
+        assertThat(called).isTrue()
+    }
+
+    @Test
+    fun textField_performsImeAction_viaInputConnection() {
+        var called = false
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
+                keyboardActions = KeyboardActions {
+                    called = true
+                }
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).requestFocus()
+
+        inputMethodInterceptor.withInputConnection {
+            performEditorAction(EditorInfo.IME_ACTION_SEND)
+            assertThat(called).isTrue()
+        }
+    }
+
+    @Test
+    fun textField_performsUnexpectedImeAction_fromInputConnection() {
+        var calledFor: ImeAction? = null
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
+                keyboardActions = KeyboardActionsAll {
+                    calledFor = it
+                }
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).requestFocus()
+
+        inputMethodInterceptor.withInputConnection {
+            performEditorAction(EditorInfo.IME_ACTION_SEARCH)
+            assertThat(calledFor).isEqualTo(ImeAction.Search)
+        }
+    }
+
+    @Test
+    fun textField_performsDefaultBehavior_forFocusNext() {
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box1"))
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
+                )
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box2"))
+            }
+        }
+
+        rule.onNodeWithTag("box2").assertIsNotFocused()
+        rule.onNode(hasSetTextAction()).performImeAction()
+        rule.onNodeWithTag("box2").assertIsFocused()
+    }
+
+    @Test
+    fun textField_performsDefaultBehavior_forFocusPrevious() {
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box1"))
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Previous)
+                )
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box2"))
+            }
+        }
+
+        rule.onNodeWithTag("box1").assertIsNotFocused()
+        rule.onNode(hasSetTextAction()).performImeAction()
+        rule.onNodeWithTag("box1").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun textField_performsDefaultBehavior_forDone() {
+        val testKeyboardController = TestSoftwareKeyboardController(rule)
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(
+                LocalSoftwareKeyboardController provides testKeyboardController
+            ) {
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
+                )
+            }
+        }
+
+        rule.onNode(hasSetTextAction()).performClick()
+        testKeyboardController.assertShown()
+        rule.onNode(hasSetTextAction()).performImeAction()
+        testKeyboardController.assertHidden()
+    }
+
+    @Test
+    fun textField_canOverrideDefaultBehavior() {
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box1"))
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+                    keyboardActions = KeyboardActionsAll {
+                        // don't call default action
+                    }
+                )
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box2"))
+            }
+        }
+
+        rule.onNodeWithTag("box2").assertIsNotFocused()
+        rule.onNode(hasSetTextAction()).performImeAction()
+        rule.onNode(hasSetTextAction()).assertIsFocused()
+        rule.onNodeWithTag("box2").assertIsNotFocused()
+    }
+
+    @Test
+    fun textField_canRequestDefaultBehavior() {
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box1"))
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+                    keyboardActions = KeyboardActionsAll {
+                        defaultKeyboardAction(it)
+                    }
+                )
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box2"))
+            }
+        }
+
+        rule.onNodeWithTag("box2").assertIsNotFocused()
+        rule.onNode(hasSetTextAction()).performImeAction()
+        rule.onNodeWithTag("box2").assertIsFocused()
+    }
+
+    @Test
+    fun textField_performsGo_whenReceivedImeActionIsGo() {
+        var called = false
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardActions = KeyboardActions(onGo = {
+                    called = true
+                })
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).requestFocus()
+
+        inputMethodInterceptor.withInputConnection {
+            performEditorAction(EditorInfo.IME_ACTION_GO)
+            assertThat(called).isTrue()
+        }
+    }
+
+    @Test
+    fun textField_doesNotPerformGo_whenReceivedImeActionIsNotGo() {
+        var called = false
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardActions = KeyboardActions(onGo = {
+                    called = true
+                })
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).requestFocus()
+
+        inputMethodInterceptor.withInputConnection {
+            performEditorAction(EditorInfo.IME_ACTION_SEARCH)
+            assertThat(called).isFalse()
+        }
+    }
+
+    @Test
+    fun textField_changingKeyboardActions_usesNewKeyboardActions() {
+        var lastCaller = 0
+        val actions1 = KeyboardActionsAll { lastCaller = 1 }
+        val actions2 = KeyboardActionsAll { lastCaller = 2 }
+        var keyboardActions by mutableStateOf(actions1)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
+                keyboardActions = keyboardActions
+            )
+        }
+
+        rule.onNode(hasSetTextAction()).performImeAction()
+        rule.runOnIdle { assertThat(lastCaller).isEqualTo(1) }
+
+        keyboardActions = actions2
+
+        // do not go through focus requests again
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.OnImeAction)
+        rule.runOnIdle { assertThat(lastCaller).isEqualTo(2) }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun textField_singleLinePressEnter_triggersPassedImeAction() {
+        var calledFor: ImeAction? = null
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
+                keyboardActions = KeyboardActionsAll {
+                    calledFor = it
+                },
+                lineLimits = SingleLine
+            )
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performClick()
+            performKeyInput { pressKey(Key.Enter) }
+        }
+        rule.runOnIdle { assertThat(calledFor).isEqualTo(ImeAction.Go) }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun textField_multiLinePressEnter_doesNotTriggerPassedImeAction() {
+        var calledFor: ImeAction? = null
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = TextFieldState(),
+                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
+                keyboardActions = KeyboardActionsAll {
+                    calledFor = it
+                },
+                lineLimits = MultiLine(maxHeightInLines = 1)
+            )
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performClick()
+            performKeyInput { pressKey(Key.Enter) }
+        }
+        rule.runOnIdle { assertThat(calledFor).isNull() }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun textField_singleLinePressEnter_triggersDefaultBehavior() {
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box1"))
+                BasicTextField2(
+                    state = TextFieldState(),
+                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+                    lineLimits = SingleLine
+                )
+                Box(
+                    Modifier
+                        .size(1.dp)
+                        .focusable()
+                        .testTag("box2"))
+            }
+        }
+
+        rule.onNodeWithTag("box2").assertIsNotFocused()
+        with(rule.onNode(hasSetTextAction())) {
+            performClick()
+            performKeyInput { pressKey(Key.Enter) }
+        }
+        rule.onNodeWithTag("box2").assertIsFocused()
+    }
+}
+
+private fun KeyboardActionsAll(
+    onAny: KeyboardActionScope.(ImeAction) -> Unit
+): KeyboardActions = KeyboardActions(
+    onDone = { onAny(ImeAction.Done) },
+    onGo = { onAny(ImeAction.Go) },
+    onNext = { onAny(ImeAction.Next) },
+    onPrevious = { onAny(ImeAction.Previous) },
+    onSearch = { onAny(ImeAction.Search) },
+    onSend = { onAny(ImeAction.Send) }
+)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
new file mode 100644
index 0000000..edcb3d1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldOutputTransformationGesturesIntegrationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicTextField2"
+
+    @Test
+    fun clickingAroundReplacement_movesCursorToEdgesOfReplacement() {
+        val text = TextFieldState("zaz", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = text,
+                modifier = Modifier.testTag(Tag),
+                textStyle = TextStyle(
+                    textAlign = TextAlign.Center,
+                    fontFamily = TEST_FONT_FAMILY,
+                    fontSize = 10.sp
+                ),
+                outputTransformation = {
+                    replace(1, 2, "bbbb") // "zbbbbz"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).assertTextEquals("zbbbbz")
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // Click 1 pixel to the right of center.
+            click(center + Offset(1f, 0f))
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
+        }
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // Add a delay to avoid triggering double-click.
+            advanceEventTime(1000)
+            // Click 1 pixel to the left of center.
+            click(center + Offset(-1f, 0f))
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        }
+    }
+
+    @Test
+    fun clickingAroundReplacement_movesCursorToEdgesOfReplacement_withLineBreak() {
+        // The top right is geometrically closer to the start of the replacement, even though the
+        // index is closer to the end.
+        // The bottom left is closer to the end.
+        // +-------->---------+
+        // | zzzzzzzzbbbb     |
+        // | bbz              |
+        // +---<--------------+
+
+        val text = TextFieldState("zzzzzzzzaz", initialSelectionInChars = TextRange(0))
+        val replacement = "bbbb\nbb"
+        val indexOfA = text.text.indexOf('a')
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = text,
+                modifier = Modifier.testTag(Tag),
+                textStyle = TextStyle(
+                    textAlign = TextAlign.Left,
+                    fontFamily = TEST_FONT_FAMILY,
+                    fontSize = 10.sp
+                ),
+                outputTransformation = {
+                    replace(indexOfA, indexOfA + 1, replacement)
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).assertTextEquals("zzzzzzzz${replacement}z")
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            click(topRight)
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars).isEqualTo(TextRange(indexOfA))
+        }
+        assertCursor(indexOfA)
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // Add a delay to avoid triggering double-click.
+            advanceEventTime(1000)
+            click(bottomLeft)
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars)
+                .isEqualTo(TextRange(indexOfA + 1))
+        }
+        assertCursor(indexOfA + replacement.length)
+    }
+
+    @Test
+    fun clickingAroundReplacement_movesCursorToEdgesOfInsertion() {
+        val text = TextFieldState("zz", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = text,
+                modifier = Modifier.testTag(Tag),
+                textStyle = TextStyle(
+                    textAlign = TextAlign.Center,
+                    fontFamily = TEST_FONT_FAMILY,
+                    fontSize = 10.sp
+                ),
+                outputTransformation = {
+                    insert(1, "bbbb") // "zbbbbz"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).assertTextEquals("zbbbbz")
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // Click 1 pixel to the right of center.
+            click(center + Offset(1f, 0f))
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        }
+        assertCursor(5)
+
+        rule.onNodeWithTag(Tag).performTouchInput {
+            // Add a delay to avoid triggering double-click.
+            advanceEventTime(1000)
+            // Click 1 pixel to the left of center.
+            click(center + Offset(-1f, 0f))
+        }
+        rule.runOnIdle {
+            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        }
+        assertCursor(1)
+    }
+
+    private fun assertCursor(offset: Int) {
+        val node = rule.onNodeWithTag(Tag).fetchSemanticsNode()
+        Truth.assertWithMessage("Selection via semantics")
+            .that(node.config[SemanticsProperties.TextSelectionRange])
+            .isEqualTo(TextRange(offset))
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
new file mode 100644
index 0000000..02a9b08
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldOutputTransformationHardwareKeysIntegrationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "BasicTextField2"
+
+    @Test
+    fun replacement_visualText() {
+        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    replace(1, 3, "efg") // "aefgd"
+                }
+            )
+        }
+
+        assertVisualText("aefgd")
+    }
+
+    @Test
+    fun replacement_cursorMovement_leftToRight_byCharacter_stateOffsets() {
+        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    replace(1, 3, "efg") // "aefgd"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(3))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun replacement_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
+        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    replace(1, 3, "efg") // "aefgd"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(4))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(3))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun replacement_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
+        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    replace(1, 3, "efg") // "aefgd"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertCursor(0)
+        pressKey(Key.DirectionRight)
+        assertCursor(1)
+        pressKey(Key.DirectionRight)
+        assertCursor(4)
+        pressKey(Key.DirectionRight)
+        assertCursor(5)
+    }
+
+    @Test
+    fun replacement_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
+        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    replace(1, 3, "efg") // "aefgd"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertCursor(5)
+        pressKey(Key.DirectionLeft)
+        assertCursor(4)
+        pressKey(Key.DirectionLeft)
+        assertCursor(1)
+        pressKey(Key.DirectionLeft)
+        assertCursor(0)
+    }
+
+    @Test
+    fun insert_visualText() {
+        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    insert(1, "efg") // "aefgb"
+                }
+            )
+        }
+
+        assertVisualText("aefgb")
+    }
+
+    @Test
+    fun insert_cursorMovement_leftToRight_byCharacter_stateOffsets() {
+        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    insert(1, "efg") // "aefgb"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionRight)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun insert_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
+        val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    insert(1, "efg") // "aefgb"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
+        pressKey(Key.DirectionLeft)
+        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun insert_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
+        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    insert(1, "efg") // "aefgb"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertCursor(0)
+        pressKey(Key.DirectionRight)
+        assertCursor(1)
+        pressKey(Key.DirectionRight)
+        assertCursor(4)
+        pressKey(Key.DirectionRight)
+        assertCursor(5)
+    }
+
+    @Test
+    fun insert_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
+        val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = text,
+                modifier = Modifier.testTag(Tag),
+                outputTransformation = {
+                    insert(1, "efg") // "aefgb"
+                }
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        assertCursor(5)
+        pressKey(Key.DirectionLeft)
+        assertCursor(4)
+        pressKey(Key.DirectionLeft)
+        assertCursor(1)
+        pressKey(Key.DirectionLeft)
+        assertCursor(0)
+    }
+
+    private fun assertVisualText(text: String) {
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .isEqualTo(text)
+    }
+
+    private fun assertCursor(offset: Int) {
+        val node = rule.onNodeWithTag(Tag).fetchSemanticsNode()
+        assertWithMessage("Selection via semantics")
+            .that(node.config[SemanticsProperties.TextSelectionRange])
+            .isEqualTo(TextRange(offset))
+    }
+
+    private fun pressKey(key: Key) {
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(key) }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
new file mode 100644
index 0000000..317ea68
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
@@ -0,0 +1,589 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.content.ClipDescription
+import android.net.Uri
+import android.os.Bundle
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import android.view.inputmethod.InputContentInfo
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.content.assertClipData
+import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.createClipData
+import androidx.compose.foundation.content.receiveContent
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.foundation.text.selection.FakeTextToolbar
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.platform.toClipEntry
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.requestFocus
+import androidx.core.view.inputmethod.EditorInfoCompat
+import androidx.core.view.inputmethod.InputConnectionCompat
+import androidx.core.view.inputmethod.InputContentInfoCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests InputConnection#commitContent calls from BasicTextField2 to receiveContent modifier.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalFoundationApi::class)
+class TextFieldReceiveContentTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val tag = "BasicTextField2"
+
+    @SdkSuppress(minSdkVersion = 25)
+    @Test
+    fun commitContentReturnsFalse_whenNoReceiveContentConfigured() {
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withInputConnection {
+            assertFalse(
+                commitContent(
+                    createInputContentInfo().unwrap() as InputContentInfo,
+                    0,
+                    null
+                )
+            )
+        }
+    }
+
+    @SdkSuppress(maxSdkVersion = 24)
+    @Test
+    fun preformPrivateCommandReturnsFalse_whenNoReceiveContentConfigured() {
+        inputMethodInterceptor.setContent {
+            BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
+            // Although we are testing `performPrivateCommand` that should return true by default
+            // in the existence of no configuration, semantically the caller is still calling
+            // commitContent which should return false by default.
+            assertFalse(
+                InputConnectionCompat.commitContent(
+                    inputConnection,
+                    editorInfo,
+                    InputContentInfoCompat(DEFAULT_CONTENT_URI, DEFAULT_CLIP_DESCRIPTION, null),
+                    0,
+                    null
+                )
+            )
+        }
+    }
+
+    @Test
+    fun singleReceiveContent_configuresEditorInfo() {
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(setOf(MediaType.Image)) { null }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withEditorInfo {
+            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
+            assertThat(contentMimeTypes).isEqualTo(arrayOf(MediaType.Image.representation))
+        }
+    }
+
+    @Test
+    fun singleReceiveContent_duplicateMediaTypes_appliedUniquely() {
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(
+                        setOf(
+                            MediaType.Image,
+                            MediaType.PlainText,
+                            MediaType.Image,
+                            MediaType.HtmlText
+                        )
+                    ) { null }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withEditorInfo {
+            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
+            assertThat(contentMimeTypes).isEqualTo(
+                arrayOf(
+                    MediaType.Image.representation,
+                    MediaType.PlainText.representation,
+                    MediaType.HtmlText.representation
+                )
+            )
+        }
+    }
+
+    @Test
+    fun multiReceiveContent_mergesMediaTypes() {
+        inputMethodInterceptor.setContent {
+            Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { null }) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image)) { null }
+                )
+            }
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withEditorInfo {
+            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
+            assertThat(contentMimeTypes).isEqualTo(
+                arrayOf(
+                    MediaType.Image.representation,
+                    MediaType.Text.representation
+                )
+            )
+        }
+    }
+
+    @Test
+    fun multiReceiveContent_mergesMediaTypes_uniquely() {
+        inputMethodInterceptor.setContent {
+            Box(modifier = Modifier.receiveContent(
+                setOf(MediaType.Text, MediaType.Image)
+            ) { null }) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image)) { null }
+                )
+            }
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withEditorInfo {
+            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
+            assertThat(contentMimeTypes).isEqualTo(
+                arrayOf(
+                    MediaType.Image.representation,
+                    MediaType.Text.representation
+                )
+            )
+        }
+    }
+
+    @Test
+    fun multiReceiveContent_mergesMediaTypes_includingAnotherTraversableNode() {
+        inputMethodInterceptor.setContent {
+            Box(modifier = Modifier
+                .receiveContent(setOf(MediaType.Text)) { null }
+                .dragAndDropTarget({ true }, object : DragAndDropTarget {
+                    override fun onDrop(event: DragAndDropEvent): Boolean {
+                        return false
+                    }
+                })
+            ) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image)) { null }
+                )
+            }
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.withEditorInfo {
+            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
+            assertThat(contentMimeTypes).isEqualTo(
+                arrayOf(
+                    MediaType.Image.representation,
+                    MediaType.Text.representation
+                )
+            )
+        }
+    }
+
+    @Test
+    fun singleReceiveContent_isCalledAfterCommitContent() {
+        var transferableContent: TransferableContent? = null
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(setOf(MediaType.All)) {
+                        transferableContent = it
+                        null
+                    }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+
+        val linkUri = Uri.parse("https://example.com")
+        val bundle = Bundle().apply { putString("key", "value") }
+        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
+            InputConnectionCompat.commitContent(
+                inputConnection,
+                editorInfo,
+                createInputContentInfo(linkUri = linkUri),
+                0,
+                bundle
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(transferableContent).isNotNull()
+            assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
+            assertThat(transferableContent?.clipMetadata?.clipDescription)
+                .isEqualTo(DEFAULT_CLIP_DESCRIPTION)
+
+            assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
+            assertThat(transferableContent?.clipEntry?.clipData?.getItemAt(0)?.uri)
+                .isEqualTo(DEFAULT_CONTENT_URI)
+
+            assertThat(transferableContent?.platformTransferableContent?.linkUri)
+                .isEqualTo(linkUri)
+            assertThat(transferableContent?.platformTransferableContent?.extras)
+                .isEqualTo(bundle)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 25) // Permissions are acquired only on SDK levels 25 or higher.
+    @Test
+    fun singleReceiveContent_permissionIsRequested() {
+        var transferableContent: TransferableContent? = null
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(setOf(MediaType.All)) {
+                        transferableContent = it
+                        null
+                    }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+
+        val inputContentInfo: InputContentInfoCompat = createInputContentInfo()
+
+        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
+            InputConnectionCompat.commitContent(
+                inputConnection,
+                editorInfo,
+                inputContentInfo,
+                InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
+                null
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(transferableContent).isNotNull()
+            assertTrue(
+                transferableContent?.platformTransferableContent
+                    ?.extras
+                    ?.containsKey("EXTRA_INPUT_CONTENT_INFO") ?: false
+            )
+        }
+    }
+
+    @Test
+    fun multiReceiveContent_delegatesRemainingItems_toParent() {
+        var childTransferableContent: TransferableContent? = null
+        var parentTransferableContent: TransferableContent? = null
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(setOf(MediaType.All)) {
+                        parentTransferableContent = it
+                        null
+                    }
+                    .receiveContent(setOf(MediaType.All)) {
+                        childTransferableContent = it
+                        it
+                    }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
+            InputConnectionCompat.commitContent(
+                inputConnection,
+                editorInfo,
+                createInputContentInfo(),
+                0,
+                null
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(childTransferableContent).isNotNull()
+            assertThat(childTransferableContent).isSameInstanceAs(parentTransferableContent)
+
+            assertThat(parentTransferableContent?.source)
+                .isEqualTo(TransferableContent.Source.Keyboard)
+            assertThat(parentTransferableContent?.clipMetadata?.clipDescription)
+                .isEqualTo(DEFAULT_CLIP_DESCRIPTION)
+
+            assertThat(parentTransferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
+            assertThat(parentTransferableContent?.clipEntry?.clipData?.getItemAt(0)?.uri)
+                .isEqualTo(DEFAULT_CONTENT_URI)
+        }
+    }
+
+    @Test
+    fun multiReceiveContent_doesNotCallParent_ifAllItemsAreProcessed() {
+        var childTransferableContent: TransferableContent? = null
+        var parentTransferableContent: TransferableContent? = null
+        inputMethodInterceptor.setContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                modifier = Modifier
+                    .testTag(tag)
+                    .receiveContent(setOf(MediaType.All)) {
+                        parentTransferableContent = it
+                        null
+                    }
+                    .receiveContent(setOf(MediaType.All)) {
+                        childTransferableContent = it
+                        null
+                    }
+            )
+        }
+        rule.onNodeWithTag(tag).requestFocus()
+        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
+            InputConnectionCompat.commitContent(
+                inputConnection,
+                editorInfo,
+                createInputContentInfo(),
+                0,
+                null
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(childTransferableContent).isNotNull()
+            assertThat(parentTransferableContent).isNull()
+        }
+    }
+
+    @Test
+    fun semanticsPasteContent_delegatesToReceiveContent() {
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
+        val clipEntry = createClipData().toClipEntry()
+        clipboardManager.setClip(clipEntry)
+        lateinit var transferableContent: TransferableContent
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image)) {
+                            transferableContent = it
+                            null
+                        }
+                )
+            }
+        }
+
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertClipData(transferableContent.clipEntry.clipData)
+                .isEqualToClipData(clipEntry.clipData)
+        }
+    }
+
+    @Test
+    fun semanticsPasteContent_pastesLeftOverText() {
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
+        val clipEntry = createClipData {
+            addText("some text")
+            addUri()
+            addIntent()
+            addText("more text")
+        }.toClipEntry()
+        clipboardManager.setClip(clipEntry)
+        val state = TextFieldState()
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image, MediaType.Text)) {
+                            it.consumeEach { item ->
+                                // only consume if there's no text
+                                item.text == null
+                            }
+                        }
+                )
+            }
+        }
+
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("some text\nmore text")
+        }
+    }
+
+    @Test
+    fun semanticsPasteContent_goesFromChildToParent() {
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
+        val clipEntry = createClipData {
+            addText("a")
+            addText("b")
+            addText("c")
+            addText("d")
+        }.toClipEntry()
+        clipboardManager.setClip(clipEntry)
+
+        lateinit var transferableContent1: TransferableContent
+        lateinit var transferableContent2: TransferableContent
+        lateinit var transferableContent3: TransferableContent
+        val state = TextFieldState()
+
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Text)) {
+                            transferableContent1 = it
+                            it.consumeEach {
+                                it.text.contains("a")
+                            }
+                        }
+                        .receiveContent(setOf(MediaType.Text)) {
+                            transferableContent2 = it
+                            it.consumeEach {
+                                it.text.contains("b")
+                            }
+                        }
+                        .receiveContent(setOf(MediaType.Text)) {
+                            transferableContent3 = it
+                            it.consumeEach {
+                                it.text.contains("c")
+                            }
+                        }
+                )
+            }
+        }
+
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("d")
+            assertThat(transferableContent3.clipEntry.clipData.itemCount).isEqualTo(4)
+            assertThat(transferableContent2.clipEntry.clipData.itemCount).isEqualTo(3)
+            assertThat(transferableContent1.clipEntry.clipData.itemCount).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun toolbarPasteContent_delegatesToReceiveContent() {
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
+        val clipEntry = createClipData().toClipEntry()
+        clipboardManager.setClip(clipEntry)
+        var pasteOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOption = onPasteRequested
+            },
+            onHideMenu = {}
+        )
+        lateinit var transferableContent: TransferableContent
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalClipboardManager provides clipboardManager,
+                LocalTextToolbar provides textToolbar
+            ) {
+                BasicTextField2(
+                    state = rememberTextFieldState(),
+                    modifier = Modifier
+                        .testTag(tag)
+                        .receiveContent(setOf(MediaType.Image)) {
+                            transferableContent = it
+                            null
+                        }
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            pasteOption?.invoke()
+        }
+
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
+
+        rule.runOnIdle {
+            assertClipData(transferableContent.clipEntry.clipData)
+                .isEqualToClipData(clipEntry.clipData)
+        }
+    }
+
+    companion object {
+        private val DEFAULT_CONTENT_URI = Uri.parse("content://com.example.app/content")
+        private val DEFAULT_CLIP_DESCRIPTION = ClipDescription("image", arrayOf("image/jpeg"))
+
+        private fun createInputContentInfo(
+            contentUri: Uri = DEFAULT_CONTENT_URI,
+            clipDescription: ClipDescription = DEFAULT_CLIP_DESCRIPTION,
+            linkUri: Uri? = null
+        ) = InputContentInfoCompat(contentUri, clipDescription, linkUri)
+
+        private fun InputMethodInterceptor.onIdle(block: (EditorInfo, InputConnection) -> Unit) {
+            withInputConnection {
+                withEditorInfo {
+                    block(this@withEditorInfo, this@withInputConnection)
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
new file mode 100644
index 0000000..106eb45
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
@@ -0,0 +1,820 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.Build
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsNotFocused
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalFoundationApi::class)
+class TextFieldScrollTest : FocusedWindowTest {
+
+    private val TextfieldTag = "textField"
+
+    private val longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+        " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+        "fugiat nulla pariatur."
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var testScope: CoroutineScope
+
+    @Before
+    fun before() {
+        isDebugInspectorInfoEnabled = true
+    }
+
+    @After
+    fun after() {
+        isDebugInspectorInfoEnabled = false
+    }
+
+    @Test
+    fun textFieldScroll_horizontal_scrollable_withLongInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupHorizontallyScrollableContent(
+            TextFieldState(longText), scrollState, Modifier.size(width = 300.dp, height = 50.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
+            assertThat(scrollState.maxValue).isGreaterThan(0)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_vertical_scrollable_withLongInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupVerticallyScrollableContent(
+            state = TextFieldState(longText),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 50.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
+            assertThat(scrollState.maxValue).isGreaterThan(0)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_vertical_scrollable_withLongInput_whenMaxLinesProvided() {
+        val scrollState = ScrollState(0)
+
+        rule.setupVerticallyScrollableContent(
+            state = TextFieldState(longText),
+            modifier = Modifier.width(100.dp),
+            scrollState = scrollState,
+            maxLines = 3
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
+            assertThat(scrollState.maxValue).isGreaterThan(0)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_horizontal_notScrollable_withShortInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupHorizontallyScrollableContent(
+            state = TextFieldState("text"),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 50.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.maxValue).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_vertical_notScrollable_withShortInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupVerticallyScrollableContent(
+            state = TextFieldState("text"),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 100.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.maxValue).isEqualTo(0)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_singleLine_scrolledAndClipped() {
+        val parentSize = 200
+        val textFieldSize = 50
+        val tag = "OuterBox"
+
+        with(rule.density) {
+            rule.setContent {
+                Box(
+                    Modifier
+                        .size(parentSize.toDp())
+                        .background(color = Color.White)
+                        .testTag(tag)
+                ) {
+                    ScrollableContent(
+                        state = TextFieldState(longText),
+                        modifier = Modifier.size(textFieldSize.toDp()),
+                        scrollState = rememberScrollState(),
+                        lineLimits = SingleLine
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(tag)
+            .captureToImage()
+            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
+                if (position.x > textFieldSize || position.y > textFieldSize) Color.White else null
+            }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textField_multiline_scrolledAndClipped() {
+        val parentSize = 200
+        val textFieldSize = 50
+        val tag = "OuterBox"
+
+        with(rule.density) {
+            rule.setContent {
+                Box(
+                    Modifier
+                        .size(parentSize.toDp())
+                        .background(color = Color.White)
+                        .testTag(tag)
+                ) {
+                    ScrollableContent(
+                        state = TextFieldState(longText),
+                        modifier = Modifier.size(textFieldSize.toDp()),
+                        scrollState = rememberScrollState(),
+                        lineLimits = MultiLine()
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(tag)
+            .captureToImage()
+            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
+                if (position.x > textFieldSize || position.y > textFieldSize) Color.White else null
+            }
+    }
+
+    @Test
+    fun textFieldScroll_horizontal_swipe_whenLongInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupHorizontallyScrollableContent(
+            state = TextFieldState(longText),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 50.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(TextfieldTag)
+            .performTouchInput { swipeLeft() }
+
+        val firstSwipePosition = rule.runOnIdle {
+            scrollState.value
+        }
+        assertThat(firstSwipePosition).isGreaterThan(0)
+
+        rule.onNodeWithTag(TextfieldTag)
+            .performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isLessThan(firstSwipePosition)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_vertical_swipe_whenLongInput() {
+        val scrollState = ScrollState(0)
+
+        rule.setupVerticallyScrollableContent(
+            state = TextFieldState(longText),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 50.dp)
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(TextfieldTag)
+            .performTouchInput { swipeUp() }
+
+        val firstSwipePosition = rule.runOnIdle {
+            scrollState.value
+        }
+        assertThat(firstSwipePosition).isGreaterThan(0)
+
+        rule.onNodeWithTag(TextfieldTag)
+            .performTouchInput { swipeDown() }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isLessThan(firstSwipePosition)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_restoresScrollerPosition() {
+        val restorationTester = StateRestorationTester(rule)
+        var scrollState: ScrollState? = null
+
+        restorationTester.setContent {
+            scrollState = rememberScrollState()
+            ScrollableContent(
+                state = TextFieldState(longText),
+                modifier = Modifier.size(width = 300.dp, height = 50.dp),
+                scrollState = scrollState!!,
+                lineLimits = SingleLine
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag)
+            .performTouchInput { swipeLeft() }
+
+        val swipePosition = rule.runOnIdle { scrollState!!.value }
+        assertThat(swipePosition).isGreaterThan(0)
+
+        rule.runOnIdle {
+            scrollState = ScrollState(0)
+            assertThat(scrollState!!.value).isEqualTo(0)
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        rule.runOnIdle {
+            assertThat(scrollState!!.value).isEqualTo(swipePosition)
+        }
+    }
+
+    @Test
+    fun textFieldScrollStateChange_shouldResetTheScroll() {
+        val scrollState1 = ScrollState(0)
+        val scrollState2 = ScrollState(0)
+
+        var stateToggle by mutableStateOf(true)
+
+        rule.setContent {
+            ScrollableContent(
+                state = TextFieldState(longText),
+                scrollState = if (stateToggle) scrollState1 else scrollState2,
+                modifier = Modifier.size(width = 300.dp, height = 50.dp),
+                lineLimits = SingleLine
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollState1.maxValue).isLessThan(Int.MAX_VALUE)
+            assertThat(scrollState1.maxValue).isGreaterThan(0)
+
+            assertThat(scrollState2.maxValue).isEqualTo(Int.MAX_VALUE) // when it's not set
+            assertThat(scrollState2.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeLeft() }
+
+        rule.runOnIdle {
+            assertThat(scrollState1.value).isGreaterThan(0)
+        }
+
+        stateToggle = false
+
+        rule.runOnIdle {
+            assertThat(scrollState2.maxValue).isLessThan(Int.MAX_VALUE)
+            assertThat(scrollState2.maxValue).isGreaterThan(0)
+
+            assertThat(scrollState2.value).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textFieldDoesNotFollowCursor_whenNotFocused() {
+        val state = TextFieldState(longText)
+        val scrollState = ScrollState(0)
+        rule.setContent {
+            ScrollableContent(
+                state = state,
+                scrollState = scrollState,
+                modifier = Modifier.size(width = 300.dp, height = 50.dp),
+                lineLimits = SingleLine
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag).assertIsNotFocused()
+
+        // move cursor to the end
+        state.edit {
+            placeCursorAtEnd()
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun textFieldFollowsCursor_whenFocused() {
+        val state = TextFieldState(longText, TextRange(0))
+        val scrollState = ScrollState(0)
+        rule.setTextFieldTestContent {
+            ScrollableContent(
+                state = state,
+                scrollState = scrollState,
+                modifier = Modifier.size(width = 300.dp, height = 50.dp),
+                lineLimits = SingleLine
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag).requestFocus()
+
+        rule.runOnIdle {
+            // move cursor to the end
+            state.edit { placeCursorAtEnd() }
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
+        }
+    }
+
+    @Test
+    fun textFieldDoesNotFollowCursor_whenScrollStateChanges_butCursorRemainsTheSame() {
+        val state = TextFieldState(longText, initialSelectionInChars = TextRange(5))
+        val scrollState = ScrollState(0)
+        rule.setContent {
+            ScrollableContent(
+                state = state,
+                scrollState = scrollState,
+                modifier = Modifier.size(width = 300.dp, height = 50.dp),
+                lineLimits = SingleLine
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag).requestFocus()
+        rule.waitForIdle()
+
+        runBlockingAndAwaitIdle { scrollState.scrollTo(scrollState.maxValue) }
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+        }
+    }
+
+    @Test
+    fun textFieldRtl_horizontalScroll_isReversed() {
+        val scrollState = ScrollState(0)
+
+        rule.setupHorizontallyScrollableContent(
+            state = TextFieldState(longText),
+            scrollState = scrollState,
+            modifier = Modifier.size(width = 300.dp, height = 50.dp),
+            isRtl = true
+        )
+
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeLeft() }
+
+        // swiping left at initial position should be no-op in RTL layout
+        val firstSwipePosition = rule.runOnIdle { scrollState.value }
+        assertThat(firstSwipePosition).isEqualTo(0)
+
+        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isGreaterThan(firstSwipePosition)
+        }
+    }
+
+    @Test
+    fun textFieldScroll_testNestedScrolling() = runBlocking {
+        val size = 300.dp
+        val text = """
+            First Line
+            Second Line
+            Third Line
+            Fourth Line
+        """.trimIndent()
+
+        val textFieldScrollState = ScrollState(0)
+        val columnScrollState = ScrollState(0)
+        var touchSlop = 0f
+        val height = 60.dp
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Column(
+                Modifier
+                    .size(size)
+                    .verticalScroll(columnScrollState)
+            ) {
+                ScrollableContent(
+                    state = TextFieldState(text),
+                    modifier = Modifier.size(size, height),
+                    scrollState = textFieldScrollState,
+                    lineLimits = MultiLine()
+                )
+                Box(Modifier.size(size))
+                Box(Modifier.size(size))
+            }
+        }
+
+        assertThat(textFieldScrollState.value).isEqualTo(0)
+        assertThat(textFieldScrollState.maxValue).isGreaterThan(0)
+        assertThat(columnScrollState.value).isEqualTo(0)
+
+        with(rule.density) {
+            val x = 10.dp.toPx()
+            val desiredY = textFieldScrollState.maxValue + 10.dp.roundToPx()
+            val nearEdge = (height - 1.dp)
+            // not to exceed size
+            val slopStartY = minOf(desiredY + touchSlop, nearEdge.toPx())
+            val slopStart = Offset(x, slopStartY)
+            val end = Offset(x, 0f)
+            rule.onNodeWithTag(TextfieldTag)
+                .performTouchInput {
+                    swipe(slopStart, end)
+                }
+        }
+
+        assertThat(textFieldScrollState.value).isGreaterThan(0)
+        assertThat(textFieldScrollState.value).isEqualTo(textFieldScrollState.maxValue)
+        assertThat(columnScrollState.value).isGreaterThan(0)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inHorizontallyScrollableField_whenAtStart() {
+        val state = TextFieldState("baaaaaaaaaa")
+        val scrollState = ScrollState(Int.MAX_VALUE)
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            BasicTextField2(
+                state,
+                scrollState = scrollState,
+                lineLimits = SingleLine,
+                modifier = Modifier
+                    // Force the field to be scrollable.
+                    // Must be at least as wide as the cursor rectangle for the assertions to work.
+                    .requiredWidth(10.dp)
+                    .testTag("field")
+            )
+        }
+        rule.onNodeWithTag("field").requestFocus()
+        rule.runOnIdle {
+            // Start the cursor at index 1 then backspace to move it to zero. This makes the
+            // assertion easier to write since we don't have to know the width of the glyph to
+            // calculate the expected scroll offset. We have to do this after requesting focus since
+            // the cursor will move change when focus is gained.
+            state.edit {
+                placeCursorBeforeCharAt(1)
+            }
+        }
+        rule.runOnIdle {
+            coroutineScope.launch {
+                scrollState.scrollTo(scrollState.maxValue)
+            }
+        }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
+        }
+        rule.onNodeWithTag("field").assertTextEquals("baaaaaaaaaa")
+
+        rule.onNodeWithTag("field").performKeyInput {
+            pressKey(Key.Backspace)
+        }
+
+        rule.onNodeWithTag("field").assertTextEquals("aaaaaaaaaa")
+        rule.waitUntil(
+            "scrollState.value (${scrollState.value}) == 0 && " +
+                "state.text.selectionInChars (${state.text.selectionInChars}) == TextRange(0)"
+        ) {
+            scrollState.value == 0 && state.text.selectionInChars == TextRange(0)
+        }
+    }
+
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inHorizontallyScrollableField_whenAtEnd() {
+        val state = TextFieldState("aaaaaaaaaa")
+        val scrollState = ScrollState(0)
+        rule.setContent {
+            BasicTextField2(
+                state,
+                scrollState = scrollState,
+                lineLimits = SingleLine,
+                modifier = Modifier
+                    // Force the field to be scrollable.
+                    // Must be at least as wide as the cursor rectangle for the assertions to work.
+                    .requiredWidth(10.dp)
+                    .testTag("field")
+            )
+        }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag("field").performTextInput("b")
+
+        rule.waitUntil(
+            "scrollState.value (${scrollState.value}) == " +
+                "scrollState.maxValue (${scrollState.maxValue})"
+        ) {
+            scrollState.value == scrollState.maxValue
+        }
+    }
+
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenAtTop() {
+        val state = TextFieldState("a\na\na\na\n", initialSelectionInChars = TextRange(0))
+        val scrollState = ScrollState(Int.MAX_VALUE)
+        rule.setContent {
+            BasicTextField2(
+                state,
+                scrollState = scrollState,
+                lineLimits = MultiLine(maxHeightInLines = 1),
+                modifier = Modifier.testTag("field")
+            )
+        }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
+        }
+
+        rule.onNodeWithTag("field").performTextInput("b")
+
+        rule.waitUntil("scrollState.value (${scrollState.value}) == 0") {
+            scrollState.value == 0
+        }
+    }
+
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenAtBottom() {
+        val state = TextFieldState("a\na\na\na\n")
+        val scrollState = ScrollState(0)
+        rule.setContent {
+            BasicTextField2(
+                state,
+                scrollState = scrollState,
+                lineLimits = MultiLine(maxHeightInLines = 1),
+                modifier = Modifier.testTag("field")
+            )
+        }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag("field").performTextInput("b")
+
+        rule.waitUntil(
+            "scrollState.value (${scrollState.value}) == " +
+                "scrollState.maxValue (${scrollState.maxValue})"
+        ) {
+            scrollState.value == scrollState.maxValue
+        }
+    }
+
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenMovesBelowViewport() {
+        val state = TextFieldState("a\na\na\na\n")
+        val scrollState = ScrollState(Int.MAX_VALUE)
+        rule.setContent {
+            BasicTextField2(
+                state,
+                scrollState = scrollState,
+                lineLimits = MultiLine(maxHeightInLines = 1),
+                modifier = Modifier.testTag("field")
+            )
+        }
+        rule.onNodeWithTag("field").requestFocus()
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
+        }
+
+        // At this point the field is scrolled all the way to the bottom, but then we enter a
+        // newline, which will push the cursor below the bottom of the field. It should scroll up
+        // to stay in view.
+        rule.onNodeWithTag("field").performTextInput("\n")
+
+        rule.waitUntil(
+            "scrollState.value (${scrollState.value}) == " +
+                "scrollState.maxValue (${scrollState.maxValue})"
+        ) {
+            scrollState.value == scrollState.maxValue
+        }
+    }
+
+    @Test
+    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableContainer_whenFieldExpands() {
+        // Start as a single line, then enter '\n' to grow to 2 lines.
+        val state = TextFieldState("a")
+        val scrollState = ScrollState(0)
+        var containerHeight by mutableStateOf(0.dp)
+        rule.setContent {
+            Box(
+                Modifier
+                    .requiredHeight(containerHeight)
+                    .fillMaxWidth()
+                    .border(1.dp, Color.Red)
+                    .verticalScroll(scrollState)
+            ) {
+                BasicTextField2(
+                    state,
+                    // The field should never scroll internally.
+                    lineLimits = MultiLine(maxHeightInLines = Int.MAX_VALUE),
+                    modifier = Modifier
+                        .testTag("field")
+                        .border(1.dp, Color.Blue)
+                )
+            }
+        }
+        rule.onNodeWithTag("field").requestFocus()
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+
+        // Make the container height equal to the size of the single-line text field.
+        with(rule.density) {
+            containerHeight = rule.onNodeWithTag("field").fetchSemanticsNode().size.height.toDp()
+        }
+
+        // Enter a newline, which will move the cursor to line 2 and grow the field to be 2 lines
+        // tall. The second line will initially be hidden by the container, but should be scrolled
+        // back into view.
+        rule.onNodeWithTag("field").performTextInput("\n")
+
+        rule.waitUntil(
+            "maxValue (${scrollState.maxValue} > 0 && " +
+                "scrollState.value (${scrollState.value}) == maxValue",
+            timeoutMillis = 10_000
+        ) {
+            val maxValue = scrollState.maxValue
+            maxValue > 0 && scrollState.value == maxValue
+        }
+    }
+
+    private fun ComposeContentTestRule.setupHorizontallyScrollableContent(
+        state: TextFieldState,
+        scrollState: ScrollState,
+        modifier: Modifier = Modifier,
+        isRtl: Boolean = false
+    ) {
+        setContent {
+            val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
+            CompositionLocalProvider(LocalLayoutDirection provides direction) {
+                ScrollableContent(
+                    state = state,
+                    scrollState = scrollState,
+                    modifier = modifier,
+                    lineLimits = SingleLine
+                )
+            }
+        }
+    }
+
+    private fun ComposeContentTestRule.setupVerticallyScrollableContent(
+        state: TextFieldState,
+        scrollState: ScrollState,
+        modifier: Modifier = Modifier,
+        maxLines: Int = Int.MAX_VALUE,
+        isRtl: Boolean = false
+    ) {
+        setContent {
+            val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
+            CompositionLocalProvider(LocalLayoutDirection provides direction) {
+                ScrollableContent(
+                    state = state,
+                    scrollState = scrollState,
+                    modifier = modifier,
+                    lineLimits = MultiLine(maxHeightInLines = maxLines)
+                )
+            }
+        }
+    }
+
+    @Composable
+    private fun ScrollableContent(
+        modifier: Modifier,
+        state: TextFieldState,
+        scrollState: ScrollState,
+        lineLimits: TextFieldLineLimits
+    ) {
+        testScope = rememberCoroutineScope()
+        BasicTextField2(
+            state = state,
+            scrollState = scrollState,
+            lineLimits = lineLimits,
+            modifier = modifier.testTag(TextfieldTag)
+        )
+    }
+
+    private fun runBlockingAndAwaitIdle(block: suspend CoroutineScope.() -> Unit) {
+        val job = testScope.launch(block = block)
+        rule.waitForIdle()
+        runBlocking {
+            job.join()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt
new file mode 100644
index 0000000..fd3faee
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.IntSize
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldSingleLineHeightTest : FocusedWindowTest {
+
+    private val TextfieldTag = "textField"
+
+    private val defaultText = "TEXT"
+
+    // Arabic and Thai characters combined for super tall script
+    private val tallText = "\u0627\u0644\u0646\u0635\u0E17\u0E35\u0E48"
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun singleLineTextField_fromEmptyToTallText_updatesHeight() {
+        val state = TextFieldState("")
+        var reportedSize: IntSize = IntSize.Zero
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier.onSizeChanged {
+                    reportedSize = it
+                }
+            )
+        }
+
+        rule.waitForIdle()
+        val emptyHeight = reportedSize.height
+
+        state.setTextAndPlaceCursorAtEnd(tallText)
+
+        rule.waitForIdle()
+        val tallHeight = reportedSize.height
+
+        assertThat(emptyHeight).isLessThan(tallHeight)
+    }
+
+    @Test
+    fun singleLineTextField_fromLatinToTallText_updatesHeight() {
+        val state = TextFieldState(defaultText)
+        var reportedSize: IntSize = IntSize.Zero
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier.onSizeChanged {
+                    reportedSize = it
+                }
+            )
+        }
+
+        rule.waitForIdle()
+        val latinHeight = reportedSize.height
+
+        state.setTextAndPlaceCursorAtEnd(tallText)
+
+        rule.waitForIdle()
+        val tallHeight = reportedSize.height
+
+        assertThat(latinHeight).isLessThan(tallHeight)
+    }
+
+    @Test
+    fun singleLineTextField_withTallText_showsCursorHandle_whenClicked() {
+        val state = TextFieldState(tallText)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier.testTag(TextfieldTag)
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag).performClick()
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+    }
+
+    @Test
+    fun multiLineTextField_withTallText_showsCursorHandle_whenClicked() {
+        val state = TextFieldState(tallText)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                lineLimits = TextFieldLineLimits.MultiLine(1, 1),
+                modifier = Modifier.testTag(TextfieldTag)
+            )
+        }
+
+        rule.onNodeWithTag(TextfieldTag).performClick()
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt
new file mode 100644
index 0000000..6b27d9f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.text.InputType
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.PlatformTextInputModifierNode
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.platform.establishTextInputSession
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import kotlin.test.assertFalse
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AndroidTextInputSessionTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var coroutineScope: CoroutineScope
+    private lateinit var hostView: View
+    private lateinit var textInputNode: PlatformTextInputModifierNode
+
+    @Before
+    fun setup() {
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            hostView = LocalView.current
+            Box(
+                modifier = Modifier
+                    .size(1.dp)
+                    .testTag("tag")
+                    .then(TestTextElement())
+                    .focusable()
+            )
+        }
+        rule.onNodeWithTag("tag").requestFocus()
+        rule.waitForIdle()
+    }
+
+    @Test
+    fun createInputConnection_modifiesEditorInfo() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0, 5))
+        launchInputSessionWithDefaultsForTest(state)
+        val editorInfo = EditorInfo()
+
+        rule.runOnUiThread {
+            hostView.onCreateInputConnection(editorInfo)
+        }
+
+        Truth.assertThat(editorInfo.initialSelStart).isEqualTo(0)
+        Truth.assertThat(editorInfo.initialSelEnd).isEqualTo(5)
+        Truth.assertThat(editorInfo.inputType).isEqualTo(
+            InputType.TYPE_CLASS_TEXT or
+                InputType.TYPE_TEXT_FLAG_MULTI_LINE or
+                InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+        )
+        Truth.assertThat(editorInfo.imeOptions).isEqualTo(
+            EditorInfo.IME_FLAG_NO_FULLSCREEN or
+                EditorInfo.IME_FLAG_NO_ENTER_ACTION
+        )
+    }
+
+    @Test
+    fun inputConnection_sendsUpdates_toActiveSession() {
+        val state1 = TextFieldState()
+        val state2 = TextFieldState()
+        launchInputSessionWithDefaultsForTest(state1)
+
+        rule.runOnIdle {
+            hostView.onCreateInputConnection(EditorInfo())
+                .commitText("hello", 1)
+
+            Truth.assertThat(state1.text.toString()).isEqualTo("hello")
+            Truth.assertThat(state2.text.toString()).isEqualTo("")
+        }
+
+        launchInputSessionWithDefaultsForTest(state2)
+
+        rule.runOnIdle {
+            hostView.onCreateInputConnection(EditorInfo())
+                .commitText("world", 1)
+
+            Truth.assertThat(state1.text.toString()).isEqualTo("hello")
+            Truth.assertThat(state2.text.toString()).isEqualTo("world")
+        }
+    }
+
+    @Test
+    fun inputConnection_sendsEditorAction_toActiveSession() {
+        var imeActionFromOne: ImeAction? = null
+        var imeActionFromTwo: ImeAction? = null
+
+        launchInputSessionWithDefaultsForTest(
+            imeOptions = ImeOptions(imeAction = ImeAction.Done),
+            onImeAction = { imeActionFromOne = it }
+        )
+
+        rule.runOnIdle {
+            hostView.onCreateInputConnection(EditorInfo())
+                .performEditorAction(EditorInfo.IME_ACTION_DONE)
+
+            Truth.assertThat(imeActionFromOne).isEqualTo(ImeAction.Done)
+            Truth.assertThat(imeActionFromTwo).isNull()
+        }
+
+        launchInputSessionWithDefaultsForTest(
+            imeOptions = ImeOptions(imeAction = ImeAction.Go),
+            onImeAction = { imeActionFromTwo = it }
+        )
+
+        rule.runOnIdle {
+            hostView.onCreateInputConnection(EditorInfo())
+                .performEditorAction(EditorInfo.IME_ACTION_GO)
+
+            Truth.assertThat(imeActionFromOne).isEqualTo(ImeAction.Done)
+            Truth.assertThat(imeActionFromTwo).isEqualTo(ImeAction.Go)
+        }
+    }
+
+    @Test
+    fun createInputConnection_updatesEditorInfo() {
+        launchInputSessionWithDefaultsForTest(
+            imeOptions = ImeOptions(
+                singleLine = true,
+                keyboardType = KeyboardType.Email,
+                autoCorrect = false,
+                imeAction = ImeAction.Search,
+                capitalization = KeyboardCapitalization.Words
+            )
+        )
+        val editorInfo = EditorInfo()
+
+        rule.runOnIdle {
+            hostView.onCreateInputConnection(editorInfo)
+        }
+
+        Truth.assertThat(editorInfo.inputType).isEqualTo(
+            InputType.TYPE_CLASS_TEXT or
+                InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or
+                InputType.TYPE_TEXT_FLAG_CAP_WORDS
+        )
+        Truth.assertThat(editorInfo.imeOptions).isEqualTo(
+            EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN
+        )
+    }
+
+    @Test
+    fun debugMode_isDisabled() {
+        // run this in presubmit to check that we are not accidentally enabling logs on prod
+        assertFalse(
+            TIA_DEBUG,
+            "Oops, looks like you accidentally enabled logging. Don't worry, we've all " +
+                "been there. Just remember to turn it off before you deploy your code."
+        )
+    }
+
+    private fun launchInputSessionWithDefaultsForTest(
+        state: TextFieldState = TextFieldState(),
+        imeOptions: ImeOptions = ImeOptions.Default,
+        onImeAction: (ImeAction) -> Unit = {}
+    ) {
+        coroutineScope.launch {
+            textInputNode.establishTextInputSession {
+                inputSessionWithDefaultsForTest(
+                    state,
+                    imeOptions,
+                    onImeAction
+                )
+            }
+        }
+    }
+
+    private suspend fun PlatformTextInputSession.inputSessionWithDefaultsForTest(
+        state: TextFieldState = TextFieldState(),
+        imeOptions: ImeOptions = ImeOptions.Default,
+        onImeAction: (ImeAction) -> Unit = {},
+        receiveContentConfiguration: ReceiveContentConfiguration? = null
+    ): Nothing = platformSpecificTextInputSession(
+        state = TransformedTextFieldState(
+            textFieldState = state,
+            inputTransformation = null,
+            codepointTransformation = null
+        ),
+        layoutState = TextLayoutState(),
+        imeOptions = imeOptions,
+        receiveContentConfiguration = receiveContentConfiguration,
+        onImeAction = onImeAction,
+    )
+
+    private inner class TestTextElement : ModifierNodeElement<TestTextNode>() {
+        override fun create(): TestTextNode = TestTextNode()
+        override fun update(node: TestTextNode) {}
+        override fun hashCode(): Int = 0
+        override fun equals(other: Any?): Boolean = other is TestTextElement
+    }
+
+    private inner class TestTextNode : Modifier.Node(), PlatformTextInputModifierNode {
+        override fun onAttach() {
+            textInputNode = this
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BackspaceCommandTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BackspaceCommandTest.kt
new file mode 100644
index 0000000..5f64d12
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BackspaceCommandTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BackspaceCommandTest {
+
+    // Test sample surrogate pair characters.
+    private val SP1 = "\uD83D\uDE00" // U+1F600: GRINNING FACE
+    private val SP2 = "\uD83D\uDE01" // U+1F601: GRINNING FACE WITH SMILING EYES
+    private val SP3 = "\uD83D\uDE02" // U+1F602: FACE WITH TEARS OF JOY
+    private val SP4 = "\uD83D\uDE03" // U+1F603: SMILING FACE WITH OPEN MOUTH
+    private val SP5 = "\uD83D\uDE04" // U+1F604: SMILING FACE WITH OPEN MOUTH AND SMILING EYES
+
+    // Family ZWJ Emoji: U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
+    private val ZWJ_EMOJI = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
+
+    @Test
+    fun test_delete() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("BCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_from_offset0() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_with_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(2, 3))
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABDE")
+        Truth.assertThat(eb.cursor).isEqualTo(2)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_with_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+        eb.setComposition(2, 3)
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABDE")
+        Truth.assertThat(eb.cursor).isEqualTo(1)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_surrogate_pair() {
+        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("$SP2$SP3$SP4$SP5")
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_with_selection_surrogate_pair() {
+        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(4, 6))
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("$SP1$SP2$SP4$SP5")
+        Truth.assertThat(eb.cursor).isEqualTo(4)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_with_composition_surrogate_pair() {
+        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
+        eb.setComposition(4, 6)
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo("$SP1$SP2$SP4$SP5")
+        Truth.assertThat(eb.cursor).isEqualTo(2)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 26)
+    fun test_delete_with_composition_zwj_emoji() {
+        val eb = EditingBuffer(
+            "$ZWJ_EMOJI$ZWJ_EMOJI",
+            TextRange(ZWJ_EMOJI.length)
+        )
+
+        eb.backspace()
+
+        Truth.assertThat(eb.toString()).isEqualTo(ZWJ_EMOJI)
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManagerTest.kt
new file mode 100644
index 0000000..19e7e08
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ComposeInputMethodManagerTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun restartInput_startsNewInputConnection() {
+        var calledCreateInputConnection: EditorInfo? = null
+        var imm: ComposeInputMethodManager? = null
+        var view: View? = null
+        rule.setContent {
+            AndroidView(factory = { context ->
+                TestView(context) { editorInfo ->
+                    calledCreateInputConnection = editorInfo
+                    null
+                }.also {
+                    view = it
+                    imm = ComposeInputMethodManager(it)
+                }
+            })
+        }
+
+        rule.runOnUiThread {
+            view?.requestFocus()
+            imm?.restartInput()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(calledCreateInputConnection).isNotNull()
+        }
+    }
+
+    @Test
+    fun everyRestartInput_createsNewInputConnection() {
+        var createInputConnectionCalled = 0
+        var imm: ComposeInputMethodManager? = null
+        var view: View? = null
+        rule.setContent {
+            AndroidView(factory = { context ->
+                TestView(context) {
+                    createInputConnectionCalled++
+                    null
+                }.also {
+                    view = it
+                    imm = ComposeInputMethodManager(it)
+                }
+            })
+        }
+
+        rule.runOnUiThread {
+            view?.requestFocus()
+            imm?.restartInput()
+        }
+
+        rule.waitUntil {
+            createInputConnectionCalled >= 1
+        }
+        rule.runOnIdle {
+            // when first time we start input, checkFocus in platform code causes
+            // onCreateInputConnection to be called twice. However, this is not guaranteed.
+            Truth.assertThat(createInputConnectionCalled).isAtLeast(1)
+        }
+
+        val previousCreateInputConnectionCalled = createInputConnectionCalled
+        rule.runOnUiThread {
+            imm?.restartInput()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(createInputConnectionCalled)
+                .isEqualTo(previousCreateInputConnectionCalled + 1)
+        }
+    }
+}
+
+private class TestView(
+    context: Context,
+    val createInputConnection: (EditorInfo?) -> InputConnection? = { null }
+) : View(context) {
+
+    init {
+        isFocusable = true
+        isFocusableInTouchMode = true
+        isEnabled = true
+    }
+
+    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection? {
+        return createInputConnection(outAttrs)
+    }
+
+    override fun isInEditMode(): Boolean {
+        return true
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilderTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilderTest.kt
index 582bbb3..8f8a561 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilderTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilderTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 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.
@@ -20,9 +20,6 @@
 import android.graphics.RectF
 import android.os.Build
 import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
-import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
-import android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.MultiParagraph
@@ -35,8 +32,6 @@
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.font.createFontFamilyResolver
 import androidx.compose.ui.text.font.toFontFamily
-import androidx.compose.ui.text.input.OffsetMapping
-import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -50,7 +45,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.fonts.R
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
 import kotlin.math.ceil
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,63 +64,90 @@
 
     @Test
     fun testSelectionDefault() {
-        val textFieldValue = TextFieldValue()
+        val text = ""
+        val selection = TextRange(0)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(text, selection, composition, getTextLayoutResult(text), matrix)
 
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(0)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(0)
+        Truth.assertThat(cursorAnchorInfo.selectionStart).isEqualTo(0)
+        Truth.assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(0)
     }
 
     @Test
     fun testSelectionCursor() {
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(2))
+        val text = "abc"
+        val selection = TextRange(2)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(text, selection, composition, getTextLayoutResult(text), matrix)
 
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(2)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
+        Truth.assertThat(cursorAnchorInfo.selectionStart).isEqualTo(2)
+        Truth.assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
     }
 
     @Test
     fun testSelectionRange() {
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1, 2))
+        val text = "abc"
+        val selection = TextRange(1, 2)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
+                    matrix
+                )
 
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(1)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
+        Truth.assertThat(cursorAnchorInfo.selectionStart).isEqualTo(1)
+        Truth.assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
     }
 
     @Test
     fun testCompositionNone() {
-        val textFieldValue = TextFieldValue(composition = null)
+        val text = ""
+        val selection = TextRange(0)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
+                    matrix
+                )
 
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
-        assertThat(cursorAnchorInfo.composingText).isNull()
+        Truth.assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
+        Truth.assertThat(cursorAnchorInfo.composingText).isNull()
     }
 
     @Test
     fun testCompositionCoveringAllString() {
         val text = "abc"
-        val textFieldValue = TextFieldValue(text, composition = TextRange(0, text.length))
+        val selection = TextRange(0)
+        val composition = TextRange(0, text.length)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
+                    matrix
+                )
 
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(0)
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
+        Truth.assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(0)
+        Truth.assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
     }
 
     @Test
@@ -133,18 +155,22 @@
         val word1 = "123 "
         val word2 = "456"
         val word3 = " 789"
-        val textFieldValue =
-            TextFieldValue(
-                word1 + word2 + word3,
-                composition = TextRange(word1.length, (word1 + word2).length)
-            )
+        val text = word1 + word2 + word3
+        val selection = TextRange(0)
+        val composition = TextRange(word1.length, (word1 + word2).length)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
+                    matrix
+                )
 
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(word1.length)
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(word2)
+        Truth.assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(word1.length)
+        Truth.assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(word2)
     }
 
     @Test
@@ -152,81 +178,106 @@
         val word1 = "123 "
         val word2 = "456"
         val word3 = " 789"
-        val textFieldValue =
-            TextFieldValue(
-                word1 + word2 + word3,
-                composition = TextRange(word1.length, (word1 + word2).length)
-            )
+        val text = word1 + word2 + word3
+        val selection = TextRange(0)
+        val composition = TextRange(word1.length, (word1 + word2).length)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    getTextLayoutResult(textFieldValue.text),
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
                     matrix,
                     includeCharacterBounds = false
                 )
 
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
-        assertThat(cursorAnchorInfo.composingText).isNull()
+        Truth.assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
+        Truth.assertThat(cursorAnchorInfo.composingText).isNull()
     }
 
     @Test
     fun testResetsBetweenExecutions() {
         val text = "abc"
-        val textFieldValue = TextFieldValue(text, composition = TextRange(0, text.length))
+        val selection = TextRange(0)
+        val composition = TextRange(0, text.length)
         val builder = CursorAnchorInfo.Builder()
 
         val cursorAnchorInfo =
-            builder.build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+            builder.build(
+                text,
+                selection,
+                composition,
+                getTextLayoutResult(text),
+                matrix
+            )
 
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(textFieldValue.composition!!.min)
+        Truth.assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
+        Truth.assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(composition.min)
 
         val cursorAnchorInfo1 =
-            builder.build(TextFieldValue("abcd"), getTextLayoutResult(textFieldValue.text), matrix)
+            builder.build(
+                "abcd",
+                selection = TextRange(0),
+                composition = null,
+                getTextLayoutResult(text),
+                matrix
+            )
 
-        assertThat(cursorAnchorInfo1.composingText).isNull()
-        assertThat(cursorAnchorInfo1.composingTextStart).isEqualTo(-1)
+        Truth.assertThat(cursorAnchorInfo1.composingText).isNull()
+        Truth.assertThat(cursorAnchorInfo1.composingTextStart).isEqualTo(-1)
     }
 
     @Test
     fun testInsertionMarkerCursor() {
         val fontSize = 10.sp
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "abc"
+        val selection = TextRange(1)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
 
         val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
 
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION)
     }
 
     @Test
     fun testInsertionMarkerSelectionIsSameWithCursor() {
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1, 2))
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text)
+        val text = "abc"
+        val selection = TextRange(1, 2)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text)
         val builder = CursorAnchorInfo.Builder()
 
-        val cursorAnchorInfo1 = builder.build(textFieldValue, textLayoutResult, matrix)
+        val cursorAnchorInfo1 =
+            builder.build(text, selection, composition, textLayoutResult, matrix)
 
         val cursorAnchorInfo2 =
-            builder.build(textFieldValue.copy(selection = TextRange(1)), textLayoutResult, matrix)
+            builder.build(
+                text,
+                selection = TextRange(1),
+                composition,
+                textLayoutResult,
+                matrix
+            )
 
-        assertThat(cursorAnchorInfo1.insertionMarkerHorizontal)
+        Truth.assertThat(cursorAnchorInfo1.insertionMarkerHorizontal)
             .isEqualTo(cursorAnchorInfo2.insertionMarkerHorizontal)
-        assertThat(cursorAnchorInfo1.insertionMarkerTop)
+        Truth.assertThat(cursorAnchorInfo1.insertionMarkerTop)
             .isEqualTo(cursorAnchorInfo2.insertionMarkerTop)
-        assertThat(cursorAnchorInfo1.insertionMarkerBottom)
+        Truth.assertThat(cursorAnchorInfo1.insertionMarkerBottom)
             .isEqualTo(cursorAnchorInfo2.insertionMarkerBottom)
-        assertThat(cursorAnchorInfo1.insertionMarkerBaseline)
+        Truth.assertThat(cursorAnchorInfo1.insertionMarkerBaseline)
             .isEqualTo(cursorAnchorInfo2.insertionMarkerBaseline)
-        assertThat(cursorAnchorInfo1.insertionMarkerFlags)
+        Truth.assertThat(cursorAnchorInfo1.insertionMarkerFlags)
             .isEqualTo(cursorAnchorInfo2.insertionMarkerFlags)
     }
 
@@ -235,20 +286,23 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
 
-        val textFieldValue = TextFieldValue("abc   ", selection = TextRange(5))
+        val text = "abc   "
+        val selection = TextRange(5)
+        val composition: TextRange? = null
         val width = 4 * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
 
         val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
 
         // The cursor position is clamped to the width of the layout.
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION)
     }
 
     @Test
@@ -256,49 +310,31 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
 
-        val textFieldValue = TextFieldValue("\u05D0\u05D1\u05D2", selection = TextRange(0))
+        val text = "\u05D0\u05D1\u05D2"
+        val selection = TextRange(0)
+        val composition: TextRange? = null
         val width = 3 * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
 
         val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
 
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags)
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
-    }
-
-    @Test
-    fun testInsertionMarkerWithVisualTransformation() {
-        val fontSize = 10.sp
-        val textFieldValue = TextFieldValue("abcde", selection = TextRange(2))
-        val offsetMapping = object : OffsetMapping {
-            override fun originalToTransformed(offset: Int) = if (offset < 2) offset else offset + 3
-            override fun transformedToOriginal(offset: Int) = throw NotImplementedError()
-        }
-        val textLayoutResult = getTextLayoutResult("ab---cde", fontSize = fontSize)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(textFieldValue, textLayoutResult, matrix, offsetMapping = offsetMapping)
-
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(5 * fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION or CursorAnchorInfo.FLAG_IS_RTL)
     }
 
     @Test
     fun testInsertionMarkerNotVisible() {
         val fontSize = 10.sp
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "abc"
+        val selection = TextRange(1)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         // insertionMarkerHorizontal = fontSizeInPx, so the insertion marker is completely outside
         // this rectangle.
@@ -308,26 +344,30 @@
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
+                    text,
+                    selection,
+                    composition,
                     textLayoutResult,
                     matrix,
                     innerTextFieldBounds = innerTextFieldBounds,
                     decorationBoxBounds = innerTextFieldBounds
                 )
 
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION)
     }
 
     @Test
     fun testInsertionMarkerPartiallyVisible() {
         val fontSize = 10.sp
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "abc"
+        val selection = TextRange(1)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         // insertionMarkerTop = 0 and insertionMarkerBottom = fontSizeInPx, so this rectangle covers
         // the top of the insertion marker but not the bottom.
@@ -336,37 +376,50 @@
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
+                    text,
+                    selection,
+                    composition,
                     textLayoutResult,
                     matrix,
                     innerTextFieldBounds = innerTextFieldBounds,
                     decorationBoxBounds = innerTextFieldBounds
                 )
 
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags)
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(
+                CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION or
+                CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
+            )
     }
 
     @Test
     fun testInsertionMarkerNotIncludedWhenIncludeInsertionMarkerFalse() {
         val fontSize = 10.sp
-        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "abc"
+        val selection = TextRange(1)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, textLayoutResult, matrix, includeInsertionMarker = false)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    textLayoutResult,
+                    matrix,
+                    includeInsertionMarker = false
+                )
 
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(0)
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isNaN()
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerTop).isNaN()
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBottom).isNaN()
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerBaseline).isNaN()
+        Truth.assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(0)
     }
 
     @Test
@@ -374,27 +427,27 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         val text = "a bc d"
+        val selection = TextRange(0)
         // Composition is on "bc"
         val composition = TextRange(2, 4)
-        val textFieldValue = TextFieldValue(text, composition = composition)
         val width = text.length * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
 
         val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
 
         for (index in text.indices) {
             if (index in composition) {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                Truth.assertThat(cursorAnchorInfo.getCharacterBounds(index))
                     .isEqualTo(
                         RectF(index * fontSizeInPx, 0f, (index + 1) * fontSizeInPx, fontSizeInPx)
                     )
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
-                    .isEqualTo(FLAG_HAS_VISIBLE_REGION)
+                Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
+                    .isEqualTo(CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION)
             } else {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+                Truth.assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+                Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
             }
         }
     }
@@ -404,19 +457,19 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         val text = "\u05D0 \u05D1\u05D2 \u05D3"
+        val selection = TextRange(0)
         // Composition is on "\u05D1\u05D2"
         val composition = TextRange(2, 4)
-        val textFieldValue = TextFieldValue(text, composition = composition)
         val width = text.length * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
 
         val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
 
         for (index in text.indices) {
             if (index in composition) {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                Truth.assertThat(cursorAnchorInfo.getCharacterBounds(index))
                     .isEqualTo(
                         RectF(
                             width - ((index + 1) * fontSizeInPx),
@@ -425,52 +478,14 @@
                             fontSizeInPx
                         )
                     )
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
-                    .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
-            } else {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
-            }
-        }
-    }
-
-    @Test
-    fun testCharacterBoundsWithVisualTransformation() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        val text = "abcd"
-        // Composition is on "bc"
-        val composition = TextRange(2, 4)
-        val offsetMapping = object : OffsetMapping {
-            override fun originalToTransformed(offset: Int) = 2 * offset
-            override fun transformedToOriginal(offset: Int) = throw NotImplementedError()
-        }
-        val transformedText = "a-b-c-d-"
-        val textFieldValue = TextFieldValue(text, composition = composition)
-        val width = transformedText.length * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(transformedText, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(textFieldValue, textLayoutResult, matrix, offsetMapping = offsetMapping)
-
-        for (index in text.indices) {
-            if (index in composition) {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
                     .isEqualTo(
-                        RectF(
-                            2 * index * fontSizeInPx,
-                            0f,
-                            (2 * index + 1) * fontSizeInPx,
-                            fontSizeInPx
-                        )
+                        CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION or
+                            CursorAnchorInfo.FLAG_IS_RTL
                     )
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
-                    .isEqualTo(FLAG_HAS_VISIBLE_REGION)
             } else {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+                Truth.assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+                Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
             }
         }
     }
@@ -480,19 +495,20 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         val text = "a bc d"
+        val selection = TextRange(0)
         // Composition is on "bc"
         val composition = TextRange(2, 4)
-        val textFieldValue = TextFieldValue(text, composition = composition)
         val width = text.length * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
         val innerTextFieldBounds = Rect(3.5f * fontSizeInPx, 0f, 4f * fontSizeInPx, fontSizeInPx)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
+                    text,
+                    selection,
+                    composition,
                     textLayoutResult,
                     matrix,
                     innerTextFieldBounds = innerTextFieldBounds,
@@ -501,11 +517,15 @@
 
         // Character at index 2 spans horizontal range [2 * fontSizeInPx, 3 * fontSizeInPx], so it
         // is completely outside innerTextFieldBounds.
-        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2)).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2))
+            .isEqualTo(CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION)
         // Character at index 3 spans horizontal range [3 * fontSizeInPx, 4 * fontSizeInPx], so it
         // is partially inside innerTextFieldBounds.
-        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3))
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
+        Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3))
+            .isEqualTo(
+                CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION or
+                    CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
+            )
     }
 
     @Test
@@ -513,62 +533,76 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         val text = "a bc d"
+        val selection = TextRange(0)
         // Composition is on "bc"
         val composition = TextRange(2, 4)
-        val textFieldValue = TextFieldValue(text, composition = composition)
         val width = text.length * fontSizeInPx
         val textLayoutResult =
-            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+            getTextLayoutResult(text, fontSize = fontSize, width = width)
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
-                .build(textFieldValue, textLayoutResult, matrix, includeCharacterBounds = false)
+                .build(
+                    text,
+                    selection,
+                    composition,
+                    textLayoutResult,
+                    matrix,
+                    includeCharacterBounds = false
+                )
 
         for (index in text.indices) {
-            assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-            assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+            Truth.assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+            Truth.assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
         }
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     fun testEditorBounds() {
-        val textFieldValue = TextFieldValue()
+        val text = ""
+        val selection = TextRange(0)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
-                    getTextLayoutResult(textFieldValue.text),
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
                     matrix,
                     innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
                     decorationBoxBounds = Rect(5f, 6f, 7f, 8f)
                 )
 
-        assertThat(cursorAnchorInfo.editorBoundsInfo?.editorBounds).isEqualTo(RectF(5f, 6f, 7f, 8f))
-        assertThat(cursorAnchorInfo.editorBoundsInfo?.handwritingBounds)
+        Truth.assertThat(cursorAnchorInfo.editorBoundsInfo?.editorBounds)
+            .isEqualTo(RectF(5f, 6f, 7f, 8f))
+        Truth.assertThat(cursorAnchorInfo.editorBoundsInfo?.handwritingBounds)
             .isEqualTo(RectF(5f, 6f, 7f, 8f))
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     fun testEditorBoundsNotIncludedWhenIncludeEditorBoundsFalse() {
-        val textFieldValue = TextFieldValue()
+        val text = ""
+        val selection = TextRange(0)
+        val composition: TextRange? = null
 
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
-                    getTextLayoutResult(textFieldValue.text),
+                    text,
+                    selection,
+                    composition,
+                    getTextLayoutResult(text),
                     matrix,
                     innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
                     decorationBoxBounds = Rect(5f, 6f, 7f, 8f),
                     includeEditorBounds = false
                 )
 
-        assertThat(cursorAnchorInfo.editorBoundsInfo).isNull()
+        Truth.assertThat(cursorAnchorInfo.editorBoundsInfo).isNull()
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -577,8 +611,10 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         // 6 lines of text
-        val textFieldValue = TextFieldValue("a\nbb\nccc\ndddd\neeeee\nffffff")
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "a\nbb\nccc\ndddd\neeeee\nffffff"
+        val selection = TextRange(0)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
         // Lines 2, 3, 4 are visible
         val innerTextFieldBounds =
             Rect(
@@ -591,17 +627,18 @@
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
+                    text,
+                    selection,
+                    composition,
                     textLayoutResult,
                     matrix,
                     innerTextFieldBounds = innerTextFieldBounds,
                     decorationBoxBounds = innerTextFieldBounds
                 )
 
-        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(3)
+        Truth.assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(3)
         // Line 2 "ccc" has 3 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[0])
+        Truth.assertThat(cursorAnchorInfo.visibleLineBounds[0])
             .isEqualTo(
                 RectF(
                     0f,
@@ -611,7 +648,7 @@
                 )
             )
         // Line 3 "dddd" has 4 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[1])
+        Truth.assertThat(cursorAnchorInfo.visibleLineBounds[1])
             .isEqualTo(
                 RectF(
                     0f,
@@ -621,7 +658,7 @@
                 )
             )
         // Line 4 "eeeee" has 5 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[2])
+        Truth.assertThat(cursorAnchorInfo.visibleLineBounds[2])
             .isEqualTo(
                 RectF(
                     0f,
@@ -638,8 +675,10 @@
         val fontSize = 10.sp
         val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
         // 6 lines of text
-        val textFieldValue = TextFieldValue("a\nbb\nccc\ndddd\neeeee\nffffff")
-        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val text = "a\nbb\nccc\ndddd\neeeee\nffffff"
+        val selection = TextRange(0)
+        val composition: TextRange? = null
+        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
         // Lines 2, 3, 4 are visible
         val innerTextFieldBounds =
             Rect(
@@ -652,8 +691,9 @@
         val cursorAnchorInfo =
             CursorAnchorInfo.Builder()
                 .build(
-                    textFieldValue,
-                    OffsetMapping.Identity,
+                    text,
+                    selection,
+                    composition,
                     textLayoutResult,
                     matrix,
                     innerTextFieldBounds = innerTextFieldBounds,
@@ -661,7 +701,7 @@
                     includeLineBounds = false
                 )
 
-        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(0)
+        Truth.assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(0)
     }
 
     private fun getTextLayoutResult(
@@ -701,10 +741,11 @@
 }
 
 private fun CursorAnchorInfo.Builder.build(
-    textFieldValue: TextFieldValue,
+    text: CharSequence,
+    selection: TextRange,
+    composition: TextRange?,
     textLayoutResult: TextLayoutResult,
     matrix: Matrix,
-    offsetMapping: OffsetMapping = OffsetMapping.Identity,
     includeInsertionMarker: Boolean = true,
     includeCharacterBounds: Boolean = true,
     includeEditorBounds: Boolean = true,
@@ -713,8 +754,9 @@
     val innerTextFieldBounds =
         Rect(0f, 0f, textLayoutResult.size.width.toFloat(), textLayoutResult.size.height.toFloat())
     return build(
-        textFieldValue,
-        offsetMapping,
+        text,
+        selection,
+        composition,
         textLayoutResult,
         matrix,
         innerTextFieldBounds = innerTextFieldBounds,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/DragAndDropTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/DragAndDropTestUtils.kt
new file mode 100644
index 0000000..00192ec
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/DragAndDropTestUtils.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.content.ClipData
+import android.net.Uri
+import android.os.Build
+import android.os.Parcel
+import android.view.DragEvent
+import androidx.compose.foundation.content.createClipData
+import androidx.compose.ui.geometry.Offset
+
+/**
+ * Helper utilities for creating drag events.
+ *
+ * This class is a copy-paste from DragAndDrop artifact with the addition of configurable offset.
+ * Also it does not mock but uses Parcel to create a DragEvent.
+ */
+object DragAndDropTestUtils {
+    private const val SAMPLE_TEXT = "Drag Text"
+    private val SAMPLE_URI = Uri.parse("http://www.google.com")
+
+    /**
+     * Makes a stub drag event containing fake text data.
+     *
+     * @param action One of the [DragEvent] actions.
+     */
+    fun makeTextDragEvent(
+        action: Int,
+        text: String = SAMPLE_TEXT,
+        offset: Offset = Offset.Zero,
+    ): DragEvent {
+        return makeDragEvent(
+            action = action,
+            clipData = createClipData { addText(text) },
+            offset = offset
+        )
+    }
+
+    /**
+     * Makes a stub drag event containing an image mimetype and fake uri.
+     *
+     * @param action One of the [DragEvent] actions.
+     */
+    fun makeImageDragEvent(
+        action: Int,
+        item: Uri = SAMPLE_URI,
+        offset: Offset = Offset.Zero,
+    ): DragEvent {
+        return makeDragEvent(
+            action = action,
+            clipData = createClipData {
+                // We're not actually resolving Uris in these tests, so this can be anything:
+                addUri(item, mimeType = "image/png")
+            },
+            offset = offset
+        )
+    }
+
+    fun makeDragEvent(
+        action: Int,
+        clipData: ClipData,
+        offset: Offset = Offset.Zero
+    ): DragEvent {
+        val parcel = Parcel.obtain()
+
+        parcel.writeInt(action)
+        parcel.writeFloat(offset.x)
+        parcel.writeFloat(offset.y)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            // mOffset was made part of DragEvent in API 31.
+            parcel.writeFloat(0f)
+            parcel.writeFloat(0f)
+        }
+        parcel.writeInt(0) // Result
+        parcel.writeInt(1)
+        clipData.writeToParcel(parcel, 0)
+        parcel.writeInt(1)
+        clipData.description.writeToParcel(parcel, 0)
+
+        parcel.setDataPosition(0)
+        return DragEvent.CREATOR.createFromParcel(parcel)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
index 5b75e88..ed23d87 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
@@ -23,9 +23,9 @@
 import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.KeyboardCapitalization
 import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.PlatformImeOptions
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -536,20 +536,43 @@
         assertThat(info.initialSelEnd).isEqualTo(selection.end)
     }
 
+    @SdkSuppress(minSdkVersion = 25)
     @Test
-    fun test_privateImeOptions_is_set() {
+    fun if_not_null_contentMimeTypes_are_set_above25() {
+        val contentMimeTypes = arrayOf("text/*", "image/png")
         val info = EditorInfo()
-        val privateImeOptions = "testOptions"
-        info.update(
-            ImeOptions(
-                platformImeOptions = PlatformImeOptions(privateImeOptions)
-            )
-        )
+        info.update(ImeOptions.Default, contentMimeTypes)
 
-        assertThat(info.privateImeOptions).isEqualTo(privateImeOptions)
+        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
     }
 
-    private fun EditorInfo.update(imeOptions: ImeOptions) {
-        this.update("", TextRange.Zero, imeOptions)
+    @SdkSuppress(minSdkVersion = 25)
+    @Test
+    fun if_null_contentMimeTypes_are_not_set() {
+        val contentMimeTypes = arrayOf("text/*", "image/png")
+        val info = EditorInfo()
+        info.update(ImeOptions.Default, contentMimeTypes)
+
+        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
+
+        info.update(ImeOptions.Default, null)
+        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
+    }
+
+    @SdkSuppress(maxSdkVersion = 24)
+    @Test
+    fun if_not_null_contentMimeTypes_are_set_below24() {
+        val contentMimeTypes = arrayOf("text/*", "image/png")
+        val info = EditorInfo()
+        info.update(ImeOptions.Default, contentMimeTypes)
+
+        assertThat(info.extras.keySet().any { it.contains("CONTENT_MIME_TYPES") }).isTrue()
+    }
+
+    private fun EditorInfo.update(
+        imeOptions: ImeOptions,
+        contentMimeTypes: Array<String>? = null
+    ) {
+        this.update("", TextRange.Zero, imeOptions, contentMimeTypes)
     }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilderTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilderTest.kt
new file mode 100644
index 0000000..0b4fb84
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilderTest.kt
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import android.graphics.Matrix
+import android.graphics.RectF
+import android.os.Build
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
+import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
+import android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.MultiParagraph
+import androidx.compose.ui.text.TextLayoutInput
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.fonts.R
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LegacyCursorAnchorInfoBuilderTest {
+
+    private val fontFamilyMeasureFont =
+        Font(resId = R.font.sample_font, weight = FontWeight.Normal, style = FontStyle.Normal)
+            .toFontFamily()
+
+    private val context = InstrumentationRegistry.getInstrumentation().context
+    private val defaultDensity = Density(density = 1f)
+    private val matrix = Matrix()
+
+    @Test
+    fun testSelectionDefault() {
+        val textFieldValue = TextFieldValue()
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(0)
+        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun testSelectionCursor() {
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(2))
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(2)
+        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun testSelectionRange() {
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1, 2))
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(1)
+        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun testCompositionNone() {
+        val textFieldValue = TextFieldValue(composition = null)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
+        assertThat(cursorAnchorInfo.composingText).isNull()
+    }
+
+    @Test
+    fun testCompositionCoveringAllString() {
+        val text = "abc"
+        val textFieldValue = TextFieldValue(text, composition = TextRange(0, text.length))
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(0)
+        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
+    }
+
+    @Test
+    fun testCompositionCoveringPortionOfString() {
+        val word1 = "123 "
+        val word2 = "456"
+        val word3 = " 789"
+        val textFieldValue =
+            TextFieldValue(
+                word1 + word2 + word3,
+                composition = TextRange(word1.length, (word1 + word2).length)
+            )
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(word1.length)
+        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(word2)
+    }
+
+    @Test
+    fun testCompositionNotIncludedWhenIncludeCharacterBoundsFalse() {
+        val word1 = "123 "
+        val word2 = "456"
+        val word3 = " 789"
+        val textFieldValue =
+            TextFieldValue(
+                word1 + word2 + word3,
+                composition = TextRange(word1.length, (word1 + word2).length)
+            )
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    getTextLayoutResult(textFieldValue.text),
+                    matrix,
+                    includeCharacterBounds = false
+                )
+
+        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
+        assertThat(cursorAnchorInfo.composingText).isNull()
+    }
+
+    @Test
+    fun testResetsBetweenExecutions() {
+        val text = "abc"
+        val textFieldValue = TextFieldValue(text, composition = TextRange(0, text.length))
+        val builder = CursorAnchorInfo.Builder()
+
+        val cursorAnchorInfo =
+            builder.build(textFieldValue, getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
+        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(textFieldValue.composition!!.min)
+
+        val cursorAnchorInfo1 =
+            builder.build(TextFieldValue("abcd"), getTextLayoutResult(textFieldValue.text), matrix)
+
+        assertThat(cursorAnchorInfo1.composingText).isNull()
+        assertThat(cursorAnchorInfo1.composingTextStart).isEqualTo(-1)
+    }
+
+    @Test
+    fun testInsertionMarkerCursor() {
+        val fontSize = 10.sp
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+    }
+
+    @Test
+    fun testInsertionMarkerSelectionIsSameWithCursor() {
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1, 2))
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text)
+        val builder = CursorAnchorInfo.Builder()
+
+        val cursorAnchorInfo1 = builder.build(textFieldValue, textLayoutResult, matrix)
+
+        val cursorAnchorInfo2 =
+            builder.build(textFieldValue.copy(selection = TextRange(1)), textLayoutResult, matrix)
+
+        assertThat(cursorAnchorInfo1.insertionMarkerHorizontal)
+            .isEqualTo(cursorAnchorInfo2.insertionMarkerHorizontal)
+        assertThat(cursorAnchorInfo1.insertionMarkerTop)
+            .isEqualTo(cursorAnchorInfo2.insertionMarkerTop)
+        assertThat(cursorAnchorInfo1.insertionMarkerBottom)
+            .isEqualTo(cursorAnchorInfo2.insertionMarkerBottom)
+        assertThat(cursorAnchorInfo1.insertionMarkerBaseline)
+            .isEqualTo(cursorAnchorInfo2.insertionMarkerBaseline)
+        assertThat(cursorAnchorInfo1.insertionMarkerFlags)
+            .isEqualTo(cursorAnchorInfo2.insertionMarkerFlags)
+    }
+
+    @Test
+    fun testInsertionMarkerCursorClamped() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+
+        val textFieldValue = TextFieldValue("abc   ", selection = TextRange(5))
+        val width = 4 * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+
+        // The cursor position is clamped to the width of the layout.
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+    }
+
+    @Test
+    fun testInsertionMarkerRtl() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+
+        val textFieldValue = TextFieldValue("\u05D0\u05D1\u05D2", selection = TextRange(0))
+        val width = 3 * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
+    }
+
+    @Test
+    fun testInsertionMarkerWithVisualTransformation() {
+        val fontSize = 10.sp
+        val textFieldValue = TextFieldValue("abcde", selection = TextRange(2))
+        val offsetMapping = object : OffsetMapping {
+            override fun originalToTransformed(offset: Int) = if (offset < 2) offset else offset + 3
+            override fun transformedToOriginal(offset: Int) = throw NotImplementedError()
+        }
+        val textLayoutResult = getTextLayoutResult("ab---cde", fontSize = fontSize)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, textLayoutResult, matrix, offsetMapping = offsetMapping)
+
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(5 * fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
+    }
+
+    @Test
+    fun testInsertionMarkerNotVisible() {
+        val fontSize = 10.sp
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        // insertionMarkerHorizontal = fontSizeInPx, so the insertion marker is completely outside
+        // this rectangle.
+        val innerTextFieldBounds =
+            Rect(0f, 0f, fontSizeInPx - 1f, textLayoutResult.size.height.toFloat())
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    textLayoutResult,
+                    matrix,
+                    innerTextFieldBounds = innerTextFieldBounds,
+                    decorationBoxBounds = innerTextFieldBounds
+                )
+
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
+    }
+
+    @Test
+    fun testInsertionMarkerPartiallyVisible() {
+        val fontSize = 10.sp
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        // insertionMarkerTop = 0 and insertionMarkerBottom = fontSizeInPx, so this rectangle covers
+        // the top of the insertion marker but not the bottom.
+        val innerTextFieldBounds = Rect(fontSizeInPx - 1f, 0f, fontSizeInPx + 1f, fontSizeInPx - 1f)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    textLayoutResult,
+                    matrix,
+                    innerTextFieldBounds = innerTextFieldBounds,
+                    decorationBoxBounds = innerTextFieldBounds
+                )
+
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
+        assertThat(cursorAnchorInfo.insertionMarkerFlags)
+            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
+    }
+
+    @Test
+    fun testInsertionMarkerNotIncludedWhenIncludeInsertionMarkerFalse() {
+        val fontSize = 10.sp
+        val textFieldValue = TextFieldValue("abc", selection = TextRange(1))
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, textLayoutResult, matrix, includeInsertionMarker = false)
+
+        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isNaN()
+        assertThat(cursorAnchorInfo.insertionMarkerTop).isNaN()
+        assertThat(cursorAnchorInfo.insertionMarkerBottom).isNaN()
+        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isNaN()
+        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(0)
+    }
+
+    @Test
+    fun testCharacterBoundsLtr() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        val text = "a bc d"
+        // Composition is on "bc"
+        val composition = TextRange(2, 4)
+        val textFieldValue = TextFieldValue(text, composition = composition)
+        val width = text.length * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+
+        for (index in text.indices) {
+            if (index in composition) {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                    .isEqualTo(
+                        RectF(index * fontSizeInPx, 0f, (index + 1) * fontSizeInPx, fontSizeInPx)
+                    )
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
+                    .isEqualTo(FLAG_HAS_VISIBLE_REGION)
+            } else {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+            }
+        }
+    }
+
+    @Test
+    fun testCharacterBoundsRtl() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        val text = "\u05D0 \u05D1\u05D2 \u05D3"
+        // Composition is on "\u05D1\u05D2"
+        val composition = TextRange(2, 4)
+        val textFieldValue = TextFieldValue(text, composition = composition)
+        val width = text.length * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder().build(textFieldValue, textLayoutResult, matrix)
+
+        for (index in text.indices) {
+            if (index in composition) {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                    .isEqualTo(
+                        RectF(
+                            width - ((index + 1) * fontSizeInPx),
+                            0f,
+                            width - (index * fontSizeInPx),
+                            fontSizeInPx
+                        )
+                    )
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
+                    .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
+            } else {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+            }
+        }
+    }
+
+    @Test
+    fun testCharacterBoundsWithVisualTransformation() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        val text = "abcd"
+        // Composition is on "bc"
+        val composition = TextRange(2, 4)
+        val offsetMapping = object : OffsetMapping {
+            override fun originalToTransformed(offset: Int) = 2 * offset
+            override fun transformedToOriginal(offset: Int) = throw NotImplementedError()
+        }
+        val transformedText = "a-b-c-d-"
+        val textFieldValue = TextFieldValue(text, composition = composition)
+        val width = transformedText.length * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(transformedText, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, textLayoutResult, matrix, offsetMapping = offsetMapping)
+
+        for (index in text.indices) {
+            if (index in composition) {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index))
+                    .isEqualTo(
+                        RectF(
+                            2 * index * fontSizeInPx,
+                            0f,
+                            (2 * index + 1) * fontSizeInPx,
+                            fontSizeInPx
+                        )
+                    )
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
+                    .isEqualTo(FLAG_HAS_VISIBLE_REGION)
+            } else {
+                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+            }
+        }
+    }
+
+    @Test
+    fun testCharacterBoundsVisibility() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        val text = "a bc d"
+        // Composition is on "bc"
+        val composition = TextRange(2, 4)
+        val textFieldValue = TextFieldValue(text, composition = composition)
+        val width = text.length * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+        val innerTextFieldBounds = Rect(3.5f * fontSizeInPx, 0f, 4f * fontSizeInPx, fontSizeInPx)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    textLayoutResult,
+                    matrix,
+                    innerTextFieldBounds = innerTextFieldBounds,
+                    decorationBoxBounds = innerTextFieldBounds
+                )
+
+        // Character at index 2 spans horizontal range [2 * fontSizeInPx, 3 * fontSizeInPx], so it
+        // is completely outside innerTextFieldBounds.
+        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2)).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
+        // Character at index 3 spans horizontal range [3 * fontSizeInPx, 4 * fontSizeInPx], so it
+        // is partially inside innerTextFieldBounds.
+        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3))
+            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
+    }
+
+    @Test
+    fun testCharacterBoundsNotIncludedWhenIncludeCharacterBoundsFalse() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        val text = "a bc d"
+        // Composition is on "bc"
+        val composition = TextRange(2, 4)
+        val textFieldValue = TextFieldValue(text, composition = composition)
+        val width = text.length * fontSizeInPx
+        val textLayoutResult =
+            getTextLayoutResult(textFieldValue.text, fontSize = fontSize, width = width)
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(textFieldValue, textLayoutResult, matrix, includeCharacterBounds = false)
+
+        for (index in text.indices) {
+            assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
+            assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testEditorBounds() {
+        val textFieldValue = TextFieldValue()
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    getTextLayoutResult(textFieldValue.text),
+                    matrix,
+                    innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
+                    decorationBoxBounds = Rect(5f, 6f, 7f, 8f)
+                )
+
+        assertThat(cursorAnchorInfo.editorBoundsInfo?.editorBounds).isEqualTo(RectF(5f, 6f, 7f, 8f))
+        assertThat(cursorAnchorInfo.editorBoundsInfo?.handwritingBounds)
+            .isEqualTo(RectF(5f, 6f, 7f, 8f))
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testEditorBoundsNotIncludedWhenIncludeEditorBoundsFalse() {
+        val textFieldValue = TextFieldValue()
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    getTextLayoutResult(textFieldValue.text),
+                    matrix,
+                    innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
+                    decorationBoxBounds = Rect(5f, 6f, 7f, 8f),
+                    includeEditorBounds = false
+                )
+
+        assertThat(cursorAnchorInfo.editorBoundsInfo).isNull()
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    fun testLineBounds() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        // 6 lines of text
+        val textFieldValue = TextFieldValue("a\nbb\nccc\ndddd\neeeee\nffffff")
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        // Lines 2, 3, 4 are visible
+        val innerTextFieldBounds =
+            Rect(
+                0f,
+                textLayoutResult.getLineTop(2) + 1f,
+                fontSizeInPx,
+                textLayoutResult.getLineBottom(4) - 1f
+            )
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    textLayoutResult,
+                    matrix,
+                    innerTextFieldBounds = innerTextFieldBounds,
+                    decorationBoxBounds = innerTextFieldBounds
+                )
+
+        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(3)
+        // Line 2 "ccc" has 3 characters
+        assertThat(cursorAnchorInfo.visibleLineBounds[0])
+            .isEqualTo(
+                RectF(
+                    0f,
+                    textLayoutResult.getLineTop(2),
+                    3 * fontSizeInPx,
+                    textLayoutResult.getLineBottom(2)
+                )
+            )
+        // Line 3 "dddd" has 4 characters
+        assertThat(cursorAnchorInfo.visibleLineBounds[1])
+            .isEqualTo(
+                RectF(
+                    0f,
+                    textLayoutResult.getLineTop(3),
+                    4 * fontSizeInPx,
+                    textLayoutResult.getLineBottom(3)
+                )
+            )
+        // Line 4 "eeeee" has 5 characters
+        assertThat(cursorAnchorInfo.visibleLineBounds[2])
+            .isEqualTo(
+                RectF(
+                    0f,
+                    textLayoutResult.getLineTop(4),
+                    5 * fontSizeInPx,
+                    textLayoutResult.getLineBottom(4)
+                )
+            )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    fun testLineBoundsNotIncludedWhenIncludeLineBoundsFalse() {
+        val fontSize = 10.sp
+        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
+        // 6 lines of text
+        val textFieldValue = TextFieldValue("a\nbb\nccc\ndddd\neeeee\nffffff")
+        val textLayoutResult = getTextLayoutResult(textFieldValue.text, fontSize = fontSize)
+        // Lines 2, 3, 4 are visible
+        val innerTextFieldBounds =
+            Rect(
+                0f,
+                textLayoutResult.getLineTop(2) + 1f,
+                fontSizeInPx,
+                textLayoutResult.getLineBottom(4) - 1f
+            )
+
+        val cursorAnchorInfo =
+            CursorAnchorInfo.Builder()
+                .build(
+                    textFieldValue,
+                    OffsetMapping.Identity,
+                    textLayoutResult,
+                    matrix,
+                    innerTextFieldBounds = innerTextFieldBounds,
+                    decorationBoxBounds = innerTextFieldBounds,
+                    includeLineBounds = false
+                )
+
+        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(0)
+    }
+
+    private fun getTextLayoutResult(
+        text: String,
+        fontSize: TextUnit = 12.sp,
+        width: Float = with(defaultDensity) { 1000.dp.toPx() }
+    ): TextLayoutResult {
+        val intWidth = ceil(width).toInt()
+
+        val fontFamilyResolver = createFontFamilyResolver(context)
+
+        val input =
+            TextLayoutInput(
+                text = AnnotatedString(text),
+                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = fontSize),
+                placeholders = listOf(),
+                maxLines = Int.MAX_VALUE,
+                softWrap = true,
+                overflow = TextOverflow.Visible,
+                density = defaultDensity,
+                layoutDirection = LayoutDirection.Ltr,
+                fontFamilyResolver = fontFamilyResolver,
+                constraints = Constraints(maxWidth = intWidth)
+            )
+
+        val paragraph =
+            MultiParagraph(
+                annotatedString = input.text,
+                style = input.style,
+                constraints = Constraints(maxWidth = ceil(width).toInt()),
+                density = input.density,
+                fontFamilyResolver = fontFamilyResolver
+            )
+
+        return TextLayoutResult(input, paragraph, IntSize(intWidth, ceil(paragraph.height).toInt()))
+    }
+}
+
+private fun CursorAnchorInfo.Builder.build(
+    textFieldValue: TextFieldValue,
+    textLayoutResult: TextLayoutResult,
+    matrix: Matrix,
+    offsetMapping: OffsetMapping = OffsetMapping.Identity,
+    includeInsertionMarker: Boolean = true,
+    includeCharacterBounds: Boolean = true,
+    includeEditorBounds: Boolean = true,
+    includeLineBounds: Boolean = true
+): CursorAnchorInfo {
+    val innerTextFieldBounds =
+        Rect(0f, 0f, textLayoutResult.size.width.toFloat(), textLayoutResult.size.height.toFloat())
+    return build(
+        textFieldValue,
+        offsetMapping,
+        textLayoutResult,
+        matrix,
+        innerTextFieldBounds = innerTextFieldBounds,
+        decorationBoxBounds = innerTextFieldBounds,
+        includeInsertionMarker = includeInsertionMarker,
+        includeCharacterBounds = includeCharacterBounds,
+        includeEditorBounds = includeEditorBounds,
+        includeLineBounds = includeLineBounds
+    )
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyEditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyEditorInfoTest.kt
new file mode 100644
index 0000000..53336bc
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyEditorInfoTest.kt
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import android.text.InputType
+import android.view.inputmethod.EditorInfo
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PlatformImeOptions
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class LegacyEditorInfoTest {
+
+    @Test
+    fun test_fill_editor_info_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_ascii() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_number() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_phone() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Phone,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_PHONE and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_uri() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Uri,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_URI and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_email() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Email,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS and info.inputType) != 0)
+            .isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_password() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Password,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_number_password() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.NumberPassword,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_NUMBER_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_decimal_number() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Decimal,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_NUMBER_FLAG_DECIMAL and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_none() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.None
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_NONE
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_go() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Go
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_GO
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_next() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Next
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_NEXT
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_previous() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Previous
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_PREVIOUS
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_search() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Search,
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_SEARCH
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_send() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Send
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_SEND
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_done() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_DONE
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line_with_default_action() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_single_line() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = true,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_single_line_changes_ime_from_unspecified_to_done() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = true,
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((EditorInfo.IME_ACTION_DONE and info.imeOptions) == 0).isFalse()
+        assertThat((EditorInfo.IME_ACTION_UNSPECIFIED and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line_not_set_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_none() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.None,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_characters() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Characters,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isFalse()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_words() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Words,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isFalse()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_sentences() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Sentences,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done,
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_not_added_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Sentences,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done,
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_auto_correct_on() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = true,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_auto_correct_off() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = false,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun autocorrect_not_added_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = true,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun initial_default_selection_info_is_set() {
+        val info = EditorInfo()
+        info.update(ImeOptions.Default)
+
+        assertThat(info.initialSelStart).isEqualTo(0)
+        assertThat(info.initialSelEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun initial_selection_info_is_set() {
+        val selection = TextRange(1, 2)
+        val info = EditorInfo()
+        info.update("abc", selection, ImeOptions.Default)
+
+        assertThat(info.initialSelStart).isEqualTo(selection.start)
+        assertThat(info.initialSelEnd).isEqualTo(selection.end)
+    }
+
+    @Test
+    fun test_privateImeOptions_is_set() {
+        val info = EditorInfo()
+        val privateImeOptions = "testOptions"
+        info.update(
+            ImeOptions(
+                platformImeOptions = PlatformImeOptions(privateImeOptions)
+            )
+        )
+
+        assertThat(info.privateImeOptions).isEqualTo(privateImeOptions)
+    }
+
+    private fun EditorInfo.update(imeOptions: ImeOptions) {
+        this.update("", TextRange.Zero, imeOptions)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/MoveCursorCommandTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/MoveCursorCommandTest.kt
new file mode 100644
index 0000000..4d5a9c3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/MoveCursorCommandTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MoveCursorCommandTest {
+    private val CH1 = "\uD83D\uDE00" // U+1F600
+    private val CH2 = "\uD83D\uDE01" // U+1F601
+    private val CH3 = "\uD83D\uDE02" // U+1F602
+    private val CH4 = "\uD83D\uDE03" // U+1F603
+    private val CH5 = "\uD83D\uDE04" // U+1F604
+
+    // U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
+    private val FAMILY = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
+
+    @Test
+    fun test_left() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.moveCursor(-1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(2)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_left_multiple() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.moveCursor(-2)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(1)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_left_from_offset0() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.moveCursor(-1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_right() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.moveCursor(1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(4)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_right_multiple() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.moveCursor(2)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(5)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_right_from_offset_length() {
+        val eb = EditingBuffer("ABCDE", TextRange(5))
+
+        eb.moveCursor(1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("ABCDE")
+        Truth.assertThat(eb.cursor).isEqualTo(5)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_left_surrogate_pair() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.moveCursor(-1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
+        Truth.assertThat(eb.cursor).isEqualTo(4)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_left_multiple_surrogate_pair() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.moveCursor(-2)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
+        Truth.assertThat(eb.cursor).isEqualTo(2)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_right_surrogate_pair() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.moveCursor(1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
+        Truth.assertThat(eb.cursor).isEqualTo(8)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_right_multiple_surrogate_pair() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.moveCursor(2)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
+        Truth.assertThat(eb.cursor).isEqualTo(10)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 26)
+    fun test_left_emoji() {
+        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
+
+        eb.moveCursor(-1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$FAMILY$FAMILY")
+        Truth.assertThat(eb.cursor).isEqualTo(0)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 26)
+    fun test_right_emoji() {
+        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
+
+        eb.moveCursor(1)
+
+        Truth.assertThat(eb.toString()).isEqualTo("$FAMILY$FAMILY")
+        Truth.assertThat(eb.cursor).isEqualTo(2 * FAMILY.length)
+        Truth.assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt
new file mode 100644
index 0000000..d027b6d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt
@@ -0,0 +1,490 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.content.ClipDescription
+import android.net.Uri
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputContentInfo
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.firstUriOrNull
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.core.view.inputmethod.EditorInfoCompat
+import androidx.core.view.inputmethod.InputConnectionCompat
+import androidx.core.view.inputmethod.InputContentInfoCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatelessInputConnectionTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var ic: StatelessInputConnection
+    private val activeSession: TextInputSession = object : TextInputSession {
+        override val text: TextFieldCharSequence
+            get() = [email protected]
+
+        override fun onImeAction(imeAction: ImeAction) {
+            [email protected]?.invoke(imeAction)
+        }
+
+        override fun requestEdit(
+            notifyImeOfChanges: Boolean,
+            block: EditingBuffer.() -> Unit
+        ) {
+            onRequestEdit?.invoke(notifyImeOfChanges, block)
+        }
+
+        override fun sendKeyEvent(keyEvent: KeyEvent) {
+            onSendKeyEvent?.invoke(keyEvent)
+        }
+
+        override fun requestCursorUpdates(cursorUpdateMode: Int) {
+        }
+
+        override fun onCommitContent(transferableContent: TransferableContent): Boolean {
+            return [email protected]?.invoke(transferableContent)
+                ?: false
+        }
+    }
+
+    private var state: TextFieldState = TextFieldState()
+    private var value: TextFieldCharSequence = TextFieldCharSequence()
+        set(value) {
+            field = value
+            state = TextFieldState(value.toString(), value.selectionInChars)
+        }
+    private var onRequestEdit: ((Boolean, EditingBuffer.() -> Unit) -> Unit)? = null
+    private var onSendKeyEvent: ((KeyEvent) -> Unit)? = null
+    private var onImeAction: ((ImeAction) -> Unit)? = null
+    private var onCommitContent: ((TransferableContent) -> Boolean)? = null
+
+    @Before
+    fun setup() {
+        ic = StatelessInputConnection(activeSession, EditorInfo())
+    }
+
+    @Test
+    fun getTextBeforeAndAfterCursorTest() {
+        Truth.assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("")
+        Truth.assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("")
+
+        // Set "Hello, World", and place the cursor at the beginning of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange.Zero
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("")
+        Truth.assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("Hello, World")
+
+        // Set "Hello, World", and place the cursor between "H" and "e".
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(1)
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("H")
+        Truth.assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("ello, World")
+
+        // Set "Hello, World", and place the cursor at the end of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(12)
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("Hello, World")
+        Truth.assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("")
+    }
+
+    @Test
+    fun getTextBeforeAndAfterCursorTest_maxCharTest() {
+        // Set "Hello, World", and place the cursor at the beginning of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange.Zero
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("")
+        Truth.assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("Hello")
+
+        // Set "Hello, World", and place the cursor between "H" and "e".
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(1)
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("H")
+        Truth.assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("ello,")
+
+        // Set "Hello, World", and place the cursor at the end of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(12)
+        )
+
+        Truth.assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("World")
+        Truth.assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("")
+    }
+
+    @Test
+    fun getSelectedTextTest() {
+        // Set "Hello, World", and place the cursor at the beginning of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange.Zero
+        )
+
+        Truth.assertThat(ic.getSelectedText(0)).isNull()
+
+        // Set "Hello, World", and place the cursor between "H" and "e".
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(0, 1)
+        )
+
+        Truth.assertThat(ic.getSelectedText(0)).isEqualTo("H")
+
+        // Set "Hello, World", and place the cursor at the end of the text.
+        value = TextFieldCharSequence(
+            text = "Hello, World",
+            selection = TextRange(0, 12)
+        )
+
+        Truth.assertThat(ic.getSelectedText(0)).isEqualTo("Hello, World")
+    }
+
+    @Test
+    fun commitTextTest_batchSession() {
+        var requestEditsCalled = 0
+        onRequestEdit = { _, block ->
+            requestEditsCalled++
+            state.mainBuffer.block()
+        }
+        value = TextFieldCharSequence(text = "", selection = TextRange.Zero)
+
+        // IME set text "Hello, World." with two commitText API within the single batch session.
+        // Do not callback to listener during batch session.
+        ic.beginBatchEdit()
+
+        Truth.assertThat(ic.commitText("Hello, ", 1)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        Truth.assertThat(ic.commitText("World.", 1)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        ic.endBatchEdit()
+
+        Truth.assertThat(requestEditsCalled).isEqualTo(1)
+        Truth.assertThat(state.mainBuffer.toString()).isEqualTo("Hello, World.")
+        Truth.assertThat(state.mainBuffer.selection).isEqualTo(TextRange(13))
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @SdkSuppress(minSdkVersion = 25)
+    @Test
+    fun commitContent_parsesToTransferableContent() {
+        var transferableContent: TransferableContent? = null
+        onCommitContent = {
+            transferableContent = it
+            true
+        }
+        val contentUri = Uri.parse("content://com.example/content")
+        val linkUri = Uri.parse("https://example.com")
+        val description = ClipDescription("label", arrayOf("text/plain"))
+        val extras = Bundle().apply { putString("key", "value") }
+        val result = ic.commitContent(
+            InputContentInfo(contentUri, description, linkUri),
+            InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
+            extras
+        )
+
+        Truth.assertThat(transferableContent).isNotNull()
+        Truth.assertThat(transferableContent?.clipEntry).isNotNull()
+        Truth.assertThat(transferableContent?.clipEntry?.firstUriOrNull()).isEqualTo(contentUri)
+        Truth.assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
+        Truth.assertThat(transferableContent?.clipMetadata?.clipDescription)
+            .isSameInstanceAs(description)
+
+        Truth.assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
+        Truth.assertThat(transferableContent?.platformTransferableContent?.linkUri)
+            .isEqualTo(linkUri)
+        Truth.assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
+            .contains("key")
+        Truth.assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
+            .contains("EXTRA_INPUT_CONTENT_INFO")
+
+        assertTrue(result)
+    }
+
+    @SdkSuppress(minSdkVersion = 25)
+    @Test
+    fun commitContent_returnsResultIfFalse() {
+        onCommitContent = {
+            false
+        }
+        val contentUri = Uri.parse("content://com.example/content")
+        val description = ClipDescription("label", arrayOf("text/plain"))
+        val result = ic.commitContent(InputContentInfo(contentUri, description), 0, null)
+
+        assertFalse(result)
+    }
+
+    @SdkSuppress(minSdkVersion = 25)
+    @Test
+    fun commitContent_returnsFalseWhenNotDefined() {
+        onCommitContent = null
+        val contentUri = Uri.parse("content://com.example/content")
+        val description = ClipDescription("label", arrayOf("text/plain"))
+        val result = ic.commitContent(InputContentInfo(contentUri, description), 0, null)
+
+        assertFalse(result)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @SdkSuppress(maxSdkVersion = 24)
+    @Test
+    fun performPrivateCommand_parsesToTransferableContent() {
+        var transferableContent: TransferableContent? = null
+        onCommitContent = {
+            transferableContent = it
+            true
+        }
+
+        val editorInfo = EditorInfo()
+        EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("text/plain"))
+
+        ic = StatelessInputConnection(activeSession, editorInfo)
+
+        val contentUri = Uri.parse("content://com.example/content")
+        val linkUri = Uri.parse("https://example.com")
+        val description = ClipDescription("label", arrayOf("text/plain"))
+        val extras = Bundle().apply { putString("key", "value") }
+        // this will internally call performPrivateCommand when SDK <= 24
+        val result = InputConnectionCompat.commitContent(
+            ic,
+            editorInfo,
+            InputContentInfoCompat(contentUri, description, linkUri),
+            InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
+            extras
+        )
+
+        Truth.assertThat(transferableContent).isNotNull()
+        Truth.assertThat(transferableContent?.clipEntry).isNotNull()
+        Truth.assertThat(transferableContent?.clipEntry?.firstUriOrNull()).isEqualTo(contentUri)
+        Truth.assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
+        Truth.assertThat(transferableContent?.clipMetadata?.clipDescription)
+            .isSameInstanceAs(description)
+
+        Truth.assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
+        Truth.assertThat(transferableContent?.platformTransferableContent?.linkUri)
+            .isEqualTo(linkUri)
+        Truth.assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
+            .contains("key")
+        // Permissions do not exist below SDK 25
+        Truth.assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
+            .doesNotContain("EXTRA_INPUT_CONTENT_INFO")
+
+        assertTrue(result)
+    }
+
+    @Test
+    fun mixedAPICalls_batchSession() {
+        var requestEditsCalled = 0
+        onRequestEdit = { _, block ->
+            requestEditsCalled++
+            state.mainBuffer.block()
+        }
+        value = TextFieldCharSequence(text = "", selection = TextRange.Zero)
+
+        // Do not callback to listener during batch session.
+        ic.beginBatchEdit()
+
+        Truth.assertThat(ic.setComposingText("Hello, ", 1)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        Truth.assertThat(ic.finishComposingText()).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        Truth.assertThat(ic.commitText("World.", 1)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        Truth.assertThat(ic.setSelection(0, 12)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        Truth.assertThat(ic.commitText("", 1)).isTrue()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+
+        ic.endBatchEdit()
+
+        Truth.assertThat(requestEditsCalled).isEqualTo(1)
+        Truth.assertThat(state.mainBuffer.toString()).isEqualTo(".")
+        Truth.assertThat(state.mainBuffer.selection).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun closeConnection() {
+        // Everything is internal and there is nothing to expect.
+        // Just make sure it is not crashed by calling method.
+        ic.closeConnection()
+    }
+
+    @Test
+    fun do_not_callback_if_only_readonly_ops() {
+        var requestEditsCalled = 0
+        onRequestEdit = { _, _ -> requestEditsCalled++ }
+        ic.beginBatchEdit()
+        ic.getSelectedText(1)
+        ic.endBatchEdit()
+        Truth.assertThat(requestEditsCalled).isEqualTo(0)
+    }
+
+    @Test
+    fun sendKeyEvent_whenIMERequests() {
+        val keyEvents = mutableListOf<KeyEvent>()
+        onSendKeyEvent = {
+            keyEvents += it
+        }
+        val keyEvent1 = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0)
+        val keyEvent2 = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)
+        ic.sendKeyEvent(keyEvent1)
+        ic.sendKeyEvent(keyEvent2)
+
+        Truth.assertThat(keyEvents.size).isEqualTo(2)
+        Truth.assertThat(keyEvents.first()).isEqualTo(keyEvent1)
+        Truth.assertThat(keyEvents.last()).isEqualTo(keyEvent2)
+    }
+
+    @Test
+    fun performImeAction_whenIMERequests() {
+        val receivedImeActions = mutableListOf<ImeAction>()
+        onImeAction = {
+            receivedImeActions += it
+        }
+        ic.performEditorAction(EditorInfo.IME_ACTION_DONE)
+        ic.performEditorAction(EditorInfo.IME_ACTION_GO)
+        ic.performEditorAction(EditorInfo.IME_ACTION_NEXT)
+        ic.performEditorAction(EditorInfo.IME_ACTION_NONE)
+        ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS)
+        ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH)
+        ic.performEditorAction(EditorInfo.IME_ACTION_SEND)
+        ic.performEditorAction(EditorInfo.IME_ACTION_UNSPECIFIED)
+        ic.performEditorAction(-1)
+
+        Truth.assertThat(receivedImeActions).isEqualTo(listOf(
+            ImeAction.Done,
+            ImeAction.Go,
+            ImeAction.Next,
+            ImeAction.Default, // None is evaluated back to Default.
+            ImeAction.Previous,
+            ImeAction.Search,
+            ImeAction.Send,
+            ImeAction.Default, // Unspecified is evaluated back to Default.
+            ImeAction.Default // Unrecognized is evaluated back to Default.
+        ))
+    }
+
+    @Test
+    fun selectAll_contextMenuAction_triggersSelectionAndImeNotification() {
+        value = TextFieldCharSequence("Hello")
+        var callCount = 0
+        var isNotifyIme = false
+        onRequestEdit = { notify, block ->
+            isNotifyIme = notify
+            callCount++
+            state.mainBuffer.block()
+        }
+
+        ic.performContextMenuAction(android.R.id.selectAll)
+
+        Truth.assertThat(callCount).isEqualTo(1)
+        Truth.assertThat(isNotifyIme).isTrue()
+        Truth.assertThat(state.mainBuffer.selection).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun cut_contextMenuAction_triggersSyntheticKeyEvents() {
+        val keyEvents = mutableListOf<KeyEvent>()
+        onSendKeyEvent = { keyEvents += it }
+
+        ic.performContextMenuAction(android.R.id.cut)
+
+        Truth.assertThat(keyEvents.size).isEqualTo(2)
+        Truth.assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
+        Truth.assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_CUT)
+        Truth.assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
+        Truth.assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_CUT)
+    }
+
+    @Test
+    fun copy_contextMenuAction_triggersSyntheticKeyEvents() {
+        val keyEvents = mutableListOf<KeyEvent>()
+        onSendKeyEvent = { keyEvents += it }
+
+        ic.performContextMenuAction(android.R.id.copy)
+
+        Truth.assertThat(keyEvents.size).isEqualTo(2)
+        Truth.assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
+        Truth.assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_COPY)
+        Truth.assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
+        Truth.assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_COPY)
+    }
+
+    @Test
+    fun paste_contextMenuAction_triggersSyntheticKeyEvents() {
+        val keyEvents = mutableListOf<KeyEvent>()
+        onSendKeyEvent = { keyEvents += it }
+
+        ic.performContextMenuAction(android.R.id.paste)
+
+        Truth.assertThat(keyEvents.size).isEqualTo(2)
+        Truth.assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
+        Truth.assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_PASTE)
+        Truth.assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
+        Truth.assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_PASTE)
+    }
+
+    @Test
+    fun debugMode_isDisabled() {
+        // run this in presubmit to check that we are not accidentally enabling logs on prod
+        assertFalse(
+            SIC_DEBUG,
+            "Oops, looks like you accidentally enabled logging. Don't worry, we've all " +
+                "been there. Just remember to turn it off before you deploy your code."
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt
new file mode 100644
index 0000000..9acdbc4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt
@@ -0,0 +1,782 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.ObserverHandle
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotStateObserver
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TextFieldLayoutStateCacheTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var textFieldState = TextFieldState()
+    private var transformedTextFieldState = TransformedTextFieldState(
+        textFieldState,
+        inputTransformation = null,
+        codepointTransformation = null
+    )
+    private var textStyle = TextStyle()
+    private var singleLine = false
+    private var softWrap = false
+    private var cache = TextFieldLayoutStateCache()
+    private var density = Density(1f, 1f)
+    private var layoutDirection = LayoutDirection.Ltr
+    private var fontFamilyResolver =
+        createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
+    private var constraints = Constraints()
+
+    private lateinit var globalWriteObserverHandle: ObserverHandle
+
+    @Before
+    fun setUp() {
+        globalWriteObserverHandle = Snapshot.registerGlobalWriteObserver {
+            Snapshot.sendApplyNotifications()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        globalWriteObserverHandle.dispose()
+    }
+
+    @Test
+    fun updateAllInputs_doesntInvalidateSnapshot_whenNothingChanged() {
+        assertInvalidationsOnChange(0) {
+            updateNonMeasureInputs()
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenTextContentChanged() {
+        textFieldState.edit {
+            replace(0, length, "")
+            placeCursorBeforeCharAt(0)
+        }
+        assertInvalidationsOnChange(1) {
+            textFieldState.edit {
+                append("hello")
+                placeCursorBeforeCharAt(0)
+            }
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenTextSelectionChanged() {
+        textFieldState.edit {
+            append("hello")
+            placeCursorBeforeCharAt(0)
+        }
+        assertInvalidationsOnChange(1) {
+            textFieldState.edit {
+                placeCursorBeforeCharAt(1)
+            }
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenCodepointTransformationChanged() {
+        assertInvalidationsOnChange(1) {
+            val codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+            transformedTextFieldState = TransformedTextFieldState(
+                textFieldState,
+                inputTransformation = null,
+                codepointTransformation
+            )
+            updateNonMeasureInputs()
+        }
+    }
+
+    @FlakyTest(bugId = 300168644)
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenStyleLayoutAffectingAttrsChanged() {
+        textStyle = TextStyle(fontSize = 12.sp)
+        assertInvalidationsOnChange(1) {
+            textStyle = TextStyle(fontSize = 23.sp)
+            updateNonMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_doesInvalidateSnapshot_whenStyleDrawAffectingAttrsChanged() {
+        // Measure still does not happen but TextLayoutInput object inside TextLayoutResult
+        // should change to reflect the latest inputs that need to be used during the draw phase.
+        textStyle = TextStyle(color = Color.Black)
+        assertInvalidationsOnChange(1) {
+            textStyle = TextStyle(color = Color.Blue)
+            updateNonMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenSingleLineChanged() {
+        assertInvalidationsOnChange(1) {
+            singleLine = !singleLine
+            updateNonMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateNonMeasureInputs_invalidatesSnapshot_whenSoftWrapChanged() {
+        assertInvalidationsOnChange(1) {
+            softWrap = !softWrap
+            updateNonMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenDensityInstanceChangedWithDifferentValues() {
+        density = Density(1f, 1f)
+        assertInvalidationsOnChange(1) {
+            density = Density(1f, 2f)
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_doesntInvalidateSnapshot_whenDensityInstanceChangedWithSameValues() {
+        density = Density(1f, 1f)
+        assertInvalidationsOnChange(0) {
+            density = Density(1f, 1f)
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenDensityValueChangedWithSameInstance() {
+        var densityValue = 1f
+        density = object : Density {
+            override val density: Float
+                get() = densityValue
+            override val fontScale: Float = 1f
+        }
+        assertInvalidationsOnChange(1) {
+            densityValue = 2f
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenFontScaleChangedWithSameInstance() {
+        var fontScale = 1f
+        density = object : Density {
+            override val density: Float = 1f
+            override val fontScale: Float
+                get() = fontScale
+        }
+        assertInvalidationsOnChange(1) {
+            fontScale = 2f
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenLayoutDirectionChanged() {
+        layoutDirection = LayoutDirection.Ltr
+        assertInvalidationsOnChange(1) {
+            layoutDirection = LayoutDirection.Rtl
+            updateMeasureInputs()
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenFontFamilyResolverChanged() {
+        assertInvalidationsOnChange(1) {
+            fontFamilyResolver =
+                createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
+            updateMeasureInputs()
+        }
+    }
+
+    @Ignore("b/294443266: figure out how to make fonts stale for test")
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenFontFamilyResolverFontChanged() {
+        fontFamilyResolver =
+            createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
+        assertInvalidationsOnChange(1) {
+            TODO("b/294443266: make fonts stale")
+        }
+    }
+
+    @Test
+    fun updateMeasureInputs_invalidatesSnapshot_whenConstraintsChanged() {
+        constraints = Constraints.fixed(5, 5)
+        assertInvalidationsOnChange(1) {
+            constraints = Constraints.fixed(6, 5)
+            updateMeasureInputs()
+        }
+    }
+
+    /**
+     * A scope that reads the layout cache and has a full cache hit – that is, all the inputs match
+     * – will never run the CodepointTransformation, and thus never register a "read" of any of the
+     * state objects the transformation function happens to read. If those inputs change, all scopes
+     * should be invalidated, even the ones that never actually ran the transformation function.
+     *
+     * The first time we compute layout with a transformation function, we invoke the transformation
+     * function. This function is provided externally and may perform zero or more state reads. The
+     * first time the layout is computed, those state reads will be seen by whatever snapshot
+     * observer is observing the layout call, and when they change, that reader will be invalidated.
+     * However, if somewhere else some different code asks for the layout, and none of the inputs
+     * have changed, it will return the cached value without ever running the transformation
+     * function. This means that when states read by the transformation change, that second reader
+     * won't be invalidated since it never observed those reads.
+     *
+     * To fix this, we manually record reads done by the transformation function and re-read them
+     * explicitly when checking for a full cache hit.
+     */
+    @Ignore("b/306198696")
+    @Test
+    fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingSameVisualText() {
+        var transformationState by mutableStateOf(1)
+        var transformationInvocations = 0
+        val codepointTransformation = CodepointTransformation { _, codepoint ->
+            transformationInvocations++
+            @Suppress("UNUSED_EXPRESSION")
+            transformationState
+            codepoint + 1
+        }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState, inputTransformation = null,
+            codepointTransformation
+        )
+        // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
+        // simpler.
+        textFieldState.setTextAndPlaceCursorAtEnd("h")
+        val expectedVisualText = "i"
+
+        fun assertVisualText() {
+            Truth.assertThat(cache.value?.layoutInput?.text?.text).isEqualTo(expectedVisualText)
+        }
+
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        var primaryInvalidations = 0
+        var secondaryInvalidations = 0
+
+        val primaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
+        val secondaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
+        try {
+            primaryObserver.start()
+            secondaryObserver.start()
+
+            // This will compute the initial layout.
+            primaryObserver.observeReads(Unit, onValueChangedForScope = {
+                primaryInvalidations++
+                assertVisualText()
+            }) { assertVisualText() }
+            Truth.assertThat(transformationInvocations).isEqualTo(1)
+
+            // This should be a full cache hit.
+            secondaryObserver.observeReads(Unit, onValueChangedForScope = {
+                secondaryInvalidations++
+                assertVisualText()
+            }) { assertVisualText() }
+            Truth.assertThat(transformationInvocations).isEqualTo(1)
+
+            // Invalidate the transformation.
+            transformationState++
+        } finally {
+            primaryObserver.stop()
+            secondaryObserver.stop()
+        }
+
+        assertVisualText()
+        Truth.assertThat(transformationInvocations).isEqualTo(2)
+        Truth.assertThat(primaryInvalidations).isEqualTo(1)
+        Truth.assertThat(secondaryInvalidations).isEqualTo(1)
+    }
+
+    @FlakyTest(bugId = 299662404)
+    @Test
+    fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingNewVisualText() {
+        var transformationState by mutableStateOf(1)
+        var transformationInvocations = 0
+        val codepointTransformation = CodepointTransformation { _, codepoint ->
+            transformationInvocations++
+            codepoint + transformationState
+        }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
+        // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
+        // simpler.
+        textFieldState.setTextAndPlaceCursorAtEnd("h")
+        var expectedVisualText = "i"
+
+        fun assertVisualText() {
+            Truth.assertThat(cache.value?.layoutInput?.text?.text).isEqualTo(expectedVisualText)
+        }
+
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        var primaryInvalidations = 0
+        var secondaryInvalidations = 0
+
+        val primaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
+        val secondaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
+        try {
+            primaryObserver.start()
+            secondaryObserver.start()
+
+            // This will compute the initial layout.
+            primaryObserver.observeReads(Unit, onValueChangedForScope = {
+                primaryInvalidations++
+                assertVisualText()
+            }) { assertVisualText() }
+            Truth.assertThat(transformationInvocations).isEqualTo(1)
+
+            // This should be a full cache hit.
+            secondaryObserver.observeReads(Unit, onValueChangedForScope = {
+                secondaryInvalidations++
+                assertVisualText()
+            }) { assertVisualText() }
+            Truth.assertThat(transformationInvocations).isEqualTo(1)
+
+            // Invalidate the transformation.
+            expectedVisualText = "j"
+            transformationState++
+        } finally {
+            primaryObserver.stop()
+            secondaryObserver.stop()
+        }
+
+        assertVisualText()
+        // Two more reads means two more applications of the transformation.
+        Truth.assertThat(transformationInvocations).isEqualTo(2)
+        Truth.assertThat(primaryInvalidations).isEqualTo(1)
+        Truth.assertThat(secondaryInvalidations).isEqualTo(1)
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenTextContentsChanged() {
+        textFieldState.edit {
+            replace(0, length, "h")
+            placeCursorBeforeCharAt(0)
+        }
+        assertLayoutChange(
+            change = {
+                textFieldState.edit {
+                    replace(0, length, "hello")
+                    placeCursorBeforeCharAt(0)
+                }
+            },
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.text.text).isEqualTo("h")
+            Truth.assertThat(new.layoutInput.text.text).isEqualTo("hello")
+        }
+    }
+
+    @Test
+    fun value_returnsCachedLayout_whenTextSelectionChanged() {
+        textFieldState.edit {
+            replace(0, length, "hello")
+            placeCursorBeforeCharAt(0)
+        }
+        assertLayoutChange(
+            change = {
+                textFieldState.edit {
+                    placeCursorBeforeCharAt(1)
+                }
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenCodepointTransformationInstanceChangedWithDifferentOutput() {
+        textFieldState.setTextAndPlaceCursorAtEnd("h")
+        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
+        assertLayoutChange(
+            change = {
+                codepointTransformation = CodepointTransformation { _, codepoint -> codepoint + 1 }
+                transformedTextFieldState = TransformedTextFieldState(
+                    textFieldState,
+                    inputTransformation = null,
+                    codepointTransformation
+                )
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.text.text).isEqualTo("h")
+            Truth.assertThat(new.layoutInput.text.text).isEqualTo("i")
+        }
+    }
+
+    @Test
+    fun value_returnsCachedLayout_whenCodepointTransformationInstanceChangedWithSameOutput() {
+        textFieldState.setTextAndPlaceCursorAtEnd("h")
+        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
+        assertLayoutChange(
+            change = {
+                codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+                transformedTextFieldState = TransformedTextFieldState(
+                    textFieldState,
+                    inputTransformation = null,
+                    codepointTransformation
+                )
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenStyleLayoutAffectingAttributesChanged() {
+        textStyle = TextStyle(fontSize = 12.sp)
+        assertLayoutChange(
+            change = {
+                textStyle = TextStyle(fontSize = 23.sp)
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.style.fontSize).isEqualTo(12.sp)
+            Truth.assertThat(new.layoutInput.style.fontSize).isEqualTo(23.sp)
+        }
+    }
+
+    @Test
+    fun value_returnsCachedLayout_whenStyleDrawAffectingAttributesChanged() {
+        textStyle = TextStyle(color = Color.Black)
+        assertLayoutChange(
+            change = {
+                textStyle = TextStyle(color = Color.Blue)
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            // TextLayoutInput needs to change. We only care whether multiParagraph is reused.
+            Truth.assertThat(new.multiParagraph).isSameInstanceAs(old.multiParagraph)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenSingleLineChanged() {
+        assertLayoutChange(
+            change = {
+                singleLine = !singleLine
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isNotSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenSoftWrapChanged() {
+        assertLayoutChange(
+            change = {
+                softWrap = !softWrap
+                updateNonMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.softWrap).isEqualTo(!softWrap)
+            Truth.assertThat(new.layoutInput.softWrap).isEqualTo(softWrap)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenDensityValueChangedWithSameInstance() {
+        var densityValue = 1f
+        density = object : Density {
+            override val density: Float
+                get() = densityValue
+            override val fontScale: Float = 1f
+        }
+        assertLayoutChange(
+            change = {
+                densityValue = 2f
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isNotSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenFontScaleChangedWithSameInstance() {
+        var fontScale = 1f
+        density = object : Density {
+            override val density: Float = 1f
+            override val fontScale: Float
+                get() = fontScale
+        }
+        assertLayoutChange(
+            change = {
+                fontScale = 2f
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isNotSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsCachedLayout_whenDensityInstanceChangedWithSameValues() {
+        density = Density(1f, 1f)
+        assertLayoutChange(
+            change = {
+                density = Density(1f, 1f)
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenLayoutDirectionChanged() {
+        layoutDirection = LayoutDirection.Ltr
+        assertLayoutChange(
+            change = {
+                layoutDirection = LayoutDirection.Rtl
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
+            Truth.assertThat(new.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenFontFamilyResolverChanged() {
+        assertLayoutChange(
+            change = {
+                fontFamilyResolver =
+                    createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isNotSameInstanceAs(old)
+        }
+    }
+
+    @Ignore("b/294443266: figure out how to make fonts stale for test")
+    @Test
+    fun value_returnsNewLayout_whenFontFamilyResolverFontChanged() {
+        fontFamilyResolver =
+            createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
+        assertLayoutChange(
+            change = {
+                TODO("b/294443266: make fonts stale")
+            }
+        ) { old, new ->
+            Truth.assertThat(new).isNotSameInstanceAs(old)
+        }
+    }
+
+    @Test
+    fun value_returnsNewLayout_whenConstraintsChanged() {
+        constraints = Constraints.fixed(5, 5)
+        assertLayoutChange(
+            change = {
+                constraints = Constraints.fixed(6, 5)
+                updateMeasureInputs()
+            }
+        ) { old, new ->
+            Truth.assertThat(old.layoutInput.constraints).isEqualTo(Constraints.fixed(5, 5))
+            Truth.assertThat(new.layoutInput.constraints).isEqualTo(Constraints.fixed(6, 5))
+        }
+    }
+
+    @Test
+    fun cacheUpdateInSnapshot_onlyVisibleToParentSnapshotAfterApply() {
+        layoutDirection = LayoutDirection.Ltr
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        val initialLayout = cache.value!!
+        val snapshot = Snapshot.takeMutableSnapshot()
+
+        try {
+            snapshot.enter {
+                layoutDirection = LayoutDirection.Rtl
+                updateMeasureInputs()
+
+                val newLayout = cache.value!!
+                Truth.assertThat(initialLayout.layoutInput.layoutDirection)
+                    .isEqualTo(LayoutDirection.Ltr)
+                Truth.assertThat(newLayout.layoutInput.layoutDirection)
+                    .isEqualTo(LayoutDirection.Rtl)
+                Truth.assertThat(cache.value!!).isSameInstanceAs(newLayout)
+            }
+
+            // Not visible in parent yet.
+            Truth.assertThat(initialLayout.layoutInput.layoutDirection)
+                .isEqualTo(LayoutDirection.Ltr)
+            Truth.assertThat(cache.value!!).isSameInstanceAs(initialLayout)
+            snapshot.apply().check()
+
+            // Now visible in parent.
+            val newLayout = cache.value!!
+            Truth.assertThat(initialLayout.layoutInput.layoutDirection)
+                .isEqualTo(LayoutDirection.Ltr)
+            Truth.assertThat(newLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
+            Truth.assertThat(cache.value!!).isSameInstanceAs(newLayout)
+        } finally {
+            snapshot.dispose()
+        }
+    }
+
+    @Test
+    fun cachedValue_recomputed_afterSnapshotWithConflictingInputsApplied() {
+        softWrap = false
+        layoutDirection = LayoutDirection.Ltr
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        val snapshot = Snapshot.takeMutableSnapshot()
+
+        try {
+            softWrap = true
+            updateNonMeasureInputs()
+            val initialLayout = cache.value!!
+
+            snapshot.enter {
+                layoutDirection = LayoutDirection.Rtl
+                updateMeasureInputs()
+                with(cache.value!!) {
+                    Truth.assertThat(layoutInput.softWrap).isEqualTo(false)
+                    Truth.assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
+                    Truth.assertThat(cache.value!!).isSameInstanceAs(this)
+                }
+            }
+
+            // Parent only sees its update.
+            with(cache.value!!) {
+                Truth.assertThat(layoutInput.softWrap).isEqualTo(true)
+                Truth.assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
+                Truth.assertThat(this).isSameInstanceAs(initialLayout)
+                Truth.assertThat(cache.value!!).isSameInstanceAs(this)
+            }
+            snapshot.apply().check()
+
+            // Cache should now reflect merged inputs.
+            with(cache.value!!) {
+                Truth.assertThat(layoutInput.softWrap).isEqualTo(true)
+                Truth.assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
+                Truth.assertThat(cache.value!!).isSameInstanceAs(this)
+            }
+        } finally {
+            snapshot.dispose()
+        }
+    }
+
+    private fun assertLayoutChange(
+        change: () -> Unit,
+        compare: (old: TextLayoutResult, new: TextLayoutResult) -> Unit
+    ) {
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        val initialLayout = cache.value
+
+        change()
+        val newLayout = cache.value
+
+        assertNotNull(newLayout)
+        assertNotNull(initialLayout)
+        compare(initialLayout, newLayout)
+    }
+
+    private fun assertInvalidationsOnChange(
+        expectedInvalidations: Int,
+        update: () -> Unit,
+    ) {
+        updateNonMeasureInputs()
+        updateMeasureInputs()
+        var invalidations = 0
+
+        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
+        observer.start()
+        try {
+            observer.observeReads(
+                scope = Unit,
+                onValueChangedForScope = { invalidations++ },
+                block = { cache.value }
+            )
+            update()
+            // Ensure any changes made by block are processed.
+            Snapshot.sendApplyNotifications()
+        } finally {
+            observer.stop()
+        }
+
+        Truth.assertWithMessage("Expected $expectedInvalidations invalidations")
+            .that(invalidations).isEqualTo(expectedInvalidations)
+    }
+
+    private fun updateNonMeasureInputs() {
+        cache.updateNonMeasureInputs(
+            textFieldState = transformedTextFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            softWrap = softWrap
+        )
+    }
+
+    private fun updateMeasureInputs() {
+        cache.layoutWithNewMeasureInputs(
+            density = density,
+            layoutDirection = layoutDirection,
+            fontFamilyResolver = fontFamilyResolver,
+            constraints = constraints
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
new file mode 100644
index 0000000..f357dfc
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.ExtractedText
+import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.ComposeInputMethodManagerTestRule
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.runtime.snapshots.ObserverHandle
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class TextInputServiceAndroidCursorAnchorInfoTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val composeImmRule = ComposeInputMethodManagerTestRule().apply {
+        setFactory { composeImm }
+    }
+
+    private val context = InstrumentationRegistry.getInstrumentation().context
+    private val defaultDensity = Density(density = 1f)
+    private val builder = CursorAnchorInfo.Builder()
+    private val androidMatrix = android.graphics.Matrix()
+    private val reportedCursorAnchorInfos = mutableListOf<CursorAnchorInfo>()
+
+    private val composeImm = object : ComposeInputMethodManager {
+        override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
+            reportedCursorAnchorInfos += info
+        }
+
+        override fun restartInput() {}
+        override fun showSoftInput() {}
+        override fun hideSoftInput() {}
+        override fun updateExtractedText(token: Int, extractedText: ExtractedText) {}
+        override fun updateSelection(
+            selectionStart: Int,
+            selectionEnd: Int,
+            compositionStart: Int,
+            compositionEnd: Int
+        ) {
+        }
+
+        override fun sendKeyEvent(event: KeyEvent) {}
+    }
+
+    private lateinit var inputConnection: InputConnection
+    private val session = object : PlatformTextInputSession {
+        override val view = View(InstrumentationRegistry.getInstrumentation().context)
+        override suspend fun startInputMethod(
+            request: PlatformTextInputMethodRequest
+        ): Nothing {
+            inputConnection = request.createInputConnection(EditorInfo())
+            awaitCancellation()
+        }
+    }
+
+    private val windowOffset = Offset(12f, 34f)
+    private val layoutState = TextLayoutState().apply {
+        coreNodeCoordinates = TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
+        decoratorNodeCoordinates =
+            TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
+    }
+
+    @Test
+    fun requestCursorUpdates_immediate() = runTest {
+        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        backgroundScope.startFakeTextInputSession(textFieldState)
+
+        // This requests a single update, immediately, with no future monitoring.
+        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
+
+        // Immediate update.
+        val expectedInfo = builder.build(
+            text = textFieldState.text,
+            selection = textFieldState.text.selectionInChars,
+            composition = textFieldState.text.compositionInChars,
+            textLayoutResult = layoutState.layoutResult!!,
+            matrix = getAndroidMatrix(windowOffset),
+            innerTextFieldBounds = Rect.Zero,
+            decorationBoxBounds = Rect.Zero,
+        )
+        Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
+        reportedCursorAnchorInfos.clear()
+
+        // Trigger new layout.
+        layoutState.coreNodeCoordinates =
+            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+
+        // No further updates since monitoring is off
+        Truth.assertThat(reportedCursorAnchorInfos).isEmpty()
+    }
+
+    @Test
+    fun requestCursorUpdates_immediate_beforeUpdateTextLayoutResult() = runTest {
+        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        val transformedState = TransformedTextFieldState(
+            textFieldState = textFieldState,
+            inputTransformation = null,
+            codepointTransformation = null
+        )
+
+        backgroundScope.launch(Dispatchers.Unconfined) {
+            session.platformSpecificTextInputSession(
+                state = transformedState,
+                layoutState = layoutState,
+                imeOptions = ImeOptions.Default,
+                receiveContentConfiguration = null,
+                onImeAction = null,
+            )
+        }
+
+        // This requests a single update, immediately, with no future monitoring.
+        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
+
+        Truth.assertThat(reportedCursorAnchorInfos).isEmpty()
+    }
+
+    @Test
+    fun requestCursorUpdates_monitor() = runTest {
+        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        backgroundScope.startFakeTextInputSession(textFieldState)
+
+        // This requests a single update, immediately, with no future monitoring.
+        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
+
+        // No immediate update.
+        Truth.assertThat(reportedCursorAnchorInfos).isEmpty()
+
+        // Trigger new layout.
+        layoutState.coreNodeCoordinates =
+            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+
+        // Monitoring update.
+        val expectedInfo = builder.build(
+            text = textFieldState.text,
+            selection = textFieldState.text.selectionInChars,
+            composition = textFieldState.text.compositionInChars,
+            textLayoutResult = layoutState.layoutResult!!,
+            matrix = getAndroidMatrix(Offset(67f, 89f)),
+            innerTextFieldBounds = Rect.Zero,
+            decorationBoxBounds = Rect.Zero,
+        )
+        Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
+    }
+
+    @Test
+    fun requestCursorUpdates_immediateAndMonitor() = runTest {
+        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        backgroundScope.startFakeTextInputSession(textFieldState)
+
+        inputConnection.requestCursorUpdates(
+            InputConnection.CURSOR_UPDATE_IMMEDIATE or InputConnection.CURSOR_UPDATE_MONITOR
+        )
+
+        // Immediate update.
+        val expectedInfo = builder.build(
+            text = textFieldState.text,
+            selection = textFieldState.text.selectionInChars,
+            composition = textFieldState.text.compositionInChars,
+            textLayoutResult = layoutState.layoutResult!!,
+            matrix = getAndroidMatrix(windowOffset),
+            innerTextFieldBounds = Rect.Zero,
+            decorationBoxBounds = Rect.Zero,
+        )
+        Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
+        reportedCursorAnchorInfos.clear()
+
+        // Trigger new layout.
+        layoutState.coreNodeCoordinates =
+            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+
+        // Monitoring update.
+        val expectedInfo2 = builder.build(
+            text = textFieldState.text,
+            selection = textFieldState.text.selectionInChars,
+            composition = textFieldState.text.compositionInChars,
+            textLayoutResult = layoutState.layoutResult!!,
+            matrix = getAndroidMatrix(Offset(67f, 89f)),
+            innerTextFieldBounds = Rect.Zero,
+            decorationBoxBounds = Rect.Zero,
+        )
+        Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo2)
+    }
+
+    @Test
+    fun requestCursorUpdates_cancel() = runTest {
+        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        backgroundScope.startFakeTextInputSession(textFieldState)
+
+        inputConnection.requestCursorUpdates(
+            InputConnection.CURSOR_UPDATE_IMMEDIATE or InputConnection.CURSOR_UPDATE_MONITOR
+        )
+
+        // Immediate update.
+        val expectedInfo = builder.build(
+            text = textFieldState.text,
+            selection = textFieldState.text.selectionInChars,
+            composition = textFieldState.text.compositionInChars,
+            textLayoutResult = layoutState.layoutResult!!,
+            matrix = getAndroidMatrix(windowOffset),
+            innerTextFieldBounds = Rect.Zero,
+            decorationBoxBounds = Rect.Zero,
+        )
+        Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
+        reportedCursorAnchorInfos.clear()
+
+        // Cancel monitoring.
+        inputConnection.requestCursorUpdates(0)
+
+        // Trigger new layout.
+        layoutState.coreNodeCoordinates =
+            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+
+        // No update after cancel update.
+        Truth.assertThat(reportedCursorAnchorInfos).isEmpty()
+    }
+
+    private fun runTest(testBody: suspend TestScope.() -> Unit) {
+        kotlinx.coroutines.test.runTest {
+            // Bootstrap the snapshot observation system.
+            lateinit var handle: ObserverHandle
+            try {
+                handle = Snapshot.registerGlobalWriteObserver {
+                    Snapshot.sendApplyNotifications()
+                }
+                testBody()
+            } finally {
+                handle.dispose()
+            }
+        }
+    }
+
+    private fun CoroutineScope.startFakeTextInputSession(textFieldState: TextFieldState) {
+        val transformedState = TransformedTextFieldState(
+            textFieldState = textFieldState,
+            inputTransformation = null,
+            codepointTransformation = null
+        )
+
+        launch(Dispatchers.Unconfined) {
+            session.platformSpecificTextInputSession(
+                state = transformedState,
+                layoutState = layoutState,
+                imeOptions = ImeOptions.Default,
+                receiveContentConfiguration = null,
+                onImeAction = null,
+            )
+        }
+
+        layoutState.updateNonMeasureInputs(
+            textFieldState = transformedState,
+            textStyle = TextStyle.Default,
+            singleLine = false,
+            softWrap = false
+        )
+        layoutState.layoutWithNewMeasureInputs(
+            density = defaultDensity,
+            fontFamilyResolver = createFontFamilyResolver(context, coroutineContext),
+            layoutDirection = LayoutDirection.Ltr,
+            constraints = Constraints()
+        )
+    }
+
+    private fun getAndroidMatrix(offset: Offset) =
+        androidMatrix.apply { setTranslate(offset.x, offset.y) }
+
+    private open class TestLayoutCoordinates(
+        val windowOffset: Offset = Offset.Zero,
+        override var size: IntSize = IntSize.Zero,
+        override var providedAlignmentLines: Set<AlignmentLine> = emptySet(),
+        override var parentLayoutCoordinates: LayoutCoordinates? = null,
+        override var parentCoordinates: LayoutCoordinates? = null,
+        override var isAttached: Boolean = false,
+    ) : LayoutCoordinates {
+        override fun get(alignmentLine: AlignmentLine): Int = AlignmentLine.Unspecified
+        override fun windowToLocal(relativeToWindow: Offset): Offset =
+            relativeToWindow - windowOffset
+        override fun localToWindow(relativeToLocal: Offset): Offset = relativeToLocal + windowOffset
+        override fun localToRoot(relativeToLocal: Offset): Offset = relativeToLocal
+        override fun localPositionOf(
+            sourceCoordinates: LayoutCoordinates,
+            relativeToSource: Offset
+        ): Offset = relativeToSource
+
+        override fun localBoundingBoxOf(
+            sourceCoordinates: LayoutCoordinates,
+            clipBounds: Boolean
+        ): Rect = Rect.Zero
+
+        override fun transformToScreen(matrix: Matrix) {
+            matrix.translate(windowOffset.x, windowOffset.y, 0f)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownTest.kt
new file mode 100644
index 0000000..d22250e
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownTest.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.AbsoluteAlignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val TargetTag = "TargetLayout"
+
+@RunWith(JUnit4::class)
+class PressDownTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    /**
+     * null; gesture detector has never seen a down event.
+     * true; at least a pointer is currently down.
+     * false; there was at least one down event but right now all pointers are up. Also, this
+     * requires onUp to be defined.
+     */
+    private var down: Boolean? = null
+
+    private val downDetector = layoutWithGestureDetector {
+        detectPressDownGesture(onDown = { down = true })
+    }
+
+    private val downUpDetector = layoutWithGestureDetector {
+        detectPressDownGesture(
+            onDown = { down = true },
+            onUp = { down = false }
+        )
+    }
+
+    private val nothingHandler: PointerInputChange.() -> Unit = {}
+
+    private var initialPass: PointerInputChange.() -> Unit = nothingHandler
+    private var finalPass: PointerInputChange.() -> Unit = nothingHandler
+
+    @Before
+    fun setup() {
+        down = null
+    }
+
+    private fun layoutWithGestureDetector(
+        gestureDetector: suspend PointerInputScope.() -> Unit,
+    ): @Composable () -> Unit = {
+        CompositionLocalProvider(
+            LocalDensity provides Density(1f),
+            LocalViewConfiguration provides TestViewConfiguration(
+                minimumTouchTargetSize = DpSize.Zero
+            )
+        ) {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        // Some tests execute a lambda before the initial and final passes
+                        // so they are called here, higher up the chain, so that the
+                        // calls happen prior to the gestureDetector below. The lambdas
+                        // do things like consume events on the initial pass or validate
+                        // consumption on the final pass.
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent(PointerEventPass.Initial)
+                                    event.changes.forEach {
+                                        initialPass(it)
+                                    }
+                                    awaitPointerEvent(PointerEventPass.Final)
+                                    event.changes.forEach {
+                                        finalPass(it)
+                                    }
+                                }
+                            }
+                        }
+                        .wrapContentSize(AbsoluteAlignment.TopLeft)
+                        .size(10.toDp())
+                        .pointerInput(gestureDetector, gestureDetector)
+                        .testTag(TargetTag)
+                )
+            }
+        }
+    }
+
+    private fun performTouch(
+        initialPass: PointerInputChange.() -> Unit = nothingHandler,
+        finalPass: PointerInputChange.() -> Unit = nothingHandler,
+        block: TouchInjectionScope.() -> Unit
+    ) {
+        this.initialPass = initialPass
+        this.finalPass = finalPass
+        rule.onNodeWithTag(TargetTag).performTouchInput(block)
+        rule.waitForIdle()
+        this.initialPass = nothingHandler
+        this.finalPass = nothingHandler
+    }
+
+    @Test
+    fun normalDown() {
+        rule.setContent(downUpDetector)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        Assert.assertTrue(down!!)
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        Assert.assertFalse(down!!)
+    }
+
+    @Test
+    fun normalDown_upNotSpecified() {
+        rule.setContent(downDetector)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        Assert.assertTrue(down!!)
+    }
+
+    @Test
+    fun consumedDown() {
+        rule.setContent(downUpDetector)
+
+        performTouch(initialPass = { consume() }) { down(0, Offset(5f, 5f)) }
+
+        Assert.assertTrue(down!!)
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) { up(0) }
+
+        Assert.assertFalse(down!!)
+    }
+
+    @Test
+    fun downLongPress() {
+        rule.setContent(downUpDetector)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        Assert.assertTrue(down!!)
+
+        rule.mainClock.advanceTimeBy(10_000L)
+
+        Assert.assertTrue(down!!)
+
+        rule.mainClock.advanceTimeBy(500)
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        Assert.assertFalse(down!!)
+    }
+
+    @Test
+    fun downAndMoveOut() {
+        rule.setContent(downUpDetector)
+
+        performTouch {
+            down(0, Offset(5f, 5f))
+            moveTo(0, Offset(15f, 15f))
+        }
+
+        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        Assert.assertFalse(down!!)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapTest.kt
new file mode 100644
index 0000000..6a0dedb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapTest.kt
@@ -0,0 +1,452 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.AbsoluteAlignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val TargetTag = "TargetLayout"
+
+/**
+ * These tests are mostly copied from TapGestureDetectorTest.
+ */
+@RunWith(JUnit4::class)
+class TapAndDoubleTapTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var tapped = false
+    private var doubleTapped = false
+
+    /** The time before a long press gesture attempts to win. */
+    private val LongPressTimeoutMillis: Long = 500L
+
+    /**
+     * The maximum time from the start of the first tap to the start of the second
+     * tap in a double-tap gesture.
+     */
+    private val DoubleTapTimeoutMillis: Long = 300L
+
+    private val util = layoutWithGestureDetector {
+        detectTapAndDoubleTap(
+            onTap = {
+                tapped = true
+            }
+        )
+    }
+
+    private val utilWithDoubleTap = layoutWithGestureDetector {
+        detectTapAndDoubleTap(
+            onTap = {
+                tapped = true
+            },
+            onDoubleTap = {
+                doubleTapped = true
+            }
+        )
+    }
+
+    private val nothingHandler: PointerInputChange.() -> Unit = {}
+
+    private var initialPass: PointerInputChange.() -> Unit = nothingHandler
+    private var finalPass: PointerInputChange.() -> Unit = nothingHandler
+
+    @Before
+    fun setup() {
+        tapped = false
+        doubleTapped = false
+    }
+
+    private fun layoutWithGestureDetector(
+        gestureDetector: suspend PointerInputScope.() -> Unit,
+    ): @Composable () -> Unit = {
+        CompositionLocalProvider(
+            LocalDensity provides Density(1f),
+            LocalViewConfiguration provides TestViewConfiguration(
+                minimumTouchTargetSize = DpSize.Zero
+            )
+        ) {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        // Some tests execute a lambda before the initial and final passes
+                        // so they are called here, higher up the chain, so that the
+                        // calls happen prior to the gestureDetector below. The lambdas
+                        // do things like consume events on the initial pass or validate
+                        // consumption on the final pass.
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent(PointerEventPass.Initial)
+                                    event.changes.forEach {
+                                        initialPass(it)
+                                    }
+                                    awaitPointerEvent(PointerEventPass.Final)
+                                    event.changes.forEach {
+                                        finalPass(it)
+                                    }
+                                }
+                            }
+                        }
+                        .wrapContentSize(AbsoluteAlignment.TopLeft)
+                        .size(10.toDp())
+                        .pointerInput(gestureDetector, gestureDetector)
+                        .testTag(TargetTag)
+                )
+            }
+        }
+    }
+
+    private fun performTouch(
+        initialPass: PointerInputChange.() -> Unit = nothingHandler,
+        finalPass: PointerInputChange.() -> Unit = nothingHandler,
+        block: TouchInjectionScope.() -> Unit
+    ) {
+        this.initialPass = initialPass
+        this.finalPass = finalPass
+        rule.onNodeWithTag(TargetTag).performTouchInput(block)
+        rule.waitForIdle()
+        this.initialPass = nothingHandler
+        this.finalPass = nothingHandler
+    }
+
+    @Test
+    fun normalTap() {
+        rule.setContent(util)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        assertFalse(tapped)
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            up(0)
+        }
+
+        assertTrue(tapped)
+    }
+
+    @Test
+    fun normalTapWithDoubleTapDefined_butNotExecuted() {
+        rule.setContent(utilWithDoubleTap)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            up(0)
+        }
+
+        // we don't wait for double tap, tap should be called immediately.
+        assertTrue(tapped)
+        assertFalse(doubleTapped)
+
+        rule.mainClock.advanceTimeBy(DoubleTapTimeoutMillis + 10)
+
+        assertTrue(tapped)
+        assertFalse(doubleTapped)
+    }
+
+    @Test
+    fun normalDoubleTap() {
+        rule.setContent(utilWithDoubleTap)
+
+        performTouch { down(0, Offset(5f, 5f)) }
+        performTouch(finalPass = { assertTrue(isConsumed) }) { up(0) }
+
+        assertTrue(tapped)
+        assertFalse(doubleTapped)
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch { down(0, Offset(5f, 5f)) }
+        performTouch(finalPass = { assertTrue(isConsumed) }) { up(0) }
+
+        assertTrue(tapped)
+        assertTrue(doubleTapped)
+    }
+
+    @Test
+    fun normalLongPress() {
+        rule.setContent(utilWithDoubleTap)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+        }
+
+        assertFalse(tapped)
+        assertFalse(doubleTapped)
+
+        rule.mainClock.advanceTimeBy(LongPressTimeoutMillis + 10)
+
+        assertFalse(tapped)
+        assertFalse(doubleTapped)
+
+        rule.mainClock.advanceTimeBy(500)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+        assertFalse(doubleTapped)
+    }
+
+    /**
+     * Pressing in the region, sliding out and then lifting should result in
+     * the callback not being invoked
+     */
+    @Test
+    fun tapMiss() {
+        rule.setContent(util)
+
+        performTouch {
+            down(0, Offset(5f, 5f))
+            moveTo(0, Offset(15f, 15f))
+        }
+
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+    }
+
+    /**
+     * Pressing in the region, sliding out and then lifting should result in
+     * the callback not being invoked
+     */
+    @Test
+    fun longPressMiss() {
+        rule.setContent(utilWithDoubleTap)
+
+        performTouch {
+            down(0, Offset(5f, 5f))
+            moveTo(0, Offset(15f, 15f))
+        }
+
+        rule.mainClock.advanceTimeBy(LongPressTimeoutMillis + 10)
+
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+        assertFalse(doubleTapped)
+    }
+
+    /**
+     * Pressing in the region, sliding out and then lifting should result in
+     * the callback not being invoked for double-tap
+     */
+    @Test
+    fun doubleTapMiss() {
+        rule.setContent(utilWithDoubleTap)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+            up(0)
+        }
+
+        assertTrue(tapped)
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch {
+            down(1, Offset(5f, 5f))
+            moveTo(1, Offset(15f, 15f))
+        }
+
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(1)
+        }
+
+        assertTrue(tapped)
+        assertFalse(doubleTapped)
+    }
+
+    /**
+     * After a first tap, a second tap should also be detected.
+     */
+    @Test
+    fun secondTap() {
+        rule.setContent(util)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(5f, 5f))
+            up(0)
+        }
+
+        assertTrue(tapped)
+
+        tapped = false
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(1, Offset(4f, 4f))
+            up(1)
+        }
+
+        assertTrue(tapped)
+    }
+
+    /**
+     * Clicking in the region with the up already consumed should result in the callback not
+     * being invoked.
+     */
+    @Test
+    fun consumedUpTap() {
+        rule.setContent(util)
+
+        performTouch {
+            down(0, Offset(5f, 5f))
+        }
+
+        assertFalse(tapped)
+
+        performTouch(initialPass = { if (pressed != previousPressed) consume() }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+    }
+
+    /**
+     * Clicking in the region with the motion consumed should result in the callback not
+     * being invoked.
+     */
+    @Test
+    fun consumedMotionTap() {
+        rule.setContent(util)
+
+        performTouch {
+            down(0, Offset(5f, 5f))
+        }
+
+        performTouch(initialPass = { consume() }) {
+            moveTo(0, Offset(6f, 2f))
+        }
+
+        rule.mainClock.advanceTimeBy(50)
+
+        performTouch {
+            up(0)
+        }
+
+        assertFalse(tapped)
+    }
+
+    /**
+     * Ensure that two-finger taps work.
+     */
+    @Test
+    fun twoFingerTap() {
+        rule.setContent(util)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            down(0, Offset(1f, 1f))
+        }
+
+        assertFalse(tapped)
+
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            down(1, Offset(9f, 5f))
+        }
+
+        assertFalse(tapped)
+
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+
+        performTouch(finalPass = { assertTrue(isConsumed) }) {
+            up(1)
+        }
+
+        assertTrue(tapped)
+    }
+
+    /**
+     * A position change consumption on any finger should cause tap to cancel.
+     */
+    @Test
+    fun twoFingerTapCancel() {
+        rule.setContent(util)
+
+        performTouch {
+            down(0, Offset(1f, 1f))
+        }
+        assertFalse(tapped)
+
+        performTouch {
+            down(1, Offset(9f, 5f))
+        }
+
+        performTouch(initialPass = { consume() }) {
+            moveTo(0, Offset(5f, 5f))
+        }
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(0)
+        }
+
+        assertFalse(tapped)
+
+        rule.mainClock.advanceTimeBy(50)
+        performTouch(finalPass = { assertFalse(isConsumed) }) {
+            up(1)
+        }
+
+        assertFalse(tapped)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
new file mode 100644
index 0000000..b2823e1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+class TextFieldClickToMoveCursorTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var state: TextFieldState
+
+    private val TAG = "BasicTextField2"
+
+    private val fontSize = 10.sp
+
+    private val defaultTextStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = fontSize)
+
+    @Test
+    fun emptyTextField_clinkOnCornersAndCenter() {
+        // this test is more about detecting a possible crash
+        state = TextFieldState()
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(50.dp)
+                    .height(15.dp)
+            )
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(center) }
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            performTouchInput { click(Offset(left + 1f, top + 1f)) } // topLeft
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            performTouchInput { click(Offset(right - 1f, top + 1f)) } // topRight
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            performTouchInput { click(Offset(left + 1f, bottom - 1f)) } // bottomLeft
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+            performTouchInput { click(Offset(right - 1f, bottom - 1f)) } // bottomRight
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+        }
+    }
+
+    @Test
+    fun clickOnText_ltr() {
+        state = TextFieldState("abc")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(50.dp)
+                    .height(15.dp)
+            )
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun clickOnText_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = defaultTextStyle,
+                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+                )
+            }
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun clickOnText_ltr_in_rtlLayout() {
+        state = TextFieldState("abc")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = defaultTextStyle,
+                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+                )
+            }
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+    }
+
+    @Test
+    fun clickOnText_rtl_in_ltrLayout() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+            )
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+    }
+
+    @Test
+    fun clickOnEmptyRegion_ltr() {
+        state = TextFieldState("abc")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+            )
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset((fontSize * 4).toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+    }
+
+    @Test
+    fun clickOnEmptyRegion_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = defaultTextStyle,
+                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+                )
+            }
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset(fontSize.toPx(), height / 2f)) }
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+    }
+
+    @Test
+    fun clickOnTextEdge_horizontalScrollable() {
+        state = TextFieldState("abcabcabcabc")
+        val scrollState = ScrollState(0)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(57.dp)
+                    .height(12.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(right - 1f, height / 2f))
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(6))
+            assertThat(scrollState.value).isGreaterThan(0)
+        }
+    }
+
+    @Test
+    fun clickOnTextEdge_verticalScrollable() {
+        state = TextFieldState("abc abc abc abc")
+        val scrollState = ScrollState(0)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(50.dp)
+                    .height(17.dp)
+            )
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performTouchInput { click(Offset(fontSize.toPx(), bottom - 1f)) }
+        }
+        rule.waitForIdle()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+        assertThat(scrollState.value).isGreaterThan(0)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
new file mode 100644
index 0000000..fe3827a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
@@ -0,0 +1,995 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.DefaultCursorThickness
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.text.selection.assertHandlePositionMatches
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.times
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+class TextFieldCursorHandleTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var state: TextFieldState
+
+    private val TAG = "BasicTextField2"
+
+    private val fontSize = 10.sp
+
+    private val fontSizePx = with(rule.density) { fontSize.toPx() }
+
+    private val fontSizeDp = with(rule.density) { fontSize.toDp() }
+
+    private val cursorWidth = DefaultCursorThickness
+
+    @Test
+    fun cursorHandle_showsAtCorrectLocation_ltr() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            (2 * fontSize.value).dp + cursorWidth / 2,
+            fontSize.value.dp
+        )
+    }
+
+    @Test
+    fun cursorHandle_hasMinimumTouchSizeArea() = with(rule.density) {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.width(100.dp).testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+
+        var actualBottomRight = Offset.Zero
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            actualBottomRight = bottomRight
+        }
+
+        val expectedBottomRight = Offset(40.dp.toPx(), 40.dp.toPx())
+        assertThat(actualBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
+        assertThat(actualBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
+    }
+
+    @Test
+    fun tapTextField_cursorHandleFiltered() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                inputTransformation = { _, valueWithChanges ->
+                    valueWithChanges.selectCharsIn(TextRange(4))
+                },
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_ltr() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            5 * fontSizeDp + cursorWidth / 2,
+            fontSizeDp
+        )
+    }
+
+    @Test
+    fun cursorHandle_showsAtCorrectLocation_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2\u05D3\u05D4")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSize.value.dp * 5)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            (2 * fontSize.value).dp + cursorWidth / 2,
+            fontSize.value.dp
+        )
+    }
+
+    @Test
+    fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_rtl() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            (5 * fontSize.value).dp + cursorWidth / 2,
+            fontSize.value.dp
+        )
+    }
+
+    @Test
+    fun cursorHandle_notVisibleOnEmptyField() {
+        state = TextFieldState()
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_doesNotShow_whenTextFieldIsReadOnly() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG),
+                readOnly = true
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_disappears_whenTextIsEdited() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        rule.onNodeWithTag(TAG).performTextInput("m")
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_disappears_whenTextStateChanges() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        state.setTextAndPlaceCursorAtEnd("hello2")
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_doesNotDisappear_whenSelectionChanges() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        state.edit { placeCursorBeforeCharAt(2) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            (2 * fontSize.value).dp + cursorWidth / 2,
+            fontSize.value.dp
+        )
+    }
+
+    @Test
+    fun cursorHandle_disappears_whenWindowLosesFocus() {
+        state = TextFieldState("hello")
+        val focusWindow = mutableStateOf(true)
+        val windowInfo = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focusWindow.value
+        }
+        rule.setContent {
+            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier.testTag(TAG)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_coercesAtBoundaries_ltr() {
+        state = TextFieldState("hello")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    // Make this TextField guaranteed to be wider than the text content
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick() // cursor handle appears
+        rule.runOnIdle {
+            state.edit { selectCharsIn(TextRange(0)) } // move cursor to the start of text
+        }
+
+        val characterSize = fontSizeDp // width and height is the same.
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            cursorWidth / 2,
+            characterSize
+        )
+
+        rule.runOnIdle {
+            state.edit { selectCharsIn(TextRange(5)) } // move cursor to the end of text
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            // Move 5 characters to right (5 * character), finally account for the center of
+            // cursor (cursorWidth / 2).
+            5 * characterSize + cursorWidth / 2,
+            fontSizeDp
+        )
+    }
+
+    @Test
+    fun cursorHandle_coercesAtBoundaries_rtl() = with(rule.density) {
+        state = TextFieldState("\u05D0\u05D1\u05D2\u05D3\u05D4")
+        var width = 0
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        // Make this TextField guaranteed to be wider than the text content
+                        .width(fontSizeDp * 10)
+                        .onSizeChanged { width = it.width }
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick() // cursor handle appears
+
+        rule.runOnIdle {
+            state.edit { selectCharsIn(TextRange(0)) } // move cursor to the start of text
+        }
+
+        val characterSize = fontSizeDp // width and height is the same.
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            width.toDp() - cursorWidth / 2, // Should align to the right
+            characterSize
+        )
+
+        rule.runOnIdle {
+            state.edit { selectCharsIn(TextRange(5)) } // move cursor to the end of text
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
+            // Start from right (width), move 5 characters to left (5 * character), finally account
+            // for the center of cursor (cursorWidth / 2).
+            width.toDp() - 5 * characterSize - cursorWidth / 2,
+            fontSizeDp
+        )
+    }
+
+    @Test
+    fun cursorHandle_disappearsOnVerticalScroll() {
+        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
+        val scrollState = ScrollState(0)
+        lateinit var scope: CoroutineScope
+        rule.setTextFieldTestContent {
+            scope = rememberCoroutineScope()
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                // scrollable but still only show maximum one line in its viewport
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 1),
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 5)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        scope.runBlockingOnIdle {
+            scrollState.scrollTo(scrollState.maxValue)
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_disappearsOnHorizontalScroll() {
+        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
+        val scrollState = ScrollState(0)
+        lateinit var scope: CoroutineScope
+        rule.setTextFieldTestContent {
+            scope = rememberCoroutineScope()
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                // scrollable but still only show maximum one line in its viewport
+                lineLimits = TextFieldLineLimits.SingleLine,
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        scope.runBlockingOnIdle {
+            scrollState.scrollTo(scrollState.maxValue)
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun cursorHandle_reappearsOnVerticalScroll() {
+        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
+        val scrollState = ScrollState(0)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                // scrollable but still only show maximum one line in its viewport
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 1),
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 5)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            swipeUp(endY = -bottom)
+        }
+        rule.waitForIdle()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            swipeDown(endY = 2 * bottom)
+        }
+        rule.waitForIdle()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+    }
+
+    @Test
+    fun cursorHandle_reappearsOnHorizontalScroll() {
+        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
+        val scrollState = ScrollState(0)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                // scrollable but still only show maximum one line in its viewport
+                lineLimits = TextFieldLineLimits.SingleLine,
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 5)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performClick()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft(endX = -right) }
+        rule.waitForIdle()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeRight(endX = 2 * right) }
+        rule.waitForIdle()
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+    }
+
+    @Test
+    fun cursorHandleDrag_getsFiltered() {
+        state = TextFieldState("abc abc")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                inputTransformation = { _, valueWithChanges ->
+                    valueWithChanges.selectCharsIn(TextRange.Zero)
+                },
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(fontSizePx * 5)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
+    }
+
+    // region ltr drag tests
+    @Test
+    fun moveCursorHandleToRight_ltr() {
+        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(fontSizePx)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_ltr() {
+        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(fontSizePx)
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        }
+    }
+
+    @Test
+    fun moveCursorHandleToRight_ltr_outOfBounds() {
+        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 5)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(getTextFieldWidth() * 2)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_ltr_outOfBounds() {
+        state = TextFieldState("abc", initialSelectionInChars = TextRange(3))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 5)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(getTextFieldWidth() * 2)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
+    }
+
+    @Test
+    fun moveCursorHandleToRight_ltr_outOfBounds_scrollable_continuesDrag() {
+        state = TextFieldState(
+            initialText = "abcd abcd abcd abcd abcd",
+            initialSelectionInChars = TextRange.Zero
+        )
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(getTextFieldWidth() * 3)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
+    }
+
+    @Test
+    fun moveCursorHandleToRight_ltr_outOfBounds_scrollable() {
+        state = TextFieldState(
+            initialText = "abcd abcd abcd abcd abcd",
+            initialSelectionInChars = TextRange.Zero
+        )
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(fontSizeDp * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(fontSizePx * 12, durationMillis = 1)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(12))
+        }
+
+        swipeToRight(fontSizePx * 2, durationMillis = 1)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(14))
+        }
+    }
+
+    // endregion
+
+    // region rtl drag tests
+    @Test
+    fun moveCursorHandleToRight_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 10)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(fontSizePx)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_rtl() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 10)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(fontSizePx)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+    }
+
+    @Test
+    fun moveCursorHandleToRight_rtl_outOfBounds() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 5)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(getTextFieldWidth() * 2)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_rtl_outOfBounds() {
+        state = TextFieldState("\u05D0\u05D1\u05D2")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 5)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(getTextFieldWidth() * 2)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_rtl_outOfBounds_scrollable_continuesDrag() {
+        state = TextFieldState(
+            "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3"
+        )
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    lineLimits = TextFieldLineLimits.SingleLine,
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 10)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(getTextFieldWidth() * 3)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
+    }
+
+    @Test
+    fun moveCursorHandleToLeft_rtl_outOfBounds_scrollable() {
+        val scrollState = ScrollState(0)
+        state = TextFieldState(
+            initialText = "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3",
+            initialSelectionInChars = TextRange.Zero
+        )
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    lineLimits = TextFieldLineLimits.SingleLine,
+                    scrollState = scrollState,
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(fontSizeDp * 10f)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(topRight) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToLeft(fontSizePx * 12, durationMillis = 1)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(12))
+        }
+
+        swipeToLeft(fontSizePx * 2, durationMillis = 1)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(14))
+        }
+    }
+
+    // endregion
+
+    private fun focusAndWait() {
+        rule.onNode(hasSetTextAction()).requestFocus()
+    }
+
+    private fun swipeToLeft(swipeDistance: Float, durationMillis: Long = 1000) =
+        performHandleDrag(Handle.Cursor, true, swipeDistance, durationMillis)
+
+    private fun swipeToRight(swipeDistance: Float, durationMillis: Long = 1000) =
+        performHandleDrag(Handle.Cursor, false, swipeDistance, durationMillis)
+
+    private fun performHandleDrag(
+        handle: Handle,
+        toLeft: Boolean,
+        swipeDistance: Float = 1f,
+        durationMillis: Long = 1000
+    ) {
+        val handleNode = rule.onNode(isSelectionHandle(handle))
+
+        handleNode.performTouchInput {
+            if (toLeft) {
+                swipeLeft(
+                    startX = centerX,
+                    endX = centerX - viewConfiguration.touchSlop - swipeDistance,
+                    durationMillis = durationMillis
+                )
+            } else {
+                swipeRight(
+                    startX = centerX,
+                    endX = centerX + viewConfiguration.touchSlop + swipeDistance,
+                    durationMillis = durationMillis
+                )
+            }
+        }
+    }
+
+    private fun getTextFieldWidth() = rule.onNodeWithTag(TAG)
+        .fetchSemanticsNode()
+        .boundsInRoot.width
+
+    private fun CoroutineScope.runBlockingOnIdle(block: suspend CoroutineScope.() -> Unit) {
+        val job = rule.runOnIdle {
+            launch(block = block)
+        }
+        runBlocking { job.join() }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
new file mode 100644
index 0000000..3134afe
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
@@ -0,0 +1,630 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.foundation.text.selection.FakeTextToolbar
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Tests for long click interactions on BasicTextField2.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+class TextFieldLongPressTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val TAG = "BasicTextField2"
+
+    private val fontSize = 10.sp
+
+    private val defaultTextStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = fontSize)
+
+    @Test
+    fun emptyTextField_longPressDoesNotShowCursor() {
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = rememberTextFieldState(),
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { longClick() }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+    }
+
+    @Test
+    fun longPress_requestsFocus_beforePointerIsReleased() {
+        val state = TextFieldState("Hello, World!")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(center)
+        }
+
+        rule.onNodeWithTag(TAG).assertIsFocused()
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+    }
+
+    @Test
+    fun longPressOnEmptyRegion_showsCursorAtTheEnd() {
+        val state = TextFieldState("abc")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+    }
+
+    @Test
+    fun longPressOnEmptyRegion_showsTextToolbar() {
+        val state = TextFieldState("abc")
+        var showMenuCalled = 0
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, _, _ ->
+                showMenuCalled++
+            }, onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager("hello")
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(
+                LocalTextToolbar provides textToolbar,
+                LocalClipboardManager provides clipboardManager
+            ) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = defaultTextStyle,
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(100.dp)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
+        }
+
+        rule.runOnIdle {
+            assertThat(showMenuCalled).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun longPressOnWord_selectsWord() {
+        val state = TextFieldState("abc def ghi")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
+    }
+
+    @Test
+    fun longPressOnWhitespace_doesNotSelectWhitespace() {
+        val state = TextFieldState("abc def ghi")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(Offset(fontSize.toPx() * 7.5f, fontSize.toPx() / 2))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+        assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7, 8))
+        assertThat(state.text.selectionInChars.collapsed).isFalse()
+    }
+
+    @Test
+    fun longPressOnScrolledTextField_selectsWord() {
+        val state = TextFieldState("abc def ghi abc def ghi")
+        val scrollState = ScrollState(0)
+        lateinit var scope: CoroutineScope
+        rule.setTextFieldTestContent {
+            scope = rememberCoroutineScope()
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                scrollState = scrollState,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(30.dp)
+            )
+        }
+
+        assertThat(scrollState.maxValue).isGreaterThan(0)
+        scope.launch { scrollState.scrollTo(scrollState.maxValue) }
+
+        rule.onNodeWithTag(TAG).performTouchInput { longClick(centerRight) }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(20, 23))
+    }
+
+    @Test
+    fun longPressOnDecoratedTextField_selectsWord() {
+        val state = TextFieldState("abc def ghi")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG),
+                decorator = {
+                    Box(modifier = Modifier.padding(32.dp)) {
+                        it()
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(
+                Offset(
+                    x = 32.dp.toPx() + fontSize.toPx() * 5f,
+                    y = 32.dp.toPx() + fontSize.toPx() / 2
+                )
+            )
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
+    }
+
+    @Test
+    fun longPress_dragToRight_selectsCurrentAndNextWord_ltr() {
+        val state = TextFieldState("abc def ghi")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(fontSize.toPx() * 3f, 0f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
+    }
+
+    @Test
+    fun longPress_dragToLeft_selectsCurrentAndPreviousWord_ltr() {
+        val state = TextFieldState("abc def ghi")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(-fontSize.toPx() * 3f, 0f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
+    }
+
+    @Test
+    fun longPress_dragDown_selectsFromCurrentToTargetWord_ltr() {
+        val state = TextFieldState("abc def\nabc def\nabc def")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(0f, fontSize.toPx()))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 15))
+    }
+
+    @Test
+    fun longPress_dragUp_selectsFromCurrentToTargetWord_ltr() {
+        val state = TextFieldState("abc def\nabc def\nabc def")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() * 3 / 2)) // second line, def
+            moveBy(Offset(0f, -fontSize.toPx()))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 15))
+    }
+
+    @Test
+    fun longPress_startingFromEndPadding_dragToLeft_selectsLastWord_ltr() {
+        val state = TextFieldState("abc def")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(centerRight)
+            moveTo(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
+    }
+
+    //region RTL
+
+    @Test
+    fun longPress_dragToRight_selectsCurrentAndPreviousWord_rtl() {
+        val state = TextFieldState(rtlText3)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(fontSize.toPx() * 3f, 0f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
+    }
+
+    @Test
+    fun longPress_dragToLeft_selectsCurrentAndNextWord_rtl() {
+        val state = TextFieldState(rtlText3)
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(-fontSize.toPx() * 3f, 0f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
+    }
+
+    @Test
+    fun longPress_dragDown_selectsFromCurrentToTargetWord_rtl() {
+        val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
+            moveBy(Offset(0f, fontSize.toPx()))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 11))
+    }
+
+    @Test
+    fun longPress_dragUp_selectsFromCurrentToTargetWord_rtl() {
+        val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() * 3 / 2))
+            moveBy(Offset(0f, -fontSize.toPx()))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 11))
+    }
+
+    @Test
+    fun longPress_startingFromEndPadding_dragToRight_selectsLastWord_rtl() {
+        val state = TextFieldState(rtlText2)
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = defaultTextStyle,
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(100.dp)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(centerLeft)
+            moveTo(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2f))
+            up()
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
+    }
+
+    @Test
+    fun longPress_startDraggingToScrollRight_startHandleDoesNotShow_ltr() {
+        val state = TextFieldState("abc def ghi ".repeat(10))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(center)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx(), fontSize.toPx() / 2))
+            moveBy(Offset(fontSize.toPx() * 30, 0f))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+
+        // slightly back a little bit so that selection seems to be collapsing but the acting
+        // handle should remain the same
+        rule.onNodeWithTag(TAG).performTouchInput {
+            moveBy(Offset(-fontSize.toPx(), 0f))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+    }
+
+    @Test
+    fun longPress_startDraggingToScrollDown_startHandleDoesNotShow_ltr() {
+        val state = TextFieldState("abc def ghi ".repeat(10))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.MultiLine(1, 3),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(center)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(fontSize.toPx(), fontSize.toPx() / 2))
+            moveBy(Offset(0f, fontSize.toPx() * 30))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+
+        // slightly back a little bit so that selection seems to be collapsing but the acting
+        // handle should remain the same
+        rule.onNodeWithTag(TAG).performTouchInput {
+            moveBy(Offset(0f, -fontSize.toPx()))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+    }
+
+    @Test
+    fun longPress_startDraggingToScrollLeft_endHandleDoesNotShow_ltr() {
+        val state = TextFieldState("abc def ghi ".repeat(10))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(center)
+            // swipe to the absolute right by specifying a huge swipe delta
+            swipeLeft(endX = -10000f)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(right - 1f, centerY))
+            moveBy(Offset(-fontSize.toPx() * 30, 0f))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
+
+        // slightly back a little bit so that selection seems to be collapsing but the acting
+        // handle should remain the same
+        rule.onNodeWithTag(TAG).performTouchInput {
+            moveBy(Offset(fontSize.toPx(), 0f))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
+    }
+
+    @Test
+    fun longPress_startDraggingToScrollUp_endHandleDoesNotShow_ltr() {
+        val state = TextFieldState("abc def ghi ".repeat(10))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state = state,
+                textStyle = defaultTextStyle,
+                lineLimits = TextFieldLineLimits.MultiLine(1, 3),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(center)
+            // swipe to the absolute bottom by specifying a huge swipe delta
+            swipeUp(endY = -10000f)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(centerX, bottom - 1f))
+            moveBy(Offset(0f, -fontSize.toPx() * 30))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
+
+        // slightly back a little bit so that selection seems to be collapsing but the acting
+        // handle should remain the same
+        rule.onNodeWithTag(TAG).performTouchInput {
+            moveBy(Offset(0f, fontSize.toPx()))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
+    }
+
+    //endregion
+
+    companion object {
+        private const val rtlText2 = "\u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5"
+        private const val rtlText3 = "\u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5 \u05D6\u05D7\u05D8"
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt
new file mode 100644
index 0000000..1399054
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt
@@ -0,0 +1,454 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import android.view.DragEvent
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.internal.DragAndDropTestUtils.makeImageDragEvent
+import androidx.compose.foundation.text.input.internal.DragAndDropTestUtils.makeTextDragEvent
+import androidx.compose.foundation.text.selection.AbstractSelectionMagnifierTests
+import androidx.compose.foundation.text.selection.assertMagnifierExists
+import androidx.compose.foundation.text.selection.assertNoMagnifierExists
+import androidx.compose.foundation.text.selection.getMagnifierCenterOffset
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.toSize
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@MediumTest
+@SdkSuppress(minSdkVersion = 28)
+@RunWith(AndroidJUnit4::class)
+internal class TextFieldMagnifierTest : AbstractSelectionMagnifierTests() {
+
+    @Composable
+    override fun TestContent(
+        text: String,
+        modifier: Modifier,
+        style: TextStyle,
+        onTextLayout: (TextLayoutResult) -> Unit,
+        maxLines: Int
+    ) {
+        val state = remember { TextFieldState(text) }
+        BasicTextField2(
+            state = state,
+            modifier = modifier,
+            textStyle = style,
+            onTextLayout = { it()?.let(onTextLayout) }
+        )
+    }
+
+    @Test
+    fun magnifier_followsCursorHorizontally_whenDragged() {
+        checkMagnifierFollowsHandleHorizontally(Handle.Cursor)
+    }
+
+    @Test
+    fun magnifier_staysAtLineEnd_whenCursorDraggedPastStart() {
+        checkMagnifierConstrainedToLineHorizontalBounds(
+            Handle.Cursor,
+            checkStart = true
+        )
+    }
+
+    @Test
+    fun magnifier_staysAtLineEnd_whenCursorDraggedPastEnd() {
+        checkMagnifierConstrainedToLineHorizontalBounds(
+            Handle.Cursor,
+            checkStart = false
+        )
+    }
+
+    @Test
+    fun magnifier_hidden_whenCursorDraggedFarPastStartOfLine() {
+        checkMagnifierHiddenWhenDraggedTooFar(Handle.Cursor, checkStart = true)
+    }
+
+    @Test
+    fun magnifier_hidden_whenCursorDraggedFarPastEndOfLine() {
+        checkMagnifierHiddenWhenDraggedTooFar(Handle.Cursor, checkStart = false)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenCursorDraggedPastScrollThreshold_Ltr() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.Cursor, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenCursorDraggedPastScrollThreshold_Rtl() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.Cursor, LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenSelectionStartDraggedPastScrollThreshold_Ltr() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionStart, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenSelectionStartDraggedPastScrollThreshold_Rtl() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionStart, LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenSelectionEndDraggedPastScrollThreshold_Ltr() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionEnd, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun magnifier_staysAtVisibleRegion_whenSelectionEndDraggedPastScrollThreshold_Rtl() {
+        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionEnd, LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun magnifier_shows_whenTextIsDraggingFromAnotherApp() {
+        val view = setupDragAndDropContent()
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(40f, 10f)
+            )
+
+            view.dispatchDragEvent(startEvent)
+            view.dispatchDragEvent(enterEvent)
+            view.dispatchDragEvent(moveEvent)
+        }
+
+        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(Offset(40f, 10f))
+    }
+
+    @Test
+    fun magnifier_doesNotShow_ifDraggingItem_doesNotHaveText() {
+        val view = setupDragAndDropContent()
+
+        rule.runOnIdle {
+            val startEvent = makeImageDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeImageDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeImageDragEvent(
+                DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(40f, 10f)
+            )
+
+            view.dispatchDragEvent(startEvent)
+            view.dispatchDragEvent(enterEvent)
+            view.dispatchDragEvent(moveEvent)
+        }
+
+        assertNoMagnifierExists(rule)
+    }
+
+    @Test
+    fun magnifier_doesNotLinger_whenDraggingItemLeaves() {
+        val view = setupDragAndDropContent()
+
+        rule.runOnIdle {
+            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
+            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
+            val moveEvent = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(40f, 10f)
+            )
+
+            view.dispatchDragEvent(startEvent)
+            view.dispatchDragEvent(enterEvent)
+            view.dispatchDragEvent(moveEvent)
+        }
+
+        assertMagnifierExists(rule)
+
+        rule.runOnIdle {
+            val moveEvent2 = makeTextDragEvent(
+                action = DragEvent.ACTION_DRAG_LOCATION,
+                offset = Offset(40f, 40f) // force it out of BTF2's hit box
+            )
+            view.dispatchDragEvent(moveEvent2)
+        }
+
+        assertNoMagnifierExists(rule)
+    }
+
+    @Test
+    fun magnifier_insideDecorationBox() {
+        val tag = "BasicTextField2"
+        val state = TextFieldState(
+            "aaaa",
+            initialSelectionInChars = TextRange.Zero
+        )
+
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
+                BasicTextField2(
+                    state = state,
+                    Modifier.testTag(tag),
+                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                    lineLimits = TextFieldLineLimits.SingleLine,
+                    decorator = {
+                        Box(modifier = Modifier.padding(8.dp)) {
+                            it()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(tag).performTouchInput {
+            click(topLeft)
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            down(center)
+            movePastSlopBy(Offset(-0.1f, 0.1f))
+        }
+
+        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
+            Offset(0f, 10f) + Offset(8f, 8f)
+        )
+    }
+
+    @Test
+    fun magnifier_insideDecorationBox_scrolledVertically() {
+        val tag = "BasicTextField2"
+        val state = TextFieldState(
+            "aaaa\naaaa\naaaa\n".repeat(5),
+            initialSelectionInChars = TextRange.Zero
+        )
+        val scrollState = ScrollState(0)
+        var coroutineScope: CoroutineScope? = null
+
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
+                coroutineScope = rememberCoroutineScope()
+                BasicTextField2(
+                    state = state,
+                    Modifier.testTag(tag),
+                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                    lineLimits = TextFieldLineLimits.MultiLine(1, 2),
+                    scrollState = scrollState,
+                    decorator = {
+                        Box(modifier = Modifier.padding(8.dp)) {
+                            it()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        coroutineScope?.launch {
+            scrollState.scrollTo(scrollState.maxValue)
+        }
+
+        rule.onNodeWithTag(tag).performTouchInput {
+            click(bottomLeft)
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            down(center)
+            movePastSlopBy(Offset(0.1f, 0.1f))
+        }
+
+        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
+            Offset(0f, 30f) + Offset(8f, 8f)
+        )
+    }
+
+    @Test
+    fun magnifier_insideDecorationBox_scrolledHorizontally() {
+        val tag = "BasicTextField2"
+        val state = TextFieldState(
+            "aaaa aaaa aaaa ".repeat(5),
+            initialSelectionInChars = TextRange.Zero
+        )
+        val scrollState = ScrollState(0)
+        var coroutineScope: CoroutineScope? = null
+
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
+                coroutineScope = rememberCoroutineScope()
+                BasicTextField2(
+                    state = state,
+                    Modifier.testTag(tag).width(100.dp),
+                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                    lineLimits = TextFieldLineLimits.SingleLine,
+                    scrollState = scrollState,
+                    decorator = {
+                        Box(modifier = Modifier.padding(8.dp)) {
+                            it()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        coroutineScope?.launch {
+            scrollState.scrollTo(scrollState.maxValue)
+        }
+
+        rule.onNodeWithTag(tag).performTouchInput {
+            click(centerRight)
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            down(center)
+            movePastSlopBy(Offset(0.1f, 0.1f))
+        }
+
+        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
+            // x: drag threshold, y: line center(2nd line in view) + x: padding, y: padding
+            Offset(100f - 16f, 10f) + Offset(8f, 8f)
+        )
+    }
+
+    @OptIn(ExperimentalTestApi::class, ExperimentalFoundationApi::class)
+    private fun checkMagnifierStayAtEndWhenDraggedBeyondScroll(
+        handle: Handle,
+        layoutDirection: LayoutDirection = LayoutDirection.Ltr
+    ) {
+        var screenSize = Size.Zero
+        val dragDirection = if (layoutDirection == LayoutDirection.Rtl) -1f else 1f
+        val directionVector = Offset(1f, 0f) * dragDirection
+        val fillerWord = if (layoutDirection == LayoutDirection.Ltr)
+            "aaaa"
+        else
+            "\u05D0\u05D1\u05D2\u05D3"
+
+        val tag = "BasicTextField2"
+        val state = TextFieldState(
+            "$fillerWord $fillerWord $fillerWord ".repeat(10),
+            initialSelectionInChars = TextRange.Zero
+        )
+
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                BasicTextField2(
+                    state = state,
+                    Modifier
+                        .fillMaxWidth()
+                        .onSizeChanged { screenSize = it.toSize() }
+                        .wrapContentSize()
+                        .testTag(tag),
+                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY),
+                    lineLimits = TextFieldLineLimits.SingleLine
+                )
+            }
+        }
+
+        if (handle == Handle.Cursor) {
+            rule.onNodeWithTag(tag).performClick()
+        } else {
+            rule.onNodeWithTag(tag).performTextInputSelection(TextRange(5, 9))
+        }
+
+        // Touch and move the handle to show the magnifier.
+        rule.onNode(isSelectionHandle(handle)).performTouchInput {
+            down(center)
+            // If cursor, we have to drag the cursor to show the magnifier,
+            // press alone will not suffice
+            movePastSlopBy(directionVector)
+        }
+
+        val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
+
+        // Drag all the way past the end of the line.
+        rule.onNode(isSelectionHandle(handle))
+            .performTouchInput {
+                val delta = Offset(
+                    x = screenSize.width * directionVector.x,
+                    y = screenSize.height * directionVector.y
+                )
+                moveBy(delta)
+            }
+
+        val x = if (layoutDirection == LayoutDirection.Ltr) screenSize.width else 0f
+        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
+            Offset(x, magnifierInitialPosition.y)
+        )
+    }
+
+    private fun setupDragAndDropContent(): View {
+        val state = TextFieldState(
+            "aaaa",
+            initialSelectionInChars = TextRange.Zero
+        )
+        var view: View? = null
+        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
+            view = LocalView.current
+            CompositionLocalProvider(
+                LocalDensity provides Density(1f, 1f),
+                LocalWindowInfo provides object : WindowInfo {
+                    override val isWindowFocused = false
+                }
+            ) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
+                    lineLimits = TextFieldLineLimits.SingleLine
+                )
+            }
+        }
+
+        return view!!
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
new file mode 100644
index 0000000..f454f9a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
@@ -0,0 +1,886 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import android.os.Build
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.selection.SelectionHandleAnchor
+import androidx.compose.foundation.text.selection.assertHandleAnchorMatches
+import androidx.compose.foundation.text.selection.assertHandlePositionMatches
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.doubleClick
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+class TextFieldSelectionHandlesTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var state: TextFieldState
+
+    private val TAG = "BasicTextField2"
+
+    private val fontSize = 10.sp
+    private val fontSizePx = with(rule.density) { fontSize.toPx() }
+
+    @Test
+    fun selectionHandles_doNotShow_whenFieldNotFocused() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        assertHandlesNotExist()
+    }
+
+    @Test
+    fun selectionHandles_haveMinimumTouchSizeArea() = with(rule.density) {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        rule.setContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+
+        var actualStartBottomRight = Offset.Zero
+        var actualEndBottomRight = Offset.Zero
+        rule.onNode(isSelectionHandle(Handle.SelectionStart)).performTouchInput {
+            actualStartBottomRight = bottomRight
+        }
+        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).performTouchInput {
+            actualEndBottomRight = bottomRight
+        }
+
+        val expectedBottomRight = Offset(40.dp.toPx(), 40.dp.toPx())
+        assertThat(actualStartBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
+        assertThat(actualStartBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
+        assertThat(actualEndBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
+        assertThat(actualEndBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
+    }
+
+    @Test
+    fun selectionHandles_appears_whenFieldGetsFocused() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+    }
+
+    @Test
+    fun selectionHandles_disappear_whenFieldLosesFocus() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        val focusRequester = FocusRequester()
+        rule.setTextFieldTestContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(100.dp)
+                        .focusRequester(focusRequester)
+                        .focusable())
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(100.dp)
+                )
+            }
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+        assertHandlesNotExist()
+    }
+
+    @Test
+    fun textField_noSelectionHandles_whenWindowLosesFocus() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        val focusWindow = mutableStateOf(true)
+        val windowInfo = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focusWindow.value
+        }
+
+        rule.setContent {
+            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier.testTag(TAG).width(100.dp)
+                )
+            }
+        }
+
+        // selection handles displayed
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        // window lost focus, make sure handles disappeared
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        assertHandlesNotExist()
+    }
+
+    @Test
+    fun textField_redisplaysSelectionHandlesAndToolbar_whenWindowRegainsFocus() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        val focusWindow = mutableStateOf(true)
+        val windowInfo = object : WindowInfo {
+            override val isWindowFocused: Boolean
+                get() = focusWindow.value
+        }
+
+        rule.setContent {
+            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier.testTag(TAG).width(100.dp)
+                )
+            }
+        }
+
+        // selection handles displayed
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        // window lost focus, make sure handles disappeared
+        focusWindow.value = false
+        rule.waitForIdle()
+
+        assertHandlesNotExist()
+
+        // regain window focus
+        focusWindow.value = true
+        rule.waitForIdle()
+
+        assertHandlesDisplayed()
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun selectionHandles_locatedAtTheRightPosition_ltr_ltr() {
+        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+
+        with(rule.onNode(isSelectionHandle(Handle.SelectionStart))) {
+            assertHandlePositionMatches(
+                (2 * fontSize.value).dp,
+                fontSize.value.dp
+            )
+            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
+        }
+
+        with(rule.onNode(isSelectionHandle(Handle.SelectionEnd))) {
+            assertHandlePositionMatches(
+                (5 * fontSize.value).dp,
+                fontSize.value.dp
+            )
+            assertHandleAnchorMatches(SelectionHandleAnchor.Right)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun selectionHandles_locatedAtTheRightPosition_ltr_rtl() {
+        state = TextFieldState("abc \u05D0\u05D1\u05D2", initialSelectionInChars = TextRange(1, 6))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+
+        with(rule.onNode(isSelectionHandle(Handle.SelectionStart))) {
+            assertHandlePositionMatches(
+                (1 * fontSize.value).dp,
+                fontSize.value.dp
+            )
+            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
+        }
+
+        with(rule.onNode(isSelectionHandle(Handle.SelectionEnd))) {
+            assertHandlePositionMatches(
+                (5 * fontSize.value).dp,
+                fontSize.value.dp
+            )
+            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
+        }
+    }
+
+    @Test
+    fun selectionHandlesDisappear_whenScrolledOutOfView_horizontally() {
+        // make it scrollable
+        state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft() }
+        assertHandlesNotExist()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeRight() }
+        assertHandlesDisplayed()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+    }
+
+    @Test
+    fun selectionHandlesDisappear_whenScrolledOutOfView_vertically() {
+        // make it scrollable
+        state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            swipeUp()
+        }
+        assertHandlesNotExist()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            swipeDown()
+        }
+        assertHandlesDisplayed()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+    }
+
+    @Test
+    fun selectionHandlesDisappear_whenScrolledOutOfView_horizontally_inContainer() {
+        // make it scrollable
+        val containerTag = "container"
+        state = TextFieldState("hello", initialSelectionInChars = TextRange(1, 2))
+        rule.setTextFieldTestContent {
+            Row(modifier = Modifier
+                .width(200.dp)
+                .horizontalScroll(rememberScrollState())
+                .testTag(containerTag)
+            ) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .width(100.dp)
+                )
+                Box(modifier = Modifier
+                    .height(12.dp)
+                    .width(400.dp))
+            }
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        rule.onNodeWithTag(containerTag).performTouchInput {
+            swipeLeft()
+        }
+        assertHandlesNotExist()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+
+        rule.onNodeWithTag(containerTag).performTouchInput {
+            swipeRight()
+        }
+        assertHandlesDisplayed()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+    }
+
+    @Test
+    fun selectionHandlesDisappear_whenScrolledOutOfView_vertically_inContainer() {
+        // make it scrollable
+        val containerTag = "container"
+        state = TextFieldState("hello", initialSelectionInChars = TextRange(1, 2))
+        rule.setTextFieldTestContent {
+            Column(modifier = Modifier
+                .height(200.dp)
+                .verticalScroll(rememberScrollState())
+                .testTag(containerTag)
+            ) {
+                BasicTextField2(
+                    state,
+                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                    modifier = Modifier
+                        .testTag(TAG)
+                        .height(100.dp)
+                )
+                Box(modifier = Modifier
+                    .width(12.dp)
+                    .height(400.dp))
+            }
+        }
+
+        focusAndWait()
+        assertHandlesDisplayed()
+
+        rule.onNodeWithTag(containerTag).performTouchInput {
+            swipeUp()
+        }
+        assertHandlesNotExist()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+
+        rule.onNodeWithTag(containerTag).performTouchInput {
+            swipeDown()
+        }
+        assertHandlesDisplayed()
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandle_toExtendSelection() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToLeft(Handle.SelectionStart, fontSizePx * 4)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandle_toExtendSelection() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToRight(Handle.SelectionEnd, fontSizePx * 4)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
+        }
+    }
+
+    @Test
+    fun doubleClickOnWord_toSelectWord() {
+        state = TextFieldState("abc def ghj")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            doubleClick(Offset(fontSizePx * 5, fontSizePx / 2)) // middle word
+        }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun doubleClickOnWhitespace_doesNotSelectWhitespace() {
+        state = TextFieldState("abc def ghj")
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            // space between first and second words
+            doubleClick(Offset(fontSizePx * 3.5f, fontSizePx / 2))
+        }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(3, 4))
+            assertThat(state.text.selectionInChars.collapsed).isFalse()
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandle_outOfBounds_horizontally() {
+        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(77, 80))
+        val scrollState = ScrollState(0)
+        lateinit var scope: CoroutineScope
+        rule.setTextFieldTestContent {
+            scope = rememberCoroutineScope()
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.SingleLine,
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.waitForIdle()
+        scope.launch { scrollState.scrollTo(scrollState.maxValue) } // scroll to the most right
+        focusAndWait() // selection handles show up
+
+        repeat(80) {
+            swipeToLeft(Handle.SelectionStart, fontSizePx)
+        }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandle_outOfBounds_vertically() {
+        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(77, 80))
+        val scrollState = ScrollState(0)
+        lateinit var scope: CoroutineScope
+        rule.setTextFieldTestContent {
+            scope = rememberCoroutineScope()
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
+                scrollState = scrollState,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(50.dp)
+            )
+        }
+
+        rule.waitForIdle()
+        scope.launch { scrollState.scrollTo(scrollState.maxValue) } // scroll to the bottom
+        focusAndWait() // selection handles show up
+
+        swipeUp(Handle.SelectionStart, scrollState.maxValue.toFloat() * 2)
+        // make sure that we also swipe to start on the first line
+        swipeToLeft(Handle.SelectionStart, fontSizePx * 10)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandle_outOfBounds_horizontally() {
+        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.waitForIdle()
+        focusAndWait() // selection handles show up
+
+        repeat(80) {
+            swipeToRight(Handle.SelectionEnd, fontSizePx)
+        }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandle_outOfBounds_vertically() {
+        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
+        lateinit var layoutResult: () -> TextLayoutResult?
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
+                onTextLayout = { layoutResult = it },
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(100.dp)
+            )
+        }
+
+        rule.waitForIdle()
+        focusAndWait() // selection handles show up
+
+        @Suppress("NAME_SHADOWING")
+        layoutResult()!!.let { layoutResult ->
+            swipeDown(Handle.SelectionEnd, layoutResult.size.height.toFloat())
+            swipeToRight(Handle.SelectionEnd, layoutResult.size.width.toFloat())
+        }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandle_extendsByWord() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToLeft(Handle.SelectionStart, fontSizePx * 2) // only move by 2 characters
+        rule.runOnIdle {
+            // selection extends by a word
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandle_extendsByWord() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToRight(Handle.SelectionEnd, fontSizePx * 2) // only move by 2 characters
+        rule.runOnIdle {
+            // selection extends by a word
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandle_shrinksByCharacter() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToRight(Handle.SelectionStart, fontSizePx) // only move by a single character
+        rule.runOnIdle {
+            // selection shrinks by a character
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5, 7))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandle_shrinksByCharacter() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToLeft(Handle.SelectionEnd, fontSizePx) // only move by a single character
+        rule.runOnIdle {
+            // selection shrinks by a character
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 6))
+        }
+    }
+
+    @Test
+    fun dragStartSelectionHandlePastEndHandle_reversesTheSelection() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToRight(Handle.SelectionStart, fontSizePx * 7)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(11, 7))
+        }
+    }
+
+    @Test
+    fun dragEndSelectionHandlePastStartHandle_canReverseSelection() {
+        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
+        rule.setTextFieldTestContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(200.dp)
+            )
+        }
+
+        focusAndWait()
+
+        swipeToLeft(Handle.SelectionEnd, fontSizePx * 7)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 0))
+        }
+    }
+
+    private fun focusAndWait() {
+        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.RequestFocus)
+    }
+
+    private fun assertHandlesDisplayed(
+        assertStartHandle: Boolean = true,
+        assertEndHandle: Boolean = true
+    ) {
+        if (assertStartHandle) {
+            rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
+        }
+        if (assertEndHandle) {
+            rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
+        }
+    }
+
+    private fun assertHandlesNotExist(
+        assertStartHandle: Boolean = true,
+        assertEndHandle: Boolean = true
+    ) {
+        if (assertStartHandle) {
+            rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
+        }
+        if (assertEndHandle) {
+            rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
+        }
+    }
+
+    private fun swipeUp(handle: Handle, swipeDistance: Float) =
+        performHandleDrag(handle, true, swipeDistance, Orientation.Vertical)
+
+    private fun swipeDown(handle: Handle, swipeDistance: Float) =
+        performHandleDrag(handle, false, swipeDistance, Orientation.Vertical)
+
+    private fun swipeToLeft(handle: Handle, swipeDistance: Float) =
+        performHandleDrag(handle, true, swipeDistance)
+
+    private fun swipeToRight(handle: Handle, swipeDistance: Float) =
+        performHandleDrag(handle, false, swipeDistance)
+
+    private fun performHandleDrag(
+        handle: Handle,
+        toStart: Boolean,
+        swipeDistance: Float = 1f,
+        orientation: Orientation = Orientation.Horizontal
+    ) {
+        val handleNode = rule.onNode(isSelectionHandle(handle))
+
+        handleNode.performTouchInput {
+            if (orientation == Orientation.Horizontal) {
+                if (toStart) {
+                    swipeLeft(
+                        startX = centerX,
+                        endX = centerX - viewConfiguration.touchSlop - swipeDistance,
+                        durationMillis = 1000
+                    )
+                } else {
+                    swipeRight(
+                        startX = centerX,
+                        endX = centerX + viewConfiguration.touchSlop + swipeDistance,
+                        durationMillis = 1000
+                    )
+                }
+            } else {
+                if (toStart) {
+                    swipeUp(
+                        startY = centerY,
+                        endY = centerY - viewConfiguration.touchSlop - swipeDistance,
+                        durationMillis = 1000
+                    )
+                } else {
+                    swipeDown(
+                        startY = centerY,
+                        endY = centerY + viewConfiguration.touchSlop + swipeDistance,
+                        durationMillis = 1000
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
new file mode 100644
index 0000000..84b03e2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalTestApi::class,
+    ExperimentalFoundationApi::class
+)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldSelectionOnBackTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val Tag = "BasicTextField2"
+
+    @Test
+    fun whenBackPressed_andReleased_textFieldClearsSelection() {
+        val state = TextFieldState("hello", TextRange(0, 0))
+        rule.setContent {
+            BasicTextField2(
+                state,
+                Modifier
+                    .testTag(Tag)
+                    .wrapContentSize()
+            )
+        }
+        val textNode = rule.onNodeWithTag(Tag)
+        textNode.performTextInputSelection(TextRange(0, 3))
+        rule.waitForIdle()
+        textNode.performKeyInput { pressKey(Key.Back) }
+        val expected = TextRange(3, 3)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(expected)
+        }
+    }
+
+    @Test
+    fun whenBackPressed_andReleased_textFieldDoesNotPropagateBackPress() {
+        val state = TextFieldState("hello", TextRange(0, 0))
+        var backPressed = 0
+        rule.setContent {
+            BasicTextField2(
+                state,
+                Modifier
+                    .testTag(Tag)
+                    .wrapContentSize()
+                    .onKeyEvent {
+                        if (it.type == KeyEventType.KeyUp && it.key == Key.Back) {
+                            backPressed++
+                        }
+                        false
+                    }
+            )
+        }
+        val textNode = rule.onNodeWithTag(Tag)
+        textNode.performTextInputSelection(TextRange(0, 3))
+        rule.waitForIdle()
+        textNode.performKeyInput { pressKey(Key.Back) }
+        val expected = TextRange(3, 3)
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(expected)
+            assertThat(backPressed).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun whenBackPressed_coreTextFieldRetainsSelection() {
+        val state = TextFieldState("hello", TextRange(0, 0))
+        rule.setContent {
+            BasicTextField2(
+                state,
+                Modifier
+                    .testTag(Tag)
+                    .wrapContentSize()
+            )
+        }
+        val expected = TextRange(0, 3)
+        val textNode = rule.onNodeWithTag(Tag)
+        textNode.performTextInputSelection(expected)
+        rule.waitForIdle()
+        // should have no effect
+        textNode.performKeyInput { keyDown(Key.Back) }
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(expected)
+        }
+    }
+
+    @Test
+    fun whenBackPressed_andReleased_whenCursorHandleShown_doesNotConsumeEvent() {
+        var backPressed = 0
+        var softwareKeyboardController: SoftwareKeyboardController? = null
+        val state = TextFieldState("Hello")
+        rule.setTextFieldTestContent {
+            softwareKeyboardController = LocalSoftwareKeyboardController.current
+            BasicTextField2(
+                state,
+                Modifier
+                    .testTag(Tag)
+                    .onKeyEvent {
+                        if (it.type == KeyEventType.KeyUp && it.key == Key.Back) {
+                            backPressed++
+                        }
+                        false
+                    }
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            // Show the handle.
+            performClick()
+            rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+            // Hide the keyboard before pressing back, since the first back should be consumed by
+            // the keyboard.
+            rule.runOnUiThread {
+                softwareKeyboardController!!.hide()
+            }
+
+            // Press back.
+            performKeyInput { pressKey(Key.Back) }
+
+            // Ensure back event was propagated up past the text field.
+            rule.runOnIdle {
+                assertThat(backPressed).isEqualTo(1)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
new file mode 100644
index 0000000..d2b1d17a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
@@ -0,0 +1,1008 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.foundation.content.createClipData
+import androidx.compose.foundation.content.receiveContent
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.placeCursorAtEnd
+import androidx.compose.foundation.text.input.selectAll
+import androidx.compose.foundation.text.selection.FakeTextToolbar
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.ClipMetadata
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.platform.toClipEntry
+import androidx.compose.ui.platform.toClipMetadata
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.withKeyDown
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@LargeTest
+class TextFieldTextToolbarTest : FocusedWindowTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val fontSize = 10.sp
+
+    val fontSizePx = with(rule.density) { fontSize.toPx() }
+
+    val TAG = "BasicTextField2"
+
+    private var enabled by mutableStateOf(true)
+
+    @Test
+    fun toolbarAppears_whenCursorHandleIsClicked() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenCursorHandleIsClickedAgain() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun longClickOnEmptyTextField_showsToolbar_butNoHandle() {
+        val state = TextFieldState("")
+        val textToolbar = FakeTextToolbar({ _, _, _, _, _ -> }, {})
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longClick(Offset(fontSize.toPx(), fontSize.toPx() / 2))
+        }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextStateIsUpdated() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        state.edit {
+            append(" World!")
+            placeCursorAtEnd()
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaHardwareKeys() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        with(rule.onNodeWithTag(TAG)) {
+            requestFocus()
+            performKeyInput {
+                withKeyDown(Key.ShiftLeft) {
+                    pressKey(Key.DirectionLeft)
+                    pressKey(Key.DirectionLeft)
+                    pressKey(Key.DirectionLeft)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5, 2))
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+
+        with(rule.onNodeWithTag(TAG)) {
+            performKeyInput {
+                withKeyDown(Key.CtrlLeft) {
+                    pressKey(Key.A)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaStateUpdate() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        with(rule.onNodeWithTag(TAG)) {
+            requestFocus()
+        }
+
+        rule.runOnIdle {
+            state.edit { selectAll() }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaSemantics() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        with(rule.onNodeWithTag(TAG)) {
+            requestFocus()
+            performTextInputSelection(TextRange(0, 5))
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarAppears_ifSelectionIsInitiatedViaSemantics_inNoneTraversalMode() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        with(rule.onNodeWithTag(TAG)) {
+            requestFocus()
+            performSemanticsAction(SemanticsActions.SetSelection) {
+                it(0, 5, false)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextIsEntered_throughIME() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.onNodeWithTag(TAG).performTextInput(" World!")
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun cursorToolbarDisappears_whenTextField_getsDisabled_doesNotReappear() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        enabled = false
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+
+        enabled = true
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun selectionToolbarDisappears_whenTextField_getsDisabled_doesNotReappear() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(2, 4))
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        enabled = false
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+
+        enabled = true
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun toolbarDisappears_whenTextIsEntered_throughHardwareKeyboard() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.onNodeWithTag(TAG).performKeyInput {
+            pressKey(Key.W)
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarTemporarilyHides_whenHandleIsBeingDragged() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(0f, fontSizePx / 2)) }
+
+        with(rule.onNode(isSelectionHandle(Handle.Cursor))) {
+            performClick()
+            rule.runOnIdle {
+                assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            }
+            performTouchInput {
+                down(center)
+                moveBy(Offset(viewConfiguration.touchSlop, 0f))
+                moveBy(Offset(fontSizePx, 0f))
+            }
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            up()
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarTemporarilyHides_whenCursor_goesOutOfBounds() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft(startX = fontSizePx * 3, endX = 0f) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeRight(startX = 0f, endX = fontSizePx * 3) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarFollowsTheCursor_whenTextFieldIsScrolled() {
+        var shownRect: Rect? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { rect, _, _, _, _ ->
+                shownRect = rect
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        lateinit var firstRectAnchor: Rect
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            firstRectAnchor = shownRect!!
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            down(center)
+            moveBy(Offset(-viewConfiguration.touchSlop - fontSizePx, 0f))
+            up()
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            val secondRectAnchor = shownRect!!
+            Truth.assertAbout(RectSubject.SUBJECT_FACTORY)
+                .that(secondRectAnchor)!!
+                .isEqualToWithTolerance(
+                    firstRectAnchor.translate(
+                        translateX = -fontSizePx,
+                        translateY = 0f
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun toolbarShowsSelectAll() {
+        var selectAllOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, _, onSelectAllRequested ->
+                selectAllOptionAvailable = onSelectAllRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(selectAllOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowSelectAll_whenAllTextIsAlreadySelected() {
+        var selectAllOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, _, onSelectAllRequested ->
+                selectAllOption = onSelectAllRequested
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(selectAllOption).isNotNull()
+        }
+
+        selectAllOption?.invoke()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+        rule.runOnIdle {
+            assertThat(selectAllOption).isNull()
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowPaste_whenClipboardHasNoContent() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isFalse()
+        }
+    }
+
+    @Test
+    fun toolbarShowsPaste_whenClipboardHasText() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager("world")
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowPaste_whenClipboardHasContent_butNoReceiveContentConfigured() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true).apply {
+            setClip(createClipData().toClipEntry())
+        }
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isFalse()
+        }
+    }
+
+    @Test
+    fun toolbarShowsPaste_whenClipboardHasContent_andReceiveContentConfigured() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager(supportsClipEntry = true).apply {
+            setClip(createClipData().toClipEntry())
+        }
+        val state = TextFieldState("Hello")
+        setupContent(
+            state = state,
+            toolbar = textToolbar,
+            singleLine = true,
+            clipboardManager = clipboardManager,
+            modifier = Modifier.receiveContent(setOf(MediaType.Image)) { null }
+        )
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun pasteInsertsContentAtCursor_placesCursorAfterInsertedContent() {
+        var pasteOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOption = onPasteRequested
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager("world")
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, 0f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            pasteOption!!.invoke()
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("Heworldllo")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(7))
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun toolbarDoesNotShowCopyOrCut_whenSelectionIsCollapsed() {
+        var cutOptionAvailable = false
+        var copyOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+                copyOptionAvailable = onCopyRequested != null
+                cutOptionAvailable = onCutRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(2, 2))
+
+        rule.runOnIdle {
+            assertThat(copyOptionAvailable).isFalse()
+            assertThat(cutOptionAvailable).isFalse()
+        }
+    }
+
+    @Test
+    fun toolbarShowsCopyAndCut_whenSelectionIsExpanded() {
+        var cutOptionAvailable = false
+        var copyOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+                copyOptionAvailable = onCopyRequested != null
+                cutOptionAvailable = onCutRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(2, 4))
+
+        rule.runOnIdle {
+            assertThat(copyOptionAvailable).isTrue()
+            assertThat(cutOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun copyUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion() {
+        var copyOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, onCopyRequested, _, _, _ ->
+                copyOption = onCopyRequested
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(0, 5))
+
+        rule.runOnIdle {
+            copyOption!!.invoke()
+        }
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+        }
+    }
+
+    @Test
+    fun cutUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion_removesTheCutContent() {
+        var cutOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, onCutRequested, _ ->
+                cutOption = onCutRequested
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager()
+        val state = TextFieldState("Hello World!")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(1, 5))
+
+        rule.runOnIdle {
+            cutOption!!.invoke()
+        }
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
+            assertThat(state.text.toString()).isEqualTo("H World!")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        }
+    }
+
+    @Test
+    fun cutAppliesFilter() {
+        var cutOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, onCutRequested, _ ->
+                cutOption = onCutRequested
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager()
+        val state = TextFieldState("Hello World!")
+        setupContent(state, textToolbar, true, clipboardManager) { original, changes ->
+            // only reject text changes, accept selection
+            val selection = changes.selectionInChars
+            changes.replace(0, changes.length, original.toString())
+            changes.selectCharsIn(selection)
+        }
+
+        rule.onNodeWithTag(TAG).requestFocus()
+        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(1, 5))
+
+        rule.runOnIdle {
+            cutOption!!.invoke()
+        }
+
+        rule.runOnIdle {
+            assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
+            assertThat(state.text.toString()).isEqualTo("Hello World!")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        }
+    }
+
+    @Test
+    fun tappingTextField_hidesTheToolbar() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.mainClock.advanceTimeBy(1000) // to not cause double click
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun interactingWithTextFieldByMouse_doeNotShowTheToolbar() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performMouseInput {
+            click()
+        }
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+    }
+
+    @Test
+    fun toolbarDisappears_whenFocusIsLost() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        val focusRequester = FocusRequester()
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+                Column {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .focusable()
+                            .size(100.dp)
+                    )
+                    BasicTextField2(
+                        state = state,
+                        modifier = Modifier
+                            .width(100.dp)
+                            .testTag(TAG),
+                        textStyle = TextStyle(
+                            fontFamily = TEST_FONT_FAMILY,
+                            fontSize = fontSize
+                        )
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        focusRequester.requestFocus()
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextFieldIsDisposed() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        val toggleState = mutableStateOf(true)
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+                Column {
+                    if (toggleState.value) {
+                        BasicTextField2(
+                            state = state,
+                            modifier = Modifier
+                                .width(100.dp)
+                                .testTag(TAG),
+                            textStyle = TextStyle(
+                                fontFamily = TEST_FONT_FAMILY,
+                                fontSize = fontSize
+                            )
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        toggleState.value = false
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenLongPressIsInitiated() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            longPress(Offset(3 * fontSizePx * 2, fontSizePx / 2))
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    private fun setupContent(
+        state: TextFieldState = TextFieldState(),
+        toolbar: TextToolbar = FakeTextToolbar(),
+        singleLine: Boolean = false,
+        clipboardManager: ClipboardManager = FakeClipboardManager(),
+        modifier: Modifier = Modifier,
+        filter: InputTransformation? = null,
+    ) {
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(
+                LocalTextToolbar provides toolbar,
+                LocalClipboardManager provides clipboardManager
+            ) {
+                BasicTextField2(
+                    state = state,
+                    modifier = modifier
+                        .width(100.dp)
+                        .testTag(TAG),
+                    textStyle = TextStyle(
+                        fontFamily = TEST_FONT_FAMILY,
+                        fontSize = fontSize
+                    ),
+                    enabled = enabled,
+                    lineLimits = if (singleLine) {
+                        TextFieldLineLimits.SingleLine
+                    } else {
+                        TextFieldLineLimits.Default
+                    },
+                    inputTransformation = filter
+                )
+            }
+        }
+    }
+
+    private fun FakeTextToolbar() = FakeTextToolbar(
+        onShowMenu = { _, _, _, _, _ -> },
+        onHideMenu = {
+            println("hide")
+        }
+    )
+}
+
+internal class RectSubject private constructor(
+    failureMetadata: FailureMetadata?,
+    private val subject: Rect?
+) : Subject(failureMetadata, subject) {
+
+    companion object {
+        internal val SUBJECT_FACTORY: Factory<RectSubject?, Rect?> =
+            Factory { failureMetadata, subject -> RectSubject(failureMetadata, subject) }
+    }
+
+    fun isEqualToWithTolerance(expected: Rect, tolerance: Float = 1f) {
+        if (subject == null) failWithoutActual(Fact.simpleFact("is null"))
+        check("instanceOf()").that(subject).isInstanceOf(Rect::class.java)
+        assertThat(subject!!.left).isWithin(tolerance).of(expected.left)
+        assertThat(subject.top).isWithin(tolerance).of(expected.top)
+        assertThat(subject.right).isWithin(tolerance).of(expected.right)
+        assertThat(subject.bottom).isWithin(tolerance).of(expected.bottom)
+    }
+}
+
+internal fun FakeClipboardManager(
+    initialText: String? = null,
+    supportsClipEntry: Boolean = false,
+) = object : ClipboardManager {
+    private var currentText: AnnotatedString? = initialText?.let { AnnotatedString(it) }
+    private var currentClipEntry: ClipEntry? = null
+
+    override fun setText(annotatedString: AnnotatedString) {
+        currentText = annotatedString
+    }
+
+    override fun getText(): AnnotatedString? {
+        return currentText
+    }
+
+    override fun getClip(): ClipEntry? {
+        if (supportsClipEntry) {
+            return currentClipEntry
+        } else {
+            throw NotImplementedError("This clipboard does not support clip entries")
+        }
+    }
+
+    override fun getClipMetadata(): ClipMetadata? {
+        if (supportsClipEntry) {
+            return currentClipEntry?.clipData?.description?.toClipMetadata()
+        } else {
+            throw NotImplementedError("This clipboard does not support clip entries")
+        }
+    }
+
+    override fun hasClip(): Boolean {
+        if (supportsClipEntry) {
+            return currentClipEntry != null
+        } else {
+            throw NotImplementedError("This clipboard does not support clip entries")
+        }
+    }
+
+    override fun setClip(clipEntry: ClipEntry) {
+        if (supportsClipEntry) {
+            currentClipEntry = clipEntry
+        } else {
+            throw NotImplementedError("This clipboard does not support clip entries")
+        }
+    }
+}
+
+/**
+ * Toolbar does not show up when text is selected with traversal mode off (relative to original
+ * text). This is an override of [SemanticsNodeInteraction.performTextInputSelection] that
+ * makes sure the toolbar shows up after selection is initiated.
+ */
+fun SemanticsNodeInteraction.performTextInputSelectionShowingToolbar(selection: TextRange) {
+    requestFocus()
+    performSemanticsAction(SemanticsActions.SetSelection) {
+        it(selection.min, selection.max, false)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
new file mode 100644
index 0000000..aa12202
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection.gesture
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.isPlatformMagnifierSupported
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.FocusedWindowTest
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.foundation.text.selection.HandlePressedScope
+import androidx.compose.foundation.text.selection.assertNoMagnifierExists
+import androidx.compose.foundation.text.selection.assertThatOffset
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.getMagnifierCenterOffset
+import androidx.compose.foundation.text.selection.withHandlePressed
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test that TextField works as expected even when it is scrolled.
+ *
+ * Regression test for b/314385218.
+ */
+@ExperimentalFoundationApi
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldScrolledSelectionGestureTest : FocusedWindowTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val fontFamily = TEST_FONT_FAMILY
+    private val fontSize = 15.sp
+    private val textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize)
+    private val density = Density(1f)
+    private val pointerAreaTag = "testTag"
+
+    private fun setContent(content: @Composable (tag: String) -> Unit) {
+        rule.setTextFieldTestContent {
+            CompositionLocalProvider(
+                LocalDensity provides density,
+                LocalViewConfiguration provides TestViewConfiguration(
+                    minimumTouchTargetSize = DpSize.Zero,
+                    touchSlop = Float.MIN_VALUE,
+                ),
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .padding(32.dp)
+                        .wrapContentSize()
+                ) {
+                    content(pointerAreaTag)
+                }
+            }
+        }
+    }
+
+    private abstract inner class AbstractScope(
+        val textFieldState: TextFieldState,
+        val onTextField: SemanticsNodeInteraction,
+        val textFieldLayoutCoordinates: LayoutCoordinates,
+    ) {
+        /** Returns the offset needed to translate the amount scrolled. */
+        abstract fun TextLayoutResult.translateScroll(): Offset
+
+        fun characterBoxScrolled(offset: Int): Rect = onTextField.fetchTextLayoutResult().run {
+            getBoundingBox(offset).translate(translateScroll())
+        }
+
+        fun positionForCharacterScrolled(offset: Int): Offset =
+            characterBoxScrolled(offset).centerLeft
+
+        fun HandlePressedScope.moveHandleToCharacter(characterOffset: Int) {
+            val boundingBox = onTextField.fetchTextLayoutResult().getBoundingBox(characterOffset)
+            val destinationPosition = when (fetchHandleInfo().handle) {
+                Handle.SelectionStart -> boundingBox.bottomLeft
+                Handle.SelectionEnd -> boundingBox.bottomRight
+                Handle.Cursor -> fail("Unexpected handle ${Handle.Cursor}")
+            }
+            moveHandleTo(destinationPosition)
+        }
+
+        fun assertSelectionEquals(selectionRange: Pair<Int, Int>) {
+            val (start, end) = selectionRange
+            assertThat(textFieldState.text.selectionInChars).isEqualTo(TextRange(start, end))
+        }
+
+        fun assertNoMagnifierExists() {
+            if (!isPlatformMagnifierSupported()) return
+            assertNoMagnifierExists(rule)
+        }
+
+        fun assertOneMagnifierExistsAt(expectedOffset: Offset) {
+            if (!isPlatformMagnifierSupported()) return
+            val offsetInRoot = getMagnifierCenterOffset(rule, requireSpecified = true)
+            val offsetInTextField = offsetInRoot - textFieldLayoutCoordinates.positionInRoot()
+            assertThatOffset(offsetInTextField).equalsWithTolerance(expectedOffset)
+        }
+    }
+
+    private inner class HorizontalScope(
+        textFieldState: TextFieldState,
+        onTextField: SemanticsNodeInteraction,
+        textFieldLayoutCoordinates: LayoutCoordinates,
+        val textFieldSize: IntSize,
+    ) : AbstractScope(textFieldState, onTextField, textFieldLayoutCoordinates) {
+        override fun TextLayoutResult.translateScroll(): Offset {
+            val textLayoutSize = size
+            assertThat(textFieldSize.height).isEqualTo(textLayoutSize.height)
+            val translateX = textFieldSize.width - textLayoutSize.width
+            return Offset(translateX.toFloat(), 0f)
+        }
+    }
+
+    /** Create a horizontally scrollable text field that is scrolled all the way to the end. */
+    private fun runHorizontalTest(block: HorizontalScope.() -> Unit) {
+        val text = (0..9).joinToString(separator = " ") { "text$it" }
+        lateinit var textFieldLayoutCoordinates: LayoutCoordinates
+        var sizeNullable: MutableState<IntSize?>? = null
+        lateinit var tfs: TextFieldState
+        setContent { tag ->
+            sizeNullable = remember { mutableStateOf(null) }
+            tfs = rememberTextFieldState(text)
+            BasicTextField2(
+                state = tfs,
+                textStyle = textStyle,
+                lineLimits = TextFieldLineLimits.SingleLine,
+                modifier = Modifier
+                    .width(300.dp)
+                    .testTag(tag = tag)
+                    .onSizeChanged { sizeNullable!!.value = it }
+                    .onGloballyPositioned { textFieldLayoutCoordinates = it }
+            )
+        }
+        val onTextField = rule.onNodeWithTag(pointerAreaTag)
+        onTextField.requestFocus()
+
+        // scroll to the end
+        onTextField.performTouchInput {
+            repeat(4) {
+                swipe(start = centerRight, end = centerLeft)
+            }
+        }
+
+        assertThat(sizeNullable!!.value).isNotNull()
+        HorizontalScope(tfs, onTextField, textFieldLayoutCoordinates, sizeNullable!!.value!!)
+            .block()
+    }
+
+    @Test
+    fun whenHorizontalScroll_longPressGesture_selectAndDrag() = runHorizontalTest {
+        // select "text8".
+        val char50Position = positionForCharacterScrolled(50)
+        onTextField.performTouchInput { longPress(char50Position) }
+        assertSelectionEquals(48 to 53)
+        assertOneMagnifierExistsAt(char50Position)
+
+        // Backwards select through "text7" so that the selection is "text7 ".
+        val char46Position = positionForCharacterScrolled(46)
+        onTextField.performTouchInput { moveTo(char46Position) }
+        assertSelectionEquals(42 to 53)
+        assertOneMagnifierExistsAt(char46Position)
+
+        onTextField.performTouchInput { up() }
+        assertSelectionEquals(42 to 53)
+        assertNoMagnifierExists()
+    }
+
+    @Test
+    fun whenHorizontalScroll_handleGesture_drag() = runHorizontalTest {
+        // select "text8".
+        onTextField.performTouchInput { longClick(positionForCharacterScrolled(50)) }
+        assertSelectionEquals(48 to 53)
+        assertNoMagnifierExists()
+
+        // Backwards select through "text7" so that the selection is "text7 ".
+        rule.withHandlePressed(Handle.SelectionStart) {
+            assertSelectionEquals(48 to 53)
+            assertOneMagnifierExistsAt(positionForCharacterScrolled(48))
+            moveHandleToCharacter(45)
+            assertSelectionEquals(42 to 53)
+            assertOneMagnifierExistsAt(positionForCharacterScrolled(45))
+        }
+        assertSelectionEquals(42 to 53)
+        assertNoMagnifierExists()
+    }
+
+    /** Create a vertically scrollable text field that is scrolled all the way to the end. */
+    private inner class VerticalScope(
+        textFieldState: TextFieldState,
+        onTextField: SemanticsNodeInteraction,
+        textFieldLayoutCoordinates: LayoutCoordinates,
+        val textFieldSize: IntSize,
+    ) : AbstractScope(textFieldState, onTextField, textFieldLayoutCoordinates) {
+        override fun TextLayoutResult.translateScroll(): Offset {
+            val textLayoutSize = size
+            assertThat(textFieldSize.width).isEqualTo(textLayoutSize.width)
+            val translateY = textFieldSize.height - textLayoutSize.height
+            return Offset(0f, translateY.toFloat())
+        }
+    }
+
+    /**
+     * Create a horizontally scrollable text field that is scrolled all the way to the end.
+     */
+    private fun runVerticalTest(block: VerticalScope.() -> Unit) {
+        val text = (0..9).joinToString(separator = "\n") { "text$it" }
+        lateinit var textFieldLayoutCoordinates: LayoutCoordinates
+        var sizeNullable: MutableState<IntSize?>? = null
+        lateinit var tfs: TextFieldState
+        setContent { tag ->
+            sizeNullable = remember { mutableStateOf(null) }
+            tfs = rememberTextFieldState(text)
+            BasicTextField2(
+                state = tfs,
+                textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
+                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 4),
+                modifier = Modifier
+                    .width(300.dp)
+                    .testTag(tag = tag)
+                    .onSizeChanged { sizeNullable!!.value = it }
+                    .onGloballyPositioned { textFieldLayoutCoordinates = it }
+            )
+        }
+        assertThat(sizeNullable).isNotNull()
+        val onTextField = rule.onNodeWithTag(pointerAreaTag)
+        onTextField.requestFocus()
+
+        // scroll to the end
+        onTextField.performTouchInput {
+            repeat(4) {
+                swipe(start = bottomCenter, end = topCenter)
+            }
+        }
+
+        assertThat(sizeNullable!!.value).isNotNull()
+        VerticalScope(tfs, onTextField, textFieldLayoutCoordinates, sizeNullable!!.value!!).block()
+    }
+
+    @Test
+    fun whenVerticalScroll_longPressGesture_selectAndDrag() = runVerticalTest {
+        // select "text8".
+        val char50Position = positionForCharacterScrolled(50)
+        onTextField.performTouchInput { longPress(char50Position) }
+        assertSelectionEquals(48 to 53)
+        assertOneMagnifierExistsAt(char50Position)
+
+        // Backwards select through "text7" so that the selection is "text7 ".
+        val char46Position = positionForCharacterScrolled(46)
+        onTextField.performTouchInput { moveTo(char46Position) }
+        assertSelectionEquals(42 to 53)
+        assertOneMagnifierExistsAt(char46Position)
+
+        onTextField.performTouchInput { up() }
+        assertSelectionEquals(42 to 53)
+        assertNoMagnifierExists()
+    }
+
+    // TODO(b/316940648)
+    //  The TextToolbar at the top of the screen messes up the popup position calculations,
+    //  so suppress SDKs that don't have the floating popup.
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun whenVerticalScroll_handleGesture_drag() = runVerticalTest {
+        // select "text8".
+        onTextField.performTouchInput { longClick(positionForCharacterScrolled(50)) }
+        assertSelectionEquals(48 to 53)
+        assertNoMagnifierExists()
+
+        // Backwards select through "text7" so that the selection is "text7 ".
+        rule.withHandlePressed(Handle.SelectionStart) {
+            assertSelectionEquals(48 to 53)
+            assertOneMagnifierExistsAt(positionForCharacterScrolled(48))
+            moveHandleToCharacter(45)
+            assertSelectionEquals(42 to 53)
+            assertOneMagnifierExistsAt(positionForCharacterScrolled(45))
+        }
+        assertSelectionEquals(42 to 53)
+        assertNoMagnifierExists()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt
new file mode 100644
index 0000000..cdb3d02
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt
@@ -0,0 +1,374 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextClearance
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicTextField2UndoTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun canUndo_imeInsert() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).performTextInput(", World")
+        state.assertText("Hello, World")
+
+        state.undoState.undo()
+        state.assertText("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun canRedo_imeInsert() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).performTextInput(", World")
+
+        state.undoState.undo()
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+
+        state.undoState.redo()
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
+    }
+
+    @Test
+    fun undoMerges_imeInserts() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).typeText(", World")
+        state.assertText("Hello, World")
+
+        state.undoState.undo()
+        state.assertText("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun undoMerges_imeInserts_onlyInForwardsDirection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInput(", World")
+            performTextInputSelection(TextRange(5))
+            performTextInput(" Compose")
+        }
+        state.assertText("Hello Compose, World")
+
+        state.undoState.undo()
+        state.assertText("Hello, World")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
+
+        state.undoState.undo()
+        state.assertText("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun undoMerges_deletes() {
+        val state = TextFieldState("Hello, World", TextRange(12))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            performKeyInput {
+                repeat(12) {
+                    pressKey(Key.Backspace)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("Hello, World", TextRange(12))
+    }
+
+    @Test
+    fun undoDoesNotMerge_deletes_inBothDirections() {
+        val state = TextFieldState("Hello, World", TextRange(6))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            performKeyInput {
+                repeat(6) {
+                    pressKey(Key.Backspace)
+                }
+                repeat(6) {
+                    pressKey(Key.Delete)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+        state.assertTextAndSelection(" World", TextRange(0))
+
+        state.undoState.undo()
+        state.assertTextAndSelection("Hello, World", TextRange(6))
+    }
+
+    @Test
+    fun undo_revertsSelection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInputSelection(TextRange(0, 5))
+            performTextInput("a")
+        }
+        state.assertTextAndSelection("a", TextRange(1))
+
+        state.undoState.undo()
+        state.assertTextAndSelection("Hello", TextRange(0, 5))
+    }
+
+    @Test
+    fun redo_revertsSelection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInputSelection(TextRange(2))
+            performTextInput(" abc ")
+        }
+
+        state.assertTextAndSelection("He abc llo", TextRange(7))
+
+        state.undoState.undo()
+
+        rule.runOnIdle {
+            assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7))
+        }
+
+        state.undoState.redo()
+
+        state.assertTextAndSelection("He abc llo", TextRange(7))
+    }
+
+    @Test
+    fun variousEditOperations() {
+        val state = TextFieldState()
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def")
+            performTextInputSelection(TextRange(4))
+            typeText("123 ")
+            performTextInputSelection(TextRange(0, 3))
+            typeText("ghi")
+            performTextClearance()
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+        state.undoState.undo()
+        state.assertTextAndSelection("ghi 123 def", TextRange(3))
+        state.undoState.undo()
+        state.assertTextAndSelection("g 123 def", TextRange(1))
+        state.undoState.undo()
+        state.assertTextAndSelection("abc 123 def", TextRange(0, 3))
+        state.undoState.undo()
+        state.assertTextAndSelection("abc def", TextRange(4))
+        state.undoState.undo()
+        state.assertTextAndSelection("", TextRange.Zero)
+        assertThat(state.undoState.canUndo).isFalse()
+    }
+
+    @Ignore("b/323405120")
+    @Test
+    fun clearHistory_removesAllUndoAndRedo() {
+        val state = TextFieldState()
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def")
+            performTextInputSelection(TextRange(4))
+            typeText("123 ")
+            performTextInputSelection(TextRange(0, 3))
+            typeText("ghi")
+            performTextClearance()
+        }
+        rule.waitForIdle()
+        state.undoState.undo()
+        rule.waitForIdle()
+        state.undoState.undo()
+        rule.waitForIdle()
+        state.undoState.undo()
+
+        rule.runOnIdle {
+            assertThat(state.undoState.canUndo).isTrue()
+            assertThat(state.undoState.canRedo).isTrue()
+        }
+
+        state.undoState.clearHistory()
+
+        rule.runOnIdle {
+            assertThat(state.undoState.canUndo).isFalse()
+            assertThat(state.undoState.canRedo).isFalse()
+        }
+    }
+
+    @Ignore("b/323344335")
+    @Test
+    fun paste_neverMerges() {
+        val state = TextFieldState()
+        val clipboardManager = FakeClipboardManager("ghi")
+
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(state)
+            }
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def ")
+            performSemanticsAction(SemanticsActions.PasteText)
+            typeText(" jkl")
+        }
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ghi", TextRange(11))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ", TextRange(8))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("", TextRange.Zero)
+    }
+
+    @Test
+    fun cut_neverMerges() {
+        val state = TextFieldState("abc def ghi", TextRange(11))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            repeat(4) {
+                performKeyInput {
+                    pressKey(Key.Backspace)
+                }
+            }
+            performTextInputSelection(TextRange(4, 7))
+            performSemanticsAction(SemanticsActions.CutText)
+            repeat(4) {
+                performKeyInput {
+                    pressKey(Key.Backspace)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc ", TextRange(4))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def", TextRange(4, 7))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ghi", TextRange(11))
+    }
+
+    private fun SemanticsNodeInteraction.typeText(text: String) {
+        text.forEach { performTextInput(it.toString()) }
+    }
+
+    private fun TextFieldState.assertText(text: String) {
+        rule.runOnIdle {
+            assertThat(this.text.toString()).isEqualTo(text)
+        }
+    }
+
+    private fun TextFieldState.assertTextAndSelection(text: String, selection: TextRange) {
+        rule.runOnIdle {
+            assertThat(this.text.toString()).isEqualTo(text)
+            assertThat(this.text.selectionInChars).isEqualTo(selection)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionCopyTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionCopyTest.kt
new file mode 100644
index 0000000..f2db4e6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionCopyTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.selection
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.selection.gestures.util.SelectionSubject
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.unit.sp
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalTestApi::class)
+class SelectionCopyTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val fontFamily = TEST_FONT_FAMILY
+    private val fontSize = 20.sp
+    private val testTextStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize)
+
+    private val textTag = "textTag"
+
+    private val selection = mutableStateOf<Selection?>(null)
+    private val startClipboardText = "Clipboard content at start of test."
+
+    @Test
+    fun whenSelect_thenCopy_clipboardContainsSelectedText() {
+        lateinit var clipboardManager: ClipboardManager
+        val textContent = "text"
+        val selectionRange = 0 to 4
+        rule.setContent {
+            clipboardManager = LocalClipboardManager.current
+            TestContent(textContent)
+        }
+
+        rule.waitForIdle()
+        val onNode = rule.onNodeWithTag(textTag)
+        clipboardManager.setText(AnnotatedString(startClipboardText))
+        onNode.startSelection()
+
+        rule.waitForIdle()
+        assertSelection(textContent, selectionRange)
+        clipboardManager.assertClipboardText(startClipboardText)
+        onNode.performCopy()
+
+        rule.waitForIdle()
+        assertSelection(textContent, selectionRange)
+        clipboardManager.assertClipboardText(textContent)
+    }
+
+    // Regression test for b/322066508 where shortening the selected text
+    // then trying to copy it would crash
+    @Test
+    fun whenSelect_thenEditUnderlyingText_thenCopy_clipboardContainsSelectedText() {
+        val textContent = mutableStateOf("text")
+        val selectionRange = 0 to 4
+        lateinit var clipboardManager: ClipboardManager
+        rule.setContent {
+            clipboardManager = LocalClipboardManager.current
+            TestContent(textContent.value)
+        }
+
+        rule.waitForIdle()
+        val onNode = rule.onNodeWithTag(textTag)
+        clipboardManager.setText(AnnotatedString(startClipboardText))
+        onNode.startSelection()
+
+        rule.waitForIdle()
+        assertSelection(textContent.value, selectionRange)
+        clipboardManager.assertClipboardText(startClipboardText)
+
+        // shorten the text, the selection should shorten as well
+        textContent.value = "tex"
+
+        rule.waitForIdle()
+        onNode.performCopy()
+
+        rule.waitForIdle()
+        assertSelection(textContent.value, null)
+        clipboardManager.assertClipboardText(startClipboardText)
+        textContent.value
+    }
+
+    @Composable
+    private fun TestContent(textContent: String) {
+        SelectionContainer(
+            selection = selection.value,
+            onSelectionChange = {
+                selection.value = it
+            },
+            modifier = Modifier.fillMaxSize(),
+        ) {
+            BasicText(
+                text = textContent,
+                modifier = Modifier
+                    .wrapContentSize()
+                    .testTag(textTag),
+                style = testTextStyle
+            )
+        }
+    }
+
+    private fun SemanticsNodeInteraction.startSelection(offset: Int = 0) {
+        val textLayoutResult = fetchTextLayoutResult()
+        val boundingBox = textLayoutResult.getBoundingBox(offset)
+        performTouchInput { longClick(boundingBox.center) }
+    }
+
+    private fun SemanticsNodeInteraction.performCopy() {
+        performKeyInput {
+            keyDown(Key.CtrlLeft)
+            keyDown(Key.C)
+            keyUp(Key.C)
+            keyUp(Key.CtrlLeft)
+        }
+    }
+
+    private fun ClipboardManager.assertClipboardText(textContent: String) {
+        assertThat(getText()?.text).isEqualTo(textContent)
+    }
+
+    private fun assertSelection(text: String, selectionRange: Pair<Int, Int>?) {
+        Truth.assertAbout(SelectionSubject.withContent(text))
+            .that(selection.value)
+            .hasSelection(
+                expected = selectionRange?.run { TextRange(first, second) },
+                startTextDirection = ResolvedTextDirection.Ltr,
+                endTextDirection = ResolvedTextDirection.Ltr,
+            )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/OWNERS b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/OWNERS
deleted file mode 100644
index f897dda5..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 630734
-include /TEXT_OWNERS
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicSecureTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicSecureTextFieldTest.kt
deleted file mode 100644
index 6e3327b..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicSecureTextFieldTest.kt
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.selection.FakeTextToolbar
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text2.BasicSecureTextField
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.semantics.getOrNull
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTextReplacement
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-internal class BasicSecureTextFieldTest {
-
-    // Keyboard shortcut tests for BasicSecureTextField are in TextFieldKeyEventTest
-
-    @get:Rule
-    val rule = createComposeRule().apply {
-        mainClock.autoAdvance = false
-    }
-
-    @get:Rule
-    val immRule = ComposeInputMethodManagerTestRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicSecureTextField"
-    private val imm = FakeInputMethodManager()
-
-    @Before
-    fun setUp() {
-        immRule.setFactory { imm }
-    }
-
-    @Test
-    fun passwordSemanticsAreSet() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = remember {
-                    TextFieldState("Hello", initialSelectionInChars = TextRange(0, 1))
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.waitForIdle()
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Password))
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
-        // temporarily define copy and cut actions on BasicSecureTextField but make them no-op
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CopyText))
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CutText))
-    }
-
-    @Test
-    fun lastTypedCharacterIsRevealedTemporarily() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("a")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
-            rule.mainClock.advanceTimeBy(1500)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022")
-        }
-    }
-
-    @Test
-    fun lastTypedCharacterIsRevealed_hidesAfterAnotherCharacterIsTyped() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("a")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
-            performTextInput("b")
-            rule.mainClock.advanceTimeBy(50)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022b")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun lastTypedCharacterIsRevealed_whenInsertedInMiddle() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022")
-            performTextInputSelection(TextRange(1))
-            performTextInput("d")
-            rule.mainClock.advanceTimeBy(50)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022d\u2022\u2022")
-        }
-    }
-
-    @Test
-    fun lastTypedCharacterIsRevealed_hidesAfterFocusIsLost() {
-        inputMethodInterceptor.setContent {
-            Column {
-                BasicSecureTextField(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier.testTag(Tag)
-                )
-                Box(
-                    modifier = Modifier
-                        .size(1.dp)
-                        .testTag("otherFocusable")
-                        .focusable()
-                )
-            }
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("a")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("a")
-            rule.onNodeWithTag("otherFocusable")
-                .requestFocus()
-            rule.mainClock.advanceTimeBy(50)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text).isEqualTo("\u2022")
-        }
-    }
-
-    @Test
-    fun lastTypedCharacterIsRevealed_hidesAfterAnotherCharacterRemoved() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeBy(200)
-            performTextInput("d")
-            rule.mainClock.advanceTimeBy(50)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022d")
-            performTextReplacement("bcd")
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022")
-        }
-    }
-
-    @Test
-    fun obfuscationMethodVisible_doesNotHideAnything() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                textObfuscationMode = TextObfuscationMode.Visible,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("abc")
-            rule.mainClock.advanceTimeBy(1500)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("abc")
-        }
-    }
-
-    @Test
-    fun obfuscationMethodVisible_revealsEverythingWhenSwitchedTo() {
-        var obfuscationMode by mutableStateOf(TextObfuscationMode.Hidden)
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                textObfuscationMode = obfuscationMode,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeBy(200)
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022")
-            obfuscationMode = TextObfuscationMode.Visible
-            rule.mainClock.advanceTimeByFrame()
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("abc")
-        }
-    }
-
-    @Test
-    fun obfuscationMethodHidden_hidesEverything() {
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                textObfuscationMode = TextObfuscationMode.Hidden,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeByFrame()
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022")
-            performTextInput("d")
-            rule.mainClock.advanceTimeByFrame()
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022\u2022")
-        }
-    }
-
-    @Test
-    fun obfuscationMethodHidden_hidesEverythingWhenSwitchedTo() {
-        var obfuscationMode by mutableStateOf(TextObfuscationMode.Visible)
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = rememberTextFieldState(),
-                textObfuscationMode = obfuscationMode,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextInput("abc")
-            rule.mainClock.advanceTimeByFrame()
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("abc")
-            obfuscationMode = TextObfuscationMode.Hidden
-            rule.mainClock.advanceTimeByFrame()
-            assertThat(fetchTextLayoutResult().layoutInput.text.text)
-                .isEqualTo("\u2022\u2022\u2022")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_copy() {
-        val state = TextFieldState("Hello World!")
-        val clipboardManager = FakeClipboardManager("initial")
-        inputMethodInterceptor.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicSecureTextField(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("initial")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_cut() {
-        val state = TextFieldState("Hello World!")
-        val clipboardManager = FakeClipboardManager("initial")
-        inputMethodInterceptor.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicSecureTextField(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("initial")
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotShowCopyOrCut() {
-        var copyOptionAvailable = false
-        var cutOptionAvailable = false
-        var showMenuRequested = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
-                showMenuRequested = true
-                copyOptionAvailable = onCopyRequested != null
-                cutOptionAvailable = onCutRequested != null
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        inputMethodInterceptor.setContent {
-            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
-                BasicSecureTextField(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).requestFocus()
-        // We need to disable the traversalMode to show the toolbar.
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.SetSelection) {
-            it(0, 5, false)
-        }
-
-        rule.runOnIdle {
-            assertThat(showMenuRequested).isTrue()
-            assertThat(copyOptionAvailable).isFalse()
-            assertThat(cutOptionAvailable).isFalse()
-        }
-    }
-
-    @Test
-    fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
-        var text by mutableStateOf("hello")
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = { text = it },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.runOnIdle {
-            text = "world"
-        }
-        // Auto-advance is disabled.
-        rule.mainClock.advanceTimeByFrame()
-
-        assertThat(
-            rule.onNodeWithTag(Tag).fetchSemanticsNode().config[SemanticsProperties.EditableText]
-                .text
-        ).isEqualTo("world")
-    }
-
-    @Test
-    fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
-        var text by mutableStateOf("hello")
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = { text = it },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            text = "world"
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_onFocus() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        // Act: wiggle the cursor around a bit.
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        // Act: wiggle the composition around a bit
-        inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
-        inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        rule.runOnIdle {
-            text = "hello"
-        }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            text = "hello"
-        }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun inputMethod_doesNotRestart_inResponseToKeyEvents() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
-        inputMethodInterceptor.setContent {
-            BasicSecureTextField(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            requestFocus()
-            imm.resetCalls()
-
-            performKeyInput { pressKey(Key.Backspace) }
-            performTextInputSelection(TextRange.Zero)
-            performKeyInput { pressKey(Key.Delete) }
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(4, 4, -1, -1)")
-            imm.expectCall("updateSelection(0, 0, -1, -1)")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    private fun requestFocus(tag: String) =
-        rule.onNodeWithTag(tag).requestFocus()
-
-    private fun assertTextSelection(expected: TextRange) {
-        val selection = rule.onNodeWithTag(Tag).fetchSemanticsNode()
-            .config.getOrNull(SemanticsProperties.TextSelectionRange)
-        assertWithMessage("Expected selection to be $expected")
-            .that(selection).isEqualTo(expected)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2DrawPhaseToggleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2DrawPhaseToggleTest.kt
deleted file mode 100644
index 74a9d26..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2DrawPhaseToggleTest.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.os.Build
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.assertContainsColor
-import androidx.compose.testutils.assertDoesNotContainColor
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.toPixelMap
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.unit.sp
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-class BasicTextField2DrawPhaseToggleTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var state: TextFieldState
-
-    private val fontSize = 20.sp
-    private val textStyle = TextStyle(
-        fontSize = fontSize,
-        fontFamily = TEST_FONT_FAMILY
-    )
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun colorChange_reflectsOnView() {
-        state = TextFieldState("abc")
-        var color by mutableStateOf(Color.Red)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle.copy(color = color),
-                modifier = Modifier.background(Color.White)
-            )
-        }
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(Color.Red)
-            .assertContainsColor(Color.White)
-            .assertDoesNotContainColor(Color.Blue)
-
-        color = Color.Blue
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(Color.Blue)
-            .assertContainsColor(Color.White)
-            .assertDoesNotContainColor(Color.Red)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun brushChange_reflectsOnView() {
-        state = TextFieldState("abc")
-        var brush by mutableStateOf(
-            Brush.linearGradient(listOf(Color.Red, Color.Blue), end = Offset(20f, 20f))
-        )
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle.copy(brush = brush),
-                // use brush also for background to get rid of weird antialiasing edges
-                modifier = Modifier.background(brush)
-            )
-        }
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertPixelConsistency { color ->
-                // gradient should not contain any discernible level of green channel
-                color.green <= 0.02f
-            }
-
-        brush = Brush.linearGradient(listOf(Color.Red, Color.Green), end = Offset(20f, 20f))
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertPixelConsistency { color ->
-                // gradient should not contain any discernible level of blue channel
-                color.blue <= 0.02f
-            }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun shadowChange_reflectsOnView() {
-        state = TextFieldState("abc")
-        var shadow by mutableStateOf<Shadow?>(null)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle.copy(color = Color.White, shadow = shadow),
-                modifier = Modifier.background(Color.White)
-            )
-        }
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertPixelConsistency { color ->
-                color == Color.White
-            }
-
-        shadow = Shadow(blurRadius = 8f)
-
-        val pixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
-        for (x in 0 until pixelMap.width) {
-            for (y in 0 until pixelMap.height) {
-                if (pixelMap[x, y] != Color.White) return // everything is fine, end the test
-            }
-        }
-        throw AssertionError("Could not detect a Shadow in the view")
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textDecorationChange_reflectsOnView() {
-        state = TextFieldState("abc")
-        var textDecoration by mutableStateOf(TextDecoration.None)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle.copy(
-                    textDecoration = textDecoration
-                ),
-                modifier = Modifier.background(Color.White)
-            )
-        }
-
-        val initialPixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
-
-        textDecoration = TextDecoration.Underline
-
-        val underlinedPixelMap = rule.onNode(hasSetTextAction()).captureToImage().toPixelMap()
-
-        assertThat(initialPixelMap.width to initialPixelMap.height)
-            .isEqualTo(underlinedPixelMap.width to underlinedPixelMap.height)
-
-        // They should not be the same due to underline.
-        assertThat(initialPixelMap.buffer).isNotEqualTo(underlinedPixelMap.buffer)
-    }
-}
-
-/**
- * Instead of looking for an exact match of pixel values, this assertion provides the ability to
- * judge each pixel individually to whether it fits a predefined filter.
- */
-private inline fun ImageBitmap.assertPixelConsistency(
-    filter: (color: Color) -> Boolean
-) {
-    val pixel = toPixelMap()
-    for (x in 0 until width) {
-        for (y in 0 until height) {
-            val pxColor = pixel[x, y]
-            if (!filter(pxColor)) {
-                throw AssertionError(
-                    "Pixel at [$x, $y] with the value of [$pxColor] is unexpected!"
-                )
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2ImmIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2ImmIntegrationTest.kt
deleted file mode 100644
index 8294671..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2ImmIntegrationTest.kt
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(
-    ExperimentalFoundationApi::class,
-    ExperimentalTestApi::class,
-)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2ImmIntegrationTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @get:Rule
-    val immRule = ComposeInputMethodManagerTestRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicTextField2"
-    private val imm = FakeInputMethodManager()
-
-    @Before
-    fun setUp() {
-        immRule.setFactory { imm }
-    }
-
-    @Test
-    fun becomesTextEditor_whenFocusGained() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            commitText("hello", 0)
-            assertThat(state.text.toString()).isEqualTo("hello")
-        }
-    }
-
-    @Test
-    fun stopsBeingTextEditor_whenFocusLost() {
-        val state = TextFieldState()
-        lateinit var focusManager: FocusManager
-        inputMethodInterceptor.setTextFieldTestContent {
-            focusManager = LocalFocusManager.current
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-        requestFocus(Tag)
-        rule.runOnIdle {
-            focusManager.clearFocus()
-        }
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun stopsBeingTextEditor_whenChangedToReadOnly() {
-        val state = TextFieldState()
-        var readOnly by mutableStateOf(false)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(state, Modifier.testTag(Tag), readOnly = readOnly)
-        }
-        requestFocus(Tag)
-        inputMethodInterceptor.assertSessionActive()
-
-        readOnly = true
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun stopsBeingTextEditor_whenChangedToDisabled() {
-        val state = TextFieldState()
-        var enabled by mutableStateOf(true)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(state, Modifier.testTag(Tag), enabled = enabled)
-        }
-        requestFocus(Tag)
-        inputMethodInterceptor.assertSessionActive()
-
-        enabled = false
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun staysTextEditor_whenFocusTransferred() {
-        val state1 = TextFieldState()
-        val state2 = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(state1, Modifier.testTag(Tag + 1))
-            BasicTextField2(state2, Modifier.testTag(Tag + 2))
-        }
-
-        requestFocus(Tag + 1)
-        requestFocus(Tag + 2)
-
-        inputMethodInterceptor.withInputConnection {
-            commitText("hello", 0)
-            endBatchEdit()
-            assertThat(state2.text.toString()).isEqualTo("hello")
-            assertThat(state1.text.toString()).isEmpty()
-        }
-    }
-
-    @Test
-    fun stopsBeingTextEditor_whenRemovedFromCompositionWhileFocused() {
-        val state = TextFieldState()
-        var compose by mutableStateOf(true)
-        inputMethodInterceptor.setTextFieldTestContent {
-            if (compose) {
-                BasicTextField2(state, Modifier.testTag(Tag))
-            }
-        }
-        requestFocus(Tag)
-        rule.runOnIdle {
-            compose = false
-        }
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun inputRestarted_whenStateInstanceChanged() {
-        val state1 = TextFieldState()
-        val state2 = TextFieldState()
-        var state by mutableStateOf(state1)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-        requestFocus(Tag)
-
-        state = state2
-
-        inputMethodInterceptor.withInputConnection {
-            commitText("hello", 0)
-            assertThat(state2.text.toString()).isEqualTo("hello")
-            assertThat(state1.text.toString()).isEmpty()
-        }
-    }
-
-    @Test
-    fun immUpdated_whenFilterChangesText_fromInputConnection() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, new ->
-                    // Force the selection not to change.
-                    val initialSelection = new.selectionInChars
-                    new.append("world")
-                    new.selectCharsIn(initialSelection)
-                }
-            )
-        }
-        requestFocus(Tag)
-        inputMethodInterceptor.withInputConnection {
-            // TODO move this before withInputConnection?
-            imm.resetCalls()
-
-            commitText("hello", 1)
-
-            @Suppress("SpellCheckingInspection")
-            assertThat(state.text.toString()).isEqualTo("helloworld")
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("restartInput")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immUpdated_whenFilterChangesText_fromKeyEvent() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, new ->
-                    val initialSelection = new.selectionInChars
-                    new.append("world")
-                    new.selectCharsIn(initialSelection)
-                }
-            )
-        }
-        requestFocus(Tag)
-        rule.runOnIdle { imm.resetCalls() }
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
-
-        rule.runOnIdle {
-            imm.expectCall("restartInput")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @FlakyTest(bugId = 290927588)
-    @Test
-    fun immUpdated_whenFilterChangesSelection_fromInputConnection() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, new -> new.selectAll() }
-            )
-        }
-        requestFocus(Tag)
-        inputMethodInterceptor.withInputConnection {
-            imm.resetCalls()
-            setComposingText("hello", 1)
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(0, 5, 0, 5)")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immUpdated_whenEditChangesText() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-        requestFocus(Tag)
-        rule.runOnIdle {
-            imm.resetCalls()
-
-            state.edit {
-                append("hello")
-                placeCursorBeforeCharAt(0)
-            }
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("restartInput")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immUpdated_whenEditChangesSelection() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-        requestFocus(Tag)
-        rule.runOnIdle {
-            imm.resetCalls()
-
-            state.edit {
-                placeCursorAtEnd()
-            }
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(5, 5, -1, -1)")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immUpdated_whenEditChangesTextAndSelection() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state, Modifier.testTag(Tag))
-        }
-        requestFocus(Tag)
-        rule.runOnIdle {
-            imm.resetCalls()
-
-            state.edit {
-                append("hello")
-                placeCursorAtEnd()
-            }
-        }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(5, 5, -1, -1)")
-            imm.expectCall("restartInput")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
-            )
-        }
-        requestFocus(Tag)
-        rule.runOnIdle { imm.resetCalls() }
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(1, 1, -1, -1)")
-            imm.expectCall("updateSelection(0, 0, -1, -1)")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    @Test
-    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword_fromTransformation() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = object : InputTransformation {
-                    override val keyboardOptions: KeyboardOptions =
-                        KeyboardOptions(keyboardType = KeyboardType.Password)
-
-                    override fun transformInput(
-                        originalValue: TextFieldCharSequence,
-                        valueWithChanges: TextFieldBuffer
-                    ) {
-                        valueWithChanges.append('A')
-                    }
-                }
-            )
-        }
-        requestFocus(Tag)
-        rule.runOnIdle { imm.resetCalls() }
-
-        // "" -key-> "A" -filter> "AA"
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
-        // "AA" -key-> "A" -filter> "AA"
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
-
-        rule.runOnIdle {
-            imm.expectCall("updateSelection(2, 2, -1, -1)")
-            imm.expectCall("updateSelection(2, 2, -1, -1)")
-            imm.expectNoMoreCalls()
-        }
-    }
-
-    private fun requestFocus(tag: String) =
-        rule.onNodeWithTag(tag).requestFocus()
-}
-
-// sets the WindowInfo with isWindowFocused is true
-internal fun InputMethodInterceptor.setTextFieldTestContent(
-    content: @Composable () -> Unit
-) {
-    val windowInfo = object : WindowInfo {
-        override val isWindowFocused = true
-    }
-    this.setContent {
-        CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
-            Row {
-                // Extra focusable that takes initial focus when focus is cleared.
-                Box(Modifier.size(10.dp).focusable())
-                Box { content() }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2SemanticsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2SemanticsTest.kt
deleted file mode 100644
index a813559..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2SemanticsTest.kt
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.semantics.getOrNull
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertHasClickAction
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.hasImeAction
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.isEditable
-import androidx.compose.ui.test.isEnabled
-import androidx.compose.ui.test.isFocused
-import androidx.compose.ui.test.isNotEnabled
-import androidx.compose.ui.test.isNotFocused
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTextReplacement
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.ImeAction
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class BasicTextField2SemanticsTest : FocusedWindowTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val Tag = "TextField"
-
-    @Test
-    fun defaultSemantics() {
-        rule.setContent {
-            BasicTextField2(
-                modifier = Modifier.testTag(Tag),
-                state = remember { TextFieldState() },
-                decorator = {
-                    Column {
-                        BasicText("label")
-                        it()
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assertEditableTextEquals("")
-            .assertTextEquals("label", includeEditableText = false)
-            .assert(isEditable())
-            .assertHasClickAction()
-            .assert(hasSetTextAction())
-            .assert(hasImeAction(ImeAction.Default))
-            .assert(isNotFocused())
-            .assert(
-                SemanticsMatcher.expectValue(
-                    SemanticsProperties.TextSelectionRange,
-                    TextRange.Zero
-                )
-            )
-            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetText))
-            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
-            .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Password))
-            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetSelection))
-            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.GetTextLayoutResult))
-
-        val textLayoutResults = mutableListOf<TextLayoutResult>()
-        rule.onNodeWithTag(Tag)
-            .performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(textLayoutResults) }
-        assert(textLayoutResults.size == 1) { "TextLayoutResult is null" }
-    }
-
-    @Test
-    fun semantics_enabledStatus() {
-        var enabled by mutableStateOf(true)
-        rule.setContent {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                enabled = enabled
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isEnabled())
-
-        enabled = false
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotEnabled())
-    }
-
-    @Test
-    fun semantics_setTextAction() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotFocused())
-            .performTextReplacement("Hello")
-        rule.onNodeWithTag(Tag)
-            .assert(isFocused())
-            .assertTextEquals("Hello")
-
-        assertThat(state.text.toString()).isEqualTo("Hello")
-    }
-
-    @Test
-    fun semantics_performSetTextAction_whenReadOnly() {
-        val state = TextFieldState("", initialSelectionInChars = TextRange(1))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                readOnly = true
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .performTextReplacement("hello")
-
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun semantics_setTextAction_appliesFilter() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, changes ->
-                    if (changes.length > 1) {
-                        val newText = changes.asCharSequence().asSequence().joinToString("-")
-                        changes.replace(0, changes.length, newText)
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotFocused())
-            .performTextReplacement("Hello")
-        rule.onNodeWithTag(Tag)
-            .assert(isFocused())
-            .assertTextEquals("H-e-l-l-o")
-
-        assertThat(state.text.toString()).isEqualTo("H-e-l-l-o")
-    }
-
-    @Test
-    fun semantics_performTextInputAction() {
-        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotFocused())
-            .performTextInput("a")
-        rule.onNodeWithTag(Tag)
-            .assert(isFocused())
-            .assertTextEquals("Haello")
-
-        assertThat(state.text.toString()).isEqualTo("Haello")
-    }
-
-    @Test
-    fun semantics_performTextInputAction_whenReadOnly() {
-        val state = TextFieldState("", initialSelectionInChars = TextRange(1))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                readOnly = true
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .performTextInput("hello")
-
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun semantics_performTextInputAction_appliesFilter() {
-        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, changes ->
-                    val newChange = changes.asCharSequence().replace(Regex("a"), "")
-                    changes.replace(0, changes.length, newChange)
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotFocused())
-            .performTextInput("abc")
-        rule.onNodeWithTag(Tag)
-            .assert(isFocused())
-            .assertTextEquals("Hbcello")
-
-        assertThat(state.text.toString()).isEqualTo("Hbcello")
-    }
-
-    @Test
-    fun semantics_clickAction() {
-        rule.setContent {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag)
-            .assert(isNotFocused())
-            .performSemanticsAction(SemanticsActions.OnClick)
-        rule.onNodeWithTag(Tag)
-            .assert(isFocused())
-    }
-
-    @Test
-    fun semantics_imeOption() {
-        rule.setContent {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assert(hasImeAction(ImeAction.Search))
-    }
-
-    @Test
-    fun contentSemanticsAreSet_inTheFirstComposition() {
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-    }
-
-    @Test
-    fun contentSemanticsAreSet_afterRecomposition() {
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        state.setTextAndPlaceCursorAtEnd("hello2")
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello2")
-    }
-
-    @Test
-    fun selectionSemanticsAreSet_inTheFirstComposition() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            assertTextEquals("hello")
-            assertSelection(TextRange(2))
-        }
-    }
-
-    @Test
-    fun selectionSemanticsAreSet_afterRecomposition() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange.Zero)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            assertTextEquals("hello")
-            assertSelection(TextRange.Zero)
-        }
-
-        state.edit {
-            selectCharsIn(TextRange(2))
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            assertTextEquals("hello")
-            assertSelection(TextRange(2))
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun inputSelection_changesSelectionState() {
-        val state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2, 3))
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2, 3))
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun inputSelection_changesSelectionState_appliesFilter() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = { _, changes ->
-                    changes.revertAllChanges()
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2))
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-        }
-    }
-
-    @Test
-    fun textLayoutResultSemanticsAreSet_inTheFirstComposition() {
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
-            .isEqualTo("hello")
-    }
-
-    @Test
-    fun textLayoutResultSemanticsAreUpdated_afterRecomposition() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("")
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
-            .isEqualTo("hello")
-    }
-
-    @Test
-    fun semanticsAreSet_afterStateObjectChanges() {
-        val state1 = TextFieldState("hello", initialSelectionInChars = TextRange.Zero)
-        val state2 = TextFieldState("world", initialSelectionInChars = TextRange(2))
-        var chosenState by mutableStateOf(true)
-        rule.setContent {
-            BasicTextField2(
-                state = if (chosenState) state1 else state2,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            assertTextEquals("hello")
-            assertSelection(TextRange.Zero)
-        }
-
-        chosenState = false
-
-        with(rule.onNodeWithTag(Tag)) {
-            assertTextEquals("world")
-            assertSelection(TextRange(2))
-        }
-    }
-
-    @Test
-    fun semantics_paste_notAvailable_whenDisabledOrReadOnly() {
-        val state = TextFieldState("World!", initialSelectionInChars = TextRange(0))
-        var enabled by mutableStateOf(false)
-        var readOnly by mutableStateOf(false)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                enabled = enabled,
-                readOnly = readOnly
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.PasteText))
-
-        enabled = true
-        readOnly = true
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.PasteText))
-
-        enabled = true
-        readOnly = false
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.PasteText))
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_paste() {
-        val state = TextFieldState("Here World!")
-        val clipboardManager = FakeClipboardManager("Hello")
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 4))
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_paste_appliesFilter() {
-        val state = TextFieldState("Here World!")
-        val clipboardManager = FakeClipboardManager("Hello")
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag),
-                    inputTransformation = { _, changes ->
-                        // remove all 'l' characters
-                        if (changes.changes.changeCount != 0) {
-                            val newChange = changes.asCharSequence().replace(Regex("l"), "")
-                            changes.replace(0, changes.length, newChange)
-                            changes.placeCursorAtEnd()
-                        }
-                    }
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 4))
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(9))
-            assertThat(state.text.toString()).isEqualTo("Heo Word!")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_copy() {
-        val state = TextFieldState("Hello World!")
-        val clipboardManager = FakeClipboardManager()
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-        }
-    }
-
-    @Test
-    fun semantics_copy_disabled_whenSelectionCollapsed() {
-        val state = TextFieldState("Hello World!")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CopyText))
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_copy_appliesFilter() {
-        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
-        val clipboardManager = FakeClipboardManager()
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag),
-                    inputTransformation = { original, changes ->
-                        // reject copy action collapsing the selection
-                        if (changes.selectionInChars != original.selectionInChars) {
-                            changes.revertAllChanges()
-                        }
-                    }
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_cut() {
-        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
-        val clipboardManager = FakeClipboardManager()
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo(" World!")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun semantics_cut_appliesFilter() {
-        val state = TextFieldState("Hello World!", initialSelectionInChars = TextRange(0, 5))
-        val clipboardManager = FakeClipboardManager()
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag),
-                    inputTransformation = { _, changes ->
-                        changes.revertAllChanges()
-                    }
-                )
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CutText)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-        }
-    }
-
-    @Test
-    fun semantics_cut_notAvailable_whenDisabledOrReadOnly() {
-        val state = TextFieldState("World!", initialSelectionInChars = TextRange(0, 1))
-        var enabled by mutableStateOf(false)
-        var readOnly by mutableStateOf(false)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                enabled = enabled,
-                readOnly = readOnly
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CutText))
-
-        enabled = true
-        readOnly = true
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CutText))
-
-        enabled = true
-        readOnly = false
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyIsDefined(SemanticsActions.CutText))
-    }
-
-    @Test
-    fun semantics_isNotEditable_whenDisabledOrReadOnly() {
-        val state = TextFieldState()
-        var enabled by mutableStateOf(true)
-        var readOnly by mutableStateOf(false)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                enabled = enabled,
-                readOnly = readOnly
-            )
-        }
-        rule.onNodeWithTag(Tag).assert(isEditable())
-
-        enabled = true
-        readOnly = true
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
-
-        enabled = false
-        readOnly = false
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
-
-        enabled = false
-        readOnly = true
-        rule.onNodeWithTag(Tag).assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Editable))
-
-        // Make editable again.
-        enabled = true
-        readOnly = false
-        rule.onNodeWithTag(Tag).assert(isEditable())
-    }
-
-    private fun SemanticsNodeInteraction.assertSelection(expected: TextRange) {
-        val selection = fetchSemanticsNode().config
-            .getOrNull(SemanticsProperties.TextSelectionRange)
-        assertThat(selection).isEqualTo(expected)
-    }
-
-    private fun SemanticsNodeInteraction.assertEditableTextEquals(
-        value: String
-    ): SemanticsNodeInteraction =
-        assert(
-            SemanticsMatcher("${SemanticsProperties.EditableText.name} = '$value'") {
-                it.config.getOrNull(SemanticsProperties.EditableText)?.text.equals(value)
-            }
-        )
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2Test.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2Test.kt
deleted file mode 100644
index a6fff29..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/BasicTextField2Test.kt
+++ /dev/null
@@ -1,1445 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.text.InputType
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputConnection
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.KeyboardHelper
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.ClipboardManager
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.semantics.SemanticsProperties.TextSelectionRange
-import androidx.compose.ui.semantics.getOrNull
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.assertIsNotFocused
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTextReplacement
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.drop
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2Test {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @get:Rule
-    val immRule = ComposeInputMethodManagerTestRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicTextField2"
-
-    private val imm = FakeInputMethodManager()
-
-    @Test
-    fun textField_rendersEmptyContent() {
-        var textLayoutResult: (() -> TextLayoutResult?)? = null
-        inputMethodInterceptor.setTextFieldTestContent {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.fillMaxSize(),
-                onTextLayout = { textLayoutResult = it }
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(textLayoutResult).isNotNull()
-            assertThat(textLayoutResult?.invoke()?.layoutInput?.text).isEqualTo(AnnotatedString(""))
-        }
-    }
-
-    @Test
-    fun textFieldState_textChange_updatesState() {
-        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("World!")
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-        }
-    }
-
-    @Test
-    fun textFieldState_textChange_updatesSemantics() {
-        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("World!")
-
-        rule.onNodeWithTag(Tag).assertTextEquals("Hello World!")
-        assertTextSelection(TextRange("Hello World!".length))
-    }
-
-    @Test
-    fun stringValue_textChange_updatesState() {
-        var state by mutableStateOf("Hello ")
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = state,
-                onValueChange = { state = it },
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("World!")
-
-        rule.runOnIdle {
-            assertThat(state).isEqualTo("Hello World!")
-        }
-    }
-
-    /**
-     * This is a goal that we set for ourselves. Only updating the editing buffer should not cause
-     * BasicTextField to recompose.
-     */
-    @Test
-    fun textField_imeUpdatesDontCauseRecomposition() {
-        val state = TextFieldState()
-        var compositionCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            compositionCount++
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag),
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        rule.onNodeWithTag(Tag).performTextInput("world")
-
-        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
-        rule.runOnIdle {
-            assertThat(compositionCount).isEqualTo(1)
-        }
-    }
-
-    @Test
-    fun textField_textStyleFontSizeChange_relayouts() {
-        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
-        var style by mutableStateOf(TextStyle(fontSize = 20.sp))
-        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
-        val textLayoutResults = mutableListOf<TextLayoutResult?>()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag),
-                textStyle = style,
-                onTextLayout = { textLayoutResultState = it }
-            )
-
-            LaunchedEffect(Unit) {
-                snapshotFlow { textLayoutResultState?.invoke() }
-                    .drop(1)
-                    .collect { textLayoutResults += it }
-            }
-        }
-
-        style = TextStyle(fontSize = 30.sp)
-
-        rule.runOnIdle {
-            assertThat(textLayoutResults.size).isEqualTo(2)
-            assertThat(textLayoutResults.map { it?.layoutInput?.style?.fontSize })
-                .containsExactly(20.sp, 30.sp)
-                .inOrder()
-        }
-    }
-
-    @Test
-    fun textField_textStyleColorChange_doesNotRelayout() {
-        val state = TextFieldState("Hello")
-        var style by mutableStateOf(TextStyle(color = Color.Red))
-        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
-        val textLayoutResults = mutableListOf<TextLayoutResult?>()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag),
-                textStyle = style,
-                onTextLayout = { textLayoutResultState = it }
-            )
-
-            LaunchedEffect(Unit) {
-                snapshotFlow { textLayoutResultState?.invoke() }
-                    .drop(1)
-                    .collect { textLayoutResults += it }
-            }
-        }
-
-        style = TextStyle(color = Color.Blue)
-
-        rule.runOnIdle {
-            assertThat(textLayoutResults.size).isEqualTo(2)
-            assertThat(textLayoutResults[0]?.multiParagraph)
-                .isSameInstanceAs(textLayoutResults[1]?.multiParagraph)
-            assertThat(textLayoutResults[0]?.layoutInput?.style?.color).isEqualTo(Color.Red)
-            assertThat(textLayoutResults[1]?.layoutInput?.style?.color).isEqualTo(Color.Blue)
-        }
-    }
-
-    @Test
-    fun textField_contentChange_relayouts() {
-        val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
-        var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
-        val textLayoutResults = mutableListOf<TextLayoutResult?>()
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
-                override val isWindowFocused = true
-            }) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier
-                        .fillMaxSize()
-                        .testTag(Tag),
-                    onTextLayout = { textLayoutResultState = it }
-                )
-            }
-
-            LaunchedEffect(Unit) {
-                snapshotFlow { textLayoutResultState?.invoke() }
-                    .drop(1)
-                    .collect { textLayoutResults += it }
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("World!")
-
-        rule.runOnIdle {
-            assertThat(textLayoutResults.map { it?.layoutInput?.text?.text })
-                .containsExactly("Hello ", "Hello World!")
-                .inOrder()
-        }
-    }
-
-    @Test
-    fun textField_focus_showsSoftwareKeyboard() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performClick()
-        rule.onNodeWithTag(Tag).assertIsFocused()
-
-        inputMethodInterceptor.assertSessionActive()
-    }
-
-    @Test
-    fun textField_focus_doesNotShowSoftwareKeyboard_ifDisabled() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                enabled = false,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertIsNotEnabled()
-        rule.onNodeWithTag(Tag).performClick()
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun textField_focus_doesNotShowSoftwareKeyboard_ifReadOnly() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                readOnly = true,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performClick()
-        rule.onNodeWithTag(Tag).assertIsFocused()
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun textField_focus_doesNotShowSoftwareKeyboard_whenNotShowSoftwareKeyboard() {
-        val state = TextFieldState()
-        val focusRequester = FocusRequester()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-                    .focusRequester(focusRequester)
-            )
-        }
-        rule.runOnUiThread {
-            focusRequester.requestFocus()
-        }
-        rule.waitForIdle()
-        rule.onNodeWithTag(Tag).assertIsFocused()
-
-        inputMethodInterceptor.assertNoSessionActive()
-    }
-
-    @Test
-    fun textField_tap_showSoftwareKeyboard_whenNotShowSoftwareKeyboard() {
-        val state = TextFieldState()
-        val focusRequester = FocusRequester()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-                    .focusRequester(focusRequester)
-            )
-        }
-        rule.runOnUiThread {
-            focusRequester.requestFocus()
-        }
-        rule.waitForIdle()
-        rule.onNodeWithTag(Tag).assertIsFocused()
-        rule.onNodeWithTag(Tag).performClick()
-
-        inputMethodInterceptor.assertSessionActive()
-    }
-
-    @Test
-    fun hideKeyboardWhenDisposed() {
-        val keyboardHelper = KeyboardHelper(rule)
-        val state = TextFieldState("initial text")
-        var toggle by mutableStateOf(true)
-        rule.setContent {
-            keyboardHelper.initialize()
-
-            if (toggle) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag("TextField")
-                )
-            }
-        }
-
-        rule.onNodeWithTag("TextField").requestFocus()
-        keyboardHelper.waitForKeyboardVisibility(true)
-        assertTrue(keyboardHelper.isSoftwareKeyboardShown())
-
-        toggle = false
-        rule.waitForIdle()
-
-        keyboardHelper.waitForKeyboardVisibility(false)
-        assertFalse(keyboardHelper.isSoftwareKeyboardShown())
-    }
-
-    @Test
-    fun hideKeyboardWhenFocusCleared() {
-        val keyboardHelper = KeyboardHelper(rule)
-        val state = TextFieldState("initial text")
-        lateinit var focusManager: FocusManager
-        rule.setContent {
-            keyboardHelper.initialize()
-            focusManager = LocalFocusManager.current
-            Row {
-                // Extra focusable that takes initial focus when focus is cleared.
-                Box(
-                    Modifier
-                        .size(10.dp)
-                        .focusable())
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag("TextField")
-                )
-            }
-        }
-
-        rule.onNodeWithTag("TextField").requestFocus()
-        keyboardHelper.waitForKeyboardVisibility(true)
-        assertTrue(keyboardHelper.isSoftwareKeyboardShown())
-
-        rule.runOnIdle {
-            focusManager.clearFocus()
-        }
-
-        keyboardHelper.waitForKeyboardVisibility(false)
-        assertFalse(keyboardHelper.isSoftwareKeyboardShown())
-    }
-
-    @Test
-    fun textField_whenStateObjectChanges_newTextIsRendered() {
-        val state1 = TextFieldState("Hello")
-        val state2 = TextFieldState("World")
-        var toggleState by mutableStateOf(true)
-        val state by derivedStateOf { if (toggleState) state1 else state2 }
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                enabled = true,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("Hello")
-        toggleState = !toggleState
-        rule.onNodeWithTag(Tag).assertTextEquals("World")
-    }
-
-    @Test
-    fun textField_whenStateObjectChanges_restartsInput() {
-        val state1 = TextFieldState("Hello")
-        val state2 = TextFieldState("World")
-        var toggleState by mutableStateOf(true)
-        val state by derivedStateOf { if (toggleState) state1 else state2 }
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                enabled = true,
-                modifier = Modifier
-                    .fillMaxSize()
-                    .testTag(Tag)
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            performTextReplacement("Compose")
-            assertTextEquals("Compose")
-        }
-        toggleState = !toggleState
-        with(rule.onNodeWithTag(Tag)) {
-            performTextReplacement("Compose2")
-            assertTextEquals("Compose2")
-        }
-        assertThat(state1.text.toString()).isEqualTo("Compose")
-        assertThat(state2.text.toString()).isEqualTo("Compose2")
-    }
-
-    @Test
-    fun textField_passesKeyboardOptionsThrough() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                // We don't need to test all combinations here, that is tested in EditorInfoTest.
-                keyboardOptions = KeyboardOptions(
-                    capitalization = KeyboardCapitalization.Characters,
-                    keyboardType = KeyboardType.Email,
-                    imeAction = ImeAction.Previous
-                )
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
-            assertThat(inputType and EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
-            assertThat(inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textField_appliesFilter_toInputConnection() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = RejectAllTextFilter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection { commitText("hello") }
-        rule.onNodeWithTag(Tag).assertTextEquals("")
-    }
-
-    @Test
-    fun textField_appliesFilter_toSetTextSemanticsAction() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = RejectAllTextFilter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextReplacement("hello")
-        rule.onNodeWithTag(Tag).assertTextEquals("")
-    }
-
-    @Test
-    fun textField_appliesFilter_toInsertTextSemanticsAction() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = RejectAllTextFilter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        rule.onNodeWithTag(Tag).assertTextEquals("")
-    }
-
-    @Test
-    fun textField_appliesFilter_toKeyEvents() {
-        val state = TextFieldState()
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = RejectAllTextFilter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
-        rule.onNodeWithTag(Tag).assertTextEquals("")
-    }
-
-    @Test
-    fun textField_appliesFilter_toInputConnection_afterChanging() {
-        val state = TextFieldState()
-        var filter by mutableStateOf<InputTransformation?>(null)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = filter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection { commitText("hello") }
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = RejectAllTextFilter
-
-        inputMethodInterceptor.withInputConnection { commitText("world") }
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = null
-
-        inputMethodInterceptor.withInputConnection { commitText("world") }
-        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
-    }
-
-    @Test
-    fun textField_appliesFilter_toSetTextSemanticsAction_afterChanging() {
-        val state = TextFieldState()
-        var filter by mutableStateOf<InputTransformation?>(null)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = filter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = RejectAllTextFilter
-
-        rule.onNodeWithTag(Tag).performTextReplacement("world")
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = null
-
-        rule.onNodeWithTag(Tag).performTextReplacement("world")
-        rule.onNodeWithTag(Tag).assertTextEquals("world")
-    }
-
-    @Test
-    fun textField_appliesFilter_toInsertTextSemanticsAction_afterChanging() {
-        val state = TextFieldState()
-        var filter by mutableStateOf<InputTransformation?>(null)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = filter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = RejectAllTextFilter
-
-        rule.onNodeWithTag(Tag).performTextInput("world")
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = null
-
-        rule.onNodeWithTag(Tag).performTextInput("world")
-        rule.onNodeWithTag(Tag).assertTextEquals("helloworld")
-    }
-
-    @Test
-    fun textField_appliesFilter_toKeyEvents_afterChanging() {
-        val state = TextFieldState()
-        var filter by mutableStateOf<InputTransformation?>(null)
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = filter,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = RejectAllTextFilter
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Spacebar) }
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-
-        filter = null
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Spacebar) }
-        rule.onNodeWithTag(Tag).assertTextEquals("hello ")
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenInputConnectionCommits() {
-        val state = TextFieldState()
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection { commitText("hello") }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0, 0))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenInputConnectionComposes() {
-        val state = TextFieldState()
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection { setComposingText("hello", 1) }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenInputConnectionDeletes() {
-        val state = TextFieldState("hello")
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            beginBatchEdit()
-            finishComposingText()
-            setSelection(5, 5)
-            deleteSurroundingText(1, 0)
-            endBatchEdit()
-        }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(4, 4))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenInputConnectionDeletesViaComposition() {
-        val state = TextFieldState("hello")
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            beginBatchEdit()
-            setComposingRegion(0, 5)
-            setComposingText("h", 1)
-            endBatchEdit()
-        }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(1, 1))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(1, 5))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenKeyEventInserts() {
-        val state = TextFieldState()
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-        requestFocus(Tag)
-
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 1))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenKeyEventDeletes() {
-        val state = TextFieldState("hello")
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(4, 4))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
-        }
-    }
-
-    @Test
-    fun textField_changesAreTracked_whenSemanticsActionInserts() {
-        val state = TextFieldState()
-        lateinit var changes: ChangeList
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                inputTransformation = { _, new ->
-                    if (new.changes.changeCount > 0) {
-                        changes = new.changes
-                    }
-                },
-                modifier = Modifier.testTag(Tag),
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTextInput("hello")
-
-        rule.runOnIdle {
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(0, 5))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-        }
-    }
-
-    @Test
-    fun textField_filterKeyboardOptions_sentToIme() {
-        val filter = KeyboardOptionsFilter(
-            KeyboardOptions(
-                keyboardType = KeyboardType.Email,
-                imeAction = ImeAction.Previous
-            )
-        )
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = filter,
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
-            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textField_filterKeyboardOptions_mergedWithParams() {
-        val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = filter,
-                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
-            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textField_filterKeyboardOptions_overriddenByParams() {
-        val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag),
-                inputTransformation = filter,
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
-            )
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_SEARCH).isNotEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textField_filterKeyboardOptions_applyWhenFilterChanged() {
-        var filter by mutableStateOf(
-            KeyboardOptionsFilter(
-                KeyboardOptions(
-                    keyboardType = KeyboardType.Email,
-                    imeAction = ImeAction.Previous
-                )
-            )
-        )
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
-                override val isWindowFocused = true
-            }) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier.testTag(Tag),
-                    inputTransformation = filter,
-                )
-            }
-        }
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_PREVIOUS).isNotEqualTo(0)
-            assertThat(inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotEqualTo(0)
-        }
-
-        filter = KeyboardOptionsFilter(
-            KeyboardOptions(
-                keyboardType = KeyboardType.Decimal,
-                imeAction = ImeAction.Search
-            )
-        )
-
-        inputMethodInterceptor.withEditorInfo {
-            assertThat(imeOptions and EditorInfo.IME_ACTION_SEARCH).isNotEqualTo(0)
-            assertThat(inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotEqualTo(0)
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 23)
-    @Test
-    fun textField_showsKeyboardAgainWhenTapped_ifFocused() {
-        val testKeyboardController = TestSoftwareKeyboardController(rule)
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(
-                LocalSoftwareKeyboardController provides testKeyboardController
-            ) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-        // Focusing the field will show the keyboard without using the SoftwareKeyboardController.
-        rule.onNodeWithTag(Tag).requestFocus()
-        testKeyboardController.hide()
-
-        // This will go through the SoftwareKeyboardController to show the keyboard, since a session
-        // is already active.
-        rule.onNodeWithTag(Tag).performClick()
-
-        testKeyboardController.assertShown()
-    }
-
-    @Test
-    fun swipingThroughTextField_doesNotGainFocus() {
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // swipe through
-            swipeRight(endX = right + 200, durationMillis = 100)
-        }
-        rule.onNodeWithTag(Tag).assertIsNotFocused()
-    }
-
-    @Test
-    fun swipingTextFieldInScrollableContainer_doesNotGainFocus() {
-        val scrollState = ScrollState(0)
-        inputMethodInterceptor.setTextFieldTestContent {
-            Column(
-                Modifier
-                    .height(100.dp)
-                    .verticalScroll(scrollState)
-            ) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier.testTag(Tag)
-                )
-                Box(Modifier.height(200.dp))
-            }
-        }
-
-        rule.onNodeWithTag(Tag).performTouchInput { swipeUp() }
-        rule.onNodeWithTag(Tag).assertIsNotFocused()
-        assertThat(scrollState.value).isNotEqualTo(0)
-    }
-
-    @Test
-    fun densityChanges_causesRelayout() {
-        val state = TextFieldState("Hello")
-        var density by mutableStateOf(Density(1f))
-        val fontSize = 20.sp
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalDensity provides density) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = TextStyle(
-                        fontFamily = TEST_FONT_FAMILY,
-                        fontSize = fontSize
-                    ),
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        val firstSize = rule.onNodeWithTag(Tag).fetchTextLayoutResult().size
-
-        density = Density(2f)
-
-        val secondSize = rule.onNodeWithTag(Tag).fetchTextLayoutResult().size
-
-        assertThat(secondSize.width).isEqualTo(firstSize.width * 2)
-        assertThat(secondSize.height).isEqualTo(firstSize.height * 2)
-    }
-
-    @Test
-    fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
-        var text by mutableStateOf("hello")
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = { text = it },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        rule.runOnIdle {
-            text = "world"
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("world")
-    }
-
-    @Test
-    fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
-        var text by mutableStateOf("hello")
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = { text = it },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            text = "world"
-        }
-
-        rule.onNodeWithTag(Tag).assertTextEquals("hello")
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_onFocus() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        // Act: wiggle the cursor around a bit.
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        requestFocus(Tag)
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        // Act: wiggle the composition around a bit
-        inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
-        inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-
-        rule.runOnIdle {
-            text = "hello"
-        }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
-        var text by mutableStateOf("")
-        var onValueChangedCount = 0
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                value = text,
-                onValueChange = {
-                    text = it
-                    onValueChangedCount++
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-        assertThat(onValueChangedCount).isEqualTo(0)
-        requestFocus(Tag)
-
-        rule.runOnIdle {
-            text = "hello"
-        }
-
-        rule.runOnIdle {
-            assertThat(onValueChangedCount).isEqualTo(0)
-        }
-    }
-
-    // Regression test for b/311834126
-    @Test
-    fun whenPastingTextThatIncreasesEndOffset_noCrashAndCursorAtEndOfPastedText() {
-        val longText = "Text".repeat(4)
-        val shortText = "Text".repeat(2)
-
-        lateinit var tfs: TextFieldState
-        val clipboardManager = object : ClipboardManager {
-            var contents: AnnotatedString? = null
-
-            override fun setText(annotatedString: AnnotatedString) {
-                contents = annotatedString
-            }
-
-            override fun getText(): AnnotatedString? {
-                return contents
-            }
-        }
-        inputMethodInterceptor.setTextFieldTestContent {
-            tfs = rememberTextFieldState(shortText)
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = tfs,
-                    modifier = Modifier.testTag(Tag),
-                )
-            }
-        }
-        clipboardManager.setText(AnnotatedString(longText))
-        rule.waitForIdle()
-
-        val node = rule.onNodeWithTag(Tag)
-        node.performTouchInput { longClick(center) }
-        rule.waitForIdle()
-
-        node.performSemanticsAction(SemanticsActions.PasteText) { it() }
-        rule.waitForIdle()
-
-        assertThat(tfs.text.toString()).isEqualTo(longText)
-        assertThat(tfs.text.selectionInChars).isEqualTo(TextRange(longText.length))
-    }
-
-    @Test
-    fun selectAll_contextMenuAction_informsImeOfSelectionChange() {
-        immRule.setFactory { imm }
-        val state = TextFieldState("Hello")
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            performContextMenuAction(android.R.id.selectAll)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(imm.expectCall("updateSelection(0, 5, -1, -1)"))
-        }
-    }
-
-    @Test
-    fun cut_contextMenuAction_cutsIntoClipboard() {
-        val clipboardManager = FakeClipboardManager("World")
-        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            performContextMenuAction(android.R.id.cut)
-        }
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.text).isEqualTo("He")
-            assertThat(state.text.toString()).isEqualTo("llo")
-        }
-    }
-
-    @Test
-    fun copy_contextMenuAction_copiesIntoClipboard() {
-        val clipboardManager = FakeClipboardManager("World")
-        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            performContextMenuAction(android.R.id.copy)
-        }
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.text).isEqualTo("He")
-        }
-    }
-
-    @Test
-    fun paste_contextMenuAction_pastesFromClipboard() {
-        val clipboardManager = FakeClipboardManager("World")
-        val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 4))
-        inputMethodInterceptor.setTextFieldTestContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.testTag(Tag)
-                )
-            }
-        }
-
-        requestFocus(Tag)
-
-        inputMethodInterceptor.withInputConnection {
-            performContextMenuAction(android.R.id.paste)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("Worldo")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-        }
-    }
-
-    private fun requestFocus(tag: String) =
-        rule.onNodeWithTag(tag).requestFocus()
-
-    private fun assertTextSelection(expected: TextRange) {
-        val selection = rule.onNodeWithTag(Tag).fetchSemanticsNode()
-            .config.getOrNull(TextSelectionRange)
-        assertThat(selection).isEqualTo(expected)
-    }
-
-    private fun InputConnection.commitText(text: String) {
-        beginBatchEdit()
-        finishComposingText()
-        commitText(text, 1)
-        endBatchEdit()
-    }
-
-    private object RejectAllTextFilter : InputTransformation {
-        override fun transformInput(
-            originalValue: TextFieldCharSequence,
-            valueWithChanges: TextFieldBuffer
-        ) {
-            valueWithChanges.revertAllChanges()
-        }
-    }
-
-    private class KeyboardOptionsFilter(override val keyboardOptions: KeyboardOptions) :
-        InputTransformation {
-        override fun transformInput(
-            originalValue: TextFieldCharSequence,
-            valueWithChanges: TextFieldBuffer
-        ) {
-            // Noop
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/ComposeInputMethodManagerTestRule.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/ComposeInputMethodManagerTestRule.kt
deleted file mode 100644
index 63ad2fe..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/ComposeInputMethodManagerTestRule.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import android.view.View
-import androidx.compose.foundation.text2.input.internal.ComposeInputMethodManager
-import androidx.compose.foundation.text2.input.internal.overrideComposeInputMethodManagerFactoryForTests
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Rule to help setting the factory used to create [ComposeInputMethodManager] instances for tests.
- * Restores the previous factory after the test finishes.
- */
-internal class ComposeInputMethodManagerTestRule : TestRule {
-    private var initialFactory: ((View) -> ComposeInputMethodManager)? = null
-
-    fun setFactory(factory: (View) -> ComposeInputMethodManager) {
-        val previousFactory = overrideComposeInputMethodManagerFactoryForTests(factory)
-        if (initialFactory == null) {
-            initialFactory = previousFactory
-        }
-    }
-
-    override fun apply(base: Statement, description: Description): Statement =
-        object : Statement() {
-            override fun evaluate() {
-                try {
-                    base.evaluate()
-                } finally {
-                    // Reset the factory if it was set during the test so the next test gets the
-                    // default behavior.
-                    initialFactory?.let(::overrideComposeInputMethodManagerFactoryForTests)
-                }
-            }
-        }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/DecorationBoxTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/DecorationBoxTest.kt
deleted file mode 100644
index 511d1a1..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/DecorationBoxTest.kt
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.isFocused
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class DecorationBoxTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val Tag = "BasicTextField2"
-    private val DecorationTag = "DecorationBox"
-
-    @Test
-    fun focusIsAppliedOnDecoratedComposable() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = { innerTextField ->
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                            .testTag(DecorationTag)
-                    ) {
-                        innerTextField()
-                    }
-                }
-            )
-        }
-
-        // requestFocus on node
-        rule.onNodeWithTag(Tag).performClick()
-
-        // assertThat decoration modifier has a focused parent.
-        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).assert(hasParent(isFocused()))
-    }
-
-    @Test
-    fun semanticsAreAppliedOnDecoratedComposable() {
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = { innerTextField ->
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                            .testTag(DecorationTag)
-                    ) {
-                        innerTextField()
-                    }
-                }
-            )
-        }
-
-        // assertThat decoration modifier has a focused parent.
-        with(rule.onNodeWithTag(DecorationTag, useUnmergedTree = true)) {
-            assert(hasParent(hasText("hello")))
-            assert(hasParent(hasSetTextAction()))
-        }
-    }
-
-    @Test
-    fun clickGestureIsAppliedOnDecoratedComposable() {
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = { innerTextField ->
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                            .testTag(DecorationTag)
-                    ) {
-                        innerTextField()
-                    }
-                }
-            )
-        }
-
-        // click on decoration box
-        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).performTouchInput {
-            // should be on the box not on inner text field since there is a padding
-            click(Offset(1f, 1f))
-        }
-
-        // assertThat textfield has focus
-        rule.onNodeWithTag(Tag).assertIsFocused()
-    }
-
-    @Test
-    fun nonPlacedInnerTextField_stillAcceptsTextInput() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = {
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                    )
-                }
-            )
-        }
-
-        // requestFocus on node
-        with(rule.onNodeWithTag(Tag)) {
-            performClick()
-            performTextInput("hello")
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("hello")
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun nonPlacedInnerTextField_stillAcceptsKeyInput() {
-        val state = TextFieldState()
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = {
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                    )
-                }
-            )
-        }
-
-        // requestFocus on node
-        with(rule.onNodeWithTag(Tag)) {
-            performClick()
-            performKeyInput {
-                pressKey(Key.H)
-                pressKey(Key.E)
-                pressKey(Key.L)
-                pressKey(Key.L)
-                pressKey(Key.O)
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("hello")
-        }
-    }
-
-    @Test
-    fun minConstraintsArePropagated() {
-        val state = TextFieldState()
-        var decorationBoxConstraints: Constraints? = null
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.fillMaxSize().testTag(Tag),
-                decorator = {
-                    Layout { _, constraints ->
-                        decorationBoxConstraints = constraints
-                        layout(0, 0) {}
-                    }
-                }
-            )
-        }
-
-        rule.waitForIdle()
-
-        assertThat(decorationBoxConstraints?.minWidth)
-            .isNotEqualTo(0)
-        assertThat(decorationBoxConstraints?.minWidth)
-            .isEqualTo(decorationBoxConstraints?.maxWidth)
-
-        assertThat(decorationBoxConstraints?.minHeight)
-            .isNotEqualTo(0)
-        assertThat(decorationBoxConstraints?.minHeight)
-            .isEqualTo(decorationBoxConstraints?.maxHeight)
-    }
-
-    @Ignore // TODO(halilibo): enable when pointerInput gestures are enabled
-    @Test
-    fun longClickGestureIsAppliedOnDecoratedComposable() {
-        // create a decorated BasicTextField2
-        val state = TextFieldState("hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                decorator = { innerTextField ->
-                    Box(
-                        modifier = Modifier
-                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
-                            .padding(16.dp)
-                            .testTag(DecorationTag)
-                    ) {
-                        innerTextField()
-                    }
-                }
-            )
-        }
-
-        // click on decoration box
-        rule.onNodeWithTag(DecorationTag, useUnmergedTree = true).performTouchInput {
-            // should be on the box not on inner text field since there is a padding
-            longClick(Offset(1f, 1f))
-        }
-
-        // assertThat selection happened
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/FakeInputMethodManager.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/FakeInputMethodManager.kt
deleted file mode 100644
index b396365..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/FakeInputMethodManager.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import android.view.KeyEvent
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.ExtractedText
-import androidx.compose.foundation.text2.input.internal.ComposeInputMethodManager
-import com.google.common.truth.Truth.assertThat
-
-internal class FakeInputMethodManager : ComposeInputMethodManager {
-    private val calls = mutableListOf<String>()
-
-    fun expectCall(description: String) {
-        assertThat(calls.removeFirst()).isEqualTo(description)
-    }
-
-    fun expectNoMoreCalls() {
-        assertThat(calls).isEmpty()
-    }
-
-    fun resetCalls() {
-        calls.clear()
-    }
-
-    override fun restartInput() {
-        calls += "restartInput"
-    }
-
-    override fun showSoftInput() {
-        calls += "showSoftInput"
-    }
-
-    override fun hideSoftInput() {
-        calls += "hideSoftInput"
-    }
-
-    override fun updateExtractedText(token: Int, extractedText: ExtractedText) {
-        calls += "updateExtractedText"
-    }
-
-    override fun updateSelection(
-        selectionStart: Int,
-        selectionEnd: Int,
-        compositionStart: Int,
-        compositionEnd: Int
-    ) {
-        calls += "updateSelection($selectionStart, $selectionEnd, " +
-            "$compositionStart, $compositionEnd)"
-    }
-
-    override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
-        calls += "updateCursorAnchorInfo"
-    }
-
-    override fun sendKeyEvent(event: KeyEvent) {
-        calls += "sendKeyEvent"
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/HeightInLinesModifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/HeightInLinesModifierTest.kt
deleted file mode 100644
index 7212597..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/HeightInLinesModifierTest.kt
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 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.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package androidx.compose.foundation.text2.input
-
-import android.content.Context
-import android.graphics.Typeface
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.text.TEST_FONT
-import androidx.compose.foundation.text.heightInLines
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.InspectableValue
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalFontFamilyResolver
-import androidx.compose.ui.platform.ValueElement
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.AndroidFont
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontLoadingStrategy
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontVariation
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.createFontFamilyResolver
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class HeightInLinesModifierTest {
-
-    private val longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
-        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
-        " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
-        "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-        "fugiat nulla pariatur."
-
-    private val context = InstrumentationRegistry.getInstrumentation().context
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun minLines_shortInputText() {
-        var subjectLayout: (() -> TextLayoutResult?)? = null
-        var subjectHeight: Int? = null
-        var twoLineHeight: Int? = null
-        val positionedLatch = CountDownLatch(1)
-        val twoLinePositionedLatch = CountDownLatch(1)
-
-        rule.setContent {
-            HeightObservingText(
-                onGlobalHeightPositioned = {
-                    subjectHeight = it
-                    positionedLatch.countDown()
-                },
-                onTextLayoutResult = {
-                    subjectLayout = it
-                },
-                text = "abc",
-                lineLimits = MultiLine(minHeightInLines = 2)
-            )
-            HeightObservingText(
-                onGlobalHeightPositioned = {
-                    twoLineHeight = it
-                    twoLinePositionedLatch.countDown()
-                },
-                onTextLayoutResult = {},
-                text = "1\n2",
-                lineLimits = MultiLine(minHeightInLines = 2)
-            )
-        }
-        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
-        assertThat(twoLinePositionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
-
-        rule.runOnIdle {
-            assertThat(subjectLayout).isNotNull()
-            assertThat(subjectLayout!!.invoke()?.lineCount).isEqualTo(1)
-            assertThat(subjectHeight!!).isEqualTo(twoLineHeight)
-        }
-    }
-
-    @Test
-    fun maxLines_shortInputText() {
-        val (textLayoutResult, height) = setTextFieldWithMaxLines(
-            text = "abc",
-            lines = MultiLine(maxHeightInLines = 5)
-        )
-
-        rule.runOnIdle {
-            assertThat(textLayoutResult).isNotNull()
-            assertThat(textLayoutResult!!.invoke()?.lineCount).isEqualTo(1)
-            assertThat(textLayoutResult()?.size?.height).isEqualTo(height)
-        }
-    }
-
-    @Test
-    fun maxLines_notApplied_infiniteMaxLines() {
-        val (textLayoutResult, height) =
-            setTextFieldWithMaxLines(longText, MultiLine(minHeightInLines = Int.MAX_VALUE))
-
-        rule.runOnIdle {
-            assertThat(textLayoutResult).isNotNull()
-            assertThat(textLayoutResult!!.invoke()?.size?.height).isEqualTo(height)
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun minLines_invalidValue() {
-        rule.setContent {
-            Box(
-                modifier = Modifier.heightInLines(textStyle = TextStyle.Default, minLines = 0)
-            )
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun maxLines_invalidValue() {
-        rule.setContent {
-            Box(
-                modifier = Modifier.heightInLines(textStyle = TextStyle.Default, maxLines = 0)
-            )
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun minLines_greaterThan_maxLines_invalidValue() {
-        rule.setContent {
-            Box(
-                modifier = Modifier.heightInLines(
-                    textStyle = TextStyle.Default,
-                    minLines = 2,
-                    maxLines = 1
-                )
-            )
-        }
-    }
-
-    @Test
-    fun minLines_longInputText() {
-        val (textLayoutResult, height) = setTextFieldWithMaxLines(
-            text = longText,
-            MultiLine(minHeightInLines = 2)
-        )
-
-        rule.runOnIdle {
-            assertThat(textLayoutResult).isNotNull()
-            // should be in the 20s, but use this to create invariant for the next assertion
-            assertThat(textLayoutResult!!.invoke()?.lineCount).isGreaterThan(2)
-            assertThat(textLayoutResult()?.size?.height).isEqualTo(height)
-        }
-    }
-
-    @Test
-    fun maxLines_longInputText() {
-        var subjectLayout: (() -> TextLayoutResult?)? = null
-        var subjectHeight: Int? = null
-        var twoLineHeight: Int? = null
-        val positionedLatch = CountDownLatch(1)
-        val twoLinePositionedLatch = CountDownLatch(1)
-
-        rule.setContent {
-            HeightObservingText(
-                onGlobalHeightPositioned = {
-                    subjectHeight = it
-                    positionedLatch.countDown()
-                },
-                onTextLayoutResult = {
-                    subjectLayout = it
-                },
-                text = longText,
-                lineLimits = MultiLine(maxHeightInLines = 2)
-            )
-            HeightObservingText(
-                onGlobalHeightPositioned = {
-                    twoLineHeight = it
-                    twoLinePositionedLatch.countDown()
-                },
-                onTextLayoutResult = {},
-                text = "1\n2",
-                lineLimits = MultiLine(maxHeightInLines = 2)
-            )
-        }
-        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
-        assertThat(twoLinePositionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
-
-        rule.runOnIdle {
-            assertThat(subjectLayout).isNotNull()
-            // should be in the 20s, but use this to create invariant for the next assertion
-            assertThat(subjectLayout!!.invoke()?.lineCount).isGreaterThan(2)
-            assertThat(subjectHeight!!).isEqualTo(twoLineHeight)
-        }
-    }
-
-    @OptIn(ExperimentalTextApi::class, ExperimentalCoroutinesApi::class)
-    @Test
-    fun asyncFontLoad_changesLineHeight() {
-        val testDispatcher = UnconfinedTestDispatcher()
-        val resolver = createFontFamilyResolver(context, testDispatcher)
-
-        val typefaceDeferred = CompletableDeferred<Typeface>()
-        val asyncLoader = object : AndroidFont.TypefaceLoader {
-            override fun loadBlocking(context: Context, font: AndroidFont): Typeface =
-                TODO("Not yet implemented")
-
-            override suspend fun awaitLoad(context: Context, font: AndroidFont): Typeface {
-                return typefaceDeferred.await()
-            }
-        }
-        val fontFamily = FontFamily(
-            object : AndroidFont(FontLoadingStrategy.Async, asyncLoader, FontVariation.Settings()) {
-                override val weight: FontWeight = FontWeight.Normal
-                override val style: FontStyle = FontStyle.Normal
-            },
-            TEST_FONT
-        )
-
-        val heights = mutableListOf<Int>()
-
-        rule.setContent {
-            CompositionLocalProvider(
-                LocalFontFamilyResolver provides resolver,
-                LocalDensity provides Density(1.0f, 1f)
-            ) {
-                HeightObservingText(
-                    onGlobalHeightPositioned = {
-                        heights.add(it)
-                    },
-                    onTextLayoutResult = {},
-                    text = longText,
-                    lineLimits = MultiLine(maxHeightInLines = 10),
-                    textStyle = TextStyle.Default.copy(
-                        fontFamily = fontFamily,
-                        fontSize = 80.sp
-                    )
-                )
-            }
-        }
-
-        val before = heights.toList()
-        typefaceDeferred.complete(Typeface.create("cursive", Typeface.BOLD_ITALIC))
-
-        rule.runOnIdle {
-            assertThat(heights.size).isGreaterThan(before.size)
-            assertThat(heights.distinct().size).isGreaterThan(before.distinct().size)
-        }
-    }
-
-    @Test
-    fun testInspectableValue() {
-        isDebugInspectorInfoEnabled = true
-
-        val modifier = Modifier.heightInLines(
-            textStyle = TextStyle.Default,
-            minLines = 5,
-            maxLines = 10
-        ) as InspectableValue
-        assertThat(modifier.nameFallback).isEqualTo("heightInLines")
-        assertThat(modifier.inspectableElements.asIterable()).containsExactly(
-            ValueElement("minLines", 5),
-            ValueElement("maxLines", 10),
-            ValueElement("textStyle", TextStyle.Default)
-        )
-
-        isDebugInspectorInfoEnabled = false
-    }
-
-    private fun setTextFieldWithMaxLines(
-        text: String,
-        lines: MultiLine
-    ): Pair<(() -> TextLayoutResult?)?, Int?> {
-        var textLayoutResult: (() -> TextLayoutResult?)? = null
-        var height: Int? = null
-        val positionedLatch = CountDownLatch(1)
-
-        rule.setContent {
-            HeightObservingText(
-                onGlobalHeightPositioned = {
-                    height = it
-                    positionedLatch.countDown()
-                },
-                onTextLayoutResult = {
-                    textLayoutResult = it
-                },
-                text = text,
-                lineLimits = lines
-            )
-        }
-        assertThat(positionedLatch.await(1, TimeUnit.SECONDS)).isTrue()
-
-        return Pair(textLayoutResult, height)
-    }
-
-    @Composable
-    private fun HeightObservingText(
-        onGlobalHeightPositioned: (Int) -> Unit,
-        onTextLayoutResult: Density.(getResult: () -> TextLayoutResult?) -> Unit,
-        text: String,
-        lineLimits: MultiLine,
-        textStyle: TextStyle = TextStyle.Default
-    ) {
-        Box(
-            Modifier.onGloballyPositioned {
-                onGlobalHeightPositioned(it.size.height)
-            }
-        ) {
-            BasicTextField2(
-                state = remember { TextFieldState(text) },
-                textStyle = textStyle,
-                lineLimits = lineLimits,
-                modifier = Modifier.requiredWidth(100.dp),
-                onTextLayout = onTextLayoutResult
-            )
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/InputMethodInterceptor.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/InputMethodInterceptor.kt
deleted file mode 100644
index 7ddfc89..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/InputMethodInterceptor.kt
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import android.os.Looper
-import android.view.View
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputConnection
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.PlatformTextInputMethodRequest
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.PlatformTextInputMethodTestOverride
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import com.google.common.truth.IntegerSubject
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlin.reflect.KClass
-import kotlin.test.assertNotNull
-import kotlinx.coroutines.awaitCancellation
-
-/**
- * Helper class for testing integration of BasicTextField and BasicTextField2 with the platform IME.
- */
-class InputMethodInterceptor(private val rule: ComposeContentTestRule) {
-
-    private var currentRequest: PlatformTextInputMethodRequest? = null
-    private val editorInfo = EditorInfo()
-    private var inputConnection: InputConnection? = null
-
-    /**
-     * The total number of sessions that have been requested on this interceptor, including the
-     * current one if active.
-     */
-    private var sessionCount = 0
-
-    /**
-     * Asserts that there is an active session.
-     *
-     * Can be called from any thread, including main and test runner.
-     */
-    fun assertSessionActive() {
-        runOnIdle {
-            assertWithMessage("Expected a text input session to be active")
-                .that(currentRequest).isNotNull()
-        }
-    }
-
-    /**
-     * Asserts that there is no active session.
-     *
-     * Can be called from any thread, including main and test runner.
-     */
-    fun assertNoSessionActive() {
-        runOnIdle {
-            assertWithMessage("Expected no text input session to be active")
-                .that(currentRequest).isNull()
-        }
-    }
-
-    /**
-     * Returns a subject that will assert on the total number of sessions requested on this
-     * interceptor, including the current one if active.
-     */
-    fun assertThatSessionCount(): IntegerSubject = assertThat(runOnIdle { sessionCount })
-
-    /**
-     * Runs [block] on the main thread and passes it the [PlatformTextInputMethodRequest]
-     * for the current input session.
-     *
-     * @throws AssertionError if no session is active.
-     */
-    inline fun <reified T : PlatformTextInputMethodRequest> withCurrentRequest(
-        noinline block: T.() -> Unit
-    ) {
-        withCurrentRequest(T::class, block)
-    }
-
-    /**
-     * Runs [block] on the main thread and passes it the [PlatformTextInputMethodRequest]
-     * for the current input session.
-     *
-     * @throws AssertionError if no session is active.
-     */
-    fun <T : PlatformTextInputMethodRequest> withCurrentRequest(
-        asClass: KClass<T>,
-        block: T.() -> Unit
-    ) {
-        runOnIdle {
-            val currentRequest =
-                assertNotNull(currentRequest, "Expected a text input session to be active")
-            assertThat(currentRequest).isInstanceOf(asClass.java)
-            @Suppress("UNCHECKED_CAST")
-            block(currentRequest as T)
-        }
-    }
-
-    /**
-     * Runs [block] on the main thread and passes it the [EditorInfo] configured by the current
-     * input session.
-     *
-     * @throws AssertionError if no session is active.
-     */
-    fun withEditorInfo(block: EditorInfo.() -> Unit) {
-        runOnIdle {
-            assertWithMessage("Expected a text input session to be active")
-                .that(currentRequest).isNotNull()
-            block(editorInfo)
-        }
-    }
-
-    /**
-     * Runs [block] on the main thread and passes it the [InputConnection] created by the current
-     * input session.
-     *
-     * @throws AssertionError if no session is active.
-     */
-    fun withInputConnection(block: InputConnection.() -> Unit) {
-        runOnIdle {
-            val inputConnection = checkNotNull(inputConnection) {
-                "Tried to read inputConnection while no session was active"
-            }
-            block(inputConnection)
-        }
-    }
-
-    /**
-     * Sets the content of the test, overriding the [PlatformTextInputSession] handler.
-     *
-     * This is just a convenience method for calling `rule.setContent` and then calling this class's
-     * [Content] method yourself.
-     */
-    fun setContent(content: @Composable () -> Unit) {
-        rule.setContent {
-            Content(content)
-        }
-    }
-
-    /**
-     * Wraps the content of the test to override the [PlatformTextInputSession] handler.
-     *
-     * @see setContent
-     */
-    @OptIn(ExperimentalTestApi::class)
-    @Composable
-    fun Content(content: @Composable () -> Unit) {
-        val view = LocalView.current
-        val sessionHandler = remember { SessionHandler(view) }
-        PlatformTextInputMethodTestOverride(
-            sessionHandler = sessionHandler,
-            content = content
-        )
-    }
-
-    private fun <T> runOnIdle(block: () -> T): T {
-        return if (Looper.myLooper() != Looper.getMainLooper()) {
-            rule.runOnIdle(block)
-        } else {
-            block()
-        }
-    }
-
-    private inner class SessionHandler(override val view: View) : PlatformTextInputSession {
-        override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing {
-            currentRequest = request
-            sessionCount++
-            try {
-                inputConnection = request.createInputConnection(editorInfo)
-                awaitCancellation()
-            } finally {
-                currentRequest = null
-                inputConnection = null
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/RememberTextFieldStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/RememberTextFieldStateTest.kt
deleted file mode 100644
index a5b2a58..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/RememberTextFieldStateTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.remember
-import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class RememberTextFieldStateTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val restorationTester = StateRestorationTester(rule)
-
-    @Test
-    fun rememberTextFieldState_withInitialTextAndSelection() {
-        lateinit var state: TextFieldState
-        rule.setContent {
-            state = rememberTextFieldState(
-                initialText = "hello",
-                initialSelectionInChars = TextRange(2)
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        }
-    }
-
-    @Test
-    fun rememberTextFieldState_restoresTextAndSelection() {
-        lateinit var originalState: TextFieldState
-        lateinit var restoredState: TextFieldState
-        var rememberCount = 0
-        restorationTester.setContent {
-            val state = rememberTextFieldState()
-            if (remember { rememberCount++ } == 0) {
-                originalState = state
-            } else {
-                restoredState = state
-            }
-        }
-        rule.runOnIdle {
-            originalState.edit {
-                append("hello, world")
-                selectAll()
-            }
-        }
-
-        restorationTester.emulateSavedInstanceStateRestore()
-
-        rule.runOnIdle {
-            assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-            assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 12))
-        }
-    }
-
-    @Test
-    fun rememberTextFieldState_withInitialTextAndSelection_restoresTextAndSelection() {
-        lateinit var originalState: TextFieldState
-        lateinit var restoredState: TextFieldState
-        var rememberCount = 0
-        restorationTester.setContent {
-            val state = rememberTextFieldState(
-                initialText = "this should be ignored",
-                initialSelectionInChars = TextRange.Zero
-            )
-            if (remember { rememberCount++ } == 0) {
-                originalState = state
-            } else {
-                restoredState = state
-            }
-        }
-        rule.runOnIdle {
-            originalState.edit {
-                replace(0, length, "hello, world")
-                selectAll()
-            }
-        }
-
-        restorationTester.emulateSavedInstanceStateRestore()
-
-        rule.runOnIdle {
-            assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-            assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 12))
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TestSoftwareKeyboardController.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TestSoftwareKeyboardController.kt
deleted file mode 100644
index 1f2fcad..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TestSoftwareKeyboardController.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.ui.platform.SoftwareKeyboardController
-import androidx.compose.ui.test.junit4.ComposeTestRule
-import com.google.common.truth.Truth.assertWithMessage
-
-class TestSoftwareKeyboardController(
-    private val rule: ComposeTestRule
-) : SoftwareKeyboardController {
-    private var shown = false
-
-    override fun show() {
-        shown = true
-    }
-
-    override fun hide() {
-        shown = false
-    }
-
-    fun assertShown() {
-        rule.runOnIdle {
-            assertWithMessage("Expected last call on SoftwareKeyboardController to be show")
-                .that(shown).isTrue()
-        }
-    }
-
-    fun assertHidden() {
-        rule.runOnIdle {
-            assertWithMessage("Expected last call on SoftwareKeyboardController to be hide")
-                .that(shown).isFalse()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCodepointTransformationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCodepointTransformationTest.kt
deleted file mode 100644
index 8ca4a6a1..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCodepointTransformationTest.kt
+++ /dev/null
@@ -1,843 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.withKeyDown
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlin.test.fail
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldCodepointTransformationTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicTextField2"
-
-    @Test
-    fun textField_rendersTheResultOf_codepointTransformation() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = { _, codepoint -> codepoint + 1 },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Ifmmp") // one character after in lexical order
-    }
-
-    @Test
-    fun textField_rendersTheResultOf_codepointTransformation_codepointIndex() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = { index, codepoint ->
-                    if (index % 2 == 0) codepoint + 1 else codepoint - 1
-                },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Idmkp") // one character after and before in lexical order
-    }
-
-    @Test
-    fun textField_toggleCodepointTransformation_affectsNextFrame() {
-        rule.mainClock.autoAdvance = false
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        var codepointTransformation: CodepointTransformation? by mutableStateOf(null)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = codepointTransformation,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Hello") // no change
-        codepointTransformation = CodepointTransformation.mask('c')
-
-        rule.mainClock.advanceTimeByFrame()
-        assertLayoutText("ccccc") // all characters turn to c
-    }
-
-    @Test
-    fun textField_statefulCodepointTransformation_reactsToStateChange() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        var mask by mutableStateOf('-')
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = CodepointTransformation.mask(mask),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("-----")
-        mask = '@'
-
-        rule.waitForIdle()
-        assertLayoutText("@@@@@")
-    }
-
-    @Test
-    fun textField_removingCodepointTransformation_rendersTextNormally() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        var codepointTransformation by mutableStateOf<CodepointTransformation?>(
-            CodepointTransformation.mask('*')
-        )
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = codepointTransformation,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("*****")
-        codepointTransformation = null
-
-        rule.waitForIdle()
-        assertLayoutText("Hello")
-    }
-
-    @Test
-    fun textField_codepointTransformation_continuesToRenderUpdatedText() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                codepointTransformation = CodepointTransformation.mask('*'),
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("*****")
-        rule.waitForIdle()
-        rule.onNodeWithTag(Tag).performTextInput(", World!")
-        assertLayoutText("*".repeat("Hello, World!".length))
-    }
-
-    @Test
-    fun textField_singleLine_removesLineFeedViaCodepointTransformation() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Hello World")
-        rule.onNodeWithTag(Tag).performTextInput("\n")
-        assertLayoutText("Hello World ")
-    }
-
-    @Test
-    fun textField_singleLine_removesCarriageReturnViaCodepointTransformation() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello\rWorld")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Hello\uFEFFWorld")
-    }
-
-    @Test
-    fun textField_singleLine_doesNotOverrideGivenCodepointTransformation() {
-        val state = TextFieldState()
-        state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                codepointTransformation = { _, codepoint -> codepoint },
-                modifier = Modifier.testTag(Tag)
-            )
-        }
-
-        assertLayoutText("Hello\nWorld")
-    }
-
-    @Test
-    fun surrogateToNonSurrogate_singleCodepoint_isTransformed() {
-        val state = TextFieldState(SingleSurrogateCodepointString)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-
-        assertLayoutText(".")
-    }
-
-    @Test
-    fun surrogateToNonSurrogate_multipleCodepoints_areTransformed() {
-        val state = TextFieldState(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-
-        assertLayoutText("..")
-    }
-
-    @Test
-    fun surrogateToNonSurrogate_withNonSurrogates_areTransformed() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString}b")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-
-        assertLayoutText("...")
-    }
-
-    @Test
-    fun nonSurrogateToSurrogate_singleCodepoint_isTransformed() {
-        val state = TextFieldState("a")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-
-        assertLayoutText(SingleSurrogateCodepointString)
-    }
-
-    @Test
-    fun nonSurrogateToSurrogate_multipleCodepoints_areTransformed() {
-        val state = TextFieldState("ab")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-
-        assertLayoutText(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
-    }
-
-    @Test
-    fun nonSurrogateToSurrogate_withNonSurrogates_areTransformed() {
-        val state = TextFieldState("abc")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = { i, codepoint ->
-                    if (i == 1) SurrogateCodepoint else codepoint
-                }
-            )
-        }
-
-        assertLayoutText("a${SingleSurrogateCodepointString}c")
-    }
-
-    @Test
-    fun surrogateToNonSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
-        val state = TextFieldState(SingleSurrogateCodepointString)
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-
-        assertVisualTextLength(1)
-        state.assertSelectionMappings(
-            TextRange(0) to TextRange(0),
-            TextRange(0, 1) to TextRange(0, 2),
-            TextRange(1, 0) to TextRange(2, 0),
-            TextRange(1) to TextRange(2),
-        )
-    }
-
-    @Test
-    fun nonSurrogateToSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
-        val state = TextFieldState("a")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-
-        assertVisualTextLength(2)
-        state.assertSelectionMappings(
-            TextRange(0) to TextRange(0),
-            TextRange(0, 1) to TextRange(0, 1),
-            TextRange(0, 2) to TextRange(0, 1),
-            TextRange(1, 0) to TextRange(1, 0),
-            TextRange(1) to TextRange(0, 1),
-            TextRange(1, 2) to TextRange(0, 1),
-            TextRange(2, 0) to TextRange(1, 0),
-            TextRange(2, 1) to TextRange(1, 0),
-            TextRange(2) to TextRange(1),
-        )
-    }
-
-    @FlakyTest(bugId = 317749301)
-    @Test
-    fun multipleCodepoints_selectionIsMappedAroundCodepoints() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = { i, codepoint ->
-                    when (codepoint) {
-                        'a'.code, 'c'.code -> SurrogateCodepoint
-                        SurrogateCodepoint -> 'b'.code
-                        else -> fail(
-                            "unrecognized codepoint at index $i: " +
-                                String(intArrayOf(codepoint), 0, 1)
-                        )
-                    }
-                }
-            )
-        }
-
-        assertVisualTextLength(5)
-        state.assertSelectionMappings(
-            TextRange(0) to TextRange(0),
-            TextRange(0, 1) to TextRange(0, 1),
-            TextRange(0, 2) to TextRange(0, 1),
-            TextRange(0, 3) to TextRange(0, 3),
-            TextRange(0, 4) to TextRange(0, 4),
-            TextRange(0, 5) to TextRange(0, 4),
-            TextRange(1, 0) to TextRange(1, 0),
-            TextRange(1) to TextRange(0, 1),
-            TextRange(1, 2) to TextRange(0, 1),
-            TextRange(1, 3) to TextRange(0, 3),
-            TextRange(1, 4) to TextRange(0, 4),
-            TextRange(1, 5) to TextRange(0, 4),
-            TextRange(2, 0) to TextRange(1, 0),
-            TextRange(2, 1) to TextRange(1, 0),
-            TextRange(2) to TextRange(1),
-            TextRange(2, 3) to TextRange(1, 3),
-            TextRange(2, 4) to TextRange(1, 4),
-            TextRange(2, 5) to TextRange(1, 4),
-            TextRange(3, 0) to TextRange(3, 0),
-            TextRange(3, 1) to TextRange(3, 0),
-            TextRange(3, 2) to TextRange(3, 1),
-            TextRange(3) to TextRange(3),
-            TextRange(3, 4) to TextRange(3, 4),
-            TextRange(3, 5) to TextRange(3, 4),
-            TextRange(4, 0) to TextRange(4, 0),
-            TextRange(4, 1) to TextRange(4, 0),
-            TextRange(4, 2) to TextRange(4, 1),
-            TextRange(4, 3) to TextRange(4, 3),
-            TextRange(4) to TextRange(3, 4),
-            TextRange(4, 5) to TextRange(3, 4),
-        )
-    }
-
-    @Test
-    fun cursorTraversal_withArrowKeys() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = { i, codepoint ->
-                    when (codepoint) {
-                        'a'.code -> SurrogateCodepoint
-                        SurrogateCodepoint -> 'b'.code
-                        'c'.code -> SurrogateCodepoint
-                        else -> fail(
-                            "unrecognized codepoint at index $i: " +
-                                String(intArrayOf(codepoint), 0, 1)
-                        )
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        listOf(0, 1, 3, 4).forEachIndexed { i, expectedCursor ->
-            rule.runOnIdle {
-                assertWithMessage("After pressing right arrow $i times")
-                    .that(state.text.selectionInChars).isEqualTo(TextRange(expectedCursor))
-            }
-            rule.onNodeWithTag(Tag).performKeyInput {
-                pressKey(Key.DirectionRight)
-            }
-        }
-    }
-
-    @Test
-    fun expandSelectionForward_withArrowKeys() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = { i, codepoint ->
-                    when (codepoint) {
-                        'a'.code -> SurrogateCodepoint
-                        SurrogateCodepoint -> 'b'.code
-                        'c'.code -> SurrogateCodepoint
-                        else -> fail(
-                            "unrecognized codepoint at index $i: " +
-                                String(intArrayOf(codepoint), 0, 1)
-                        )
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        listOf(
-            TextRange(0),
-            TextRange(0, 1),
-            TextRange(0, 3),
-            TextRange(0, 4)
-        ).forEachIndexed { i, expectedSelection ->
-            rule.runOnIdle {
-                assertWithMessage("After pressing shift+right arrow $i times")
-                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
-            }
-            rule.onNodeWithTag(Tag).performKeyInput {
-                withKeyDown(Key.ShiftLeft) {
-                    pressKey(Key.DirectionRight)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun expandSelectionBackward_withArrowKeys() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = { i, codepoint ->
-                    when (codepoint) {
-                        'a'.code -> SurrogateCodepoint
-                        SurrogateCodepoint -> 'b'.code
-                        'c'.code -> SurrogateCodepoint
-                        else -> fail(
-                            "unrecognized codepoint at index $i: " +
-                                String(intArrayOf(codepoint), 0, 1)
-                        )
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(4))
-
-        listOf(
-            TextRange(4),
-            TextRange(4, 3),
-            TextRange(4, 1),
-            TextRange(4, 0)
-        ).forEachIndexed { i, expectedSelection ->
-            rule.runOnIdle {
-                assertWithMessage("After pressing shift+left arrow $i times")
-                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
-            }
-            rule.onNodeWithTag(Tag).performKeyInput {
-                withKeyDown(Key.ShiftLeft) {
-                    pressKey(Key.DirectionLeft)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun insertNonSurrogates_intoSurrogateMask_fromKeyEvents() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.X)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Y)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Z)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(10)
-    }
-
-    @Test
-    fun insertNonSurrogates_intoNonSurrogateMask_fromKeyEvents() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.X)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Y)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Z)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(5)
-    }
-
-    @Test
-    fun insertText_intoSurrogateMask_fromSemantics() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        // Use semantics to actually input the text, just use key events to move the cursor.
-        rule.onNodeWithTag(Tag).performTextInput("x")
-        pressKey(Key.DirectionRight)
-        rule.onNodeWithTag(Tag).performTextInput("y")
-        pressKey(Key.DirectionRight)
-        rule.onNodeWithTag(Tag).performTextInput("z")
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(10)
-    }
-
-    @Test
-    fun insertNonSurrogates_intoNonSurrogateMask_fromSemantics() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
-
-        // Use semantics to actually input the text, just use key events to move the cursor.
-        rule.onNodeWithTag(Tag).performTextInput("x")
-        pressKey(Key.DirectionRight)
-        rule.onNodeWithTag(Tag).performTextInput("y")
-        pressKey(Key.DirectionRight)
-        rule.onNodeWithTag(Tag).performTextInput("z")
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(5)
-    }
-
-    @Test
-    fun insertText_intoSurrogateMask_fromIme() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        inputMethodInterceptor.withInputConnection {
-            beginBatchEdit()
-            finishComposingText()
-            setSelection(0, 0)
-            endBatchEdit()
-        }
-
-        inputMethodInterceptor.withInputConnection { commitText("x", 1) }
-        pressKey(Key.DirectionRight)
-        inputMethodInterceptor.withInputConnection { commitText("y", 1) }
-        pressKey(Key.DirectionRight)
-        inputMethodInterceptor.withInputConnection { commitText("z", 1) }
-        pressKey(Key.DirectionRight)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(10)
-    }
-
-    @Test
-    fun insertText_intoNonSurrogateMask_fromIme() {
-        val state = TextFieldState("a$SingleSurrogateCodepointString")
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        inputMethodInterceptor.withInputConnection {
-            beginBatchEdit()
-            finishComposingText()
-            setSelection(0, 0)
-            endBatchEdit()
-        }
-
-        inputMethodInterceptor.withInputConnection { commitText("x", 1) }
-        pressKey(Key.DirectionRight)
-        inputMethodInterceptor.withInputConnection { commitText("y", 1) }
-        pressKey(Key.DirectionRight)
-        inputMethodInterceptor.withInputConnection { commitText("z", 1) }
-        pressKey(Key.DirectionRight)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
-        }
-        assertVisualTextLength(5)
-    }
-
-    @Test
-    fun removeNonSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
-        val state = TextFieldState("${SingleSurrogateCodepointString.repeat(2)}aa")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.Backspace)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString())
-                .isEqualTo("${SingleSurrogateCodepointString.repeat(2)}a")
-        }
-        assertVisualTextLength(3)
-    }
-
-    @Test
-    fun removeSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
-        val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithNonSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.Backspace)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString())
-                .isEqualTo("aa$SingleSurrogateCodepointString")
-        }
-        assertVisualTextLength(3)
-    }
-
-    @Test
-    fun removeNonSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
-        val state = TextFieldState("a${SingleSurrogateCodepointString.repeat(2)}a")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.Backspace)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString())
-                .isEqualTo("a${SingleSurrogateCodepointString.repeat(2)}")
-        }
-        assertVisualTextLength(6)
-    }
-
-    @Test
-    fun removeSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
-        val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
-        rule.setContent {
-            BasicTextField2(
-                state = state,
-                modifier = Modifier.testTag(Tag),
-                codepointTransformation = MaskWithSurrogate
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(6))
-
-        rule.onNodeWithTag(Tag).performKeyInput {
-            pressKey(Key.Backspace)
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString())
-                .isEqualTo("aa$SingleSurrogateCodepointString")
-        }
-        assertVisualTextLength(6)
-    }
-
-    private fun assertLayoutText(text: String) {
-        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
-            .isEqualTo(text)
-    }
-
-    private fun assertVisualTextLength(expectedLength: Int) {
-        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
-            .hasLength(expectedLength)
-    }
-
-    private fun TextFieldState.assertSelectionMappings(
-        vararg mappings: Pair<TextRange, TextRange>
-    ) {
-        mappings.forEach { (write, expected) ->
-            val existingSelection = rule.onNodeWithTag(Tag)
-                .fetchSemanticsNode().config[SemanticsProperties.TextSelectionRange]
-            // Setting the selection to the current selection will return false.
-            if (existingSelection != write) {
-                assertWithMessage("Expected to be able to select $write")
-                    .that(performSelectionOnVisualText(write)).isTrue()
-                rule.runOnIdle {
-                    assertWithMessage("Visual selection $write to mapped")
-                        .that(text.selectionInChars).isEqualTo(expected)
-                }
-            }
-        }
-    }
-
-    private fun performSelectionOnVisualText(selection: TextRange): Boolean {
-        rule.onNodeWithTag(Tag).requestFocus()
-        var actionSucceeded = false
-        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.SetSelection) {
-            actionSucceeded = it(selection.start, selection.end, /* relativeToOriginal= */ false)
-        }
-        return actionSucceeded
-    }
-
-    private fun pressKey(key: Key) {
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(key) }
-    }
-
-    private companion object {
-        /** This is "𐐷", a surrogate codepoint. */
-        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
-        const val SingleSurrogateCodepointString = "\uD801\uDC37"
-
-        val MaskWithSurrogate = CodepointTransformation { _, _ -> SurrogateCodepoint }
-        val MaskWithNonSurrogate = CodepointTransformation { _, _ -> '.'.code }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCursorTest.kt
deleted file mode 100644
index 7549825..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCursorTest.kt
+++ /dev/null
@@ -1,1105 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.os.Build
-import android.view.DragEvent
-import android.view.View
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.DefaultCursorThickness
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.LocalTextSelectionColors
-import androidx.compose.foundation.text.selection.TextSelectionColors
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.internal.DragAndDropTestUtils.makeTextDragEvent
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.assertContainsColor
-import androidx.compose.testutils.assertDoesNotContainColor
-import androidx.compose.testutils.assertPixelColor
-import androidx.compose.testutils.assertPixels
-import androidx.compose.testutils.assertShape
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.MotionDurationScale
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.toPixelMap
-import androidx.compose.ui.layout.layout
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.unit.toOffset
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlin.math.ceil
-import kotlin.math.floor
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@LargeTest
-class TextFieldCursorTest : FocusedWindowTest {
-
-    private val motionDurationScale = object : MotionDurationScale {
-        override var scaleFactor: Float by mutableStateOf(1f)
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @get:Rule
-    val rule = createComposeRule(effectContext = motionDurationScale).also {
-        it.mainClock.autoAdvance = false
-    }
-
-    private lateinit var state: TextFieldState
-
-    private val boxPadding = 8.dp
-
-    // Both TextField background and font color should be the same to make sure that only
-    // cursor is visible
-    private val contentColor = Color.White
-    private val cursorColor = Color.Red
-    private val fontSize = 10.sp
-    private val textStyle = TextStyle(
-        color = contentColor,
-        background = contentColor,
-        fontSize = fontSize,
-        fontFamily = TEST_FONT_FAMILY
-    )
-
-    private var isFocused = false
-    private var textLayoutResult: (() -> TextLayoutResult?)? = null
-    private val cursorRect: Rect
-        // assume selection is collapsed
-        get() = textLayoutResult?.invoke()?.getCursorRect(state.text.selectionInChars.start)
-            ?: Rect.Zero
-
-    private val cursorSize: DpSize by lazy {
-        with(rule.density) {
-            DpSize(DefaultCursorThickness, fontSize.toDp())
-        }
-    }
-
-    private val cursorSizePx: Size by lazy {
-        with(rule.density) { cursorSize.toSize() }
-    }
-
-    private val cursorTopCenterInLtr: Offset
-        // assume selection is collapsed
-        get() = cursorRect.topCenter + Offset(cursorSizePx.width / 2f, 0f)
-
-    private val cursorTopCenterInRtl: Offset
-        // assume selection is collapsed
-        get() = cursorRect.topCenter - Offset(cursorSizePx.width / 2f, 0f)
-
-    private val backgroundModifier = Modifier.background(contentColor)
-    private val focusModifier = Modifier.onFocusChanged { if (it.isFocused) isFocused = true }
-
-    // default TextFieldModifier
-    private val textFieldModifier = Modifier
-        .then(backgroundModifier)
-        .then(focusModifier)
-
-    // default onTextLayout to capture cursor boundaries.
-    private val onTextLayout: Density.(() -> TextLayoutResult?) -> Unit = { textLayoutResult = it }
-
-    private fun ComposeContentTestRule.setTestContent(
-        content: @Composable () -> Unit
-    ) {
-        this.setTextFieldTestContent {
-            // The padding helps if the test is run accidentally in landscape. Landscape makes
-            // the cursor to be next to the navigation bar which affects the red color to be a
-            // bit different - possibly anti-aliasing.
-            Box(Modifier.padding(boxPadding)) {
-                content()
-            }
-        }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldFocused_cursorRendered() {
-        state = TextFieldState()
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldFocused_cursorRendered_rtlLayout() {
-        state = TextFieldState()
-        rule.setTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier.width(30.dp),
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        // an empty text layout will be placed on the right side of 30.dp-width area
-        // cursor will be at the most right side
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInRtl)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldFocused_cursorRendered_rtlText_ltrLayout() {
-        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldFocused_cursorRendered_rtlTextLayout() {
-        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
-        rule.setTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier.width(50.dp),
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            // 20 - 2(cursor)
-            .assertCursor(cursorTopCenterInRtl)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldCursorAtTheEnd_coercedIntoView() {
-        state = TextFieldState("hello", TextRange(5))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier.width(50.dp),
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr - Offset(cursorSizePx.width, 0f))
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldCursorAtTheEnd_coercedIntoView_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
-        rule.setTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier.width(30.dp),
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInRtl + Offset(cursorSizePx.width, 0f))
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textFieldFocused_cursorWithBrush() {
-        state = TextFieldState()
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle.copy(fontSize = textStyle.fontSize * 2),
-                modifier = Modifier
-                    .then(backgroundModifier)
-                    .then(focusModifier),
-                cursorBrush = Brush.verticalGradient(
-                    // make a brush double/triple color at the beginning and end so we have
-                    // stable colors at the ends.
-                    // Without triple bottom, the bottom color never hits to the provided color.
-                    listOf(
-                        Color.Blue,
-                        Color.Blue,
-                        Color.Green,
-                        Color.Green,
-                        Color.Green
-                    )
-                ),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-
-        val bitmap = rule.onNode(hasSetTextAction())
-            .captureToImage().toPixelMap()
-
-        val cursorLeft = ceil(cursorRect.left).toInt() + 1
-        val cursorTop = ceil(cursorRect.top).toInt() + 1
-        val cursorBottom = floor(cursorRect.bottom).toInt() - 1
-        bitmap.assertPixelColor(Color.Blue, x = cursorLeft, y = cursorTop)
-        bitmap.assertPixelColor(Color.Green, x = cursorLeft, y = cursorBottom)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun cursorBlinkingAnimation() {
-        state = TextFieldState()
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        // cursor visible first 500 ms
-        rule.mainClock.advanceTimeBy(100)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-
-        // cursor invisible during next 500 ms
-        rule.mainClock.advanceTimeBy(700)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Suppress("UnnecessaryOptInAnnotation")
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun cursorBlinkingAnimation_whenSystemDisablesAnimations() {
-        motionDurationScale.scaleFactor = 0f
-        state = TextFieldState()
-
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        // cursor visible first 500 ms
-        rule.mainClock.advanceTimeBy(100)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-
-        // cursor invisible during next 500 ms
-        rule.mainClock.advanceTimeBy(700)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun cursorUnsetColor_noCursor() {
-        state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(Color.Unspecified)
-            )
-        }
-
-        focusAndWait()
-
-        // no cursor when usually shown
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-
-        // no cursor when should be no cursor
-        rule.mainClock.advanceTimeBy(700)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @FlakyTest(bugId = 303503435)
-    fun cursorNotBlinking_whileTyping() {
-        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier.width(100.dp),
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        // cursor visible first 500 ms
-        rule.mainClock.advanceTimeBy(500)
-        // TODO(b/170298051) check here that cursor is visible when we have a way to control
-        //  cursor position when sending a text
-
-        // change text field value
-        rule.onNode(hasSetTextAction())
-            .performTextInput("s")
-
-        // cursor would have been invisible during next 500 ms if cursor blinks while typing.
-        // To prevent blinking while typing we restart animation when new symbol is typed.
-        rule.mainClock.advanceTimeBy(300)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @FlakyTest(bugId = 303903824)
-    fun selectionChanges_cursorNotBlinking() {
-        state = TextFieldState("test", initialSelectionInChars = TextRange(2))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        // hide the cursor
-        rule.mainClock.advanceTimeBy(500)
-        rule.mainClock.advanceTimeByFrame()
-
-        // TODO(b/170298051) check here that cursor is visible when we have a way to control
-        //  cursor position when sending a text
-
-        rule.onNode(hasSetTextAction())
-            .performTextInputSelection(TextRange(0))
-
-        // necessary for animation to start (shows cursor again)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun brushChanged_doesntResetTimer() {
-        var cursorBrush by mutableStateOf(SolidColor(cursorColor))
-        state = TextFieldState()
-        rule.setTestContent {
-            Box(Modifier.padding(boxPadding)) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier,
-                    cursorBrush = cursorBrush,
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(800)
-        cursorBrush = SolidColor(Color.Green)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun selectionNotCollapsed_cursorNotDrawn() {
-        state = TextFieldState("test", initialSelectionInChars = TextRange(2, 3))
-        rule.setTestContent {
-            // set selection highlight to a known color
-            CompositionLocalProvider(
-                LocalTextSelectionColors provides TextSelectionColors(Color.Blue, Color.Blue)
-            ) {
-                BasicTextField2(
-                    state = state,
-                    // make sure that background is not obstructing selection
-                    textStyle = textStyle.copy(
-                        background = Color.Unspecified
-                    ),
-                    modifier = textFieldModifier,
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        focusAndWait()
-
-        // cursor should still be visible if there wasn't a selection
-        rule.mainClock.advanceTimeBy(300)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertDoesNotContainColor(cursorColor)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun focusLost_cursorHidesImmediately() {
-        state = TextFieldState("test")
-        rule.setTestContent {
-            Column {
-                BasicTextField2(
-                    state = state,
-                    // make sure that background is not obstructing selection
-                    textStyle = textStyle,
-                    modifier = textFieldModifier,
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-                Box(
-                    modifier = Modifier
-                        .focusable(true)
-                        .testTag("box")
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-
-        rule.onNodeWithTag("box").requestFocus()
-        rule.mainClock.advanceTimeByFrame()
-
-        // cursor should hide immediately.
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun readOnly_cursorIsNotDrawn() {
-        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                readOnly = true,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertDoesNotContainColor(cursorColor)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun toggling_readOnly_drawsCursorAgain() {
-        var readOnly by mutableStateOf(true)
-        state = TextFieldState("test", initialSelectionInChars = TextRange(4))
-        rule.setTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                readOnly = readOnly,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        focusAndWait()
-
-        rule.mainClock.advanceTimeBy(100)
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertDoesNotContainColor(cursorColor)
-
-        readOnly = false
-        rule.mainClock.advanceTimeByFrame()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun cursorNotBlinking_whenWindowLostFocus() {
-        state = TextFieldState()
-        val focusWindow = mutableStateOf(true)
-        fun createWindowInfo(focused: Boolean) = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focused
-        }
-
-        rule.setTestContent {
-            CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
-                Box(Modifier.padding(boxPadding)) {
-                    BasicTextField2(
-                        state = state,
-                        textStyle = textStyle,
-                        modifier = textFieldModifier,
-                        cursorBrush = SolidColor(cursorColor),
-                        onTextLayout = onTextLayout
-                    )
-                }
-            }
-        }
-
-        focusAndWait()
-
-        // cursor visible first 500ms
-        rule.mainClock.advanceTimeBy(100)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(cursorColor)
-
-        // window loses focus
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        // check that text field cursor disappeared even within visible 500ms
-        rule.mainClock.advanceTimeBy(300)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertDoesNotContainColor(cursorColor)
-    }
-
-    @Ignore("b/305799612")
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun focusedTextField_resumeBlinking_whenWindowRegainsFocus() {
-        state = TextFieldState()
-        val focusWindow = mutableStateOf(true)
-        fun createWindowInfo(focused: Boolean) = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focused
-        }
-
-        rule.setTestContent {
-            CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
-                Box(Modifier.padding(boxPadding)) {
-                    BasicTextField2(
-                        state = state,
-                        textStyle = textStyle,
-                        modifier = textFieldModifier,
-                        cursorBrush = SolidColor(cursorColor),
-                        onTextLayout = onTextLayout
-                    )
-                }
-            }
-        }
-
-        focusAndWait()
-
-        // window loses focus
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        // check that text field cursor disappeared even within visible 500ms
-        rule.mainClock.advanceTimeBy(100)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertDoesNotContainColor(cursorColor)
-
-        // window regains focus within 500ms
-        focusWindow.value = true
-        rule.waitForIdle()
-
-        rule.mainClock.advanceTimeBy(100)
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(cursorColor)
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_keepsSelection_whenWindowLosesFocus() {
-        state = TextFieldState("hello", initialSelectionInChars = TextRange(0, 5))
-        val selectionColor = Color.Blue
-        val focusWindow = mutableStateOf(true)
-        val windowInfo = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focusWindow.value
-        }
-
-        rule.setTestContent {
-            CompositionLocalProvider(
-                LocalWindowInfo provides windowInfo,
-                LocalTextSelectionColors provides TextSelectionColors(
-                    selectionColor,
-                    selectionColor
-                )
-            ) {
-                BasicTextField2(
-                    state = state,
-                    // make sure that background is not obstructing selection
-                    textStyle = textStyle.copy(background = Color.Unspecified),
-                    modifier = textFieldModifier,
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(selectionColor)
-
-        // window lost focus, make sure selection still drawn
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertContainsColor(selectionColor)
-    }
-
-    @Ignore("b/305799612")
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_textDragging_cursorRendered() {
-        state = TextFieldState("Hello World")
-        var view: View? = null
-        rule.setTestContent {
-            view = LocalView.current
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
-            )
-
-            view?.dispatchDragEvent(startEvent)
-            view?.dispatchDragEvent(enterEvent)
-            view?.dispatchDragEvent(moveEvent)
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Ignore("b/305799612")
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_textDragging_cursorDisappearsAfterTimeout() {
-        state = TextFieldState("Hello World")
-        var view: View? = null
-        rule.setTestContent {
-            view = LocalView.current
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
-            )
-
-            view?.dispatchDragEvent(startEvent)
-            view?.dispatchDragEvent(enterEvent)
-            view?.dispatchDragEvent(moveEvent)
-        }
-
-        rule.mainClock.advanceTimeBy(500)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = RectangleShape,
-                shapeColor = contentColor,
-                backgroundColor = contentColor,
-                shapeOverlapPixelCount = 0.0f
-            )
-    }
-
-    @Ignore("b/305799612")
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_textDragging_cursorDoesNotDisappearWhileMoving() {
-        state = TextFieldState("Hello World")
-        var view: View? = null
-        rule.setTestContent {
-            view = LocalView.current
-            BasicTextField2(
-                state = state,
-                textStyle = textStyle,
-                modifier = textFieldModifier,
-                cursorBrush = SolidColor(cursorColor),
-                onTextLayout = onTextLayout
-            )
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
-            )
-
-            view?.dispatchDragEvent(startEvent)
-            view?.dispatchDragEvent(enterEvent)
-            view?.dispatchDragEvent(moveEvent)
-        }
-
-        rule.mainClock.advanceTimeBy(300)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-
-        val moveEvent2 = makeTextDragEvent(
-            action = DragEvent.ACTION_DRAG_LOCATION,
-            offset = Offset(with(rule.density) { fontSize.toPx() * 4 }, 5f)
-        )
-        view?.dispatchDragEvent(moveEvent2)
-        rule.mainClock.advanceTimeBy(400)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    @Ignore("b/305799612")
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_textDragging_noWindowFocus_cursorRendered() {
-        state = TextFieldState("Hello World")
-        var view: View? = null
-        rule.setContent {
-            Box(Modifier.padding(boxPadding)) {
-                view = LocalView.current
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier,
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(with(rule.density) { fontSize.toPx() * 3 }, 5f)
-            )
-
-            view?.dispatchDragEvent(startEvent)
-            view?.dispatchDragEvent(enterEvent)
-            view?.dispatchDragEvent(moveEvent)
-        }
-
-        rule.mainClock.advanceTimeBy(100)
-
-        rule.onNode(hasSetTextAction())
-            .captureToImage()
-            .assertCursor(cursorTopCenterInLtr)
-    }
-
-    private fun focusAndWait() {
-        rule.onNode(hasSetTextAction()).requestFocus()
-        rule.mainClock.advanceTimeUntil { isFocused }
-    }
-
-    /**
-     * @param cursorPosition Top center of cursor rectangle
-     */
-    private fun ImageBitmap.assertCursor(cursorPosition: Offset) {
-        assertThat(cursorPosition.x).isAtLeast(0f)
-        assertThat(cursorPosition.y).isAtLeast(0f)
-
-        // assert cursor width is greater than 2 since we will shrink the check area by 1 on each
-        // side
-        assertThat(cursorSizePx.width).isGreaterThan(2)
-
-        // shrink the check are by 1px for left, top, right, bottom
-        val checkRect = Rect(
-            ceil(cursorPosition.x - cursorSizePx.width / 2) + 1,
-            ceil(cursorPosition.y) + 1,
-            floor(cursorPosition.x + cursorSizePx.width / 2) - 1,
-            floor(cursorPosition.y + cursorSizePx.height) - 1
-        )
-
-        // skip an expanded rectangle that is 1px larger than cursor rectangle due to antialiasing
-        val skipRect = Rect(
-            floor(cursorPosition.x - cursorSizePx.width / 2) - 1,
-            floor(cursorPosition.y) - 1,
-            ceil(cursorPosition.x + cursorSizePx.width / 2) + 1,
-            ceil(cursorPosition.y + cursorSizePx.height) + 1
-        )
-
-        val width = width
-        val height = height
-        this.assertPixels(
-            IntSize(width, height)
-        ) { position ->
-            if (checkRect.contains(position.toOffset())) {
-                // cursor
-                cursorColor
-            } else if (skipRect.contains(position.toOffset())) {
-                // skip some pixels around cursor
-                null
-            } else {
-                // text field background
-                contentColor
-            }
-        }
-    }
-
-    @Test
-    fun textFieldCursor_alwaysReadLatestState_duringDraw() {
-        state = TextFieldState("hello world", TextRange(5))
-        rule.setTestContent {
-            Box(Modifier.padding(boxPadding)) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = textStyle,
-                    modifier = textFieldModifier.layout { measurable, constraints ->
-                        // change the state during layout so draw can read the new state
-                        val currValue = state.text
-                        if (currValue.isNotEmpty()) {
-                            val newText = currValue.dropLast(1)
-                            state.setTextAndPlaceCursorAtEnd(newText.toString())
-                        }
-
-                        val p = measurable.measure(constraints)
-                        layout(p.width, p.height) {
-                            p.place(0, 0)
-                        }
-                    },
-                    cursorBrush = SolidColor(cursorColor),
-                    onTextLayout = onTextLayout
-                )
-            }
-        }
-
-        rule.waitForIdle()
-
-        rule.onNode(hasSetTextAction()).assertTextEquals("")
-        // this test just needs to finish without crashing. There is no other assertion
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldDragAndDropTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldDragAndDropTest.kt
deleted file mode 100644
index 2389e60..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldDragAndDropTest.kt
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.net.Uri
-import android.view.View
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.TestActivity
-import androidx.compose.foundation.content.DragAndDropScope
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.foundation.content.ReceiveContentListener
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.consumeEach
-import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
-import androidx.compose.foundation.content.testDragAndDrop
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsHoveredAsState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.firstUriOrNull
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
-@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldDragAndDropTest {
-
-    @get:Rule
-    val rule = createAndroidComposeRule<TestActivity>()
-
-    @Test
-    fun nonTextContent_isNotAccepted() {
-        rule.setContentAndTestDragAndDrop {
-            val startSelection = state.text.selectionInChars
-            drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(state.text.selectionInChars).isEqualTo(startSelection)
-        }
-    }
-
-    @Test
-    fun nonTextContent_isAcceptedIfReceiveContentDefined() {
-        rule.setContentAndTestDragAndDrop(
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                null
-            }
-        ) {
-            val accepted = drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(accepted).isTrue()
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        }
-    }
-
-    @Test
-    fun textContent_isAccepted() {
-        rule.setContentAndTestDragAndDrop {
-            drag(Offset(fontSize.toPx() * 2, 10f), "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        }
-    }
-
-    @Test
-    fun draggingText_updatesSelection() {
-        rule.setContentAndTestDragAndDrop {
-            drag(Offset(fontSize.toPx() * 1, 10f), "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-            drag(Offset(fontSize.toPx() * 2, 10f), "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-            drag(Offset(fontSize.toPx() * 3, 10f), "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-        }
-    }
-
-    @Test
-    fun draggingNonText_updatesSelection_withReceiveContent() {
-        rule.setContentAndTestDragAndDrop(
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                null
-            }
-        ) {
-            drag(Offset(fontSize.toPx() * 1, 10f), defaultUri)
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-            drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-            drag(Offset(fontSize.toPx() * 3, 10f), defaultUri)
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-        }
-    }
-
-    @Test
-    fun draggingText_toEndPadding_updatesSelection() {
-        rule.setContentAndTestDragAndDrop(
-            style = TextStyle(textAlign = TextAlign.Center),
-            modifier = Modifier.width(300.dp)
-        ) {
-            drag(Offset.Zero, "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            drag(Offset(295.dp.toPx(), 10f), "hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-        }
-    }
-
-    @Test
-    fun interactionSource_receivesHoverEnter_whenDraggingTextEnters() {
-        val interactionSource = MutableInteractionSource()
-        rule.setContentAndTestDragAndDrop(
-            style = TextStyle(textAlign = TextAlign.Center),
-            interactionSource = interactionSource,
-            modifier = Modifier.width(200.dp)
-        ) {
-            drag(Offset(1f, 1f), "hello")
-            assertThat(isHovered).isTrue()
-        }
-    }
-
-    @Test
-    fun interactionSource_receivesHoverExit_whenDraggingTextExits() {
-        val interactionSource = MutableInteractionSource()
-        rule.setContentAndTestDragAndDrop(
-            style = TextStyle(textAlign = TextAlign.Center),
-            interactionSource = interactionSource,
-            modifier = Modifier.width(200.dp)
-        ) {
-            drag(Offset(1f, 1f), "hello")
-            assertThat(isHovered).isTrue()
-
-            drag(Offset(1000f, 1f), "hello")
-            assertThat(isHovered).isFalse()
-        }
-    }
-
-    @Test
-    fun interactionSource_receivesHoverExit_whenDraggingTextEnds() {
-        val interactionSource = MutableInteractionSource()
-        rule.setContentAndTestDragAndDrop(
-            style = TextStyle(textAlign = TextAlign.Center),
-            interactionSource = interactionSource,
-            modifier = Modifier.width(200.dp)
-        ) {
-            drag(Offset(1f, 1f), "hello")
-            assertThat(isHovered).isTrue()
-
-            cancelDrag()
-            assertThat(isHovered).isFalse()
-        }
-    }
-
-    @Test
-    fun interactionSource_receivesHoverExit_whenDraggingTextDrops() {
-        val interactionSource = MutableInteractionSource()
-        rule.setContentAndTestDragAndDrop(
-            style = TextStyle(textAlign = TextAlign.Center),
-            interactionSource = interactionSource,
-            modifier = Modifier.width(200.dp)
-        ) {
-            drag(Offset(1f, 1f), "hello")
-            assertThat(isHovered).isTrue()
-
-            drop()
-            assertThat(isHovered).isFalse()
-        }
-    }
-
-    @Test
-    fun draggingOntoTextField_keepsWrapperReceiveContentEntered() {
-        // this is a nested scenario where moving a dragging item from receiveContent to
-        // BTF2 area does not send an exit event to receiveContent drag listener
-        lateinit var view: View
-        val density = Density(1f, 1f)
-        val calls = mutableListOf<String>()
-        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
-            view = LocalView.current
-            CompositionLocalProvider(
-                LocalDensity provides density,
-                LocalWindowInfo provides object : WindowInfo {
-                    override val isWindowFocused = false
-                }
-            ) {
-                Box(
-                    modifier = Modifier
-                        .size(200.dp)
-                        .receiveContent(emptySet(), object : ReceiveContentListener {
-                            override fun onDragStart() {
-                                calls += "start"
-                            }
-
-                            override fun onDragEnd() {
-                                calls += "end"
-                            }
-
-                            override fun onDragEnter() {
-                                calls += "enter"
-                            }
-
-                            override fun onDragExit() {
-                                calls += "exit"
-                            }
-
-                            override fun onReceive(c: TransferableContent): TransferableContent? {
-                                calls += "receive"
-                                return null
-                            }
-                        })
-                ) {
-                    BasicTextField2(
-                        state = rememberTextFieldState(),
-                        textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                        lineLimits = TextFieldLineLimits.SingleLine,
-                        modifier = Modifier
-                            .width(100.dp)
-                            .height(40.dp)
-                            .align(Alignment.Center)
-                    )
-                }
-            }
-        }
-
-        testDragAndDrop(view, density) {
-            drag(Offset(1f, 1f), defaultUri)
-            assertThat(calls).isEqualTo(listOf("start", "enter"))
-
-            cancelDrag()
-            assertThat(calls).isEqualTo(listOf("start", "enter", "end"))
-            calls.clear()
-
-            drag(Offset(1f, 1f), defaultUri)
-            drag(Offset(100f, 100f), defaultUri) // should be inside TextField's area
-
-            // expect no extra enter/exit calls
-            assertThat(calls).isEqualTo(listOf("start", "enter"))
-            drop()
-
-            assertThat(calls).isEqualTo(listOf("start", "enter", "receive"))
-        }
-    }
-
-    @Test
-    fun draggingOutOfTextField_keepsWrapperReceiveContentEntered() {
-        // this is a nested scenario where moving a dragging item from receiveContent to
-        // BTF2 area does not send an exit event to receiveContent drag listener
-        lateinit var view: View
-        val density = Density(1f, 1f)
-        val calls = mutableListOf<String>()
-        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
-            view = LocalView.current
-            CompositionLocalProvider(
-                LocalDensity provides density,
-                LocalWindowInfo provides object : WindowInfo {
-                    override val isWindowFocused = false
-                }
-            ) {
-                Box(
-                    modifier = Modifier
-                        .size(200.dp)
-                        .receiveContent(emptySet(), object : ReceiveContentListener {
-                            override fun onDragStart() {
-                                calls += "start"
-                            }
-
-                            override fun onDragEnd() {
-                                calls += "end"
-                            }
-
-                            override fun onDragEnter() {
-                                calls += "enter"
-                            }
-
-                            override fun onDragExit() {
-                                calls += "exit"
-                            }
-
-                            override fun onReceive(c: TransferableContent): TransferableContent? {
-                                calls += "receive"
-                                return null
-                            }
-                        })
-                ) {
-                    BasicTextField2(
-                        state = rememberTextFieldState(),
-                        textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                        lineLimits = TextFieldLineLimits.SingleLine,
-                        modifier = Modifier
-                            .width(100.dp)
-                            .height(40.dp)
-                            .align(Alignment.Center)
-                    )
-                }
-            }
-        }
-
-        testDragAndDrop(view, density) {
-            drag(Offset(100f, 100f), defaultUri) // should be inside TextField's area
-            assertThat(calls).isEqualTo(listOf("start", "enter"))
-
-            drag(Offset(199f, 199f), defaultUri)
-            assertThat(calls).isEqualTo(listOf("start", "enter")) // no exit event
-
-            drag(Offset(201f, 201f), defaultUri)
-            assertThat(calls).isEqualTo(listOf("start", "enter", "exit")) // no exit event
-        }
-    }
-
-    @Test
-    fun droppedText_insertsAtCursor() {
-        rule.setContentAndTestDragAndDrop("Hello World!") {
-            drag(
-                Offset(fontSize.toPx() * 5, 10f),
-                " Awesome"
-            )
-            drop()
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange("Hello Awesome".length))
-            assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
-        }
-    }
-
-    @Test
-    fun dropped_textAndNonTextCombined_insertsAtCursor() {
-        lateinit var receivedContent: TransferableContent
-        rule.setContentAndTestDragAndDrop(
-            "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                receivedContent = it
-                receivedContent.consumeEach {
-                    // do not consume text
-                    it.uri != null
-                }
-            }
-        ) {
-            val clipData = createClipData {
-                addText(" Awesome")
-                addUri(defaultUri)
-            }
-            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
-            drop()
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange("Hello Awesome".length))
-            assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
-            assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
-            assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
-        }
-    }
-
-    @Test
-    fun dropped_textAndNonTextCombined_consumedEverything_doesNotInsert() {
-        lateinit var receivedContent: TransferableContent
-        rule.setContentAndTestDragAndDrop(
-            "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                receivedContent = it
-                // consume everything
-                null
-            }
-        ) {
-            val clipData = createClipData {
-                addText(" Awesome")
-                addUri(defaultUri)
-            }
-            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
-            drop()
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-            assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
-            assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
-        }
-    }
-
-    @Test
-    fun dropped_consumedAndReplaced_insertsAtCursor() {
-        lateinit var receivedContent: TransferableContent
-        rule.setContentAndTestDragAndDrop(
-            "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                receivedContent = it
-                val uri = receivedContent.clipEntry.firstUriOrNull()
-                // replace the content
-                val clipData = createClipData { addText(uri.toString()) }
-                TransferableContent(clipData)
-            }
-        ) {
-            val clipData = createClipData {
-                addUri(defaultUri)
-            }
-            drag(Offset(fontSize.toPx() * 5, 10f), clipData)
-            drop()
-            assertThat(state.text.toString()).isEqualTo("Hello$defaultUri World!")
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 24)
-    @Test
-    fun droppedItem_requestsPermission_ifReceiveContent() {
-        rule.setContentAndTestDragAndDrop(
-            "Hello World!",
-            modifier = Modifier.receiveContent(emptySet()) { null }
-        ) {
-            drag(Offset(fontSize.toPx() * 5, 10f), defaultUri)
-            drop()
-            assertThat(rule.activity.requestedDragAndDropPermissions).isNotEmpty()
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 24)
-    @Test
-    fun droppedItem_doesNotRequestPermission_ifNoReceiveContent() {
-        rule.setContentAndTestDragAndDrop("Hello World!") {
-            drag(Offset(fontSize.toPx() * 5, 10f), createClipData {
-                addText()
-                addUri()
-            })
-            drop()
-            assertThat(rule.activity.requestedDragAndDropPermissions).isEmpty()
-        }
-    }
-
-    @Test
-    fun multipleClipDataItems_concatsByNewLine() {
-        rule.setContentAndTestDragAndDrop("aaaa") {
-            drag(
-                Offset(fontSize.toPx() * 2, 10f),
-                createClipData {
-                    addText("Hello")
-                    addText("World")
-                }
-            )
-            drop()
-            assertThat(state.text.toString()).isEqualTo("aaHello\nWorldaa")
-        }
-    }
-
-    private fun ComposeContentTestRule.setContentAndTestDragAndDrop(
-        textContent: String = "aaaa",
-        isWindowFocused: Boolean = false,
-        style: TextStyle = TextStyle.Default,
-        interactionSource: MutableInteractionSource? = null,
-        modifier: Modifier = Modifier,
-        block: DragAndDropTestScope.() -> Unit
-    ) {
-        val state = TextFieldState(
-            textContent,
-            initialSelectionInChars = TextRange.Zero
-        )
-        var view: View? = null
-        val density = Density(1f, 1f)
-        val mergedStyle = TextStyle(
-            fontFamily = TEST_FONT_FAMILY,
-            fontSize = 20.sp
-        ).merge(style)
-        var isHovered: State<Boolean>? = null
-        setContent { // Do not use setTextFieldTestContent for DnD tests.
-            view = LocalView.current
-            CompositionLocalProvider(
-                LocalDensity provides density,
-                LocalWindowInfo provides object : WindowInfo {
-                    override val isWindowFocused = isWindowFocused
-                }
-            ) {
-                isHovered = interactionSource?.collectIsHoveredAsState()
-                BasicTextField2(
-                    state = state,
-                    textStyle = mergedStyle,
-                    lineLimits = TextFieldLineLimits.SingleLine,
-                    interactionSource = interactionSource,
-                    modifier = modifier
-                )
-            }
-        }
-
-        testDragAndDrop(view!!, density) {
-            DragAndDropTestScope(state, mergedStyle.fontSize, isHovered, this).block()
-        }
-    }
-
-    @OptIn(ExperimentalFoundationApi::class)
-    private class DragAndDropTestScope(
-        val state: TextFieldState,
-        val fontSize: TextUnit,
-        isHovered: State<Boolean>?,
-        dragAndDropScopeImpl: DragAndDropScope,
-    ) : DragAndDropScope by dragAndDropScopeImpl {
-        val isHovered: Boolean by (isHovered ?: mutableStateOf(false))
-    }
-}
-
-private val defaultUri = Uri.parse("content://com.example/content.jpg")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldFocusTest.kt
deleted file mode 100644
index bd0725f..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldFocusTest.kt
+++ /dev/null
@@ -1,501 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.os.SystemClock
-import android.view.InputDevice
-import android.view.KeyEvent
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.border
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.NativeKeyEvent
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.onRoot
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyPress
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-internal class TextFieldFocusTest {
-    @get:Rule
-    val rule = createComposeRule()
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val testKeyboardController = TestSoftwareKeyboardController(rule)
-
-    @Composable
-    private fun TextFieldApp(dataList: List<FocusTestData>) {
-        for (data in dataList) {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                modifier = Modifier
-                    .focusRequester(data.focusRequester)
-                    .onFocusChanged { data.focused = it.isFocused }
-                    .requiredWidth(10.dp)
-            )
-        }
-    }
-
-    data class FocusTestData(val focusRequester: FocusRequester, var focused: Boolean = false)
-
-    @Test
-    fun requestFocus() {
-        lateinit var testDataList: List<FocusTestData>
-
-        rule.setContent {
-            testDataList = listOf(
-                FocusTestData(FocusRequester()),
-                FocusTestData(FocusRequester()),
-                FocusTestData(FocusRequester())
-            )
-
-            TextFieldApp(testDataList)
-        }
-
-        rule.runOnIdle { testDataList[0].focusRequester.requestFocus() }
-
-        rule.runOnIdle {
-            assertThat(testDataList[0].focused).isTrue()
-            assertThat(testDataList[1].focused).isFalse()
-            assertThat(testDataList[2].focused).isFalse()
-        }
-
-        rule.runOnIdle { testDataList[1].focusRequester.requestFocus() }
-        rule.runOnIdle {
-            assertThat(testDataList[0].focused).isFalse()
-            assertThat(testDataList[1].focused).isTrue()
-            assertThat(testDataList[2].focused).isFalse()
-        }
-
-        rule.runOnIdle { testDataList[2].focusRequester.requestFocus() }
-        rule.runOnIdle {
-            assertThat(testDataList[0].focused).isFalse()
-            assertThat(testDataList[1].focused).isFalse()
-            assertThat(testDataList[2].focused).isTrue()
-        }
-    }
-
-    @Test
-    fun noCrashWhenSwitchingBetweenEnabledFocusedAndDisabledTextField() {
-        val enabled = mutableStateOf(true)
-        var focused = false
-        val tag = "textField"
-        rule.setContent {
-            val state = remember { TextFieldState() }
-            BasicTextField2(
-                state = state,
-                enabled = enabled.value,
-                modifier = Modifier
-                    .testTag(tag)
-                    .onFocusChanged {
-                        focused = it.isFocused
-                    }
-                    .requiredWidth(10.dp)
-            )
-        }
-        // bring enabled text field into focus
-        rule.onNodeWithTag(tag).performClick()
-        rule.runOnIdle {
-            assertThat(focused).isTrue()
-        }
-
-        // make text field disabled
-        enabled.value = false
-        rule.runOnIdle {
-            assertThat(focused).isFalse()
-        }
-
-        // make text field enabled again, it must not crash
-        enabled.value = true
-        rule.runOnIdle {
-            assertThat(focused).isFalse()
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun textInputStarted_forFieldInActivity_whenFocusRequestedImmediately_fromLaunchedEffect() {
-        textInputStarted_whenFocusRequestedImmediately_fromEffect(
-            runEffect = {
-                LaunchedEffect(Unit) {
-                    it()
-                }
-            }
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun textInputStarted_forFieldInActivity_whenFocusRequestedImmediately_fromDisposableEffect() {
-        textInputStarted_whenFocusRequestedImmediately_fromEffect(
-            runEffect = {
-                DisposableEffect(Unit) {
-                    it()
-                    onDispose {}
-                }
-            }
-        )
-    }
-
-    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
-    //  this test can't assert.
-    @SdkSuppress(minSdkVersion = 30)
-    @Test
-    fun textInputStarted_forFieldInDialog_whenFocusRequestedImmediately_fromLaunchedEffect() {
-        textInputStarted_whenFocusRequestedImmediately_fromEffect(
-            runEffect = {
-                LaunchedEffect(Unit) {
-                    it()
-                }
-            },
-            wrapContent = {
-                Dialog(onDismissRequest = {}, content = it)
-            }
-        )
-    }
-
-    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
-    //  this test can't assert.
-    @SdkSuppress(minSdkVersion = 30)
-    @Test
-    fun textInputStarted_forFieldInDialog_whenFocusRequestedImmediately_fromDisposableEffect() {
-        textInputStarted_whenFocusRequestedImmediately_fromEffect(
-            runEffect = {
-                DisposableEffect(Unit) {
-                    it()
-                    onDispose {}
-                }
-            },
-            wrapContent = {
-                Dialog(onDismissRequest = {}, content = it)
-            }
-        )
-    }
-
-    private fun textInputStarted_whenFocusRequestedImmediately_fromEffect(
-        runEffect: @Composable (body: () -> Unit) -> Unit,
-        wrapContent: @Composable (@Composable () -> Unit) -> Unit = { it() }
-    ) {
-        val focusRequester = FocusRequester()
-        val state = TextFieldState()
-
-        inputMethodInterceptor.setContent {
-            wrapContent {
-                runEffect {
-                    focusRequester.requestFocus()
-                }
-
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier.focusRequester(focusRequester)
-                )
-            }
-        }
-
-        inputMethodInterceptor.assertSessionActive()
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun basicTextField_checkFocusNavigation_onDPadLeft() {
-        setupAndEnableBasicTextField()
-        inputSingleLineTextInBasicTextField()
-
-        // Dismiss keyboard on back press
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
-        rule.waitForIdle()
-
-        // Move focus to the focusable element on left
-        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_LEFT)) return
-
-        // Check if the element to the left of text field gains focus
-        rule.onNodeWithTag("test-button-left").assertIsFocused()
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun basicTextField_checkFocusNavigation_onDPadRight() {
-        setupAndEnableBasicTextField()
-        inputSingleLineTextInBasicTextField()
-
-        // Dismiss keyboard on back press
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
-        rule.waitForIdle()
-
-        // Move focus to the focusable element on right
-        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_RIGHT)) return
-
-        // Check if the element to the right of text field gains focus
-        rule.onNodeWithTag("test-button-right").assertIsFocused()
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun basicTextField_checkFocusNavigation_onDPadUp() {
-        setupAndEnableBasicTextField()
-        inputMultilineTextInBasicTextField()
-
-        // Dismiss keyboard on back press
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
-        rule.waitForIdle()
-
-        // Move focus to the focusable element on top
-        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_UP)) return
-
-        // Check if the element on the top of text field gains focus
-        rule.onNodeWithTag("test-button-top").assertIsFocused()
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742195
-    @Test
-    fun basicTextField_checkFocusNavigation_onDPadDown() {
-        setupAndEnableBasicTextField()
-        inputMultilineTextInBasicTextField()
-
-        // Dismiss keyboard on back press
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
-        rule.waitForIdle()
-
-        // Move focus to the focusable element on bottom
-        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_DOWN)) return
-
-        // Check if the element to the bottom of text field gains focus
-        rule.onNodeWithTag("test-button-bottom").assertIsFocused()
-    }
-
-    @FlakyTest(bugId = 305087008)
-    @Test
-    fun basicTextField_checkKeyboardShown_onDPadCenter() {
-        setupAndEnableBasicTextField()
-        inputSingleLineTextInBasicTextField()
-
-        // Dismiss keyboard on back press
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
-        testKeyboardController.assertHidden()
-
-        // Check if keyboard is enabled on Dpad center key press
-        if (!keyPressOnDpadInputDevice(rule, NativeKeyEvent.KEYCODE_DPAD_CENTER)) return
-        testKeyboardController.assertShown()
-    }
-
-    @Test
-    fun basicTextField_handlesInvalidDevice() {
-        setupAndEnableBasicTextField()
-        inputSingleLineTextInBasicTextField()
-
-        // -2 shouldn't be a valid device – we verify this below by asserting the device in the
-        // event is actually null.
-        val invalidDeviceId = -2
-        val keyCode = NativeKeyEvent.KEYCODE_DPAD_CENTER
-        val keyEventDown = KeyEvent(
-            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
-            KeyEvent.ACTION_DOWN, keyCode, 0, 0, invalidDeviceId, 0
-        )
-        assertThat(keyEventDown.device).isNull()
-        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
-        rule.waitForIdle()
-        val keyEventUp = KeyEvent(
-            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
-            KeyEvent.ACTION_UP, keyCode, 0, 0, invalidDeviceId, 0
-        )
-        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
-        rule.waitForIdle()
-    }
-
-    private fun setupAndEnableBasicTextField() {
-        setupContent()
-
-        rule.onNodeWithTag("test-text-field-1").assertIsFocused()
-    }
-
-    private fun inputSingleLineTextInBasicTextField() {
-        // Input "abc"
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
-        rule.waitForIdle()
-    }
-
-    private fun inputMultilineTextInBasicTextField() {
-        // Input "a\nb\nc"
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
-        rule.waitForIdle()
-        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
-        rule.waitForIdle()
-    }
-
-    private fun setupContent() {
-        rule.setContent {
-            CompositionLocalProvider(
-                LocalSoftwareKeyboardController provides testKeyboardController
-            ) {
-                Column {
-                    Row(horizontalArrangement = Arrangement.Center) {
-                        TestFocusableElement(id = "top")
-                    }
-                    Row {
-                        TestFocusableElement(id = "left")
-                        TestBasicTextField2(id = "1", requestFocus = true)
-                        TestFocusableElement(id = "right")
-                    }
-                    Row(horizontalArrangement = Arrangement.Center) {
-                        TestFocusableElement(id = "bottom")
-                    }
-                }
-            }
-        }
-        rule.waitForIdle()
-    }
-
-    @Composable
-    private fun TestFocusableElement(id: String) {
-        var isFocused by remember {
-            mutableStateOf(false)
-        }
-        BasicText(
-            text = "test-button-$id",
-            modifier = Modifier
-                .testTag("test-button-$id")
-                .padding(10.dp)
-                .onFocusChanged {
-                    isFocused = it.hasFocus
-                }
-                .focusable()
-                .border(2.dp, if (isFocused) Color.Green else Color.Cyan)
-        )
-    }
-
-    @Composable
-    private fun TestBasicTextField2(
-        id: String,
-        requestFocus: Boolean = false
-    ) {
-        val state = rememberTextFieldState()
-        var isFocused by remember {
-            mutableStateOf(false)
-        }
-        val focusRequester = remember {
-            FocusRequester()
-        }
-        val modifier = if (requestFocus) Modifier.focusRequester(focusRequester) else Modifier
-
-        BasicTextField2(
-            state = state,
-            modifier = modifier
-                .testTag("test-text-field-$id")
-                .padding(10.dp)
-                .onFocusChanged {
-                    isFocused = it.isFocused || it.hasFocus
-                }
-                .border(2.dp, if (isFocused) Color.Red else Color.Transparent)
-        )
-
-        LaunchedEffect(requestFocus, focusRequester) {
-            if (requestFocus) focusRequester.requestFocus()
-        }
-    }
-
-    /** Triggers a key press on the root node from a non-virtual dpad device (if supported). */
-    private fun keyPressOnDpadInputDevice(
-        rule: ComposeContentTestRule,
-        keyCode: Int,
-        count: Int = 1
-    ) = keyPressOnPhysicalDevice(rule, keyCode, InputDevice.SOURCE_DPAD, count)
-
-    private fun keyPressOnPhysicalDevice(
-        rule: ComposeContentTestRule,
-        keyCode: Int,
-        source: Int,
-        count: Int = 1,
-        metaState: Int = 0,
-    ): Boolean {
-        val deviceId = InputDevice.getDeviceIds().firstOrNull { id ->
-            InputDevice.getDevice(id)?.isVirtual?.not() ?: false &&
-                InputDevice.getDevice(id)?.supportsSource(source) ?: false
-        } ?: return false
-        val keyEventDown = KeyEvent(
-            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
-            KeyEvent.ACTION_DOWN, keyCode, 0, metaState,
-            deviceId, 0, 0, InputDevice.SOURCE_DPAD
-        )
-        val keyEventUp = KeyEvent(
-            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
-            KeyEvent.ACTION_UP, keyCode, 0, metaState,
-            deviceId, 0, 0, InputDevice.SOURCE_DPAD
-        )
-
-        repeat(count) {
-            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
-            rule.waitForIdle()
-            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
-        }
-        return true
-    }
-
-    /** Triggers a key press on the virtual keyboard. */
-    private fun keyPressOnVirtualKeyboard(keyCode: Int, count: Int = 1) {
-        repeat(count) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyEventTest.kt
deleted file mode 100644
index 8bbe99b..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyEventTest.kt
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text2.BasicSecureTextField
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.ClipboardManager
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.KeyInjectionScope
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.withKeyDown
-import androidx.compose.ui.test.withKeysDown
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(
-    ExperimentalFoundationApi::class,
-    ExperimentalTestApi::class
-)
-class TextFieldKeyEventTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val tag = "TextFieldTestTag"
-
-    private var defaultDensity = Density(1f)
-
-    @Test
-    fun textField_typedEvents() {
-        keysSequenceTest {
-            pressKey(Key.H)
-            press(Key.ShiftLeft + Key.I)
-            expectedText("hI")
-        }
-    }
-
-    @Test
-    fun textField_copyPaste() {
-        keysSequenceTest("hello") {
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.A)
-                pressKey(Key.C)
-            }
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Spacebar)
-            press(Key.CtrlLeft + Key.V)
-            expectedText("hello hello")
-        }
-    }
-
-    @Test
-    fun secureTextField_doesNotAllowCopy() {
-        keysSequenceTest("hello", secure = true) {
-            clipboardManager.setText(AnnotatedString("world"))
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.A)
-                pressKey(Key.C)
-            }
-            pressKey(Key.Copy) // also attempt direct copy
-            expectedClipboardText("world")
-        }
-    }
-
-    @Test
-    fun textField_directCopyPaste() {
-        keysSequenceTest("hello") {
-            press(Key.CtrlLeft + Key.A)
-            pressKey(Key.Copy)
-            expectedText("hello")
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Spacebar)
-            pressKey(Key.Paste)
-            expectedText("hello hello")
-        }
-    }
-
-    @Test
-    fun textField_directCutPaste() {
-        keysSequenceTest("hello") {
-            press(Key.CtrlLeft + Key.A)
-            pressKey(Key.Cut)
-            expectedText("")
-            pressKey(Key.Paste)
-            expectedText("hello")
-        }
-    }
-
-    @Test
-    fun secureTextField_doesNotAllowCut() {
-        keysSequenceTest("hello", secure = true) {
-            clipboardManager.setText(AnnotatedString("world"))
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.A)
-                pressKey(Key.X)
-            }
-            pressKey(Key.Cut) // Also attempts direct cut
-            expectedText("hello")
-            expectedClipboardText("world")
-        }
-    }
-
-    @Test
-    fun textField_linesNavigation() {
-        keysSequenceTest("hello\nworld") {
-            pressKey(Key.DirectionDown)
-            pressKey(Key.A)
-            pressKey(Key.DirectionUp)
-            pressKey(Key.A)
-            expectedText("haello\naworld")
-            pressKey(Key.DirectionUp)
-            pressKey(Key.A)
-            expectedText("ahaello\naworld")
-        }
-    }
-
-    @Test
-    fun textField_linesNavigation_cache() {
-        keysSequenceTest("hello\n\nworld") {
-            pressKey(Key.DirectionRight)
-            pressKey(Key.DirectionDown)
-            pressKey(Key.DirectionDown)
-            pressKey(Key.Zero)
-            expectedText("hello\n\nw0orld")
-        }
-    }
-
-    @Test
-    fun textField_newLine() {
-        keysSequenceTest("hello") {
-            pressKey(Key.Enter)
-            expectedText("\nhello")
-        }
-    }
-
-    @Test
-    fun textField_backspace() {
-        keysSequenceTest("hello") {
-            pressKey(Key.DirectionRight)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Backspace)
-            expectedText("hllo")
-        }
-    }
-
-    @Test
-    fun textField_delete() {
-        keysSequenceTest("hello") {
-            pressKey(Key.Delete)
-            expectedText("ello")
-        }
-    }
-
-    @Test
-    fun textField_delete_atEnd() {
-        keysSequenceTest("hello", TextRange(5)) {
-            pressKey(Key.Delete)
-            expectedText("hello")
-        }
-    }
-
-    @Test
-    fun textField_delete_whenEmpty() {
-        keysSequenceTest {
-            pressKey(Key.Delete)
-            expectedText("")
-        }
-    }
-
-    @Test
-    fun textField_nextWord() {
-        keysSequenceTest("hello world") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.Zero)
-            expectedText("hello0 world")
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.Zero)
-            expectedText("hello0 world0")
-        }
-    }
-
-    @Test
-    fun textField_nextWord_doubleSpace() {
-        keysSequenceTest("hello  world") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.DirectionRight)
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.Zero)
-            expectedText("hello  world0")
-        }
-    }
-
-    @Test
-    fun textField_prevWord() {
-        keysSequenceTest("hello world") {
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionLeft)
-            }
-            pressKey(Key.Zero)
-            expectedText("hello 0world")
-        }
-    }
-
-    @Test
-    fun textField_HomeAndEnd() {
-        keysSequenceTest("hello world") {
-            pressKey(Key.MoveEnd)
-            pressKey(Key.Zero)
-            pressKey(Key.MoveHome)
-            pressKey(Key.Zero)
-            expectedText("0hello world0")
-        }
-    }
-
-    @Test
-    fun textField_byWordSelection() {
-        keysSequenceTest("hello  world\nhi") {
-            withKeysDown(listOf(Key.ShiftLeft, Key.CtrlLeft)) {
-                pressKey(Key.DirectionRight)
-                expectedSelection(TextRange(0, 5))
-                pressKey(Key.DirectionRight)
-                expectedSelection(TextRange(0, 12))
-                pressKey(Key.DirectionRight)
-                expectedSelection(TextRange(0, 15))
-                pressKey(Key.DirectionLeft)
-                expectedSelection(TextRange(0, 13))
-            }
-        }
-    }
-
-    @Test
-    fun textField_lineEndStart() {
-        keysSequenceTest(initText = "hi\nhello world\nhi") {
-            pressKey(Key.MoveEnd)
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Zero)
-            expectedText("hi\n0hello world\nhi")
-            pressKey(Key.MoveEnd)
-            pressKey(Key.Zero)
-            expectedText("hi\n0hello world0\nhi")
-            withKeyDown(Key.ShiftLeft) { pressKey(Key.MoveHome) }
-            expectedSelection(TextRange(16, 3))
-            pressKey(Key.MoveHome)
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) { pressKey(Key.MoveEnd) }
-            expectedSelection(TextRange(4, 16))
-            expectedText("hi\n0hello world0\nhi")
-        }
-    }
-
-    @Test
-    fun textField_altLineLeftRight() {
-        keysSequenceTest(initText = "hi\nhello world\nhi") {
-            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionRight) }
-            pressKey(Key.DirectionRight)
-            pressKey(Key.Zero)
-            expectedText("hi\n0hello world\nhi")
-            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionRight) }
-            pressKey(Key.Zero)
-            expectedText("hi\n0hello world0\nhi")
-            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionLeft) }
-            expectedSelection(TextRange(16, 3))
-            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionLeft) }
-            pressKey(Key.DirectionRight)
-            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionRight) }
-            expectedSelection(TextRange(4, 16))
-            expectedText("hi\n0hello world0\nhi")
-        }
-    }
-
-    @Test
-    fun textField_altTop() {
-        keysSequenceTest(initText = "hi\nhello world\nhi") {
-            pressKey(Key.MoveEnd)
-            repeat(3) { pressKey(Key.DirectionRight) }
-            pressKey(Key.Zero)
-            expectedText("hi\nhe0llo world\nhi")
-            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionUp) }
-            pressKey(Key.Zero)
-            expectedText("0hi\nhe0llo world\nhi")
-            pressKey(Key.MoveEnd)
-            repeat(3) { pressKey(Key.DirectionRight) }
-            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionUp) }
-            expectedSelection(TextRange(6, 0))
-            expectedText("0hi\nhe0llo world\nhi")
-        }
-    }
-
-    @Test
-    fun textField_altBottom() {
-        keysSequenceTest(initText = "hi\nhello world\nhi") {
-            pressKey(Key.MoveEnd)
-            repeat(3) { pressKey(Key.DirectionRight) }
-            pressKey(Key.Zero)
-            expectedText("hi\nhe0llo world\nhi")
-            withKeysDown(listOf(Key.ShiftLeft, Key.AltLeft)) { pressKey(Key.DirectionDown) }
-            expectedSelection(TextRange(6, 18))
-            pressKey(Key.DirectionLeft)
-            pressKey(Key.Zero)
-            expectedText("hi\nhe00llo world\nhi")
-            withKeyDown(Key.AltLeft) { pressKey(Key.DirectionDown) }
-            pressKey(Key.Zero)
-            expectedText("hi\nhe00llo world\nhi0")
-        }
-    }
-
-    @Ignore("b/305692638")
-    @Test
-    fun textField_deleteWords() {
-        keysSequenceTest("hello world\nhi world") {
-            pressKey(Key.MoveEnd)
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.Backspace)
-                expectedText("hello \nhi world")
-                pressKey(Key.Delete)
-            }
-            expectedText("hello  world")
-        }
-    }
-
-    @Test
-    fun textField_deleteToBeginningOfLine() {
-        keysSequenceTest("hello world\nhi world") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-
-            withKeyDown(Key.AltLeft) {
-                pressKey(Key.Backspace)
-                expectedText(" world\nhi world")
-                pressKey(Key.Backspace)
-                expectedText(" world\nhi world")
-            }
-
-            repeat(3) { pressKey(Key.DirectionRight) }
-
-            press(Key.AltLeft + Key.Backspace)
-            expectedText("rld\nhi world")
-            pressKey(Key.DirectionDown)
-            pressKey(Key.MoveEnd)
-
-            withKeyDown(Key.AltLeft) {
-                pressKey(Key.Backspace)
-                expectedText("rld\n")
-                pressKey(Key.Backspace)
-                expectedText("rld\n")
-            }
-        }
-    }
-
-    @Test
-    fun textField_deleteToEndOfLine() {
-        keysSequenceTest("hello world\nhi world") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-            withKeyDown(Key.AltLeft) {
-                pressKey(Key.Delete)
-                expectedText("hello\nhi world")
-                pressKey(Key.Delete)
-                expectedText("hello\nhi world")
-            }
-
-            repeat(3) { pressKey(Key.DirectionRight) }
-
-            press(Key.AltLeft + Key.Delete)
-            expectedText("hello\nhi")
-
-            pressKey(Key.MoveHome)
-            withKeyDown(Key.AltLeft) {
-                pressKey(Key.Delete)
-                expectedText("hello\n")
-                pressKey(Key.Delete)
-                expectedText("hello\n")
-            }
-        }
-    }
-
-    @Test
-    fun textField_paragraphNavigation() {
-        keysSequenceTest("hello world\nhi") {
-            press(Key.CtrlLeft + Key.DirectionDown)
-            pressKey(Key.Zero)
-            expectedText("hello world0\nhi")
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.DirectionDown)
-                pressKey(Key.DirectionUp)
-            }
-            pressKey(Key.Zero)
-            expectedText("hello world0\n0hi")
-            withKeyDown(Key.CtrlLeft) {
-                pressKey(Key.DirectionUp)
-                pressKey(Key.DirectionUp)
-            }
-            pressKey(Key.Zero)
-            expectedText("0hello world0\n0hi")
-        }
-    }
-
-    @Test
-    fun textField_selectionCaret() {
-        keysSequenceTest("hello world") {
-            press(Key.CtrlLeft + Key.ShiftLeft + Key.DirectionRight)
-            expectedSelection(TextRange(0, 5))
-            press(Key.ShiftLeft + Key.DirectionRight)
-            expectedSelection(TextRange(0, 6))
-            press(Key.CtrlLeft + Key.Backslash)
-            expectedSelection(TextRange(6, 6))
-            press(Key.CtrlLeft + Key.ShiftLeft + Key.DirectionLeft)
-            expectedSelection(TextRange(6, 0))
-            press(Key.ShiftLeft + Key.DirectionRight)
-            expectedSelection(TextRange(6, 1))
-        }
-    }
-
-    @Test
-    fun textField_pageNavigationDown() {
-        keysSequenceTest(
-            initText = "A\nB\nC\nD\nE",
-            modifier = Modifier.requiredSize(73.dp)
-        ) {
-            pressKey(Key.PageDown)
-            expectedSelection(TextRange(4))
-        }
-    }
-
-    @Test
-    fun textField_pageNavigationDown_exactFit() {
-        keysSequenceTest(
-            initText = "A\nB\nC\nD\nE",
-            modifier = Modifier.requiredSize(90.dp) // exactly 3 lines fit
-        ) {
-            pressKey(Key.PageDown)
-            expectedSelection(TextRange(6))
-        }
-    }
-
-    @Test
-    fun textField_pageNavigationUp() {
-        keysSequenceTest(
-            initText = "A\nB\nC\nD\nE",
-            initSelection = TextRange(8), // just before 5
-            modifier = Modifier.requiredSize(73.dp)
-        ) {
-            pressKey(Key.PageUp)
-            expectedSelection(TextRange(4))
-        }
-    }
-
-    @Test
-    fun textField_pageNavigationUp_exactFit() {
-        keysSequenceTest(
-            initText = "A\nB\nC\nD\nE",
-            initSelection = TextRange(8), // just before 5
-            modifier = Modifier.requiredSize(90.dp) // exactly 3 lines fit
-        ) {
-            pressKey(Key.PageUp)
-            expectedSelection(TextRange(2))
-        }
-    }
-
-    @Test
-    fun textField_pageNavigationUp_cantGoUp() {
-        keysSequenceTest(
-            initText = "1\n2\n3\n4\n5",
-            initSelection = TextRange(0),
-            modifier = Modifier.requiredSize(90.dp)
-        ) {
-            pressKey(Key.PageUp)
-            expectedSelection(TextRange(0))
-        }
-    }
-
-    @Test
-    fun textField_tabSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            pressKey(Key.Tab)
-            expectedText("text") // no change, should try focus change instead
-        }
-    }
-
-    @Test
-    fun textField_tabMultiLine() {
-        keysSequenceTest("text") {
-            pressKey(Key.Tab)
-            expectedText("\ttext")
-        }
-    }
-
-    @Test
-    fun textField_shiftTabSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            press(Key.ShiftLeft + Key.Tab)
-            expectedText("text") // no change, should try focus change instead
-        }
-    }
-
-    @Test
-    fun textField_enterSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            pressKey(Key.Enter)
-            expectedText("text") // no change, should do ime action instead
-        }
-    }
-
-    @Test
-    fun textField_enterMultiLine() {
-        keysSequenceTest("text") {
-            pressKey(Key.Enter)
-            expectedText("\ntext")
-        }
-    }
-
-    @Test
-    fun textField_withActiveSelection_tabSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-            }
-            pressKey(Key.Tab)
-            expectedText("text") // no change, should try focus change instead
-        }
-    }
-
-    @Test
-    fun textField_withActiveSelection_tabMultiLine() {
-        keysSequenceTest("text") {
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-            }
-            pressKey(Key.Tab)
-            expectedText("t\tt")
-        }
-    }
-
-    @Test
-    fun textField_selectToLeft() {
-        keysSequenceTest("hello world hello") {
-            pressKey(Key.MoveEnd)
-            expectedSelection(TextRange(17))
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionLeft)
-                pressKey(Key.DirectionLeft)
-                pressKey(Key.DirectionLeft)
-            }
-            expectedSelection(TextRange(17, 14))
-        }
-    }
-
-    @Test
-    fun textField_withActiveSelection_shiftTabSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-                pressKey(Key.Tab)
-            }
-            expectedText("text") // no change, should try focus change instead
-        }
-    }
-
-    @Test
-    fun textField_withActiveSelection_enterSingleLine() {
-        keysSequenceTest("text", singleLine = true) {
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-            }
-            pressKey(Key.Enter)
-            expectedText("text") // no change, should do ime action instead
-        }
-    }
-
-    @Test
-    fun textField_withActiveSelection_enterMultiLine() {
-        keysSequenceTest("text") {
-            pressKey(Key.DirectionRight)
-            withKeyDown(Key.ShiftLeft) {
-                pressKey(Key.DirectionRight)
-                pressKey(Key.DirectionRight)
-            }
-            pressKey(Key.Enter)
-            expectedText("t\nt")
-        }
-    }
-
-    @Test
-    fun textField_simpleUndo() {
-        keysSequenceTest("hello") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.Spacebar)
-            pressKey(Key.A)
-            pressKey(Key.B)
-            pressKey(Key.C)
-            expectedText("hello abc")
-            press(Key.CtrlLeft + Key.Z)
-            expectedText("hello")
-        }
-    }
-
-    @Test
-    fun textField_simpleRedo() {
-        keysSequenceTest("hello") {
-            press(Key.CtrlLeft + Key.DirectionRight)
-            pressKey(Key.Spacebar)
-            pressKey(Key.A)
-            pressKey(Key.B)
-            pressKey(Key.C)
-            expectedText("hello abc")
-            press(Key.CtrlLeft + Key.Z)
-            expectedText("hello")
-            press(Key.CtrlLeft + Key.ShiftLeft + Key.Z)
-            expectedText("hello abc")
-        }
-    }
-
-    private inner class SequenceScope(
-        val state: TextFieldState,
-        val clipboardManager: ClipboardManager,
-        private val keyInjectionScope: KeyInjectionScope
-    ) : KeyInjectionScope by keyInjectionScope {
-
-        fun press(keys: List<Key>) {
-            require(keys.isNotEmpty()) { "At least one key must be specified for press action" }
-            if (keys.size == 1) {
-                pressKey(keys.first())
-            } else {
-                withKeysDown(keys.dropLast(1)) { pressKey(keys.last()) }
-            }
-        }
-
-        infix operator fun Key.plus(other: Key): MutableList<Key> {
-            return mutableListOf(this, other)
-        }
-
-        fun expectedText(text: String) {
-            rule.runOnIdle {
-                assertThat(state.text.toString()).isEqualTo(text)
-            }
-        }
-
-        fun expectedSelection(selection: TextRange) {
-            rule.runOnIdle {
-                assertThat(state.text.selectionInChars).isEqualTo(selection)
-            }
-        }
-
-        fun expectedClipboardText(text: String) {
-            rule.runOnIdle {
-                assertThat(clipboardManager.getText()?.text).isEqualTo(text)
-            }
-        }
-    }
-
-    private fun keysSequenceTest(
-        initText: String = "",
-        initSelection: TextRange = TextRange.Zero,
-        modifier: Modifier = Modifier.fillMaxSize(),
-        singleLine: Boolean = false,
-        secure: Boolean = false,
-        sequence: SequenceScope.() -> Unit,
-    ) {
-        val state = TextFieldState(initText, initSelection)
-        val focusRequester = FocusRequester()
-        val clipboardManager = FakeClipboardManager("InitialTestText")
-        rule.setContent {
-            CompositionLocalProvider(
-                LocalDensity provides defaultDensity,
-                LocalClipboardManager provides clipboardManager,
-            ) {
-                if (!secure) {
-                    BasicTextField2(
-                        state = state,
-                        textStyle = TextStyle(
-                            fontFamily = TEST_FONT_FAMILY,
-                            fontSize = 30.sp
-                        ),
-                        modifier = modifier
-                            .focusRequester(focusRequester)
-                            .testTag(tag),
-                        lineLimits = if (singleLine) SingleLine else MultiLine(),
-                    )
-                } else {
-                    BasicSecureTextField(
-                        state = state,
-                        textStyle = TextStyle(
-                            fontFamily = TEST_FONT_FAMILY,
-                            fontSize = 30.sp
-                        ),
-                        modifier = modifier
-                            .focusRequester(focusRequester)
-                            .testTag(tag)
-                    )
-                }
-            }
-        }
-
-        rule.runOnIdle { focusRequester.requestFocus() }
-
-        rule.waitForIdle()
-        rule.mainClock.advanceTimeBy(1000)
-
-        rule.onNodeWithTag(tag).performKeyInput {
-            sequence(SequenceScope(state, clipboardManager, this@performKeyInput))
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyboardActionsTest.kt
deleted file mode 100644
index 67a6dee..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldKeyboardActionsTest.kt
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import android.view.inputmethod.EditorInfo
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.KeyboardActionScope
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.assertIsNotFocused
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performImeAction
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldKeyboardActionsTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    @Test
-    fun textField_performsImeAction_viaSemantics() {
-        var called = false
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
-                keyboardActions = KeyboardActions {
-                    called = true
-                }
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).performImeAction()
-
-        assertThat(called).isTrue()
-    }
-
-    @Test
-    fun textField_performsImeAction_viaInputConnection() {
-        var called = false
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
-                keyboardActions = KeyboardActions {
-                    called = true
-                }
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).requestFocus()
-
-        inputMethodInterceptor.withInputConnection {
-            performEditorAction(EditorInfo.IME_ACTION_SEND)
-            assertThat(called).isTrue()
-        }
-    }
-
-    @Test
-    fun textField_performsUnexpectedImeAction_fromInputConnection() {
-        var calledFor: ImeAction? = null
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
-                keyboardActions = KeyboardActionsAll {
-                    calledFor = it
-                }
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).requestFocus()
-
-        inputMethodInterceptor.withInputConnection {
-            performEditorAction(EditorInfo.IME_ACTION_SEARCH)
-            assertThat(calledFor).isEqualTo(ImeAction.Search)
-        }
-    }
-
-    @Test
-    fun textField_performsDefaultBehavior_forFocusNext() {
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box1"))
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
-                )
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box2"))
-            }
-        }
-
-        rule.onNodeWithTag("box2").assertIsNotFocused()
-        rule.onNode(hasSetTextAction()).performImeAction()
-        rule.onNodeWithTag("box2").assertIsFocused()
-    }
-
-    @Test
-    fun textField_performsDefaultBehavior_forFocusPrevious() {
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box1"))
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Previous)
-                )
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box2"))
-            }
-        }
-
-        rule.onNodeWithTag("box1").assertIsNotFocused()
-        rule.onNode(hasSetTextAction()).performImeAction()
-        rule.onNodeWithTag("box1").assertIsFocused()
-    }
-
-    @SdkSuppress(minSdkVersion = 23)
-    @Test
-    fun textField_performsDefaultBehavior_forDone() {
-        val testKeyboardController = TestSoftwareKeyboardController(rule)
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(
-                LocalSoftwareKeyboardController provides testKeyboardController
-            ) {
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
-                )
-            }
-        }
-
-        rule.onNode(hasSetTextAction()).performClick()
-        testKeyboardController.assertShown()
-        rule.onNode(hasSetTextAction()).performImeAction()
-        testKeyboardController.assertHidden()
-    }
-
-    @Test
-    fun textField_canOverrideDefaultBehavior() {
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box1"))
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
-                    keyboardActions = KeyboardActionsAll {
-                        // don't call default action
-                    }
-                )
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box2"))
-            }
-        }
-
-        rule.onNodeWithTag("box2").assertIsNotFocused()
-        rule.onNode(hasSetTextAction()).performImeAction()
-        rule.onNode(hasSetTextAction()).assertIsFocused()
-        rule.onNodeWithTag("box2").assertIsNotFocused()
-    }
-
-    @Test
-    fun textField_canRequestDefaultBehavior() {
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box1"))
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
-                    keyboardActions = KeyboardActionsAll {
-                        defaultKeyboardAction(it)
-                    }
-                )
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box2"))
-            }
-        }
-
-        rule.onNodeWithTag("box2").assertIsNotFocused()
-        rule.onNode(hasSetTextAction()).performImeAction()
-        rule.onNodeWithTag("box2").assertIsFocused()
-    }
-
-    @Test
-    fun textField_performsGo_whenReceivedImeActionIsGo() {
-        var called = false
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardActions = KeyboardActions(onGo = {
-                    called = true
-                })
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).requestFocus()
-
-        inputMethodInterceptor.withInputConnection {
-            performEditorAction(EditorInfo.IME_ACTION_GO)
-            assertThat(called).isTrue()
-        }
-    }
-
-    @Test
-    fun textField_doesNotPerformGo_whenReceivedImeActionIsNotGo() {
-        var called = false
-        inputMethodInterceptor.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardActions = KeyboardActions(onGo = {
-                    called = true
-                })
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).requestFocus()
-
-        inputMethodInterceptor.withInputConnection {
-            performEditorAction(EditorInfo.IME_ACTION_SEARCH)
-            assertThat(called).isFalse()
-        }
-    }
-
-    @Test
-    fun textField_changingKeyboardActions_usesNewKeyboardActions() {
-        var lastCaller = 0
-        val actions1 = KeyboardActionsAll { lastCaller = 1 }
-        val actions2 = KeyboardActionsAll { lastCaller = 2 }
-        var keyboardActions by mutableStateOf(actions1)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
-                keyboardActions = keyboardActions
-            )
-        }
-
-        rule.onNode(hasSetTextAction()).performImeAction()
-        rule.runOnIdle { assertThat(lastCaller).isEqualTo(1) }
-
-        keyboardActions = actions2
-
-        // do not go through focus requests again
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.OnImeAction)
-        rule.runOnIdle { assertThat(lastCaller).isEqualTo(2) }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun textField_singleLinePressEnter_triggersPassedImeAction() {
-        var calledFor: ImeAction? = null
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
-                keyboardActions = KeyboardActionsAll {
-                    calledFor = it
-                },
-                lineLimits = SingleLine
-            )
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            performClick()
-            performKeyInput { pressKey(Key.Enter) }
-        }
-        rule.runOnIdle { assertThat(calledFor).isEqualTo(ImeAction.Go) }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun textField_multiLinePressEnter_doesNotTriggerPassedImeAction() {
-        var calledFor: ImeAction? = null
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = TextFieldState(),
-                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
-                keyboardActions = KeyboardActionsAll {
-                    calledFor = it
-                },
-                lineLimits = MultiLine(maxHeightInLines = 1)
-            )
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            performClick()
-            performKeyInput { pressKey(Key.Enter) }
-        }
-        rule.runOnIdle { assertThat(calledFor).isNull() }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun textField_singleLinePressEnter_triggersDefaultBehavior() {
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box1"))
-                BasicTextField2(
-                    state = TextFieldState(),
-                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
-                    lineLimits = SingleLine
-                )
-                Box(
-                    Modifier
-                        .size(1.dp)
-                        .focusable()
-                        .testTag("box2"))
-            }
-        }
-
-        rule.onNodeWithTag("box2").assertIsNotFocused()
-        with(rule.onNode(hasSetTextAction())) {
-            performClick()
-            performKeyInput { pressKey(Key.Enter) }
-        }
-        rule.onNodeWithTag("box2").assertIsFocused()
-    }
-}
-
-private fun KeyboardActionsAll(
-    onAny: KeyboardActionScope.(ImeAction) -> Unit
-): KeyboardActions = KeyboardActions(
-    onDone = { onAny(ImeAction.Done) },
-    onGo = { onAny(ImeAction.Go) },
-    onNext = { onAny(ImeAction.Next) },
-    onPrevious = { onAny(ImeAction.Previous) },
-    onSearch = { onAny(ImeAction.Search) },
-    onSend = { onAny(ImeAction.Send) }
-)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationGesturesIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
deleted file mode 100644
index 1c6d56f..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldOutputTransformationGesturesIntegrationTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicTextField2"
-
-    @Test
-    fun clickingAroundReplacement_movesCursorToEdgesOfReplacement() {
-        val text = TextFieldState("zaz", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = text,
-                modifier = Modifier.testTag(Tag),
-                textStyle = TextStyle(
-                    textAlign = TextAlign.Center,
-                    fontFamily = TEST_FONT_FAMILY,
-                    fontSize = 10.sp
-                ),
-                outputTransformation = {
-                    replace(1, 2, "bbbb") // "zbbbbz"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).assertTextEquals("zbbbbz")
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // Click 1 pixel to the right of center.
-            click(center + Offset(1f, 0f))
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
-        }
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // Add a delay to avoid triggering double-click.
-            advanceEventTime(1000)
-            // Click 1 pixel to the left of center.
-            click(center + Offset(-1f, 0f))
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        }
-    }
-
-    @Test
-    fun clickingAroundReplacement_movesCursorToEdgesOfReplacement_withLineBreak() {
-        // The top right is geometrically closer to the start of the replacement, even though the
-        // index is closer to the end.
-        // The bottom left is closer to the end.
-        // +-------->---------+
-        // | zzzzzzzzbbbb     |
-        // | bbz              |
-        // +---<--------------+
-
-        val text = TextFieldState("zzzzzzzzaz", initialSelectionInChars = TextRange(0))
-        val replacement = "bbbb\nbb"
-        val indexOfA = text.text.indexOf('a')
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = text,
-                modifier = Modifier.testTag(Tag),
-                textStyle = TextStyle(
-                    textAlign = TextAlign.Left,
-                    fontFamily = TEST_FONT_FAMILY,
-                    fontSize = 10.sp
-                ),
-                outputTransformation = {
-                    replace(indexOfA, indexOfA + 1, replacement)
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).assertTextEquals("zzzzzzzz${replacement}z")
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            click(topRight)
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars).isEqualTo(TextRange(indexOfA))
-        }
-        assertCursor(indexOfA)
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // Add a delay to avoid triggering double-click.
-            advanceEventTime(1000)
-            click(bottomLeft)
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars)
-                .isEqualTo(TextRange(indexOfA + 1))
-        }
-        assertCursor(indexOfA + replacement.length)
-    }
-
-    @Test
-    fun clickingAroundReplacement_movesCursorToEdgesOfInsertion() {
-        val text = TextFieldState("zz", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = text,
-                modifier = Modifier.testTag(Tag),
-                textStyle = TextStyle(
-                    textAlign = TextAlign.Center,
-                    fontFamily = TEST_FONT_FAMILY,
-                    fontSize = 10.sp
-                ),
-                outputTransformation = {
-                    insert(1, "bbbb") // "zbbbbz"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).assertTextEquals("zbbbbz")
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // Click 1 pixel to the right of center.
-            click(center + Offset(1f, 0f))
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        }
-        assertCursor(5)
-
-        rule.onNodeWithTag(Tag).performTouchInput {
-            // Add a delay to avoid triggering double-click.
-            advanceEventTime(1000)
-            // Click 1 pixel to the left of center.
-            click(center + Offset(-1f, 0f))
-        }
-        rule.runOnIdle {
-            assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        }
-        assertCursor(1)
-    }
-
-    private fun assertCursor(offset: Int) {
-        val node = rule.onNodeWithTag(Tag).fetchSemanticsNode()
-        Truth.assertWithMessage("Selection via semantics")
-            .that(node.config[SemanticsProperties.TextSelectionRange])
-            .isEqualTo(TextRange(offset))
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
deleted file mode 100644
index aa5bcf8..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldOutputTransformationHardwareKeysIntegrationTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val Tag = "BasicTextField2"
-
-    @Test
-    fun replacement_visualText() {
-        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    replace(1, 3, "efg") // "aefgd"
-                }
-            )
-        }
-
-        assertVisualText("aefgd")
-    }
-
-    @Test
-    fun replacement_cursorMovement_leftToRight_byCharacter_stateOffsets() {
-        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    replace(1, 3, "efg") // "aefgd"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(3))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(4))
-    }
-
-    @Test
-    fun replacement_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
-        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    replace(1, 3, "efg") // "aefgd"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(4))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(3))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun replacement_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
-        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    replace(1, 3, "efg") // "aefgd"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertCursor(0)
-        pressKey(Key.DirectionRight)
-        assertCursor(1)
-        pressKey(Key.DirectionRight)
-        assertCursor(4)
-        pressKey(Key.DirectionRight)
-        assertCursor(5)
-    }
-
-    @Test
-    fun replacement_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
-        val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    replace(1, 3, "efg") // "aefgd"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertCursor(5)
-        pressKey(Key.DirectionLeft)
-        assertCursor(4)
-        pressKey(Key.DirectionLeft)
-        assertCursor(1)
-        pressKey(Key.DirectionLeft)
-        assertCursor(0)
-    }
-
-    @Test
-    fun insert_visualText() {
-        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    insert(1, "efg") // "aefgb"
-                }
-            )
-        }
-
-        assertVisualText("aefgb")
-    }
-
-    @Test
-    fun insert_cursorMovement_leftToRight_byCharacter_stateOffsets() {
-        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    insert(1, "efg") // "aefgb"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionRight)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun insert_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
-        val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    insert(1, "efg") // "aefgb"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(2))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(1))
-        pressKey(Key.DirectionLeft)
-        assertThat(text.text.selectionInChars).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun insert_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
-        val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    insert(1, "efg") // "aefgb"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertCursor(0)
-        pressKey(Key.DirectionRight)
-        assertCursor(1)
-        pressKey(Key.DirectionRight)
-        assertCursor(4)
-        pressKey(Key.DirectionRight)
-        assertCursor(5)
-    }
-
-    @Test
-    fun insert_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
-        val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = text,
-                modifier = Modifier.testTag(Tag),
-                outputTransformation = {
-                    insert(1, "efg") // "aefgb"
-                }
-            )
-        }
-        rule.onNodeWithTag(Tag).requestFocus()
-
-        assertCursor(5)
-        pressKey(Key.DirectionLeft)
-        assertCursor(4)
-        pressKey(Key.DirectionLeft)
-        assertCursor(1)
-        pressKey(Key.DirectionLeft)
-        assertCursor(0)
-    }
-
-    private fun assertVisualText(text: String) {
-        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
-            .isEqualTo(text)
-    }
-
-    private fun assertCursor(offset: Int) {
-        val node = rule.onNodeWithTag(Tag).fetchSemanticsNode()
-        assertWithMessage("Selection via semantics")
-            .that(node.config[SemanticsProperties.TextSelectionRange])
-            .isEqualTo(TextRange(offset))
-    }
-
-    private fun pressKey(key: Key) {
-        rule.onNodeWithTag(Tag).performKeyInput { pressKey(key) }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldReceiveContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldReceiveContentTest.kt
deleted file mode 100644
index ded6bb20..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldReceiveContentTest.kt
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input
-
-import android.content.ClipDescription
-import android.net.Uri
-import android.os.Bundle
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputConnection
-import android.view.inputmethod.InputContentInfo
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.assertClipData
-import androidx.compose.foundation.content.consumeEach
-import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
-import androidx.compose.foundation.draganddrop.dragAndDropTarget
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.text.selection.FakeTextToolbar
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.draganddrop.DragAndDropTarget
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.platform.toClipEntry
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.requestFocus
-import androidx.core.view.inputmethod.EditorInfoCompat
-import androidx.core.view.inputmethod.InputConnectionCompat
-import androidx.core.view.inputmethod.InputContentInfoCompat
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Tests InputConnection#commitContent calls from BasicTextField2 to receiveContent modifier.
- */
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalFoundationApi::class)
-class TextFieldReceiveContentTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val inputMethodInterceptor = InputMethodInterceptor(rule)
-
-    private val tag = "BasicTextField2"
-
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun commitContentReturnsFalse_whenNoReceiveContentConfigured() {
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withInputConnection {
-            assertFalse(
-                commitContent(
-                    createInputContentInfo().unwrap() as InputContentInfo,
-                    0,
-                    null
-                )
-            )
-        }
-    }
-
-    @SdkSuppress(maxSdkVersion = 24)
-    @Test
-    fun preformPrivateCommandReturnsFalse_whenNoReceiveContentConfigured() {
-        inputMethodInterceptor.setContent {
-            BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
-            // Although we are testing `performPrivateCommand` that should return true by default
-            // in the existence of no configuration, semantically the caller is still calling
-            // commitContent which should return false by default.
-            assertFalse(
-                InputConnectionCompat.commitContent(
-                    inputConnection,
-                    editorInfo,
-                    InputContentInfoCompat(DEFAULT_CONTENT_URI, DEFAULT_CLIP_DESCRIPTION, null),
-                    0,
-                    null
-                )
-            )
-        }
-    }
-
-    @Test
-    fun singleReceiveContent_configuresEditorInfo() {
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(setOf(MediaType.Image)) { null }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(arrayOf(MediaType.Image.representation))
-        }
-    }
-
-    @Test
-    fun singleReceiveContent_duplicateMediaTypes_appliedUniquely() {
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(
-                        setOf(
-                            MediaType.Image,
-                            MediaType.PlainText,
-                            MediaType.Image,
-                            MediaType.HtmlText
-                        )
-                    ) { null }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.PlainText.representation,
-                    MediaType.HtmlText.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { null }) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes_uniquely() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier.receiveContent(
-                setOf(MediaType.Text, MediaType.Image)
-            ) { null }) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes_includingAnotherTraversableNode() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Text)) { null }
-                .dragAndDropTarget({ true }, object : DragAndDropTarget {
-                    override fun onDrop(event: DragAndDropEvent): Boolean {
-                        return false
-                    }
-                })
-            ) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun singleReceiveContent_isCalledAfterCommitContent() {
-        var transferableContent: TransferableContent? = null
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
-                        transferableContent = it
-                        null
-                    }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-
-        val linkUri = Uri.parse("https://example.com")
-        val bundle = Bundle().apply { putString("key", "value") }
-        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
-            InputConnectionCompat.commitContent(
-                inputConnection,
-                editorInfo,
-                createInputContentInfo(linkUri = linkUri),
-                0,
-                bundle
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(transferableContent).isNotNull()
-            assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
-            assertThat(transferableContent?.clipMetadata?.clipDescription)
-                .isEqualTo(DEFAULT_CLIP_DESCRIPTION)
-
-            assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
-            assertThat(transferableContent?.clipEntry?.clipData?.getItemAt(0)?.uri)
-                .isEqualTo(DEFAULT_CONTENT_URI)
-
-            assertThat(transferableContent?.platformTransferableContent?.linkUri)
-                .isEqualTo(linkUri)
-            assertThat(transferableContent?.platformTransferableContent?.extras)
-                .isEqualTo(bundle)
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 25) // Permissions are acquired only on SDK levels 25 or higher.
-    @Test
-    fun singleReceiveContent_permissionIsRequested() {
-        var transferableContent: TransferableContent? = null
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
-                        transferableContent = it
-                        null
-                    }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-
-        val inputContentInfo: InputContentInfoCompat = createInputContentInfo()
-
-        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
-            InputConnectionCompat.commitContent(
-                inputConnection,
-                editorInfo,
-                inputContentInfo,
-                InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
-                null
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(transferableContent).isNotNull()
-            assertTrue(
-                transferableContent?.platformTransferableContent
-                    ?.extras
-                    ?.containsKey("EXTRA_INPUT_CONTENT_INFO") ?: false
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_delegatesRemainingItems_toParent() {
-        var childTransferableContent: TransferableContent? = null
-        var parentTransferableContent: TransferableContent? = null
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
-                        parentTransferableContent = it
-                        null
-                    }
-                    .receiveContent(setOf(MediaType.All)) {
-                        childTransferableContent = it
-                        it
-                    }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
-            InputConnectionCompat.commitContent(
-                inputConnection,
-                editorInfo,
-                createInputContentInfo(),
-                0,
-                null
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(childTransferableContent).isNotNull()
-            assertThat(childTransferableContent).isSameInstanceAs(parentTransferableContent)
-
-            assertThat(parentTransferableContent?.source)
-                .isEqualTo(TransferableContent.Source.Keyboard)
-            assertThat(parentTransferableContent?.clipMetadata?.clipDescription)
-                .isEqualTo(DEFAULT_CLIP_DESCRIPTION)
-
-            assertThat(parentTransferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
-            assertThat(parentTransferableContent?.clipEntry?.clipData?.getItemAt(0)?.uri)
-                .isEqualTo(DEFAULT_CONTENT_URI)
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_doesNotCallParent_ifAllItemsAreProcessed() {
-        var childTransferableContent: TransferableContent? = null
-        var parentTransferableContent: TransferableContent? = null
-        inputMethodInterceptor.setContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
-                        parentTransferableContent = it
-                        null
-                    }
-                    .receiveContent(setOf(MediaType.All)) {
-                        childTransferableContent = it
-                        null
-                    }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
-            InputConnectionCompat.commitContent(
-                inputConnection,
-                editorInfo,
-                createInputContentInfo(),
-                0,
-                null
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(childTransferableContent).isNotNull()
-            assertThat(parentTransferableContent).isNull()
-        }
-    }
-
-    @Test
-    fun semanticsPasteContent_delegatesToReceiveContent() {
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
-        val clipEntry = createClipData().toClipEntry()
-        clipboardManager.setClip(clipEntry)
-        lateinit var transferableContent: TransferableContent
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) {
-                            transferableContent = it
-                            null
-                        }
-                )
-            }
-        }
-
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertClipData(transferableContent.clipEntry.clipData)
-                .isEqualToClipData(clipEntry.clipData)
-        }
-    }
-
-    @Test
-    fun semanticsPasteContent_pastesLeftOverText() {
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
-        val clipEntry = createClipData {
-            addText("some text")
-            addUri()
-            addIntent()
-            addText("more text")
-        }.toClipEntry()
-        clipboardManager.setClip(clipEntry)
-        val state = TextFieldState()
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image, MediaType.Text)) {
-                            it.consumeEach { item ->
-                                // only consume if there's no text
-                                item.text == null
-                            }
-                        }
-                )
-            }
-        }
-
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("some text\nmore text")
-        }
-    }
-
-    @Test
-    fun semanticsPasteContent_goesFromChildToParent() {
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
-        val clipEntry = createClipData {
-            addText("a")
-            addText("b")
-            addText("c")
-            addText("d")
-        }.toClipEntry()
-        clipboardManager.setClip(clipEntry)
-
-        lateinit var transferableContent1: TransferableContent
-        lateinit var transferableContent2: TransferableContent
-        lateinit var transferableContent3: TransferableContent
-        val state = TextFieldState()
-
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(
-                    state = state,
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Text)) {
-                            transferableContent1 = it
-                            it.consumeEach {
-                                it.text.contains("a")
-                            }
-                        }
-                        .receiveContent(setOf(MediaType.Text)) {
-                            transferableContent2 = it
-                            it.consumeEach {
-                                it.text.contains("b")
-                            }
-                        }
-                        .receiveContent(setOf(MediaType.Text)) {
-                            transferableContent3 = it
-                            it.consumeEach {
-                                it.text.contains("c")
-                            }
-                        }
-                )
-            }
-        }
-
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("d")
-            assertThat(transferableContent3.clipEntry.clipData.itemCount).isEqualTo(4)
-            assertThat(transferableContent2.clipEntry.clipData.itemCount).isEqualTo(3)
-            assertThat(transferableContent1.clipEntry.clipData.itemCount).isEqualTo(2)
-        }
-    }
-
-    @Test
-    fun toolbarPasteContent_delegatesToReceiveContent() {
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true)
-        val clipEntry = createClipData().toClipEntry()
-        clipboardManager.setClip(clipEntry)
-        var pasteOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOption = onPasteRequested
-            },
-            onHideMenu = {}
-        )
-        lateinit var transferableContent: TransferableContent
-        rule.setContent {
-            CompositionLocalProvider(
-                LocalClipboardManager provides clipboardManager,
-                LocalTextToolbar provides textToolbar
-            ) {
-                BasicTextField2(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) {
-                            transferableContent = it
-                            null
-                        }
-                )
-            }
-        }
-
-        rule.runOnIdle {
-            pasteOption?.invoke()
-        }
-
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.PasteText)
-
-        rule.runOnIdle {
-            assertClipData(transferableContent.clipEntry.clipData)
-                .isEqualToClipData(clipEntry.clipData)
-        }
-    }
-
-    companion object {
-        private val DEFAULT_CONTENT_URI = Uri.parse("content://com.example.app/content")
-        private val DEFAULT_CLIP_DESCRIPTION = ClipDescription("image", arrayOf("image/jpeg"))
-
-        private fun createInputContentInfo(
-            contentUri: Uri = DEFAULT_CONTENT_URI,
-            clipDescription: ClipDescription = DEFAULT_CLIP_DESCRIPTION,
-            linkUri: Uri? = null
-        ) = InputContentInfoCompat(contentUri, clipDescription, linkUri)
-
-        private fun InputMethodInterceptor.onIdle(block: (EditorInfo, InputConnection) -> Unit) {
-            withInputConnection {
-                withEditorInfo {
-                    block(this@withEditorInfo, this@withInputConnection)
-                }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldScrollTest.kt
deleted file mode 100644
index 2c2b710..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldScrollTest.kt
+++ /dev/null
@@ -1,820 +0,0 @@
-/*
- * Copyright 2020 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.compose.foundation.text2.input
-
-import android.os.Build
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.assertPixels
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertIsNotFocused
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.swipe
-import androidx.compose.ui.test.swipeDown
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalFoundationApi::class)
-class TextFieldScrollTest : FocusedWindowTest {
-
-    private val TextfieldTag = "textField"
-
-    private val longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
-        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
-        " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
-        "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-        "fugiat nulla pariatur."
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var testScope: CoroutineScope
-
-    @Before
-    fun before() {
-        isDebugInspectorInfoEnabled = true
-    }
-
-    @After
-    fun after() {
-        isDebugInspectorInfoEnabled = false
-    }
-
-    @Test
-    fun textFieldScroll_horizontal_scrollable_withLongInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupHorizontallyScrollableContent(
-            TextFieldState(longText), scrollState, Modifier.size(width = 300.dp, height = 50.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
-            assertThat(scrollState.maxValue).isGreaterThan(0)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_vertical_scrollable_withLongInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupVerticallyScrollableContent(
-            state = TextFieldState(longText),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 50.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
-            assertThat(scrollState.maxValue).isGreaterThan(0)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_vertical_scrollable_withLongInput_whenMaxLinesProvided() {
-        val scrollState = ScrollState(0)
-
-        rule.setupVerticallyScrollableContent(
-            state = TextFieldState(longText),
-            modifier = Modifier.width(100.dp),
-            scrollState = scrollState,
-            maxLines = 3
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.maxValue).isLessThan(Int.MAX_VALUE)
-            assertThat(scrollState.maxValue).isGreaterThan(0)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_horizontal_notScrollable_withShortInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupHorizontallyScrollableContent(
-            state = TextFieldState("text"),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 50.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.maxValue).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_vertical_notScrollable_withShortInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupVerticallyScrollableContent(
-            state = TextFieldState("text"),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 100.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.maxValue).isEqualTo(0)
-        }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_singleLine_scrolledAndClipped() {
-        val parentSize = 200
-        val textFieldSize = 50
-        val tag = "OuterBox"
-
-        with(rule.density) {
-            rule.setContent {
-                Box(
-                    Modifier
-                        .size(parentSize.toDp())
-                        .background(color = Color.White)
-                        .testTag(tag)
-                ) {
-                    ScrollableContent(
-                        state = TextFieldState(longText),
-                        modifier = Modifier.size(textFieldSize.toDp()),
-                        scrollState = rememberScrollState(),
-                        lineLimits = SingleLine
-                    )
-                }
-            }
-        }
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(tag)
-            .captureToImage()
-            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
-                if (position.x > textFieldSize || position.y > textFieldSize) Color.White else null
-            }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun textField_multiline_scrolledAndClipped() {
-        val parentSize = 200
-        val textFieldSize = 50
-        val tag = "OuterBox"
-
-        with(rule.density) {
-            rule.setContent {
-                Box(
-                    Modifier
-                        .size(parentSize.toDp())
-                        .background(color = Color.White)
-                        .testTag(tag)
-                ) {
-                    ScrollableContent(
-                        state = TextFieldState(longText),
-                        modifier = Modifier.size(textFieldSize.toDp()),
-                        scrollState = rememberScrollState(),
-                        lineLimits = MultiLine()
-                    )
-                }
-            }
-        }
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag(tag)
-            .captureToImage()
-            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
-                if (position.x > textFieldSize || position.y > textFieldSize) Color.White else null
-            }
-    }
-
-    @Test
-    fun textFieldScroll_horizontal_swipe_whenLongInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupHorizontallyScrollableContent(
-            state = TextFieldState(longText),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 50.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag(TextfieldTag)
-            .performTouchInput { swipeLeft() }
-
-        val firstSwipePosition = rule.runOnIdle {
-            scrollState.value
-        }
-        assertThat(firstSwipePosition).isGreaterThan(0)
-
-        rule.onNodeWithTag(TextfieldTag)
-            .performTouchInput { swipeRight() }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isLessThan(firstSwipePosition)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_vertical_swipe_whenLongInput() {
-        val scrollState = ScrollState(0)
-
-        rule.setupVerticallyScrollableContent(
-            state = TextFieldState(longText),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 50.dp)
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag(TextfieldTag)
-            .performTouchInput { swipeUp() }
-
-        val firstSwipePosition = rule.runOnIdle {
-            scrollState.value
-        }
-        assertThat(firstSwipePosition).isGreaterThan(0)
-
-        rule.onNodeWithTag(TextfieldTag)
-            .performTouchInput { swipeDown() }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isLessThan(firstSwipePosition)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_restoresScrollerPosition() {
-        val restorationTester = StateRestorationTester(rule)
-        var scrollState: ScrollState? = null
-
-        restorationTester.setContent {
-            scrollState = rememberScrollState()
-            ScrollableContent(
-                state = TextFieldState(longText),
-                modifier = Modifier.size(width = 300.dp, height = 50.dp),
-                scrollState = scrollState!!,
-                lineLimits = SingleLine
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag)
-            .performTouchInput { swipeLeft() }
-
-        val swipePosition = rule.runOnIdle { scrollState!!.value }
-        assertThat(swipePosition).isGreaterThan(0)
-
-        rule.runOnIdle {
-            scrollState = ScrollState(0)
-            assertThat(scrollState!!.value).isEqualTo(0)
-        }
-
-        restorationTester.emulateSavedInstanceStateRestore()
-
-        rule.runOnIdle {
-            assertThat(scrollState!!.value).isEqualTo(swipePosition)
-        }
-    }
-
-    @Test
-    fun textFieldScrollStateChange_shouldResetTheScroll() {
-        val scrollState1 = ScrollState(0)
-        val scrollState2 = ScrollState(0)
-
-        var stateToggle by mutableStateOf(true)
-
-        rule.setContent {
-            ScrollableContent(
-                state = TextFieldState(longText),
-                scrollState = if (stateToggle) scrollState1 else scrollState2,
-                modifier = Modifier.size(width = 300.dp, height = 50.dp),
-                lineLimits = SingleLine
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(scrollState1.maxValue).isLessThan(Int.MAX_VALUE)
-            assertThat(scrollState1.maxValue).isGreaterThan(0)
-
-            assertThat(scrollState2.maxValue).isEqualTo(Int.MAX_VALUE) // when it's not set
-            assertThat(scrollState2.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeLeft() }
-
-        rule.runOnIdle {
-            assertThat(scrollState1.value).isGreaterThan(0)
-        }
-
-        stateToggle = false
-
-        rule.runOnIdle {
-            assertThat(scrollState2.maxValue).isLessThan(Int.MAX_VALUE)
-            assertThat(scrollState2.maxValue).isGreaterThan(0)
-
-            assertThat(scrollState2.value).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textFieldDoesNotFollowCursor_whenNotFocused() {
-        val state = TextFieldState(longText)
-        val scrollState = ScrollState(0)
-        rule.setContent {
-            ScrollableContent(
-                state = state,
-                scrollState = scrollState,
-                modifier = Modifier.size(width = 300.dp, height = 50.dp),
-                lineLimits = SingleLine
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag).assertIsNotFocused()
-
-        // move cursor to the end
-        state.edit {
-            placeCursorAtEnd()
-        }
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun textFieldFollowsCursor_whenFocused() {
-        val state = TextFieldState(longText, TextRange(0))
-        val scrollState = ScrollState(0)
-        rule.setTextFieldTestContent {
-            ScrollableContent(
-                state = state,
-                scrollState = scrollState,
-                modifier = Modifier.size(width = 300.dp, height = 50.dp),
-                lineLimits = SingleLine
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag).requestFocus()
-
-        rule.runOnIdle {
-            // move cursor to the end
-            state.edit { placeCursorAtEnd() }
-        }
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-        }
-    }
-
-    @Test
-    fun textFieldDoesNotFollowCursor_whenScrollStateChanges_butCursorRemainsTheSame() {
-        val state = TextFieldState(longText, initialSelectionInChars = TextRange(5))
-        val scrollState = ScrollState(0)
-        rule.setContent {
-            ScrollableContent(
-                state = state,
-                scrollState = scrollState,
-                modifier = Modifier.size(width = 300.dp, height = 50.dp),
-                lineLimits = SingleLine
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag).requestFocus()
-        rule.waitForIdle()
-
-        runBlockingAndAwaitIdle { scrollState.scrollTo(scrollState.maxValue) }
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-        }
-    }
-
-    @Test
-    fun textFieldRtl_horizontalScroll_isReversed() {
-        val scrollState = ScrollState(0)
-
-        rule.setupHorizontallyScrollableContent(
-            state = TextFieldState(longText),
-            scrollState = scrollState,
-            modifier = Modifier.size(width = 300.dp, height = 50.dp),
-            isRtl = true
-        )
-
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeLeft() }
-
-        // swiping left at initial position should be no-op in RTL layout
-        val firstSwipePosition = rule.runOnIdle { scrollState.value }
-        assertThat(firstSwipePosition).isEqualTo(0)
-
-        rule.onNodeWithTag(TextfieldTag).performTouchInput { swipeRight() }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isGreaterThan(firstSwipePosition)
-        }
-    }
-
-    @Test
-    fun textFieldScroll_testNestedScrolling() = runBlocking {
-        val size = 300.dp
-        val text = """
-            First Line
-            Second Line
-            Third Line
-            Fourth Line
-        """.trimIndent()
-
-        val textFieldScrollState = ScrollState(0)
-        val columnScrollState = ScrollState(0)
-        var touchSlop = 0f
-        val height = 60.dp
-
-        rule.setContent {
-            touchSlop = LocalViewConfiguration.current.touchSlop
-            Column(
-                Modifier
-                    .size(size)
-                    .verticalScroll(columnScrollState)
-            ) {
-                ScrollableContent(
-                    state = TextFieldState(text),
-                    modifier = Modifier.size(size, height),
-                    scrollState = textFieldScrollState,
-                    lineLimits = MultiLine()
-                )
-                Box(Modifier.size(size))
-                Box(Modifier.size(size))
-            }
-        }
-
-        assertThat(textFieldScrollState.value).isEqualTo(0)
-        assertThat(textFieldScrollState.maxValue).isGreaterThan(0)
-        assertThat(columnScrollState.value).isEqualTo(0)
-
-        with(rule.density) {
-            val x = 10.dp.toPx()
-            val desiredY = textFieldScrollState.maxValue + 10.dp.roundToPx()
-            val nearEdge = (height - 1.dp)
-            // not to exceed size
-            val slopStartY = minOf(desiredY + touchSlop, nearEdge.toPx())
-            val slopStart = Offset(x, slopStartY)
-            val end = Offset(x, 0f)
-            rule.onNodeWithTag(TextfieldTag)
-                .performTouchInput {
-                    swipe(slopStart, end)
-                }
-        }
-
-        assertThat(textFieldScrollState.value).isGreaterThan(0)
-        assertThat(textFieldScrollState.value).isEqualTo(textFieldScrollState.maxValue)
-        assertThat(columnScrollState.value).isGreaterThan(0)
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inHorizontallyScrollableField_whenAtStart() {
-        val state = TextFieldState("baaaaaaaaaa")
-        val scrollState = ScrollState(Int.MAX_VALUE)
-        lateinit var coroutineScope: CoroutineScope
-        rule.setContent {
-            coroutineScope = rememberCoroutineScope()
-            BasicTextField2(
-                state,
-                scrollState = scrollState,
-                lineLimits = SingleLine,
-                modifier = Modifier
-                    // Force the field to be scrollable.
-                    // Must be at least as wide as the cursor rectangle for the assertions to work.
-                    .requiredWidth(10.dp)
-                    .testTag("field")
-            )
-        }
-        rule.onNodeWithTag("field").requestFocus()
-        rule.runOnIdle {
-            // Start the cursor at index 1 then backspace to move it to zero. This makes the
-            // assertion easier to write since we don't have to know the width of the glyph to
-            // calculate the expected scroll offset. We have to do this after requesting focus since
-            // the cursor will move change when focus is gained.
-            state.edit {
-                placeCursorBeforeCharAt(1)
-            }
-        }
-        rule.runOnIdle {
-            coroutineScope.launch {
-                scrollState.scrollTo(scrollState.maxValue)
-            }
-        }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-        }
-        rule.onNodeWithTag("field").assertTextEquals("baaaaaaaaaa")
-
-        rule.onNodeWithTag("field").performKeyInput {
-            pressKey(Key.Backspace)
-        }
-
-        rule.onNodeWithTag("field").assertTextEquals("aaaaaaaaaa")
-        rule.waitUntil(
-            "scrollState.value (${scrollState.value}) == 0 && " +
-                "state.text.selectionInChars (${state.text.selectionInChars}) == TextRange(0)"
-        ) {
-            scrollState.value == 0 && state.text.selectionInChars == TextRange(0)
-        }
-    }
-
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inHorizontallyScrollableField_whenAtEnd() {
-        val state = TextFieldState("aaaaaaaaaa")
-        val scrollState = ScrollState(0)
-        rule.setContent {
-            BasicTextField2(
-                state,
-                scrollState = scrollState,
-                lineLimits = SingleLine,
-                modifier = Modifier
-                    // Force the field to be scrollable.
-                    // Must be at least as wide as the cursor rectangle for the assertions to work.
-                    .requiredWidth(10.dp)
-                    .testTag("field")
-            )
-        }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag("field").performTextInput("b")
-
-        rule.waitUntil(
-            "scrollState.value (${scrollState.value}) == " +
-                "scrollState.maxValue (${scrollState.maxValue})"
-        ) {
-            scrollState.value == scrollState.maxValue
-        }
-    }
-
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenAtTop() {
-        val state = TextFieldState("a\na\na\na\n", initialSelectionInChars = TextRange(0))
-        val scrollState = ScrollState(Int.MAX_VALUE)
-        rule.setContent {
-            BasicTextField2(
-                state,
-                scrollState = scrollState,
-                lineLimits = MultiLine(maxHeightInLines = 1),
-                modifier = Modifier.testTag("field")
-            )
-        }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-        }
-
-        rule.onNodeWithTag("field").performTextInput("b")
-
-        rule.waitUntil("scrollState.value (${scrollState.value}) == 0") {
-            scrollState.value == 0
-        }
-    }
-
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenAtBottom() {
-        val state = TextFieldState("a\na\na\na\n")
-        val scrollState = ScrollState(0)
-        rule.setContent {
-            BasicTextField2(
-                state,
-                scrollState = scrollState,
-                lineLimits = MultiLine(maxHeightInLines = 1),
-                modifier = Modifier.testTag("field")
-            )
-        }
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        rule.onNodeWithTag("field").performTextInput("b")
-
-        rule.waitUntil(
-            "scrollState.value (${scrollState.value}) == " +
-                "scrollState.maxValue (${scrollState.maxValue})"
-        ) {
-            scrollState.value == scrollState.maxValue
-        }
-    }
-
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableField_whenMovesBelowViewport() {
-        val state = TextFieldState("a\na\na\na\n")
-        val scrollState = ScrollState(Int.MAX_VALUE)
-        rule.setContent {
-            BasicTextField2(
-                state,
-                scrollState = scrollState,
-                lineLimits = MultiLine(maxHeightInLines = 1),
-                modifier = Modifier.testTag("field")
-            )
-        }
-        rule.onNodeWithTag("field").requestFocus()
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-        }
-
-        // At this point the field is scrolled all the way to the bottom, but then we enter a
-        // newline, which will push the cursor below the bottom of the field. It should scroll up
-        // to stay in view.
-        rule.onNodeWithTag("field").performTextInput("\n")
-
-        rule.waitUntil(
-            "scrollState.value (${scrollState.value}) == " +
-                "scrollState.maxValue (${scrollState.maxValue})"
-        ) {
-            scrollState.value == scrollState.maxValue
-        }
-    }
-
-    @Test
-    fun cursorScrolledIntoViewWhenTyping_inVerticallyScrollableContainer_whenFieldExpands() {
-        // Start as a single line, then enter '\n' to grow to 2 lines.
-        val state = TextFieldState("a")
-        val scrollState = ScrollState(0)
-        var containerHeight by mutableStateOf(0.dp)
-        rule.setContent {
-            Box(
-                Modifier
-                    .requiredHeight(containerHeight)
-                    .fillMaxWidth()
-                    .border(1.dp, Color.Red)
-                    .verticalScroll(scrollState)
-            ) {
-                BasicTextField2(
-                    state,
-                    // The field should never scroll internally.
-                    lineLimits = MultiLine(maxHeightInLines = Int.MAX_VALUE),
-                    modifier = Modifier
-                        .testTag("field")
-                        .border(1.dp, Color.Blue)
-                )
-            }
-        }
-        rule.onNodeWithTag("field").requestFocus()
-        rule.runOnIdle {
-            assertThat(scrollState.value).isEqualTo(0)
-        }
-
-        // Make the container height equal to the size of the single-line text field.
-        with(rule.density) {
-            containerHeight = rule.onNodeWithTag("field").fetchSemanticsNode().size.height.toDp()
-        }
-
-        // Enter a newline, which will move the cursor to line 2 and grow the field to be 2 lines
-        // tall. The second line will initially be hidden by the container, but should be scrolled
-        // back into view.
-        rule.onNodeWithTag("field").performTextInput("\n")
-
-        rule.waitUntil(
-            "maxValue (${scrollState.maxValue} > 0 && " +
-                "scrollState.value (${scrollState.value}) == maxValue",
-            timeoutMillis = 10_000
-        ) {
-            val maxValue = scrollState.maxValue
-            maxValue > 0 && scrollState.value == maxValue
-        }
-    }
-
-    private fun ComposeContentTestRule.setupHorizontallyScrollableContent(
-        state: TextFieldState,
-        scrollState: ScrollState,
-        modifier: Modifier = Modifier,
-        isRtl: Boolean = false
-    ) {
-        setContent {
-            val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
-            CompositionLocalProvider(LocalLayoutDirection provides direction) {
-                ScrollableContent(
-                    state = state,
-                    scrollState = scrollState,
-                    modifier = modifier,
-                    lineLimits = SingleLine
-                )
-            }
-        }
-    }
-
-    private fun ComposeContentTestRule.setupVerticallyScrollableContent(
-        state: TextFieldState,
-        scrollState: ScrollState,
-        modifier: Modifier = Modifier,
-        maxLines: Int = Int.MAX_VALUE,
-        isRtl: Boolean = false
-    ) {
-        setContent {
-            val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
-            CompositionLocalProvider(LocalLayoutDirection provides direction) {
-                ScrollableContent(
-                    state = state,
-                    scrollState = scrollState,
-                    modifier = modifier,
-                    lineLimits = MultiLine(maxHeightInLines = maxLines)
-                )
-            }
-        }
-    }
-
-    @Composable
-    private fun ScrollableContent(
-        modifier: Modifier,
-        state: TextFieldState,
-        scrollState: ScrollState,
-        lineLimits: TextFieldLineLimits
-    ) {
-        testScope = rememberCoroutineScope()
-        BasicTextField2(
-            state = state,
-            scrollState = scrollState,
-            lineLimits = lineLimits,
-            modifier = modifier.testTag(TextfieldTag)
-        )
-    }
-
-    private fun runBlockingAndAwaitIdle(block: suspend CoroutineScope.() -> Unit) {
-        val job = testScope.launch(block = block)
-        rule.waitForIdle()
-        runBlocking {
-            job.join()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldSingleLineHeightTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldSingleLineHeightTest.kt
deleted file mode 100644
index 616621c..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/TextFieldSingleLineHeightTest.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.unit.IntSize
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldSingleLineHeightTest : FocusedWindowTest {
-
-    private val TextfieldTag = "textField"
-
-    private val defaultText = "TEXT"
-
-    // Arabic and Thai characters combined for super tall script
-    private val tallText = "\u0627\u0644\u0646\u0635\u0E17\u0E35\u0E48"
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun singleLineTextField_fromEmptyToTallText_updatesHeight() {
-        val state = TextFieldState("")
-        var reportedSize: IntSize = IntSize.Zero
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier.onSizeChanged {
-                    reportedSize = it
-                }
-            )
-        }
-
-        rule.waitForIdle()
-        val emptyHeight = reportedSize.height
-
-        state.setTextAndPlaceCursorAtEnd(tallText)
-
-        rule.waitForIdle()
-        val tallHeight = reportedSize.height
-
-        assertThat(emptyHeight).isLessThan(tallHeight)
-    }
-
-    @Test
-    fun singleLineTextField_fromLatinToTallText_updatesHeight() {
-        val state = TextFieldState(defaultText)
-        var reportedSize: IntSize = IntSize.Zero
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier.onSizeChanged {
-                    reportedSize = it
-                }
-            )
-        }
-
-        rule.waitForIdle()
-        val latinHeight = reportedSize.height
-
-        state.setTextAndPlaceCursorAtEnd(tallText)
-
-        rule.waitForIdle()
-        val tallHeight = reportedSize.height
-
-        assertThat(latinHeight).isLessThan(tallHeight)
-    }
-
-    @Test
-    fun singleLineTextField_withTallText_showsCursorHandle_whenClicked() {
-        val state = TextFieldState(tallText)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier.testTag(TextfieldTag)
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag).performClick()
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-    }
-
-    @Test
-    fun multiLineTextField_withTallText_showsCursorHandle_whenClicked() {
-        val state = TextFieldState(tallText)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                lineLimits = TextFieldLineLimits.MultiLine(1, 1),
-                modifier = Modifier.testTag(TextfieldTag)
-            )
-        }
-
-        rule.onNodeWithTag(TextfieldTag).performClick()
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt
deleted file mode 100644
index 0560732..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.text.InputType
-import android.view.View
-import android.view.inputmethod.EditorInfo
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.PlatformTextInputModifierNode
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.platform.establishTextInputSession
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.ImeOptions
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFalse
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AndroidTextInputSessionTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var coroutineScope: CoroutineScope
-    private lateinit var hostView: View
-    private lateinit var textInputNode: PlatformTextInputModifierNode
-
-    @Before
-    fun setup() {
-        rule.setContent {
-            coroutineScope = rememberCoroutineScope()
-            hostView = LocalView.current
-            Box(
-                modifier = Modifier
-                    .size(1.dp)
-                    .testTag("tag")
-                    .then(TestTextElement())
-                    .focusable()
-            )
-        }
-        rule.onNodeWithTag("tag").requestFocus()
-        rule.waitForIdle()
-    }
-
-    @Test
-    fun createInputConnection_modifiesEditorInfo() {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0, 5))
-        launchInputSessionWithDefaultsForTest(state)
-        val editorInfo = EditorInfo()
-
-        rule.runOnUiThread {
-            hostView.onCreateInputConnection(editorInfo)
-        }
-
-        assertThat(editorInfo.initialSelStart).isEqualTo(0)
-        assertThat(editorInfo.initialSelEnd).isEqualTo(5)
-        assertThat(editorInfo.inputType).isEqualTo(
-            InputType.TYPE_CLASS_TEXT or
-                InputType.TYPE_TEXT_FLAG_MULTI_LINE or
-                InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
-        )
-        assertThat(editorInfo.imeOptions).isEqualTo(
-            EditorInfo.IME_FLAG_NO_FULLSCREEN or
-                EditorInfo.IME_FLAG_NO_ENTER_ACTION
-        )
-    }
-
-    @Test
-    fun inputConnection_sendsUpdates_toActiveSession() {
-        val state1 = TextFieldState()
-        val state2 = TextFieldState()
-        launchInputSessionWithDefaultsForTest(state1)
-
-        rule.runOnIdle {
-            hostView.onCreateInputConnection(EditorInfo())
-                .commitText("hello", 1)
-
-            assertThat(state1.text.toString()).isEqualTo("hello")
-            assertThat(state2.text.toString()).isEqualTo("")
-        }
-
-        launchInputSessionWithDefaultsForTest(state2)
-
-        rule.runOnIdle {
-            hostView.onCreateInputConnection(EditorInfo())
-                .commitText("world", 1)
-
-            assertThat(state1.text.toString()).isEqualTo("hello")
-            assertThat(state2.text.toString()).isEqualTo("world")
-        }
-    }
-
-    @Test
-    fun inputConnection_sendsEditorAction_toActiveSession() {
-        var imeActionFromOne: ImeAction? = null
-        var imeActionFromTwo: ImeAction? = null
-
-        launchInputSessionWithDefaultsForTest(
-            imeOptions = ImeOptions(imeAction = ImeAction.Done),
-            onImeAction = { imeActionFromOne = it }
-        )
-
-        rule.runOnIdle {
-            hostView.onCreateInputConnection(EditorInfo())
-                .performEditorAction(EditorInfo.IME_ACTION_DONE)
-
-            assertThat(imeActionFromOne).isEqualTo(ImeAction.Done)
-            assertThat(imeActionFromTwo).isNull()
-        }
-
-        launchInputSessionWithDefaultsForTest(
-            imeOptions = ImeOptions(imeAction = ImeAction.Go),
-            onImeAction = { imeActionFromTwo = it }
-        )
-
-        rule.runOnIdle {
-            hostView.onCreateInputConnection(EditorInfo())
-                .performEditorAction(EditorInfo.IME_ACTION_GO)
-
-            assertThat(imeActionFromOne).isEqualTo(ImeAction.Done)
-            assertThat(imeActionFromTwo).isEqualTo(ImeAction.Go)
-        }
-    }
-
-    @Test
-    fun createInputConnection_updatesEditorInfo() {
-        launchInputSessionWithDefaultsForTest(
-            imeOptions = ImeOptions(
-                singleLine = true,
-                keyboardType = KeyboardType.Email,
-                autoCorrect = false,
-                imeAction = ImeAction.Search,
-                capitalization = KeyboardCapitalization.Words
-            )
-        )
-        val editorInfo = EditorInfo()
-
-        rule.runOnIdle {
-            hostView.onCreateInputConnection(editorInfo)
-        }
-
-        assertThat(editorInfo.inputType).isEqualTo(
-            InputType.TYPE_CLASS_TEXT or
-                InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or
-                InputType.TYPE_TEXT_FLAG_CAP_WORDS
-        )
-        assertThat(editorInfo.imeOptions).isEqualTo(
-            EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN
-        )
-    }
-
-    @Test
-    fun debugMode_isDisabled() {
-        // run this in presubmit to check that we are not accidentally enabling logs on prod
-        assertFalse(
-            TIA_DEBUG,
-            "Oops, looks like you accidentally enabled logging. Don't worry, we've all " +
-                "been there. Just remember to turn it off before you deploy your code."
-        )
-    }
-
-    private fun launchInputSessionWithDefaultsForTest(
-        state: TextFieldState = TextFieldState(),
-        imeOptions: ImeOptions = ImeOptions.Default,
-        onImeAction: (ImeAction) -> Unit = {}
-    ) {
-        coroutineScope.launch {
-            textInputNode.establishTextInputSession {
-                inputSessionWithDefaultsForTest(
-                    state,
-                    imeOptions,
-                    onImeAction
-                )
-            }
-        }
-    }
-
-    private suspend fun PlatformTextInputSession.inputSessionWithDefaultsForTest(
-        state: TextFieldState = TextFieldState(),
-        imeOptions: ImeOptions = ImeOptions.Default,
-        onImeAction: (ImeAction) -> Unit = {},
-        receiveContentConfiguration: ReceiveContentConfiguration? = null
-    ): Nothing = platformSpecificTextInputSession(
-        state = TransformedTextFieldState(
-            textFieldState = state,
-            inputTransformation = null,
-            codepointTransformation = null
-        ),
-        layoutState = TextLayoutState(),
-        imeOptions = imeOptions,
-        receiveContentConfiguration = receiveContentConfiguration,
-        onImeAction = onImeAction,
-    )
-
-    private inner class TestTextElement : ModifierNodeElement<TestTextNode>() {
-        override fun create(): TestTextNode = TestTextNode()
-        override fun update(node: TestTextNode) {}
-        override fun hashCode(): Int = 0
-        override fun equals(other: Any?): Boolean = other is TestTextElement
-    }
-
-    private inner class TestTextNode : Modifier.Node(), PlatformTextInputModifierNode {
-        override fun onAttach() {
-            textInputNode = this
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/BackspaceCommandTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/BackspaceCommandTest.kt
deleted file mode 100644
index 93c22ac..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/BackspaceCommandTest.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BackspaceCommandTest {
-
-    // Test sample surrogate pair characters.
-    private val SP1 = "\uD83D\uDE00" // U+1F600: GRINNING FACE
-    private val SP2 = "\uD83D\uDE01" // U+1F601: GRINNING FACE WITH SMILING EYES
-    private val SP3 = "\uD83D\uDE02" // U+1F602: FACE WITH TEARS OF JOY
-    private val SP4 = "\uD83D\uDE03" // U+1F603: SMILING FACE WITH OPEN MOUTH
-    private val SP5 = "\uD83D\uDE04" // U+1F604: SMILING FACE WITH OPEN MOUTH AND SMILING EYES
-
-    // Family ZWJ Emoji: U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
-    private val ZWJ_EMOJI = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
-
-    @Test
-    fun test_delete() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("BCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_from_offset0() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_with_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(2, 3))
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("ABDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_with_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-        eb.setComposition(2, 3)
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("ABDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_surrogate_pair() {
-        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("$SP2$SP3$SP4$SP5")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_with_selection_surrogate_pair() {
-        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(4, 6))
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("$SP1$SP2$SP4$SP5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_with_composition_surrogate_pair() {
-        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
-        eb.setComposition(4, 6)
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo("$SP1$SP2$SP4$SP5")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 26)
-    fun test_delete_with_composition_zwj_emoji() {
-        val eb = EditingBuffer(
-            "$ZWJ_EMOJI$ZWJ_EMOJI",
-            TextRange(ZWJ_EMOJI.length)
-        )
-
-        eb.backspace()
-
-        assertThat(eb.toString()).isEqualTo(ZWJ_EMOJI)
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManagerTest.kt
deleted file mode 100644
index 9cfa759..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManagerTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.content.Context
-import android.view.View
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputConnection
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ComposeInputMethodManagerTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun restartInput_startsNewInputConnection() {
-        var calledCreateInputConnection: EditorInfo? = null
-        var imm: ComposeInputMethodManager? = null
-        var view: View? = null
-        rule.setContent {
-            AndroidView(factory = { context ->
-                TestView(context) { editorInfo ->
-                    calledCreateInputConnection = editorInfo
-                    null
-                }.also {
-                    view = it
-                    imm = ComposeInputMethodManager(it)
-                }
-            })
-        }
-
-        rule.runOnUiThread {
-            view?.requestFocus()
-            imm?.restartInput()
-        }
-
-        rule.runOnIdle {
-            assertThat(calledCreateInputConnection).isNotNull()
-        }
-    }
-
-    @Test
-    fun everyRestartInput_createsNewInputConnection() {
-        var createInputConnectionCalled = 0
-        var imm: ComposeInputMethodManager? = null
-        var view: View? = null
-        rule.setContent {
-            AndroidView(factory = { context ->
-                TestView(context) {
-                    createInputConnectionCalled++
-                    null
-                }.also {
-                    view = it
-                    imm = ComposeInputMethodManager(it)
-                }
-            })
-        }
-
-        rule.runOnUiThread {
-            view?.requestFocus()
-            imm?.restartInput()
-        }
-
-        rule.waitUntil {
-            createInputConnectionCalled >= 1
-        }
-        rule.runOnIdle {
-            // when first time we start input, checkFocus in platform code causes
-            // onCreateInputConnection to be called twice. However, this is not guaranteed.
-            assertThat(createInputConnectionCalled).isAtLeast(1)
-        }
-
-        val previousCreateInputConnectionCalled = createInputConnectionCalled
-        rule.runOnUiThread {
-            imm?.restartInput()
-        }
-
-        rule.runOnIdle {
-            assertThat(createInputConnectionCalled)
-                .isEqualTo(previousCreateInputConnectionCalled + 1)
-        }
-    }
-}
-
-private class TestView(
-    context: Context,
-    val createInputConnection: (EditorInfo?) -> InputConnection? = { null }
-) : View(context) {
-
-    init {
-        isFocusable = true
-        isFocusableInTouchMode = true
-        isEnabled = true
-    }
-
-    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection? {
-        return createInputConnection(outAttrs)
-    }
-
-    override fun isInEditMode(): Boolean {
-        return true
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilderTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilderTest.kt
deleted file mode 100644
index 78ce6c3..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilderTest.kt
+++ /dev/null
@@ -1,758 +0,0 @@
-/*
- * Copyright 2022 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.compose.foundation.text2.input.internal
-
-import android.graphics.Matrix
-import android.graphics.RectF
-import android.os.Build
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
-import android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
-import android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.MultiParagraph
-import androidx.compose.ui.text.TextLayoutInput
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.createFontFamilyResolver
-import androidx.compose.ui.text.font.toFontFamily
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.fonts.R
-import com.google.common.truth.Truth.assertThat
-import kotlin.math.ceil
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CursorAnchorInfoBuilderTest {
-
-    private val fontFamilyMeasureFont =
-        Font(resId = R.font.sample_font, weight = FontWeight.Normal, style = FontStyle.Normal)
-            .toFontFamily()
-
-    private val context = InstrumentationRegistry.getInstrumentation().context
-    private val defaultDensity = Density(density = 1f)
-    private val matrix = Matrix()
-
-    @Test
-    fun testSelectionDefault() {
-        val text = ""
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(text, selection, composition, getTextLayoutResult(text), matrix)
-
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(0)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(0)
-    }
-
-    @Test
-    fun testSelectionCursor() {
-        val text = "abc"
-        val selection = TextRange(2)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(text, selection, composition, getTextLayoutResult(text), matrix)
-
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(2)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun testSelectionRange() {
-        val text = "abc"
-        val selection = TextRange(1, 2)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix
-                )
-
-        assertThat(cursorAnchorInfo.selectionStart).isEqualTo(1)
-        assertThat(cursorAnchorInfo.selectionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun testCompositionNone() {
-        val text = ""
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix
-                )
-
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
-        assertThat(cursorAnchorInfo.composingText).isNull()
-    }
-
-    @Test
-    fun testCompositionCoveringAllString() {
-        val text = "abc"
-        val selection = TextRange(0)
-        val composition = TextRange(0, text.length)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix
-                )
-
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(0)
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
-    }
-
-    @Test
-    fun testCompositionCoveringPortionOfString() {
-        val word1 = "123 "
-        val word2 = "456"
-        val word3 = " 789"
-        val text = word1 + word2 + word3
-        val selection = TextRange(0)
-        val composition = TextRange(word1.length, (word1 + word2).length)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix
-                )
-
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(word1.length)
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(word2)
-    }
-
-    @Test
-    fun testCompositionNotIncludedWhenIncludeCharacterBoundsFalse() {
-        val word1 = "123 "
-        val word2 = "456"
-        val word3 = " 789"
-        val text = word1 + word2 + word3
-        val selection = TextRange(0)
-        val composition = TextRange(word1.length, (word1 + word2).length)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix,
-                    includeCharacterBounds = false
-                )
-
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(-1)
-        assertThat(cursorAnchorInfo.composingText).isNull()
-    }
-
-    @Test
-    fun testResetsBetweenExecutions() {
-        val text = "abc"
-        val selection = TextRange(0)
-        val composition = TextRange(0, text.length)
-        val builder = CursorAnchorInfo.Builder()
-
-        val cursorAnchorInfo =
-            builder.build(
-                text,
-                selection,
-                composition,
-                getTextLayoutResult(text),
-                matrix
-            )
-
-        assertThat(cursorAnchorInfo.composingText.toString()).isEqualTo(text)
-        assertThat(cursorAnchorInfo.composingTextStart).isEqualTo(composition.min)
-
-        val cursorAnchorInfo1 =
-            builder.build(
-                "abcd",
-                selection = TextRange(0),
-                composition = null,
-                getTextLayoutResult(text),
-                matrix
-            )
-
-        assertThat(cursorAnchorInfo1.composingText).isNull()
-        assertThat(cursorAnchorInfo1.composingTextStart).isEqualTo(-1)
-    }
-
-    @Test
-    fun testInsertionMarkerCursor() {
-        val fontSize = 10.sp
-        val text = "abc"
-        val selection = TextRange(1)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
-
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
-    }
-
-    @Test
-    fun testInsertionMarkerSelectionIsSameWithCursor() {
-        val text = "abc"
-        val selection = TextRange(1, 2)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text)
-        val builder = CursorAnchorInfo.Builder()
-
-        val cursorAnchorInfo1 =
-            builder.build(text, selection, composition, textLayoutResult, matrix)
-
-        val cursorAnchorInfo2 =
-            builder.build(
-                text,
-                selection = TextRange(1),
-                composition,
-                textLayoutResult,
-                matrix
-            )
-
-        assertThat(cursorAnchorInfo1.insertionMarkerHorizontal)
-            .isEqualTo(cursorAnchorInfo2.insertionMarkerHorizontal)
-        assertThat(cursorAnchorInfo1.insertionMarkerTop)
-            .isEqualTo(cursorAnchorInfo2.insertionMarkerTop)
-        assertThat(cursorAnchorInfo1.insertionMarkerBottom)
-            .isEqualTo(cursorAnchorInfo2.insertionMarkerBottom)
-        assertThat(cursorAnchorInfo1.insertionMarkerBaseline)
-            .isEqualTo(cursorAnchorInfo2.insertionMarkerBaseline)
-        assertThat(cursorAnchorInfo1.insertionMarkerFlags)
-            .isEqualTo(cursorAnchorInfo2.insertionMarkerFlags)
-    }
-
-    @Test
-    fun testInsertionMarkerCursorClamped() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-
-        val text = "abc   "
-        val selection = TextRange(5)
-        val composition: TextRange? = null
-        val width = 4 * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
-
-        // The cursor position is clamped to the width of the layout.
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_VISIBLE_REGION)
-    }
-
-    @Test
-    fun testInsertionMarkerRtl() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-
-        val text = "\u05D0\u05D1\u05D2"
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-        val width = 3 * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
-
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(width)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags)
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
-    }
-
-    @Test
-    fun testInsertionMarkerNotVisible() {
-        val fontSize = 10.sp
-        val text = "abc"
-        val selection = TextRange(1)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        // insertionMarkerHorizontal = fontSizeInPx, so the insertion marker is completely outside
-        // this rectangle.
-        val innerTextFieldBounds =
-            Rect(0f, 0f, fontSizeInPx - 1f, textLayoutResult.size.height.toFloat())
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    innerTextFieldBounds = innerTextFieldBounds,
-                    decorationBoxBounds = innerTextFieldBounds
-                )
-
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
-    }
-
-    @Test
-    fun testInsertionMarkerPartiallyVisible() {
-        val fontSize = 10.sp
-        val text = "abc"
-        val selection = TextRange(1)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        // insertionMarkerTop = 0 and insertionMarkerBottom = fontSizeInPx, so this rectangle covers
-        // the top of the insertion marker but not the bottom.
-        val innerTextFieldBounds = Rect(fontSizeInPx - 1f, 0f, fontSizeInPx + 1f, fontSizeInPx - 1f)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    innerTextFieldBounds = innerTextFieldBounds,
-                    decorationBoxBounds = innerTextFieldBounds
-                )
-
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isEqualTo(0f)
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isEqualTo(fontSizeInPx)
-        assertThat(cursorAnchorInfo.insertionMarkerFlags)
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
-    }
-
-    @Test
-    fun testInsertionMarkerNotIncludedWhenIncludeInsertionMarkerFalse() {
-        val fontSize = 10.sp
-        val text = "abc"
-        val selection = TextRange(1)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    includeInsertionMarker = false
-                )
-
-        assertThat(cursorAnchorInfo.insertionMarkerHorizontal).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerTop).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerBottom).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerBaseline).isNaN()
-        assertThat(cursorAnchorInfo.insertionMarkerFlags).isEqualTo(0)
-    }
-
-    @Test
-    fun testCharacterBoundsLtr() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        val text = "a bc d"
-        val selection = TextRange(0)
-        // Composition is on "bc"
-        val composition = TextRange(2, 4)
-        val width = text.length * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
-
-        for (index in text.indices) {
-            if (index in composition) {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index))
-                    .isEqualTo(
-                        RectF(index * fontSizeInPx, 0f, (index + 1) * fontSizeInPx, fontSizeInPx)
-                    )
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
-                    .isEqualTo(FLAG_HAS_VISIBLE_REGION)
-            } else {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
-            }
-        }
-    }
-
-    @Test
-    fun testCharacterBoundsRtl() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        val text = "\u05D0 \u05D1\u05D2 \u05D3"
-        val selection = TextRange(0)
-        // Composition is on "\u05D1\u05D2"
-        val composition = TextRange(2, 4)
-        val width = text.length * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder().build(text, selection, composition, textLayoutResult, matrix)
-
-        for (index in text.indices) {
-            if (index in composition) {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index))
-                    .isEqualTo(
-                        RectF(
-                            width - ((index + 1) * fontSizeInPx),
-                            0f,
-                            width - (index * fontSizeInPx),
-                            fontSizeInPx
-                        )
-                    )
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index))
-                    .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_IS_RTL)
-            } else {
-                assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-                assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
-            }
-        }
-    }
-
-    @Test
-    fun testCharacterBoundsVisibility() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        val text = "a bc d"
-        val selection = TextRange(0)
-        // Composition is on "bc"
-        val composition = TextRange(2, 4)
-        val width = text.length * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-        val innerTextFieldBounds = Rect(3.5f * fontSizeInPx, 0f, 4f * fontSizeInPx, fontSizeInPx)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    innerTextFieldBounds = innerTextFieldBounds,
-                    decorationBoxBounds = innerTextFieldBounds
-                )
-
-        // Character at index 2 spans horizontal range [2 * fontSizeInPx, 3 * fontSizeInPx], so it
-        // is completely outside innerTextFieldBounds.
-        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2)).isEqualTo(FLAG_HAS_INVISIBLE_REGION)
-        // Character at index 3 spans horizontal range [3 * fontSizeInPx, 4 * fontSizeInPx], so it
-        // is partially inside innerTextFieldBounds.
-        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3))
-            .isEqualTo(FLAG_HAS_VISIBLE_REGION or FLAG_HAS_INVISIBLE_REGION)
-    }
-
-    @Test
-    fun testCharacterBoundsNotIncludedWhenIncludeCharacterBoundsFalse() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        val text = "a bc d"
-        val selection = TextRange(0)
-        // Composition is on "bc"
-        val composition = TextRange(2, 4)
-        val width = text.length * fontSizeInPx
-        val textLayoutResult =
-            getTextLayoutResult(text, fontSize = fontSize, width = width)
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    includeCharacterBounds = false
-                )
-
-        for (index in text.indices) {
-            assertThat(cursorAnchorInfo.getCharacterBounds(index)).isNull()
-            assertThat(cursorAnchorInfo.getCharacterBoundsFlags(index)).isEqualTo(0)
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    @Test
-    fun testEditorBounds() {
-        val text = ""
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix,
-                    innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
-                    decorationBoxBounds = Rect(5f, 6f, 7f, 8f)
-                )
-
-        assertThat(cursorAnchorInfo.editorBoundsInfo?.editorBounds).isEqualTo(RectF(5f, 6f, 7f, 8f))
-        assertThat(cursorAnchorInfo.editorBoundsInfo?.handwritingBounds)
-            .isEqualTo(RectF(5f, 6f, 7f, 8f))
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    @Test
-    fun testEditorBoundsNotIncludedWhenIncludeEditorBoundsFalse() {
-        val text = ""
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    getTextLayoutResult(text),
-                    matrix,
-                    innerTextFieldBounds = Rect(1f, 2f, 3f, 4f),
-                    decorationBoxBounds = Rect(5f, 6f, 7f, 8f),
-                    includeEditorBounds = false
-                )
-
-        assertThat(cursorAnchorInfo.editorBoundsInfo).isNull()
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Test
-    fun testLineBounds() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        // 6 lines of text
-        val text = "a\nbb\nccc\ndddd\neeeee\nffffff"
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-        // Lines 2, 3, 4 are visible
-        val innerTextFieldBounds =
-            Rect(
-                0f,
-                textLayoutResult.getLineTop(2) + 1f,
-                fontSizeInPx,
-                textLayoutResult.getLineBottom(4) - 1f
-            )
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    innerTextFieldBounds = innerTextFieldBounds,
-                    decorationBoxBounds = innerTextFieldBounds
-                )
-
-        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(3)
-        // Line 2 "ccc" has 3 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[0])
-            .isEqualTo(
-                RectF(
-                    0f,
-                    textLayoutResult.getLineTop(2),
-                    3 * fontSizeInPx,
-                    textLayoutResult.getLineBottom(2)
-                )
-            )
-        // Line 3 "dddd" has 4 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[1])
-            .isEqualTo(
-                RectF(
-                    0f,
-                    textLayoutResult.getLineTop(3),
-                    4 * fontSizeInPx,
-                    textLayoutResult.getLineBottom(3)
-                )
-            )
-        // Line 4 "eeeee" has 5 characters
-        assertThat(cursorAnchorInfo.visibleLineBounds[2])
-            .isEqualTo(
-                RectF(
-                    0f,
-                    textLayoutResult.getLineTop(4),
-                    5 * fontSizeInPx,
-                    textLayoutResult.getLineBottom(4)
-                )
-            )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Test
-    fun testLineBoundsNotIncludedWhenIncludeLineBoundsFalse() {
-        val fontSize = 10.sp
-        val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
-        // 6 lines of text
-        val text = "a\nbb\nccc\ndddd\neeeee\nffffff"
-        val selection = TextRange(0)
-        val composition: TextRange? = null
-        val textLayoutResult = getTextLayoutResult(text, fontSize = fontSize)
-        // Lines 2, 3, 4 are visible
-        val innerTextFieldBounds =
-            Rect(
-                0f,
-                textLayoutResult.getLineTop(2) + 1f,
-                fontSizeInPx,
-                textLayoutResult.getLineBottom(4) - 1f
-            )
-
-        val cursorAnchorInfo =
-            CursorAnchorInfo.Builder()
-                .build(
-                    text,
-                    selection,
-                    composition,
-                    textLayoutResult,
-                    matrix,
-                    innerTextFieldBounds = innerTextFieldBounds,
-                    decorationBoxBounds = innerTextFieldBounds,
-                    includeLineBounds = false
-                )
-
-        assertThat(cursorAnchorInfo.visibleLineBounds.size).isEqualTo(0)
-    }
-
-    private fun getTextLayoutResult(
-        text: String,
-        fontSize: TextUnit = 12.sp,
-        width: Float = with(defaultDensity) { 1000.dp.toPx() }
-    ): TextLayoutResult {
-        val intWidth = ceil(width).toInt()
-
-        val fontFamilyResolver = createFontFamilyResolver(context)
-
-        val input =
-            TextLayoutInput(
-                text = AnnotatedString(text),
-                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = fontSize),
-                placeholders = listOf(),
-                maxLines = Int.MAX_VALUE,
-                softWrap = true,
-                overflow = TextOverflow.Visible,
-                density = defaultDensity,
-                layoutDirection = LayoutDirection.Ltr,
-                fontFamilyResolver = fontFamilyResolver,
-                constraints = Constraints(maxWidth = intWidth)
-            )
-
-        val paragraph =
-            MultiParagraph(
-                annotatedString = input.text,
-                style = input.style,
-                constraints = Constraints(maxWidth = ceil(width).toInt()),
-                density = input.density,
-                fontFamilyResolver = fontFamilyResolver
-            )
-
-        return TextLayoutResult(input, paragraph, IntSize(intWidth, ceil(paragraph.height).toInt()))
-    }
-}
-
-private fun CursorAnchorInfo.Builder.build(
-    text: CharSequence,
-    selection: TextRange,
-    composition: TextRange?,
-    textLayoutResult: TextLayoutResult,
-    matrix: Matrix,
-    includeInsertionMarker: Boolean = true,
-    includeCharacterBounds: Boolean = true,
-    includeEditorBounds: Boolean = true,
-    includeLineBounds: Boolean = true
-): CursorAnchorInfo {
-    val innerTextFieldBounds =
-        Rect(0f, 0f, textLayoutResult.size.width.toFloat(), textLayoutResult.size.height.toFloat())
-    return build(
-        text,
-        selection,
-        composition,
-        textLayoutResult,
-        matrix,
-        innerTextFieldBounds = innerTextFieldBounds,
-        decorationBoxBounds = innerTextFieldBounds,
-        includeInsertionMarker = includeInsertionMarker,
-        includeCharacterBounds = includeCharacterBounds,
-        includeEditorBounds = includeEditorBounds,
-        includeLineBounds = includeLineBounds
-    )
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/DragAndDropTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/DragAndDropTestUtils.kt
deleted file mode 100644
index c5494a8..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/DragAndDropTestUtils.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.content.ClipData
-import android.net.Uri
-import android.os.Build
-import android.os.Parcel
-import android.view.DragEvent
-import androidx.compose.foundation.content.createClipData
-import androidx.compose.ui.geometry.Offset
-
-/**
- * Helper utilities for creating drag events.
- *
- * This class is a copy-paste from DragAndDrop artifact with the addition of configurable offset.
- * Also it does not mock but uses Parcel to create a DragEvent.
- */
-object DragAndDropTestUtils {
-    private const val SAMPLE_TEXT = "Drag Text"
-    private val SAMPLE_URI = Uri.parse("http://www.google.com")
-
-    /**
-     * Makes a stub drag event containing fake text data.
-     *
-     * @param action One of the [DragEvent] actions.
-     */
-    fun makeTextDragEvent(
-        action: Int,
-        text: String = SAMPLE_TEXT,
-        offset: Offset = Offset.Zero,
-    ): DragEvent {
-        return makeDragEvent(
-            action = action,
-            clipData = createClipData { addText(text) },
-            offset = offset
-        )
-    }
-
-    /**
-     * Makes a stub drag event containing an image mimetype and fake uri.
-     *
-     * @param action One of the [DragEvent] actions.
-     */
-    fun makeImageDragEvent(
-        action: Int,
-        item: Uri = SAMPLE_URI,
-        offset: Offset = Offset.Zero,
-    ): DragEvent {
-        return makeDragEvent(
-            action = action,
-            clipData = createClipData {
-                // We're not actually resolving Uris in these tests, so this can be anything:
-                addUri(item, mimeType = "image/png")
-            },
-            offset = offset
-        )
-    }
-
-    fun makeDragEvent(
-        action: Int,
-        clipData: ClipData,
-        offset: Offset = Offset.Zero
-    ): DragEvent {
-        val parcel = Parcel.obtain()
-
-        parcel.writeInt(action)
-        parcel.writeFloat(offset.x)
-        parcel.writeFloat(offset.y)
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            // mOffset was made part of DragEvent in API 31.
-            parcel.writeFloat(0f)
-            parcel.writeFloat(0f)
-        }
-        parcel.writeInt(0) // Result
-        parcel.writeInt(1)
-        clipData.writeToParcel(parcel, 0)
-        parcel.writeInt(1)
-        clipData.description.writeToParcel(parcel, 0)
-
-        parcel.setDataPosition(0)
-        return DragEvent.CREATOR.createFromParcel(parcel)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/EditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/EditorInfoTest.kt
deleted file mode 100644
index d55dec4..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/EditorInfoTest.kt
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.text.InputType
-import android.view.inputmethod.EditorInfo
-import androidx.compose.foundation.text.input.internal.update
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.ImeOptions
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class EditorInfoTest {
-
-    @Test
-    fun test_fill_editor_info_text() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Text,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_ascii() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_number() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Number,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_phone() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Phone,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_PHONE and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_uri() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Uri,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_VARIATION_URI and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_email() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Email,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS and info.inputType) != 0)
-            .isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_password() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Password,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_number_password() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.NumberPassword,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
-        assertThat((InputType.TYPE_NUMBER_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_decimal_number() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Decimal,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
-        assertThat((InputType.TYPE_NUMBER_FLAG_DECIMAL and info.inputType) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_UNSPECIFIED
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_none() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.None
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_NONE
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_go() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Go
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_GO
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_next() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Next
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_NEXT
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_previous() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Previous
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_PREVIOUS
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_search() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Search,
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_SEARCH
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_send() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Send
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_SEND
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_action_done() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
-        assertThat(
-            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
-                == EditorInfo.IME_ACTION_DONE
-        ).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_multi_line() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                singleLine = false,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
-        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_multi_line_with_default_action() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                singleLine = false,
-                keyboardType = KeyboardType.Text,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
-        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isFalse()
-    }
-
-    @Test
-    fun test_fill_editor_info_single_line() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                singleLine = true,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_single_line_changes_ime_from_unspecified_to_done() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                singleLine = true,
-                keyboardType = KeyboardType.Text,
-                imeAction = ImeAction.Default
-            )
-        )
-
-        assertThat((EditorInfo.IME_ACTION_DONE and info.imeOptions) == 0).isFalse()
-        assertThat((EditorInfo.IME_ACTION_UNSPECIFIED and info.imeOptions) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_multi_line_not_set_when_input_type_is_not_text() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                singleLine = false,
-                keyboardType = KeyboardType.Number,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
-        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_capitalization_none() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                capitalization = KeyboardCapitalization.None,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_capitalization_characters() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                capitalization = KeyboardCapitalization.Characters,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isFalse()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_capitalization_words() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                capitalization = KeyboardCapitalization.Words,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isFalse()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_capitalization_sentences() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                capitalization = KeyboardCapitalization.Sentences,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done,
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isFalse()
-    }
-
-    @Test
-    fun test_fill_editor_info_capitalization_not_added_when_input_type_is_not_text() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                capitalization = KeyboardCapitalization.Sentences,
-                keyboardType = KeyboardType.Number,
-                imeAction = ImeAction.Done,
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
-        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun test_fill_editor_info_auto_correct_on() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                autoCorrect = true,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isFalse()
-    }
-
-    @Test
-    fun test_fill_editor_info_auto_correct_off() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                autoCorrect = false,
-                keyboardType = KeyboardType.Ascii,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun autocorrect_not_added_when_input_type_is_not_text() {
-        val info = EditorInfo()
-        info.update(
-            ImeOptions(
-                autoCorrect = true,
-                keyboardType = KeyboardType.Number,
-                imeAction = ImeAction.Done
-            )
-        )
-
-        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
-    }
-
-    @Test
-    fun initial_default_selection_info_is_set() {
-        val info = EditorInfo()
-        info.update(ImeOptions.Default)
-
-        assertThat(info.initialSelStart).isEqualTo(0)
-        assertThat(info.initialSelEnd).isEqualTo(0)
-    }
-
-    @Test
-    fun initial_selection_info_is_set() {
-        val selection = TextRange(1, 2)
-        val info = EditorInfo()
-        info.update("abc", selection, ImeOptions.Default)
-
-        assertThat(info.initialSelStart).isEqualTo(selection.start)
-        assertThat(info.initialSelEnd).isEqualTo(selection.end)
-    }
-
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun if_not_null_contentMimeTypes_are_set_above25() {
-        val contentMimeTypes = arrayOf("text/*", "image/png")
-        val info = EditorInfo()
-        info.update(ImeOptions.Default, contentMimeTypes)
-
-        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
-    }
-
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun if_null_contentMimeTypes_are_not_set() {
-        val contentMimeTypes = arrayOf("text/*", "image/png")
-        val info = EditorInfo()
-        info.update(ImeOptions.Default, contentMimeTypes)
-
-        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
-
-        info.update(ImeOptions.Default, null)
-        assertThat(info.contentMimeTypes).isEqualTo(contentMimeTypes)
-    }
-
-    @SdkSuppress(maxSdkVersion = 24)
-    @Test
-    fun if_not_null_contentMimeTypes_are_set_below24() {
-        val contentMimeTypes = arrayOf("text/*", "image/png")
-        val info = EditorInfo()
-        info.update(ImeOptions.Default, contentMimeTypes)
-
-        assertThat(info.extras.keySet().any { it.contains("CONTENT_MIME_TYPES") }).isTrue()
-    }
-
-    private fun EditorInfo.update(
-        imeOptions: ImeOptions,
-        contentMimeTypes: Array<String>? = null
-    ) {
-        this.update("", TextRange.Zero, imeOptions, contentMimeTypes)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/MoveCursorCommandTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/MoveCursorCommandTest.kt
deleted file mode 100644
index 117045c..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/MoveCursorCommandTest.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MoveCursorCommandTest {
-    private val CH1 = "\uD83D\uDE00" // U+1F600
-    private val CH2 = "\uD83D\uDE01" // U+1F601
-    private val CH3 = "\uD83D\uDE02" // U+1F602
-    private val CH4 = "\uD83D\uDE03" // U+1F603
-    private val CH5 = "\uD83D\uDE04" // U+1F604
-
-    // U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
-    private val FAMILY = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
-
-    @Test
-    fun test_left() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.moveCursor(-1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_left_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.moveCursor(-2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_left_from_offset0() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.moveCursor(-1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_right() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.moveCursor(1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_right_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.moveCursor(2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_right_from_offset_length() {
-        val eb = EditingBuffer("ABCDE", TextRange(5))
-
-        eb.moveCursor(1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_left_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.moveCursor(-1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_left_multiple_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.moveCursor(-2)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_right_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.moveCursor(1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(8)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_right_multiple_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.moveCursor(2)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(10)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 26)
-    fun test_left_emoji() {
-        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
-
-        eb.moveCursor(-1)
-
-        assertThat(eb.toString()).isEqualTo("$FAMILY$FAMILY")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 26)
-    fun test_right_emoji() {
-        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
-
-        eb.moveCursor(1)
-
-        assertThat(eb.toString()).isEqualTo("$FAMILY$FAMILY")
-        assertThat(eb.cursor).isEqualTo(2 * FAMILY.length)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt
deleted file mode 100644
index 1653ef2..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.content.ClipDescription
-import android.net.Uri
-import android.os.Bundle
-import android.view.KeyEvent
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputContentInfo
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.platform.firstUriOrNull
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.ImeAction
-import androidx.core.view.inputmethod.EditorInfoCompat
-import androidx.core.view.inputmethod.InputConnectionCompat
-import androidx.core.view.inputmethod.InputContentInfoCompat
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class StatelessInputConnectionTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var ic: StatelessInputConnection
-    private val activeSession: TextInputSession = object : TextInputSession {
-        override val text: TextFieldCharSequence
-            get() = [email protected]
-
-        override fun onImeAction(imeAction: ImeAction) {
-            [email protected]?.invoke(imeAction)
-        }
-
-        override fun requestEdit(
-            notifyImeOfChanges: Boolean,
-            block: EditingBuffer.() -> Unit
-        ) {
-            onRequestEdit?.invoke(notifyImeOfChanges, block)
-        }
-
-        override fun sendKeyEvent(keyEvent: KeyEvent) {
-            onSendKeyEvent?.invoke(keyEvent)
-        }
-
-        override fun requestCursorUpdates(cursorUpdateMode: Int) {
-        }
-
-        override fun onCommitContent(transferableContent: TransferableContent): Boolean {
-            return [email protected]?.invoke(transferableContent)
-                ?: false
-        }
-    }
-
-    private var state: TextFieldState = TextFieldState()
-    private var value: TextFieldCharSequence = TextFieldCharSequence()
-        set(value) {
-            field = value
-            state = TextFieldState(value.toString(), value.selectionInChars)
-        }
-    private var onRequestEdit: ((Boolean, EditingBuffer.() -> Unit) -> Unit)? = null
-    private var onSendKeyEvent: ((KeyEvent) -> Unit)? = null
-    private var onImeAction: ((ImeAction) -> Unit)? = null
-    private var onCommitContent: ((TransferableContent) -> Boolean)? = null
-
-    @Before
-    fun setup() {
-        ic = StatelessInputConnection(activeSession, EditorInfo())
-    }
-
-    @Test
-    fun getTextBeforeAndAfterCursorTest() {
-        assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("")
-        assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("")
-
-        // Set "Hello, World", and place the cursor at the beginning of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange.Zero
-        )
-
-        assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("")
-        assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("Hello, World")
-
-        // Set "Hello, World", and place the cursor between "H" and "e".
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(1)
-        )
-
-        assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("H")
-        assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("ello, World")
-
-        // Set "Hello, World", and place the cursor at the end of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(12)
-        )
-
-        assertThat(ic.getTextBeforeCursor(100, 0)).isEqualTo("Hello, World")
-        assertThat(ic.getTextAfterCursor(100, 0)).isEqualTo("")
-    }
-
-    @Test
-    fun getTextBeforeAndAfterCursorTest_maxCharTest() {
-        // Set "Hello, World", and place the cursor at the beginning of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange.Zero
-        )
-
-        assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("")
-        assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("Hello")
-
-        // Set "Hello, World", and place the cursor between "H" and "e".
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(1)
-        )
-
-        assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("H")
-        assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("ello,")
-
-        // Set "Hello, World", and place the cursor at the end of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(12)
-        )
-
-        assertThat(ic.getTextBeforeCursor(5, 0)).isEqualTo("World")
-        assertThat(ic.getTextAfterCursor(5, 0)).isEqualTo("")
-    }
-
-    @Test
-    fun getSelectedTextTest() {
-        // Set "Hello, World", and place the cursor at the beginning of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange.Zero
-        )
-
-        assertThat(ic.getSelectedText(0)).isNull()
-
-        // Set "Hello, World", and place the cursor between "H" and "e".
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(0, 1)
-        )
-
-        assertThat(ic.getSelectedText(0)).isEqualTo("H")
-
-        // Set "Hello, World", and place the cursor at the end of the text.
-        value = TextFieldCharSequence(
-            text = "Hello, World",
-            selection = TextRange(0, 12)
-        )
-
-        assertThat(ic.getSelectedText(0)).isEqualTo("Hello, World")
-    }
-
-    @Test
-    fun commitTextTest_batchSession() {
-        var requestEditsCalled = 0
-        onRequestEdit = { _, block ->
-            requestEditsCalled++
-            state.mainBuffer.block()
-        }
-        value = TextFieldCharSequence(text = "", selection = TextRange.Zero)
-
-        // IME set text "Hello, World." with two commitText API within the single batch session.
-        // Do not callback to listener during batch session.
-        ic.beginBatchEdit()
-
-        assertThat(ic.commitText("Hello, ", 1)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        assertThat(ic.commitText("World.", 1)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        ic.endBatchEdit()
-
-        assertThat(requestEditsCalled).isEqualTo(1)
-        assertThat(state.mainBuffer.toString()).isEqualTo("Hello, World.")
-        assertThat(state.mainBuffer.selection).isEqualTo(TextRange(13))
-    }
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun commitContent_parsesToTransferableContent() {
-        var transferableContent: TransferableContent? = null
-        onCommitContent = {
-            transferableContent = it
-            true
-        }
-        val contentUri = Uri.parse("content://com.example/content")
-        val linkUri = Uri.parse("https://example.com")
-        val description = ClipDescription("label", arrayOf("text/plain"))
-        val extras = Bundle().apply { putString("key", "value") }
-        val result = ic.commitContent(
-            InputContentInfo(contentUri, description, linkUri),
-            InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
-            extras
-        )
-
-        assertThat(transferableContent).isNotNull()
-        assertThat(transferableContent?.clipEntry).isNotNull()
-        assertThat(transferableContent?.clipEntry?.firstUriOrNull()).isEqualTo(contentUri)
-        assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
-        assertThat(transferableContent?.clipMetadata?.clipDescription)
-            .isSameInstanceAs(description)
-
-        assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
-        assertThat(transferableContent?.platformTransferableContent?.linkUri).isEqualTo(linkUri)
-        assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
-            .contains("key")
-        assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
-            .contains("EXTRA_INPUT_CONTENT_INFO")
-
-        assertTrue(result)
-    }
-
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun commitContent_returnsResultIfFalse() {
-        onCommitContent = {
-            false
-        }
-        val contentUri = Uri.parse("content://com.example/content")
-        val description = ClipDescription("label", arrayOf("text/plain"))
-        val result = ic.commitContent(InputContentInfo(contentUri, description), 0, null)
-
-        assertFalse(result)
-    }
-
-    @SdkSuppress(minSdkVersion = 25)
-    @Test
-    fun commitContent_returnsFalseWhenNotDefined() {
-        onCommitContent = null
-        val contentUri = Uri.parse("content://com.example/content")
-        val description = ClipDescription("label", arrayOf("text/plain"))
-        val result = ic.commitContent(InputContentInfo(contentUri, description), 0, null)
-
-        assertFalse(result)
-    }
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @SdkSuppress(maxSdkVersion = 24)
-    @Test
-    fun performPrivateCommand_parsesToTransferableContent() {
-        var transferableContent: TransferableContent? = null
-        onCommitContent = {
-            transferableContent = it
-            true
-        }
-
-        val editorInfo = EditorInfo()
-        EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("text/plain"))
-
-        ic = StatelessInputConnection(activeSession, editorInfo)
-
-        val contentUri = Uri.parse("content://com.example/content")
-        val linkUri = Uri.parse("https://example.com")
-        val description = ClipDescription("label", arrayOf("text/plain"))
-        val extras = Bundle().apply { putString("key", "value") }
-        // this will internally call performPrivateCommand when SDK <= 24
-        val result = InputConnectionCompat.commitContent(
-            ic,
-            editorInfo,
-            InputContentInfoCompat(contentUri, description, linkUri),
-            InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
-            extras
-        )
-
-        assertThat(transferableContent).isNotNull()
-        assertThat(transferableContent?.clipEntry).isNotNull()
-        assertThat(transferableContent?.clipEntry?.firstUriOrNull()).isEqualTo(contentUri)
-        assertThat(transferableContent?.clipEntry?.clipData?.itemCount).isEqualTo(1)
-        assertThat(transferableContent?.clipMetadata?.clipDescription)
-            .isSameInstanceAs(description)
-
-        assertThat(transferableContent?.source).isEqualTo(TransferableContent.Source.Keyboard)
-        assertThat(transferableContent?.platformTransferableContent?.linkUri).isEqualTo(linkUri)
-        assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
-            .contains("key")
-        // Permissions do not exist below SDK 25
-        assertThat(transferableContent?.platformTransferableContent?.extras?.keySet())
-            .doesNotContain("EXTRA_INPUT_CONTENT_INFO")
-
-        assertTrue(result)
-    }
-
-    @Test
-    fun mixedAPICalls_batchSession() {
-        var requestEditsCalled = 0
-        onRequestEdit = { _, block ->
-            requestEditsCalled++
-            state.mainBuffer.block()
-        }
-        value = TextFieldCharSequence(text = "", selection = TextRange.Zero)
-
-        // Do not callback to listener during batch session.
-        ic.beginBatchEdit()
-
-        assertThat(ic.setComposingText("Hello, ", 1)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        assertThat(ic.finishComposingText()).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        assertThat(ic.commitText("World.", 1)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        assertThat(ic.setSelection(0, 12)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        assertThat(ic.commitText("", 1)).isTrue()
-        assertThat(requestEditsCalled).isEqualTo(0)
-
-        ic.endBatchEdit()
-
-        assertThat(requestEditsCalled).isEqualTo(1)
-        assertThat(state.mainBuffer.toString()).isEqualTo(".")
-        assertThat(state.mainBuffer.selection).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun closeConnection() {
-        // Everything is internal and there is nothing to expect.
-        // Just make sure it is not crashed by calling method.
-        ic.closeConnection()
-    }
-
-    @Test
-    fun do_not_callback_if_only_readonly_ops() {
-        var requestEditsCalled = 0
-        onRequestEdit = { _, _ -> requestEditsCalled++ }
-        ic.beginBatchEdit()
-        ic.getSelectedText(1)
-        ic.endBatchEdit()
-        assertThat(requestEditsCalled).isEqualTo(0)
-    }
-
-    @Test
-    fun sendKeyEvent_whenIMERequests() {
-        val keyEvents = mutableListOf<KeyEvent>()
-        onSendKeyEvent = {
-            keyEvents += it
-        }
-        val keyEvent1 = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0)
-        val keyEvent2 = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)
-        ic.sendKeyEvent(keyEvent1)
-        ic.sendKeyEvent(keyEvent2)
-
-        assertThat(keyEvents.size).isEqualTo(2)
-        assertThat(keyEvents.first()).isEqualTo(keyEvent1)
-        assertThat(keyEvents.last()).isEqualTo(keyEvent2)
-    }
-
-    @Test
-    fun performImeAction_whenIMERequests() {
-        val receivedImeActions = mutableListOf<ImeAction>()
-        onImeAction = {
-            receivedImeActions += it
-        }
-        ic.performEditorAction(EditorInfo.IME_ACTION_DONE)
-        ic.performEditorAction(EditorInfo.IME_ACTION_GO)
-        ic.performEditorAction(EditorInfo.IME_ACTION_NEXT)
-        ic.performEditorAction(EditorInfo.IME_ACTION_NONE)
-        ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS)
-        ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH)
-        ic.performEditorAction(EditorInfo.IME_ACTION_SEND)
-        ic.performEditorAction(EditorInfo.IME_ACTION_UNSPECIFIED)
-        ic.performEditorAction(-1)
-
-        assertThat(receivedImeActions).isEqualTo(listOf(
-            ImeAction.Done,
-            ImeAction.Go,
-            ImeAction.Next,
-            ImeAction.Default, // None is evaluated back to Default.
-            ImeAction.Previous,
-            ImeAction.Search,
-            ImeAction.Send,
-            ImeAction.Default, // Unspecified is evaluated back to Default.
-            ImeAction.Default // Unrecognized is evaluated back to Default.
-        ))
-    }
-
-    @Test
-    fun selectAll_contextMenuAction_triggersSelectionAndImeNotification() {
-        value = TextFieldCharSequence("Hello")
-        var callCount = 0
-        var isNotifyIme = false
-        onRequestEdit = { notify, block ->
-            isNotifyIme = notify
-            callCount++
-            state.mainBuffer.block()
-        }
-
-        ic.performContextMenuAction(android.R.id.selectAll)
-
-        assertThat(callCount).isEqualTo(1)
-        assertThat(isNotifyIme).isTrue()
-        assertThat(state.mainBuffer.selection).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun cut_contextMenuAction_triggersSyntheticKeyEvents() {
-        val keyEvents = mutableListOf<KeyEvent>()
-        onSendKeyEvent = { keyEvents += it }
-
-        ic.performContextMenuAction(android.R.id.cut)
-
-        assertThat(keyEvents.size).isEqualTo(2)
-        assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
-        assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_CUT)
-        assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
-        assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_CUT)
-    }
-
-    @Test
-    fun copy_contextMenuAction_triggersSyntheticKeyEvents() {
-        val keyEvents = mutableListOf<KeyEvent>()
-        onSendKeyEvent = { keyEvents += it }
-
-        ic.performContextMenuAction(android.R.id.copy)
-
-        assertThat(keyEvents.size).isEqualTo(2)
-        assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
-        assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_COPY)
-        assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
-        assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_COPY)
-    }
-
-    @Test
-    fun paste_contextMenuAction_triggersSyntheticKeyEvents() {
-        val keyEvents = mutableListOf<KeyEvent>()
-        onSendKeyEvent = { keyEvents += it }
-
-        ic.performContextMenuAction(android.R.id.paste)
-
-        assertThat(keyEvents.size).isEqualTo(2)
-        assertThat(keyEvents[0].action).isEqualTo(KeyEvent.ACTION_DOWN)
-        assertThat(keyEvents[0].keyCode).isEqualTo(KeyEvent.KEYCODE_PASTE)
-        assertThat(keyEvents[1].action).isEqualTo(KeyEvent.ACTION_UP)
-        assertThat(keyEvents[1].keyCode).isEqualTo(KeyEvent.KEYCODE_PASTE)
-    }
-
-    @Test
-    fun debugMode_isDisabled() {
-        // run this in presubmit to check that we are not accidentally enabling logs on prod
-        assertFalse(
-            SIC_DEBUG,
-            "Oops, looks like you accidentally enabled logging. Don't worry, we've all " +
-                "been there. Just remember to turn it off before you deploy your code."
-        )
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt
deleted file mode 100644
index 9f073ac..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt
+++ /dev/null
@@ -1,780 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.setTextAndPlaceCursorAtEnd
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.ObserverHandle
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.SnapshotStateObserver
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.createFontFamilyResolver
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.MediumTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlin.test.assertNotNull
-import org.junit.After
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(AndroidJUnit4::class)
-@MediumTest
-class TextFieldLayoutStateCacheTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private var textFieldState = TextFieldState()
-    private var transformedTextFieldState = TransformedTextFieldState(
-        textFieldState,
-        inputTransformation = null,
-        codepointTransformation = null
-    )
-    private var textStyle = TextStyle()
-    private var singleLine = false
-    private var softWrap = false
-    private var cache = TextFieldLayoutStateCache()
-    private var density = Density(1f, 1f)
-    private var layoutDirection = LayoutDirection.Ltr
-    private var fontFamilyResolver =
-        createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
-    private var constraints = Constraints()
-
-    private lateinit var globalWriteObserverHandle: ObserverHandle
-
-    @Before
-    fun setUp() {
-        globalWriteObserverHandle = Snapshot.registerGlobalWriteObserver {
-            Snapshot.sendApplyNotifications()
-        }
-    }
-
-    @After
-    fun tearDown() {
-        globalWriteObserverHandle.dispose()
-    }
-
-    @Test
-    fun updateAllInputs_doesntInvalidateSnapshot_whenNothingChanged() {
-        assertInvalidationsOnChange(0) {
-            updateNonMeasureInputs()
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenTextContentChanged() {
-        textFieldState.edit {
-            replace(0, length, "")
-            placeCursorBeforeCharAt(0)
-        }
-        assertInvalidationsOnChange(1) {
-            textFieldState.edit {
-                append("hello")
-                placeCursorBeforeCharAt(0)
-            }
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenTextSelectionChanged() {
-        textFieldState.edit {
-            append("hello")
-            placeCursorBeforeCharAt(0)
-        }
-        assertInvalidationsOnChange(1) {
-            textFieldState.edit {
-                placeCursorBeforeCharAt(1)
-            }
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenCodepointTransformationChanged() {
-        assertInvalidationsOnChange(1) {
-            val codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
-            transformedTextFieldState = TransformedTextFieldState(
-                textFieldState,
-                inputTransformation = null,
-                codepointTransformation
-            )
-            updateNonMeasureInputs()
-        }
-    }
-
-    @FlakyTest(bugId = 300168644)
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenStyleLayoutAffectingAttrsChanged() {
-        textStyle = TextStyle(fontSize = 12.sp)
-        assertInvalidationsOnChange(1) {
-            textStyle = TextStyle(fontSize = 23.sp)
-            updateNonMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_doesInvalidateSnapshot_whenStyleDrawAffectingAttrsChanged() {
-        // Measure still does not happen but TextLayoutInput object inside TextLayoutResult
-        // should change to reflect the latest inputs that need to be used during the draw phase.
-        textStyle = TextStyle(color = Color.Black)
-        assertInvalidationsOnChange(1) {
-            textStyle = TextStyle(color = Color.Blue)
-            updateNonMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenSingleLineChanged() {
-        assertInvalidationsOnChange(1) {
-            singleLine = !singleLine
-            updateNonMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateNonMeasureInputs_invalidatesSnapshot_whenSoftWrapChanged() {
-        assertInvalidationsOnChange(1) {
-            softWrap = !softWrap
-            updateNonMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenDensityInstanceChangedWithDifferentValues() {
-        density = Density(1f, 1f)
-        assertInvalidationsOnChange(1) {
-            density = Density(1f, 2f)
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_doesntInvalidateSnapshot_whenDensityInstanceChangedWithSameValues() {
-        density = Density(1f, 1f)
-        assertInvalidationsOnChange(0) {
-            density = Density(1f, 1f)
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenDensityValueChangedWithSameInstance() {
-        var densityValue = 1f
-        density = object : Density {
-            override val density: Float
-                get() = densityValue
-            override val fontScale: Float = 1f
-        }
-        assertInvalidationsOnChange(1) {
-            densityValue = 2f
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenFontScaleChangedWithSameInstance() {
-        var fontScale = 1f
-        density = object : Density {
-            override val density: Float = 1f
-            override val fontScale: Float
-                get() = fontScale
-        }
-        assertInvalidationsOnChange(1) {
-            fontScale = 2f
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenLayoutDirectionChanged() {
-        layoutDirection = LayoutDirection.Ltr
-        assertInvalidationsOnChange(1) {
-            layoutDirection = LayoutDirection.Rtl
-            updateMeasureInputs()
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenFontFamilyResolverChanged() {
-        assertInvalidationsOnChange(1) {
-            fontFamilyResolver =
-                createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
-            updateMeasureInputs()
-        }
-    }
-
-    @Ignore("b/294443266: figure out how to make fonts stale for test")
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenFontFamilyResolverFontChanged() {
-        fontFamilyResolver =
-            createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
-        assertInvalidationsOnChange(1) {
-            TODO("b/294443266: make fonts stale")
-        }
-    }
-
-    @Test
-    fun updateMeasureInputs_invalidatesSnapshot_whenConstraintsChanged() {
-        constraints = Constraints.fixed(5, 5)
-        assertInvalidationsOnChange(1) {
-            constraints = Constraints.fixed(6, 5)
-            updateMeasureInputs()
-        }
-    }
-
-    /**
-     * A scope that reads the layout cache and has a full cache hit – that is, all the inputs match
-     * – will never run the CodepointTransformation, and thus never register a "read" of any of the
-     * state objects the transformation function happens to read. If those inputs change, all scopes
-     * should be invalidated, even the ones that never actually ran the transformation function.
-     *
-     * The first time we compute layout with a transformation function, we invoke the transformation
-     * function. This function is provided externally and may perform zero or more state reads. The
-     * first time the layout is computed, those state reads will be seen by whatever snapshot
-     * observer is observing the layout call, and when they change, that reader will be invalidated.
-     * However, if somewhere else some different code asks for the layout, and none of the inputs
-     * have changed, it will return the cached value without ever running the transformation
-     * function. This means that when states read by the transformation change, that second reader
-     * won't be invalidated since it never observed those reads.
-     *
-     * To fix this, we manually record reads done by the transformation function and re-read them
-     * explicitly when checking for a full cache hit.
-     */
-    @Ignore("b/306198696")
-    @Test
-    fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingSameVisualText() {
-        var transformationState by mutableStateOf(1)
-        var transformationInvocations = 0
-        val codepointTransformation = CodepointTransformation { _, codepoint ->
-            transformationInvocations++
-            @Suppress("UNUSED_EXPRESSION")
-            transformationState
-            codepoint + 1
-        }
-        transformedTextFieldState = TransformedTextFieldState(
-            textFieldState, inputTransformation = null,
-            codepointTransformation
-        )
-        // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
-        // simpler.
-        textFieldState.setTextAndPlaceCursorAtEnd("h")
-        val expectedVisualText = "i"
-
-        fun assertVisualText() {
-            assertThat(cache.value?.layoutInput?.text?.text).isEqualTo(expectedVisualText)
-        }
-
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        var primaryInvalidations = 0
-        var secondaryInvalidations = 0
-
-        val primaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
-        val secondaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
-        try {
-            primaryObserver.start()
-            secondaryObserver.start()
-
-            // This will compute the initial layout.
-            primaryObserver.observeReads(Unit, onValueChangedForScope = {
-                primaryInvalidations++
-                assertVisualText()
-            }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(1)
-
-            // This should be a full cache hit.
-            secondaryObserver.observeReads(Unit, onValueChangedForScope = {
-                secondaryInvalidations++
-                assertVisualText()
-            }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(1)
-
-            // Invalidate the transformation.
-            transformationState++
-        } finally {
-            primaryObserver.stop()
-            secondaryObserver.stop()
-        }
-
-        assertVisualText()
-        assertThat(transformationInvocations).isEqualTo(2)
-        assertThat(primaryInvalidations).isEqualTo(1)
-        assertThat(secondaryInvalidations).isEqualTo(1)
-    }
-
-    @FlakyTest(bugId = 299662404)
-    @Test
-    fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingNewVisualText() {
-        var transformationState by mutableStateOf(1)
-        var transformationInvocations = 0
-        val codepointTransformation = CodepointTransformation { _, codepoint ->
-            transformationInvocations++
-            codepoint + transformationState
-        }
-        transformedTextFieldState = TransformedTextFieldState(
-            textFieldState,
-            inputTransformation = null,
-            codepointTransformation
-        )
-        // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
-        // simpler.
-        textFieldState.setTextAndPlaceCursorAtEnd("h")
-        var expectedVisualText = "i"
-
-        fun assertVisualText() {
-            assertThat(cache.value?.layoutInput?.text?.text).isEqualTo(expectedVisualText)
-        }
-
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        var primaryInvalidations = 0
-        var secondaryInvalidations = 0
-
-        val primaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
-        val secondaryObserver = SnapshotStateObserver(onChangedExecutor = { it() })
-        try {
-            primaryObserver.start()
-            secondaryObserver.start()
-
-            // This will compute the initial layout.
-            primaryObserver.observeReads(Unit, onValueChangedForScope = {
-                primaryInvalidations++
-                assertVisualText()
-            }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(1)
-
-            // This should be a full cache hit.
-            secondaryObserver.observeReads(Unit, onValueChangedForScope = {
-                secondaryInvalidations++
-                assertVisualText()
-            }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(1)
-
-            // Invalidate the transformation.
-            expectedVisualText = "j"
-            transformationState++
-        } finally {
-            primaryObserver.stop()
-            secondaryObserver.stop()
-        }
-
-        assertVisualText()
-        // Two more reads means two more applications of the transformation.
-        assertThat(transformationInvocations).isEqualTo(2)
-        assertThat(primaryInvalidations).isEqualTo(1)
-        assertThat(secondaryInvalidations).isEqualTo(1)
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenTextContentsChanged() {
-        textFieldState.edit {
-            replace(0, length, "h")
-            placeCursorBeforeCharAt(0)
-        }
-        assertLayoutChange(
-            change = {
-                textFieldState.edit {
-                    replace(0, length, "hello")
-                    placeCursorBeforeCharAt(0)
-                }
-            },
-        ) { old, new ->
-            assertThat(old.layoutInput.text.text).isEqualTo("h")
-            assertThat(new.layoutInput.text.text).isEqualTo("hello")
-        }
-    }
-
-    @Test
-    fun value_returnsCachedLayout_whenTextSelectionChanged() {
-        textFieldState.edit {
-            replace(0, length, "hello")
-            placeCursorBeforeCharAt(0)
-        }
-        assertLayoutChange(
-            change = {
-                textFieldState.edit {
-                    placeCursorBeforeCharAt(1)
-                }
-            }
-        ) { old, new ->
-            assertThat(new).isSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenCodepointTransformationInstanceChangedWithDifferentOutput() {
-        textFieldState.setTextAndPlaceCursorAtEnd("h")
-        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
-        transformedTextFieldState = TransformedTextFieldState(
-            textFieldState,
-            inputTransformation = null,
-            codepointTransformation
-        )
-        assertLayoutChange(
-            change = {
-                codepointTransformation = CodepointTransformation { _, codepoint -> codepoint + 1 }
-                transformedTextFieldState = TransformedTextFieldState(
-                    textFieldState,
-                    inputTransformation = null,
-                    codepointTransformation
-                )
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(old.layoutInput.text.text).isEqualTo("h")
-            assertThat(new.layoutInput.text.text).isEqualTo("i")
-        }
-    }
-
-    @Test
-    fun value_returnsCachedLayout_whenCodepointTransformationInstanceChangedWithSameOutput() {
-        textFieldState.setTextAndPlaceCursorAtEnd("h")
-        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
-        transformedTextFieldState = TransformedTextFieldState(
-            textFieldState,
-            inputTransformation = null,
-            codepointTransformation
-        )
-        assertLayoutChange(
-            change = {
-                codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
-                transformedTextFieldState = TransformedTextFieldState(
-                    textFieldState,
-                    inputTransformation = null,
-                    codepointTransformation
-                )
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenStyleLayoutAffectingAttributesChanged() {
-        textStyle = TextStyle(fontSize = 12.sp)
-        assertLayoutChange(
-            change = {
-                textStyle = TextStyle(fontSize = 23.sp)
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(old.layoutInput.style.fontSize).isEqualTo(12.sp)
-            assertThat(new.layoutInput.style.fontSize).isEqualTo(23.sp)
-        }
-    }
-
-    @Test
-    fun value_returnsCachedLayout_whenStyleDrawAffectingAttributesChanged() {
-        textStyle = TextStyle(color = Color.Black)
-        assertLayoutChange(
-            change = {
-                textStyle = TextStyle(color = Color.Blue)
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            // TextLayoutInput needs to change. We only care whether multiParagraph is reused.
-            assertThat(new.multiParagraph).isSameInstanceAs(old.multiParagraph)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenSingleLineChanged() {
-        assertLayoutChange(
-            change = {
-                singleLine = !singleLine
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isNotSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenSoftWrapChanged() {
-        assertLayoutChange(
-            change = {
-                softWrap = !softWrap
-                updateNonMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(old.layoutInput.softWrap).isEqualTo(!softWrap)
-            assertThat(new.layoutInput.softWrap).isEqualTo(softWrap)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenDensityValueChangedWithSameInstance() {
-        var densityValue = 1f
-        density = object : Density {
-            override val density: Float
-                get() = densityValue
-            override val fontScale: Float = 1f
-        }
-        assertLayoutChange(
-            change = {
-                densityValue = 2f
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isNotSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenFontScaleChangedWithSameInstance() {
-        var fontScale = 1f
-        density = object : Density {
-            override val density: Float = 1f
-            override val fontScale: Float
-                get() = fontScale
-        }
-        assertLayoutChange(
-            change = {
-                fontScale = 2f
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isNotSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsCachedLayout_whenDensityInstanceChangedWithSameValues() {
-        density = Density(1f, 1f)
-        assertLayoutChange(
-            change = {
-                density = Density(1f, 1f)
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenLayoutDirectionChanged() {
-        layoutDirection = LayoutDirection.Ltr
-        assertLayoutChange(
-            change = {
-                layoutDirection = LayoutDirection.Rtl
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(old.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
-            assertThat(new.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenFontFamilyResolverChanged() {
-        assertLayoutChange(
-            change = {
-                fontFamilyResolver =
-                    createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(new).isNotSameInstanceAs(old)
-        }
-    }
-
-    @Ignore("b/294443266: figure out how to make fonts stale for test")
-    @Test
-    fun value_returnsNewLayout_whenFontFamilyResolverFontChanged() {
-        fontFamilyResolver =
-            createFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
-        assertLayoutChange(
-            change = {
-                TODO("b/294443266: make fonts stale")
-            }
-        ) { old, new ->
-            assertThat(new).isNotSameInstanceAs(old)
-        }
-    }
-
-    @Test
-    fun value_returnsNewLayout_whenConstraintsChanged() {
-        constraints = Constraints.fixed(5, 5)
-        assertLayoutChange(
-            change = {
-                constraints = Constraints.fixed(6, 5)
-                updateMeasureInputs()
-            }
-        ) { old, new ->
-            assertThat(old.layoutInput.constraints).isEqualTo(Constraints.fixed(5, 5))
-            assertThat(new.layoutInput.constraints).isEqualTo(Constraints.fixed(6, 5))
-        }
-    }
-
-    @Test
-    fun cacheUpdateInSnapshot_onlyVisibleToParentSnapshotAfterApply() {
-        layoutDirection = LayoutDirection.Ltr
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        val initialLayout = cache.value!!
-        val snapshot = Snapshot.takeMutableSnapshot()
-
-        try {
-            snapshot.enter {
-                layoutDirection = LayoutDirection.Rtl
-                updateMeasureInputs()
-
-                val newLayout = cache.value!!
-                assertThat(initialLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
-                assertThat(newLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
-                assertThat(cache.value!!).isSameInstanceAs(newLayout)
-            }
-
-            // Not visible in parent yet.
-            assertThat(initialLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
-            assertThat(cache.value!!).isSameInstanceAs(initialLayout)
-            snapshot.apply().check()
-
-            // Now visible in parent.
-            val newLayout = cache.value!!
-            assertThat(initialLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
-            assertThat(newLayout.layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
-            assertThat(cache.value!!).isSameInstanceAs(newLayout)
-        } finally {
-            snapshot.dispose()
-        }
-    }
-
-    @Test
-    fun cachedValue_recomputed_afterSnapshotWithConflictingInputsApplied() {
-        softWrap = false
-        layoutDirection = LayoutDirection.Ltr
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        val snapshot = Snapshot.takeMutableSnapshot()
-
-        try {
-            softWrap = true
-            updateNonMeasureInputs()
-            val initialLayout = cache.value!!
-
-            snapshot.enter {
-                layoutDirection = LayoutDirection.Rtl
-                updateMeasureInputs()
-                with(cache.value!!) {
-                    assertThat(layoutInput.softWrap).isEqualTo(false)
-                    assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
-                    assertThat(cache.value!!).isSameInstanceAs(this)
-                }
-            }
-
-            // Parent only sees its update.
-            with(cache.value!!) {
-                assertThat(layoutInput.softWrap).isEqualTo(true)
-                assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Ltr)
-                assertThat(this).isSameInstanceAs(initialLayout)
-                assertThat(cache.value!!).isSameInstanceAs(this)
-            }
-            snapshot.apply().check()
-
-            // Cache should now reflect merged inputs.
-            with(cache.value!!) {
-                assertThat(layoutInput.softWrap).isEqualTo(true)
-                assertThat(layoutInput.layoutDirection).isEqualTo(LayoutDirection.Rtl)
-                assertThat(cache.value!!).isSameInstanceAs(this)
-            }
-        } finally {
-            snapshot.dispose()
-        }
-    }
-
-    private fun assertLayoutChange(
-        change: () -> Unit,
-        compare: (old: TextLayoutResult, new: TextLayoutResult) -> Unit
-    ) {
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        val initialLayout = cache.value
-
-        change()
-        val newLayout = cache.value
-
-        assertNotNull(newLayout)
-        assertNotNull(initialLayout)
-        compare(initialLayout, newLayout)
-    }
-
-    private fun assertInvalidationsOnChange(
-        expectedInvalidations: Int,
-        update: () -> Unit,
-    ) {
-        updateNonMeasureInputs()
-        updateMeasureInputs()
-        var invalidations = 0
-
-        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
-        observer.start()
-        try {
-            observer.observeReads(
-                scope = Unit,
-                onValueChangedForScope = { invalidations++ },
-                block = { cache.value }
-            )
-            update()
-            // Ensure any changes made by block are processed.
-            Snapshot.sendApplyNotifications()
-        } finally {
-            observer.stop()
-        }
-
-        assertWithMessage("Expected $expectedInvalidations invalidations")
-            .that(invalidations).isEqualTo(expectedInvalidations)
-    }
-
-    private fun updateNonMeasureInputs() {
-        cache.updateNonMeasureInputs(
-            textFieldState = transformedTextFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            softWrap = softWrap
-        )
-    }
-
-    private fun updateMeasureInputs() {
-        cache.layoutWithNewMeasureInputs(
-            density = density,
-            layoutDirection = layoutDirection,
-            fontFamilyResolver = fontFamilyResolver,
-            constraints = constraints
-        )
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
deleted file mode 100644
index 32b62ca..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.view.KeyEvent
-import android.view.View
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.ExtractedText
-import android.view.inputmethod.InputConnection
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.ComposeInputMethodManagerTestRule
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.runtime.snapshots.ObserverHandle
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Matrix
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.platform.PlatformTextInputMethodRequest
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.createFontFamilyResolver
-import androidx.compose.ui.text.input.ImeOptions
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-internal class TextInputServiceAndroidCursorAnchorInfoTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @get:Rule
-    val composeImmRule = ComposeInputMethodManagerTestRule().apply {
-        setFactory { composeImm }
-    }
-
-    private val context = InstrumentationRegistry.getInstrumentation().context
-    private val defaultDensity = Density(density = 1f)
-    private val builder = CursorAnchorInfo.Builder()
-    private val androidMatrix = android.graphics.Matrix()
-    private val reportedCursorAnchorInfos = mutableListOf<CursorAnchorInfo>()
-
-    private val composeImm = object : ComposeInputMethodManager {
-        override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
-            reportedCursorAnchorInfos += info
-        }
-
-        override fun restartInput() {}
-        override fun showSoftInput() {}
-        override fun hideSoftInput() {}
-        override fun updateExtractedText(token: Int, extractedText: ExtractedText) {}
-        override fun updateSelection(
-            selectionStart: Int,
-            selectionEnd: Int,
-            compositionStart: Int,
-            compositionEnd: Int
-        ) {
-        }
-
-        override fun sendKeyEvent(event: KeyEvent) {}
-    }
-
-    private lateinit var inputConnection: InputConnection
-    private val session = object : PlatformTextInputSession {
-        override val view = View(InstrumentationRegistry.getInstrumentation().context)
-        override suspend fun startInputMethod(
-            request: PlatformTextInputMethodRequest
-        ): Nothing {
-            inputConnection = request.createInputConnection(EditorInfo())
-            awaitCancellation()
-        }
-    }
-
-    private val windowOffset = Offset(12f, 34f)
-    private val layoutState = TextLayoutState().apply {
-        coreNodeCoordinates = TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
-        decoratorNodeCoordinates =
-            TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
-    }
-
-    @Test
-    fun requestCursorUpdates_immediate() = runTest {
-        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        backgroundScope.startFakeTextInputSession(textFieldState)
-
-        // This requests a single update, immediately, with no future monitoring.
-        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
-
-        // Immediate update.
-        val expectedInfo = builder.build(
-            text = textFieldState.text,
-            selection = textFieldState.text.selectionInChars,
-            composition = textFieldState.text.compositionInChars,
-            textLayoutResult = layoutState.layoutResult!!,
-            matrix = getAndroidMatrix(windowOffset),
-            innerTextFieldBounds = Rect.Zero,
-            decorationBoxBounds = Rect.Zero,
-        )
-        assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
-        reportedCursorAnchorInfos.clear()
-
-        // Trigger new layout.
-        layoutState.coreNodeCoordinates =
-            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
-
-        // No further updates since monitoring is off
-        assertThat(reportedCursorAnchorInfos).isEmpty()
-    }
-
-    @Test
-    fun requestCursorUpdates_immediate_beforeUpdateTextLayoutResult() = runTest {
-        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        val transformedState = TransformedTextFieldState(
-            textFieldState = textFieldState,
-            inputTransformation = null,
-            codepointTransformation = null
-        )
-
-        backgroundScope.launch(Dispatchers.Unconfined) {
-            session.platformSpecificTextInputSession(
-                state = transformedState,
-                layoutState = layoutState,
-                imeOptions = ImeOptions.Default,
-                receiveContentConfiguration = null,
-                onImeAction = null,
-            )
-        }
-
-        // This requests a single update, immediately, with no future monitoring.
-        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
-
-        assertThat(reportedCursorAnchorInfos).isEmpty()
-    }
-
-    @Test
-    fun requestCursorUpdates_monitor() = runTest {
-        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        backgroundScope.startFakeTextInputSession(textFieldState)
-
-        // This requests a single update, immediately, with no future monitoring.
-        inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
-
-        // No immediate update.
-        assertThat(reportedCursorAnchorInfos).isEmpty()
-
-        // Trigger new layout.
-        layoutState.coreNodeCoordinates =
-            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
-
-        // Monitoring update.
-        val expectedInfo = builder.build(
-            text = textFieldState.text,
-            selection = textFieldState.text.selectionInChars,
-            composition = textFieldState.text.compositionInChars,
-            textLayoutResult = layoutState.layoutResult!!,
-            matrix = getAndroidMatrix(Offset(67f, 89f)),
-            innerTextFieldBounds = Rect.Zero,
-            decorationBoxBounds = Rect.Zero,
-        )
-        assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
-    }
-
-    @Test
-    fun requestCursorUpdates_immediateAndMonitor() = runTest {
-        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        backgroundScope.startFakeTextInputSession(textFieldState)
-
-        inputConnection.requestCursorUpdates(
-            InputConnection.CURSOR_UPDATE_IMMEDIATE or InputConnection.CURSOR_UPDATE_MONITOR
-        )
-
-        // Immediate update.
-        val expectedInfo = builder.build(
-            text = textFieldState.text,
-            selection = textFieldState.text.selectionInChars,
-            composition = textFieldState.text.compositionInChars,
-            textLayoutResult = layoutState.layoutResult!!,
-            matrix = getAndroidMatrix(windowOffset),
-            innerTextFieldBounds = Rect.Zero,
-            decorationBoxBounds = Rect.Zero,
-        )
-        assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
-        reportedCursorAnchorInfos.clear()
-
-        // Trigger new layout.
-        layoutState.coreNodeCoordinates =
-            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
-
-        // Monitoring update.
-        val expectedInfo2 = builder.build(
-            text = textFieldState.text,
-            selection = textFieldState.text.selectionInChars,
-            composition = textFieldState.text.compositionInChars,
-            textLayoutResult = layoutState.layoutResult!!,
-            matrix = getAndroidMatrix(Offset(67f, 89f)),
-            innerTextFieldBounds = Rect.Zero,
-            decorationBoxBounds = Rect.Zero,
-        )
-        assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo2)
-    }
-
-    @Test
-    fun requestCursorUpdates_cancel() = runTest {
-        val textFieldState = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        backgroundScope.startFakeTextInputSession(textFieldState)
-
-        inputConnection.requestCursorUpdates(
-            InputConnection.CURSOR_UPDATE_IMMEDIATE or InputConnection.CURSOR_UPDATE_MONITOR
-        )
-
-        // Immediate update.
-        val expectedInfo = builder.build(
-            text = textFieldState.text,
-            selection = textFieldState.text.selectionInChars,
-            composition = textFieldState.text.compositionInChars,
-            textLayoutResult = layoutState.layoutResult!!,
-            matrix = getAndroidMatrix(windowOffset),
-            innerTextFieldBounds = Rect.Zero,
-            decorationBoxBounds = Rect.Zero,
-        )
-        assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
-        reportedCursorAnchorInfos.clear()
-
-        // Cancel monitoring.
-        inputConnection.requestCursorUpdates(0)
-
-        // Trigger new layout.
-        layoutState.coreNodeCoordinates =
-            TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
-
-        // No update after cancel update.
-        assertThat(reportedCursorAnchorInfos).isEmpty()
-    }
-
-    private fun runTest(testBody: suspend TestScope.() -> Unit) {
-        kotlinx.coroutines.test.runTest {
-            // Bootstrap the snapshot observation system.
-            lateinit var handle: ObserverHandle
-            try {
-                handle = Snapshot.registerGlobalWriteObserver {
-                    Snapshot.sendApplyNotifications()
-                }
-                testBody()
-            } finally {
-                handle.dispose()
-            }
-        }
-    }
-
-    private fun CoroutineScope.startFakeTextInputSession(textFieldState: TextFieldState) {
-        val transformedState = TransformedTextFieldState(
-            textFieldState = textFieldState,
-            inputTransformation = null,
-            codepointTransformation = null
-        )
-
-        launch(Dispatchers.Unconfined) {
-            session.platformSpecificTextInputSession(
-                state = transformedState,
-                layoutState = layoutState,
-                imeOptions = ImeOptions.Default,
-                receiveContentConfiguration = null,
-                onImeAction = null,
-            )
-        }
-
-        layoutState.updateNonMeasureInputs(
-            textFieldState = transformedState,
-            textStyle = TextStyle.Default,
-            singleLine = false,
-            softWrap = false
-        )
-        layoutState.layoutWithNewMeasureInputs(
-            density = defaultDensity,
-            fontFamilyResolver = createFontFamilyResolver(context, coroutineContext),
-            layoutDirection = LayoutDirection.Ltr,
-            constraints = Constraints()
-        )
-    }
-
-    private fun getAndroidMatrix(offset: Offset) =
-        androidMatrix.apply { setTranslate(offset.x, offset.y) }
-
-    private open class TestLayoutCoordinates(
-        val windowOffset: Offset = Offset.Zero,
-        override var size: IntSize = IntSize.Zero,
-        override var providedAlignmentLines: Set<AlignmentLine> = emptySet(),
-        override var parentLayoutCoordinates: LayoutCoordinates? = null,
-        override var parentCoordinates: LayoutCoordinates? = null,
-        override var isAttached: Boolean = false,
-    ) : LayoutCoordinates {
-        override fun get(alignmentLine: AlignmentLine): Int = AlignmentLine.Unspecified
-        override fun windowToLocal(relativeToWindow: Offset): Offset =
-            relativeToWindow - windowOffset
-        override fun localToWindow(relativeToLocal: Offset): Offset = relativeToLocal + windowOffset
-        override fun localToRoot(relativeToLocal: Offset): Offset = relativeToLocal
-        override fun localPositionOf(
-            sourceCoordinates: LayoutCoordinates,
-            relativeToSource: Offset
-        ): Offset = relativeToSource
-
-        override fun localBoundingBoxOf(
-            sourceCoordinates: LayoutCoordinates,
-            clipBounds: Boolean
-        ): Rect = Rect.Zero
-
-        override fun transformToScreen(matrix: Matrix) {
-            matrix.translate(windowOffset.x, windowOffset.y, 0f)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownTest.kt
deleted file mode 100644
index 4ccc2b1..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownTest.kt
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.testutils.TestViewConfiguration
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.TouchInjectionScope
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.DpSize
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-private const val TargetTag = "TargetLayout"
-
-@RunWith(JUnit4::class)
-class PressDownTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    /**
-     * null; gesture detector has never seen a down event.
-     * true; at least a pointer is currently down.
-     * false; there was at least one down event but right now all pointers are up. Also, this
-     * requires onUp to be defined.
-     */
-    private var down: Boolean? = null
-
-    private val downDetector = layoutWithGestureDetector {
-        detectPressDownGesture(onDown = { down = true })
-    }
-
-    private val downUpDetector = layoutWithGestureDetector {
-        detectPressDownGesture(
-            onDown = { down = true },
-            onUp = { down = false }
-        )
-    }
-
-    private val nothingHandler: PointerInputChange.() -> Unit = {}
-
-    private var initialPass: PointerInputChange.() -> Unit = nothingHandler
-    private var finalPass: PointerInputChange.() -> Unit = nothingHandler
-
-    @Before
-    fun setup() {
-        down = null
-    }
-
-    private fun layoutWithGestureDetector(
-        gestureDetector: suspend PointerInputScope.() -> Unit,
-    ): @Composable () -> Unit = {
-        CompositionLocalProvider(
-            LocalDensity provides Density(1f),
-            LocalViewConfiguration provides TestViewConfiguration(
-                minimumTouchTargetSize = DpSize.Zero
-            )
-        ) {
-            with(LocalDensity.current) {
-                Box(
-                    Modifier
-                        .fillMaxSize()
-                        // Some tests execute a lambda before the initial and final passes
-                        // so they are called here, higher up the chain, so that the
-                        // calls happen prior to the gestureDetector below. The lambdas
-                        // do things like consume events on the initial pass or validate
-                        // consumption on the final pass.
-                        .pointerInput(Unit) {
-                            awaitPointerEventScope {
-                                while (true) {
-                                    val event = awaitPointerEvent(PointerEventPass.Initial)
-                                    event.changes.forEach {
-                                        initialPass(it)
-                                    }
-                                    awaitPointerEvent(PointerEventPass.Final)
-                                    event.changes.forEach {
-                                        finalPass(it)
-                                    }
-                                }
-                            }
-                        }
-                        .wrapContentSize(AbsoluteAlignment.TopLeft)
-                        .size(10.toDp())
-                        .pointerInput(gestureDetector, gestureDetector)
-                        .testTag(TargetTag)
-                )
-            }
-        }
-    }
-
-    private fun performTouch(
-        initialPass: PointerInputChange.() -> Unit = nothingHandler,
-        finalPass: PointerInputChange.() -> Unit = nothingHandler,
-        block: TouchInjectionScope.() -> Unit
-    ) {
-        this.initialPass = initialPass
-        this.finalPass = finalPass
-        rule.onNodeWithTag(TargetTag).performTouchInput(block)
-        rule.waitForIdle()
-        this.initialPass = nothingHandler
-        this.finalPass = nothingHandler
-    }
-
-    @Test
-    fun normalDown() {
-        rule.setContent(downUpDetector)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        Assert.assertTrue(down!!)
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        Assert.assertFalse(down!!)
-    }
-
-    @Test
-    fun normalDown_upNotSpecified() {
-        rule.setContent(downDetector)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        Assert.assertTrue(down!!)
-    }
-
-    @Test
-    fun consumedDown() {
-        rule.setContent(downUpDetector)
-
-        performTouch(initialPass = { consume() }) { down(0, Offset(5f, 5f)) }
-
-        Assert.assertTrue(down!!)
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) { up(0) }
-
-        Assert.assertFalse(down!!)
-    }
-
-    @Test
-    fun downLongPress() {
-        rule.setContent(downUpDetector)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        Assert.assertTrue(down!!)
-
-        rule.mainClock.advanceTimeBy(10_000L)
-
-        Assert.assertTrue(down!!)
-
-        rule.mainClock.advanceTimeBy(500)
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        Assert.assertFalse(down!!)
-    }
-
-    @Test
-    fun downAndMoveOut() {
-        rule.setContent(downUpDetector)
-
-        performTouch {
-            down(0, Offset(5f, 5f))
-            moveTo(0, Offset(15f, 15f))
-        }
-
-        performTouch(finalPass = { Assert.assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        Assert.assertFalse(down!!)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapTest.kt
deleted file mode 100644
index 81d65f1..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapTest.kt
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.testutils.TestViewConfiguration
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.TouchInjectionScope
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.DpSize
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-private const val TargetTag = "TargetLayout"
-
-/**
- * These tests are mostly copied from TapGestureDetectorTest.
- */
-@RunWith(JUnit4::class)
-class TapAndDoubleTapTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private var tapped = false
-    private var doubleTapped = false
-
-    /** The time before a long press gesture attempts to win. */
-    private val LongPressTimeoutMillis: Long = 500L
-
-    /**
-     * The maximum time from the start of the first tap to the start of the second
-     * tap in a double-tap gesture.
-     */
-    private val DoubleTapTimeoutMillis: Long = 300L
-
-    private val util = layoutWithGestureDetector {
-        detectTapAndDoubleTap(
-            onTap = {
-                tapped = true
-            }
-        )
-    }
-
-    private val utilWithDoubleTap = layoutWithGestureDetector {
-        detectTapAndDoubleTap(
-            onTap = {
-                tapped = true
-            },
-            onDoubleTap = {
-                doubleTapped = true
-            }
-        )
-    }
-
-    private val nothingHandler: PointerInputChange.() -> Unit = {}
-
-    private var initialPass: PointerInputChange.() -> Unit = nothingHandler
-    private var finalPass: PointerInputChange.() -> Unit = nothingHandler
-
-    @Before
-    fun setup() {
-        tapped = false
-        doubleTapped = false
-    }
-
-    private fun layoutWithGestureDetector(
-        gestureDetector: suspend PointerInputScope.() -> Unit,
-    ): @Composable () -> Unit = {
-        CompositionLocalProvider(
-            LocalDensity provides Density(1f),
-            LocalViewConfiguration provides TestViewConfiguration(
-                minimumTouchTargetSize = DpSize.Zero
-            )
-        ) {
-            with(LocalDensity.current) {
-                Box(
-                    Modifier
-                        .fillMaxSize()
-                        // Some tests execute a lambda before the initial and final passes
-                        // so they are called here, higher up the chain, so that the
-                        // calls happen prior to the gestureDetector below. The lambdas
-                        // do things like consume events on the initial pass or validate
-                        // consumption on the final pass.
-                        .pointerInput(Unit) {
-                            awaitPointerEventScope {
-                                while (true) {
-                                    val event = awaitPointerEvent(PointerEventPass.Initial)
-                                    event.changes.forEach {
-                                        initialPass(it)
-                                    }
-                                    awaitPointerEvent(PointerEventPass.Final)
-                                    event.changes.forEach {
-                                        finalPass(it)
-                                    }
-                                }
-                            }
-                        }
-                        .wrapContentSize(AbsoluteAlignment.TopLeft)
-                        .size(10.toDp())
-                        .pointerInput(gestureDetector, gestureDetector)
-                        .testTag(TargetTag)
-                )
-            }
-        }
-    }
-
-    private fun performTouch(
-        initialPass: PointerInputChange.() -> Unit = nothingHandler,
-        finalPass: PointerInputChange.() -> Unit = nothingHandler,
-        block: TouchInjectionScope.() -> Unit
-    ) {
-        this.initialPass = initialPass
-        this.finalPass = finalPass
-        rule.onNodeWithTag(TargetTag).performTouchInput(block)
-        rule.waitForIdle()
-        this.initialPass = nothingHandler
-        this.finalPass = nothingHandler
-    }
-
-    @Test
-    fun normalTap() {
-        rule.setContent(util)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        assertFalse(tapped)
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            up(0)
-        }
-
-        assertTrue(tapped)
-    }
-
-    @Test
-    fun normalTapWithDoubleTapDefined_butNotExecuted() {
-        rule.setContent(utilWithDoubleTap)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            up(0)
-        }
-
-        // we don't wait for double tap, tap should be called immediately.
-        assertTrue(tapped)
-        assertFalse(doubleTapped)
-
-        rule.mainClock.advanceTimeBy(DoubleTapTimeoutMillis + 10)
-
-        assertTrue(tapped)
-        assertFalse(doubleTapped)
-    }
-
-    @Test
-    fun normalDoubleTap() {
-        rule.setContent(utilWithDoubleTap)
-
-        performTouch { down(0, Offset(5f, 5f)) }
-        performTouch(finalPass = { assertTrue(isConsumed) }) { up(0) }
-
-        assertTrue(tapped)
-        assertFalse(doubleTapped)
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch { down(0, Offset(5f, 5f)) }
-        performTouch(finalPass = { assertTrue(isConsumed) }) { up(0) }
-
-        assertTrue(tapped)
-        assertTrue(doubleTapped)
-    }
-
-    @Test
-    fun normalLongPress() {
-        rule.setContent(utilWithDoubleTap)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-        }
-
-        assertFalse(tapped)
-        assertFalse(doubleTapped)
-
-        rule.mainClock.advanceTimeBy(LongPressTimeoutMillis + 10)
-
-        assertFalse(tapped)
-        assertFalse(doubleTapped)
-
-        rule.mainClock.advanceTimeBy(500)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-        assertFalse(doubleTapped)
-    }
-
-    /**
-     * Pressing in the region, sliding out and then lifting should result in
-     * the callback not being invoked
-     */
-    @Test
-    fun tapMiss() {
-        rule.setContent(util)
-
-        performTouch {
-            down(0, Offset(5f, 5f))
-            moveTo(0, Offset(15f, 15f))
-        }
-
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-    }
-
-    /**
-     * Pressing in the region, sliding out and then lifting should result in
-     * the callback not being invoked
-     */
-    @Test
-    fun longPressMiss() {
-        rule.setContent(utilWithDoubleTap)
-
-        performTouch {
-            down(0, Offset(5f, 5f))
-            moveTo(0, Offset(15f, 15f))
-        }
-
-        rule.mainClock.advanceTimeBy(LongPressTimeoutMillis + 10)
-
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-        assertFalse(doubleTapped)
-    }
-
-    /**
-     * Pressing in the region, sliding out and then lifting should result in
-     * the callback not being invoked for double-tap
-     */
-    @Test
-    fun doubleTapMiss() {
-        rule.setContent(utilWithDoubleTap)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-            up(0)
-        }
-
-        assertTrue(tapped)
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch {
-            down(1, Offset(5f, 5f))
-            moveTo(1, Offset(15f, 15f))
-        }
-
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(1)
-        }
-
-        assertTrue(tapped)
-        assertFalse(doubleTapped)
-    }
-
-    /**
-     * After a first tap, a second tap should also be detected.
-     */
-    @Test
-    fun secondTap() {
-        rule.setContent(util)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(5f, 5f))
-            up(0)
-        }
-
-        assertTrue(tapped)
-
-        tapped = false
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(1, Offset(4f, 4f))
-            up(1)
-        }
-
-        assertTrue(tapped)
-    }
-
-    /**
-     * Clicking in the region with the up already consumed should result in the callback not
-     * being invoked.
-     */
-    @Test
-    fun consumedUpTap() {
-        rule.setContent(util)
-
-        performTouch {
-            down(0, Offset(5f, 5f))
-        }
-
-        assertFalse(tapped)
-
-        performTouch(initialPass = { if (pressed != previousPressed) consume() }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-    }
-
-    /**
-     * Clicking in the region with the motion consumed should result in the callback not
-     * being invoked.
-     */
-    @Test
-    fun consumedMotionTap() {
-        rule.setContent(util)
-
-        performTouch {
-            down(0, Offset(5f, 5f))
-        }
-
-        performTouch(initialPass = { consume() }) {
-            moveTo(0, Offset(6f, 2f))
-        }
-
-        rule.mainClock.advanceTimeBy(50)
-
-        performTouch {
-            up(0)
-        }
-
-        assertFalse(tapped)
-    }
-
-    /**
-     * Ensure that two-finger taps work.
-     */
-    @Test
-    fun twoFingerTap() {
-        rule.setContent(util)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            down(0, Offset(1f, 1f))
-        }
-
-        assertFalse(tapped)
-
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            down(1, Offset(9f, 5f))
-        }
-
-        assertFalse(tapped)
-
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-
-        performTouch(finalPass = { assertTrue(isConsumed) }) {
-            up(1)
-        }
-
-        assertTrue(tapped)
-    }
-
-    /**
-     * A position change consumption on any finger should cause tap to cancel.
-     */
-    @Test
-    fun twoFingerTapCancel() {
-        rule.setContent(util)
-
-        performTouch {
-            down(0, Offset(1f, 1f))
-        }
-        assertFalse(tapped)
-
-        performTouch {
-            down(1, Offset(9f, 5f))
-        }
-
-        performTouch(initialPass = { consume() }) {
-            moveTo(0, Offset(5f, 5f))
-        }
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(0)
-        }
-
-        assertFalse(tapped)
-
-        rule.mainClock.advanceTimeBy(50)
-        performTouch(finalPass = { assertFalse(isConsumed) }) {
-            up(1)
-        }
-
-        assertFalse(tapped)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldClickToMoveCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldClickToMoveCursorTest.kt
deleted file mode 100644
index 9b60c857..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldClickToMoveCursorTest.kt
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-class TextFieldClickToMoveCursorTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var state: TextFieldState
-
-    private val TAG = "BasicTextField2"
-
-    private val fontSize = 10.sp
-
-    private val defaultTextStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = fontSize)
-
-    @Test
-    fun emptyTextField_clinkOnCornersAndCenter() {
-        // this test is more about detecting a possible crash
-        state = TextFieldState()
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(50.dp)
-                    .height(15.dp)
-            )
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(center) }
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            performTouchInput { click(Offset(left + 1f, top + 1f)) } // topLeft
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            performTouchInput { click(Offset(right - 1f, top + 1f)) } // topRight
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            performTouchInput { click(Offset(left + 1f, bottom - 1f)) } // bottomLeft
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-            performTouchInput { click(Offset(right - 1f, bottom - 1f)) } // bottomRight
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-        }
-    }
-
-    @Test
-    fun clickOnText_ltr() {
-        state = TextFieldState("abc")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(50.dp)
-                    .height(15.dp)
-            )
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun clickOnText_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = defaultTextStyle,
-                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
-                )
-            }
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun clickOnText_ltr_in_rtlLayout() {
-        state = TextFieldState("abc")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = defaultTextStyle,
-                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
-                )
-            }
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-    }
-
-    @Test
-    fun clickOnText_rtl_in_ltrLayout() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
-            )
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-    }
-
-    @Test
-    fun clickOnEmptyRegion_ltr() {
-        state = TextFieldState("abc")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
-            )
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset((fontSize * 4).toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-    }
-
-    @Test
-    fun clickOnEmptyRegion_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = defaultTextStyle,
-                    modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
-                )
-            }
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset(fontSize.toPx(), height / 2f)) }
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-    }
-
-    @Test
-    fun clickOnTextEdge_horizontalScrollable() {
-        state = TextFieldState("abcabcabcabc")
-        val scrollState = ScrollState(0)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(57.dp)
-                    .height(12.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(right - 1f, height / 2f))
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(6))
-            assertThat(scrollState.value).isGreaterThan(0)
-        }
-    }
-
-    @Test
-    fun clickOnTextEdge_verticalScrollable() {
-        state = TextFieldState("abc abc abc abc")
-        val scrollState = ScrollState(0)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(50.dp)
-                    .height(17.dp)
-            )
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performTouchInput { click(Offset(fontSize.toPx(), bottom - 1f)) }
-        }
-        rule.waitForIdle()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-        assertThat(scrollState.value).isGreaterThan(0)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldCursorHandleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldCursorHandleTest.kt
deleted file mode 100644
index f112b39..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldCursorHandleTest.kt
+++ /dev/null
@@ -1,995 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.DefaultCursorThickness
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.assertHandlePositionMatches
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.setTextAndPlaceCursorAtEnd
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.swipeDown
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.unit.times
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-class TextFieldCursorHandleTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var state: TextFieldState
-
-    private val TAG = "BasicTextField2"
-
-    private val fontSize = 10.sp
-
-    private val fontSizePx = with(rule.density) { fontSize.toPx() }
-
-    private val fontSizeDp = with(rule.density) { fontSize.toDp() }
-
-    private val cursorWidth = DefaultCursorThickness
-
-    @Test
-    fun cursorHandle_showsAtCorrectLocation_ltr() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            (2 * fontSize.value).dp + cursorWidth / 2,
-            fontSize.value.dp
-        )
-    }
-
-    @Test
-    fun cursorHandle_hasMinimumTouchSizeArea() = with(rule.density) {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.width(100.dp).testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-
-        var actualBottomRight = Offset.Zero
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
-            actualBottomRight = bottomRight
-        }
-
-        val expectedBottomRight = Offset(40.dp.toPx(), 40.dp.toPx())
-        assertThat(actualBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
-        assertThat(actualBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
-    }
-
-    @Test
-    fun tapTextField_cursorHandleFiltered() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                inputTransformation = { _, valueWithChanges ->
-                    valueWithChanges.selectCharsIn(TextRange(4))
-                },
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-    }
-
-    @Test
-    fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_ltr() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            5 * fontSizeDp + cursorWidth / 2,
-            fontSizeDp
-        )
-    }
-
-    @Test
-    fun cursorHandle_showsAtCorrectLocation_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2\u05D3\u05D4")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSize.value.dp * 5)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            (2 * fontSize.value).dp + cursorWidth / 2,
-            fontSize.value.dp
-        )
-    }
-
-    @Test
-    fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_rtl() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            (5 * fontSize.value).dp + cursorWidth / 2,
-            fontSize.value.dp
-        )
-    }
-
-    @Test
-    fun cursorHandle_notVisibleOnEmptyField() {
-        state = TextFieldState()
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_doesNotShow_whenTextFieldIsReadOnly() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG),
-                readOnly = true
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_disappears_whenTextIsEdited() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        rule.onNodeWithTag(TAG).performTextInput("m")
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_disappears_whenTextStateChanges() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        state.setTextAndPlaceCursorAtEnd("hello2")
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_doesNotDisappear_whenSelectionChanges() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        state.edit { placeCursorBeforeCharAt(2) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            (2 * fontSize.value).dp + cursorWidth / 2,
-            fontSize.value.dp
-        )
-    }
-
-    @Test
-    fun cursorHandle_disappears_whenWindowLosesFocus() {
-        state = TextFieldState("hello")
-        val focusWindow = mutableStateOf(true)
-        val windowInfo = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focusWindow.value
-        }
-        rule.setContent {
-            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier.testTag(TAG)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_coercesAtBoundaries_ltr() {
-        state = TextFieldState("hello")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    // Make this TextField guaranteed to be wider than the text content
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick() // cursor handle appears
-        rule.runOnIdle {
-            state.edit { selectCharsIn(TextRange(0)) } // move cursor to the start of text
-        }
-
-        val characterSize = fontSizeDp // width and height is the same.
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            cursorWidth / 2,
-            characterSize
-        )
-
-        rule.runOnIdle {
-            state.edit { selectCharsIn(TextRange(5)) } // move cursor to the end of text
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            // Move 5 characters to right (5 * character), finally account for the center of
-            // cursor (cursorWidth / 2).
-            5 * characterSize + cursorWidth / 2,
-            fontSizeDp
-        )
-    }
-
-    @Test
-    fun cursorHandle_coercesAtBoundaries_rtl() = with(rule.density) {
-        state = TextFieldState("\u05D0\u05D1\u05D2\u05D3\u05D4")
-        var width = 0
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        // Make this TextField guaranteed to be wider than the text content
-                        .width(fontSizeDp * 10)
-                        .onSizeChanged { width = it.width }
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick() // cursor handle appears
-
-        rule.runOnIdle {
-            state.edit { selectCharsIn(TextRange(0)) } // move cursor to the start of text
-        }
-
-        val characterSize = fontSizeDp // width and height is the same.
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            width.toDp() - cursorWidth / 2, // Should align to the right
-            characterSize
-        )
-
-        rule.runOnIdle {
-            state.edit { selectCharsIn(TextRange(5)) } // move cursor to the end of text
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
-            // Start from right (width), move 5 characters to left (5 * character), finally account
-            // for the center of cursor (cursorWidth / 2).
-            width.toDp() - 5 * characterSize - cursorWidth / 2,
-            fontSizeDp
-        )
-    }
-
-    @Test
-    fun cursorHandle_disappearsOnVerticalScroll() {
-        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
-        val scrollState = ScrollState(0)
-        lateinit var scope: CoroutineScope
-        rule.setTextFieldTestContent {
-            scope = rememberCoroutineScope()
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                // scrollable but still only show maximum one line in its viewport
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 1),
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 5)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        scope.runBlockingOnIdle {
-            scrollState.scrollTo(scrollState.maxValue)
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_disappearsOnHorizontalScroll() {
-        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
-        val scrollState = ScrollState(0)
-        lateinit var scope: CoroutineScope
-        rule.setTextFieldTestContent {
-            scope = rememberCoroutineScope()
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                // scrollable but still only show maximum one line in its viewport
-                lineLimits = TextFieldLineLimits.SingleLine,
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        scope.runBlockingOnIdle {
-            scrollState.scrollTo(scrollState.maxValue)
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun cursorHandle_reappearsOnVerticalScroll() {
-        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
-        val scrollState = ScrollState(0)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                // scrollable but still only show maximum one line in its viewport
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 1),
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 5)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            swipeUp(endY = -bottom)
-        }
-        rule.waitForIdle()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            swipeDown(endY = 2 * bottom)
-        }
-        rule.waitForIdle()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-    }
-
-    @Test
-    fun cursorHandle_reappearsOnHorizontalScroll() {
-        state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
-        val scrollState = ScrollState(0)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                // scrollable but still only show maximum one line in its viewport
-                lineLimits = TextFieldLineLimits.SingleLine,
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 5)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performClick()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft(endX = -right) }
-        rule.waitForIdle()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeRight(endX = 2 * right) }
-        rule.waitForIdle()
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-    }
-
-    @Test
-    fun cursorHandleDrag_getsFiltered() {
-        state = TextFieldState("abc abc")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                inputTransformation = { _, valueWithChanges ->
-                    valueWithChanges.selectCharsIn(TextRange.Zero)
-                },
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(fontSizePx * 5)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
-    }
-
-    // region ltr drag tests
-    @Test
-    fun moveCursorHandleToRight_ltr() {
-        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(fontSizePx)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_ltr() {
-        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(fontSizePx)
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        }
-    }
-
-    @Test
-    fun moveCursorHandleToRight_ltr_outOfBounds() {
-        state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 5)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(getTextFieldWidth() * 2)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_ltr_outOfBounds() {
-        state = TextFieldState("abc", initialSelectionInChars = TextRange(3))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 5)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(getTextFieldWidth() * 2)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
-    }
-
-    @Test
-    fun moveCursorHandleToRight_ltr_outOfBounds_scrollable_continuesDrag() {
-        state = TextFieldState(
-            initialText = "abcd abcd abcd abcd abcd",
-            initialSelectionInChars = TextRange.Zero
-        )
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(getTextFieldWidth() * 3)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
-    }
-
-    @Test
-    fun moveCursorHandleToRight_ltr_outOfBounds_scrollable() {
-        state = TextFieldState(
-            initialText = "abcd abcd abcd abcd abcd",
-            initialSelectionInChars = TextRange.Zero
-        )
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(fontSizeDp * 10)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(fontSizePx * 12, durationMillis = 1)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(12))
-        }
-
-        swipeToRight(fontSizePx * 2, durationMillis = 1)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(14))
-        }
-    }
-
-    // endregion
-
-    // region rtl drag tests
-    @Test
-    fun moveCursorHandleToRight_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 10)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(fontSizePx)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_rtl() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 10)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(fontSizePx)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-    }
-
-    @Test
-    fun moveCursorHandleToRight_rtl_outOfBounds() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 5)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToRight(getTextFieldWidth() * 2)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_rtl_outOfBounds() {
-        state = TextFieldState("\u05D0\u05D1\u05D2")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 5)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(getTextFieldWidth() * 2)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_rtl_outOfBounds_scrollable_continuesDrag() {
-        state = TextFieldState(
-            "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3"
-        )
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    lineLimits = TextFieldLineLimits.SingleLine,
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 10)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight - Offset(1f, 1f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(getTextFieldWidth() * 3)
-        rule.waitForIdle()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(state.text.length))
-    }
-
-    @Test
-    fun moveCursorHandleToLeft_rtl_outOfBounds_scrollable() {
-        val scrollState = ScrollState(0)
-        state = TextFieldState(
-            initialText = "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3 " +
-                "\u05D0\u05D1\u05D2\u05D3",
-            initialSelectionInChars = TextRange.Zero
-        )
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    lineLimits = TextFieldLineLimits.SingleLine,
-                    scrollState = scrollState,
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(fontSizeDp * 10f)
-                )
-            }
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(topRight) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-        swipeToLeft(fontSizePx * 12, durationMillis = 1)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(12))
-        }
-
-        swipeToLeft(fontSizePx * 2, durationMillis = 1)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(14))
-        }
-    }
-
-    // endregion
-
-    private fun focusAndWait() {
-        rule.onNode(hasSetTextAction()).requestFocus()
-    }
-
-    private fun swipeToLeft(swipeDistance: Float, durationMillis: Long = 1000) =
-        performHandleDrag(Handle.Cursor, true, swipeDistance, durationMillis)
-
-    private fun swipeToRight(swipeDistance: Float, durationMillis: Long = 1000) =
-        performHandleDrag(Handle.Cursor, false, swipeDistance, durationMillis)
-
-    private fun performHandleDrag(
-        handle: Handle,
-        toLeft: Boolean,
-        swipeDistance: Float = 1f,
-        durationMillis: Long = 1000
-    ) {
-        val handleNode = rule.onNode(isSelectionHandle(handle))
-
-        handleNode.performTouchInput {
-            if (toLeft) {
-                swipeLeft(
-                    startX = centerX,
-                    endX = centerX - viewConfiguration.touchSlop - swipeDistance,
-                    durationMillis = durationMillis
-                )
-            } else {
-                swipeRight(
-                    startX = centerX,
-                    endX = centerX + viewConfiguration.touchSlop + swipeDistance,
-                    durationMillis = durationMillis
-                )
-            }
-        }
-    }
-
-    private fun getTextFieldWidth() = rule.onNodeWithTag(TAG)
-        .fetchSemanticsNode()
-        .boundsInRoot.width
-
-    private fun CoroutineScope.runBlockingOnIdle(block: suspend CoroutineScope.() -> Unit) {
-        val job = rule.runOnIdle {
-            launch(block = block)
-        }
-        runBlocking { job.join() }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldLongPressTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldLongPressTest.kt
deleted file mode 100644
index 3c09b3d..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldLongPressTest.kt
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.FakeTextToolbar
-import androidx.compose.foundation.text.selection.gestures.util.longPress
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.rememberTextFieldState
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * Tests for long click interactions on BasicTextField2.
- */
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-class TextFieldLongPressTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val TAG = "BasicTextField2"
-
-    private val fontSize = 10.sp
-
-    private val defaultTextStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = fontSize)
-
-    @Test
-    fun emptyTextField_longPressDoesNotShowCursor() {
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = rememberTextFieldState(),
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { longClick() }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-    }
-
-    @Test
-    fun longPress_requestsFocus_beforePointerIsReleased() {
-        val state = TextFieldState("Hello, World!")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(center)
-        }
-
-        rule.onNodeWithTag(TAG).assertIsFocused()
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-    }
-
-    @Test
-    fun longPressOnEmptyRegion_showsCursorAtTheEnd() {
-        val state = TextFieldState("abc")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-    }
-
-    @Test
-    fun longPressOnEmptyRegion_showsTextToolbar() {
-        val state = TextFieldState("abc")
-        var showMenuCalled = 0
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, _, _, _ ->
-                showMenuCalled++
-            }, onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager("hello")
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(
-                LocalTextToolbar provides textToolbar,
-                LocalClipboardManager provides clipboardManager
-            ) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = defaultTextStyle,
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(100.dp)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
-        }
-
-        rule.runOnIdle {
-            assertThat(showMenuCalled).isEqualTo(1)
-        }
-    }
-
-    @Test
-    fun longPressOnWord_selectsWord() {
-        val state = TextFieldState("abc def ghi")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
-    }
-
-    @Test
-    fun longPressOnWhitespace_doesNotSelectWhitespace() {
-        val state = TextFieldState("abc def ghi")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(Offset(fontSize.toPx() * 7.5f, fontSize.toPx() / 2))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7, 8))
-        assertThat(state.text.selectionInChars.collapsed).isFalse()
-    }
-
-    @Test
-    fun longPressOnScrolledTextField_selectsWord() {
-        val state = TextFieldState("abc def ghi abc def ghi")
-        val scrollState = ScrollState(0)
-        lateinit var scope: CoroutineScope
-        rule.setTextFieldTestContent {
-            scope = rememberCoroutineScope()
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                scrollState = scrollState,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(30.dp)
-            )
-        }
-
-        assertThat(scrollState.maxValue).isGreaterThan(0)
-        scope.launch { scrollState.scrollTo(scrollState.maxValue) }
-
-        rule.onNodeWithTag(TAG).performTouchInput { longClick(centerRight) }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(20, 23))
-    }
-
-    @Test
-    fun longPressOnDecoratedTextField_selectsWord() {
-        val state = TextFieldState("abc def ghi")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG),
-                decorator = {
-                    Box(modifier = Modifier.padding(32.dp)) {
-                        it()
-                    }
-                }
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(
-                Offset(
-                    x = 32.dp.toPx() + fontSize.toPx() * 5f,
-                    y = 32.dp.toPx() + fontSize.toPx() / 2
-                )
-            )
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
-    }
-
-    @Test
-    fun longPress_dragToRight_selectsCurrentAndNextWord_ltr() {
-        val state = TextFieldState("abc def ghi")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(fontSize.toPx() * 3f, 0f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
-    }
-
-    @Test
-    fun longPress_dragToLeft_selectsCurrentAndPreviousWord_ltr() {
-        val state = TextFieldState("abc def ghi")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(-fontSize.toPx() * 3f, 0f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
-    }
-
-    @Test
-    fun longPress_dragDown_selectsFromCurrentToTargetWord_ltr() {
-        val state = TextFieldState("abc def\nabc def\nabc def")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(0f, fontSize.toPx()))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 15))
-    }
-
-    @Test
-    fun longPress_dragUp_selectsFromCurrentToTargetWord_ltr() {
-        val state = TextFieldState("abc def\nabc def\nabc def")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() * 3 / 2)) // second line, def
-            moveBy(Offset(0f, -fontSize.toPx()))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 15))
-    }
-
-    @Test
-    fun longPress_startingFromEndPadding_dragToLeft_selectsLastWord_ltr() {
-        val state = TextFieldState("abc def")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(centerRight)
-            moveTo(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
-    }
-
-    //region RTL
-
-    @Test
-    fun longPress_dragToRight_selectsCurrentAndPreviousWord_rtl() {
-        val state = TextFieldState(rtlText3)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(fontSize.toPx() * 3f, 0f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
-    }
-
-    @Test
-    fun longPress_dragToLeft_selectsCurrentAndNextWord_rtl() {
-        val state = TextFieldState(rtlText3)
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(-fontSize.toPx() * 3f, 0f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
-    }
-
-    @Test
-    fun longPress_dragDown_selectsFromCurrentToTargetWord_rtl() {
-        val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() / 2))
-            moveBy(Offset(0f, fontSize.toPx()))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 11))
-    }
-
-    @Test
-    fun longPress_dragUp_selectsFromCurrentToTargetWord_rtl() {
-        val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                modifier = Modifier.testTag(TAG)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx() * 5f, fontSize.toPx() * 3 / 2))
-            moveBy(Offset(0f, -fontSize.toPx()))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 11))
-    }
-
-    @Test
-    fun longPress_startingFromEndPadding_dragToRight_selectsLastWord_rtl() {
-        val state = TextFieldState(rtlText2)
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = defaultTextStyle,
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(100.dp)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(centerLeft)
-            moveTo(Offset(fontSize.toPx() * 5, fontSize.toPx() / 2f))
-            up()
-        }
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
-    }
-
-    @Test
-    fun longPress_startDraggingToScrollRight_startHandleDoesNotShow_ltr() {
-        val state = TextFieldState("abc def ghi ".repeat(10))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(center)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx(), fontSize.toPx() / 2))
-            moveBy(Offset(fontSize.toPx() * 30, 0f))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-
-        // slightly back a little bit so that selection seems to be collapsing but the acting
-        // handle should remain the same
-        rule.onNodeWithTag(TAG).performTouchInput {
-            moveBy(Offset(-fontSize.toPx(), 0f))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-    }
-
-    @Test
-    fun longPress_startDraggingToScrollDown_startHandleDoesNotShow_ltr() {
-        val state = TextFieldState("abc def ghi ".repeat(10))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.MultiLine(1, 3),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(center)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(fontSize.toPx(), fontSize.toPx() / 2))
-            moveBy(Offset(0f, fontSize.toPx() * 30))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-
-        // slightly back a little bit so that selection seems to be collapsing but the acting
-        // handle should remain the same
-        rule.onNodeWithTag(TAG).performTouchInput {
-            moveBy(Offset(0f, -fontSize.toPx()))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-    }
-
-    @Test
-    fun longPress_startDraggingToScrollLeft_endHandleDoesNotShow_ltr() {
-        val state = TextFieldState("abc def ghi ".repeat(10))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(center)
-            // swipe to the absolute right by specifying a huge swipe delta
-            swipeLeft(endX = -10000f)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(right - 1f, centerY))
-            moveBy(Offset(-fontSize.toPx() * 30, 0f))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
-
-        // slightly back a little bit so that selection seems to be collapsing but the acting
-        // handle should remain the same
-        rule.onNodeWithTag(TAG).performTouchInput {
-            moveBy(Offset(fontSize.toPx(), 0f))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
-    }
-
-    @Test
-    fun longPress_startDraggingToScrollUp_endHandleDoesNotShow_ltr() {
-        val state = TextFieldState("abc def ghi ".repeat(10))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state = state,
-                textStyle = defaultTextStyle,
-                lineLimits = TextFieldLineLimits.MultiLine(1, 3),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            click(center)
-            // swipe to the absolute bottom by specifying a huge swipe delta
-            swipeUp(endY = -10000f)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(centerX, bottom - 1f))
-            moveBy(Offset(0f, -fontSize.toPx() * 30))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
-
-        // slightly back a little bit so that selection seems to be collapsing but the acting
-        // handle should remain the same
-        rule.onNodeWithTag(TAG).performTouchInput {
-            moveBy(Offset(0f, fontSize.toPx()))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
-    }
-
-    //endregion
-
-    companion object {
-        private const val rtlText2 = "\u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5"
-        private const val rtlText3 = "\u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5 \u05D6\u05D7\u05D8"
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifierTest.kt
deleted file mode 100644
index 00c788f..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifierTest.kt
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright 2021 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.compose.foundation.text2.input.internal.selection
-
-import android.view.DragEvent
-import android.view.View
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.AbstractSelectionMagnifierTests
-import androidx.compose.foundation.text.selection.assertMagnifierExists
-import androidx.compose.foundation.text.selection.assertNoMagnifierExists
-import androidx.compose.foundation.text.selection.getMagnifierCenterOffset
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.DragAndDropTestUtils.makeImageDragEvent
-import androidx.compose.foundation.text2.input.internal.DragAndDropTestUtils.makeTextDragEvent
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.unit.toSize
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class)
-@MediumTest
-@SdkSuppress(minSdkVersion = 28)
-@RunWith(AndroidJUnit4::class)
-internal class TextFieldMagnifierTest : AbstractSelectionMagnifierTests() {
-
-    @Composable
-    override fun TestContent(
-        text: String,
-        modifier: Modifier,
-        style: TextStyle,
-        onTextLayout: (TextLayoutResult) -> Unit,
-        maxLines: Int
-    ) {
-        val state = remember { TextFieldState(text) }
-        BasicTextField2(
-            state = state,
-            modifier = modifier,
-            textStyle = style,
-            onTextLayout = { it()?.let(onTextLayout) }
-        )
-    }
-
-    @Test
-    fun magnifier_followsCursorHorizontally_whenDragged() {
-        checkMagnifierFollowsHandleHorizontally(Handle.Cursor)
-    }
-
-    @Test
-    fun magnifier_staysAtLineEnd_whenCursorDraggedPastStart() {
-        checkMagnifierConstrainedToLineHorizontalBounds(
-            Handle.Cursor,
-            checkStart = true
-        )
-    }
-
-    @Test
-    fun magnifier_staysAtLineEnd_whenCursorDraggedPastEnd() {
-        checkMagnifierConstrainedToLineHorizontalBounds(
-            Handle.Cursor,
-            checkStart = false
-        )
-    }
-
-    @Test
-    fun magnifier_hidden_whenCursorDraggedFarPastStartOfLine() {
-        checkMagnifierHiddenWhenDraggedTooFar(Handle.Cursor, checkStart = true)
-    }
-
-    @Test
-    fun magnifier_hidden_whenCursorDraggedFarPastEndOfLine() {
-        checkMagnifierHiddenWhenDraggedTooFar(Handle.Cursor, checkStart = false)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenCursorDraggedPastScrollThreshold_Ltr() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.Cursor, LayoutDirection.Ltr)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenCursorDraggedPastScrollThreshold_Rtl() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.Cursor, LayoutDirection.Rtl)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenSelectionStartDraggedPastScrollThreshold_Ltr() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionStart, LayoutDirection.Ltr)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenSelectionStartDraggedPastScrollThreshold_Rtl() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionStart, LayoutDirection.Rtl)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenSelectionEndDraggedPastScrollThreshold_Ltr() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionEnd, LayoutDirection.Ltr)
-    }
-
-    @Test
-    fun magnifier_staysAtVisibleRegion_whenSelectionEndDraggedPastScrollThreshold_Rtl() {
-        checkMagnifierStayAtEndWhenDraggedBeyondScroll(Handle.SelectionEnd, LayoutDirection.Rtl)
-    }
-
-    @Test
-    fun magnifier_shows_whenTextIsDraggingFromAnotherApp() {
-        val view = setupDragAndDropContent()
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(40f, 10f)
-            )
-
-            view.dispatchDragEvent(startEvent)
-            view.dispatchDragEvent(enterEvent)
-            view.dispatchDragEvent(moveEvent)
-        }
-
-        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(Offset(40f, 10f))
-    }
-
-    @Test
-    fun magnifier_doesNotShow_ifDraggingItem_doesNotHaveText() {
-        val view = setupDragAndDropContent()
-
-        rule.runOnIdle {
-            val startEvent = makeImageDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeImageDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeImageDragEvent(
-                DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(40f, 10f)
-            )
-
-            view.dispatchDragEvent(startEvent)
-            view.dispatchDragEvent(enterEvent)
-            view.dispatchDragEvent(moveEvent)
-        }
-
-        assertNoMagnifierExists(rule)
-    }
-
-    @Test
-    fun magnifier_doesNotLinger_whenDraggingItemLeaves() {
-        val view = setupDragAndDropContent()
-
-        rule.runOnIdle {
-            val startEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_STARTED)
-            val enterEvent = makeTextDragEvent(DragEvent.ACTION_DRAG_ENTERED)
-            val moveEvent = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(40f, 10f)
-            )
-
-            view.dispatchDragEvent(startEvent)
-            view.dispatchDragEvent(enterEvent)
-            view.dispatchDragEvent(moveEvent)
-        }
-
-        assertMagnifierExists(rule)
-
-        rule.runOnIdle {
-            val moveEvent2 = makeTextDragEvent(
-                action = DragEvent.ACTION_DRAG_LOCATION,
-                offset = Offset(40f, 40f) // force it out of BTF2's hit box
-            )
-            view.dispatchDragEvent(moveEvent2)
-        }
-
-        assertNoMagnifierExists(rule)
-    }
-
-    @Test
-    fun magnifier_insideDecorationBox() {
-        val tag = "BasicTextField2"
-        val state = TextFieldState(
-            "aaaa",
-            initialSelectionInChars = TextRange.Zero
-        )
-
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
-                BasicTextField2(
-                    state = state,
-                    Modifier.testTag(tag),
-                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                    lineLimits = TextFieldLineLimits.SingleLine,
-                    decorator = {
-                        Box(modifier = Modifier.padding(8.dp)) {
-                            it()
-                        }
-                    }
-                )
-            }
-        }
-
-        rule.onNodeWithTag(tag).performTouchInput {
-            click(topLeft)
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
-            down(center)
-            movePastSlopBy(Offset(-0.1f, 0.1f))
-        }
-
-        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
-            Offset(0f, 10f) + Offset(8f, 8f)
-        )
-    }
-
-    @Test
-    fun magnifier_insideDecorationBox_scrolledVertically() {
-        val tag = "BasicTextField2"
-        val state = TextFieldState(
-            "aaaa\naaaa\naaaa\n".repeat(5),
-            initialSelectionInChars = TextRange.Zero
-        )
-        val scrollState = ScrollState(0)
-        var coroutineScope: CoroutineScope? = null
-
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
-                coroutineScope = rememberCoroutineScope()
-                BasicTextField2(
-                    state = state,
-                    Modifier.testTag(tag),
-                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                    lineLimits = TextFieldLineLimits.MultiLine(1, 2),
-                    scrollState = scrollState,
-                    decorator = {
-                        Box(modifier = Modifier.padding(8.dp)) {
-                            it()
-                        }
-                    }
-                )
-            }
-        }
-
-        rule.waitForIdle()
-        coroutineScope?.launch {
-            scrollState.scrollTo(scrollState.maxValue)
-        }
-
-        rule.onNodeWithTag(tag).performTouchInput {
-            click(bottomLeft)
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
-            down(center)
-            movePastSlopBy(Offset(0.1f, 0.1f))
-        }
-
-        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
-            Offset(0f, 30f) + Offset(8f, 8f)
-        )
-    }
-
-    @Test
-    fun magnifier_insideDecorationBox_scrolledHorizontally() {
-        val tag = "BasicTextField2"
-        val state = TextFieldState(
-            "aaaa aaaa aaaa ".repeat(5),
-            initialSelectionInChars = TextRange.Zero
-        )
-        val scrollState = ScrollState(0)
-        var coroutineScope: CoroutineScope? = null
-
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
-                coroutineScope = rememberCoroutineScope()
-                BasicTextField2(
-                    state = state,
-                    Modifier.testTag(tag).width(100.dp),
-                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                    lineLimits = TextFieldLineLimits.SingleLine,
-                    scrollState = scrollState,
-                    decorator = {
-                        Box(modifier = Modifier.padding(8.dp)) {
-                            it()
-                        }
-                    }
-                )
-            }
-        }
-
-        rule.waitForIdle()
-        coroutineScope?.launch {
-            scrollState.scrollTo(scrollState.maxValue)
-        }
-
-        rule.onNodeWithTag(tag).performTouchInput {
-            click(centerRight)
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
-            down(center)
-            movePastSlopBy(Offset(0.1f, 0.1f))
-        }
-
-        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
-            // x: drag threshold, y: line center(2nd line in view) + x: padding, y: padding
-            Offset(100f - 16f, 10f) + Offset(8f, 8f)
-        )
-    }
-
-    @OptIn(ExperimentalTestApi::class, ExperimentalFoundationApi::class)
-    private fun checkMagnifierStayAtEndWhenDraggedBeyondScroll(
-        handle: Handle,
-        layoutDirection: LayoutDirection = LayoutDirection.Ltr
-    ) {
-        var screenSize = Size.Zero
-        val dragDirection = if (layoutDirection == LayoutDirection.Rtl) -1f else 1f
-        val directionVector = Offset(1f, 0f) * dragDirection
-        val fillerWord = if (layoutDirection == LayoutDirection.Ltr)
-            "aaaa"
-        else
-            "\u05D0\u05D1\u05D2\u05D3"
-
-        val tag = "BasicTextField2"
-        val state = TextFieldState(
-            "$fillerWord $fillerWord $fillerWord ".repeat(10),
-            initialSelectionInChars = TextRange.Zero
-        )
-
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
-                BasicTextField2(
-                    state = state,
-                    Modifier
-                        .fillMaxWidth()
-                        .onSizeChanged { screenSize = it.toSize() }
-                        .wrapContentSize()
-                        .testTag(tag),
-                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY),
-                    lineLimits = TextFieldLineLimits.SingleLine
-                )
-            }
-        }
-
-        if (handle == Handle.Cursor) {
-            rule.onNodeWithTag(tag).performClick()
-        } else {
-            rule.onNodeWithTag(tag).performTextInputSelection(TextRange(5, 9))
-        }
-
-        // Touch and move the handle to show the magnifier.
-        rule.onNode(isSelectionHandle(handle)).performTouchInput {
-            down(center)
-            // If cursor, we have to drag the cursor to show the magnifier,
-            // press alone will not suffice
-            movePastSlopBy(directionVector)
-        }
-
-        val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
-
-        // Drag all the way past the end of the line.
-        rule.onNode(isSelectionHandle(handle))
-            .performTouchInput {
-                val delta = Offset(
-                    x = screenSize.width * directionVector.x,
-                    y = screenSize.height * directionVector.y
-                )
-                moveBy(delta)
-            }
-
-        val x = if (layoutDirection == LayoutDirection.Ltr) screenSize.width else 0f
-        Truth.assertThat(getMagnifierCenterOffset(rule)).isEqualTo(
-            Offset(x, magnifierInitialPosition.y)
-        )
-    }
-
-    private fun setupDragAndDropContent(): View {
-        val state = TextFieldState(
-            "aaaa",
-            initialSelectionInChars = TextRange.Zero
-        )
-        var view: View? = null
-        rule.setContent { // Do not use setTextFieldTestContent for DnD tests.
-            view = LocalView.current
-            CompositionLocalProvider(
-                LocalDensity provides Density(1f, 1f),
-                LocalWindowInfo provides object : WindowInfo {
-                    override val isWindowFocused = false
-                }
-            ) {
-                BasicTextField2(
-                    state = state,
-                    textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
-                    lineLimits = TextFieldLineLimits.SingleLine
-                )
-            }
-        }
-
-        return view!!
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandlesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandlesTest.kt
deleted file mode 100644
index 7ba40d6..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandlesTest.kt
+++ /dev/null
@@ -1,886 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import android.os.Build
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.horizontalScroll
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.SelectionHandleAnchor
-import androidx.compose.foundation.text.selection.assertHandleAnchorMatches
-import androidx.compose.foundation.text.selection.assertHandlePositionMatches
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.doubleClick
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.swipeDown
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-@LargeTest
-class TextFieldSelectionHandlesTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var state: TextFieldState
-
-    private val TAG = "BasicTextField2"
-
-    private val fontSize = 10.sp
-    private val fontSizePx = with(rule.density) { fontSize.toPx() }
-
-    @Test
-    fun selectionHandles_doNotShow_whenFieldNotFocused() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        assertHandlesNotExist()
-    }
-
-    @Test
-    fun selectionHandles_haveMinimumTouchSizeArea() = with(rule.density) {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        rule.setContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-
-        var actualStartBottomRight = Offset.Zero
-        var actualEndBottomRight = Offset.Zero
-        rule.onNode(isSelectionHandle(Handle.SelectionStart)).performTouchInput {
-            actualStartBottomRight = bottomRight
-        }
-        rule.onNode(isSelectionHandle(Handle.SelectionEnd)).performTouchInput {
-            actualEndBottomRight = bottomRight
-        }
-
-        val expectedBottomRight = Offset(40.dp.toPx(), 40.dp.toPx())
-        assertThat(actualStartBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
-        assertThat(actualStartBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
-        assertThat(actualEndBottomRight.x).isWithin(1f).of(expectedBottomRight.x)
-        assertThat(actualEndBottomRight.y).isWithin(1f).of(expectedBottomRight.y)
-    }
-
-    @Test
-    fun selectionHandles_appears_whenFieldGetsFocused() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-    }
-
-    @Test
-    fun selectionHandles_disappear_whenFieldLosesFocus() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        val focusRequester = FocusRequester()
-        rule.setTextFieldTestContent {
-            Column {
-                Box(
-                    Modifier
-                        .size(100.dp)
-                        .focusRequester(focusRequester)
-                        .focusable())
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(100.dp)
-                )
-            }
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-        rule.runOnIdle {
-            focusRequester.requestFocus()
-        }
-        assertHandlesNotExist()
-    }
-
-    @Test
-    fun textField_noSelectionHandles_whenWindowLosesFocus() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        val focusWindow = mutableStateOf(true)
-        val windowInfo = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focusWindow.value
-        }
-
-        rule.setContent {
-            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier.testTag(TAG).width(100.dp)
-                )
-            }
-        }
-
-        // selection handles displayed
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        // window lost focus, make sure handles disappeared
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        assertHandlesNotExist()
-    }
-
-    @Test
-    fun textField_redisplaysSelectionHandlesAndToolbar_whenWindowRegainsFocus() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        val focusWindow = mutableStateOf(true)
-        val windowInfo = object : WindowInfo {
-            override val isWindowFocused: Boolean
-                get() = focusWindow.value
-        }
-
-        rule.setContent {
-            CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier.testTag(TAG).width(100.dp)
-                )
-            }
-        }
-
-        // selection handles displayed
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        // window lost focus, make sure handles disappeared
-        focusWindow.value = false
-        rule.waitForIdle()
-
-        assertHandlesNotExist()
-
-        // regain window focus
-        focusWindow.value = true
-        rule.waitForIdle()
-
-        assertHandlesDisplayed()
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun selectionHandles_locatedAtTheRightPosition_ltr_ltr() {
-        state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-
-        with(rule.onNode(isSelectionHandle(Handle.SelectionStart))) {
-            assertHandlePositionMatches(
-                (2 * fontSize.value).dp,
-                fontSize.value.dp
-            )
-            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
-        }
-
-        with(rule.onNode(isSelectionHandle(Handle.SelectionEnd))) {
-            assertHandlePositionMatches(
-                (5 * fontSize.value).dp,
-                fontSize.value.dp
-            )
-            assertHandleAnchorMatches(SelectionHandleAnchor.Right)
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun selectionHandles_locatedAtTheRightPosition_ltr_rtl() {
-        state = TextFieldState("abc \u05D0\u05D1\u05D2", initialSelectionInChars = TextRange(1, 6))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-
-        with(rule.onNode(isSelectionHandle(Handle.SelectionStart))) {
-            assertHandlePositionMatches(
-                (1 * fontSize.value).dp,
-                fontSize.value.dp
-            )
-            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
-        }
-
-        with(rule.onNode(isSelectionHandle(Handle.SelectionEnd))) {
-            assertHandlePositionMatches(
-                (5 * fontSize.value).dp,
-                fontSize.value.dp
-            )
-            assertHandleAnchorMatches(SelectionHandleAnchor.Left)
-        }
-    }
-
-    @Test
-    fun selectionHandlesDisappear_whenScrolledOutOfView_horizontally() {
-        // make it scrollable
-        state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft() }
-        assertHandlesNotExist()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeRight() }
-        assertHandlesDisplayed()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-    }
-
-    @Test
-    fun selectionHandlesDisappear_whenScrolledOutOfView_vertically() {
-        // make it scrollable
-        state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            swipeUp()
-        }
-        assertHandlesNotExist()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            swipeDown()
-        }
-        assertHandlesDisplayed()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-    }
-
-    @Test
-    fun selectionHandlesDisappear_whenScrolledOutOfView_horizontally_inContainer() {
-        // make it scrollable
-        val containerTag = "container"
-        state = TextFieldState("hello", initialSelectionInChars = TextRange(1, 2))
-        rule.setTextFieldTestContent {
-            Row(modifier = Modifier
-                .width(200.dp)
-                .horizontalScroll(rememberScrollState())
-                .testTag(containerTag)
-            ) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .width(100.dp)
-                )
-                Box(modifier = Modifier
-                    .height(12.dp)
-                    .width(400.dp))
-            }
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        rule.onNodeWithTag(containerTag).performTouchInput {
-            swipeLeft()
-        }
-        assertHandlesNotExist()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-
-        rule.onNodeWithTag(containerTag).performTouchInput {
-            swipeRight()
-        }
-        assertHandlesDisplayed()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-    }
-
-    @Test
-    fun selectionHandlesDisappear_whenScrolledOutOfView_vertically_inContainer() {
-        // make it scrollable
-        val containerTag = "container"
-        state = TextFieldState("hello", initialSelectionInChars = TextRange(1, 2))
-        rule.setTextFieldTestContent {
-            Column(modifier = Modifier
-                .height(200.dp)
-                .verticalScroll(rememberScrollState())
-                .testTag(containerTag)
-            ) {
-                BasicTextField2(
-                    state,
-                    textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                    modifier = Modifier
-                        .testTag(TAG)
-                        .height(100.dp)
-                )
-                Box(modifier = Modifier
-                    .width(12.dp)
-                    .height(400.dp))
-            }
-        }
-
-        focusAndWait()
-        assertHandlesDisplayed()
-
-        rule.onNodeWithTag(containerTag).performTouchInput {
-            swipeUp()
-        }
-        assertHandlesNotExist()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-
-        rule.onNodeWithTag(containerTag).performTouchInput {
-            swipeDown()
-        }
-        assertHandlesDisplayed()
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 2))
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandle_toExtendSelection() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToLeft(Handle.SelectionStart, fontSizePx * 4)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandle_toExtendSelection() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToRight(Handle.SelectionEnd, fontSizePx * 4)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
-        }
-    }
-
-    @Test
-    fun doubleClickOnWord_toSelectWord() {
-        state = TextFieldState("abc def ghj")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            doubleClick(Offset(fontSizePx * 5, fontSizePx / 2)) // middle word
-        }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 23)
-    @Test
-    fun doubleClickOnWhitespace_doesNotSelectWhitespace() {
-        state = TextFieldState("abc def ghj")
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            // space between first and second words
-            doubleClick(Offset(fontSizePx * 3.5f, fontSizePx / 2))
-        }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(3, 4))
-            assertThat(state.text.selectionInChars.collapsed).isFalse()
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandle_outOfBounds_horizontally() {
-        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(77, 80))
-        val scrollState = ScrollState(0)
-        lateinit var scope: CoroutineScope
-        rule.setTextFieldTestContent {
-            scope = rememberCoroutineScope()
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.SingleLine,
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.waitForIdle()
-        scope.launch { scrollState.scrollTo(scrollState.maxValue) } // scroll to the most right
-        focusAndWait() // selection handles show up
-
-        repeat(80) {
-            swipeToLeft(Handle.SelectionStart, fontSizePx)
-        }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandle_outOfBounds_vertically() {
-        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(77, 80))
-        val scrollState = ScrollState(0)
-        lateinit var scope: CoroutineScope
-        rule.setTextFieldTestContent {
-            scope = rememberCoroutineScope()
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
-                scrollState = scrollState,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(50.dp)
-            )
-        }
-
-        rule.waitForIdle()
-        scope.launch { scrollState.scrollTo(scrollState.maxValue) } // scroll to the bottom
-        focusAndWait() // selection handles show up
-
-        swipeUp(Handle.SelectionStart, scrollState.maxValue.toFloat() * 2)
-        // make sure that we also swipe to start on the first line
-        swipeToLeft(Handle.SelectionStart, fontSizePx * 10)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandle_outOfBounds_horizontally() {
-        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.waitForIdle()
-        focusAndWait() // selection handles show up
-
-        repeat(80) {
-            swipeToRight(Handle.SelectionEnd, fontSizePx)
-        }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandle_outOfBounds_vertically() {
-        state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
-        lateinit var layoutResult: () -> TextLayoutResult?
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
-                onTextLayout = { layoutResult = it },
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(100.dp)
-            )
-        }
-
-        rule.waitForIdle()
-        focusAndWait() // selection handles show up
-
-        @Suppress("NAME_SHADOWING")
-        layoutResult()!!.let { layoutResult ->
-            swipeDown(Handle.SelectionEnd, layoutResult.size.height.toFloat())
-            swipeToRight(Handle.SelectionEnd, layoutResult.size.width.toFloat())
-        }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 80))
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandle_extendsByWord() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToLeft(Handle.SelectionStart, fontSizePx * 2) // only move by 2 characters
-        rule.runOnIdle {
-            // selection extends by a word
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 7))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandle_extendsByWord() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToRight(Handle.SelectionEnd, fontSizePx * 2) // only move by 2 characters
-        rule.runOnIdle {
-            // selection extends by a word
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 11))
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandle_shrinksByCharacter() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToRight(Handle.SelectionStart, fontSizePx) // only move by a single character
-        rule.runOnIdle {
-            // selection shrinks by a character
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5, 7))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandle_shrinksByCharacter() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToLeft(Handle.SelectionEnd, fontSizePx) // only move by a single character
-        rule.runOnIdle {
-            // selection shrinks by a character
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 6))
-        }
-    }
-
-    @Test
-    fun dragStartSelectionHandlePastEndHandle_reversesTheSelection() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToRight(Handle.SelectionStart, fontSizePx * 7)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(11, 7))
-        }
-    }
-
-    @Test
-    fun dragEndSelectionHandlePastStartHandle_canReverseSelection() {
-        state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
-        rule.setTextFieldTestContent {
-            BasicTextField2(
-                state,
-                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
-                modifier = Modifier
-                    .testTag(TAG)
-                    .width(200.dp)
-            )
-        }
-
-        focusAndWait()
-
-        swipeToLeft(Handle.SelectionEnd, fontSizePx * 7)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 0))
-        }
-    }
-
-    private fun focusAndWait() {
-        rule.onNode(hasSetTextAction()).performSemanticsAction(SemanticsActions.RequestFocus)
-    }
-
-    private fun assertHandlesDisplayed(
-        assertStartHandle: Boolean = true,
-        assertEndHandle: Boolean = true
-    ) {
-        if (assertStartHandle) {
-            rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
-        }
-        if (assertEndHandle) {
-            rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        }
-    }
-
-    private fun assertHandlesNotExist(
-        assertStartHandle: Boolean = true,
-        assertEndHandle: Boolean = true
-    ) {
-        if (assertStartHandle) {
-            rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertDoesNotExist()
-        }
-        if (assertEndHandle) {
-            rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertDoesNotExist()
-        }
-    }
-
-    private fun swipeUp(handle: Handle, swipeDistance: Float) =
-        performHandleDrag(handle, true, swipeDistance, Orientation.Vertical)
-
-    private fun swipeDown(handle: Handle, swipeDistance: Float) =
-        performHandleDrag(handle, false, swipeDistance, Orientation.Vertical)
-
-    private fun swipeToLeft(handle: Handle, swipeDistance: Float) =
-        performHandleDrag(handle, true, swipeDistance)
-
-    private fun swipeToRight(handle: Handle, swipeDistance: Float) =
-        performHandleDrag(handle, false, swipeDistance)
-
-    private fun performHandleDrag(
-        handle: Handle,
-        toStart: Boolean,
-        swipeDistance: Float = 1f,
-        orientation: Orientation = Orientation.Horizontal
-    ) {
-        val handleNode = rule.onNode(isSelectionHandle(handle))
-
-        handleNode.performTouchInput {
-            if (orientation == Orientation.Horizontal) {
-                if (toStart) {
-                    swipeLeft(
-                        startX = centerX,
-                        endX = centerX - viewConfiguration.touchSlop - swipeDistance,
-                        durationMillis = 1000
-                    )
-                } else {
-                    swipeRight(
-                        startX = centerX,
-                        endX = centerX + viewConfiguration.touchSlop + swipeDistance,
-                        durationMillis = 1000
-                    )
-                }
-            } else {
-                if (toStart) {
-                    swipeUp(
-                        startY = centerY,
-                        endY = centerY - viewConfiguration.touchSlop - swipeDistance,
-                        durationMillis = 1000
-                    )
-                } else {
-                    swipeDown(
-                        startY = centerY,
-                        endY = centerY + viewConfiguration.touchSlop + swipeDistance,
-                        durationMillis = 1000
-                    )
-                }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionOnBackTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionOnBackTest.kt
deleted file mode 100644
index 343781d..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionOnBackTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2021 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.onKeyEvent
-import androidx.compose.ui.input.key.type
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.SoftwareKeyboardController
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(
-    ExperimentalTestApi::class,
-    ExperimentalFoundationApi::class
-)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldSelectionOnBackTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val Tag = "BasicTextField2"
-
-    @Test
-    fun whenBackPressed_andReleased_textFieldClearsSelection() {
-        val state = TextFieldState("hello", TextRange(0, 0))
-        rule.setContent {
-            BasicTextField2(
-                state,
-                Modifier
-                    .testTag(Tag)
-                    .wrapContentSize()
-            )
-        }
-        val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTextInputSelection(TextRange(0, 3))
-        rule.waitForIdle()
-        textNode.performKeyInput { pressKey(Key.Back) }
-        val expected = TextRange(3, 3)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(expected)
-        }
-    }
-
-    @Test
-    fun whenBackPressed_andReleased_textFieldDoesNotPropagateBackPress() {
-        val state = TextFieldState("hello", TextRange(0, 0))
-        var backPressed = 0
-        rule.setContent {
-            BasicTextField2(
-                state,
-                Modifier
-                    .testTag(Tag)
-                    .wrapContentSize()
-                    .onKeyEvent {
-                        if (it.type == KeyEventType.KeyUp && it.key == Key.Back) {
-                            backPressed++
-                        }
-                        false
-                    }
-            )
-        }
-        val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTextInputSelection(TextRange(0, 3))
-        rule.waitForIdle()
-        textNode.performKeyInput { pressKey(Key.Back) }
-        val expected = TextRange(3, 3)
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(expected)
-            assertThat(backPressed).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun whenBackPressed_coreTextFieldRetainsSelection() {
-        val state = TextFieldState("hello", TextRange(0, 0))
-        rule.setContent {
-            BasicTextField2(
-                state,
-                Modifier
-                    .testTag(Tag)
-                    .wrapContentSize()
-            )
-        }
-        val expected = TextRange(0, 3)
-        val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTextInputSelection(expected)
-        rule.waitForIdle()
-        // should have no effect
-        textNode.performKeyInput { keyDown(Key.Back) }
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(expected)
-        }
-    }
-
-    @Test
-    fun whenBackPressed_andReleased_whenCursorHandleShown_doesNotConsumeEvent() {
-        var backPressed = 0
-        var softwareKeyboardController: SoftwareKeyboardController? = null
-        val state = TextFieldState("Hello")
-        rule.setTextFieldTestContent {
-            softwareKeyboardController = LocalSoftwareKeyboardController.current
-            BasicTextField2(
-                state,
-                Modifier
-                    .testTag(Tag)
-                    .onKeyEvent {
-                        if (it.type == KeyEventType.KeyUp && it.key == Key.Back) {
-                            backPressed++
-                        }
-                        false
-                    }
-            )
-        }
-
-        with(rule.onNodeWithTag(Tag)) {
-            // Show the handle.
-            performClick()
-            rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-
-            // Hide the keyboard before pressing back, since the first back should be consumed by
-            // the keyboard.
-            rule.runOnUiThread {
-                softwareKeyboardController!!.hide()
-            }
-
-            // Press back.
-            performKeyInput { pressKey(Key.Back) }
-
-            // Ensure back event was propagated up past the text field.
-            rule.runOnIdle {
-                assertThat(backPressed).isEqualTo(1)
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldTextToolbarTest.kt
deleted file mode 100644
index 3e9e1ca..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldTextToolbarTest.kt
+++ /dev/null
@@ -1,1008 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.FakeTextToolbar
-import androidx.compose.foundation.text.selection.gestures.util.longPress
-import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.placeCursorAtEnd
-import androidx.compose.foundation.text2.input.selectAll
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.ClipEntry
-import androidx.compose.ui.platform.ClipMetadata
-import androidx.compose.ui.platform.ClipboardManager
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.TextToolbar
-import androidx.compose.ui.platform.TextToolbarStatus
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.platform.toClipEntry
-import androidx.compose.ui.platform.toClipMetadata
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performMouseInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.withKeyDown
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Fact
-import com.google.common.truth.FailureMetadata
-import com.google.common.truth.Subject
-import com.google.common.truth.Subject.Factory
-import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@LargeTest
-class TextFieldTextToolbarTest : FocusedWindowTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    val fontSize = 10.sp
-
-    val fontSizePx = with(rule.density) { fontSize.toPx() }
-
-    val TAG = "BasicTextField2"
-
-    private var enabled by mutableStateOf(true)
-
-    @Test
-    fun toolbarAppears_whenCursorHandleIsClicked() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-    }
-
-    @Test
-    fun toolbarDisappears_whenCursorHandleIsClickedAgain() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun longClickOnEmptyTextField_showsToolbar_butNoHandle() {
-        val state = TextFieldState("")
-        val textToolbar = FakeTextToolbar({ _, _, _, _, _ -> }, {})
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longClick(Offset(fontSize.toPx(), fontSize.toPx() / 2))
-        }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).assertDoesNotExist()
-        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-    }
-
-    @Test
-    fun toolbarDisappears_whenTextStateIsUpdated() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        state.edit {
-            append(" World!")
-            placeCursorAtEnd()
-        }
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaHardwareKeys() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        with(rule.onNodeWithTag(TAG)) {
-            requestFocus()
-            performKeyInput {
-                withKeyDown(Key.ShiftLeft) {
-                    pressKey(Key.DirectionLeft)
-                    pressKey(Key.DirectionLeft)
-                    pressKey(Key.DirectionLeft)
-                }
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5, 2))
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-
-        with(rule.onNodeWithTag(TAG)) {
-            performKeyInput {
-                withKeyDown(Key.CtrlLeft) {
-                    pressKey(Key.A)
-                }
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaStateUpdate() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        with(rule.onNodeWithTag(TAG)) {
-            requestFocus()
-        }
-
-        rule.runOnIdle {
-            state.edit { selectAll() }
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotAppear_ifSelectionIsInitiatedViaSemantics() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        with(rule.onNodeWithTag(TAG)) {
-            requestFocus()
-            performTextInputSelection(TextRange(0, 5))
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarAppears_ifSelectionIsInitiatedViaSemantics_inNoneTraversalMode() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        with(rule.onNodeWithTag(TAG)) {
-            requestFocus()
-            performSemanticsAction(SemanticsActions.SetSelection) {
-                it(0, 5, false)
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-    }
-
-    @Test
-    fun toolbarDisappears_whenTextIsEntered_throughIME() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        rule.onNodeWithTag(TAG).performTextInput(" World!")
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun cursorToolbarDisappears_whenTextField_getsDisabled_doesNotReappear() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        enabled = false
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-
-        enabled = true
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun selectionToolbarDisappears_whenTextField_getsDisabled_doesNotReappear() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(2, 4))
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        enabled = false
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-
-        enabled = true
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun toolbarDisappears_whenTextIsEntered_throughHardwareKeyboard() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        rule.onNodeWithTag(TAG).performKeyInput {
-            pressKey(Key.W)
-        }
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarTemporarilyHides_whenHandleIsBeingDragged() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(0f, fontSizePx / 2)) }
-
-        with(rule.onNode(isSelectionHandle(Handle.Cursor))) {
-            performClick()
-            rule.runOnIdle {
-                assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-            }
-            performTouchInput {
-                down(center)
-                moveBy(Offset(viewConfiguration.touchSlop, 0f))
-                moveBy(Offset(fontSizePx, 0f))
-            }
-        }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
-            up()
-        }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-    }
-
-    @Test
-    fun toolbarTemporarilyHides_whenCursor_goesOutOfBounds() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft(startX = fontSizePx * 3, endX = 0f) }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { swipeRight(startX = 0f, endX = fontSizePx * 3) }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-    }
-
-    @Test
-    fun toolbarFollowsTheCursor_whenTextFieldIsScrolled() {
-        var shownRect: Rect? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { rect, _, _, _, _ ->
-                shownRect = rect
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        lateinit var firstRectAnchor: Rect
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-            firstRectAnchor = shownRect!!
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            down(center)
-            moveBy(Offset(-viewConfiguration.touchSlop - fontSizePx, 0f))
-            up()
-        }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-            val secondRectAnchor = shownRect!!
-            Truth.assertAbout(RectSubject.SUBJECT_FACTORY)
-                .that(secondRectAnchor)!!
-                .isEqualToWithTolerance(
-                    firstRectAnchor.translate(
-                        translateX = -fontSizePx,
-                        translateY = 0f
-                    )
-                )
-        }
-    }
-
-    @Test
-    fun toolbarShowsSelectAll() {
-        var selectAllOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, _, _, onSelectAllRequested ->
-                selectAllOptionAvailable = onSelectAllRequested != null
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(selectAllOptionAvailable).isTrue()
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotShowSelectAll_whenAllTextIsAlreadySelected() {
-        var selectAllOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, _, _, onSelectAllRequested ->
-                selectAllOption = onSelectAllRequested
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(selectAllOption).isNotNull()
-        }
-
-        selectAllOption?.invoke()
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-        rule.runOnIdle {
-            assertThat(selectAllOption).isNull()
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotShowPaste_whenClipboardHasNoContent() {
-        var pasteOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOptionAvailable = onPasteRequested != null
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(pasteOptionAvailable).isFalse()
-        }
-    }
-
-    @Test
-    fun toolbarShowsPaste_whenClipboardHasText() {
-        var pasteOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOptionAvailable = onPasteRequested != null
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager("world")
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true, clipboardManager)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(pasteOptionAvailable).isTrue()
-        }
-    }
-
-    @Test
-    fun toolbarDoesNotShowPaste_whenClipboardHasContent_butNoReceiveContentConfigured() {
-        var pasteOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOptionAvailable = onPasteRequested != null
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true).apply {
-            setClip(createClipData().toClipEntry())
-        }
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true, clipboardManager)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(pasteOptionAvailable).isFalse()
-        }
-    }
-
-    @Test
-    fun toolbarShowsPaste_whenClipboardHasContent_andReceiveContentConfigured() {
-        var pasteOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOptionAvailable = onPasteRequested != null
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager(supportsClipEntry = true).apply {
-            setClip(createClipData().toClipEntry())
-        }
-        val state = TextFieldState("Hello")
-        setupContent(
-            state = state,
-            toolbar = textToolbar,
-            singleLine = true,
-            clipboardManager = clipboardManager,
-            modifier = Modifier.receiveContent(setOf(MediaType.Image)) { null }
-        )
-
-        rule.onNodeWithTag(TAG).performTouchInput { click() }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(pasteOptionAvailable).isTrue()
-        }
-    }
-
-    @Test
-    fun pasteInsertsContentAtCursor_placesCursorAfterInsertedContent() {
-        var pasteOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, onPasteRequested, _, _ ->
-                pasteOption = onPasteRequested
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager("world")
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true, clipboardManager)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, 0f)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            pasteOption!!.invoke()
-        }
-
-        rule.runOnIdle {
-            assertThat(state.text.toString()).isEqualTo("Heworldllo")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(7))
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun toolbarDoesNotShowCopyOrCut_whenSelectionIsCollapsed() {
-        var cutOptionAvailable = false
-        var copyOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
-                copyOptionAvailable = onCopyRequested != null
-                cutOptionAvailable = onCutRequested != null
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(2, 2))
-
-        rule.runOnIdle {
-            assertThat(copyOptionAvailable).isFalse()
-            assertThat(cutOptionAvailable).isFalse()
-        }
-    }
-
-    @Test
-    fun toolbarShowsCopyAndCut_whenSelectionIsExpanded() {
-        var cutOptionAvailable = false
-        var copyOptionAvailable = false
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
-                copyOptionAvailable = onCopyRequested != null
-                cutOptionAvailable = onCutRequested != null
-            },
-            onHideMenu = {}
-        )
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true)
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(2, 4))
-
-        rule.runOnIdle {
-            assertThat(copyOptionAvailable).isTrue()
-            assertThat(cutOptionAvailable).isTrue()
-        }
-    }
-
-    @Test
-    fun copyUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion() {
-        var copyOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, onCopyRequested, _, _, _ ->
-                copyOption = onCopyRequested
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar, true, clipboardManager)
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(0, 5))
-
-        rule.runOnIdle {
-            copyOption!!.invoke()
-        }
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-        }
-    }
-
-    @Test
-    fun cutUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion_removesTheCutContent() {
-        var cutOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, _, onCutRequested, _ ->
-                cutOption = onCutRequested
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager()
-        val state = TextFieldState("Hello World!")
-        setupContent(state, textToolbar, true, clipboardManager)
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(1, 5))
-
-        rule.runOnIdle {
-            cutOption!!.invoke()
-        }
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
-            assertThat(state.text.toString()).isEqualTo("H World!")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        }
-    }
-
-    @Test
-    fun cutAppliesFilter() {
-        var cutOption: (() -> Unit)? = null
-        val textToolbar = FakeTextToolbar(
-            onShowMenu = { _, _, _, onCutRequested, _ ->
-                cutOption = onCutRequested
-            },
-            onHideMenu = {}
-        )
-        val clipboardManager = FakeClipboardManager()
-        val state = TextFieldState("Hello World!")
-        setupContent(state, textToolbar, true, clipboardManager) { original, changes ->
-            // only reject text changes, accept selection
-            val selection = changes.selectionInChars
-            changes.replace(0, changes.length, original.toString())
-            changes.selectCharsIn(selection)
-        }
-
-        rule.onNodeWithTag(TAG).requestFocus()
-        rule.onNodeWithTag(TAG).performTextInputSelectionShowingToolbar(TextRange(1, 5))
-
-        rule.runOnIdle {
-            cutOption!!.invoke()
-        }
-
-        rule.runOnIdle {
-            assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
-            assertThat(state.text.toString()).isEqualTo("Hello World!")
-            assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        }
-    }
-
-    @Test
-    fun tappingTextField_hidesTheToolbar() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        rule.mainClock.advanceTimeBy(1000) // to not cause double click
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @OptIn(ExperimentalTestApi::class)
-    @Test
-    fun interactingWithTextFieldByMouse_doeNotShowTheToolbar() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performMouseInput {
-            click()
-        }
-        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-    }
-
-    @Test
-    fun toolbarDisappears_whenFocusIsLost() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        val focusRequester = FocusRequester()
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
-                Column {
-                    Box(
-                        modifier = Modifier
-                            .focusRequester(focusRequester)
-                            .focusable()
-                            .size(100.dp)
-                    )
-                    BasicTextField2(
-                        state = state,
-                        modifier = Modifier
-                            .width(100.dp)
-                            .testTag(TAG),
-                        textStyle = TextStyle(
-                            fontFamily = TEST_FONT_FAMILY,
-                            fontSize = fontSize
-                        )
-                    )
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        focusRequester.requestFocus()
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarDisappears_whenTextFieldIsDisposed() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        val toggleState = mutableStateOf(true)
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
-                Column {
-                    if (toggleState.value) {
-                        BasicTextField2(
-                            state = state,
-                            modifier = Modifier
-                                .width(100.dp)
-                                .testTag(TAG),
-                            textStyle = TextStyle(
-                                fontFamily = TEST_FONT_FAMILY,
-                                fontSize = fontSize
-                            )
-                        )
-                    }
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        toggleState.value = false
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    @Test
-    fun toolbarDisappears_whenLongPressIsInitiated() {
-        val textToolbar = FakeTextToolbar()
-        val state = TextFieldState("Hello")
-        setupContent(state, textToolbar)
-
-        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
-        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
-        }
-
-        rule.onNodeWithTag(TAG).performTouchInput {
-            longPress(Offset(3 * fontSizePx * 2, fontSizePx / 2))
-        }
-
-        rule.runOnIdle {
-            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
-        }
-    }
-
-    private fun setupContent(
-        state: TextFieldState = TextFieldState(),
-        toolbar: TextToolbar = FakeTextToolbar(),
-        singleLine: Boolean = false,
-        clipboardManager: ClipboardManager = FakeClipboardManager(),
-        modifier: Modifier = Modifier,
-        filter: InputTransformation? = null,
-    ) {
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(
-                LocalTextToolbar provides toolbar,
-                LocalClipboardManager provides clipboardManager
-            ) {
-                BasicTextField2(
-                    state = state,
-                    modifier = modifier
-                        .width(100.dp)
-                        .testTag(TAG),
-                    textStyle = TextStyle(
-                        fontFamily = TEST_FONT_FAMILY,
-                        fontSize = fontSize
-                    ),
-                    enabled = enabled,
-                    lineLimits = if (singleLine) {
-                        TextFieldLineLimits.SingleLine
-                    } else {
-                        TextFieldLineLimits.Default
-                    },
-                    inputTransformation = filter
-                )
-            }
-        }
-    }
-
-    private fun FakeTextToolbar() = FakeTextToolbar(
-        onShowMenu = { _, _, _, _, _ -> },
-        onHideMenu = {
-            println("hide")
-        }
-    )
-}
-
-internal class RectSubject private constructor(
-    failureMetadata: FailureMetadata?,
-    private val subject: Rect?
-) : Subject(failureMetadata, subject) {
-
-    companion object {
-        internal val SUBJECT_FACTORY: Factory<RectSubject?, Rect?> =
-            Factory { failureMetadata, subject -> RectSubject(failureMetadata, subject) }
-    }
-
-    fun isEqualToWithTolerance(expected: Rect, tolerance: Float = 1f) {
-        if (subject == null) failWithoutActual(Fact.simpleFact("is null"))
-        check("instanceOf()").that(subject).isInstanceOf(Rect::class.java)
-        assertThat(subject!!.left).isWithin(tolerance).of(expected.left)
-        assertThat(subject.top).isWithin(tolerance).of(expected.top)
-        assertThat(subject.right).isWithin(tolerance).of(expected.right)
-        assertThat(subject.bottom).isWithin(tolerance).of(expected.bottom)
-    }
-}
-
-internal fun FakeClipboardManager(
-    initialText: String? = null,
-    supportsClipEntry: Boolean = false,
-) = object : ClipboardManager {
-    private var currentText: AnnotatedString? = initialText?.let { AnnotatedString(it) }
-    private var currentClipEntry: ClipEntry? = null
-
-    override fun setText(annotatedString: AnnotatedString) {
-        currentText = annotatedString
-    }
-
-    override fun getText(): AnnotatedString? {
-        return currentText
-    }
-
-    override fun getClip(): ClipEntry? {
-        if (supportsClipEntry) {
-            return currentClipEntry
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-
-    override fun getClipMetadata(): ClipMetadata? {
-        if (supportsClipEntry) {
-            return currentClipEntry?.clipData?.description?.toClipMetadata()
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-
-    override fun hasClip(): Boolean {
-        if (supportsClipEntry) {
-            return currentClipEntry != null
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-
-    override fun setClip(clipEntry: ClipEntry) {
-        if (supportsClipEntry) {
-            currentClipEntry = clipEntry
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-}
-
-/**
- * Toolbar does not show up when text is selected with traversal mode off (relative to original
- * text). This is an override of [SemanticsNodeInteraction.performTextInputSelection] that
- * makes sure the toolbar shows up after selection is initiated.
- */
-fun SemanticsNodeInteraction.performTextInputSelectionShowingToolbar(selection: TextRange) {
-    requestFocus()
-    performSemanticsAction(SemanticsActions.SetSelection) {
-        it(selection.min, selection.max, false)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
deleted file mode 100644
index 7b3b53a..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection.gesture
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.isPlatformMagnifierSupported
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.FocusedWindowTest
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.TEST_FONT_FAMILY
-import androidx.compose.foundation.text.selection.HandlePressedScope
-import androidx.compose.foundation.text.selection.assertNoMagnifierExists
-import androidx.compose.foundation.text.selection.assertThatOffset
-import androidx.compose.foundation.text.selection.fetchTextLayoutResult
-import androidx.compose.foundation.text.selection.gestures.util.longPress
-import androidx.compose.foundation.text.selection.getMagnifierCenterOffset
-import androidx.compose.foundation.text.selection.withHandlePressed
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.rememberTextFieldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.testutils.TestViewConfiguration
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.test.swipe
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.fail
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Test that TextField works as expected even when it is scrolled.
- *
- * Regression test for b/314385218.
- */
-@ExperimentalFoundationApi
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TextFieldScrolledSelectionGestureTest : FocusedWindowTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    private val fontFamily = TEST_FONT_FAMILY
-    private val fontSize = 15.sp
-    private val textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize)
-    private val density = Density(1f)
-    private val pointerAreaTag = "testTag"
-
-    private fun setContent(content: @Composable (tag: String) -> Unit) {
-        rule.setTextFieldTestContent {
-            CompositionLocalProvider(
-                LocalDensity provides density,
-                LocalViewConfiguration provides TestViewConfiguration(
-                    minimumTouchTargetSize = DpSize.Zero,
-                    touchSlop = Float.MIN_VALUE,
-                ),
-            ) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxSize()
-                        .padding(32.dp)
-                        .wrapContentSize()
-                ) {
-                    content(pointerAreaTag)
-                }
-            }
-        }
-    }
-
-    private abstract inner class AbstractScope(
-        val textFieldState: TextFieldState,
-        val onTextField: SemanticsNodeInteraction,
-        val textFieldLayoutCoordinates: LayoutCoordinates,
-    ) {
-        /** Returns the offset needed to translate the amount scrolled. */
-        abstract fun TextLayoutResult.translateScroll(): Offset
-
-        fun characterBoxScrolled(offset: Int): Rect = onTextField.fetchTextLayoutResult().run {
-            getBoundingBox(offset).translate(translateScroll())
-        }
-
-        fun positionForCharacterScrolled(offset: Int): Offset =
-            characterBoxScrolled(offset).centerLeft
-
-        fun HandlePressedScope.moveHandleToCharacter(characterOffset: Int) {
-            val boundingBox = onTextField.fetchTextLayoutResult().getBoundingBox(characterOffset)
-            val destinationPosition = when (fetchHandleInfo().handle) {
-                Handle.SelectionStart -> boundingBox.bottomLeft
-                Handle.SelectionEnd -> boundingBox.bottomRight
-                Handle.Cursor -> fail("Unexpected handle ${Handle.Cursor}")
-            }
-            moveHandleTo(destinationPosition)
-        }
-
-        fun assertSelectionEquals(selectionRange: Pair<Int, Int>) {
-            val (start, end) = selectionRange
-            assertThat(textFieldState.text.selectionInChars).isEqualTo(TextRange(start, end))
-        }
-
-        fun assertNoMagnifierExists() {
-            if (!isPlatformMagnifierSupported()) return
-            assertNoMagnifierExists(rule)
-        }
-
-        fun assertOneMagnifierExistsAt(expectedOffset: Offset) {
-            if (!isPlatformMagnifierSupported()) return
-            val offsetInRoot = getMagnifierCenterOffset(rule, requireSpecified = true)
-            val offsetInTextField = offsetInRoot - textFieldLayoutCoordinates.positionInRoot()
-            assertThatOffset(offsetInTextField).equalsWithTolerance(expectedOffset)
-        }
-    }
-
-    private inner class HorizontalScope(
-        textFieldState: TextFieldState,
-        onTextField: SemanticsNodeInteraction,
-        textFieldLayoutCoordinates: LayoutCoordinates,
-        val textFieldSize: IntSize,
-    ) : AbstractScope(textFieldState, onTextField, textFieldLayoutCoordinates) {
-        override fun TextLayoutResult.translateScroll(): Offset {
-            val textLayoutSize = size
-            assertThat(textFieldSize.height).isEqualTo(textLayoutSize.height)
-            val translateX = textFieldSize.width - textLayoutSize.width
-            return Offset(translateX.toFloat(), 0f)
-        }
-    }
-
-    /** Create a horizontally scrollable text field that is scrolled all the way to the end. */
-    private fun runHorizontalTest(block: HorizontalScope.() -> Unit) {
-        val text = (0..9).joinToString(separator = " ") { "text$it" }
-        lateinit var textFieldLayoutCoordinates: LayoutCoordinates
-        var sizeNullable: MutableState<IntSize?>? = null
-        lateinit var tfs: TextFieldState
-        setContent { tag ->
-            sizeNullable = remember { mutableStateOf(null) }
-            tfs = rememberTextFieldState(text)
-            BasicTextField2(
-                state = tfs,
-                textStyle = textStyle,
-                lineLimits = TextFieldLineLimits.SingleLine,
-                modifier = Modifier
-                    .width(300.dp)
-                    .testTag(tag = tag)
-                    .onSizeChanged { sizeNullable!!.value = it }
-                    .onGloballyPositioned { textFieldLayoutCoordinates = it }
-            )
-        }
-        val onTextField = rule.onNodeWithTag(pointerAreaTag)
-        onTextField.requestFocus()
-
-        // scroll to the end
-        onTextField.performTouchInput {
-            repeat(4) {
-                swipe(start = centerRight, end = centerLeft)
-            }
-        }
-
-        assertThat(sizeNullable!!.value).isNotNull()
-        HorizontalScope(tfs, onTextField, textFieldLayoutCoordinates, sizeNullable!!.value!!)
-            .block()
-    }
-
-    @Test
-    fun whenHorizontalScroll_longPressGesture_selectAndDrag() = runHorizontalTest {
-        // select "text8".
-        val char50Position = positionForCharacterScrolled(50)
-        onTextField.performTouchInput { longPress(char50Position) }
-        assertSelectionEquals(48 to 53)
-        assertOneMagnifierExistsAt(char50Position)
-
-        // Backwards select through "text7" so that the selection is "text7 ".
-        val char46Position = positionForCharacterScrolled(46)
-        onTextField.performTouchInput { moveTo(char46Position) }
-        assertSelectionEquals(42 to 53)
-        assertOneMagnifierExistsAt(char46Position)
-
-        onTextField.performTouchInput { up() }
-        assertSelectionEquals(42 to 53)
-        assertNoMagnifierExists()
-    }
-
-    @Test
-    fun whenHorizontalScroll_handleGesture_drag() = runHorizontalTest {
-        // select "text8".
-        onTextField.performTouchInput { longClick(positionForCharacterScrolled(50)) }
-        assertSelectionEquals(48 to 53)
-        assertNoMagnifierExists()
-
-        // Backwards select through "text7" so that the selection is "text7 ".
-        rule.withHandlePressed(Handle.SelectionStart) {
-            assertSelectionEquals(48 to 53)
-            assertOneMagnifierExistsAt(positionForCharacterScrolled(48))
-            moveHandleToCharacter(45)
-            assertSelectionEquals(42 to 53)
-            assertOneMagnifierExistsAt(positionForCharacterScrolled(45))
-        }
-        assertSelectionEquals(42 to 53)
-        assertNoMagnifierExists()
-    }
-
-    /** Create a vertically scrollable text field that is scrolled all the way to the end. */
-    private inner class VerticalScope(
-        textFieldState: TextFieldState,
-        onTextField: SemanticsNodeInteraction,
-        textFieldLayoutCoordinates: LayoutCoordinates,
-        val textFieldSize: IntSize,
-    ) : AbstractScope(textFieldState, onTextField, textFieldLayoutCoordinates) {
-        override fun TextLayoutResult.translateScroll(): Offset {
-            val textLayoutSize = size
-            assertThat(textFieldSize.width).isEqualTo(textLayoutSize.width)
-            val translateY = textFieldSize.height - textLayoutSize.height
-            return Offset(0f, translateY.toFloat())
-        }
-    }
-
-    /**
-     * Create a horizontally scrollable text field that is scrolled all the way to the end.
-     */
-    private fun runVerticalTest(block: VerticalScope.() -> Unit) {
-        val text = (0..9).joinToString(separator = "\n") { "text$it" }
-        lateinit var textFieldLayoutCoordinates: LayoutCoordinates
-        var sizeNullable: MutableState<IntSize?>? = null
-        lateinit var tfs: TextFieldState
-        setContent { tag ->
-            sizeNullable = remember { mutableStateOf(null) }
-            tfs = rememberTextFieldState(text)
-            BasicTextField2(
-                state = tfs,
-                textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
-                lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 4),
-                modifier = Modifier
-                    .width(300.dp)
-                    .testTag(tag = tag)
-                    .onSizeChanged { sizeNullable!!.value = it }
-                    .onGloballyPositioned { textFieldLayoutCoordinates = it }
-            )
-        }
-        assertThat(sizeNullable).isNotNull()
-        val onTextField = rule.onNodeWithTag(pointerAreaTag)
-        onTextField.requestFocus()
-
-        // scroll to the end
-        onTextField.performTouchInput {
-            repeat(4) {
-                swipe(start = bottomCenter, end = topCenter)
-            }
-        }
-
-        assertThat(sizeNullable!!.value).isNotNull()
-        VerticalScope(tfs, onTextField, textFieldLayoutCoordinates, sizeNullable!!.value!!).block()
-    }
-
-    @Test
-    fun whenVerticalScroll_longPressGesture_selectAndDrag() = runVerticalTest {
-        // select "text8".
-        val char50Position = positionForCharacterScrolled(50)
-        onTextField.performTouchInput { longPress(char50Position) }
-        assertSelectionEquals(48 to 53)
-        assertOneMagnifierExistsAt(char50Position)
-
-        // Backwards select through "text7" so that the selection is "text7 ".
-        val char46Position = positionForCharacterScrolled(46)
-        onTextField.performTouchInput { moveTo(char46Position) }
-        assertSelectionEquals(42 to 53)
-        assertOneMagnifierExistsAt(char46Position)
-
-        onTextField.performTouchInput { up() }
-        assertSelectionEquals(42 to 53)
-        assertNoMagnifierExists()
-    }
-
-    // TODO(b/316940648)
-    //  The TextToolbar at the top of the screen messes up the popup position calculations,
-    //  so suppress SDKs that don't have the floating popup.
-    @SdkSuppress(minSdkVersion = 23)
-    @Test
-    fun whenVerticalScroll_handleGesture_drag() = runVerticalTest {
-        // select "text8".
-        onTextField.performTouchInput { longClick(positionForCharacterScrolled(50)) }
-        assertSelectionEquals(48 to 53)
-        assertNoMagnifierExists()
-
-        // Backwards select through "text7" so that the selection is "text7 ".
-        rule.withHandlePressed(Handle.SelectionStart) {
-            assertSelectionEquals(48 to 53)
-            assertOneMagnifierExistsAt(positionForCharacterScrolled(48))
-            moveHandleToCharacter(45)
-            assertSelectionEquals(42 to 53)
-            assertOneMagnifierExistsAt(positionForCharacterScrolled(45))
-        }
-        assertSelectionEquals(42 to 53)
-        assertNoMagnifierExists()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt
deleted file mode 100644
index b20e406..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.performKeyInput
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextClearance
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.text.TextRange
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2UndoTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun canUndo_imeInsert() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        rule.onNode(hasSetTextAction()).performTextInput(", World")
-        state.assertText("Hello, World")
-
-        state.undoState.undo()
-        state.assertText("Hello")
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
-    }
-
-    @Test
-    fun canRedo_imeInsert() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        rule.onNode(hasSetTextAction()).performTextInput(", World")
-
-        state.undoState.undo()
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
-
-        state.undoState.redo()
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
-    }
-
-    @Test
-    fun undoMerges_imeInserts() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        rule.onNode(hasSetTextAction()).typeText(", World")
-        state.assertText("Hello, World")
-
-        state.undoState.undo()
-        state.assertText("Hello")
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
-    }
-
-    @Test
-    fun undoMerges_imeInserts_onlyInForwardsDirection() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            performTextInput(", World")
-            performTextInputSelection(TextRange(5))
-            performTextInput(" Compose")
-        }
-        state.assertText("Hello Compose, World")
-
-        state.undoState.undo()
-        state.assertText("Hello, World")
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
-
-        state.undoState.undo()
-        state.assertText("Hello")
-        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
-    }
-
-    @Test
-    fun undoMerges_deletes() {
-        val state = TextFieldState("Hello, World", TextRange(12))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            requestFocus()
-            performKeyInput {
-                repeat(12) {
-                    pressKey(Key.Backspace)
-                }
-            }
-        }
-        state.assertTextAndSelection("", TextRange.Zero)
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("Hello, World", TextRange(12))
-    }
-
-    @Test
-    fun undoDoesNotMerge_deletes_inBothDirections() {
-        val state = TextFieldState("Hello, World", TextRange(6))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            requestFocus()
-            performKeyInput {
-                repeat(6) {
-                    pressKey(Key.Backspace)
-                }
-                repeat(6) {
-                    pressKey(Key.Delete)
-                }
-            }
-        }
-        state.assertTextAndSelection("", TextRange.Zero)
-
-        state.undoState.undo()
-        state.assertTextAndSelection(" World", TextRange(0))
-
-        state.undoState.undo()
-        state.assertTextAndSelection("Hello, World", TextRange(6))
-    }
-
-    @Test
-    fun undo_revertsSelection() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            performTextInputSelection(TextRange(0, 5))
-            performTextInput("a")
-        }
-        state.assertTextAndSelection("a", TextRange(1))
-
-        state.undoState.undo()
-        state.assertTextAndSelection("Hello", TextRange(0, 5))
-    }
-
-    @Test
-    fun redo_revertsSelection() {
-        val state = TextFieldState("Hello", TextRange(5))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            performTextInputSelection(TextRange(2))
-            performTextInput(" abc ")
-        }
-
-        state.assertTextAndSelection("He abc llo", TextRange(7))
-
-        state.undoState.undo()
-
-        rule.runOnIdle {
-            assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7))
-        }
-
-        state.undoState.redo()
-
-        state.assertTextAndSelection("He abc llo", TextRange(7))
-    }
-
-    @Test
-    fun variousEditOperations() {
-        val state = TextFieldState()
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            typeText("abc def")
-            performTextInputSelection(TextRange(4))
-            typeText("123 ")
-            performTextInputSelection(TextRange(0, 3))
-            typeText("ghi")
-            performTextClearance()
-        }
-        state.assertTextAndSelection("", TextRange.Zero)
-        state.undoState.undo()
-        state.assertTextAndSelection("ghi 123 def", TextRange(3))
-        state.undoState.undo()
-        state.assertTextAndSelection("g 123 def", TextRange(1))
-        state.undoState.undo()
-        state.assertTextAndSelection("abc 123 def", TextRange(0, 3))
-        state.undoState.undo()
-        state.assertTextAndSelection("abc def", TextRange(4))
-        state.undoState.undo()
-        state.assertTextAndSelection("", TextRange.Zero)
-        assertThat(state.undoState.canUndo).isFalse()
-    }
-
-    @Test
-    fun clearHistory_removesAllUndoAndRedo() {
-        val state = TextFieldState()
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            typeText("abc def")
-            performTextInputSelection(TextRange(4))
-            typeText("123 ")
-            performTextInputSelection(TextRange(0, 3))
-            typeText("ghi")
-            performTextClearance()
-        }
-        rule.waitForIdle()
-        state.undoState.undo()
-        rule.waitForIdle()
-        state.undoState.undo()
-        rule.waitForIdle()
-        state.undoState.undo()
-
-        rule.runOnIdle {
-            assertThat(state.undoState.canUndo).isTrue()
-            assertThat(state.undoState.canRedo).isTrue()
-        }
-
-        state.undoState.clearHistory()
-
-        rule.runOnIdle {
-            assertThat(state.undoState.canUndo).isFalse()
-            assertThat(state.undoState.canRedo).isFalse()
-        }
-    }
-
-    @Test
-    fun paste_neverMerges() {
-        val state = TextFieldState()
-        val clipboardManager = FakeClipboardManager("ghi")
-
-        rule.setContent {
-            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
-                BasicTextField2(state)
-            }
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            typeText("abc def ")
-            performSemanticsAction(SemanticsActions.PasteText)
-            typeText(" jkl")
-        }
-        state.undoState.undo()
-
-        state.assertTextAndSelection("abc def ghi", TextRange(11))
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("abc def ", TextRange(8))
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("", TextRange.Zero)
-    }
-
-    @Test
-    fun cut_neverMerges() {
-        val state = TextFieldState("abc def ghi", TextRange(11))
-
-        rule.setContent {
-            BasicTextField2(state)
-        }
-
-        with(rule.onNode(hasSetTextAction())) {
-            requestFocus()
-            repeat(4) {
-                performKeyInput {
-                    pressKey(Key.Backspace)
-                }
-            }
-            performTextInputSelection(TextRange(4, 7))
-            performSemanticsAction(SemanticsActions.CutText)
-            repeat(4) {
-                performKeyInput {
-                    pressKey(Key.Backspace)
-                }
-            }
-        }
-        state.assertTextAndSelection("", TextRange.Zero)
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("abc ", TextRange(4))
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("abc def", TextRange(4, 7))
-
-        state.undoState.undo()
-
-        state.assertTextAndSelection("abc def ghi", TextRange(11))
-    }
-
-    private fun SemanticsNodeInteraction.typeText(text: String) {
-        text.forEach { performTextInput(it.toString()) }
-    }
-
-    private fun TextFieldState.assertText(text: String) {
-        rule.runOnIdle {
-            assertThat(this.text.toString()).isEqualTo(text)
-        }
-    }
-
-    private fun TextFieldState.assertTextAndSelection(text: String, selection: TextRange) {
-        rule.runOnIdle {
-            assertThat(this.text.toString()).isEqualTo(text)
-            assertThat(this.text.selectionInChars).isEqualTo(selection)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
index fff44ec..aa3c05f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
@@ -36,8 +36,8 @@
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.CoreTextField
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.input.InputMethodInterceptor
-import androidx.compose.foundation.text2.input.TestSoftwareKeyboardController
+import androidx.compose.foundation.text.input.InputMethodInterceptor
+import androidx.compose.foundation.text.input.TestSoftwareKeyboardController
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
index 41c26ea..b7250a8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.textfield
 
 import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text2.input.InputMethodInterceptor
+import androidx.compose.foundation.text.input.InputMethodInterceptor
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.geometry.Offset
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index dd7a56f..bca9582 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -43,9 +43,9 @@
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.computeSizeForDefaultText
+import androidx.compose.foundation.text.input.InputMethodInterceptor
 import androidx.compose.foundation.text.selection.fetchTextLayoutResult
 import androidx.compose.foundation.text.selection.isSelectionHandle
-import androidx.compose.foundation.text2.input.InputMethodInterceptor
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
index 41af170..d004fc1 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
@@ -41,8 +41,6 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.semantics.SemanticsPropertyKey
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.unit.Density
@@ -151,19 +149,7 @@
         // Magnifier is only supported in >=28. So avoid doing all the work to manage the magnifier
         // state if it's not needed.
         // TODO(b/202739980) Investigate supporting Magnifier on earlier versions.
-        inspectable(
-            // Publish inspector info even if magnification isn't supported.
-            inspectorInfo = debugInspectorInfo {
-                name = "magnifier (not supported)"
-                properties["sourceCenter"] = sourceCenter
-                properties["magnifierCenter"] = magnifierCenter
-                properties["zoom"] = zoom
-                properties["size"] = size
-                properties["cornerRadius"] = cornerRadius
-                properties["elevation"] = elevation
-                properties["clippingEnabled"] = clippingEnabled
-            }
-        ) { this }
+        this
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
new file mode 100644
index 0000000..89261da
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text.input.internal
+
+import android.util.Log
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardType
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Enable to print logs during debugging, see [logDebug]. */
+@VisibleForTesting
+internal const val TIA_DEBUG = false
+private const val TIA_TAG = "AndroidTextInputSession"
+
+internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
+    state: TransformedTextFieldState,
+    layoutState: TextLayoutState,
+    imeOptions: ImeOptions,
+    receiveContentConfiguration: ReceiveContentConfiguration?,
+    onImeAction: ((ImeAction) -> Unit)?
+): Nothing {
+    platformSpecificTextInputSession(
+        state = state,
+        layoutState = layoutState,
+        imeOptions = imeOptions,
+        receiveContentConfiguration = receiveContentConfiguration,
+        onImeAction = onImeAction,
+        composeImm = ComposeInputMethodManager(view)
+    )
+}
+
+@VisibleForTesting
+internal suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
+    state: TransformedTextFieldState,
+    layoutState: TextLayoutState,
+    imeOptions: ImeOptions,
+    receiveContentConfiguration: ReceiveContentConfiguration?,
+    onImeAction: ((ImeAction) -> Unit)?,
+    composeImm: ComposeInputMethodManager
+): Nothing {
+    coroutineScope {
+        launch(start = CoroutineStart.UNDISPATCHED) {
+            state.collectImeNotifications { old, new ->
+                val needUpdateSelection =
+                    (old.selectionInChars != new.selectionInChars) ||
+                        old.compositionInChars != new.compositionInChars
+                if (needUpdateSelection) {
+                    composeImm.updateSelection(
+                        selectionStart = new.selectionInChars.min,
+                        selectionEnd = new.selectionInChars.max,
+                        compositionStart = new.compositionInChars?.min ?: -1,
+                        compositionEnd = new.compositionInChars?.max ?: -1
+                    )
+                }
+
+                // No need to restart the IME if keyboard type is configured as Password. IME
+                // should not keep an internal input state if the content needs to be secured.
+                if (!old.contentEquals(new) && imeOptions.keyboardType != KeyboardType.Password) {
+                    composeImm.restartInput()
+                }
+            }
+        }
+
+        val cursorUpdatesController = CursorAnchorInfoController(
+            composeImm = composeImm,
+            textFieldState = state,
+            textLayoutState = layoutState,
+            monitorScope = this,
+        )
+
+        startInputMethod { outAttrs ->
+            logDebug { "createInputConnection(value=\"${state.visualText}\")" }
+
+            val textInputSession = object : TextInputSession {
+                override val text: TextFieldCharSequence
+                    get() = state.visualText
+
+                override fun requestEdit(
+                    notifyImeOfChanges: Boolean,
+                    block: EditingBuffer.() -> Unit
+                ) {
+                    state.editUntransformedTextAsUser(
+                        notifyImeOfChanges = notifyImeOfChanges,
+                        block = block
+                    )
+                }
+
+                override fun sendKeyEvent(keyEvent: KeyEvent) {
+                    composeImm.sendKeyEvent(keyEvent)
+                }
+
+                override fun onImeAction(imeAction: ImeAction) {
+                    onImeAction?.invoke(imeAction)
+                }
+
+                override fun onCommitContent(transferableContent: TransferableContent): Boolean {
+                    return receiveContentConfiguration?.onCommitContent(transferableContent)
+                        ?: false
+                }
+
+                override fun requestCursorUpdates(cursorUpdateMode: Int) {
+                    cursorUpdatesController.requestUpdates(cursorUpdateMode)
+                }
+            }
+
+            val hintMediaTypes = receiveContentConfiguration?.hintMediaTypes
+            val contentMimeTypes: Array<String>? =
+                if (!hintMediaTypes.isNullOrEmpty()) {
+                    val arr = Array(hintMediaTypes.size) { "" }
+                    hintMediaTypes.forEachIndexed { i, mediaType ->
+                        arr[i] = mediaType.representation
+                    }
+                    arr
+                } else {
+                    null
+                }
+
+            outAttrs.update(
+                text = state.visualText,
+                selection = state.visualText.selectionInChars,
+                imeOptions = imeOptions,
+                contentMimeTypes = contentMimeTypes
+            )
+            StatelessInputConnection(textInputSession, outAttrs)
+        }
+    }
+}
+
+private fun logDebug(tag: String = TIA_TAG, content: () -> String) {
+    if (TIA_DEBUG) {
+        Log.d(tag, content())
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt
new file mode 100644
index 0000000..643c828
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.content.Context
+import android.os.Build
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.BaseInputConnection
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.ExtractedText
+import android.view.inputmethod.InputMethodManager
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.SoftwareKeyboardControllerCompat
+import org.jetbrains.annotations.TestOnly
+
+/**
+ * Compatibility interface for [InputMethodManager] to use in Compose text input systems.
+ *
+ * This interface is responsible for handling the calls made to platform InputMethodManager in
+ * Android. There are different ways to show and hide software keyboard depending on API level.
+ *
+ * This interface also allows us to fake out the IMM for testing. For that reason, it should match
+ * the relevant platform [InputMethodManager] APIs as closely as possible.
+ */
+internal interface ComposeInputMethodManager {
+    fun restartInput()
+
+    fun showSoftInput()
+
+    fun hideSoftInput()
+
+    fun updateExtractedText(
+        token: Int,
+        extractedText: ExtractedText
+    )
+
+    fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    )
+
+    fun updateCursorAnchorInfo(info: CursorAnchorInfo)
+
+    /**
+     * Sends a [KeyEvent] originated from an InputMethod to the Window. This is a necessary
+     * delegation when the InputConnection itself does not handle the received event.
+     */
+    fun sendKeyEvent(event: KeyEvent)
+}
+
+/**
+ * Creates a new instance of [ComposeInputMethodManager].
+ *
+ * The value returned by this function can be changed for tests by calling
+ * [overrideComposeInputMethodManagerFactoryForTests].
+ */
+internal fun ComposeInputMethodManager(view: View): ComposeInputMethodManager =
+    ComposeInputMethodManagerFactory(view)
+
+/** This lets us swap out the implementation in our own tests. */
+private var ComposeInputMethodManagerFactory: (View) -> ComposeInputMethodManager = { view ->
+    when {
+        Build.VERSION.SDK_INT >= 24 -> ComposeInputMethodManagerImplApi24(view)
+        else -> ComposeInputMethodManagerImplApi21(view)
+    }
+}
+
+/**
+ * Sets the factory used by [ComposeInputMethodManager] to create instances and returns the previous
+ * factory.
+ *
+ * Any test that calls this should call it again to restore the factory after the test finishes, to
+ * avoid breaking unrelated tests.
+ */
+@TestOnly
+@VisibleForTesting
+internal fun overrideComposeInputMethodManagerFactoryForTests(
+    factory: (View) -> ComposeInputMethodManager
+): (View) -> ComposeInputMethodManager {
+    val oldFactory = ComposeInputMethodManagerFactory
+    ComposeInputMethodManagerFactory = factory
+    return oldFactory
+}
+
+private abstract class ComposeInputMethodManagerImpl(protected val view: View) :
+    ComposeInputMethodManager {
+
+    private var imm: InputMethodManager? = null
+
+    private val softwareKeyboardControllerCompat =
+        SoftwareKeyboardControllerCompat(view)
+
+    override fun restartInput() {
+        requireImm().restartInput(view)
+    }
+
+    override fun showSoftInput() {
+        softwareKeyboardControllerCompat.show()
+    }
+
+    override fun hideSoftInput() {
+        softwareKeyboardControllerCompat.hide()
+    }
+
+    override fun updateExtractedText(
+        token: Int,
+        extractedText: ExtractedText
+    ) {
+        requireImm().updateExtractedText(view, token, extractedText)
+    }
+
+    override fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    ) {
+        requireImm().updateSelection(
+            view,
+            selectionStart,
+            selectionEnd,
+            compositionStart,
+            compositionEnd
+        )
+    }
+
+    override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
+        requireImm().updateCursorAnchorInfo(view, info)
+    }
+
+    protected fun requireImm(): InputMethodManager = imm ?: createImm().also { imm = it }
+
+    private fun createImm() =
+        view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+}
+
+private open class ComposeInputMethodManagerImplApi21(view: View) :
+    ComposeInputMethodManagerImpl(view) {
+
+    /**
+     * Prior to API24, the safest way to delegate IME originated KeyEvents to the window was
+     * through BaseInputConnection.
+     */
+    private var baseInputConnection: BaseInputConnection? = null
+
+    override fun sendKeyEvent(event: KeyEvent) {
+        val baseInputConnection = baseInputConnection
+            ?: BaseInputConnection(view, false).also { baseInputConnection = it }
+        baseInputConnection.sendKeyEvent(event)
+    }
+}
+
+@RequiresApi(24)
+private open class ComposeInputMethodManagerImplApi24(view: View) :
+    ComposeInputMethodManagerImplApi21(view) {
+
+    override fun sendKeyEvent(event: KeyEvent) {
+        requireImm().dispatchKeyEventFromInputMethod(view, event)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
index 16db015..99d3944 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
@@ -19,37 +19,25 @@
 import android.graphics.Matrix
 import android.os.Build
 import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.EditorBoundsInfo
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toAndroidRectF
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.OffsetMapping
-import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.style.ResolvedTextDirection
 
 /**
  * Helper function to build
  * [CursorAnchorInfo](https://developer.android.com/reference/android/view/inputmethod/CursorAnchorInfo).
  *
- * @param textFieldValue the text field's [TextFieldValue]
- * @param offsetMapping the offset mapping for the text field's visual transformation
- * @param textLayoutResult the text field's [TextLayoutResult]
  * @param matrix matrix that transforms local coordinates into screen coordinates
  * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an empty
  *   rectangle if the text field is not visible
  * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an empty
  *   rectangle if the decoration box is not visible
- * @param includeInsertionMarker whether to include insertion marker info in the CursorAnchorInfo
- * @param includeCharacterBounds whether to include character bounds info in the CursorAnchorInfo
- * @param includeEditorBounds whether to include editor bounds info in the CursorAnchorInfo
- * @param includeLineBounds whether to include line bounds info in the CursorAnchorInfo
  */
 internal fun CursorAnchorInfo.Builder.build(
-    textFieldValue: TextFieldValue,
-    offsetMapping: OffsetMapping,
+    text: CharSequence,
+    selection: TextRange,
+    composition: TextRange?,
     textLayoutResult: TextLayoutResult,
     matrix: Matrix,
     innerTextFieldBounds: Rect,
@@ -63,27 +51,26 @@
 
     setMatrix(matrix)
 
-    val selectionStart = textFieldValue.selection.min
-    val selectionEnd = textFieldValue.selection.max
+    val selectionStart = selection.min
+    val selectionEnd = selection.max
     setSelectionRange(selectionStart, selectionEnd)
 
     if (includeInsertionMarker) {
-        setInsertionMarker(selectionStart, offsetMapping, textLayoutResult, innerTextFieldBounds)
+        setInsertionMarker(selectionStart, textLayoutResult, innerTextFieldBounds)
     }
 
     if (includeCharacterBounds) {
-        val compositionStart = textFieldValue.composition?.min ?: -1
-        val compositionEnd = textFieldValue.composition?.max ?: -1
+        val compositionStart = composition?.min ?: -1
+        val compositionEnd = composition?.max ?: -1
 
         if (compositionStart in 0 until compositionEnd) {
             setComposingText(
                 compositionStart,
-                textFieldValue.text.subSequence(compositionStart, compositionEnd)
+                text.subSequence(compositionStart, compositionEnd)
             )
             addCharacterBounds(
                 compositionStart,
                 compositionEnd,
-                offsetMapping,
                 textLayoutResult,
                 innerTextFieldBounds
             )
@@ -107,19 +94,16 @@
 
 private fun CursorAnchorInfo.Builder.setInsertionMarker(
     selectionStart: Int,
-    offsetMapping: OffsetMapping,
     textLayoutResult: TextLayoutResult,
     innerTextFieldBounds: Rect
 ): CursorAnchorInfo.Builder {
     if (selectionStart < 0) return this
 
-    val selectionStartTransformed = offsetMapping.originalToTransformed(selectionStart)
-    val cursorRect = textLayoutResult.getCursorRect(selectionStartTransformed)
+    val cursorRect = textLayoutResult.getCursorRect(selectionStart)
     val x = cursorRect.left.coerceIn(0f, textLayoutResult.size.width.toFloat())
     val isTopVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.top)
     val isBottomVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.bottom)
-    val isRtl =
-        textLayoutResult.getBidiRunDirection(selectionStartTransformed) == ResolvedTextDirection.Rtl
+    val isRtl = textLayoutResult.getBidiRunDirection(selectionStart) == ResolvedTextDirection.Rtl
 
     var flags = 0
     if (isTopVisible || isBottomVisible) flags = flags or CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
@@ -137,28 +121,19 @@
 private fun CursorAnchorInfo.Builder.addCharacterBounds(
     startOffset: Int,
     endOffset: Int,
-    offsetMapping: OffsetMapping,
     textLayoutResult: TextLayoutResult,
     innerTextFieldBounds: Rect
 ): CursorAnchorInfo.Builder {
-    val startOffsetTransformed = offsetMapping.originalToTransformed(startOffset)
-    val endOffsetTransformed = offsetMapping.originalToTransformed(endOffset)
-    val array = FloatArray((endOffsetTransformed - startOffsetTransformed) * 4)
+    val array = FloatArray((endOffset - startOffset) * 4)
     textLayoutResult.multiParagraph.fillBoundingBoxes(
         TextRange(
-            startOffsetTransformed,
-            endOffsetTransformed
+            startOffset,
+            endOffset
         ), array, 0
     )
 
     for (offset in startOffset until endOffset) {
-        // It's possible for a visual transformation to hide some characters. If the character at
-        // the offset is hidden, then offsetTransformed points to the last preceding character that
-        // is not hidden. Since the CursorAnchorInfo API doesn't define what to return in this case,
-        // and visual transformations hiding characters should be rare, returning the bounds for the
-        // last preceding character is the simplest behavior.
-        val offsetTransformed = offsetMapping.originalToTransformed(offset)
-        val arrayIndex = 4 * (offsetTransformed - startOffsetTransformed)
+        val arrayIndex = 4 * (offset - startOffset)
         val rect =
             Rect(
                 array[arrayIndex] /* left */,
@@ -177,7 +152,7 @@
         ) {
             flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
         }
-        if (textLayoutResult.getBidiRunDirection(offsetTransformed) == ResolvedTextDirection.Rtl) {
+        if (textLayoutResult.getBidiRunDirection(offset) == ResolvedTextDirection.Rtl) {
             flags = flags or CursorAnchorInfo.FLAG_IS_RTL
         }
 
@@ -185,54 +160,3 @@
     }
     return this
 }
-
-@RequiresApi(33)
-private object CursorAnchorInfoApi33Helper {
-    @JvmStatic
-    @DoNotInline
-    fun setEditorBoundsInfo(
-        builder: CursorAnchorInfo.Builder,
-        decorationBoxBounds: Rect
-    ): CursorAnchorInfo.Builder =
-        builder.setEditorBoundsInfo(
-            EditorBoundsInfo.Builder()
-                .setEditorBounds(decorationBoxBounds.toAndroidRectF())
-                .setHandwritingBounds(decorationBoxBounds.toAndroidRectF())
-                .build()
-        )
-}
-
-@RequiresApi(34)
-private object CursorAnchorInfoApi34Helper {
-    @JvmStatic
-    @DoNotInline
-    fun addVisibleLineBounds(
-        builder: CursorAnchorInfo.Builder,
-        textLayoutResult: TextLayoutResult,
-        innerTextFieldBounds: Rect
-    ): CursorAnchorInfo.Builder {
-        if (!innerTextFieldBounds.isEmpty) {
-            val firstLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.top)
-            val lastLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.bottom)
-            for (index in firstLine..lastLine) {
-                builder.addVisibleLineBounds(
-                    textLayoutResult.getLineLeft(index),
-                    textLayoutResult.getLineTop(index),
-                    textLayoutResult.getLineRight(index),
-                    textLayoutResult.getLineBottom(index)
-                )
-            }
-        }
-        return builder
-    }
-}
-
-/**
- * Whether the point specified by the given offset lies inside or on an edge of this rectangle.
- *
- * Note this differs from [Rect.contains] which returns false for points on the bottom or right
- * edges.
- */
-private fun Rect.containsInclusive(x: Float, y: Float): Boolean {
-    return x in left..right && y in top..bottom
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
index c33c1d3..8f1f662 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
@@ -16,40 +16,95 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import android.os.Build
 import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.InputConnection
+import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
+import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
+import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
+import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.selection.visibleBounds
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.setFrom
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.input.OffsetMapping
-import androidx.compose.ui.text.input.TextFieldValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
 
+@OptIn(ExperimentalFoundationApi::class)
 internal class CursorAnchorInfoController(
-    private val localToScreen: (Matrix) -> Unit,
-    private val inputMethodManager: InputMethodManager
+    private val textFieldState: TransformedTextFieldState,
+    private val textLayoutState: TextLayoutState,
+    private val composeImm: ComposeInputMethodManager,
+    private val monitorScope: CoroutineScope,
 ) {
-    private val lock = Any()
-
     private var monitorEnabled = false
     private var hasPendingImmediateRequest = false
+    private var monitorJob: Job? = null
 
     private var includeInsertionMarker = false
     private var includeCharacterBounds = false
     private var includeEditorBounds = false
     private var includeLineBounds = false
 
-    private var textFieldValue: TextFieldValue? = null
-    private var textLayoutResult: TextLayoutResult? = null
-    private var offsetMapping: OffsetMapping? = null
-    private var innerTextFieldBounds: Rect? = null
-    private var decorationBoxBounds: Rect? = null
-
     private val builder = CursorAnchorInfo.Builder()
     private val matrix = Matrix()
     private val androidMatrix = android.graphics.Matrix()
 
     /**
-     * Requests [CursorAnchorInfo] updates to be provided to the [InputMethodManager].
+     * Requests [CursorAnchorInfo] updates to be provided to the [ComposeInputMethodManager].
+     */
+    fun requestUpdates(cursorUpdateMode: Int) {
+        val immediate = cursorUpdateMode and InputConnection.CURSOR_UPDATE_IMMEDIATE != 0
+        val monitor = cursorUpdateMode and InputConnection.CURSOR_UPDATE_MONITOR != 0
+
+        // Before Android T, filter flags are not used, and insertion marker and character bounds
+        // info are always included.
+        var includeInsertionMarker = true
+        var includeCharacterBounds = true
+        var includeEditorBounds = false
+        var includeLineBounds = false
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            includeInsertionMarker = cursorUpdateMode and CURSOR_UPDATE_FILTER_INSERTION_MARKER != 0
+            includeCharacterBounds = cursorUpdateMode and CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS != 0
+            includeEditorBounds = cursorUpdateMode and CURSOR_UPDATE_FILTER_EDITOR_BOUNDS != 0
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                includeLineBounds =
+                    cursorUpdateMode and CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS != 0
+            }
+            // If no filter flags are used, then all info should be included.
+            if (
+                !includeInsertionMarker &&
+                !includeCharacterBounds &&
+                !includeEditorBounds &&
+                !includeLineBounds
+            ) {
+                includeInsertionMarker = true
+                includeCharacterBounds = true
+                includeEditorBounds = true
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                    includeLineBounds = true
+                }
+            }
+        }
+
+        requestUpdates(
+            immediate = immediate,
+            monitor = monitor,
+            includeInsertionMarker = includeInsertionMarker,
+            includeCharacterBounds = includeCharacterBounds,
+            includeEditorBounds = includeEditorBounds,
+            includeLineBounds = includeLineBounds
+        )
+    }
+
+    /**
+     * Requests [CursorAnchorInfo] updates to be provided to the [ComposeInputMethodManager].
      *
      * Combinations of [immediate] and [monitor] are used to specify when to provide updates. If
      * these are both false, then no further updates will be provided.
@@ -65,14 +120,14 @@
      * @param includeEditorBounds whether to include editor bounds information
      * @param includeLineBounds whether to include line bounds information
      */
-    fun requestUpdate(
+    private fun requestUpdates(
         immediate: Boolean,
         monitor: Boolean,
         includeInsertionMarker: Boolean,
         includeCharacterBounds: Boolean,
         includeEditorBounds: Boolean,
         includeLineBounds: Boolean
-    ) = synchronized(lock) {
+    ) {
         this.includeInsertionMarker = includeInsertionMarker
         this.includeCharacterBounds = includeCharacterBounds
         this.includeEditorBounds = includeEditorBounds
@@ -80,80 +135,68 @@
 
         if (immediate) {
             hasPendingImmediateRequest = true
-            if (textFieldValue != null) {
-                updateCursorAnchorInfo()
-            }
+            calculateCursorAnchorInfo()?.let(composeImm::updateCursorAnchorInfo)
         }
         monitorEnabled = monitor
+        startOrStopMonitoring()
     }
 
     /**
-     * Notify the controller of layout and position changes.
-     *
-     * @param textFieldValue the text field's [TextFieldValue]
-     * @param offsetMapping the offset mapping for the visual transformation
-     * @param textLayoutResult the text field's [TextLayoutResult]
-     * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an
-     *   empty rectangle if the text field is not visible
-     * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an
-     *   empty rectangle if the decoration box is not visible
+     * If [monitorEnabled] is rue, observes changes to [textLayoutState] and monitor state (from
+     * [requestUpdates]) and sends updates to the [composeImm] as required until cancelled.
+     * Otherwise, cancels any monitor [Job].
      */
-    fun updateTextLayoutResult(
-        textFieldValue: TextFieldValue,
-        offsetMapping: OffsetMapping,
-        textLayoutResult: TextLayoutResult,
-        innerTextFieldBounds: Rect,
-        decorationBoxBounds: Rect
-    ) = synchronized(lock) {
-        this.textFieldValue = textFieldValue
-        this.offsetMapping = offsetMapping
-        this.textLayoutResult = textLayoutResult
-        this.innerTextFieldBounds = innerTextFieldBounds
-        this.decorationBoxBounds = decorationBoxBounds
-
-        if (hasPendingImmediateRequest || monitorEnabled) {
-            updateCursorAnchorInfo()
+    private fun startOrStopMonitoring() {
+        if (monitorEnabled) {
+            if (monitorJob?.isActive != true) {
+                monitorJob = monitorScope.launch(start = CoroutineStart.UNDISPATCHED) {
+                    // TODO (b/291327369) Confirm that we are sending updates at the right time.
+                    snapshotFlow { calculateCursorAnchorInfo() }
+                        .drop(1)
+                        .filterNotNull()
+                        .collect { composeImm.updateCursorAnchorInfo(it) }
+                }
+            }
+        } else {
+            monitorJob?.cancel()
+            monitorJob = null
         }
     }
 
-    /**
-     * Invalidate the last received layout and position data.
-     *
-     * This should be called when the [TextFieldValue] has changed, so the last received layout and
-     * position data is no longer valid. [CursorAnchorInfo] updates will not be sent until new
-     * layout and position data is received.
-     */
-    fun invalidate() = synchronized(lock) {
-        textFieldValue = null
-        offsetMapping = null
-        textLayoutResult = null
-        innerTextFieldBounds = null
-        decorationBoxBounds = null
-    }
+    private fun calculateCursorAnchorInfo(): CursorAnchorInfo? {
+        // State reads
+        val coreCoordinates = textLayoutState.coreNodeCoordinates
+            ?.takeIf { it.isAttached }
+            ?: return null
+        val decorationBoxCoordinates = textLayoutState.decoratorNodeCoordinates
+            ?.takeIf { it.isAttached }
+            ?: return null
+        val textLayoutResult = textLayoutState.layoutResult
+            ?: return null
+        val text = textFieldState.visualText
 
-    private fun updateCursorAnchorInfo() {
-        if (!inputMethodManager.isActive()) return
-
-        matrix.reset()
         // Updates matrix to transform text field local coordinates to screen coordinates.
-        localToScreen(matrix)
+        matrix.reset()
+        coreCoordinates.transformToScreen(matrix)
         androidMatrix.setFrom(matrix)
 
-        inputMethodManager.updateCursorAnchorInfo(
-            builder.build(
-                textFieldValue!!,
-                offsetMapping!!,
-                textLayoutResult!!,
-                androidMatrix,
-                innerTextFieldBounds!!,
-                decorationBoxBounds!!,
-                includeInsertionMarker,
-                includeCharacterBounds,
-                includeEditorBounds,
-                includeLineBounds
-            )
+        val innerTextFieldBounds: Rect = coreCoordinates.visibleBounds()
+        val decorationBoxBounds: Rect = coreCoordinates.localBoundingBoxOf(
+            decorationBoxCoordinates,
+            clipBounds = false
         )
-
-        hasPendingImmediateRequest = false
+        return builder.build(
+            text,
+            text.selectionInChars,
+            text.compositionInChars,
+            textLayoutResult,
+            androidMatrix,
+            innerTextFieldBounds,
+            decorationBoxBounds,
+            includeInsertionMarker,
+            includeCharacterBounds,
+            includeEditorBounds,
+            includeLineBounds
+        )
     }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputEventCallback2.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputEventCallback2.android.kt
index 3da03ae..39d6592 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputEventCallback2.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputEventCallback2.android.kt
@@ -46,7 +46,8 @@
     /**
      * Called when IME requests cursor information updates.
      *
-     * @see CursorAnchorInfoController.requestUpdate
+     * @see LegacyCursorAnchorInfoController.requestUpdate
+     * @see CursorAnchorInfoController.requestUpdates
      */
     fun onRequestCursorAnchorInfo(
         immediate: Boolean,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt
new file mode 100644
index 0000000..5702c47
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import android.graphics.Matrix
+import android.os.Build
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.EditorBoundsInfo
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.toAndroidRectF
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.style.ResolvedTextDirection
+
+/**
+ * Helper function to build
+ * [CursorAnchorInfo](https://developer.android.com/reference/android/view/inputmethod/CursorAnchorInfo).
+ *
+ * @param textFieldValue the text field's [TextFieldValue]
+ * @param offsetMapping the offset mapping for the text field's visual transformation
+ * @param textLayoutResult the text field's [TextLayoutResult]
+ * @param matrix matrix that transforms local coordinates into screen coordinates
+ * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an empty
+ *   rectangle if the text field is not visible
+ * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an empty
+ *   rectangle if the decoration box is not visible
+ * @param includeInsertionMarker whether to include insertion marker info in the CursorAnchorInfo
+ * @param includeCharacterBounds whether to include character bounds info in the CursorAnchorInfo
+ * @param includeEditorBounds whether to include editor bounds info in the CursorAnchorInfo
+ * @param includeLineBounds whether to include line bounds info in the CursorAnchorInfo
+ */
+internal fun CursorAnchorInfo.Builder.build(
+    textFieldValue: TextFieldValue,
+    offsetMapping: OffsetMapping,
+    textLayoutResult: TextLayoutResult,
+    matrix: Matrix,
+    innerTextFieldBounds: Rect,
+    decorationBoxBounds: Rect,
+    includeInsertionMarker: Boolean = true,
+    includeCharacterBounds: Boolean = true,
+    includeEditorBounds: Boolean = true,
+    includeLineBounds: Boolean = true
+): CursorAnchorInfo {
+    reset()
+
+    setMatrix(matrix)
+
+    val selectionStart = textFieldValue.selection.min
+    val selectionEnd = textFieldValue.selection.max
+    setSelectionRange(selectionStart, selectionEnd)
+
+    if (includeInsertionMarker) {
+        setInsertionMarker(selectionStart, offsetMapping, textLayoutResult, innerTextFieldBounds)
+    }
+
+    if (includeCharacterBounds) {
+        val compositionStart = textFieldValue.composition?.min ?: -1
+        val compositionEnd = textFieldValue.composition?.max ?: -1
+
+        if (compositionStart in 0 until compositionEnd) {
+            setComposingText(
+                compositionStart,
+                textFieldValue.text.subSequence(compositionStart, compositionEnd)
+            )
+            addCharacterBounds(
+                compositionStart,
+                compositionEnd,
+                offsetMapping,
+                textLayoutResult,
+                innerTextFieldBounds
+            )
+        }
+    }
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && includeEditorBounds) {
+        CursorAnchorInfoApi33Helper.setEditorBoundsInfo(this, decorationBoxBounds)
+    }
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && includeLineBounds) {
+        CursorAnchorInfoApi34Helper.addVisibleLineBounds(
+            this,
+            textLayoutResult,
+            innerTextFieldBounds
+        )
+    }
+
+    return build()
+}
+
+private fun CursorAnchorInfo.Builder.setInsertionMarker(
+    selectionStart: Int,
+    offsetMapping: OffsetMapping,
+    textLayoutResult: TextLayoutResult,
+    innerTextFieldBounds: Rect
+): CursorAnchorInfo.Builder {
+    if (selectionStart < 0) return this
+
+    val selectionStartTransformed = offsetMapping.originalToTransformed(selectionStart)
+    val cursorRect = textLayoutResult.getCursorRect(selectionStartTransformed)
+    val x = cursorRect.left.coerceIn(0f, textLayoutResult.size.width.toFloat())
+    val isTopVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.top)
+    val isBottomVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.bottom)
+    val isRtl =
+        textLayoutResult.getBidiRunDirection(selectionStartTransformed) == ResolvedTextDirection.Rtl
+
+    var flags = 0
+    if (isTopVisible || isBottomVisible) flags = flags or CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
+    if (!isTopVisible || !isBottomVisible)
+        flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
+    if (isRtl) flags = flags or CursorAnchorInfo.FLAG_IS_RTL
+
+    // Sets the location of the text insertion point (zero width cursor) as a rectangle in local
+    // coordinates.
+    setInsertionMarkerLocation(x, cursorRect.top, cursorRect.bottom, cursorRect.bottom, flags)
+
+    return this
+}
+
+private fun CursorAnchorInfo.Builder.addCharacterBounds(
+    startOffset: Int,
+    endOffset: Int,
+    offsetMapping: OffsetMapping,
+    textLayoutResult: TextLayoutResult,
+    innerTextFieldBounds: Rect
+): CursorAnchorInfo.Builder {
+    val startOffsetTransformed = offsetMapping.originalToTransformed(startOffset)
+    val endOffsetTransformed = offsetMapping.originalToTransformed(endOffset)
+    val array = FloatArray((endOffsetTransformed - startOffsetTransformed) * 4)
+    textLayoutResult.multiParagraph.fillBoundingBoxes(
+        TextRange(
+            startOffsetTransformed,
+            endOffsetTransformed
+        ), array, 0
+    )
+
+    for (offset in startOffset until endOffset) {
+        // It's possible for a visual transformation to hide some characters. If the character at
+        // the offset is hidden, then offsetTransformed points to the last preceding character that
+        // is not hidden. Since the CursorAnchorInfo API doesn't define what to return in this case,
+        // and visual transformations hiding characters should be rare, returning the bounds for the
+        // last preceding character is the simplest behavior.
+        val offsetTransformed = offsetMapping.originalToTransformed(offset)
+        val arrayIndex = 4 * (offsetTransformed - startOffsetTransformed)
+        val rect =
+            Rect(
+                array[arrayIndex] /* left */,
+                array[arrayIndex + 1] /* top */,
+                array[arrayIndex + 2] /* right */,
+                array[arrayIndex + 3] /* bottom */
+            )
+
+        var flags = 0
+        if (innerTextFieldBounds.overlaps(rect)) {
+            flags = flags or CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
+        }
+        if (
+            !innerTextFieldBounds.containsInclusive(rect.left, rect.top) ||
+            !innerTextFieldBounds.containsInclusive(rect.right, rect.bottom)
+        ) {
+            flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
+        }
+        if (textLayoutResult.getBidiRunDirection(offsetTransformed) == ResolvedTextDirection.Rtl) {
+            flags = flags or CursorAnchorInfo.FLAG_IS_RTL
+        }
+
+        addCharacterBounds(offset, rect.left, rect.top, rect.right, rect.bottom, flags)
+    }
+    return this
+}
+
+@RequiresApi(33)
+internal object CursorAnchorInfoApi33Helper {
+    @JvmStatic
+    @DoNotInline
+    fun setEditorBoundsInfo(
+        builder: CursorAnchorInfo.Builder,
+        decorationBoxBounds: Rect
+    ): CursorAnchorInfo.Builder =
+        builder.setEditorBoundsInfo(
+            EditorBoundsInfo.Builder()
+                .setEditorBounds(decorationBoxBounds.toAndroidRectF())
+                .setHandwritingBounds(decorationBoxBounds.toAndroidRectF())
+                .build()
+        )
+}
+
+@RequiresApi(34)
+internal object CursorAnchorInfoApi34Helper {
+    @JvmStatic
+    @DoNotInline
+    fun addVisibleLineBounds(
+        builder: CursorAnchorInfo.Builder,
+        textLayoutResult: TextLayoutResult,
+        innerTextFieldBounds: Rect
+    ): CursorAnchorInfo.Builder {
+        if (!innerTextFieldBounds.isEmpty) {
+            val firstLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.top)
+            val lastLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.bottom)
+            for (index in firstLine..lastLine) {
+                builder.addVisibleLineBounds(
+                    textLayoutResult.getLineLeft(index),
+                    textLayoutResult.getLineTop(index),
+                    textLayoutResult.getLineRight(index),
+                    textLayoutResult.getLineBottom(index)
+                )
+            }
+        }
+        return builder
+    }
+}
+
+/**
+ * Whether the point specified by the given offset lies inside or on an edge of this rectangle.
+ *
+ * Note this differs from [Rect.contains] which returns false for points on the bottom or right
+ * edges.
+ */
+internal fun Rect.containsInclusive(x: Float, y: Float): Boolean {
+    return x in left..right && y in top..bottom
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt
new file mode 100644
index 0000000..bda500a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import android.view.inputmethod.CursorAnchorInfo
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.setFrom
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TextFieldValue
+
+internal class LegacyCursorAnchorInfoController(
+    private val localToScreen: (Matrix) -> Unit,
+    private val inputMethodManager: InputMethodManager
+) {
+    private val lock = Any()
+
+    private var monitorEnabled = false
+    private var hasPendingImmediateRequest = false
+
+    private var includeInsertionMarker = false
+    private var includeCharacterBounds = false
+    private var includeEditorBounds = false
+    private var includeLineBounds = false
+
+    private var textFieldValue: TextFieldValue? = null
+    private var textLayoutResult: TextLayoutResult? = null
+    private var offsetMapping: OffsetMapping? = null
+    private var innerTextFieldBounds: Rect? = null
+    private var decorationBoxBounds: Rect? = null
+
+    private val builder = CursorAnchorInfo.Builder()
+    private val matrix = Matrix()
+    private val androidMatrix = android.graphics.Matrix()
+
+    /**
+     * Requests [CursorAnchorInfo] updates to be provided to the [InputMethodManager].
+     *
+     * Combinations of [immediate] and [monitor] are used to specify when to provide updates. If
+     * these are both false, then no further updates will be provided.
+     *
+     * @param immediate whether to update with the current [CursorAnchorInfo] immediately, or as
+     *   soon as available
+     * @param monitor whether to provide [CursorAnchorInfo] updates for all future layout or
+     *   position changes
+     * @param includeInsertionMarker whether to include insertion marker (i.e. cursor) location
+     *   information
+     * @param includeCharacterBounds whether to include character bounds information for the
+     *   composition range
+     * @param includeEditorBounds whether to include editor bounds information
+     * @param includeLineBounds whether to include line bounds information
+     */
+    fun requestUpdate(
+        immediate: Boolean,
+        monitor: Boolean,
+        includeInsertionMarker: Boolean,
+        includeCharacterBounds: Boolean,
+        includeEditorBounds: Boolean,
+        includeLineBounds: Boolean
+    ) = synchronized(lock) {
+        this.includeInsertionMarker = includeInsertionMarker
+        this.includeCharacterBounds = includeCharacterBounds
+        this.includeEditorBounds = includeEditorBounds
+        this.includeLineBounds = includeLineBounds
+
+        if (immediate) {
+            hasPendingImmediateRequest = true
+            if (textFieldValue != null) {
+                updateCursorAnchorInfo()
+            }
+        }
+        monitorEnabled = monitor
+    }
+
+    /**
+     * Notify the controller of layout and position changes.
+     *
+     * @param textFieldValue the text field's [TextFieldValue]
+     * @param offsetMapping the offset mapping for the visual transformation
+     * @param textLayoutResult the text field's [TextLayoutResult]
+     * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an
+     *   empty rectangle if the text field is not visible
+     * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an
+     *   empty rectangle if the decoration box is not visible
+     */
+    fun updateTextLayoutResult(
+        textFieldValue: TextFieldValue,
+        offsetMapping: OffsetMapping,
+        textLayoutResult: TextLayoutResult,
+        innerTextFieldBounds: Rect,
+        decorationBoxBounds: Rect
+    ) = synchronized(lock) {
+        this.textFieldValue = textFieldValue
+        this.offsetMapping = offsetMapping
+        this.textLayoutResult = textLayoutResult
+        this.innerTextFieldBounds = innerTextFieldBounds
+        this.decorationBoxBounds = decorationBoxBounds
+
+        if (hasPendingImmediateRequest || monitorEnabled) {
+            updateCursorAnchorInfo()
+        }
+    }
+
+    /**
+     * Invalidate the last received layout and position data.
+     *
+     * This should be called when the [TextFieldValue] has changed, so the last received layout and
+     * position data is no longer valid. [CursorAnchorInfo] updates will not be sent until new
+     * layout and position data is received.
+     */
+    fun invalidate() = synchronized(lock) {
+        textFieldValue = null
+        offsetMapping = null
+        textLayoutResult = null
+        innerTextFieldBounds = null
+        decorationBoxBounds = null
+    }
+
+    private fun updateCursorAnchorInfo() {
+        if (!inputMethodManager.isActive()) return
+
+        matrix.reset()
+        // Updates matrix to transform text field local coordinates to screen coordinates.
+        localToScreen(matrix)
+        androidMatrix.setFrom(matrix)
+
+        inputMethodManager.updateCursorAnchorInfo(
+            builder.build(
+                textFieldValue!!,
+                offsetMapping!!,
+                textLayoutResult!!,
+                androidMatrix,
+                innerTextFieldBounds!!,
+                decorationBoxBounds!!,
+                includeInsertionMarker,
+                includeCharacterBounds,
+                includeEditorBounds,
+                includeLineBounds
+            )
+        )
+
+        hasPendingImmediateRequest = false
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
index e48a323..8cd284c 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
@@ -176,7 +176,7 @@
     internal var focusedRect: AndroidRect? = null
 
     private val cursorAnchorInfoController =
-        CursorAnchorInfoController(localToScreen, inputMethodManager)
+        LegacyCursorAnchorInfoController(localToScreen, inputMethodManager)
 
     init {
         if (DEBUG) {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt
new file mode 100644
index 0000000..4228aa1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt
@@ -0,0 +1,522 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.content.ClipData
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Parcelable
+import android.text.TextUtils
+import android.util.Log
+import android.view.KeyEvent
+import android.view.inputmethod.CompletionInfo
+import android.view.inputmethod.CorrectionInfo
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.ExtractedText
+import android.view.inputmethod.ExtractedTextRequest
+import android.view.inputmethod.InputConnection
+import android.view.inputmethod.InputConnectionWrapper
+import android.view.inputmethod.InputContentInfo
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.PlatformTransferableContent
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.getSelectedText
+import androidx.compose.foundation.text.input.getTextAfterSelection
+import androidx.compose.foundation.text.input.getTextBeforeSelection
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.ui.platform.toClipEntry
+import androidx.compose.ui.platform.toClipMetadata
+import androidx.compose.ui.text.input.ImeAction
+import androidx.core.view.inputmethod.EditorInfoCompat
+import androidx.core.view.inputmethod.InputConnectionCompat
+import androidx.core.view.inputmethod.InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
+import androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener
+import androidx.core.view.inputmethod.InputContentInfoCompat
+
+@VisibleForTesting
+internal const val SIC_DEBUG = false
+private const val STATELESS_TAG = "StatelessIC"
+private const val DEBUG_CLASS = "StatelessInputConnection"
+
+private const val EXTRA_INPUT_CONTENT_INFO = "EXTRA_INPUT_CONTENT_INFO"
+
+/**
+ * An input connection that delegates its reads and writes to the active text input session.
+ * InputConnections are requested and used by framework to create bridge from IME to an active
+ * editor.
+ *
+ * @param editorInfo Required to create an InputConnection wrapper to support [commitContent] on
+ * all API levels.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class StatelessInputConnection(
+    private val session: TextInputSession,
+    editorInfo: EditorInfo
+) : InputConnection {
+    /**
+     * The depth of the batch session. 0 means no session.
+     *
+     * Sometimes InputConnection does not call begin/endBatchEdit functions before calling other
+     * edit functions like commitText or setComposingText. StatelessInputConnection starts and
+     * finishes a new artificial batch for every EditCommand to make sure that there is always
+     * an ongoing batch. EditCommands are only applied when batchDepth reaches 0.
+     */
+    private var batchDepth: Int = 0
+
+    /**
+     * The input state from the currently active [TextInputSession].
+     * Returns empty TextFieldValue if there is no active session.
+     */
+    private val text: TextFieldCharSequence
+        get() = session.text
+
+    /**
+     * Recording of editing operations for batch editing
+     */
+    private val editCommands = mutableVectorOf<EditingBuffer.() -> Unit>()
+
+    /**
+     * Wraps this StatelessInputConnection to halt a possible infinite loop in [commitContent]
+     * chain.
+     *
+     * if [StatelessInputConnection] is wrapped via [InputConnectionCompat] without intervention,
+     * [commitContent] and [performPrivateCommand] delegates back to their super, which would be
+     * this [StatelessInputConnection]. Then, those functions defined here would call the wrapped
+     * helper again, causing an infinite loop. Instead this terminal is introduced as a final
+     * receiver of [commitContent] and [performPrivateCommand] calls to end the chain when there's
+     * no configuration to handle the request.
+     *
+     * Note; Rather than creating an InputConnection with loads of empty or throwing defaults, we
+     * choose to wrap this [StatelessInputConnection] one more time to create this terminal.
+     * [terminalInputConnection] should never receive any call other than [commitContent] or
+     * [performPrivateCommand].
+     *
+     * Pseudo inverted stack trace after IME calls [InputConnection.commitContent].
+     * 1. StatelessInputConnection#commitContent ->
+     * 2. commitContentDelegateInputConnection#commitContent ->
+     * 3. terminalInputConnection#commitContent # ends here.
+     */
+    private val terminalInputConnection =
+        object : InputConnectionWrapper(this, false) {
+            override fun commitContent(
+                inputContentInfo: InputContentInfo,
+                flags: Int,
+                opts: Bundle?
+            ): Boolean {
+                return false
+            }
+
+            override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
+                // according to docs, return true even if we don't understand the command
+                return true
+            }
+        }
+
+    /**
+     * Compose supports below API 25 where [commitContent] is not defined. Support libraries add
+     * this functionality for IMEs and Editors via [InputConnectionCompat] and [EditorInfoCompat].
+     * To create an InputConnection that supports [commitContent] on all API levels, we need to
+     * wrap [StatelessInputConnection] using [InputConnectionCompat.createWrapper].
+     *
+     * We would like to send [commitContent] calls to the current listener
+     * [TextInputSession.onCommitContent] we have in active input session. It is not possible to
+     * create a wrapper via [InputConnectionCompat] and then update its listener. Therefore, we
+     * cannot simply wrap [StatelessInputConnection] from outside and pass it to the system.
+     * Instead, we create this internal wrapper that helps us delegate the [commitContent] calls to
+     * the active listener in [session].
+     *
+     * @see performPrivateCommand
+     * @see commitContent
+     */
+    @Suppress("DEPRECATION")
+    private val commitContentDelegateInputConnection = InputConnectionCompat.createWrapper(
+        terminalInputConnection,
+        editorInfo,
+        object : OnCommitContentListener {
+            override fun onCommitContent(
+                inputContentInfo: InputContentInfoCompat,
+                flags: Int,
+                opts: Bundle?
+            ): Boolean {
+                // The below code is mostly copied from `InputConnectionCompat.java`
+                var extras: Bundle? = opts
+                if (Build.VERSION.SDK_INT >= 25 &&
+                    (flags and INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
+                ) {
+                    try {
+                        inputContentInfo.requestPermission()
+                    } catch (e: Exception) {
+                        logDebug("Can't insert content from IME; requestPermission() failed, $e")
+                        return false
+                    }
+                    // Permissions granted above are revoked automatically by the platform when the
+                    // corresponding InputContentInfo object is garbage collected. To prevent
+                    // this from happening prematurely (before the receiving app has had a chance
+                    // to process the content), we set the InputContentInfo object into the
+                    // extras of the payload passed to onReceiveContent.
+                    val inputContentInfoFmk = inputContentInfo.unwrap() as Parcelable
+                    extras = if (opts == null) Bundle() else Bundle(opts)
+                    extras.putParcelable(EXTRA_INPUT_CONTENT_INFO, inputContentInfoFmk)
+                }
+                return session.onCommitContent(inputContentInfo.toTransferableContent(extras))
+            }
+        }
+    )
+
+    /**
+     * Add edit op to internal list with wrapping batch edit. It's not guaranteed by IME that
+     * batch editing will be used for every operation. Instead, [StatelessInputConnection] creates
+     * its own mini batches for every edit op. These batches are only applied when batch depth
+     * reaches 0, meaning that artificial batches won't be applied until the real batches are
+     * completed.
+     */
+    private fun addEditCommandWithBatch(editCommand: EditingBuffer.() -> Unit) {
+        beginBatchEditInternal()
+        try {
+            editCommands.add(editCommand)
+        } finally {
+            endBatchEditInternal()
+        }
+    }
+
+    // region Methods for batch editing and session control
+    override fun beginBatchEdit(): Boolean {
+        logDebug("beginBatchEdit()")
+        return beginBatchEditInternal()
+    }
+
+    private fun beginBatchEditInternal(): Boolean {
+        batchDepth++
+        return true
+    }
+
+    override fun endBatchEdit(): Boolean {
+        logDebug("endBatchEdit()")
+        return endBatchEditInternal()
+    }
+
+    private fun endBatchEditInternal(): Boolean {
+        batchDepth--
+        if (batchDepth == 0 && editCommands.isNotEmpty()) {
+            // apply the changes to active input session in order.
+            session.requestEdit {
+                editCommands.forEach { it.invoke(this) }
+            }
+            editCommands.clear()
+        }
+        return batchDepth > 0
+    }
+
+    override fun closeConnection() {
+        logDebug("closeConnection()")
+        editCommands.clear()
+        batchDepth = 0
+    }
+
+    //endregion
+
+    // region Callbacks for text editing
+
+    override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
+        logDebug("commitText(\"$text\", $newCursorPosition)")
+        addEditCommandWithBatch {
+            commitText(text.toString(), newCursorPosition)
+        }
+        return true
+    }
+
+    override fun setComposingRegion(start: Int, end: Int): Boolean {
+        logDebug("setComposingRegion($start, $end)")
+        addEditCommandWithBatch {
+            setComposingRegion(start, end)
+        }
+        return true
+    }
+
+    override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
+        logDebug("setComposingText(\"$text\", $newCursorPosition)")
+        addEditCommandWithBatch {
+            setComposingText(text.toString(), newCursorPosition)
+        }
+        return true
+    }
+
+    override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
+        logDebug("deleteSurroundingTextInCodePoints($beforeLength, $afterLength)")
+        addEditCommandWithBatch {
+            deleteSurroundingTextInCodePoints(beforeLength, afterLength)
+        }
+        return true
+    }
+
+    override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
+        logDebug("deleteSurroundingText($beforeLength, $afterLength)")
+        addEditCommandWithBatch {
+            deleteSurroundingText(beforeLength, afterLength)
+        }
+        return true
+    }
+
+    override fun setSelection(start: Int, end: Int): Boolean {
+        logDebug("setSelection($start, $end)")
+        addEditCommandWithBatch {
+            setSelection(start, end)
+        }
+        return true
+    }
+
+    override fun finishComposingText(): Boolean {
+        logDebug("finishComposingText()")
+        addEditCommandWithBatch {
+            finishComposingText()
+        }
+        return true
+    }
+
+    override fun sendKeyEvent(event: KeyEvent): Boolean {
+        logDebug("sendKeyEvent($event)")
+        session.sendKeyEvent(event)
+        return true
+    }
+
+    // endregion
+
+    // region Callbacks for retrieving editing buffer info by IME
+
+    override fun getTextBeforeCursor(maxChars: Int, flags: Int): CharSequence {
+        // TODO(b/135556699) should return styled text
+        val result = text.getTextBeforeSelection(maxChars).toString()
+        logDebug("getTextBeforeCursor($maxChars, $flags): $result")
+        return result
+    }
+
+    override fun getTextAfterCursor(maxChars: Int, flags: Int): CharSequence {
+        // TODO(b/135556699) should return styled text
+        val result = text.getTextAfterSelection(maxChars).toString()
+        logDebug("getTextAfterCursor($maxChars, $flags): $result")
+        return result
+    }
+
+    override fun getSelectedText(flags: Int): CharSequence? {
+        // https://source.chromium.org/chromium/chromium/src/+/master:content/public/android/java/src/org/chromium/content/browser/input/TextInputState.java;l=56;drc=0e20d1eb38227949805a4c0e9d5cdeddc8d23637
+        val result: CharSequence? = if (text.selectionInChars.collapsed) {
+            null
+        } else {
+            // TODO(b/135556699) should return styled text
+            text.getSelectedText().toString()
+        }
+        logDebug("getSelectedText($flags): $result")
+        return result
+    }
+
+    override fun requestCursorUpdates(cursorUpdateMode: Int): Boolean {
+        logDebug("requestCursorUpdates($cursorUpdateMode)")
+        session.requestCursorUpdates(cursorUpdateMode)
+        return true
+    }
+
+    override fun getExtractedText(request: ExtractedTextRequest?, flags: Int): ExtractedText {
+        logDebug("getExtractedText($request, $flags)")
+//        extractedTextMonitorMode = (flags and InputConnection.GET_EXTRACTED_TEXT_MONITOR) != 0
+//        if (extractedTextMonitorMode) {
+//            currentExtractedTextRequestToken = request?.token ?: 0
+//        }
+        // TODO(halilibo): Implement extracted text monitor
+        // TODO(b/135556699) should return styled text
+        return text.toExtractedText()
+    }
+
+    override fun getCursorCapsMode(reqModes: Int): Int {
+        logDebug("getCursorCapsMode($reqModes)")
+        return TextUtils.getCapsMode(text, text.selectionInChars.min, reqModes)
+    }
+
+    // endregion
+
+    // region Editor action and Key events.
+
+    override fun performContextMenuAction(id: Int): Boolean {
+        logDebug("performContextMenuAction($id)")
+        when (id) {
+            android.R.id.selectAll -> {
+                // no need to batch context menu actions.
+                session.requestEdit(notifyImeOfChanges = true) {
+                    setSelection(0, text.length)
+                }
+            }
+            // TODO(siyamed): Need proper connection to cut/copy/paste
+            android.R.id.cut -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_CUT)
+            android.R.id.copy -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_COPY)
+            android.R.id.paste -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_PASTE)
+            android.R.id.startSelectingText -> {} // not supported
+            android.R.id.stopSelectingText -> {} // not supported
+            android.R.id.copyUrl -> {} // not supported
+            android.R.id.switchInputMethod -> {} // not supported
+            else -> {
+                // not supported
+            }
+        }
+        return false
+    }
+
+    private fun sendSynthesizedKeyEvent(code: Int) {
+        sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, code))
+        sendKeyEvent(KeyEvent(KeyEvent.ACTION_UP, code))
+    }
+
+    override fun performEditorAction(editorAction: Int): Boolean {
+        logDebug("performEditorAction($editorAction)")
+
+        val imeAction = when (editorAction) {
+            EditorInfo.IME_ACTION_UNSPECIFIED -> ImeAction.Default
+            EditorInfo.IME_ACTION_DONE -> ImeAction.Done
+            EditorInfo.IME_ACTION_SEND -> ImeAction.Send
+            EditorInfo.IME_ACTION_SEARCH -> ImeAction.Search
+            EditorInfo.IME_ACTION_PREVIOUS -> ImeAction.Previous
+            EditorInfo.IME_ACTION_NEXT -> ImeAction.Next
+            EditorInfo.IME_ACTION_GO -> ImeAction.Go
+            else -> {
+                logDebug("IME sent an unrecognized editor action: $editorAction")
+                ImeAction.Default
+            }
+        }
+
+        session.onImeAction(imeAction)
+        return true
+    }
+
+    // endregion
+
+    // region Unsupported callbacks
+
+    override fun commitCompletion(text: CompletionInfo?): Boolean {
+        logDebug("commitCompletion(${text?.text})")
+        // We don't support this callback.
+        // The API documents says this should return if the input connection is no longer valid, but
+        // The Chromium implementation already returning false, so assuming it is safe to return
+        // false if not supported.
+        // see https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
+        return false
+    }
+
+    override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
+        // logDebug("commitCorrection($correctionInfo),autoCorrect:$autoCorrect")
+        // Should add an event here so that we can implement the autocorrect highlight
+        // Bug: 170647219
+        // TODO(halilibo): Implement autoCorrect from ImeOptions
+        return true
+    }
+
+    override fun getHandler(): Handler? {
+        logDebug("getHandler()")
+        return null // Returns null means using default Handler
+    }
+
+    override fun clearMetaKeyStates(states: Int): Boolean {
+        logDebug("clearMetaKeyStates($states)")
+        // We don't support this callback.
+        // The API documents says this should return if the input connection is no longer valid, but
+        // The Chromium implementation already returning false, so assuming it is safe to return
+        // false if not supported.
+        // see https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
+        return false
+    }
+
+    override fun reportFullscreenMode(enabled: Boolean): Boolean {
+        logDebug("reportFullscreenMode($enabled)")
+        return false // This value is ignored according to the API docs.
+    }
+
+    override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
+        logDebug("performPrivateCommand($action, $data)")
+        return commitContentDelegateInputConnection.performPrivateCommand(action, data)
+    }
+
+    override fun commitContent(
+        inputContentInfo: InputContentInfo,
+        flags: Int,
+        opts: Bundle?
+    ): Boolean {
+        logDebug("commitContent($inputContentInfo, $flags, $opts)")
+        return if (Build.VERSION.SDK_INT >= 25) {
+            Api25CommitContentImpl.commitContent(
+                inputConnection = commitContentDelegateInputConnection,
+                inputContentInfo = inputContentInfo,
+                flags = flags,
+                opts = opts
+            )
+        } else {
+            // This should never happen. Platform does not know about `commitContent` below API 25
+            // so it cannot be called.
+            false
+        }
+    }
+
+    // endregion
+
+    private fun logDebug(message: String) {
+        if (SIC_DEBUG) {
+            Log.d(STATELESS_TAG, "$DEBUG_CLASS.$message")
+        }
+    }
+}
+
+@RequiresApi(25)
+private object Api25CommitContentImpl {
+
+    @DoNotInline
+    fun commitContent(
+        inputConnection: InputConnection,
+        inputContentInfo: InputContentInfo,
+        flags: Int,
+        opts: Bundle?
+    ): Boolean {
+        return inputConnection.commitContent(inputContentInfo, flags, opts)
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun TextFieldCharSequence.toExtractedText(): ExtractedText {
+    val res = ExtractedText()
+    res.text = this
+    res.startOffset = 0
+    res.partialEndOffset = length
+    res.partialStartOffset = -1 // -1 means full text
+    res.selectionStart = selectionInChars.min
+    res.selectionEnd = selectionInChars.max
+    res.flags = if ('\n' in this) 0 else ExtractedText.FLAG_SINGLE_LINE
+    return res
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun InputContentInfoCompat.toTransferableContent(extras: Bundle?): TransferableContent {
+    val clipData = ClipData(description, ClipData.Item(contentUri))
+    return TransferableContent(
+        clipEntry = clipData.toClipEntry(),
+        source = TransferableContent.Source.Keyboard,
+        clipMetadata = description.toClipMetadata(),
+        platformTransferableContent = PlatformTransferableContent(
+            linkUri = linkUri,
+            extras = extras ?: Bundle.EMPTY
+        )
+    )
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt
new file mode 100644
index 0000000..d26d551
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropModifierNode
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.ClipMetadata
+import androidx.compose.ui.platform.toClipEntry
+import androidx.compose.ui.platform.toClipMetadata
+
+@OptIn(ExperimentalFoundationApi::class)
+internal actual fun textFieldDragAndDropNode(
+    hintMediaTypes: () -> Set<MediaType>,
+    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
+    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
+    onStarted: ((event: DragAndDropEvent) -> Unit)?,
+    onEntered: ((event: DragAndDropEvent) -> Unit)?,
+    onMoved: ((position: Offset) -> Unit)?,
+    onChanged: ((event: DragAndDropEvent) -> Unit)?,
+    onExited: ((event: DragAndDropEvent) -> Unit)?,
+    onEnded: ((event: DragAndDropEvent) -> Unit)?,
+): DragAndDropModifierNode {
+    return DragAndDropModifierNode(
+        shouldStartDragAndDrop = { dragAndDropEvent ->
+            // If there's a receiveContent modifier wrapping around this TextField, initially all
+            // dragging items should be accepted for drop. This is expected to be met by the caller
+            // of this function.
+            val clipDescription = dragAndDropEvent.toAndroidDragEvent().clipDescription
+            hintMediaTypes().any {
+                it == MediaType.All || clipDescription.hasMimeType(it.representation)
+            }
+        },
+        target = object : DragAndDropTarget {
+            override fun onDrop(event: DragAndDropEvent): Boolean {
+                dragAndDropRequestPermission(event)
+                return onDrop.invoke(
+                    event.toAndroidDragEvent().clipData.toClipEntry(),
+                    event.toAndroidDragEvent().clipDescription.toClipMetadata()
+                )
+            }
+
+            override fun onStarted(event: DragAndDropEvent) =
+                onStarted?.invoke(event) ?: Unit
+
+            override fun onEntered(event: DragAndDropEvent) =
+                onEntered?.invoke(event) ?: Unit
+
+            override fun onMoved(event: DragAndDropEvent) = with(event.toAndroidDragEvent()) {
+                onMoved?.invoke(Offset(x, y)) ?: Unit
+            }
+
+            override fun onExited(event: DragAndDropEvent) =
+                onExited?.invoke(event) ?: Unit
+
+            override fun onChanged(event: DragAndDropEvent) =
+                onChanged?.invoke(event) ?: Unit
+
+            override fun onEnded(event: DragAndDropEvent) =
+                onEnded?.invoke(event) ?: Unit
+        }
+    )
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.android.kt
new file mode 100644
index 0000000..db55023
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.android.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.view.InputDevice.SOURCE_DPAD
+import android.view.KeyEvent.KEYCODE_DPAD_CENTER
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
+import android.view.KeyEvent.KEYCODE_DPAD_UP
+import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.SoftwareKeyboardController
+
+internal actual fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler =
+    AndroidTextFieldKeyEventHandler()
+
+internal class AndroidTextFieldKeyEventHandler : TextFieldKeyEventHandler() {
+
+    override fun onPreKeyEvent(
+        event: KeyEvent,
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        focusManager: FocusManager,
+        keyboardController: SoftwareKeyboardController
+    ): Boolean {
+        // do not proceed if common code has consumed the event
+        if (
+            super.onPreKeyEvent(
+                event = event,
+                textFieldState = textFieldState,
+                textFieldSelectionState = textFieldSelectionState,
+                focusManager = focusManager,
+                keyboardController = keyboardController
+            )
+        ) return true
+
+        val device = event.nativeKeyEvent.device
+        return when {
+            device == null -> false
+
+            // Ignore key events from non-dpad sources
+            !device.supportsSource(SOURCE_DPAD) -> false
+
+            // Ignore key events from virtual keyboards
+            device.isVirtual -> false
+
+            // Ignore key release events
+            event.type != KeyDown -> false
+
+            event.isKeyCode(KEYCODE_DPAD_UP) -> focusManager.moveFocus(FocusDirection.Up)
+            event.isKeyCode(KEYCODE_DPAD_DOWN) -> focusManager.moveFocus(FocusDirection.Down)
+            event.isKeyCode(KEYCODE_DPAD_LEFT) -> focusManager.moveFocus(FocusDirection.Left)
+            event.isKeyCode(KEYCODE_DPAD_RIGHT) -> focusManager.moveFocus(FocusDirection.Right)
+            event.isKeyCode(KEYCODE_DPAD_CENTER) -> {
+                // Enable keyboard on center key press
+                keyboardController.show()
+                true
+            }
+            else -> false
+        }
+    }
+}
+
+private fun KeyEvent.isKeyCode(keyCode: Int): Boolean =
+    this.key.nativeKeyCode == keyCode
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt
new file mode 100644
index 0000000..3616297
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import android.view.KeyEvent
+import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.TextFieldValue
+
+/**
+ * The dependencies and actions required by a [StatelessInputConnection] connection. Decouples
+ * [StatelessInputConnection] from [TextFieldState] for testability.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal interface TextInputSession {
+
+    /**
+     * The current [TextFieldValue] in this input session. This value is typically supplied by a
+     * backing [TextFieldState] that is used to initialize the session.
+     */
+    val text: TextFieldCharSequence
+
+    /**
+     * Callback to execute for InputConnection to communicate the changes requested by the IME.
+     *
+     * @param notifyImeOfChanges Normally any request coming from IME should not be
+     * back-communicated but [InputConnection.performContextMenuAction] does not behave like a
+     * regular IME command. Its changes must be resent to IME to keep it in sync with
+     * [TextFieldState].
+     * @param block Lambda scoped to an EditingBuffer to apply changes direct onto a buffer.
+     */
+    fun requestEdit(notifyImeOfChanges: Boolean = false, block: EditingBuffer.() -> Unit)
+
+    /**
+     * Delegates IME requested KeyEvents.
+     */
+    fun sendKeyEvent(keyEvent: KeyEvent)
+
+    /**
+     * Callback to run when IME sends an action via [InputConnection.performEditorAction]
+     */
+    fun onImeAction(imeAction: ImeAction)
+
+    /**
+     * Callback to run when IME sends a content via [InputConnection.commitContent]
+     */
+    fun onCommitContent(transferableContent: TransferableContent): Boolean
+
+    /**
+     * Called from [InputConnection.requestCursorUpdates].
+     */
+    fun requestCursorUpdates(cursorUpdateMode: Int)
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt
new file mode 100644
index 0000000..700bbca
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.toCharArray
+
+@OptIn(ExperimentalFoundationApi::class)
+internal actual fun CharSequence.toCharArray(
+    destination: CharArray,
+    destinationOffset: Int,
+    startIndex: Int,
+    endIndex: Int
+) {
+    if (this is TextFieldCharSequence) {
+        toCharArray(destination, destinationOffset, startIndex, endIndex)
+    } else {
+        var dstIndex = destinationOffset
+        for (srcIndex in startIndex until endIndex) {
+            destination[dstIndex++] = this[srcIndex]
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/selection/AndroidTextFieldMagnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/selection/AndroidTextFieldMagnifier.android.kt
new file mode 100644
index 0000000..16bcb5e
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/selection/AndroidTextFieldMagnifier.android.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.selection
+
+import android.annotation.SuppressLint
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.MagnifierNode
+import androidx.compose.foundation.isPlatformMagnifierSupported
+import androidx.compose.foundation.text.input.internal.TextLayoutState
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+import androidx.compose.foundation.text.selection.MagnifierSpringSpec
+import androidx.compose.foundation.text.selection.OffsetDisplacementThreshold
+import androidx.compose.foundation.text.selection.UnspecifiedSafeOffsetVectorConverter
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.unit.IntSize
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+internal class TextFieldMagnifierNodeImpl28(
+    private var textFieldState: TransformedTextFieldState,
+    private var textFieldSelectionState: TextFieldSelectionState,
+    private var textLayoutState: TextLayoutState,
+    private var visible: Boolean
+) : TextFieldMagnifierNode(), CompositionLocalConsumerModifierNode {
+
+    private var magnifierSize: IntSize by mutableStateOf(IntSize.Zero)
+
+    // Can't use Offset.VectorConverter because we need to handle Unspecified specially.
+    private val animatable =
+        Animatable(
+            initialValue = calculateSelectionMagnifierCenterAndroid(
+                textFieldState = textFieldState,
+                selectionState = textFieldSelectionState,
+                textLayoutState = textLayoutState,
+                magnifierSize = magnifierSize
+            ),
+            typeConverter = UnspecifiedSafeOffsetVectorConverter,
+            visibilityThreshold = OffsetDisplacementThreshold
+        )
+
+    private val magnifierNode = delegate(
+        MagnifierNode(
+            sourceCenter = { animatable.value },
+            onSizeChanged = { size ->
+                magnifierSize = with(currentValueOf(LocalDensity)) {
+                    IntSize(size.width.roundToPx(), size.height.roundToPx())
+                }
+            },
+            useTextDefault = true
+        )
+    )
+
+    private var animationJob: Job? = null
+
+    override fun onAttach() {
+        restartAnimationJob()
+    }
+
+    override fun update(
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        textLayoutState: TextLayoutState,
+        visible: Boolean
+    ) {
+        val previousTextFieldState = this.textFieldState
+        val previousSelectionState = this.textFieldSelectionState
+        val previousLayoutState = this.textLayoutState
+        val wasVisible = this.visible
+
+        this.textFieldState = textFieldState
+        this.textFieldSelectionState = textFieldSelectionState
+        this.textLayoutState = textLayoutState
+        this.visible = visible
+
+        if (textFieldState != previousTextFieldState ||
+            textFieldSelectionState != previousSelectionState ||
+            textLayoutState != previousLayoutState ||
+            visible != wasVisible
+        ) {
+            restartAnimationJob()
+        }
+    }
+
+    private fun restartAnimationJob() {
+        animationJob?.cancel()
+        animationJob = null
+        // never start an expensive animation job if magnifier is not explicitly set to be visible
+        // or magnifier is not supported.
+        if (!visible || !isPlatformMagnifierSupported()) return
+        animationJob = coroutineScope.launch {
+            val animationScope = this
+            snapshotFlow {
+                calculateSelectionMagnifierCenterAndroid(
+                    textFieldState,
+                    textFieldSelectionState,
+                    textLayoutState,
+                    magnifierSize
+                )
+            }
+                .collect { targetValue ->
+                    // Only animate the position when moving vertically (i.e. jumping between
+                    // lines), since horizontal movement in a single line should stay as close to
+                    // the gesture as possible and animation would only add unnecessary lag.
+                    if (
+                        animatable.value.isSpecified &&
+                        targetValue.isSpecified &&
+                        animatable.value.y != targetValue.y
+                    ) {
+                        // Launch the animation, instead of cancelling and re-starting manually via
+                        // collectLatest, so if another animation is started before this one
+                        // finishes, the new one will use the correct velocity, e.g. in order to
+                        // propagate spring inertia.
+                        animationScope.launch {
+                            animatable.animateTo(targetValue, MagnifierSpringSpec)
+                        }
+                    } else {
+                        animatable.snapTo(targetValue)
+                    }
+                }
+        }
+    }
+
+    // TODO(halilibo) Remove this once delegation can propagate this events on its own
+    override fun ContentDrawScope.draw() {
+        drawContent()
+        with(magnifierNode) { draw() }
+    }
+
+    // TODO(halilibo) Remove this once delegation can propagate this events on its own
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        magnifierNode.onGloballyPositioned(coordinates)
+    }
+
+    // TODO(halilibo) Remove this once delegation can propagate this events on its own
+    override fun SemanticsPropertyReceiver.applySemantics() {
+        with(magnifierNode) { applySemantics() }
+    }
+}
+
+/**
+ * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to
+ * whether magnifier is supported.
+ */
+@SuppressLint("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
+internal actual fun textFieldMagnifierNode(
+    textFieldState: TransformedTextFieldState,
+    textFieldSelectionState: TextFieldSelectionState,
+    textLayoutState: TextLayoutState,
+    visible: Boolean
+): TextFieldMagnifierNode {
+    return if (isPlatformMagnifierSupported()) {
+        TextFieldMagnifierNodeImpl28(
+            textFieldState = textFieldState,
+            textFieldSelectionState = textFieldSelectionState,
+            textLayoutState = textLayoutState,
+            visible = visible
+        )
+    } else {
+        object : TextFieldMagnifierNode() {
+            override fun update(
+                textFieldState: TransformedTextFieldState,
+                textFieldSelectionState: TextFieldSelectionState,
+                textLayoutState: TextLayoutState,
+                visible: Boolean
+            ) {}
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt
deleted file mode 100644
index 4e58bdc..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package androidx.compose.foundation.text2.input.internal
-
-import android.util.Log
-import android.view.KeyEvent
-import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
-import androidx.compose.foundation.text.input.internal.update
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.ImeOptions
-import androidx.compose.ui.text.input.KeyboardType
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Enable to print logs during debugging, see [logDebug]. */
-@VisibleForTesting
-internal const val TIA_DEBUG = false
-private const val TAG = "AndroidTextInputSession"
-
-internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TransformedTextFieldState,
-    layoutState: TextLayoutState,
-    imeOptions: ImeOptions,
-    receiveContentConfiguration: ReceiveContentConfiguration?,
-    onImeAction: ((ImeAction) -> Unit)?
-): Nothing {
-    platformSpecificTextInputSession(
-        state = state,
-        layoutState = layoutState,
-        imeOptions = imeOptions,
-        receiveContentConfiguration = receiveContentConfiguration,
-        onImeAction = onImeAction,
-        composeImm = ComposeInputMethodManager(view)
-    )
-}
-
-@VisibleForTesting
-internal suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TransformedTextFieldState,
-    layoutState: TextLayoutState,
-    imeOptions: ImeOptions,
-    receiveContentConfiguration: ReceiveContentConfiguration?,
-    onImeAction: ((ImeAction) -> Unit)?,
-    composeImm: ComposeInputMethodManager
-): Nothing {
-    coroutineScope {
-        launch(start = CoroutineStart.UNDISPATCHED) {
-            state.collectImeNotifications { old, new ->
-                val needUpdateSelection =
-                    (old.selectionInChars != new.selectionInChars) ||
-                        old.compositionInChars != new.compositionInChars
-                if (needUpdateSelection) {
-                    composeImm.updateSelection(
-                        selectionStart = new.selectionInChars.min,
-                        selectionEnd = new.selectionInChars.max,
-                        compositionStart = new.compositionInChars?.min ?: -1,
-                        compositionEnd = new.compositionInChars?.max ?: -1
-                    )
-                }
-
-                // No need to restart the IME if keyboard type is configured as Password. IME
-                // should not keep an internal input state if the content needs to be secured.
-                if (!old.contentEquals(new) && imeOptions.keyboardType != KeyboardType.Password) {
-                    composeImm.restartInput()
-                }
-            }
-        }
-
-        val cursorUpdatesController = CursorAnchorInfoController(
-            composeImm = composeImm,
-            textFieldState = state,
-            textLayoutState = layoutState,
-            monitorScope = this,
-        )
-
-        startInputMethod { outAttrs ->
-            logDebug { "createInputConnection(value=\"${state.visualText}\")" }
-
-            val textInputSession = object : TextInputSession {
-                override val text: TextFieldCharSequence
-                    get() = state.visualText
-
-                override fun requestEdit(
-                    notifyImeOfChanges: Boolean,
-                    block: EditingBuffer.() -> Unit
-                ) {
-                    state.editUntransformedTextAsUser(
-                        notifyImeOfChanges = notifyImeOfChanges,
-                        block = block
-                    )
-                }
-
-                override fun sendKeyEvent(keyEvent: KeyEvent) {
-                    composeImm.sendKeyEvent(keyEvent)
-                }
-
-                override fun onImeAction(imeAction: ImeAction) {
-                    onImeAction?.invoke(imeAction)
-                }
-
-                override fun onCommitContent(transferableContent: TransferableContent): Boolean {
-                    return receiveContentConfiguration?.onCommitContent(transferableContent)
-                        ?: false
-                }
-
-                override fun requestCursorUpdates(cursorUpdateMode: Int) {
-                    cursorUpdatesController.requestUpdates(cursorUpdateMode)
-                }
-            }
-
-            val hintMediaTypes = receiveContentConfiguration?.hintMediaTypes
-            val contentMimeTypes: Array<String>? =
-                if (!hintMediaTypes.isNullOrEmpty()) {
-                    val arr = Array(hintMediaTypes.size) { "" }
-                    hintMediaTypes.forEachIndexed { i, mediaType ->
-                        arr[i] = mediaType.representation
-                    }
-                    arr
-                } else {
-                    null
-                }
-
-            outAttrs.update(
-                text = state.visualText,
-                selection = state.visualText.selectionInChars,
-                imeOptions = imeOptions,
-                contentMimeTypes = contentMimeTypes
-            )
-            StatelessInputConnection(textInputSession, outAttrs)
-        }
-    }
-}
-
-private fun logDebug(tag: String = TAG, content: () -> String) {
-    if (TIA_DEBUG) {
-        Log.d(tag, content())
-    }
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManager.android.kt
deleted file mode 100644
index dd231d5..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ComposeInputMethodManager.android.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.content.Context
-import android.os.Build
-import android.view.KeyEvent
-import android.view.View
-import android.view.inputmethod.BaseInputConnection
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.ExtractedText
-import android.view.inputmethod.InputMethodManager
-import androidx.annotation.RequiresApi
-import androidx.annotation.VisibleForTesting
-import androidx.core.view.SoftwareKeyboardControllerCompat
-import org.jetbrains.annotations.TestOnly
-
-/**
- * Compatibility interface for [InputMethodManager] to use in Compose text input systems.
- *
- * This interface is responsible for handling the calls made to platform InputMethodManager in
- * Android. There are different ways to show and hide software keyboard depending on API level.
- *
- * This interface also allows us to fake out the IMM for testing. For that reason, it should match
- * the relevant platform [InputMethodManager] APIs as closely as possible.
- */
-internal interface ComposeInputMethodManager {
-    fun restartInput()
-
-    fun showSoftInput()
-
-    fun hideSoftInput()
-
-    fun updateExtractedText(
-        token: Int,
-        extractedText: ExtractedText
-    )
-
-    fun updateSelection(
-        selectionStart: Int,
-        selectionEnd: Int,
-        compositionStart: Int,
-        compositionEnd: Int
-    )
-
-    fun updateCursorAnchorInfo(info: CursorAnchorInfo)
-
-    /**
-     * Sends a [KeyEvent] originated from an InputMethod to the Window. This is a necessary
-     * delegation when the InputConnection itself does not handle the received event.
-     */
-    fun sendKeyEvent(event: KeyEvent)
-}
-
-/**
- * Creates a new instance of [ComposeInputMethodManager].
- *
- * The value returned by this function can be changed for tests by calling
- * [overrideComposeInputMethodManagerFactoryForTests].
- */
-internal fun ComposeInputMethodManager(view: View): ComposeInputMethodManager =
-    ComposeInputMethodManagerFactory(view)
-
-/** This lets us swap out the implementation in our own tests. */
-private var ComposeInputMethodManagerFactory: (View) -> ComposeInputMethodManager = { view ->
-    when {
-        Build.VERSION.SDK_INT >= 24 -> ComposeInputMethodManagerImplApi24(view)
-        else -> ComposeInputMethodManagerImplApi21(view)
-    }
-}
-
-/**
- * Sets the factory used by [ComposeInputMethodManager] to create instances and returns the previous
- * factory.
- *
- * Any test that calls this should call it again to restore the factory after the test finishes, to
- * avoid breaking unrelated tests.
- */
-@TestOnly
-@VisibleForTesting
-internal fun overrideComposeInputMethodManagerFactoryForTests(
-    factory: (View) -> ComposeInputMethodManager
-): (View) -> ComposeInputMethodManager {
-    val oldFactory = ComposeInputMethodManagerFactory
-    ComposeInputMethodManagerFactory = factory
-    return oldFactory
-}
-
-private abstract class ComposeInputMethodManagerImpl(protected val view: View) :
-    ComposeInputMethodManager {
-
-    private var imm: InputMethodManager? = null
-
-    private val softwareKeyboardControllerCompat =
-        SoftwareKeyboardControllerCompat(view)
-
-    override fun restartInput() {
-        requireImm().restartInput(view)
-    }
-
-    override fun showSoftInput() {
-        softwareKeyboardControllerCompat.show()
-    }
-
-    override fun hideSoftInput() {
-        softwareKeyboardControllerCompat.hide()
-    }
-
-    override fun updateExtractedText(
-        token: Int,
-        extractedText: ExtractedText
-    ) {
-        requireImm().updateExtractedText(view, token, extractedText)
-    }
-
-    override fun updateSelection(
-        selectionStart: Int,
-        selectionEnd: Int,
-        compositionStart: Int,
-        compositionEnd: Int
-    ) {
-        requireImm().updateSelection(
-            view,
-            selectionStart,
-            selectionEnd,
-            compositionStart,
-            compositionEnd
-        )
-    }
-
-    override fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
-        requireImm().updateCursorAnchorInfo(view, info)
-    }
-
-    protected fun requireImm(): InputMethodManager = imm ?: createImm().also { imm = it }
-
-    private fun createImm() =
-        view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
-}
-
-private open class ComposeInputMethodManagerImplApi21(view: View) :
-    ComposeInputMethodManagerImpl(view) {
-
-    /**
-     * Prior to API24, the safest way to delegate IME originated KeyEvents to the window was
-     * through BaseInputConnection.
-     */
-    private var baseInputConnection: BaseInputConnection? = null
-
-    override fun sendKeyEvent(event: KeyEvent) {
-        val baseInputConnection = baseInputConnection
-            ?: BaseInputConnection(view, false).also { baseInputConnection = it }
-        baseInputConnection.sendKeyEvent(event)
-    }
-}
-
-@RequiresApi(24)
-private open class ComposeInputMethodManagerImplApi24(view: View) :
-    ComposeInputMethodManagerImplApi21(view) {
-
-    override fun sendKeyEvent(event: KeyEvent) {
-        requireImm().dispatchKeyEventFromInputMethod(view, event)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilder.android.kt
deleted file mode 100644
index 31a8fb5..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoBuilder.android.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.graphics.Matrix
-import android.os.Build
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.EditorBoundsInfo
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toAndroidRectF
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.style.ResolvedTextDirection
-
-/**
- * Helper function to build
- * [CursorAnchorInfo](https://developer.android.com/reference/android/view/inputmethod/CursorAnchorInfo).
- *
- * @param matrix matrix that transforms local coordinates into screen coordinates
- * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an empty
- *   rectangle if the text field is not visible
- * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an empty
- *   rectangle if the decoration box is not visible
- */
-internal fun CursorAnchorInfo.Builder.build(
-    text: CharSequence,
-    selection: TextRange,
-    composition: TextRange?,
-    textLayoutResult: TextLayoutResult,
-    matrix: Matrix,
-    innerTextFieldBounds: Rect,
-    decorationBoxBounds: Rect,
-    includeInsertionMarker: Boolean = true,
-    includeCharacterBounds: Boolean = true,
-    includeEditorBounds: Boolean = true,
-    includeLineBounds: Boolean = true
-): CursorAnchorInfo {
-    reset()
-
-    setMatrix(matrix)
-
-    val selectionStart = selection.min
-    val selectionEnd = selection.max
-    setSelectionRange(selectionStart, selectionEnd)
-
-    if (includeInsertionMarker) {
-        setInsertionMarker(selectionStart, textLayoutResult, innerTextFieldBounds)
-    }
-
-    if (includeCharacterBounds) {
-        val compositionStart = composition?.min ?: -1
-        val compositionEnd = composition?.max ?: -1
-
-        if (compositionStart in 0 until compositionEnd) {
-            setComposingText(
-                compositionStart,
-                text.subSequence(compositionStart, compositionEnd)
-            )
-            addCharacterBounds(
-                compositionStart,
-                compositionEnd,
-                textLayoutResult,
-                innerTextFieldBounds
-            )
-        }
-    }
-
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && includeEditorBounds) {
-        CursorAnchorInfoApi33Helper.setEditorBoundsInfo(this, decorationBoxBounds)
-    }
-
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && includeLineBounds) {
-        CursorAnchorInfoApi34Helper.addVisibleLineBounds(
-            this,
-            textLayoutResult,
-            innerTextFieldBounds
-        )
-    }
-
-    return build()
-}
-
-private fun CursorAnchorInfo.Builder.setInsertionMarker(
-    selectionStart: Int,
-    textLayoutResult: TextLayoutResult,
-    innerTextFieldBounds: Rect
-): CursorAnchorInfo.Builder {
-    if (selectionStart < 0) return this
-
-    val cursorRect = textLayoutResult.getCursorRect(selectionStart)
-    val x = cursorRect.left.coerceIn(0f, textLayoutResult.size.width.toFloat())
-    val isTopVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.top)
-    val isBottomVisible = innerTextFieldBounds.containsInclusive(x, cursorRect.bottom)
-    val isRtl = textLayoutResult.getBidiRunDirection(selectionStart) == ResolvedTextDirection.Rtl
-
-    var flags = 0
-    if (isTopVisible || isBottomVisible) flags = flags or CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
-    if (!isTopVisible || !isBottomVisible)
-        flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
-    if (isRtl) flags = flags or CursorAnchorInfo.FLAG_IS_RTL
-
-    // Sets the location of the text insertion point (zero width cursor) as a rectangle in local
-    // coordinates.
-    setInsertionMarkerLocation(x, cursorRect.top, cursorRect.bottom, cursorRect.bottom, flags)
-
-    return this
-}
-
-private fun CursorAnchorInfo.Builder.addCharacterBounds(
-    startOffset: Int,
-    endOffset: Int,
-    textLayoutResult: TextLayoutResult,
-    innerTextFieldBounds: Rect
-): CursorAnchorInfo.Builder {
-    val array = FloatArray((endOffset - startOffset) * 4)
-    textLayoutResult.multiParagraph.fillBoundingBoxes(
-        TextRange(
-            startOffset,
-            endOffset
-        ), array, 0
-    )
-
-    for (offset in startOffset until endOffset) {
-        val arrayIndex = 4 * (offset - startOffset)
-        val rect =
-            Rect(
-                array[arrayIndex] /* left */,
-                array[arrayIndex + 1] /* top */,
-                array[arrayIndex + 2] /* right */,
-                array[arrayIndex + 3] /* bottom */
-            )
-
-        var flags = 0
-        if (innerTextFieldBounds.overlaps(rect)) {
-            flags = flags or CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION
-        }
-        if (
-            !innerTextFieldBounds.containsInclusive(rect.left, rect.top) ||
-            !innerTextFieldBounds.containsInclusive(rect.right, rect.bottom)
-        ) {
-            flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
-        }
-        if (textLayoutResult.getBidiRunDirection(offset) == ResolvedTextDirection.Rtl) {
-            flags = flags or CursorAnchorInfo.FLAG_IS_RTL
-        }
-
-        addCharacterBounds(offset, rect.left, rect.top, rect.right, rect.bottom, flags)
-    }
-    return this
-}
-
-@RequiresApi(33)
-private object CursorAnchorInfoApi33Helper {
-    @JvmStatic
-    @DoNotInline
-    fun setEditorBoundsInfo(
-        builder: CursorAnchorInfo.Builder,
-        decorationBoxBounds: Rect
-    ): CursorAnchorInfo.Builder =
-        builder.setEditorBoundsInfo(
-            EditorBoundsInfo.Builder()
-                .setEditorBounds(decorationBoxBounds.toAndroidRectF())
-                .setHandwritingBounds(decorationBoxBounds.toAndroidRectF())
-                .build()
-        )
-}
-
-@RequiresApi(34)
-private object CursorAnchorInfoApi34Helper {
-    @JvmStatic
-    @DoNotInline
-    fun addVisibleLineBounds(
-        builder: CursorAnchorInfo.Builder,
-        textLayoutResult: TextLayoutResult,
-        innerTextFieldBounds: Rect
-    ): CursorAnchorInfo.Builder {
-        if (!innerTextFieldBounds.isEmpty) {
-            val firstLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.top)
-            val lastLine = textLayoutResult.getLineForVerticalPosition(innerTextFieldBounds.bottom)
-            for (index in firstLine..lastLine) {
-                builder.addVisibleLineBounds(
-                    textLayoutResult.getLineLeft(index),
-                    textLayoutResult.getLineTop(index),
-                    textLayoutResult.getLineRight(index),
-                    textLayoutResult.getLineBottom(index)
-                )
-            }
-        }
-        return builder
-    }
-}
-
-/**
- * Whether the point specified by the given offset lies inside or on an edge of this rectangle.
- *
- * Note this differs from [Rect.contains] which returns false for points on the bottom or right
- * edges.
- */
-private fun Rect.containsInclusive(x: Float, y: Float): Boolean {
-    return x in left..right && y in top..bottom
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoController.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoController.android.kt
deleted file mode 100644
index 03d209d..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/CursorAnchorInfoController.android.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.os.Build
-import android.view.inputmethod.CursorAnchorInfo
-import android.view.inputmethod.InputConnection
-import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
-import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
-import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
-import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.selection.visibleBounds
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Matrix
-import androidx.compose.ui.graphics.setFrom
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalFoundationApi::class)
-internal class CursorAnchorInfoController(
-    private val textFieldState: TransformedTextFieldState,
-    private val textLayoutState: TextLayoutState,
-    private val composeImm: ComposeInputMethodManager,
-    private val monitorScope: CoroutineScope,
-) {
-    private var monitorEnabled = false
-    private var hasPendingImmediateRequest = false
-    private var monitorJob: Job? = null
-
-    private var includeInsertionMarker = false
-    private var includeCharacterBounds = false
-    private var includeEditorBounds = false
-    private var includeLineBounds = false
-
-    private val builder = CursorAnchorInfo.Builder()
-    private val matrix = Matrix()
-    private val androidMatrix = android.graphics.Matrix()
-
-    /**
-     * Requests [CursorAnchorInfo] updates to be provided to the [ComposeInputMethodManager].
-     */
-    fun requestUpdates(cursorUpdateMode: Int) {
-        val immediate = cursorUpdateMode and InputConnection.CURSOR_UPDATE_IMMEDIATE != 0
-        val monitor = cursorUpdateMode and InputConnection.CURSOR_UPDATE_MONITOR != 0
-
-        // Before Android T, filter flags are not used, and insertion marker and character bounds
-        // info are always included.
-        var includeInsertionMarker = true
-        var includeCharacterBounds = true
-        var includeEditorBounds = false
-        var includeLineBounds = false
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-            includeInsertionMarker = cursorUpdateMode and CURSOR_UPDATE_FILTER_INSERTION_MARKER != 0
-            includeCharacterBounds = cursorUpdateMode and CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS != 0
-            includeEditorBounds = cursorUpdateMode and CURSOR_UPDATE_FILTER_EDITOR_BOUNDS != 0
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-                includeLineBounds =
-                    cursorUpdateMode and CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS != 0
-            }
-            // If no filter flags are used, then all info should be included.
-            if (
-                !includeInsertionMarker &&
-                !includeCharacterBounds &&
-                !includeEditorBounds &&
-                !includeLineBounds
-            ) {
-                includeInsertionMarker = true
-                includeCharacterBounds = true
-                includeEditorBounds = true
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-                    includeLineBounds = true
-                }
-            }
-        }
-
-        requestUpdates(
-            immediate = immediate,
-            monitor = monitor,
-            includeInsertionMarker = includeInsertionMarker,
-            includeCharacterBounds = includeCharacterBounds,
-            includeEditorBounds = includeEditorBounds,
-            includeLineBounds = includeLineBounds
-        )
-    }
-
-    /**
-     * Requests [CursorAnchorInfo] updates to be provided to the [ComposeInputMethodManager].
-     *
-     * Combinations of [immediate] and [monitor] are used to specify when to provide updates. If
-     * these are both false, then no further updates will be provided.
-     *
-     * @param immediate whether to update with the current [CursorAnchorInfo] immediately, or as
-     *   soon as available
-     * @param monitor whether to provide [CursorAnchorInfo] updates for all future layout or
-     *   position changes
-     * @param includeInsertionMarker whether to include insertion marker (i.e. cursor) location
-     *   information
-     * @param includeCharacterBounds whether to include character bounds information for the
-     *   composition range
-     * @param includeEditorBounds whether to include editor bounds information
-     * @param includeLineBounds whether to include line bounds information
-     */
-    private fun requestUpdates(
-        immediate: Boolean,
-        monitor: Boolean,
-        includeInsertionMarker: Boolean,
-        includeCharacterBounds: Boolean,
-        includeEditorBounds: Boolean,
-        includeLineBounds: Boolean
-    ) {
-        this.includeInsertionMarker = includeInsertionMarker
-        this.includeCharacterBounds = includeCharacterBounds
-        this.includeEditorBounds = includeEditorBounds
-        this.includeLineBounds = includeLineBounds
-
-        if (immediate) {
-            hasPendingImmediateRequest = true
-            calculateCursorAnchorInfo()?.let(composeImm::updateCursorAnchorInfo)
-        }
-        monitorEnabled = monitor
-        startOrStopMonitoring()
-    }
-
-    /**
-     * If [monitorEnabled] is rue, observes changes to [textLayoutState] and monitor state (from
-     * [requestUpdates]) and sends updates to the [composeImm] as required until cancelled.
-     * Otherwise, cancels any monitor [Job].
-     */
-    private fun startOrStopMonitoring() {
-        if (monitorEnabled) {
-            if (monitorJob?.isActive != true) {
-                monitorJob = monitorScope.launch(start = CoroutineStart.UNDISPATCHED) {
-                    // TODO (b/291327369) Confirm that we are sending updates at the right time.
-                    snapshotFlow { calculateCursorAnchorInfo() }
-                        .drop(1)
-                        .filterNotNull()
-                        .collect { composeImm.updateCursorAnchorInfo(it) }
-                }
-            }
-        } else {
-            monitorJob?.cancel()
-            monitorJob = null
-        }
-    }
-
-    private fun calculateCursorAnchorInfo(): CursorAnchorInfo? {
-        // State reads
-        val coreCoordinates = textLayoutState.coreNodeCoordinates
-            ?.takeIf { it.isAttached }
-            ?: return null
-        val decorationBoxCoordinates = textLayoutState.decoratorNodeCoordinates
-            ?.takeIf { it.isAttached }
-            ?: return null
-        val textLayoutResult = textLayoutState.layoutResult
-            ?: return null
-        val text = textFieldState.visualText
-
-        // Updates matrix to transform text field local coordinates to screen coordinates.
-        matrix.reset()
-        coreCoordinates.transformToScreen(matrix)
-        androidMatrix.setFrom(matrix)
-
-        val innerTextFieldBounds: Rect = coreCoordinates.visibleBounds()
-        val decorationBoxBounds: Rect = coreCoordinates.localBoundingBoxOf(
-            decorationBoxCoordinates,
-            clipBounds = false
-        )
-        return builder.build(
-            text,
-            text.selectionInChars,
-            text.compositionInChars,
-            textLayoutResult,
-            androidMatrix,
-            innerTextFieldBounds,
-            decorationBoxBounds,
-            includeInsertionMarker,
-            includeCharacterBounds,
-            includeEditorBounds,
-            includeLineBounds
-        )
-    }
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.android.kt
deleted file mode 100644
index 7eef0c9..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.android.kt
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.content.ClipData
-import android.os.Build
-import android.os.Bundle
-import android.os.Handler
-import android.os.Parcelable
-import android.text.TextUtils
-import android.util.Log
-import android.view.KeyEvent
-import android.view.inputmethod.CompletionInfo
-import android.view.inputmethod.CorrectionInfo
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.ExtractedText
-import android.view.inputmethod.ExtractedTextRequest
-import android.view.inputmethod.InputConnection
-import android.view.inputmethod.InputConnectionWrapper
-import android.view.inputmethod.InputContentInfo
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
-import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.PlatformTransferableContent
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.getSelectedText
-import androidx.compose.foundation.text2.input.getTextAfterSelection
-import androidx.compose.foundation.text2.input.getTextBeforeSelection
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.platform.toClipEntry
-import androidx.compose.ui.platform.toClipMetadata
-import androidx.compose.ui.text.input.ImeAction
-import androidx.core.view.inputmethod.EditorInfoCompat
-import androidx.core.view.inputmethod.InputConnectionCompat
-import androidx.core.view.inputmethod.InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
-import androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener
-import androidx.core.view.inputmethod.InputContentInfoCompat
-
-@VisibleForTesting
-internal const val SIC_DEBUG = false
-private const val TAG = "StatelessIC"
-private const val DEBUG_CLASS = "StatelessInputConnection"
-
-private const val EXTRA_INPUT_CONTENT_INFO = "EXTRA_INPUT_CONTENT_INFO"
-
-/**
- * An input connection that delegates its reads and writes to the active text input session.
- * InputConnections are requested and used by framework to create bridge from IME to an active
- * editor.
- *
- * @param editorInfo Required to create an InputConnection wrapper to support [commitContent] on
- * all API levels.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal class StatelessInputConnection(
-    private val session: TextInputSession,
-    editorInfo: EditorInfo
-) : InputConnection {
-    /**
-     * The depth of the batch session. 0 means no session.
-     *
-     * Sometimes InputConnection does not call begin/endBatchEdit functions before calling other
-     * edit functions like commitText or setComposingText. StatelessInputConnection starts and
-     * finishes a new artificial batch for every EditCommand to make sure that there is always
-     * an ongoing batch. EditCommands are only applied when batchDepth reaches 0.
-     */
-    private var batchDepth: Int = 0
-
-    /**
-     * The input state from the currently active [TextInputSession].
-     * Returns empty TextFieldValue if there is no active session.
-     */
-    private val text: TextFieldCharSequence
-        get() = session.text
-
-    /**
-     * Recording of editing operations for batch editing
-     */
-    private val editCommands = mutableVectorOf<EditingBuffer.() -> Unit>()
-
-    /**
-     * Wraps this StatelessInputConnection to halt a possible infinite loop in [commitContent]
-     * chain.
-     *
-     * if [StatelessInputConnection] is wrapped via [InputConnectionCompat] without intervention,
-     * [commitContent] and [performPrivateCommand] delegates back to their super, which would be
-     * this [StatelessInputConnection]. Then, those functions defined here would call the wrapped
-     * helper again, causing an infinite loop. Instead this terminal is introduced as a final
-     * receiver of [commitContent] and [performPrivateCommand] calls to end the chain when there's
-     * no configuration to handle the request.
-     *
-     * Note; Rather than creating an InputConnection with loads of empty or throwing defaults, we
-     * choose to wrap this [StatelessInputConnection] one more time to create this terminal.
-     * [terminalInputConnection] should never receive any call other than [commitContent] or
-     * [performPrivateCommand].
-     *
-     * Pseudo inverted stack trace after IME calls [InputConnection.commitContent].
-     * 1. StatelessInputConnection#commitContent ->
-     * 2. commitContentDelegateInputConnection#commitContent ->
-     * 3. terminalInputConnection#commitContent # ends here.
-     */
-    private val terminalInputConnection =
-        object : InputConnectionWrapper(this, false) {
-            override fun commitContent(
-                inputContentInfo: InputContentInfo,
-                flags: Int,
-                opts: Bundle?
-            ): Boolean {
-                return false
-            }
-
-            override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
-                // according to docs, return true even if we don't understand the command
-                return true
-            }
-        }
-
-    /**
-     * Compose supports below API 25 where [commitContent] is not defined. Support libraries add
-     * this functionality for IMEs and Editors via [InputConnectionCompat] and [EditorInfoCompat].
-     * To create an InputConnection that supports [commitContent] on all API levels, we need to
-     * wrap [StatelessInputConnection] using [InputConnectionCompat.createWrapper].
-     *
-     * We would like to send [commitContent] calls to the current listener
-     * [TextInputSession.onCommitContent] we have in active input session. It is not possible to
-     * create a wrapper via [InputConnectionCompat] and then update its listener. Therefore, we
-     * cannot simply wrap [StatelessInputConnection] from outside and pass it to the system.
-     * Instead, we create this internal wrapper that helps us delegate the [commitContent] calls to
-     * the active listener in [session].
-     *
-     * @see performPrivateCommand
-     * @see commitContent
-     */
-    @Suppress("DEPRECATION")
-    private val commitContentDelegateInputConnection = InputConnectionCompat.createWrapper(
-        terminalInputConnection,
-        editorInfo,
-        object : OnCommitContentListener {
-            override fun onCommitContent(
-                inputContentInfo: InputContentInfoCompat,
-                flags: Int,
-                opts: Bundle?
-            ): Boolean {
-                // The below code is mostly copied from `InputConnectionCompat.java`
-                var extras: Bundle? = opts
-                if (Build.VERSION.SDK_INT >= 25 &&
-                    (flags and INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
-                ) {
-                    try {
-                        inputContentInfo.requestPermission()
-                    } catch (e: Exception) {
-                        logDebug("Can't insert content from IME; requestPermission() failed, $e")
-                        return false
-                    }
-                    // Permissions granted above are revoked automatically by the platform when the
-                    // corresponding InputContentInfo object is garbage collected. To prevent
-                    // this from happening prematurely (before the receiving app has had a chance
-                    // to process the content), we set the InputContentInfo object into the
-                    // extras of the payload passed to onReceiveContent.
-                    val inputContentInfoFmk = inputContentInfo.unwrap() as Parcelable
-                    extras = if (opts == null) Bundle() else Bundle(opts)
-                    extras.putParcelable(EXTRA_INPUT_CONTENT_INFO, inputContentInfoFmk)
-                }
-                return session.onCommitContent(inputContentInfo.toTransferableContent(extras))
-            }
-        }
-    )
-
-    /**
-     * Add edit op to internal list with wrapping batch edit. It's not guaranteed by IME that
-     * batch editing will be used for every operation. Instead, [StatelessInputConnection] creates
-     * its own mini batches for every edit op. These batches are only applied when batch depth
-     * reaches 0, meaning that artificial batches won't be applied until the real batches are
-     * completed.
-     */
-    private fun addEditCommandWithBatch(editCommand: EditingBuffer.() -> Unit) {
-        beginBatchEditInternal()
-        try {
-            editCommands.add(editCommand)
-        } finally {
-            endBatchEditInternal()
-        }
-    }
-
-    // region Methods for batch editing and session control
-    override fun beginBatchEdit(): Boolean {
-        logDebug("beginBatchEdit()")
-        return beginBatchEditInternal()
-    }
-
-    private fun beginBatchEditInternal(): Boolean {
-        batchDepth++
-        return true
-    }
-
-    override fun endBatchEdit(): Boolean {
-        logDebug("endBatchEdit()")
-        return endBatchEditInternal()
-    }
-
-    private fun endBatchEditInternal(): Boolean {
-        batchDepth--
-        if (batchDepth == 0 && editCommands.isNotEmpty()) {
-            // apply the changes to active input session in order.
-            session.requestEdit {
-                editCommands.forEach { it.invoke(this) }
-            }
-            editCommands.clear()
-        }
-        return batchDepth > 0
-    }
-
-    override fun closeConnection() {
-        logDebug("closeConnection()")
-        editCommands.clear()
-        batchDepth = 0
-    }
-
-    //endregion
-
-    // region Callbacks for text editing
-
-    override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
-        logDebug("commitText(\"$text\", $newCursorPosition)")
-        addEditCommandWithBatch {
-            commitText(text.toString(), newCursorPosition)
-        }
-        return true
-    }
-
-    override fun setComposingRegion(start: Int, end: Int): Boolean {
-        logDebug("setComposingRegion($start, $end)")
-        addEditCommandWithBatch {
-            setComposingRegion(start, end)
-        }
-        return true
-    }
-
-    override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
-        logDebug("setComposingText(\"$text\", $newCursorPosition)")
-        addEditCommandWithBatch {
-            setComposingText(text.toString(), newCursorPosition)
-        }
-        return true
-    }
-
-    override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
-        logDebug("deleteSurroundingTextInCodePoints($beforeLength, $afterLength)")
-        addEditCommandWithBatch {
-            deleteSurroundingTextInCodePoints(beforeLength, afterLength)
-        }
-        return true
-    }
-
-    override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
-        logDebug("deleteSurroundingText($beforeLength, $afterLength)")
-        addEditCommandWithBatch {
-            deleteSurroundingText(beforeLength, afterLength)
-        }
-        return true
-    }
-
-    override fun setSelection(start: Int, end: Int): Boolean {
-        logDebug("setSelection($start, $end)")
-        addEditCommandWithBatch {
-            setSelection(start, end)
-        }
-        return true
-    }
-
-    override fun finishComposingText(): Boolean {
-        logDebug("finishComposingText()")
-        addEditCommandWithBatch {
-            finishComposingText()
-        }
-        return true
-    }
-
-    override fun sendKeyEvent(event: KeyEvent): Boolean {
-        logDebug("sendKeyEvent($event)")
-        session.sendKeyEvent(event)
-        return true
-    }
-
-    // endregion
-
-    // region Callbacks for retrieving editing buffer info by IME
-
-    override fun getTextBeforeCursor(maxChars: Int, flags: Int): CharSequence {
-        // TODO(b/135556699) should return styled text
-        val result = text.getTextBeforeSelection(maxChars).toString()
-        logDebug("getTextBeforeCursor($maxChars, $flags): $result")
-        return result
-    }
-
-    override fun getTextAfterCursor(maxChars: Int, flags: Int): CharSequence {
-        // TODO(b/135556699) should return styled text
-        val result = text.getTextAfterSelection(maxChars).toString()
-        logDebug("getTextAfterCursor($maxChars, $flags): $result")
-        return result
-    }
-
-    override fun getSelectedText(flags: Int): CharSequence? {
-        // https://source.chromium.org/chromium/chromium/src/+/master:content/public/android/java/src/org/chromium/content/browser/input/TextInputState.java;l=56;drc=0e20d1eb38227949805a4c0e9d5cdeddc8d23637
-        val result: CharSequence? = if (text.selectionInChars.collapsed) {
-            null
-        } else {
-            // TODO(b/135556699) should return styled text
-            text.getSelectedText().toString()
-        }
-        logDebug("getSelectedText($flags): $result")
-        return result
-    }
-
-    override fun requestCursorUpdates(cursorUpdateMode: Int): Boolean {
-        logDebug("requestCursorUpdates($cursorUpdateMode)")
-        session.requestCursorUpdates(cursorUpdateMode)
-        return true
-    }
-
-    override fun getExtractedText(request: ExtractedTextRequest?, flags: Int): ExtractedText {
-        logDebug("getExtractedText($request, $flags)")
-//        extractedTextMonitorMode = (flags and InputConnection.GET_EXTRACTED_TEXT_MONITOR) != 0
-//        if (extractedTextMonitorMode) {
-//            currentExtractedTextRequestToken = request?.token ?: 0
-//        }
-        // TODO(halilibo): Implement extracted text monitor
-        // TODO(b/135556699) should return styled text
-        return text.toExtractedText()
-    }
-
-    override fun getCursorCapsMode(reqModes: Int): Int {
-        logDebug("getCursorCapsMode($reqModes)")
-        return TextUtils.getCapsMode(text, text.selectionInChars.min, reqModes)
-    }
-
-    // endregion
-
-    // region Editor action and Key events.
-
-    override fun performContextMenuAction(id: Int): Boolean {
-        logDebug("performContextMenuAction($id)")
-        when (id) {
-            android.R.id.selectAll -> {
-                // no need to batch context menu actions.
-                session.requestEdit(notifyImeOfChanges = true) {
-                    setSelection(0, text.length)
-                }
-            }
-            // TODO(siyamed): Need proper connection to cut/copy/paste
-            android.R.id.cut -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_CUT)
-            android.R.id.copy -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_COPY)
-            android.R.id.paste -> sendSynthesizedKeyEvent(KeyEvent.KEYCODE_PASTE)
-            android.R.id.startSelectingText -> {} // not supported
-            android.R.id.stopSelectingText -> {} // not supported
-            android.R.id.copyUrl -> {} // not supported
-            android.R.id.switchInputMethod -> {} // not supported
-            else -> {
-                // not supported
-            }
-        }
-        return false
-    }
-
-    private fun sendSynthesizedKeyEvent(code: Int) {
-        sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, code))
-        sendKeyEvent(KeyEvent(KeyEvent.ACTION_UP, code))
-    }
-
-    override fun performEditorAction(editorAction: Int): Boolean {
-        logDebug("performEditorAction($editorAction)")
-
-        val imeAction = when (editorAction) {
-            EditorInfo.IME_ACTION_UNSPECIFIED -> ImeAction.Default
-            EditorInfo.IME_ACTION_DONE -> ImeAction.Done
-            EditorInfo.IME_ACTION_SEND -> ImeAction.Send
-            EditorInfo.IME_ACTION_SEARCH -> ImeAction.Search
-            EditorInfo.IME_ACTION_PREVIOUS -> ImeAction.Previous
-            EditorInfo.IME_ACTION_NEXT -> ImeAction.Next
-            EditorInfo.IME_ACTION_GO -> ImeAction.Go
-            else -> {
-                logDebug("IME sent an unrecognized editor action: $editorAction")
-                ImeAction.Default
-            }
-        }
-
-        session.onImeAction(imeAction)
-        return true
-    }
-
-    // endregion
-
-    // region Unsupported callbacks
-
-    override fun commitCompletion(text: CompletionInfo?): Boolean {
-        logDebug("commitCompletion(${text?.text})")
-        // We don't support this callback.
-        // The API documents says this should return if the input connection is no longer valid, but
-        // The Chromium implementation already returning false, so assuming it is safe to return
-        // false if not supported.
-        // see https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
-        return false
-    }
-
-    override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
-        // logDebug("commitCorrection($correctionInfo),autoCorrect:$autoCorrect")
-        // Should add an event here so that we can implement the autocorrect highlight
-        // Bug: 170647219
-        // TODO(halilibo): Implement autoCorrect from ImeOptions
-        return true
-    }
-
-    override fun getHandler(): Handler? {
-        logDebug("getHandler()")
-        return null // Returns null means using default Handler
-    }
-
-    override fun clearMetaKeyStates(states: Int): Boolean {
-        logDebug("clearMetaKeyStates($states)")
-        // We don't support this callback.
-        // The API documents says this should return if the input connection is no longer valid, but
-        // The Chromium implementation already returning false, so assuming it is safe to return
-        // false if not supported.
-        // see https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
-        return false
-    }
-
-    override fun reportFullscreenMode(enabled: Boolean): Boolean {
-        logDebug("reportFullscreenMode($enabled)")
-        return false // This value is ignored according to the API docs.
-    }
-
-    override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
-        logDebug("performPrivateCommand($action, $data)")
-        return commitContentDelegateInputConnection.performPrivateCommand(action, data)
-    }
-
-    override fun commitContent(
-        inputContentInfo: InputContentInfo,
-        flags: Int,
-        opts: Bundle?
-    ): Boolean {
-        logDebug("commitContent($inputContentInfo, $flags, $opts)")
-        return if (Build.VERSION.SDK_INT >= 25) {
-            Api25CommitContentImpl.commitContent(
-                inputConnection = commitContentDelegateInputConnection,
-                inputContentInfo = inputContentInfo,
-                flags = flags,
-                opts = opts
-            )
-        } else {
-            // This should never happen. Platform does not know about `commitContent` below API 25
-            // so it cannot be called.
-            false
-        }
-    }
-
-    // endregion
-
-    private fun logDebug(message: String) {
-        if (SIC_DEBUG) {
-            Log.d(TAG, "$DEBUG_CLASS.$message")
-        }
-    }
-}
-
-@RequiresApi(25)
-private object Api25CommitContentImpl {
-
-    @DoNotInline
-    fun commitContent(
-        inputConnection: InputConnection,
-        inputContentInfo: InputContentInfo,
-        flags: Int,
-        opts: Bundle?
-    ): Boolean {
-        return inputConnection.commitContent(inputContentInfo, flags, opts)
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private fun TextFieldCharSequence.toExtractedText(): ExtractedText {
-    val res = ExtractedText()
-    res.text = this
-    res.startOffset = 0
-    res.partialEndOffset = length
-    res.partialStartOffset = -1 // -1 means full text
-    res.selectionStart = selectionInChars.min
-    res.selectionEnd = selectionInChars.max
-    res.flags = if ('\n' in this) 0 else ExtractedText.FLAG_SINGLE_LINE
-    return res
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun InputContentInfoCompat.toTransferableContent(extras: Bundle?): TransferableContent {
-    val clipData = ClipData(description, ClipData.Item(contentUri))
-    return TransferableContent(
-        clipEntry = clipData.toClipEntry(),
-        source = TransferableContent.Source.Keyboard,
-        clipMetadata = description.toClipMetadata(),
-        platformTransferableContent = PlatformTransferableContent(
-            linkUri = linkUri,
-            extras = extras ?: Bundle.EMPTY
-        )
-    )
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.android.kt
deleted file mode 100644
index 2f4292e..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.android.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.draganddrop.DragAndDropModifierNode
-import androidx.compose.ui.draganddrop.DragAndDropTarget
-import androidx.compose.ui.draganddrop.toAndroidDragEvent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.ClipEntry
-import androidx.compose.ui.platform.ClipMetadata
-import androidx.compose.ui.platform.toClipEntry
-import androidx.compose.ui.platform.toClipMetadata
-
-@OptIn(ExperimentalFoundationApi::class)
-internal actual fun textFieldDragAndDropNode(
-    hintMediaTypes: () -> Set<MediaType>,
-    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
-    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
-    onStarted: ((event: DragAndDropEvent) -> Unit)?,
-    onEntered: ((event: DragAndDropEvent) -> Unit)?,
-    onMoved: ((position: Offset) -> Unit)?,
-    onChanged: ((event: DragAndDropEvent) -> Unit)?,
-    onExited: ((event: DragAndDropEvent) -> Unit)?,
-    onEnded: ((event: DragAndDropEvent) -> Unit)?,
-): DragAndDropModifierNode {
-    return DragAndDropModifierNode(
-        shouldStartDragAndDrop = { dragAndDropEvent ->
-            // If there's a receiveContent modifier wrapping around this TextField, initially all
-            // dragging items should be accepted for drop. This is expected to be met by the caller
-            // of this function.
-            val clipDescription = dragAndDropEvent.toAndroidDragEvent().clipDescription
-            hintMediaTypes().any {
-                it == MediaType.All || clipDescription.hasMimeType(it.representation)
-            }
-        },
-        target = object : DragAndDropTarget {
-            override fun onDrop(event: DragAndDropEvent): Boolean {
-                dragAndDropRequestPermission(event)
-                return onDrop.invoke(
-                    event.toAndroidDragEvent().clipData.toClipEntry(),
-                    event.toAndroidDragEvent().clipDescription.toClipMetadata()
-                )
-            }
-
-            override fun onStarted(event: DragAndDropEvent) =
-                onStarted?.invoke(event) ?: Unit
-
-            override fun onEntered(event: DragAndDropEvent) =
-                onEntered?.invoke(event) ?: Unit
-
-            override fun onMoved(event: DragAndDropEvent) = with(event.toAndroidDragEvent()) {
-                onMoved?.invoke(Offset(x, y)) ?: Unit
-            }
-
-            override fun onExited(event: DragAndDropEvent) =
-                onExited?.invoke(event) ?: Unit
-
-            override fun onChanged(event: DragAndDropEvent) =
-                onChanged?.invoke(event) ?: Unit
-
-            override fun onEnded(event: DragAndDropEvent) =
-                onEnded?.invoke(event) ?: Unit
-        }
-    )
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt
deleted file mode 100644
index 2e48e57..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.view.InputDevice.SOURCE_DPAD
-import android.view.KeyEvent.KEYCODE_DPAD_CENTER
-import android.view.KeyEvent.KEYCODE_DPAD_DOWN
-import android.view.KeyEvent.KEYCODE_DPAD_LEFT
-import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
-import android.view.KeyEvent.KEYCODE_DPAD_UP
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
-import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.nativeKeyCode
-import androidx.compose.ui.input.key.type
-import androidx.compose.ui.platform.SoftwareKeyboardController
-
-internal actual fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler =
-    AndroidTextFieldKeyEventHandler()
-
-internal class AndroidTextFieldKeyEventHandler : TextFieldKeyEventHandler() {
-
-    override fun onPreKeyEvent(
-        event: KeyEvent,
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        focusManager: FocusManager,
-        keyboardController: SoftwareKeyboardController
-    ): Boolean {
-        // do not proceed if common code has consumed the event
-        if (
-            super.onPreKeyEvent(
-                event = event,
-                textFieldState = textFieldState,
-                textFieldSelectionState = textFieldSelectionState,
-                focusManager = focusManager,
-                keyboardController = keyboardController
-            )
-        ) return true
-
-        val device = event.nativeKeyEvent.device
-        return when {
-            device == null -> false
-
-            // Ignore key events from non-dpad sources
-            !device.supportsSource(SOURCE_DPAD) -> false
-
-            // Ignore key events from virtual keyboards
-            device.isVirtual -> false
-
-            // Ignore key release events
-            event.type != KeyDown -> false
-
-            event.isKeyCode(KEYCODE_DPAD_UP) -> focusManager.moveFocus(FocusDirection.Up)
-            event.isKeyCode(KEYCODE_DPAD_DOWN) -> focusManager.moveFocus(FocusDirection.Down)
-            event.isKeyCode(KEYCODE_DPAD_LEFT) -> focusManager.moveFocus(FocusDirection.Left)
-            event.isKeyCode(KEYCODE_DPAD_RIGHT) -> focusManager.moveFocus(FocusDirection.Right)
-            event.isKeyCode(KEYCODE_DPAD_CENTER) -> {
-                // Enable keyboard on center key press
-                keyboardController.show()
-                true
-            }
-            else -> false
-        }
-    }
-}
-
-private fun KeyEvent.isKeyCode(keyCode: Int): Boolean =
-    this.key.nativeKeyCode == keyCode
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.android.kt
deleted file mode 100644
index 45f1dac..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.android.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.view.KeyEvent
-import android.view.inputmethod.InputConnection
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.TextFieldValue
-
-/**
- * The dependencies and actions required by a [StatelessInputConnection] connection. Decouples
- * [StatelessInputConnection] from [TextFieldState] for testability.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal interface TextInputSession {
-
-    /**
-     * The current [TextFieldValue] in this input session. This value is typically supplied by a
-     * backing [TextFieldState] that is used to initialize the session.
-     */
-    val text: TextFieldCharSequence
-
-    /**
-     * Callback to execute for InputConnection to communicate the changes requested by the IME.
-     *
-     * @param notifyImeOfChanges Normally any request coming from IME should not be
-     * back-communicated but [InputConnection.performContextMenuAction] does not behave like a
-     * regular IME command. Its changes must be resent to IME to keep it in sync with
-     * [TextFieldState].
-     * @param block Lambda scoped to an EditingBuffer to apply changes direct onto a buffer.
-     */
-    fun requestEdit(notifyImeOfChanges: Boolean = false, block: EditingBuffer.() -> Unit)
-
-    /**
-     * Delegates IME requested KeyEvents.
-     */
-    fun sendKeyEvent(keyEvent: KeyEvent)
-
-    /**
-     * Callback to run when IME sends an action via [InputConnection.performEditorAction]
-     */
-    fun onImeAction(imeAction: ImeAction)
-
-    /**
-     * Callback to run when IME sends a content via [InputConnection.commitContent]
-     */
-    fun onCommitContent(transferableContent: TransferableContent): Boolean
-
-    /**
-     * Called from [InputConnection.requestCursorUpdates].
-     */
-    fun requestCursorUpdates(cursorUpdateMode: Int)
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.android.kt
deleted file mode 100644
index fec2aee..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.android.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import android.text.TextUtils
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.toCharArray
-
-@OptIn(ExperimentalFoundationApi::class)
-internal actual fun CharSequence.toCharArray(
-    destination: CharArray,
-    destinationOffset: Int,
-    startIndex: Int,
-    endIndex: Int
-) {
-    if (this is TextFieldCharSequence) {
-        toCharArray(destination, destinationOffset, startIndex, endIndex)
-    } else {
-        TextUtils.getChars(this, startIndex, endIndex, destination, destinationOffset)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.android.kt
deleted file mode 100644
index 77f8a62c..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.android.kt
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import android.annotation.SuppressLint
-import androidx.compose.animation.core.Animatable
-import androidx.compose.foundation.MagnifierNode
-import androidx.compose.foundation.isPlatformMagnifierSupported
-import androidx.compose.foundation.text.selection.MagnifierSpringSpec
-import androidx.compose.foundation.text.selection.OffsetDisplacementThreshold
-import androidx.compose.foundation.text.selection.UnspecifiedSafeOffsetVectorConverter
-import androidx.compose.foundation.text2.input.internal.TextLayoutState
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.geometry.isSpecified
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.unit.IntSize
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-internal class TextFieldMagnifierNodeImpl28(
-    private var textFieldState: TransformedTextFieldState,
-    private var textFieldSelectionState: TextFieldSelectionState,
-    private var textLayoutState: TextLayoutState,
-    private var visible: Boolean
-) : TextFieldMagnifierNode(), CompositionLocalConsumerModifierNode {
-
-    private var magnifierSize: IntSize by mutableStateOf(IntSize.Zero)
-
-    // Can't use Offset.VectorConverter because we need to handle Unspecified specially.
-    private val animatable =
-        Animatable(
-            initialValue = calculateSelectionMagnifierCenterAndroid(
-                textFieldState = textFieldState,
-                selectionState = textFieldSelectionState,
-                textLayoutState = textLayoutState,
-                magnifierSize = magnifierSize
-            ),
-            typeConverter = UnspecifiedSafeOffsetVectorConverter,
-            visibilityThreshold = OffsetDisplacementThreshold
-        )
-
-    private val magnifierNode = delegate(
-        MagnifierNode(
-            sourceCenter = { animatable.value },
-            onSizeChanged = { size ->
-                magnifierSize = with(currentValueOf(LocalDensity)) {
-                    IntSize(size.width.roundToPx(), size.height.roundToPx())
-                }
-            },
-            useTextDefault = true
-        )
-    )
-
-    private var animationJob: Job? = null
-
-    override fun onAttach() {
-        restartAnimationJob()
-    }
-
-    override fun update(
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        textLayoutState: TextLayoutState,
-        visible: Boolean
-    ) {
-        val previousTextFieldState = this.textFieldState
-        val previousSelectionState = this.textFieldSelectionState
-        val previousLayoutState = this.textLayoutState
-        val wasVisible = this.visible
-
-        this.textFieldState = textFieldState
-        this.textFieldSelectionState = textFieldSelectionState
-        this.textLayoutState = textLayoutState
-        this.visible = visible
-
-        if (textFieldState != previousTextFieldState ||
-            textFieldSelectionState != previousSelectionState ||
-            textLayoutState != previousLayoutState ||
-            visible != wasVisible
-        ) {
-            restartAnimationJob()
-        }
-    }
-
-    private fun restartAnimationJob() {
-        animationJob?.cancel()
-        animationJob = null
-        // never start an expensive animation job if magnifier is not explicitly set to be visible
-        // or magnifier is not supported.
-        if (!visible || !isPlatformMagnifierSupported()) return
-        animationJob = coroutineScope.launch {
-            val animationScope = this
-            snapshotFlow {
-                calculateSelectionMagnifierCenterAndroid(
-                    textFieldState,
-                    textFieldSelectionState,
-                    textLayoutState,
-                    magnifierSize
-                )
-            }
-                .collect { targetValue ->
-                    // Only animate the position when moving vertically (i.e. jumping between
-                    // lines), since horizontal movement in a single line should stay as close to
-                    // the gesture as possible and animation would only add unnecessary lag.
-                    if (
-                        animatable.value.isSpecified &&
-                        targetValue.isSpecified &&
-                        animatable.value.y != targetValue.y
-                    ) {
-                        // Launch the animation, instead of cancelling and re-starting manually via
-                        // collectLatest, so if another animation is started before this one
-                        // finishes, the new one will use the correct velocity, e.g. in order to
-                        // propagate spring inertia.
-                        animationScope.launch {
-                            animatable.animateTo(targetValue, MagnifierSpringSpec)
-                        }
-                    } else {
-                        animatable.snapTo(targetValue)
-                    }
-                }
-        }
-    }
-
-    // TODO(halilibo) Remove this once delegation can propagate this events on its own
-    override fun ContentDrawScope.draw() {
-        drawContent()
-        with(magnifierNode) { draw() }
-    }
-
-    // TODO(halilibo) Remove this once delegation can propagate this events on its own
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        magnifierNode.onGloballyPositioned(coordinates)
-    }
-
-    // TODO(halilibo) Remove this once delegation can propagate this events on its own
-    override fun SemanticsPropertyReceiver.applySemantics() {
-        with(magnifierNode) { applySemantics() }
-    }
-}
-
-/**
- * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to
- * whether magnifier is supported.
- */
-@SuppressLint("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-internal actual fun textFieldMagnifierNode(
-    textFieldState: TransformedTextFieldState,
-    textFieldSelectionState: TextFieldSelectionState,
-    textLayoutState: TextLayoutState,
-    visible: Boolean
-): TextFieldMagnifierNode {
-    return if (isPlatformMagnifierSupported()) {
-        TextFieldMagnifierNodeImpl28(
-            textFieldState = textFieldState,
-            textFieldSelectionState = textFieldSelectionState,
-            textLayoutState = textLayoutState,
-            visible = visible
-        )
-    } else {
-        object : TextFieldMagnifierNode() {
-            override fun update(
-                textFieldState: TransformedTextFieldState,
-                textFieldSelectionState: TextFieldSelectionState,
-                textLayoutState: TextLayoutState,
-                visible: Boolean
-            ) {}
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
deleted file mode 100644
index c65c711..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2022 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.compose.foundation.text
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.constraintlayout.compose.ConstraintLayout
-import androidx.constraintlayout.compose.Dimension
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class BasicTextPaparazziTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun colorChangingState_changesColor() {
-        paparazzi.snapshot {
-            var color = remember { mutableStateOf(Color.Red) }
-            BasicText(
-                "ABCD",
-                color = { color.value }
-            )
-            SideEffect {
-                color.value = Color.Yellow
-            }
-        }
-    }
-
-    @Test
-    fun colorChangingState_changesColor_annotatedString() {
-        paparazzi.snapshot {
-            var color = remember { mutableStateOf(Color.Red) }
-            BasicText(
-                AnnotatedString("ABCD"),
-                color = { color.value }
-            )
-            SideEffect {
-                color.value = Color.Yellow
-            }
-        }
-    }
-
-    @Test
-    fun overflowEllipsis_doesEllipsis_whenInPreferredWrapContent() {
-        paparazzi.snapshot {
-            // b/275369323
-            ConstraintLayout(modifier = Modifier
-                .width(100.dp)
-                .background(Color.White)
-                .padding(8.dp)) {
-                val (thumbnail, textBox, actionButton) = createRefs()
-                Box(modifier = Modifier
-                    .background(Color.Green)
-                    .constrainAs(thumbnail) {
-                        top.linkTo(parent.top)
-                        start.linkTo(parent.start)
-                        bottom.linkTo(parent.bottom)
-                    }
-                    .size(28.dp)
-                )
-                // Text region
-                Column(
-                    modifier = Modifier
-                        .constrainAs(textBox) {
-                            top.linkTo(parent.top)
-                            bottom.linkTo(parent.bottom)
-                            start.linkTo(thumbnail.end)
-                            end.linkTo(actionButton.start)
-                            width = Dimension.preferredWrapContent
-                        },
-                ) {
-                    BasicText(
-                        text = "ASome very long text that is sure to clip in this layout",
-                        maxLines = 1,
-                        overflow = TextOverflow.Ellipsis
-                    )
-                }
-                Box(modifier = Modifier
-                    .constrainAs(actionButton) {
-                        top.linkTo(parent.top)
-                        bottom.linkTo(parent.bottom)
-                        end.linkTo(parent.end)
-                    }
-                    .background(Color.Blue.copy(alpha = 0.5f))
-                    .size(28.dp)
-                )
-            }
-        }
-    }
-
-    @Test
-    fun RtlAppliedCorrectly_inConstraintLayout_withWrapContentText() {
-        // b/275369323
-        paparazzi.snapshot {
-            CompositionLocalProvider(
-                LocalLayoutDirection provides LayoutDirection.Rtl
-            ) {
-                ConstraintLayout(
-                    Modifier
-                        .fillMaxWidth()
-                        .background(Color.Green)) {
-                    val (title, progressBar, expander) = createRefs()
-                    BasicText(
-                        text = "Locale-aware Text",
-                        modifier = Modifier
-                            .constrainAs(title) {
-                                top.linkTo(parent.top)
-                                start.linkTo(parent.start)
-                                end.linkTo(expander.start)
-                                width = Dimension.fillToConstraints
-                            }
-                            .border(2.dp, Color.Red)
-                    )
-                    Box(
-                        modifier = Modifier
-                            .constrainAs(progressBar) {
-                                top.linkTo(title.bottom)
-                                start.linkTo(parent.start)
-                                end.linkTo(expander.start)
-                                width = Dimension.fillToConstraints
-                                height = Dimension.value(10.dp)
-                            }
-                            .background(Color.Yellow)
-                    )
-                    // expander image button
-                    Box(modifier = Modifier
-                        .constrainAs(expander) {
-                            top.linkTo(parent.top)
-                            start.linkTo(progressBar.end)
-                            end.linkTo(parent.end)
-                            width = Dimension.value(28.dp)
-                            height = Dimension.value(28.dp)
-                        }
-                        .background(Color.Cyan)
-                    )
-                }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt
new file mode 100644
index 0000000..84ec250
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.intl.Locale
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class AllCapsTransformationTest {
+
+    @Test
+    fun allCapsTransformation_definesCharacterCapitalizationKeyboardOption() {
+        val transformation = InputTransformation.allCaps(Locale.current)
+        assertThat(transformation.keyboardOptions?.capitalization)
+            .isEqualTo(KeyboardCapitalization.Characters)
+    }
+
+    @Test
+    fun allNewTypedCharacters_convertedToUppercase() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        val originalValue = TextFieldCharSequence("")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append("hello")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("HELLO")
+    }
+
+    @Test
+    fun oldCharacters_areNotConverted() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        val originalValue = TextFieldCharSequence("hello")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append(" world")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("hello WORLD")
+    }
+
+    @Test
+    fun localeDifference_turkishI() {
+        val transformation = InputTransformation.allCaps(Locale("tr"))
+
+        val originalValue = TextFieldCharSequence("")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append("i")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("\u0130") // Turkish dotted capital i
+    }
+
+    @Test
+    fun multipleEdits() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        var originalValue = TextFieldCharSequence("hello")
+        var buffer = TextFieldBuffer(originalValue)
+
+        with(buffer) {
+            delete(0, 3) // lo
+            replace(1, 1, "abc") // lABCo
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        originalValue = buffer.toTextFieldCharSequence()
+        buffer = TextFieldBuffer(originalValue)
+
+        with(buffer) {
+            delete(2, 3) // lACo
+            append("xyz") // lACoXYZ
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("lACoXYZ")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/InputTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/InputTransformationTest.kt
new file mode 100644
index 0000000..8472310
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/InputTransformationTest.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.KeyboardOptions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+class InputTransformationTest {
+
+    @Test
+    fun chainedFilters_areEqual() {
+        val filter1 = InputTransformation { _, _ ->
+            // Noop
+        }
+        val filter2 = InputTransformation { _, _ ->
+            // Noop
+        }
+
+        val chain1 = filter1.then(filter2)
+        val chain2 = filter1.then(filter2)
+
+        assertThat(chain1).isEqualTo(chain2)
+    }
+
+    @Test
+    fun chainedFilters_areNotEqual_whenFiltersAreDifferent() {
+        val filter1 = InputTransformation { _, _ ->
+            // Noop
+        }
+        val filter2 = InputTransformation { _, _ ->
+            // Noop
+        }
+        val filter3 = InputTransformation { _, _ ->
+            // Noop
+        }
+
+        val chain1 = filter1.then(filter2)
+        val chain2 = filter1.then(filter3)
+
+        assertThat(chain1).isNotEqualTo(chain2)
+    }
+
+    @Test
+    fun chainedFilters_haveNullKeyboardOptions_whenBothOptionsAreNull() {
+        val filter1 = object : InputTransformation {
+            override val keyboardOptions = null
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+        val filter2 = object : InputTransformation {
+            override val keyboardOptions = null
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+
+        val chain = filter1.then(filter2)
+
+        assertThat(chain.keyboardOptions).isNull()
+    }
+
+    @Test
+    fun chainedFilters_takeFirstKeyboardOptions_whenSecondOptionsAreNull() {
+        val options = KeyboardOptions()
+        val filter1 = object : InputTransformation {
+            override val keyboardOptions = options
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+        val filter2 = object : InputTransformation {
+            override val keyboardOptions = null
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+
+        val chain = filter1.then(filter2)
+
+        assertThat(chain.keyboardOptions).isSameInstanceAs(options)
+    }
+
+    @Test
+    fun chainedFilters_takeSecondKeyboardOptions_whenFirstOptionsAreNull() {
+        val options = KeyboardOptions()
+        val filter1 = object : InputTransformation {
+            override val keyboardOptions = null
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+        val filter2 = object : InputTransformation {
+            override val keyboardOptions = options
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+
+        val chain = filter1.then(filter2)
+
+        assertThat(chain.keyboardOptions).isSameInstanceAs(options)
+    }
+
+    @Test
+    fun chainedFilters_takeSecondKeyboardOptions_whenFirstOptionsAreNotNull() {
+        val options1 = KeyboardOptions()
+        val options2 = KeyboardOptions()
+        val filter1 = object : InputTransformation {
+            override val keyboardOptions = options1
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+        val filter2 = object : InputTransformation {
+            override val keyboardOptions = options2
+
+            override fun transformInput(
+                originalValue: TextFieldCharSequence,
+                valueWithChanges: TextFieldBuffer
+            ) {
+            }
+        }
+
+        val chain = filter1.then(filter2)
+
+        assertThat(chain.keyboardOptions).isSameInstanceAs(options2)
+    }
+
+    @Test
+    fun byValue_reverts_whenReturnsCurrent() {
+        val transformation = InputTransformation.byValue { current, _ -> current }
+        val current = TextFieldCharSequence("a")
+        val proposed = TextFieldCharSequence("ab")
+        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
+
+        transformation.transformInput(current, buffer)
+
+        assertThat(buffer.changes.changeCount).isEqualTo(0)
+        assertThat(buffer.toString()).isEqualTo(current.toString())
+    }
+
+    @Test
+    fun byValue_appliesChanges_whenReturnsSameContentAsCurrent() {
+        val transformation = InputTransformation.byValue { _, _ -> "a" }
+        val current = TextFieldCharSequence("a")
+        val proposed = TextFieldCharSequence("ab")
+        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
+
+        transformation.transformInput(current, buffer)
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo(current.toString())
+    }
+
+    @Test
+    fun byValue_noops_whenReturnsProposed() {
+        val transformation = InputTransformation.byValue { _, _ -> "ab" }
+        val current = TextFieldCharSequence("a")
+        val proposed = TextFieldCharSequence("ab")
+        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
+
+        transformation.transformInput(current, buffer)
+
+        assertThat(buffer.changes.changeCount).isEqualTo(0)
+        assertThat(buffer.toString()).isEqualTo(proposed.toString())
+    }
+
+    @Test
+    fun byValue_appliesChanges_whenDifferentCharSequenceReturned() {
+        val transformation = InputTransformation.byValue { _, _ -> "c" }
+        val current = TextFieldCharSequence("a")
+        val proposed = TextFieldCharSequence("ab")
+        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
+
+        transformation.transformInput(current, buffer)
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("c")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldBufferTest.kt
new file mode 100644
index 0000000..fdfd9be
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldBufferTest.kt
@@ -0,0 +1,680 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.text.ParseException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TextFieldBufferTest {
+
+    @Test
+    fun initialSelection() {
+        val state = TextFieldBuffer(TextFieldCharSequence())
+        assertThat(state.selectionInChars).isEqualTo(TextRange(0))
+        assertThat(state.hasSelection).isFalse()
+    }
+
+    @Test
+    fun selectionAdjusted_empty_textInserted() {
+        testSelectionAdjustment("", { append("hello") }, "hello_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtStart_textInsertedAtCursor() {
+        testSelectionAdjustment("_hello", { insert(0, "world") }, "world_hello")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtStart_textInsertedAfterCursor() {
+        testSelectionAdjustment("_hello", { append("world") }, "_helloworld")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtStart_textReplacedAroundCursor() {
+        testSelectionAdjustment("_hello", { replace(0, length, "foo") }, "_foo")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtEnd_textInsertedAtCursor() {
+        testSelectionAdjustment("hello_", { append("world") }, "helloworld_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtEnd_textInsertedBeforeCursor() {
+        testSelectionAdjustment("hello_", { insert(0, "world") }, "worldhello_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorAtEnd_textReplacedAroundCursor() {
+        testSelectionAdjustment("hello_", { replace(0, length, "foo") }, "foo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorInMiddle_textInsertedAtCursor() {
+        testSelectionAdjustment("he_llo", { insert(2, "foo") }, "hefoo_llo")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorInMiddle_textReplacedJustBeforeCursor() {
+        testSelectionAdjustment("he_llo", { replace(0, 2, "foo") }, "foo_llo")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorInMiddle_textReplacedJustAfterCursor() {
+        testSelectionAdjustment("he_llo", { replace(2, 3, "foo") }, "he_foolo")
+    }
+
+    @Test
+    fun selectionAdjusted_whenCursorInMiddle_textReplacedAroundCursor() {
+        testSelectionAdjustment("he_llo", { replace(1, 3, "foo") }, "hfoo_lo")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_allReplacedWithShorter() {
+        testSelectionAdjustment("_hello_", { replace(0, length, "foo") }, "_foo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_allReplacedWithLonger() {
+        testSelectionAdjustment("_hello_", { replace(0, length, "abracadabra") }, "_abracadabra_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideSelection_withLonger() {
+        testSelectionAdjustment("_hello_", { replace(1, 4, "world") }, "_hworldo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideSelection_withShorter() {
+        testSelectionAdjustment("_hello_", { replace(1, 4, "w") }, "_hwo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideFromStart_withLonger() {
+        testSelectionAdjustment("_hello_", { replace(0, 3, "world") }, "_worldlo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideFromStart_withShorter() {
+        testSelectionAdjustment("_hello_", { replace(1, 3, "w") }, "_hwlo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideToEnd_withLonger() {
+        testSelectionAdjustment("_hello_", { replace(length - 3, length, "world") }, "_heworld_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenAllSelected_textReplacedInsideToEnd_withShorter() {
+        testSelectionAdjustment("_hello_", { replace(length - 3, length, "w") }, "_hew_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenInsideSelected_textReplacedJustBeforeSelection() {
+        testSelectionAdjustment("hel_lo_", { replace(0, 3, "world") }, "world_lo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenInsideSelected_textReplacedJustAfterSelection() {
+        testSelectionAdjustment("_he_llo", { replace(2, length, "world") }, "_he_world")
+    }
+
+    @Test
+    fun selectionAdjusted_whenInsideSelected_textReplacedAroundStart() {
+        testSelectionAdjustment("h_ello_", { replace(0, 3, "world") }, "world_lo_")
+    }
+
+    @Test
+    fun selectionAdjusted_whenInsideSelected_textReplacedAroundEnd() {
+        testSelectionAdjustment("_hell_o", { replace(2, length, "world") }, "_he_world")
+    }
+
+    @Test
+    fun resetTo_copiesTextAndSelection() {
+        val expectedValue = TextFieldCharSequence("world", TextRange(5))
+        val state = TextFieldBuffer(
+            initialValue = TextFieldCharSequence("hello", TextRange(2)),
+            sourceValue = expectedValue
+        )
+        state.revertAllChanges()
+        assertThat(state.toTextFieldCharSequence()).isEqualTo(expectedValue)
+        assertThat(state.changes.changeCount).isEqualTo(0)
+    }
+
+    @Test
+    fun placeCursorBeforeCharAt_emptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorBeforeCharAt(-1)
+        }
+
+        buffer.placeCursorBeforeCharAt(0)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorBeforeCharAt(1)
+        }
+    }
+
+    @Test
+    fun placeCursorBeforeCharAt_nonEmptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorBeforeCharAt(-1)
+        }
+
+        buffer.placeCursorBeforeCharAt(0)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        buffer.placeCursorBeforeCharAt(1)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(1))
+
+        buffer.placeCursorBeforeCharAt(5)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorBeforeCharAt(6)
+        }
+    }
+
+    @Test
+    fun placeCursorAfterCharAt_emptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+
+        buffer.placeCursorAfterCharAt(-1)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorAfterCharAt(0)
+        }
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorAfterCharAt(1)
+        }
+    }
+
+    @Test
+    fun placeCursorAfterCharAt_nonEmptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
+
+        buffer.placeCursorAfterCharAt(-1)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        buffer.placeCursorAfterCharAt(0)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(1))
+
+        buffer.placeCursorAfterCharAt(1)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(2))
+
+        buffer.placeCursorAfterCharAt(4)
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.placeCursorAfterCharAt(5)
+        }
+    }
+
+    @Test
+    fun selectCharsIn_emptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+
+        buffer.selectCharsIn(TextRange(0))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.selectCharsIn(TextRange(0, 1))
+        }
+    }
+
+    @Test
+    fun selectCharsIn_nonEmptyBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
+
+        buffer.selectCharsIn(TextRange(0))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
+
+        buffer.selectCharsIn(TextRange(0, 1))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0, 1))
+
+        buffer.selectCharsIn(TextRange(0, 5))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0, 5))
+
+        buffer.selectCharsIn(TextRange(4, 5))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(4, 5))
+
+        buffer.selectCharsIn(TextRange(5, 5))
+        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5, 5))
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.selectCharsIn(TextRange(5, 6))
+        }
+
+        assertFailsWith<IllegalArgumentException> {
+            buffer.selectCharsIn(TextRange(6, 6))
+        }
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenChanged() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("world")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("world")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenPrefixChanged() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("1ello")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("1ello")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenPrefixAdded() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("1hello")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("1hello")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenPrefixRemoved() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("ello")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("ello")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenSuffixChanged() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("hell1")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("hell1")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(4, 5))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenSuffixAdded() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("hello1")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("hello1")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(5))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(5, 6))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenSuffixRemoved() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("hell")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("hell")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleChanged_once() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("h1llo")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("h1llo")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 2))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 2))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleAdded_once() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("he1llo")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("he1llo")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2, 3))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleRemoved_once() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("helo")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("helo")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2, 3))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleChanged_multiple() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("h1l2o")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("h1l2o")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 4))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleAdded_multiple() {
+        val text = "hello"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("he1ll2o")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("he1ll2o")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2, 6))
+    }
+
+    @Test
+    fun setTextIfChanged_updatesText_whenMiddleRemoved_multiple() {
+        val text = "abcde"
+        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
+
+        buffer.setTextIfChanged("ace")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.toString()).isEqualTo("ace")
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 2))
+    }
+
+    @Test
+    fun setTextIfChanged_doesNotUpdateTextIfEqual() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
+
+        buffer.setTextIfChanged("hello")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(0)
+    }
+
+    @Test
+    fun setTextIfChanged_doesNotUpdateTextIfEqual_afterChange() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
+        buffer.append(" world")
+
+        buffer.setTextIfChanged("hello world")
+
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+    }
+
+    @Test
+    fun charAt_throws_whenEmpty() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence())
+
+        assertFailsWith<IndexOutOfBoundsException> {
+            buffer.charAt(0)
+        }
+    }
+
+    @Test
+    fun charAt_throws_whenOutOfBounds() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("a"))
+
+        assertFailsWith<IndexOutOfBoundsException> {
+            buffer.charAt(1)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            buffer.charAt(-1)
+        }
+    }
+
+    @Test
+    fun charAt_returnsChars() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence("ab"))
+        assertThat(buffer.charAt(0)).isEqualTo('a')
+        assertThat(buffer.charAt(1)).isEqualTo('b')
+    }
+
+    @Test
+    fun asCharSequence_isViewOfBuffer() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence())
+        val charSequence = buffer.asCharSequence()
+
+        assertThat(charSequence.toString()).isEmpty()
+
+        buffer.append("hello")
+
+        assertThat(charSequence.toString()).isEqualTo("hello")
+    }
+
+    @Test
+    fun replace_withSubSequence_crossedOffsets() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        val error = assertFailsWith<IllegalArgumentException> {
+            buffer.replace(0, 0, "hi", 2, 0)
+        }
+        assertThat(error.message).isEqualTo("Expected textStart=2 <= textEnd=0")
+    }
+
+    @Test
+    fun replace_withSubSequence_startTooSmall() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        assertFailsWith<IllegalArgumentException> {
+            buffer.replace(0, 0, "hi", -1, 0)
+        }
+    }
+
+    @Test
+    fun replace_withSubSequence_endTooBig() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        assertFailsWith<IndexOutOfBoundsException> {
+            buffer.replace(0, 0, "hi", 2, 3)
+        }
+    }
+
+    @Test
+    fun replace_withSubSequence_empty() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        buffer.replace(0, 0, "hi", 0, 0)
+        assertThat(buffer.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun replace_withSubSequence_singleCharFromStart() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        buffer.replace(0, 0, "hi", 0, 1)
+        assertThat(buffer.toString()).isEqualTo("h")
+    }
+
+    @Test
+    fun replace_withSubSequence_singleCharFromEnd() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        buffer.replace(0, 0, "hi", 1, 2)
+        assertThat(buffer.toString()).isEqualTo("i")
+    }
+
+    @Test
+    fun replace_withSubSequence_middle() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        buffer.replace(0, 0, "abcd", 1, 3)
+        assertThat(buffer.toString()).isEqualTo("bc")
+    }
+
+    @Test
+    fun replace_withSubSequence_full() {
+        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
+        buffer.replace(0, 0, "abcd", 0, 4)
+        assertThat(buffer.toString()).isEqualTo("abcd")
+    }
+
+    @Test
+    fun findCommonPrefixAndSuffix_works() {
+        assertCommonPrefixAndSuffix("", "", null)
+        assertCommonPrefixAndSuffix("a", "a", null)
+        assertCommonPrefixAndSuffix("abc", "abc", null)
+        assertCommonPrefixAndSuffix("", "b", TextRange(0) to TextRange(0, 1))
+        assertCommonPrefixAndSuffix("a", "", TextRange(0, 1) to TextRange(0))
+        assertCommonPrefixAndSuffix("ab", "ac", TextRange(1, 2) to TextRange(1, 2))
+        assertCommonPrefixAndSuffix("abb", "ac", TextRange(1, 3) to TextRange(1, 2))
+        assertCommonPrefixAndSuffix("ab", "acc", TextRange(1, 2) to TextRange(1, 3))
+        assertCommonPrefixAndSuffix("az", "bz", TextRange(0, 1) to TextRange(0, 1))
+        assertCommonPrefixAndSuffix("cba", "za", TextRange(0, 2) to TextRange(0, 1))
+        assertCommonPrefixAndSuffix("za", "cba", TextRange(0, 1) to TextRange(0, 2))
+        assertCommonPrefixAndSuffix("aoz", "apz", TextRange(1, 2) to TextRange(1, 2))
+        assertCommonPrefixAndSuffix("amnoz", "az", TextRange(1, 4) to TextRange(1, 1))
+        assertCommonPrefixAndSuffix("az", "amnoz", TextRange(1, 1) to TextRange(1, 4))
+        assertCommonPrefixAndSuffix("amnoz", "axz", TextRange(1, 4) to TextRange(1, 2))
+        assertCommonPrefixAndSuffix("axz", "amnoz", TextRange(1, 2) to TextRange(1, 4))
+    }
+
+    /** Tests of private testing helper code. */
+    @Test
+    fun testConvertTextFieldValueToAndFromString() {
+        assertThat("".parseAsTextEditState()).isEqualTo(TextFieldCharSequence())
+        assertThat("hello".parseAsTextEditState()).isEqualTo(TextFieldCharSequence("hello"))
+        assertThat("_hello".parseAsTextEditState()).isEqualTo(TextFieldCharSequence("hello"))
+        assertThat("h_ello".parseAsTextEditState())
+            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(1)))
+        assertThat("hello_".parseAsTextEditState())
+            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(5)))
+        assertThat("_hello_".parseAsTextEditState())
+            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(0, 5)))
+        assertThat("he__llo".parseAsTextEditState())
+            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(2)))
+        assertThat("he_l_lo".parseAsTextEditState())
+            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(2, 3)))
+        assertFailsWith<ParseException> {
+            "_he_llo_".parseAsTextEditState()
+        }
+
+        listOf("", "_hello", "h_ello", "hello_", "_hello_", "he_ll_o").forEach {
+            val value = it.parseAsTextEditState()
+            assertThat(value.toParsableString()).isEqualTo(it)
+        }
+    }
+
+    private fun testSelectionAdjustment(
+        initial: String,
+        transform: TextFieldBuffer.() -> Unit,
+        expected: String
+    ) {
+        val state = TextFieldBuffer(initial.parseAsTextEditState())
+        state.transform()
+        assertThat(state.toTextFieldCharSequence().toParsableString()).isEqualTo(expected)
+    }
+
+    /**
+     * Parses this string into a [TextFieldValue], replacing a single underscore with the cursor, or
+     * two underscores with a selection.
+     */
+    private fun String.parseAsTextEditState(): TextFieldCharSequence {
+        var firstMark = -1
+        var secondMark = -1
+        val source = this
+        val text = buildString {
+            source.forEachIndexed { i, char ->
+                if (char == '_') {
+                    when {
+                        firstMark == -1 -> firstMark = i
+                        secondMark == -1 -> secondMark = i - 1
+                        else -> throw ParseException("Unexpected underscore in \"$this\"", i)
+                    }
+                } else {
+                    append(char)
+                }
+            }
+        }
+
+        return TextFieldCharSequence(
+            text = text,
+            selection = when {
+                firstMark == -1 -> TextRange.Zero
+                secondMark == -1 -> TextRange(firstMark)
+                else -> TextRange(firstMark, secondMark)
+            }
+        )
+    }
+
+    private fun TextFieldCharSequence.toParsableString(): String = buildString {
+        append(this@toParsableString)
+        if (isNotEmpty()) {
+            insert(selectionInChars.min, '_')
+            if (!selectionInChars.collapsed) {
+                insert(selectionInChars.max + 1, '_')
+            }
+        }
+    }
+
+    private fun assertCommonPrefixAndSuffix(
+        a: CharSequence,
+        b: CharSequence,
+        expectedRanges: Pair<TextRange, TextRange>?
+    ) {
+        var result: Pair<TextRange, TextRange>? = null
+        findCommonPrefixAndSuffix(a, b) { aStart, aEnd, bStart, bEnd ->
+            result = Pair(TextRange(aStart, aEnd), TextRange(bStart, bEnd))
+        }
+        assertWithMessage("Expected findCommonPrefixAndSuffix(\"$a\", \"$b\") to report")
+            .that(result).isEqualTo(expectedRanges)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequenceTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequenceTest.kt
new file mode 100644
index 0000000..3c65b7c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequenceTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TextFieldCharSequenceTest {
+    private val defaultSaverScope = SaverScope { true }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun throws_exception_for_negative_selection() {
+        TextFieldCharSequence(text = "", selection = TextRange(-1))
+    }
+
+    @Test
+    fun aligns_selection_to_the_text_length() {
+        val text = "a"
+        val textFieldValue =
+            TextFieldCharSequence(text = text, selection = TextRange(text.length + 1))
+        assertThat(textFieldValue.selectionInChars.collapsed).isTrue()
+        assertThat(textFieldValue.selectionInChars.max).isEqualTo(textFieldValue.length)
+    }
+
+    @Test
+    fun keep_selection_that_is_less_than_text_length() {
+        val text = "a bc"
+        val selection = TextRange(0, "a".length)
+
+        val textFieldValue = TextFieldCharSequence(text = text, selection = selection)
+
+        assertThat(textFieldValue.toString()).isEqualTo(text)
+        assertThat(textFieldValue.selectionInChars).isEqualTo(selection)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun throws_exception_for_negative_composition() {
+        TextEditState(text = "", composition = TextRange(-1))
+    }
+
+    @Test
+    fun aligns_composition_to_text_length() {
+        val text = "a"
+        val textFieldValue = TextEditState(text = text, composition = TextRange(text.length + 1))
+        assertThat(textFieldValue.compositionInChars?.collapsed).isTrue()
+        assertThat(textFieldValue.compositionInChars?.max).isEqualTo(textFieldValue.length)
+    }
+
+    @Test
+    fun keep_composition_that_is_less_than_text_length() {
+        val text = "a bc"
+        val composition = TextRange(0, "a".length)
+
+        val textFieldValue = TextEditState(text = text, composition = composition)
+
+        assertThat(textFieldValue.toString()).isEqualTo(text)
+        assertThat(textFieldValue.compositionInChars).isEqualTo(composition)
+    }
+
+    @Test
+    fun equals_returns_true_for_same_instance() {
+        val textFieldValue = TextFieldCharSequence(
+            text = "a",
+            selection = TextRange(1),
+            composition = TextRange(2)
+        )
+
+        assertThat(textFieldValue).isEqualTo(textFieldValue)
+    }
+
+    @Test
+    fun equals_returns_true_for_equivalent_object() {
+        val textFieldValue = TextFieldCharSequence(
+            text = "a",
+            selection = TextRange(1),
+            composition = TextRange(2)
+        )
+
+        assertThat(
+            TextFieldCharSequence(
+                textFieldValue,
+                textFieldValue.selectionInChars,
+                textFieldValue.compositionInChars
+            )
+        ).isEqualTo(textFieldValue)
+    }
+
+    @Test
+    fun text_and_selection_parameter_constructor_has_null_composition() {
+        val textFieldValue = TextFieldCharSequence(
+            text = "a",
+            selection = TextRange(1)
+        )
+
+        assertThat(textFieldValue.compositionInChars).isNull()
+    }
+
+    private fun TextEditState(text: String, composition: TextRange) =
+        TextFieldCharSequence(text, selection = TextRange.Zero, composition = composition)
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt
new file mode 100644
index 0000000..2cd47c2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.internal.commitText
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+class TextFieldStateSaverTest {
+
+    @Test
+    fun savesAndRestoresTextAndSelection() {
+        val state = TextFieldState("hello, world", initialSelectionInChars = TextRange(0, 5))
+
+        val saved = with(TextFieldState.Saver) { TestSaverScope.save(state) }
+        assertNotNull(saved)
+        val restoredState = TextFieldState.Saver.restore(saved)
+
+        assertNotNull(restoredState)
+        assertThat(restoredState.text.toString()).isEqualTo("hello, world")
+        assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun savesAndRestoresUndo() {
+        val state = TextFieldState("hello, world", initialSelectionInChars = TextRange(0, 5))
+
+        state.editAsUser(null) {
+            commitText("hi", 1)
+        }
+
+        val saved = with(TextFieldState.Saver) { TestSaverScope.save(state) }
+        assertNotNull(saved)
+        val restoredState = TextFieldState.Saver.restore(saved)
+
+        assertNotNull(restoredState)
+        assertThat(restoredState.text.toString()).isEqualTo("hi, world")
+        assertThat(restoredState.undoState.canUndo).isTrue()
+        restoredState.undoState.undo()
+        assertThat(restoredState.text.toString()).isEqualTo("hello, world")
+        assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
+    private object TestSaverScope : SaverScope {
+        override fun canBeSaved(value: Any): Boolean = true
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
new file mode 100644
index 0000000..a96cf05
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
@@ -0,0 +1,638 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotStateObserver
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+class TextFieldStateTest {
+
+    private val state = TextFieldState()
+
+    @Test
+    fun defaultInitialTextAndSelection() {
+        val state = TextFieldState()
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
+    }
+
+    @Test
+    fun customInitialTextAndDefaultSelection() {
+        val state = TextFieldState(initialText = "hello")
+        assertThat(state.text.toString()).isEqualTo("hello")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun customInitialTextAndSelection() {
+        val state = TextFieldState(initialText = "hello", initialSelectionInChars = TextRange(0, 1))
+        assertThat(state.text.toString()).isEqualTo("hello")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 1))
+    }
+
+    @Test
+    fun edit_doesNotChange_whenThrows() {
+        class ExpectedException : RuntimeException()
+
+        assertFailsWith<ExpectedException> {
+            state.edit {
+                replace(0, 0, "hello")
+                throw ExpectedException()
+            }
+        }
+
+        assertThat(state.text.toString()).isEmpty()
+    }
+
+    @Test
+    fun edit_invalidates_whenSelectionChanged() = runTestWithSnapshotsThenCancelChildren {
+        val text = "hello"
+        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
+        var invalidationCount = 0
+        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
+        val observeState: () -> Unit = { state.text }
+        observer.start()
+        try {
+            observer.observeReads(
+                scope = Unit,
+                onValueChangedForScope = {
+                    invalidationCount++
+                    observeState()
+                },
+                block = observeState
+            )
+            assertThat(invalidationCount).isEqualTo(0)
+
+            // Act.
+            state.edit {
+                selectCharsIn(TextRange(0, length))
+            }
+            advanceUntilIdle()
+            runCurrent()
+
+            // Assert.
+            assertThat(invalidationCount).isEqualTo(1)
+        } finally {
+            observer.stop()
+        }
+    }
+
+    @Test
+    fun edit_invalidates_whenTextChanged() = runTestWithSnapshotsThenCancelChildren {
+        val text = "hello"
+        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
+        var invalidationCount = 0
+        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
+        val observeState: () -> Unit = { state.text }
+        observer.start()
+        try {
+            observer.observeReads(
+                scope = Unit,
+                onValueChangedForScope = {
+                    invalidationCount++
+                    observeState()
+                },
+                block = observeState
+            )
+            assertThat(invalidationCount).isEqualTo(0)
+
+            // Act.
+            state.edit {
+                append("1")
+            }
+            advanceUntilIdle()
+            runCurrent()
+
+            // Assert.
+            assertThat(invalidationCount).isEqualTo(1)
+        } finally {
+            observer.stop()
+        }
+    }
+
+    @Test
+    fun edit_doesNotInvalidate_whenNoChangesMade() = runTestWithSnapshotsThenCancelChildren {
+        val text = "hello"
+        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
+        var invalidationCount = 0
+        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
+        val observeState: () -> Unit = { state.text }
+        observer.start()
+        try {
+            observer.observeReads(
+                scope = Unit,
+                onValueChangedForScope = {
+                    invalidationCount++
+                    observeState()
+                },
+                block = observeState
+            )
+            assertThat(invalidationCount).isEqualTo(0)
+
+            // Act.
+            state.edit {
+                // Change the selection but restore it before returning.
+                val originalSelection = selectionInChars
+                selectCharsIn(TextRange(0, length))
+                selectCharsIn(originalSelection)
+
+                // This will be a no-op too.
+                setTextIfChanged(text)
+            }
+            advanceUntilIdle()
+            runCurrent()
+
+            // Assert.
+            assertThat(invalidationCount).isEqualTo(0)
+        } finally {
+            observer.stop()
+        }
+    }
+
+    @Test
+    fun edit_replace_changesValueInPlace() {
+        state.edit {
+            replace(0, 0, "hello")
+            assertThat(toString()).isEqualTo("hello")
+            assertThat(length).isEqualTo(5)
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_replace_changesStateAfterReturn() {
+        state.edit {
+            replace(0, 0, "hello")
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("hello")
+    }
+
+    @Test
+    fun edit_replace_doesNotChangeStateUntilReturn() {
+        state.edit {
+            replace(0, 0, "hello")
+            assertThat(state.text.toString()).isEmpty()
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_multipleOperations() {
+        state.edit {
+            replace(0, 0, "hello")
+            replace(5, 5, "world")
+            replace(5, 5, " ")
+            replace(6, 11, "Compose")
+            assertThat(toString()).isEqualTo("hello Compose")
+            assertThat(state.text.toString()).isEmpty()
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("hello Compose")
+    }
+
+    @Test
+    fun edit_placeCursorAtEnd() {
+        state.edit {
+            replace(0, 0, "hello")
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun edit_placeCursorBeforeChar_simpleCase() {
+        state.edit {
+            replace(0, 0, "hello")
+            placeCursorBeforeCharAt(2)
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun edit_placeCursorBeforeChar_throws_whenInvalid() {
+        state.edit {
+            assertFailsWith<IllegalArgumentException> {
+                placeCursorBeforeCharAt(500)
+            }
+            assertFailsWith<IllegalArgumentException> {
+                placeCursorBeforeCharAt(-1)
+            }
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_placeCursorBeforeCodepoint_simpleCase() {
+        state.edit {
+            replace(0, 0, "hello")
+            placeCursorBeforeCodepointAt(2)
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun edit_placeCursorBeforeCodepoint_throws_whenInvalid() {
+        state.edit {
+            assertFailsWith<IllegalArgumentException> {
+                placeCursorBeforeCodepointAt(500)
+            }
+            assertFailsWith<IllegalArgumentException> {
+                placeCursorBeforeCodepointAt(-1)
+            }
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_selectAll() {
+        state.edit {
+            replace(0, 0, "hello")
+            selectAll()
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun edit_selectChars_simpleCase() {
+        state.edit {
+            replace(0, 0, "hello")
+            selectCharsIn(TextRange(1, 4))
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 4))
+    }
+
+    @Test
+    fun edit_selectChars_throws_whenInvalid() {
+        state.edit {
+            assertFailsWith<IllegalArgumentException> {
+                selectCharsIn(TextRange(500, 501))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCharsIn(TextRange(-1, 500))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCharsIn(TextRange(500, -1))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCharsIn(TextRange(-500, -1))
+            }
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_selectCodepoints_simpleCase() {
+        state.edit {
+            replace(0, 0, "hello")
+            selectCodepointsIn(TextRange(1, 4))
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 4))
+    }
+
+    @Test
+    fun edit_selectCodepoints_throws_whenInvalid() {
+        state.edit {
+            assertFailsWith<IllegalArgumentException> {
+                selectCodepointsIn(TextRange(500, 501))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCodepointsIn(TextRange(-1, 500))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCodepointsIn(TextRange(500, -1))
+            }
+            assertFailsWith<IllegalArgumentException> {
+                selectCodepointsIn(TextRange(-500, -1))
+            }
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun edit_afterEdit() {
+        state.edit {
+            replace(0, 0, "hello")
+            placeCursorAtEnd()
+        }
+        state.edit {
+            assertThat(toString()).isEqualTo("hello")
+            replace(5, 5, " world")
+            assertThat(toString()).isEqualTo("hello world")
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("hello world")
+    }
+
+    @Test
+    fun append_char() {
+        state.edit {
+            append('c')
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("c")
+    }
+
+    @Test
+    fun append_charSequence() {
+        state.edit {
+            append("hello")
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("hello")
+    }
+
+    @Test
+    fun append_charSequence_range() {
+        state.edit {
+            append("hello world", 0, 5)
+            placeCursorAtEnd()
+        }
+        assertThat(state.text.toString()).isEqualTo("hello")
+    }
+
+    @Test
+    fun setTextAndPlaceCursorAtEnd_works() {
+        state.setTextAndPlaceCursorAtEnd("Hello")
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun setTextAndSelectAll_works() {
+        state.setTextAndSelectAll("Hello")
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun replace_changesAreTracked() {
+        val state = TextFieldState("hello world")
+        state.edit {
+            replace(6, 11, "Compose")
+            assertThat(toString()).isEqualTo("hello Compose")
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 13))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6, 11))
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun appendChar_changesAreTracked() {
+        val state = TextFieldState("hello ")
+        state.edit {
+            append('c')
+            assertThat(toString()).isEqualTo("hello c")
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 7))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun appendCharSequence_changesAreTracked() {
+        val state = TextFieldState("hello ")
+        state.edit {
+            append("world")
+            assertThat(toString()).isEqualTo("hello world")
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 11))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun appendCharSequenceRange_changesAreTracked() {
+        val state = TextFieldState("hello ")
+        state.edit {
+            append("hello world", 6, 11)
+            assertThat(toString()).isEqualTo("hello world")
+            assertThat(changes.changeCount).isEqualTo(1)
+            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 11))
+            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
+            placeCursorAtEnd()
+        }
+    }
+
+    @Test
+    fun forEachValues_fires_immediately() = runTestWithSnapshotsThenCancelChildren {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
+        val texts = mutableListOf<TextFieldCharSequence>()
+
+        launch(Dispatchers.Unconfined) {
+            state.forEachTextValue { texts += it }
+        }
+
+        assertThat(texts).hasSize(1)
+        assertThat(texts.single()).isSameInstanceAs(state.text)
+        assertThat(texts.single().toString()).isEqualTo("hello")
+        assertThat(texts.single().selectionInChars).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun forEachValue_fires_whenTextChanged() = runTestWithSnapshotsThenCancelChildren {
+        val state = TextFieldState(initialSelectionInChars = TextRange(0))
+        val texts = mutableListOf<TextFieldCharSequence>()
+        val initialText = state.text
+
+        launch(Dispatchers.Unconfined) {
+            state.forEachTextValue { texts += it }
+        }
+
+        state.edit {
+            append("hello")
+            placeCursorBeforeCharAt(0)
+        }
+
+        assertThat(texts).hasSize(2)
+        assertThat(texts.last()).isSameInstanceAs(state.text)
+        assertThat(texts.last().toString()).isEqualTo("hello")
+        assertThat(texts.last().selectionInChars).isEqualTo(initialText.selectionInChars)
+    }
+
+    @Test
+    fun forEachValue_fires_whenSelectionChanged() = runTestWithSnapshotsThenCancelChildren {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0))
+        val texts = mutableListOf<TextFieldCharSequence>()
+
+        launch(Dispatchers.Unconfined) {
+            state.forEachTextValue { texts += it }
+        }
+
+        state.edit {
+            placeCursorAtEnd()
+        }
+
+        assertThat(texts).hasSize(2)
+        assertThat(texts.last()).isSameInstanceAs(state.text)
+        assertThat(texts.last().toString()).isEqualTo("hello")
+        assertThat(texts.last().selectionInChars).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun forEachValue_firesTwice_whenEditCalledTwice() = runTestWithSnapshotsThenCancelChildren {
+        val state = TextFieldState()
+        val texts = mutableListOf<TextFieldCharSequence>()
+
+        launch(Dispatchers.Unconfined) {
+            state.forEachTextValue { texts += it }
+        }
+
+        state.edit {
+            append("hello")
+            placeCursorAtEnd()
+        }
+
+        state.edit {
+            append(" world")
+            placeCursorAtEnd()
+        }
+
+        assertThat(texts).hasSize(3)
+        assertThat(texts[1].toString()).isEqualTo("hello")
+        assertThat(texts[2]).isSameInstanceAs(state.text)
+        assertThat(texts[2].toString()).isEqualTo("hello world")
+    }
+
+    @Test
+    fun forEachValue_firesOnce_whenMultipleChangesMadeInSingleEdit() =
+        runTestWithSnapshotsThenCancelChildren {
+            val state = TextFieldState()
+            val texts = mutableListOf<TextFieldCharSequence>()
+
+            launch(Dispatchers.Unconfined) {
+                state.forEachTextValue { texts += it }
+            }
+
+            state.edit {
+                append("hello")
+                append(" world")
+                placeCursorAtEnd()
+            }
+
+            assertThat(texts.last()).isSameInstanceAs(state.text)
+            assertThat(texts.last().toString()).isEqualTo("hello world")
+        }
+
+    @Test
+    fun forEachValue_fires_whenChangeMadeInSnapshotIsApplied() =
+        runTestWithSnapshotsThenCancelChildren {
+            val state = TextFieldState()
+            val texts = mutableListOf<TextFieldCharSequence>()
+
+            launch(Dispatchers.Unconfined) {
+                state.forEachTextValue { texts += it }
+            }
+
+            val snapshot = Snapshot.takeMutableSnapshot()
+            snapshot.enter {
+                state.edit {
+                    append("hello")
+                    placeCursorAtEnd()
+                }
+                assertThat(texts.isEmpty())
+            }
+            assertThat(texts.isEmpty())
+
+            snapshot.apply()
+            snapshot.dispose()
+
+            assertThat(texts.last()).isSameInstanceAs(state.text)
+        }
+
+    @Test
+    fun forEachValue_notFired_whenChangeMadeInSnapshotThenDisposed() =
+        runTestWithSnapshotsThenCancelChildren {
+            val state = TextFieldState()
+            val texts = mutableListOf<TextFieldCharSequence>()
+
+            launch(Dispatchers.Unconfined) {
+                state.forEachTextValue { texts += it }
+            }
+
+            val snapshot = Snapshot.takeMutableSnapshot()
+            snapshot.enter {
+                state.edit {
+                    append("hello")
+                    placeCursorAtEnd()
+                }
+            }
+            snapshot.dispose()
+
+            // Only contains initial value.
+            assertThat(texts).hasSize(1)
+            assertThat(texts.single().toString()).isEmpty()
+        }
+
+    @Test
+    fun forEachValue_cancelsPreviousHandler_whenChangeMadeWhileSuspended() =
+        runTestWithSnapshotsThenCancelChildren {
+            val state = TextFieldState()
+            val texts = mutableListOf<TextFieldCharSequence>()
+
+            launch(Dispatchers.Unconfined) {
+                state.forEachTextValue {
+                    texts += it
+                    awaitCancellation()
+                }
+            }
+
+            state.setTextAndPlaceCursorAtEnd("hello")
+            state.setTextAndPlaceCursorAtEnd("world")
+
+            assertThat(texts.map { it.toString() })
+                .containsExactly("", "hello", "world")
+                .inOrder()
+        }
+
+    private fun runTestWithSnapshotsThenCancelChildren(testBody: suspend TestScope.() -> Unit) {
+        val globalWriteObserverHandle = Snapshot.registerGlobalWriteObserver {
+            // This is normally done by the compose runtime.
+            Snapshot.sendApplyNotifications()
+        }
+        try {
+            runTest {
+                testBody()
+                coroutineContext.job.cancelChildren()
+            }
+        } finally {
+            globalWriteObserverHandle.dispose()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ChangeTrackerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ChangeTrackerTest.kt
new file mode 100644
index 0000000..154b2d7
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ChangeTrackerTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ChangeTrackerTest {
+
+    @Test
+    fun initialInsert() {
+        val buffer = SimpleBuffer()
+
+        buffer.append("hello")
+
+        assertThat(buffer.toString()).isEqualTo("hello")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 5))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun deleteAll() {
+        val buffer = SimpleBuffer("hello")
+
+        buffer.replace("hello", "")
+
+        assertThat(buffer.toString()).isEqualTo("")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 0))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun multipleDiscontinuousChanges() {
+        val buffer = SimpleBuffer("hello world")
+
+        buffer.replace("world", "Compose")
+        buffer.replace("hello", "goodbye")
+
+        assertThat(buffer.toString()).isEqualTo("goodbye Compose")
+        assertThat(buffer.changes.changeCount).isEqualTo(2)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 7))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
+        assertThat(buffer.changes.getRange(1)).isEqualTo(TextRange(8, 15))
+        assertThat(buffer.changes.getOriginalRange(1)).isEqualTo(TextRange(6, 11))
+    }
+
+    @Test
+    fun twoAppends() {
+        val buffer = SimpleBuffer()
+
+        buffer.append("foo")
+        buffer.append("bar")
+
+        assertThat(buffer.toString()).isEqualTo("foobar")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun threeAppends() {
+        val buffer = SimpleBuffer()
+
+        buffer.append("foo")
+        buffer.append("bar")
+        buffer.append("baz")
+
+        assertThat(buffer.toString()).isEqualTo("foobarbaz")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 9))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun replaceWithReversedIndices() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace(2, 0, "e")
+
+        assertThat(buffer.toString()).isEqualTo("ecd")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 2))
+    }
+
+    @Test
+    fun multipleAdjacentReplaces_whenPerformedInOrder_replacementsShorter() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("ab", "e") // ecd
+        buffer.replace("cd", "f")
+
+        assertThat(buffer.toString()).isEqualTo("ef")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 2))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
+    }
+
+    @Test
+    fun multipleAdjacentReplaces_whenPerformedInOrder_replacementsLonger() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("ab", "efg") // efgcd
+        buffer.replace("cd", "hij")
+
+        assertThat(buffer.toString()).isEqualTo("efghij")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
+    }
+
+    @Test
+    fun multipleAdjacentReplaces_whenPerformedInReverseOrder_replacementsShorter() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("cd", "f") // abf
+        buffer.replace("ab", "e")
+
+        assertThat(buffer.toString()).isEqualTo("ef")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 2))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
+    }
+
+    @Test
+    fun multipleAdjacentReplaces_whenPerformedInReverseOrder_replacementsLonger() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("cd", "efg") // abhij
+        buffer.replace("ab", "hij")
+
+        assertThat(buffer.toString()).isEqualTo("hijefg")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
+    }
+
+    @Test
+    fun multiplePartiallyOverlappingChanges_atStart() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("bc", "ef") // aefd
+        buffer.replace("ae", "gh")
+
+        assertThat(buffer.toString()).isEqualTo("ghfd")
+        // Overlapping changes are merged.
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 3))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 3))
+    }
+
+    @Test
+    fun multiplePartiallyOverlappingChanges_atEnd() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("bc", "ef") // aefd
+        buffer.replace("fd", "gh")
+
+        assertThat(buffer.toString()).isEqualTo("aegh")
+        // Overlapping changes are merged.
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 4))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
+    }
+
+    @Test
+    fun multipleFullyOverlappingChanges() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace("bc", "ef") // aefd
+        buffer.replace("ef", "gh")
+
+        assertThat(buffer.toString()).isEqualTo("aghd")
+        // Overlapping changes are merged.
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 3))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 3))
+    }
+
+    private class SimpleBuffer(initialText: String = "") {
+        private val builder = StringBuilder(initialText)
+        val changes = ChangeTracker()
+
+        fun append(text: String) {
+            changes.trackChange(builder.length, builder.length, text.length)
+            builder.append(text)
+        }
+
+        fun replace(substring: String, text: String) {
+            val start = builder.indexOf(substring)
+            if (start != -1) {
+                val end = start + substring.length
+                changes.trackChange(start, end, text.length)
+                builder.replace(start, end, text)
+            }
+        }
+
+        fun replace(start: Int, end: Int, text: String) {
+            changes.trackChange(start, end, text.length)
+            builder.replace(minOf(start, end), maxOf(start, end), text)
+        }
+
+        override fun toString(): String = builder.toString()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformationTest.kt
new file mode 100644
index 0000000..f479576
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformationTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class CodepointTransformationTest {
+
+    @Test
+    fun toVisualText_codepointIndices() {
+        val source =
+            TextFieldCharSequence("a${SurrogateCodepointString}c$SurrogateCodepointString")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            val expectedCodePoint = when (i) {
+                0 -> 'a'.code
+                1 -> SurrogateCodepoint
+                2 -> 'c'.code
+                3 -> SurrogateCodepoint
+                else -> fail("Invalid codepoint index: $i")
+            }
+            assertThat(codepoint).isEqualTo(expectedCodePoint)
+            codepoint
+        }
+
+        source.toVisualText(codepointTransformation, offsetMapping)
+    }
+
+    @Test
+    fun toVisualText_mapsOffsetsForward() {
+        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            when (codepoint) {
+                'a'.code, 'c'.code -> SurrogateCodepoint
+                SurrogateCodepoint -> 'b'.code
+                else -> fail(
+                    "codepointIndex=$i, codepoint=\"${
+                        String(intArrayOf(codepoint), 0, 1)
+                    }\""
+                )
+            }
+        }
+        val visual = source.toVisualText(codepointTransformation, offsetMapping)
+
+        assertThat(visual.toString())
+            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
+
+        listOf(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(2, 3),
+            3 to TextRange(3),
+            4 to TextRange(5),
+        ).forEach { (source, dest) ->
+            assertWithMessage("Mapping from untransformed offset $source")
+                .that(offsetMapping.mapFromSource(source)).isEqualTo(dest)
+        }
+    }
+
+    @Test
+    fun toVisualText_mapsOffsetsBackward() {
+        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            when (codepoint) {
+                'a'.code, 'c'.code -> SurrogateCodepoint
+                SurrogateCodepoint -> 'b'.code
+                else -> fail(
+                    "codepointIndex=$i, codepoint=\"${
+                        String(intArrayOf(codepoint), 0, 1)
+                    }\""
+                )
+            }
+        }
+        val visual = source.toVisualText(codepointTransformation, offsetMapping)
+
+        assertThat(visual.toString())
+            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
+
+        listOf(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(3),
+            4 to TextRange(3, 4),
+            5 to TextRange(4),
+        ).forEach { (dest, source) ->
+            assertWithMessage("Mapping from transformed offset $dest")
+                .that(offsetMapping.mapFromDest(dest)).isEqualTo(source)
+        }
+    }
+
+    private companion object {
+        /** This is "𐐷", a surrogate codepoint. */
+        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
+        const val SurrogateCodepointString = "\uD801\uDC37"
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CommitTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CommitTextCommandTest.kt
new file mode 100644
index 0000000..9d35316
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CommitTextCommandTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class CommitTextCommandTest {
+
+    @Test
+    fun test_insert_empty() {
+        val eb = EditingBuffer("", TextRange.Zero)
+
+        eb.commitText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("X")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_cursor_tail() {
+        val eb = EditingBuffer("A", TextRange(1))
+
+        eb.commitText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AX")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_cursor_head() {
+        val eb = EditingBuffer("A", TextRange(1))
+
+        eb.commitText("X", 0)
+
+        assertThat(eb.toString()).isEqualTo("AX")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_cursor_far_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.commitText("X", 2)
+
+        assertThat(eb.toString()).isEqualTo("AXBCDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_cursor_far_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(4))
+
+        eb.commitText("X", -2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.commitText("", 0)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.commitText("", 1)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_far_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.commitText("", 2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_far_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(4))
+
+        eb.commitText("", -2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_cancel_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 4) // Mark "BCD" as composition
+        eb.commitText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_replace_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 4)) // select "BCD"
+
+        eb.commitText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_composition_and_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 3)) // select "BC"
+
+        eb.setComposition(2, 4) // Mark "CD" as composition
+        eb.commitText("X", 1)
+
+        // If composition and selection exists at the same time, replace composition and cancel
+        // selection and place cursor.
+        assertThat(eb.toString()).isEqualTo("ABXE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_cursor_position_too_small() {
+        val eb = EditingBuffer("ABCDE", TextRange(5))
+
+        eb.commitText("X", -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDEX")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_cursor_position_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange(5))
+
+        eb.commitText("X", 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDEX")
+        assertThat(eb.cursor).isEqualTo(6)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationStateTest.kt
new file mode 100644
index 0000000..58920e6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationStateTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+class CursorAnimationStateTest {
+
+    private val animationState = CursorAnimationState()
+
+    @Test
+    fun alphaNotAnimatingInitially() = runTest {
+        assertNotAnimating()
+    }
+
+    @Test
+    fun snapToVisibleAndAnimate_animatesAlpha() = runTest {
+        val job = launch {
+            animationState.snapToVisibleAndAnimate()
+        }
+
+        // Should start immediately.
+        assertThat(animationState.cursorAlpha).isEqualTo(0f)
+        runCurrent()
+
+        // Then let's verify a few blinks…
+        assertThat(animationState.cursorAlpha).isEqualTo(1f)
+        testScheduler.advanceTimeBy(500)
+        assertThat(animationState.cursorAlpha).isEqualTo(1f)
+        testScheduler.advanceTimeBy(500)
+        assertThat(animationState.cursorAlpha).isEqualTo(0f)
+        testScheduler.advanceTimeBy(500)
+        assertThat(animationState.cursorAlpha).isEqualTo(1f)
+
+        job.cancel()
+    }
+
+    @Test
+    fun snapToVisibleAndAnimate_suspendsWhileAnimating() = runTest {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            animationState.snapToVisibleAndAnimate()
+        }
+
+        // Advance a few blinks.
+        repeat(10) {
+            testScheduler.advanceTimeBy(500)
+            assertThat(job.isActive).isTrue()
+        }
+
+        job.cancel()
+    }
+
+    @Test
+    fun snapToVisibleAndAnimate_stopsAnimating_whenCancelledImmediately() = runTest {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            animationState.snapToVisibleAndAnimate()
+        }
+        job.cancel()
+
+        assertNotAnimating()
+        assertThat(job.isActive).isFalse()
+    }
+
+    @Test
+    fun snapToVisibleAndAnimate_stopsAnimating_whenCancelledAsync() = runTest {
+        val job = launch {
+            animationState.snapToVisibleAndAnimate()
+        }
+        job.cancel()
+
+        assertNotAnimating()
+        assertThat(job.isActive).isFalse()
+    }
+
+    @Test
+    fun snapToVisibleAndAnimate_stopsAnimating_whenCancelledAfterAWhile() = runTest {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            animationState.snapToVisibleAndAnimate()
+        }
+
+        // Advance a few blinks…
+        repeat(10) {
+            testScheduler.advanceTimeBy(500)
+        }
+        job.cancel()
+
+        assertNotAnimating()
+    }
+
+    @Test
+    fun cancelAndHide_stopsAnimating_immediately() = runTest {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            animationState.snapToVisibleAndAnimate()
+        }
+        animationState.cancelAndHide()
+
+        assertNotAnimating()
+        assertThat(job.isActive).isFalse()
+    }
+
+    @Test
+    fun cancelAndHide_beforeStart_doesntBlockAnimation() = runTest {
+        animationState.cancelAndHide()
+        val job = launch {
+            animationState.snapToVisibleAndAnimate()
+        }
+
+        runCurrent()
+        assertThat(animationState.cursorAlpha).isEqualTo(1f)
+
+        job.cancel()
+    }
+
+    @Test
+    fun cancelAndHide_stopsAnimating_afterAWhile() = runTest {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            animationState.snapToVisibleAndAnimate()
+        }
+
+        // Advance a few blinks…
+        repeat(10) {
+            testScheduler.advanceTimeBy(500)
+        }
+        animationState.cancelAndHide()
+
+        assertNotAnimating()
+        assertThat(job.isActive).isFalse()
+    }
+
+    private fun TestScope.assertNotAnimating() {
+        // Allow the cancellation to process.
+        advanceUntilIdle()
+
+        // Verify a few blinks.
+        repeat(10) {
+            assertThat(animationState.cursorAlpha).isEqualTo(0f)
+            testScheduler.advanceTimeBy(490)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextCommandTest.kt
new file mode 100644
index 0000000..f2c9580
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextCommandTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class DeleteSurroundingTextCommandTest {
+
+    @Test
+    fun test_delete_after() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.deleteSurroundingText(0, 1)
+
+        assertThat(eb.toString()).isEqualTo("ACDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.deleteSurroundingText(1, 0)
+
+        assertThat(eb.toString()).isEqualTo("BCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_after_multiple() {
+        val eb = EditingBuffer("ABCDE", TextRange(2))
+
+        eb.deleteSurroundingText(0, 2)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before_multiple() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(2, 0)
+
+        assertThat(eb.toString()).isEqualTo("ADE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both_multiple() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(2, 2)
+
+        assertThat(eb.toString()).isEqualTo("A")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_selection_preserve() {
+        val eb = EditingBuffer("ABCDE", TextRange(2, 4))
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ACD")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before_too_many() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(1000, 0)
+
+        assertThat(eb.toString()).isEqualTo("DE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_after_too_many() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(0, 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABC")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both_too_many() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.deleteSurroundingText(1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_composition_no_intersection_preceding_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(0, 1)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(1)
+    }
+
+    @Test
+    fun test_delete_composition_no_intersection_trailing_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(4, 5)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun test_delete_composition_intersection_preceding_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(0, 3)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_delete_composition_intersection_trailing_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(3, 5)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun test_delete_covered_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(2, 3)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_composition_covered() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.setComposition(0, 5)
+
+        eb.deleteSurroundingText(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun throws_whenLengthBeforeInvalid() {
+        val eb = EditingBuffer("", TextRange(0))
+        val error = assertFailsWith<IllegalArgumentException> {
+            eb.deleteSurroundingText(lengthBeforeCursor = -42, lengthAfterCursor = 0)
+        }
+        assertThat(error).hasMessageThat().contains("-42")
+    }
+
+    @Test
+    fun throws_whenLengthAfterInvalid() {
+        val eb = EditingBuffer("", TextRange(0))
+        val error = assertFailsWith<IllegalArgumentException> {
+            eb.deleteSurroundingText(lengthBeforeCursor = 0, lengthAfterCursor = -42)
+        }
+        assertThat(error).hasMessageThat().contains("-42")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt
new file mode 100644
index 0000000..a38142c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class DeleteSurroundingTextInCodePointsCommandTest {
+    val CH1 = "\uD83D\uDE00" // U+1F600
+    val CH2 = "\uD83D\uDE01" // U+1F601
+    val CH3 = "\uD83D\uDE02" // U+1F602
+    val CH4 = "\uD83D\uDE03" // U+1F603
+    val CH5 = "\uD83D\uDE04" // U+1F604
+
+    @Test
+    fun test_delete_after() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
+
+        eb.deleteSurroundingTextInCodePoints(0, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH3$CH4$CH5")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
+
+        eb.deleteSurroundingTextInCodePoints(1, 0)
+
+        assertThat(eb.toString()).isEqualTo("$CH2$CH3$CH4$CH5")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_after_multiple() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4))
+
+        eb.deleteSurroundingTextInCodePoints(0, 2)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before_multiple() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(2, 0)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH4$CH5")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both_multiple() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(2, 2)
+
+        assertThat(eb.toString()).isEqualTo(CH1)
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_selection_preserve() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4, 8))
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH3$CH4")
+        assertThat(eb.selectionStart).isEqualTo(2)
+        assertThat(eb.selectionEnd).isEqualTo(6)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_before_too_many() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(1000, 0)
+
+        assertThat(eb.toString()).isEqualTo("$CH4$CH5")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_after_too_many() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(0, 1000)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3")
+        assertThat(eb.cursor).isEqualTo(6)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_both_too_many() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.deleteSurroundingTextInCodePoints(1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_composition_no_intersection_preceding_composition() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(0, 2)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_delete_composition_no_intersection_trailing_composition() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(8, 10)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.compositionStart).isEqualTo(4)
+        assertThat(eb.compositionEnd).isEqualTo(6)
+    }
+
+    @Test
+    fun test_delete_composition_intersection_preceding_composition() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(0, 6)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun test_delete_composition_intersection_trailing_composition() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(6, 10)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.compositionStart).isEqualTo(4)
+        assertThat(eb.compositionEnd).isEqualTo(6)
+    }
+
+    @Test
+    fun test_delete_covered_composition() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(4, 6)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_delete_composition_covered() {
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
+
+        eb.setComposition(0, 10)
+
+        eb.deleteSurroundingTextInCodePoints(1, 1)
+
+        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
+        assertThat(eb.cursor).isEqualTo(4)
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(6)
+    }
+
+    @Test
+    fun throws_whenLengthBeforeInvalid() {
+        val eb = EditingBuffer("", TextRange(0))
+        val error = assertFailsWith<IllegalArgumentException> {
+            eb.deleteSurroundingTextInCodePoints(lengthBeforeCursor = 0, lengthAfterCursor = -42)
+        }
+        assertThat(error).hasMessageThat().contains("-42")
+    }
+
+    @Test
+    fun throws_whenLengthAfterInvalid() {
+        val eb = EditingBuffer("", TextRange(0))
+        val error = assertFailsWith<IllegalArgumentException> {
+            eb.deleteSurroundingTextInCodePoints(lengthBeforeCursor = -42, lengthAfterCursor = 0)
+        }
+        assertThat(error).hasMessageThat().contains("-42")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferChangeTrackingTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferChangeTrackingTest.kt
new file mode 100644
index 0000000..6bf671d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferChangeTrackingTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EditingBufferChangeTrackingTest {
+
+    @Test
+    fun normalReplaceOperation_reportedAsReplace() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(2, 4, "bfghi")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2, 7))
+    }
+
+    @Test
+    fun tailInsertionReportedAsReplace_coercesToInsertion() {
+        val eb = EditingBuffer("abcd", TextRange.Zero)
+
+        eb.replace(2, 4, "cde")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(4))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(4, 5))
+    }
+
+    @Test
+    fun headInsertionReportedAsReplace_coercesToInsertion() {
+        val eb = EditingBuffer("abcd", TextRange.Zero)
+
+        eb.replace(0, 4, "eabcd")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(0))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(0, 1))
+    }
+
+    @Test
+    fun tailInsertionInTheMiddle_reportedAsReplace_coercesToInsertion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(1, 3, "bcef")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(3))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(3, 5))
+    }
+
+    @Test
+    fun headInsertionInTheMiddle_reportedAsReplace_coercesToInsertion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(2, 4, "fgcd")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2, 4))
+    }
+
+    @Test
+    fun tailDeletionReportedAsReplace_coercesToDeletion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(0, 5, "abcd")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun headDeletionReportedAsReplace_coercesToDeletion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(0, 5, "bcde")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(0))
+    }
+
+    @Test
+    fun tailDeletionInTheMiddle_reportedAsReplace_coercesToDeletion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(1, 4, "b")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun headDeletionInTheMiddle_reportedAsReplace_coercesToDeletion() {
+        val eb = EditingBuffer("abcde", TextRange.Zero)
+
+        eb.replace(1, 4, "d")
+
+        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
+        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(1, 3))
+        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(1))
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferDeleteRangeTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferDeleteRangeTest.kt
new file mode 100644
index 0000000..4529e94
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferDeleteRangeTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EditingBufferDeleteRangeTest {
+
+    @Test
+    fun test_does_not_intersect_deleted_is_after_the_target() {
+        val target = TextRange(0, 1)
+        val deleted = TextRange(2, 3)
+        assertThat(updateRangeAfterDelete(target, deleted))
+            .isEqualTo(TextRange(target.start, target.end))
+    }
+
+    @Test
+    fun test_does_not_intersect_deleted_is_before_the_target() {
+        val target = TextRange(4, 5)
+        val deleted = TextRange(0, 2)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(2, 3))
+    }
+
+    @Test
+    fun test_deleted_covers_target() {
+        val target = TextRange(1, 2)
+        val deleted = TextRange(0, 3)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 0))
+    }
+
+    @Test
+    fun test_target_covers_deleted() {
+        val target = TextRange(0, 3)
+        val deleted = TextRange(1, 2)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 2))
+    }
+
+    @Test
+    fun test_deleted_same_as_target() {
+        val target = TextRange(1, 2)
+        val deleted = TextRange(1, 2)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(1, 1))
+    }
+
+    @Test
+    fun test_deleted_covers_first_half_of_target() {
+        val target = TextRange(1, 4)
+        val deleted = TextRange(0, 2)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 2))
+    }
+
+    @Test
+    fun test_deleted_covers_second_half_of_target() {
+        val target = TextRange(1, 4)
+        val deleted = TextRange(3, 5)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(1, 3))
+    }
+
+    @Test
+    fun test_delete_trailing_cursor() {
+        val target = TextRange(3, 3)
+        val deleted = TextRange(1, 2)
+        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(2, 2))
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferTest.kt
new file mode 100644
index 0000000..48cc4cd
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/EditingBufferTest.kt
@@ -0,0 +1,483 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.text.input.internal.matchers.assertThat
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EditingBufferTest {
+
+    @Test
+    fun insert() {
+        val eb = EditingBuffer("", TextRange.Zero)
+
+        eb.replace(0, 0, "A")
+
+        assertThat(eb).hasChars("A")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        // Keep inserting text to the end of string. Cursor should follow.
+        eb.replace(1, 1, "BC")
+        assertThat(eb).hasChars("ABC")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        // Insert into middle position. Cursor should be end of inserted text.
+        eb.replace(1, 1, "D")
+        assertThat(eb).hasChars("ADBC")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.selectionStart).isEqualTo(2)
+        assertThat(eb.selectionEnd).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+    }
+
+    @Test
+    fun delete() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.replace(0, 1, "")
+
+        // Delete the left character at the cursor.
+        assertThat(eb).hasChars("BCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        // Delete the text before the cursor
+        eb.replace(0, 2, "")
+        assertThat(eb).hasChars("DE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        // Delete end of the text.
+        eb.replace(1, 2, "")
+        assertThat(eb).hasChars("D")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+    }
+
+    @Test
+    fun setSelection() {
+        val eb = EditingBuffer("ABCDE", TextRange(0, 3))
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(-1)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.setSelection(0, 5) // Change the selection
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(-1)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.replace(0, 3, "X") // replace function cancel the selection and place cursor.
+        assertThat(eb).hasChars("XDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.setSelection(0, 2) // Set the selection again
+        assertThat(eb).hasChars("XDE")
+        assertThat(eb.cursor).isEqualTo(-1)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+    }
+
+    @Test fun setSelection_coerces_whenNegativeStart() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(-1, 1)
+
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+    }
+
+    @Test fun setSelection_coerces_whenNegativeEnd() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(1, -1)
+
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun setSelection_allowReversedSelection() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+        eb.setSelection(4, 2)
+
+        assertThat(eb.selection).isEqualTo(TextRange(4, 2))
+    }
+
+    @Test
+    fun replace_reversedRegion() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+        eb.replace(3, 1, "FGHI")
+
+        assertThat(eb).hasChars("AFGHIDE")
+        assertThat(eb.cursor).isEqualTo(5)
+        assertThat(eb.selectionStart).isEqualTo(5)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+    }
+
+    @Test
+    fun setComposition_and_cancelComposition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(0, 5) // Make all text as composition
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(5)
+
+        eb.replace(2, 3, "X") // replace function cancel the composition text.
+        assertThat(eb).hasChars("ABXDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.setComposition(2, 4) // set composition again
+        assertThat(eb).hasChars("ABXDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun setComposition_and_commitComposition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(0, 5) // Make all text as composition
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(5)
+
+        eb.replace(2, 3, "X") // replace function cancel the composition text.
+        assertThat(eb).hasChars("ABXDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.setComposition(2, 4) // set composition again
+        assertThat(eb).hasChars("ABXDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+
+        eb.commitComposition() // commit the composition
+        assertThat(eb).hasChars("ABXDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+    }
+
+    @Test
+    fun setCursor_and_get_cursor() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.cursor = 1
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.cursor = 2
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.selectionStart).isEqualTo(2)
+        assertThat(eb.selectionEnd).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+
+        eb.cursor = 5
+        assertThat(eb).hasChars("ABCDE")
+        assertThat(eb.cursor).isEqualTo(5)
+        assertThat(eb.selectionStart).isEqualTo(5)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+        assertThat(eb.hasComposition()).isFalse()
+        assertThat(eb.compositionStart).isEqualTo(-1)
+        assertThat(eb.compositionEnd).isEqualTo(-1)
+    }
+
+    @Test
+    fun delete_preceding_cursor_no_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.delete(1, 2)
+        assertThat(eb).hasChars("ACDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun delete_trailing_cursor_no_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(3))
+
+        eb.delete(1, 2)
+        assertThat(eb).hasChars("ACDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun delete_preceding_selection_no_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(0, 1))
+
+        eb.delete(1, 2)
+        assertThat(eb).hasChars("ACDE")
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun delete_trailing_selection_no_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange(4, 5))
+
+        eb.delete(1, 2)
+        assertThat(eb).hasChars("ACDE")
+        assertThat(eb.selectionStart).isEqualTo(3)
+        assertThat(eb.selectionEnd).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun delete_covered_cursor() {
+        // AB[]CDE
+        val eb = EditingBuffer("ABCDE", TextRange(2, 2))
+
+        eb.delete(1, 3)
+        // A[]DE
+        assertThat(eb).hasChars("ADE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+    }
+
+    @Test
+    fun delete_covered_selection() {
+        // A[BC]DE
+        val eb = EditingBuffer("ABCDE", TextRange(1, 3))
+
+        eb.delete(0, 4)
+        // []E
+        assertThat(eb).hasChars("E")
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun delete_covered_reversedSelection() {
+        // A[BC]DE
+        val eb = EditingBuffer("ABCDE", TextRange(3, 1))
+
+        eb.delete(0, 4)
+        // []E
+        assertThat(eb).hasChars("E")
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun delete_intersects_first_half_of_selection() {
+        // AB[CD]E
+        val eb = EditingBuffer("ABCDE", TextRange(2, 4))
+
+        eb.delete(1, 3)
+        // A[D]E
+        assertThat(eb).hasChars("ADE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun delete_intersects_first_half_of_reversedSelection() {
+        // AB[CD]E
+        val eb = EditingBuffer("ABCDE", TextRange(4, 2))
+
+        eb.delete(3, 1)
+        // A[D]E
+        assertThat(eb).hasChars("ADE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun delete_intersects_second_half_of_selection() {
+        // A[BCD]EFG
+        val eb = EditingBuffer("ABCDEFG", TextRange(1, 4))
+
+        eb.delete(3, 5)
+        // A[BC]FG
+        assertThat(eb).hasChars("ABCFG")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun delete_intersects_second_half_of_reversedSelection() {
+        // A[BCD]EFG
+        val eb = EditingBuffer("ABCDEFG", TextRange(4, 1))
+
+        eb.delete(5, 3)
+        // A[BC]FG
+        assertThat(eb).hasChars("ABCFG")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun delete_preceding_composition_no_intersection() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 2)
+        eb.delete(2, 3)
+
+        assertThat(eb).hasChars("ABDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun delete_trailing_composition_no_intersection() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(3, 4)
+        eb.delete(2, 3)
+
+        assertThat(eb).hasChars("ABDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun delete_preceding_composition_intersection() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 3)
+        eb.delete(2, 4)
+
+        assertThat(eb).hasChars("ABE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun delete_trailing_composition_intersection() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(3, 5)
+        eb.delete(2, 4)
+
+        assertThat(eb).hasChars("ABE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun delete_composition_contains_delrange() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(2, 5)
+        eb.delete(3, 4)
+
+        assertThat(eb).hasChars("ABCE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun delete_delrange_contains_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(3, 4)
+        eb.delete(2, 5)
+
+        assertThat(eb).hasChars("AB")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/FinishComposingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/FinishComposingTextCommandTest.kt
new file mode 100644
index 0000000..6c5b836
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/FinishComposingTextCommandTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class FinishComposingTextCommandTest {
+
+    @Test
+    fun test_set() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 4)
+        eb.finishComposingText()
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_preserve_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
+
+        eb.setComposition(2, 5)
+        eb.finishComposingText()
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/GapBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/GapBufferTest.kt
new file mode 100644
index 0000000..a0a73a8
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/GapBufferTest.kt
@@ -0,0 +1,874 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.text.input.internal.matchers.assertThat
+import com.google.common.truth.Truth.assertThat
+import kotlin.random.Random
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class GapBufferTest {
+
+    @Test
+    fun insertTest_insert_to_empty_string() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "A")
+            }
+        ).hasChars("A")
+    }
+
+    @Test
+    fun insertTest_insert_and_append() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "A")
+                replace(0, 0, "B")
+            }
+        ).hasChars("BA")
+    }
+
+    @Test
+    fun insertTest_insert_and_prepend() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "A")
+                replace(1, 1, "B")
+            }
+        ).hasChars("AB")
+    }
+
+    @Test
+    fun insertTest_insert_and_insert_into_middle() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "AA")
+                replace(1, 1, "B")
+            }
+        ).hasChars("ABA")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_prepend() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(0, 0, "A")
+            }
+        ).hasChars("AXX")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_insert_into_middle() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(1, 1, "A")
+            }
+        ).hasChars("XAX")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_append() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(2, 2, "A")
+            }
+        ).hasChars("XXA")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_prepend_and_prepend() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(0, 0, "A")
+                replace(0, 0, "B")
+            }
+        ).hasChars("BAXX")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_prepend_and_append() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(0, 0, "A")
+                replace(1, 1, "B")
+            }
+        ).hasChars("ABXX")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_prepend_and_insert_middle() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(0, 0, "A")
+                replace(2, 2, "B")
+            }
+        ).hasChars("AXBX")
+    }
+
+    @Test
+    fun insertTest_intoExistingText_insert_two_chars_and_append() {
+        assertThat(
+            PartialGapBuffer("XX").apply {
+                replace(0, 0, "AA")
+                replace(1, 1, "B")
+            }
+        ).hasChars("ABAXX")
+    }
+
+    @Test
+    fun insert_withSubRange_empty_fromHead() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 0)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_empty_fromTail() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 4, 4)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_empty_fromMiddle() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 1)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_singleChar_fromHead() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 1)
+
+        assertThat(buffer).hasChars("AXYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_singleChar_fromTail() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 3, 4)
+
+        assertThat(buffer).hasChars("DXYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_singleChar_fromMiddle() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 2)
+
+        assertThat(buffer).hasChars("BXYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_multipleChars_fromHead() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 2)
+
+        assertThat(buffer).hasChars("ABXYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_multipleChars_fromTail() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 2, 4)
+
+        assertThat(buffer).hasChars("CDXYZ")
+    }
+
+    @Test
+    fun insert_withSubRange_multipleChars_fromMiddle() {
+        val buffer = PartialGapBuffer("")
+        buffer.replace(0, 0, "XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 3)
+
+        assertThat(buffer).hasChars("BCXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_empty_fromHead() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 0)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_empty_fromTail() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 4, 4)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_empty_fromMiddle() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 1)
+
+        assertThat(buffer).hasChars("XYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_singleChar_fromHead() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 1)
+
+        assertThat(buffer).hasChars("AXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_singleChar_fromTail() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 3, 4)
+
+        assertThat(buffer).hasChars("DXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_singleChar_fromMiddle() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 2)
+
+        assertThat(buffer).hasChars("BXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_multipleChars_fromHead() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 0, 2)
+
+        assertThat(buffer).hasChars("ABXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_multipleChars_fromTail() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 2, 4)
+
+        assertThat(buffer).hasChars("CDXYZ")
+    }
+
+    @Test
+    fun insert_intoExistingText_withSubRange_multipleChars_fromMiddle() {
+        val buffer = PartialGapBuffer("XYZ")
+
+        buffer.replace(0, 0, "ABCD", 1, 3)
+
+        assertThat(buffer).hasChars("BCXYZ")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_from_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "")
+            }
+        ).hasChars("BC")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_middle() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 2, "")
+            }
+        ).hasChars("AC")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(2, 3, "")
+            }
+        ).hasChars("AB")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_two_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 2, "")
+            }
+        ).hasChars("C")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_two_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 3, "")
+            }
+        ).hasChars("A")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_with_two_instruction_from_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "")
+                replace(0, 1, "")
+            }
+        ).hasChars("C")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_with_two_instruction_from_head_and_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "")
+                replace(1, 2, "")
+            }
+        ).hasChars("B")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_with_two_instruction_from_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 2, "")
+                replace(1, 2, "")
+            }
+        ).hasChars("A")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_three_chars() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 3, "")
+            }
+        ).hasChars("")
+    }
+
+    @Test
+    fun deleteTest_insert_and_delete_three_chars_with_three_instructions() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "")
+                replace(0, 1, "")
+                replace(0, 1, "")
+            }
+        ).hasChars("")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "")
+            }
+        ).hasChars("BC")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_from_middle() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 2, "")
+            }
+        ).hasChars("AC")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(2, 3, "")
+            }
+        ).hasChars("AB")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_two_chars_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 2, "")
+            }
+        ).hasChars("C")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_two_chars_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 3, "")
+            }
+        ).hasChars("A")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "")
+                replace(0, 1, "")
+            }
+        ).hasChars("C")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head_and_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "")
+                replace(1, 2, "")
+            }
+        ).hasChars("B")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 2, "")
+                replace(1, 2, "")
+            }
+        ).hasChars("A")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_three_chars() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 3, "")
+            }
+        ).hasChars("")
+    }
+
+    @Test
+    fun deleteTest_fromExistingText_delete_three_chars_with_three_instructions() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "")
+                replace(0, 1, "")
+                replace(0, 1, "")
+            }
+        ).hasChars("")
+    }
+
+    @Test
+    fun replaceTest_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "X")
+            }
+        ).hasChars("XBC")
+    }
+
+    @Test
+    fun replaceTest_middle() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 2, "X")
+            }
+        ).hasChars("AXC")
+    }
+
+    @Test
+    fun replaceTest_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(2, 3, "X")
+            }
+        ).hasChars("ABX")
+    }
+
+    @Test
+    fun replaceTest_head_two_chars() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 2, "X")
+            }
+        ).hasChars("XC")
+    }
+
+    @Test
+    fun replaceTest_middle_two_chars() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 3, "X")
+            }
+        ).hasChars("AX")
+    }
+
+    @Test
+    fun replaceTest_three_chars() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 3, "X")
+            }
+        ).hasChars("X")
+    }
+
+    @Test
+    fun replaceTest_one_char_with_two_chars_from_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 1, "XY")
+            }
+        ).hasChars("XYBC")
+    }
+
+    @Test
+    fun replaceTest_one_char_with_two_chars_from_middle() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 2, "XY")
+            }
+        ).hasChars("AXYC")
+    }
+
+    @Test
+    fun replaceTest_one_char_with_two_chars_from_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(2, 3, "XY")
+            }
+        ).hasChars("ABXY")
+    }
+
+    @Test
+    fun replaceTest_two_chars_with_two_chars_from_head() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 2, "XY")
+            }
+        ).hasChars("XYC")
+    }
+
+    @Test
+    fun replaceTest_two_chars_with_two_chars_from_tail() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(1, 3, "XY")
+            }
+        ).hasChars("AXY")
+    }
+
+    @Test
+    fun replaceTest_three_chars_with_two_char() {
+        assertThat(
+            PartialGapBuffer("").apply {
+                replace(0, 0, "ABC")
+                replace(0, 3, "XY")
+            }
+        ).hasChars("XY")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "X")
+            }
+        ).hasChars("XBC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_middle() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 2, "X")
+            }
+        ).hasChars("AXC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(2, 3, "X")
+            }
+        ).hasChars("ABX")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_two_chars_with_one_char_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 2, "X")
+            }
+        ).hasChars("XC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_two_chars_with_one_char_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 3, "X")
+            }
+        ).hasChars("AX")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_three_chars() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 3, "X")
+            }
+        ).hasChars("X")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_one_char_with_two_chars_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 1, "XY")
+            }
+        ).hasChars("XYBC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_one_char_with_two_chars_from_middle() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 2, "XY")
+            }
+        ).hasChars("AXYC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_one_char_with_two_chars_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(2, 3, "XY")
+            }
+        ).hasChars("ABXY")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_two_chars_with_two_chars_from_head() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 2, "XY")
+            }
+        ).hasChars("XYC")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_two_chars_with_two_chars_from_tail() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(1, 3, "XY")
+            }
+        ).hasChars("AXY")
+    }
+
+    @Test
+    fun replaceTest_fromExistingText_three_chars_with_three_chars() {
+        assertThat(
+            PartialGapBuffer("ABC").apply {
+                replace(0, 3, "XY")
+            }
+        ).hasChars("XY")
+    }
+
+    @Test
+    fun replace_throws_whenStartGreaterThanEnd() {
+        val buffer = PartialGapBuffer("ABCD")
+
+        val error = assertFailsWith<IllegalArgumentException> {
+            buffer.replace(3, 2, "")
+        }
+        assertThat(error).hasMessageThat().contains("start=3 > end=2")
+    }
+
+    @Test
+    fun replace_throws_whenTextStartGreaterThanTextEnd() {
+        val buffer = PartialGapBuffer("ABCD")
+
+        val error = assertFailsWith<IllegalArgumentException> {
+            buffer.replace(2, 3, "", 1, 0)
+        }
+        assertThat(error).hasMessageThat().contains("textStart=1 > textEnd=0")
+    }
+
+    @Test
+    fun replace_throws_whenStartNegative() {
+        val buffer = PartialGapBuffer("ABCD")
+
+        val error = assertFailsWith<IllegalArgumentException> {
+            buffer.replace(-1, 2, "XY")
+        }
+        assertThat(error).hasMessageThat().contains("-1")
+    }
+
+    @Test
+    fun replace_throws_whenTextStartNegative() {
+        val buffer = PartialGapBuffer("ABCD")
+
+        val error = assertFailsWith<IllegalArgumentException> {
+            buffer.replace(1, 2, "XY", -1, 2)
+        }
+        assertThat(error).hasMessageThat().contains("-1")
+    }
+
+    /** Compare with the result of StringBuffer. We trust the StringBuffer works correctly. */
+    private fun assertReplace(
+        start: Int,
+        end: Int,
+        str: String,
+        sb: StringBuffer,
+        gb: PartialGapBuffer
+    ) {
+        sb.replace(start, end, str)
+        gb.replace(start, end, str)
+        assertThat(gb).hasChars(sb.toString())
+    }
+
+    private val LONG_INIT_TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(256)
+    private val SHORT_TEXT = "A"
+    private val MEDIUM_TEXT = "Hello, World"
+    private val LONG_TEXT = "abcdefghijklmnopqrstuvwxyz".repeat(16)
+
+    @Test
+    fun longTextTest_keep_insertion() {
+        val sb = StringBuffer(LONG_INIT_TEXT)
+        val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+        var c = 256 // cursor
+        assertReplace(c, c, SHORT_TEXT, sb, gb)
+        c += SHORT_TEXT.length
+        assertReplace(c, c, MEDIUM_TEXT, sb, gb)
+        c += MEDIUM_TEXT.length
+        assertReplace(c, c, LONG_TEXT, sb, gb)
+        c += LONG_TEXT.length
+        assertReplace(c, c, MEDIUM_TEXT, sb, gb)
+        c += MEDIUM_TEXT.length
+        assertReplace(c, c, SHORT_TEXT, sb, gb)
+    }
+
+    @Test
+    fun longTextTest_keep_deletion() {
+        val sb = StringBuffer(LONG_INIT_TEXT)
+        val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+        var c = 2048 // cursor
+        // Forward deletion
+        assertReplace(c, c + 10, "", sb, gb)
+        assertReplace(c, c + 100, "", sb, gb)
+        assertReplace(c, c + 1000, "", sb, gb)
+
+        // Backspacing
+        assertReplace(c - 10, c, "", sb, gb)
+        c -= 10
+        assertReplace(c - 100, c, "", sb, gb)
+        c -= 100
+        assertReplace(c - 1000, c, "", sb, gb)
+    }
+
+    @Test
+    fun longTextTest_farInput() {
+        val sb = StringBuffer(LONG_INIT_TEXT)
+        val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+        assertReplace(1024, 1024, "Hello, World", sb, gb)
+        assertReplace(128, 128, LONG_TEXT, sb, gb)
+    }
+
+    @Test
+    fun randomInsertDeleteStressTest() {
+        val sb = StringBuffer(LONG_INIT_TEXT)
+        val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+        val r = Random(10 /* fix the seed for reproduction */)
+
+        val insertTexts = arrayOf(SHORT_TEXT, MEDIUM_TEXT, LONG_TEXT)
+        val delLengths = arrayOf(1, 10, 100)
+
+        var c = LONG_INIT_TEXT.length / 2
+
+        for (i in 0..100) {
+            when (r.nextInt() % 4) {
+                0 -> { // insert
+                    val txt = insertTexts.random(r)
+                    assertReplace(c, c, txt, sb, gb)
+                    c += txt.length
+                }
+                1 -> { // forward delete
+                    assertReplace(c, c + delLengths.random(r), "", sb, gb)
+                }
+                2 -> { // backspacing
+                    val len = delLengths.random(r)
+                    assertReplace(c - len, c, "", sb, gb)
+                    c -= len
+                }
+                3 -> { // replacing
+                    val txt = insertTexts.random(r)
+                    val len = delLengths.random(r)
+
+                    assertReplace(c, c + len, txt, sb, gb)
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/MathUtilsTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/MathUtilsTest.kt
new file mode 100644
index 0000000..c1dda03
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/MathUtilsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MathUtilsTest {
+
+    @Test
+    fun findClosestRect_findsClosestRect_withZeroSize() {
+        val rect1 = Rect(Offset.Zero, Size.Zero)
+        val rect2 = Rect(Offset(4f, 0f), Size.Zero)
+
+        assertThat(Offset(1.9f, 0f).findClosestRect(rect1, rect2)).isEqualTo(-1)
+        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
+        assertThat(Offset(2.1f, 0f).findClosestRect(rect1, rect2)).isEqualTo(1)
+    }
+
+    @Test
+    fun findClosestRect_findsClosestRect_withZeroWidth() {
+        val rect1 = Rect(Offset.Zero, Size(0f, 10f))
+        val rect2 = Rect(Offset(4f, 0f), Size(0f, 10f))
+
+        assertThat(Offset(1.9f, 0f).findClosestRect(rect1, rect2)).isEqualTo(-1)
+        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
+        assertThat(Offset(2.1f, 0f).findClosestRect(rect1, rect2)).isEqualTo(1)
+    }
+
+    @Test
+    fun findClosestRect_findsClosestRect_withZeroWidth_differentY() {
+        val rect1 = Rect(Offset(0f, -10f), Size(0f, 9f))
+        val rect2 = Rect(Offset(4f, 1f), Size(0f, 9f))
+
+        assertThat(Offset(2f, -1f).findClosestRect(rect1, rect2)).isEqualTo(-1)
+        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
+        assertThat(Offset(2f, 1f).findClosestRect(rect1, rect2)).isEqualTo(1)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculatorTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculatorTest.kt
new file mode 100644
index 0000000..e7e70e6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculatorTest.kt
@@ -0,0 +1,902 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class OffsetMappingCalculatorTest {
+
+    @Test
+    fun noChanges() {
+        val builder = TestEditBuffer()
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun insertCharIntoEmpty() {
+        val builder = TestEditBuffer()
+        builder.append("a")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 1),
+            1 to TextRange(2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun insertCharIntoMiddle() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "c")
+        assertThat(builder.toString()).isEqualTo("acb")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun deleteCharFromMiddle() {
+        val builder = TestEditBuffer("abc")
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("ac")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun replaceCharInMiddle() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(1, "d")
+        assertThat(builder.toString()).isEqualTo("adc")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun insertStringIntoEmpty() {
+        val builder = TestEditBuffer("")
+        builder.append("ab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun insertStringIntoMiddle() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "cd")
+        assertThat(builder.toString()).isEqualTo("acdb")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+            5 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun deleteStringFromMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.delete(1, 3)
+        assertThat(builder.toString()).isEqualTo("ad")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceStringWithEqualLengthInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "ef")
+        assertThat(builder.toString()).isEqualTo("aefd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceStringWithLongerInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "efg")
+        assertThat(builder.toString()).isEqualTo("aefgd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 4),
+            3 to TextRange(4),
+            4 to TextRange(5),
+            5 to TextRange(6),
+            6 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(1, 3),
+            4 to TextRange(3),
+            5 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun replaceStringWithShorterInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "e")
+        assertThat(builder.toString()).isEqualTo("aed")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 2),
+            3 to TextRange(2),
+            4 to TextRange(3),
+            5 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(3),
+            3 to TextRange(4),
+            4 to TextRange(5),
+            5 to TextRange(6),
+        )
+    }
+
+    @Test
+    fun replaceAllWithEqualLength() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("efgh")
+        assertThat(builder.toString()).isEqualTo("efgh")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceAllWithLonger() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("efghi")
+        assertThat(builder.toString()).isEqualTo("efghi")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 5),
+            2 to TextRange(0, 5),
+            3 to TextRange(0, 5),
+            4 to TextRange(5),
+            5 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(0, 4),
+            5 to TextRange(4),
+            6 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceAllWithShorter() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("ef")
+        assertThat(builder.toString()).isEqualTo("ef")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 2),
+            3 to TextRange(0, 2),
+            4 to TextRange(2),
+            5 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+            5 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun prependCharToString() {
+        val builder = TestEditBuffer("a")
+        builder.insert(0, "b")
+        assertThat(builder.toString()).isEqualTo("ba")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 1),
+            1 to TextRange(2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun prependStringToString() {
+        val builder = TestEditBuffer("a")
+        builder.insert(0, "bc")
+        assertThat(builder.toString()).isEqualTo("bca")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun appendCharToString() {
+        val builder = TestEditBuffer("a")
+        builder.append("b")
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun appendStringToString() {
+        val builder = TestEditBuffer("a")
+        builder.append("bc")
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun multiplePrepends() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(0, "c")
+        builder.insert(0, "d")
+        builder.insert(0, "ef")
+        assertThat(builder.toString()).isEqualTo("efdcab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 4),
+            1 to TextRange(5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(0),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multipleAppends() {
+        val builder = TestEditBuffer("ab")
+        builder.append("c")
+        builder.append("d")
+        builder.append("ef")
+        assertThat(builder.toString()).isEqualTo("abcdef")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2, 6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(2),
+            4 to TextRange(2),
+            5 to TextRange(2),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multiplePrependsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(0, "cde") // cdeab
+        builder.delete(2) // cdab
+        builder.delete(0) // dab
+        builder.insert(0, "f") // fdab
+        builder.delete(0, 2)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleAppendsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.append("cde") // abcde
+        builder.delete(2) // abde
+        builder.delete(3) // abd
+        builder.append("f") // abdf
+        builder.delete(2, 4)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleInsertsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "c")
+        builder.insert(2, "de")
+        builder.insert(1, "f")
+        builder.delete(1, 3)
+        builder.delete(1)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtStartInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(0)
+        builder.delete(0)
+        builder.delete(0)
+        assertThat(builder.toString()).isEqualTo("def")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(1),
+            5 to TextRange(2),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0, 3),
+            1 to TextRange(4),
+            2 to TextRange(5),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtStartOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(1)
+        builder.delete(1)
+        builder.delete(0)
+        assertThat(builder.toString()).isEqualTo("def")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(1),
+            5 to TextRange(2),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0, 3),
+            1 to TextRange(4),
+            2 to TextRange(5),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtEndInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(builder.length - 1)
+        builder.delete(builder.length - 1)
+        builder.delete(builder.length - 1)
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3),
+            4 to TextRange(3),
+            5 to TextRange(3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3, 6),
+            4 to TextRange(7),
+            5 to TextRange(8),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtEndOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(4)
+        builder.delete(3)
+        builder.delete(3)
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3),
+            4 to TextRange(3),
+            5 to TextRange(3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3, 6),
+            4 to TextRange(7),
+            5 to TextRange(8),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesInMiddleInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(1)
+        builder.delete(1, 3)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("af")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+            5 to TextRange(9),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesInMiddleOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(2)
+        builder.delete(2, 4)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("af")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+            5 to TextRange(9),
+        )
+    }
+
+    @Test
+    fun discontinuousInsertsAndDeletes() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "cde") // acdeb
+        builder.delete(2) // aceb
+        builder.append("fgh") // acebfgh
+        builder.delete(4) // acebgh
+        builder.insert(0, "ijk") // ijkacebgh
+        builder.delete(2) // ijacebgh
+        assertThat(builder.toString()).isEqualTo("ijacebgh")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3, 5),
+            2 to TextRange(6, 8),
+            3 to TextRange(9),
+            4 to TextRange(10),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(2),
+            8 to TextRange(2),
+            9 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multipleContinuousOneToOneReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, "f")
+        builder.replace(1, "f")
+        builder.replace(2, "f")
+        assertThat(builder.toString()).isEqualTo("fff")
+        builder.assertIdentityMapping()
+    }
+
+    /** This simulates an expanding codepoint transform. */
+    @Test
+    fun multipleContinuousOneToManyReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, "dd") // ddbc
+        builder.replace(2, "ee") // ddeec
+        builder.replace(4, "ff")
+        assertThat(builder.toString()).isEqualTo("ddeeff")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun multipleContinuousOneToManyReplacementsReversed() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(2, "dd") // abdd
+        builder.replace(1, "ee") // aeedd
+        builder.replace(0, "ff")
+        assertThat(builder.toString()).isEqualTo("ffeedd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+    }
+
+    /** This simulates a contracting codepoint transform. */
+    @Test
+    fun multipleContinuousManyToOneReplacements() {
+        val builder = TestEditBuffer("abcdef")
+        builder.replace(0, 2, "g") // gcdef
+        builder.replace(1, 3, "h") // ghef
+        builder.replace(2, 4, "i")
+        assertThat(builder.toString()).isEqualTo("ghi")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousManyToOneReplacementsReversed() {
+        val builder = TestEditBuffer("abcdef")
+        builder.replace(4, 6, "g") // abcdg
+        builder.replace(2, 4, "h") // abhg
+        builder.replace(0, 2, "i")
+        assertThat(builder.toString()).isEqualTo("ihg")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    /**
+     * This sequence of operations is basically nonsense and so the mappings don't make much sense
+     * either. This test is just here to ensure the output is consistent and doesn't crash.
+     */
+    @Test
+    fun twoOverlappingReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, 2, "wx") // wxc
+        builder.replace(1, 3, "yz")
+        assertThat(builder.toString()).isEqualTo("wyz")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 3),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+        )
+    }
+
+    /**
+     * This sequence of operations is basically nonsense and so the mappings don't make much sense
+     * either. This test is just here to ensure the output is consistent and doesn't crash.
+     */
+    @Test
+    fun fourOverlappingReplacementsReversed() {
+        val builder = TestEditBuffer("abcde")
+        builder.replace(1, 3, "fg") // afgde
+        builder.replace(2, 4, "hi") // afhie
+        builder.replace(0, 2, "jk") // jkhie
+        builder.replace(3, 5, "lm")
+        assertThat(builder.toString()).isEqualTo("jkhlm")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 5),
+            3 to TextRange(2, 5),
+            4 to TextRange(3, 5),
+            5 to TextRange(5),
+            6 to TextRange(6),
+            7 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 3),
+            2 to TextRange(1, 3),
+            3 to TextRange(1, 4),
+            4 to TextRange(1, 5),
+            5 to TextRange(5),
+            6 to TextRange(6),
+            7 to TextRange(7),
+        )
+    }
+
+    private fun TestEditBuffer.assertIdentityMapping() {
+        // Check well off the end of the valid index range just to be sure.
+        repeat(length + 2) {
+            assertWithMessage("Mapping from source offset $it")
+                .that(mapFromSource(it)).isEqualTo(TextRange(it))
+            assertWithMessage("Mapping from dest offset $it")
+                .that(mapFromDest(it)).isEqualTo(TextRange(it))
+        }
+    }
+
+    private fun TestEditBuffer.assertMappingsFromSource(
+        vararg expectedMappings: Pair<Int, TextRange>
+    ) {
+        expectedMappings.forEach { (srcOffset, dstRange) ->
+            assertWithMessage("Mapping from source offset $srcOffset")
+                .that(mapFromSource(srcOffset)).isEqualTo(dstRange)
+        }
+    }
+
+    private fun TestEditBuffer.assertMappingsFromDest(
+        vararg expectedMappings: Pair<Int, TextRange>
+    ) {
+        expectedMappings.forEach { (dstOffset, srcRange) ->
+            assertWithMessage("Mapping from dest offset $dstOffset")
+                .that(mapFromDest(dstOffset)).isEqualTo(srcRange)
+        }
+    }
+
+    /**
+     * Basic implementation of a text editing buffer that uses [OffsetMappingCalculator] to make
+     * testing easier.
+     */
+    private class TestEditBuffer private constructor(
+        private val builder: StringBuilder
+    ) : CharSequence by builder {
+        constructor(text: CharSequence = "") : this(StringBuilder(text))
+
+        private val tracker = OffsetMappingCalculator()
+
+        fun append(text: CharSequence) {
+            tracker.recordEditOperation(length, length, text.length)
+            builder.append(text)
+        }
+
+        fun insert(offset: Int, value: CharSequence) {
+            tracker.recordEditOperation(offset, offset, value.length)
+            builder.insert(offset, value)
+        }
+
+        fun delete(start: Int, end: Int = start + 1) {
+            tracker.recordEditOperation(start, end, 0)
+            builder.delete(minOf(start, end), maxOf(start, end))
+        }
+
+        fun replace(value: String) {
+            replace(0, length, value)
+        }
+
+        fun replace(start: Int, value: String) {
+            replace(start, start + 1, value)
+        }
+
+        fun replace(start: Int, end: Int, value: String) {
+            tracker.recordEditOperation(start, end, value.length)
+            builder.replace(minOf(start, end), maxOf(start, end), value)
+        }
+
+        fun mapFromSource(offset: Int): TextRange = tracker.mapFromSource(offset)
+        fun mapFromDest(offset: Int): TextRange = tracker.mapFromDest(offset)
+
+        override fun toString(): String = builder.toString()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingRegionCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingRegionCommandTest.kt
new file mode 100644
index 0000000..418f2c3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingRegionCommandTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SetComposingRegionCommandTest {
+
+    @Test
+    fun test_set() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(1, 4)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun test_preserve_ongoing_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 3)
+
+        eb.setComposingRegion(2, 4)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun test_preserve_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
+
+        eb.setComposingRegion(2, 4)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(4)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun test_set_reversed() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(4, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(4)
+    }
+
+    @Test
+    fun test_set_too_small() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(-1000, -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_small_and_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(-1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(5)
+    }
+
+    @Test
+    fun test_set_too_small_and_too_large_reversed() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposingRegion(1000, -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(5)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingTextCommandTest.kt
new file mode 100644
index 0000000..4d4a03c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetComposingTextCommandTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SetComposingTextCommandTest {
+
+    @Test
+    fun test_insert_empty() {
+        val eb = EditingBuffer("", TextRange.Zero)
+
+        eb.setComposingText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("X")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(0)
+        assertThat(eb.compositionEnd).isEqualTo(1)
+    }
+
+    @Test
+    fun test_insert_cursor_tail() {
+        val eb = EditingBuffer("A", TextRange(1))
+
+        eb.setComposingText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AX")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_insert_cursor_head() {
+        val eb = EditingBuffer("A", TextRange(1))
+
+        eb.setComposingText("X", 0)
+
+        assertThat(eb.toString()).isEqualTo("AX")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_insert_cursor_far_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.setComposingText("X", 2)
+
+        assertThat(eb.toString()).isEqualTo("AXBCDE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_insert_cursor_far_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(4))
+
+        eb.setComposingText("X", -2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(4)
+        assertThat(eb.compositionEnd).isEqualTo(5)
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.setComposingText("", 0)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.setComposingText("", 1)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_far_tail() {
+        val eb = EditingBuffer("ABCDE", TextRange(1))
+
+        eb.setComposingText("", 2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_insert_empty_text_cursor_far_head() {
+        val eb = EditingBuffer("ABCDE", TextRange(4))
+
+        eb.setComposingText("", -2)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_cancel_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 4) // Mark "BCD" as composition
+        eb.setComposingText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_replace_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 4)) // select "BCD"
+
+        eb.setComposingText("X", 1)
+
+        assertThat(eb.toString()).isEqualTo("AXE")
+        assertThat(eb.cursor).isEqualTo(2)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(2)
+    }
+
+    @Test
+    fun test_composition_and_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 3)) // select "BC"
+
+        eb.setComposition(2, 4) // Mark "CD" as composition
+        eb.setComposingText("X", 1)
+
+        // If composition and selection exists at the same time, replace composition and cancel
+        // selection and place cursor.
+        assertThat(eb.toString()).isEqualTo("ABXE")
+        assertThat(eb.cursor).isEqualTo(3)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(2)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun test_cursor_position_too_small() {
+        val eb = EditingBuffer("ABCDE", TextRange(5))
+
+        eb.setComposingText("X", -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDEX")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(5)
+        assertThat(eb.compositionEnd).isEqualTo(6)
+    }
+
+    @Test
+    fun test_cursor_position_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange(5))
+
+        eb.setComposingText("X", 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDEX")
+        assertThat(eb.cursor).isEqualTo(6)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(5)
+        assertThat(eb.compositionEnd).isEqualTo(6)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetSelectionCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetSelectionCommandTest.kt
new file mode 100644
index 0000000..ca8cae9
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/SetSelectionCommandTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SetSelectionCommandTest {
+
+    @Test
+    fun test_set() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(1, 4)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(1)
+        assertThat(eb.selectionEnd).isEqualTo(4)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_preserve_ongoing_composition() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setComposition(1, 3)
+
+        eb.setSelection(2, 4)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(2)
+        assertThat(eb.selectionEnd).isEqualTo(4)
+        assertThat(eb.hasComposition()).isTrue()
+        assertThat(eb.compositionStart).isEqualTo(1)
+        assertThat(eb.compositionEnd).isEqualTo(3)
+    }
+
+    @Test
+    fun test_cancel_ongoing_selection() {
+        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
+
+        eb.setSelection(2, 5)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(2)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_reversed() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(4, 1)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(4)
+        assertThat(eb.selectionEnd).isEqualTo(1)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_small() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(-1000, -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.cursor).isEqualTo(5)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_small_too_large() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(-1000, 1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(0)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+
+    @Test
+    fun test_set_too_small_too_large_reversed() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+
+        eb.setSelection(1000, -1000)
+
+        assertThat(eb.toString()).isEqualTo("ABCDE")
+        assertThat(eb.selectionStart).isEqualTo(5)
+        assertThat(eb.selectionEnd).isEqualTo(0)
+        assertThat(eb.hasComposition()).isFalse()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
new file mode 100644
index 0000000..5ccc7ab
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
@@ -0,0 +1,518 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TextFieldStateInternalBufferTest {
+
+    @Test
+    fun initializeValue() {
+        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
+        val state = TextFieldState(firstValue)
+
+        assertThat(state.text).isEqualTo(firstValue)
+    }
+
+    @Test
+    fun apply_commitTextCommand_changesValue() {
+        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
+        val state = TextFieldState(firstValue)
+
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        state.editAsUser { commitText("X", 1) }
+        val newState = state.text
+
+        assertThat(newState.toString()).isEqualTo("XABCDE")
+        assertThat(newState.selectionInChars.min).isEqualTo(1)
+        assertThat(newState.selectionInChars.max).isEqualTo(1)
+        // edit command updates should not trigger reset listeners.
+        assertThat(resetCalled).isEqualTo(0)
+    }
+
+    @Test
+    fun apply_setSelectionCommand_changesValue() {
+        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
+        val state = TextFieldState(firstValue)
+
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        state.editAsUser { setSelection(0, 2) }
+        val newState = state.text
+
+        assertThat(newState.toString()).isEqualTo("ABCDE")
+        assertThat(newState.selectionInChars.min).isEqualTo(0)
+        assertThat(newState.selectionInChars.max).isEqualTo(2)
+        // edit command updates should not trigger reset listeners.
+        assertThat(resetCalled).isEqualTo(0)
+    }
+
+    @Test
+    fun testNewState_bufferNotUpdated_ifSameModelStructurally() {
+        val state = TextFieldState()
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        val initialBuffer = state.mainBuffer
+        state.resetStateAndNotifyIme(
+            TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
+        )
+        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
+
+        val updatedBuffer = state.mainBuffer
+        state.resetStateAndNotifyIme(
+            TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
+        )
+        assertThat(state.mainBuffer).isSameInstanceAs(updatedBuffer)
+
+        assertThat(resetCalled).isEqualTo(2)
+    }
+
+    @Test
+    fun testNewState_new_buffer_created_if_text_is_different() {
+        val state = TextFieldState()
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
+        state.resetStateAndNotifyIme(textFieldValue)
+        val initialBuffer = state.mainBuffer
+
+        val newTextFieldValue = TextFieldCharSequence("abc")
+        state.resetStateAndNotifyIme(newTextFieldValue)
+
+        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
+        assertThat(resetCalled).isEqualTo(2)
+    }
+
+    @Test
+    fun testNewState_buffer_not_recreated_if_selection_is_different() {
+        val state = TextFieldState()
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
+        state.resetStateAndNotifyIme(textFieldValue)
+        val initialBuffer = state.mainBuffer
+
+        val newTextFieldValue = TextFieldCharSequence(textFieldValue, selection = TextRange(1))
+        state.resetStateAndNotifyIme(newTextFieldValue)
+
+        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
+        assertThat(newTextFieldValue.selectionInChars.start)
+            .isEqualTo(state.mainBuffer.selectionStart)
+        assertThat(newTextFieldValue.selectionInChars.end).isEqualTo(
+            state.mainBuffer.selectionEnd
+        )
+        assertThat(resetCalled).isEqualTo(2)
+    }
+
+    @Test
+    fun testNewState_buffer_not_recreated_if_composition_is_different() {
+        val state = TextFieldState()
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange(1))
+        state.resetStateAndNotifyIme(textFieldValue)
+        val initialBuffer = state.mainBuffer
+
+        // composition can not be set from app, IME owns it.
+        assertThat(EditingBuffer.NOWHERE).isEqualTo(initialBuffer.compositionStart)
+        assertThat(EditingBuffer.NOWHERE).isEqualTo(initialBuffer.compositionEnd)
+
+        val newTextFieldValue = TextFieldCharSequence(
+            textFieldValue,
+            textFieldValue.selectionInChars,
+            composition = null
+        )
+        state.resetStateAndNotifyIme(newTextFieldValue)
+
+        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
+        assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionStart)
+        assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionEnd)
+        assertThat(resetCalled).isEqualTo(2)
+    }
+
+    @Test
+    fun testNewState_reversedSelection_setsTheSelection() {
+        val initialSelection = TextRange(2, 1)
+        val textFieldValue = TextFieldCharSequence("qwerty", initialSelection, TextRange(1))
+        val state = TextFieldState(textFieldValue)
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        val initialBuffer = state.mainBuffer
+
+        assertThat(initialSelection.start).isEqualTo(initialBuffer.selectionStart)
+        assertThat(initialSelection.end).isEqualTo(initialBuffer.selectionEnd)
+
+        val updatedSelection = TextRange(3, 0)
+        val newTextFieldValue = TextFieldCharSequence(textFieldValue, selection = updatedSelection)
+        // set the new selection
+        state.resetStateAndNotifyIme(newTextFieldValue)
+
+        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
+        assertThat(updatedSelection.start).isEqualTo(initialBuffer.selectionStart)
+        assertThat(updatedSelection.end).isEqualTo(initialBuffer.selectionEnd)
+        assertThat(resetCalled).isEqualTo(1)
+    }
+
+    @Test
+    fun compositionIsCleared_when_textChanged() {
+        val state = TextFieldState()
+        var resetCalled = 0
+        state.addNotifyImeListener { _, _ -> resetCalled++ }
+
+        // set the initial value
+        state.editAsUser {
+            commitText("ab", 0)
+            setComposingRegion(0, 2)
+        }
+
+        // change the text
+        val newValue =
+            TextFieldCharSequence(
+                "cd",
+                state.text.selectionInChars,
+                state.text.compositionInChars
+            )
+        state.resetStateAndNotifyIme(newValue)
+
+        assertThat(state.text.toString()).isEqualTo(newValue.toString())
+        assertThat(state.text.compositionInChars).isNull()
+    }
+
+    @Test
+    fun compositionIsNotCleared_when_textIsSame() {
+        val state = TextFieldState()
+        val composition = TextRange(0, 2)
+
+        // set the initial value
+        state.editAsUser {
+            commitText("ab", 0)
+            setComposingRegion(composition.start, composition.end)
+        }
+
+        // use the same TextFieldValue
+        val newValue =
+            TextFieldCharSequence(
+                state.text,
+                state.text.selectionInChars,
+                state.text.compositionInChars
+            )
+        state.resetStateAndNotifyIme(newValue)
+
+        assertThat(state.text.toString()).isEqualTo(newValue.toString())
+        assertThat(state.text.compositionInChars).isEqualTo(composition)
+    }
+
+    @Test
+    fun compositionIsCleared_when_compositionReset() {
+        val state = TextFieldState()
+
+        // set the initial value
+        state.editAsUser {
+            commitText("ab", 0)
+            setComposingRegion(-1, -1)
+        }
+
+        // change the composition
+        val newValue =
+            TextFieldCharSequence(
+                state.text,
+                state.text.selectionInChars,
+                composition = TextRange(0, 2)
+            )
+        state.resetStateAndNotifyIme(newValue)
+
+        assertThat(state.text.toString()).isEqualTo(newValue.toString())
+        assertThat(state.text.compositionInChars).isNull()
+    }
+
+    @Test
+    fun compositionIsCleared_when_compositionChanged() {
+        val state = TextFieldState()
+
+        // set the initial value
+        state.editAsUser {
+            commitText("ab", 0)
+            setComposingRegion(0, 2)
+        }
+
+        // change the composition
+        val newValue = TextFieldCharSequence(
+            state.text,
+            state.text.selectionInChars,
+            composition = TextRange(0, 1)
+        )
+        state.resetStateAndNotifyIme(newValue)
+
+        assertThat(state.text.toString()).isEqualTo(newValue.toString())
+        assertThat(state.text.compositionInChars).isNull()
+    }
+
+    @Test
+    fun compositionIsNotCleared_when_onlySelectionChanged() {
+        val state = TextFieldState()
+
+        val composition = TextRange(0, 2)
+        val selection = TextRange(0, 2)
+
+        // set the initial value
+        state.editAsUser {
+            commitText("ab", 0)
+            setComposingRegion(composition.start, composition.end)
+            setSelection(selection.start, selection.end)
+        }
+
+        // change selection
+        val newSelection = TextRange(1)
+        val newValue = TextFieldCharSequence(
+            state.text,
+            selection = newSelection,
+            composition = state.text.compositionInChars
+        )
+        state.resetStateAndNotifyIme(newValue)
+
+        assertThat(state.text.toString()).isEqualTo(newValue.toString())
+        assertThat(state.text.compositionInChars).isEqualTo(composition)
+        assertThat(state.text.selectionInChars).isEqualTo(newSelection)
+    }
+
+    @Test
+    fun filterThatDoesNothing_doesNotResetBuffer() {
+        val state = TextFieldState(
+            TextFieldCharSequence(
+                "abc",
+                selection = TextRange(3),
+                composition = TextRange(0, 3)
+            )
+        )
+
+        val initialBuffer = state.mainBuffer
+
+        state.editAsUser { commitText("d", 4) }
+
+        val value = state.text
+
+        assertThat(value.toString()).isEqualTo("abcd")
+        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
+    }
+
+    @Test
+    fun returningTheEquivalentValueFromFilter_doesNotResetBuffer() {
+        val state = TextFieldState(
+            TextFieldCharSequence(
+                "abc",
+                selection = TextRange(3),
+                composition = TextRange(0, 3)
+            )
+        )
+
+        val initialBuffer = state.mainBuffer
+
+        state.editAsUser { commitText("d", 4) }
+
+        val value = state.text
+
+        assertThat(value.toString()).isEqualTo("abcd")
+        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
+    }
+
+    @Test
+    fun returningOldValueFromFilter_resetsTheBuffer() {
+        val state = TextFieldState(
+            TextFieldCharSequence(
+                "abc",
+                selection = TextRange(3),
+                composition = TextRange(0, 3)
+            )
+        )
+
+        var resetCalledOld: TextFieldCharSequence? = null
+        var resetCalledNew: TextFieldCharSequence? = null
+        state.addNotifyImeListener { old, new ->
+            resetCalledOld = old
+            resetCalledNew = new
+        }
+
+        val initialBuffer = state.mainBuffer
+
+        state.editAsUser(
+            inputTransformation = { _, new -> new.revertAllChanges() },
+            notifyImeOfChanges = false
+        ) {
+            commitText("d", 4)
+        }
+
+        val value = state.text
+
+        assertThat(value.toString()).isEqualTo("abc")
+        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
+        assertThat(resetCalledOld?.toString()).isEqualTo("abcd") // what IME applied
+        assertThat(resetCalledNew?.toString()).isEqualTo("abc") // what is decided by filter
+    }
+
+    @Test
+    fun filterNotRan_whenNoCommands() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            fail("filter ran, old=\"$old\", new=\"$new\"")
+        }
+
+        state.editAsUser(inputTransformation, notifyImeOfChanges = false) {}
+    }
+
+    @Test
+    fun filterNotRan_whenOnlyFinishComposingTextCommand_noComposition() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            fail("filter ran, old=\"$old\", new=\"$new\"")
+        }
+
+        state.editAsUser(
+            inputTransformation = inputTransformation,
+            notifyImeOfChanges = false
+        ) { finishComposingText() }
+    }
+
+    @Test
+    fun filterNotRan_whenOnlyFinishComposingTextCommand_withComposition() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = TextRange(0, 5))
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            fail("filter ran, old=\"$old\", new=\"$new\"")
+        }
+
+        state.editAsUser(
+            inputTransformation = inputTransformation,
+            notifyImeOfChanges = false
+        ) { finishComposingText() }
+    }
+
+    @Test
+    fun filterNotRan_whenCommandsResultInInitialValue() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = TextRange(0, 5))
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            fail(
+                "filter ran, old=\"$old\" (${old.selectionInChars}), " +
+                    "new=\"$new\" (${new.selectionInChars})"
+            )
+        }
+
+        state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
+            setComposingRegion(0, 5)
+            commitText("hello", 1)
+            setSelection(2, 2)
+        }
+    }
+
+    @Test
+    fun filterRan_whenOnlySelectionChanges() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
+        var filterRan = false
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            // Filter should only run once.
+            assertThat(filterRan).isFalse()
+            filterRan = true
+            assertThat(new.toString()).isEqualTo(old.toString())
+            assertThat(old.selectionInChars).isEqualTo(TextRange(2))
+            assertThat(new.selectionInChars).isEqualTo(TextRange(0, 5))
+        }
+
+        state.editAsUser(
+            inputTransformation = inputTransformation,
+            notifyImeOfChanges = false
+        ) { setSelection(0, 5) }
+    }
+
+    @Test
+    fun filterRan_whenOnlyTextChanges() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
+        var filterRan = false
+        val state = TextFieldState(initialValue)
+        val inputTransformation = InputTransformation { old, new ->
+            // Filter should only run once.
+            assertThat(filterRan).isFalse()
+            filterRan = true
+            assertThat(new.selectionInChars).isEqualTo(old.selectionInChars)
+            assertThat(old.toString()).isEqualTo("hello")
+            assertThat(new.toString()).isEqualTo("world")
+        }
+
+        state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
+            deleteAll()
+            commitText("world", 1)
+            setSelection(2, 2)
+        }
+    }
+
+    @Test
+    fun stateUpdated_whenOnlyCompositionChanges_noFilter() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(5), composition = TextRange(0, 5))
+        val state = TextFieldState(initialValue)
+
+        state.editAsUser { setComposingRegion(2, 3) }
+
+        assertThat(state.text.compositionInChars).isEqualTo(TextRange(2, 3))
+    }
+
+    @Test
+    fun stateUpdated_whenOnlyCompositionChanges_withFilter() {
+        val initialValue =
+            TextFieldCharSequence("hello", selection = TextRange(5), composition = TextRange(0, 5))
+        val state = TextFieldState(initialValue)
+
+        state.editAsUser { setComposingRegion(2, 3) }
+
+        assertThat(state.text.compositionInChars).isEqualTo(TextRange(2, 3))
+    }
+
+    private fun TextFieldState(
+        value: TextFieldCharSequence
+    ) = TextFieldState(value.toString(), value.selectionInChars)
+
+    private fun TextFieldState.editAsUser(block: EditingBuffer.() -> Unit) {
+        editAsUser(inputTransformation = null, notifyImeOfChanges = false, block = block)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ToCharArrayTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ToCharArrayTest.kt
new file mode 100644
index 0000000..2da6ea4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/ToCharArrayTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFails
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class ToCharArrayTest {
+
+    private val sources = listOf(
+        "hello",
+        TextFieldCharSequence("hello"),
+        CustomCharSequence("hello"),
+    )
+
+    private val dest = CharArray(10)
+
+    @Test
+    fun toCharArray_invalidSourceStartIndex() {
+        sources.forEach { source ->
+            assertFails {
+                source.toCharArray(dest, 0, source.length + 1, source.length + 2)
+            }
+        }
+    }
+
+    @Test
+    fun toCharArray_invalidSourceEndIndex() {
+        sources.forEach { source ->
+            assertFails {
+                source.toCharArray(dest, 0, 0, source.length + 1)
+            }
+        }
+    }
+
+    @Test
+    fun toCharArray_invalidDestStartIndex() {
+        sources.forEach { source ->
+            assertFails {
+                source.toCharArray(dest, dest.size + 1, 0, 1)
+            }
+        }
+    }
+
+    @Test
+    fun toCharArray_invalidDestEndIndex() {
+        sources.forEach { source ->
+            assertFails {
+                source.toCharArray(dest, dest.size, 0, 1)
+            }
+        }
+    }
+
+    @Test
+    fun toCharArray_copiesChars_toStartOfDest() {
+        sources.forEach { source ->
+            dest.fill(Char(0))
+            source.toCharArray(dest, 0, 0, source.length)
+            assertThat(dest).asList().containsExactly(
+                'h', 'e', 'l', 'l', 'o', Char(0), Char(0), Char(0), Char(0), Char(0)
+            ).inOrder()
+        }
+    }
+
+    @Test
+    fun toCharArray_copiesChars_toEndOfDest() {
+        sources.forEach { source ->
+            dest.fill(Char(0))
+            source.toCharArray(dest, 5, 0, source.length)
+            assertThat(dest).asList().containsExactly(
+                Char(0), Char(0), Char(0), Char(0), Char(0), 'h', 'e', 'l', 'l', 'o'
+            ).inOrder()
+        }
+    }
+
+    private class CustomCharSequence(text: String) : CharSequence by text
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt
new file mode 100644
index 0000000..1510470
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.delete
+import androidx.compose.foundation.text.input.insert
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TransformedTextFieldStateTest {
+
+    @Test
+    fun outputTransformationAffectsPresentedAndVisualText() {
+        val state = TextFieldState("hello")
+        val outputTransformation = OutputTransformation {
+            append("world")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.untransformedText.toString()).isEqualTo("hello")
+        assertThat(transformedState.outputText.toString()).isEqualTo("helloworld")
+        assertThat(transformedState.visualText.toString()).isEqualTo("helloworld")
+    }
+
+    @Test
+    fun mapToTransformed_insertions() {
+        val state = TextFieldState("zzzz")
+        val outputTransformation = OutputTransformation {
+            insert(2, "bb")
+            insert(0, "aa")
+            append("cc")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("aazzbbzzcc")
+        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0, 2))
+        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(3))
+        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(4, 6))
+        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(7))
+        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(8, 10))
+        // Past the end.
+        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(11))
+    }
+
+    @Test
+    fun mapToTransformed_deletions() {
+        val state = TextFieldState("aazzbbzzcc")
+        val outputTransformation = OutputTransformation {
+            delete(8, 10)
+            delete(4, 6)
+            delete(0, 2)
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("zzzz")
+        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(1))
+        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapToTransformed(6)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapToTransformed(7)).isEqualTo(TextRange(3))
+        assertThat(transformedState.mapToTransformed(8)).isEqualTo(TextRange(4))
+        assertThat(transformedState.mapToTransformed(9)).isEqualTo(TextRange(4))
+        assertThat(transformedState.mapToTransformed(10)).isEqualTo(TextRange(4))
+        // Past the end.
+        assertThat(transformedState.mapToTransformed(11)).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun mapToTransformed_replacements() {
+        val state = TextFieldState("aabb")
+        val outputTransformation = OutputTransformation {
+            replace(2, 4, "ddd")
+            replace(0, 2, "c")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("cddd")
+        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(0, 1))
+        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(1))
+        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(1, 4))
+        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(4))
+        // Past the end.
+        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun mapFromTransformed_insertions() {
+        val state = TextFieldState("zzzz")
+        val outputTransformation = OutputTransformation {
+            insert(2, "bb")
+            insert(0, "aa")
+            append("cc")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("aazzbbzzcc")
+        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(1))
+        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapFromTransformed(6)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapFromTransformed(7)).isEqualTo(TextRange(3))
+        assertThat(transformedState.mapFromTransformed(8)).isEqualTo(TextRange(4))
+        assertThat(transformedState.mapFromTransformed(9)).isEqualTo(TextRange(4))
+        assertThat(transformedState.mapFromTransformed(10)).isEqualTo(TextRange(4))
+        // Past the end.
+        assertThat(transformedState.mapFromTransformed(11)).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun mapFromTransformed_deletions() {
+        val state = TextFieldState("aazzbbzzcc")
+        val outputTransformation = OutputTransformation {
+            delete(8, 10)
+            delete(4, 6)
+            delete(0, 2)
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("zzzz")
+        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0, 2))
+        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(3))
+        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(4, 6))
+        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(7))
+        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(8, 10))
+        // Past the end.
+        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(11))
+    }
+
+    @Test
+    fun mapFromTransformed_replacements() {
+        val state = TextFieldState("aabb")
+        val outputTransformation = OutputTransformation {
+            replace(2, 4, "ddd")
+            replace(0, 2, "c")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+
+        assertThat(transformedState.outputText.toString()).isEqualTo("cddd")
+        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0))
+        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(2))
+        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(2, 4))
+        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(2, 4))
+        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(4))
+        // Past the end.
+        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(5))
+    }
+
+    @Test
+    fun textFieldStateSelection_isTransformed_toPresentedText() {
+        val state = TextFieldState("hello")
+        val outputTransformation = OutputTransformation {
+            insert(0, "aa")
+        }
+        val transformedState = TransformedTextFieldState(
+            textFieldState = state,
+            outputTransformation = outputTransformation
+        )
+        assertThat(transformedState.outputText.toString()).isEqualTo("aahello")
+
+        state.edit { selectCharsIn(TextRange(0, 2)) }
+        assertThat(transformedState.outputText.selectionInChars).isEqualTo(TextRange(0, 4))
+        // Rest of indices and wedge affinity are covered by mapToTransformed tests.
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt
new file mode 100644
index 0000000..b16004f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.delete
+import androidx.compose.foundation.text.input.insert
+import androidx.compose.foundation.text.input.internal.selection.calculateAdjacentCursorPosition
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TransformedTextSelectionMovementTest {
+
+    @Test
+    fun calculateNextCursorPosition_aroundReplacement() {
+        val state = TextFieldState("abc", initialSelectionInChars = TextRange(1))
+        val outputTransformation = OutputTransformation {
+            replace(1, 2, "zz") // "azzc"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+
+        calculateNextCursorPosition(transformedState)
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
+    }
+
+    @Test
+    fun calculatePreviousCursorPosition_aroundReplacement() {
+        val state = TextFieldState("abc", initialSelectionInChars = TextRange(2))
+        val outputTransformation = OutputTransformation {
+            replace(1, 2, "zz") // "azzc"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
+
+        calculatePreviousCursorPosition(transformedState)
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
+    }
+
+    @Test
+    fun calculateNextCursorPosition_aroundInsertion() {
+        val state = TextFieldState("ab", initialSelectionInChars = TextRange(0))
+        val outputTransformation = OutputTransformation {
+            insert(1, "zz") // "azzb"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+
+        calculateNextCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
+
+        calculateNextCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
+
+        calculateNextCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(4))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
+    }
+
+    @Test
+    fun calculatePreviousCursorPosition_aroundInsertion() {
+        val state = TextFieldState("ab", initialSelectionInChars = TextRange(2))
+        val outputTransformation = OutputTransformation {
+            insert(1, "zz") // "azzb"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(4))
+
+        calculatePreviousCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
+
+        calculatePreviousCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
+
+        calculatePreviousCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(0))
+        assertThat(transformedState.selectionWedgeAffinity)
+            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
+    }
+
+    @Test
+    fun calculateNextCursorPosition_aroundDeletion() {
+        val state = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
+        val outputTransformation = OutputTransformation {
+            delete(1, 3) // "ad"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+
+        calculateNextCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 3))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
+
+        calculateNextCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun calculatePreviousCursorPosition_aroundDeletion() {
+        val state = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
+        val outputTransformation = OutputTransformation {
+            delete(1, 3) // "ad"
+        }
+        val transformedState =
+            TransformedTextFieldState(state, outputTransformation = outputTransformation)
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(2))
+
+        calculatePreviousCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 3))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
+
+        calculatePreviousCursorPosition(transformedState)
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(0))
+    }
+
+    private fun calculateNextCursorPosition(state: TransformedTextFieldState) {
+        val newCursor = calculateAdjacentCursorPosition(
+            state.visualText.toString(),
+            state.visualText.selectionInChars.end,
+            forward = true,
+            state
+        )
+        state.placeCursorBeforeCharAt(newCursor)
+    }
+
+    private fun calculatePreviousCursorPosition(state: TransformedTextFieldState) {
+        val newCursor = calculateAdjacentCursorPosition(
+            state.visualText.toString(),
+            state.visualText.selectionInChars.end,
+            forward = false,
+            state
+        )
+        state.placeCursorBeforeCharAt(newCursor)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/matchers/EditBufferSubject.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/matchers/EditBufferSubject.kt
new file mode 100644
index 0000000..798e02d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/matchers/EditBufferSubject.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 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.
+ */
+
+@file:OptIn(InternalFoundationTextApi::class)
+
+package androidx.compose.foundation.text.input.internal.matchers
+
+import androidx.compose.foundation.text.InternalFoundationTextApi
+import androidx.compose.foundation.text.input.internal.EditingBuffer
+import androidx.compose.foundation.text.input.internal.PartialGapBuffer
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+
+@OptIn(InternalFoundationTextApi::class)
+internal fun assertThat(buffer: PartialGapBuffer): EditBufferSubject {
+    return assertAbout(EditBufferSubject.SUBJECT_FACTORY)
+        .that(GapBufferWrapper(buffer))!!
+}
+
+internal fun assertThat(buffer: EditingBuffer): EditBufferSubject {
+    return assertAbout(EditBufferSubject.SUBJECT_FACTORY)
+        .that(EditingBufferWrapper(buffer))!!
+}
+
+internal abstract class GetOperatorWrapper(val buffer: Any) {
+    abstract operator fun get(index: Int): Char
+    override fun toString(): String = buffer.toString()
+}
+
+private class EditingBufferWrapper(buffer: EditingBuffer) : GetOperatorWrapper(buffer) {
+    override fun get(index: Int): Char = (buffer as EditingBuffer)[index]
+}
+
+@OptIn(InternalFoundationTextApi::class)
+private class GapBufferWrapper(buffer: PartialGapBuffer) : GetOperatorWrapper(buffer) {
+    override fun get(index: Int): Char = (buffer as PartialGapBuffer)[index]
+}
+
+/**
+ * Truth extension for Editing Buffers.
+ */
+internal class EditBufferSubject private constructor(
+    failureMetadata: FailureMetadata?,
+    private val subject: GetOperatorWrapper
+) : Subject(failureMetadata, subject) {
+
+    companion object {
+        internal val SUBJECT_FACTORY: Factory<EditBufferSubject, GetOperatorWrapper> =
+            Factory { failureMetadata, subject -> EditBufferSubject(failureMetadata, subject) }
+    }
+
+    fun hasChars(expected: String) {
+        assertThat(subject.buffer.toString()).isEqualTo(expected)
+        for (i in expected.indices) {
+            assertThat(subject[i]).isEqualTo(expected[i])
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt
new file mode 100644
index 0000000..6eab192
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.allCaps
+import androidx.compose.foundation.text.input.internal.commitText
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.intl.Locale
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TextUndoTest {
+
+    @Test
+    fun insertionFromEndPointCanMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a")
+        state.typeAtEnd("b")
+        state.typeAtEnd("c")
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun insertionFromStartPointCannotMerge() {
+        val state = TextFieldState()
+        state.typeAtStart("a")
+        state.typeAtStart("b")
+        state.typeAtStart("c")
+
+        assertThat(state.text.toString()).isEqualTo("cba")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ba")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+    }
+
+    @Test
+    fun insertionFromMiddleCannotMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a")
+        state.typeAtEnd("c")
+        state.typeAt(1, "b")
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ac")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun deletionFromEndPointCanMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(3)
+        state.deleteAt(2)
+        state.deleteAt(1)
+        state.deleteAt(0)
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionFromStartPointCanMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(0)
+        state.deleteAt(0)
+        state.deleteAt(0)
+        state.deleteAt(0)
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionFromMiddleCannotMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(2) // "ab|c"
+        state.deleteAt(1) // "a|c"
+        state.deleteAt(1) // "a|"
+        state.deleteAt(0) // "|"
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ac")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionsWithDifferentDirectionsCannotMerge() {
+        val state = TextFieldState("abcdef")
+        state.placeCursorAt(3) // "abc|def"
+        state.deleteAt(2) // "ab|def"
+        state.deleteAt(2) // "ab|ef"
+
+        assertThat(state.text.toString()).isEqualTo("abef")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abdef")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abcdef")
+    }
+
+    @Test
+    fun insertionAndDeletionNeverMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a") // "a|"
+        state.typeAtEnd("b") // "ab|"
+        state.deleteAt(1) // "a|"
+        state.typeAtStart("c") // "|a" -> "c|a"
+
+        assertThat(state.text.toString()).isEqualTo("ca")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun replaceDoesNotMergeWithInsertion() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d") // "abcd|"
+        state.replaceAt(0, 4, "def") // "def|"
+        state.typeAtEnd("g") // "defg|"
+
+        assertThat(state.text.toString()).isEqualTo("defg")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("def")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abcd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun replaceDoesNotMergeWithDeletion() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(3)
+        state.deleteAt(2) // "ab|"
+        state.replaceAt(0, 2, "def") // "def|"
+        state.deleteAt(2) // "de|"
+
+        assertThat(state.text.toString()).isEqualTo("de")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("def")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun newLineInsert_doesNotMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a") // "a|"
+        state.typeAtEnd("b") // "ab|"
+        state.typeAtEnd("\n") // "ab\n|"
+        state.typeAtEnd("c") // "ab\nc|"
+
+        assertThat(state.text.toString()).isEqualTo("ab\nc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab\n")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun undoRecoversSelectionState() {
+        val state = TextFieldState("abc")
+        state.select(2, 0) // "|ab|c"
+        state.type("d") // "d|c"
+
+        assertThat(state.text.toString()).isEqualTo("dc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2, 0))
+    }
+
+    @Test
+    fun redoDoesNotRecoverSelectionState() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d") // "abcd|"
+        state.select(2, 0) // "|ab|cd"
+        state.type("e") // "e|cd"
+
+        assertThat(state.text.toString()).isEqualTo("ecd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo() // "|ab|cd"
+        state.undoState.undo() // "|abc"
+        state.undoState.redo() // "abcd|"
+
+        assertThat(state.text.toString()).isEqualTo("abcd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 4))
+    }
+
+    @Test
+    fun undoHistoryIncludesInputTransformation() {
+        val allCapsTransformation = InputTransformation.allCaps(Locale.current)
+        val state = TextFieldState("abc", TextRange(3))
+
+        // this test also tests for AllCapsTransformation
+        state.editAsUser(inputTransformation = allCapsTransformation) {
+            commitComposition()
+            commitText("d", 1)
+        } // "abcD|"
+        state.editAsUser(inputTransformation = allCapsTransformation) {
+            commitComposition()
+            commitText("e", 1)
+        } // "abcDE|"
+
+        state.undoState.undo() // "abc|"
+        assertThat(state.text.toString()).isEqualTo("abc")
+
+        state.undoState.redo() // "abcDE|"
+        assertThat(state.text.toString()).isEqualTo("abcDE")
+    }
+
+    @Test
+    fun directEditsClearTheUndoHistory() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d")
+        state.typeAtStart("e")
+        state.typeAtEnd("f")
+
+        state.edit { replace(0, 1, "x") }
+
+        assertThat(state.undoState.canUndo).isEqualTo(false)
+        assertThat(state.undoState.canRedo).isEqualTo(false)
+    }
+
+    companion object {
+
+        private fun TextFieldState.typeAtEnd(text: String) {
+            placeCursorAt(this.text.length)
+            typeAt(this.text.length, text)
+        }
+
+        private fun TextFieldState.typeAtStart(text: String) {
+            placeCursorAt(0)
+            typeAt(0, text)
+        }
+
+        private fun TextFieldState.typeAt(index: Int, text: String) {
+            placeCursorAt(index)
+            editAsUser(inputTransformation = null) {
+                replace(index, index, text)
+            }
+        }
+
+        private fun TextFieldState.type(text: String) {
+            editAsUser(inputTransformation = null) {
+                commitComposition()
+                commitText(text, 1)
+            }
+        }
+
+        private fun TextFieldState.deleteAt(index: Int) {
+            editAsUser(inputTransformation = null) {
+                delete(index, index + 1)
+            }
+        }
+
+        private fun TextFieldState.placeCursorAt(index: Int) {
+            select(index, index)
+        }
+
+        private fun TextFieldState.select(start: Int, end: Int) {
+            editAsUser(inputTransformation = null) {
+                setSelection(start, end)
+            }
+        }
+
+        private fun TextFieldState.replaceAt(start: Int, end: Int, newText: String) {
+            editAsUser(inputTransformation = null) {
+                replace(start, end, newText)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerSaverTest.kt
new file mode 100644
index 0000000..0ac9b47
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerSaverTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.undo
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.autoSaver
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
+import org.junit.Test
+
+class UndoManagerSaverTest {
+
+    @Test
+    fun savesAndRestoresTextAndSelection() {
+        val undoManager = UndoManager<Int>()
+
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+
+        // undoStack; 1-2 redoStack; 3
+
+        val saver = UndoManager.createSaver(autoSaver<Int>())
+        val saved = with(saver) {
+            TestSaverScope.save(undoManager)
+        }
+        assertNotNull(saved)
+        val restoredState = saver.restore(saved)
+
+        assertNotNull(restoredState)
+        assertThat(restoredState.canUndo).isTrue()
+        assertThat(restoredState.canRedo).isTrue()
+
+        var redoValue = undoManager.redo()
+
+        assertThat(redoValue).isEqualTo(3)
+
+        val undoValues = mutableListOf<Int>()
+        while (undoManager.canUndo) {
+            undoValues += undoManager.undo()
+        }
+
+        assertThat(undoValues).containsExactly(3, 2, 1)
+    }
+
+    private object TestSaverScope : SaverScope {
+        override fun canBeSaved(value: Any): Boolean = true
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerTest.kt
new file mode 100644
index 0000000..ebb054e
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManagerTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal.undo
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class UndoManagerTest {
+
+    @Test
+    fun negativeCapacityThrows() {
+        assertFailsWith<IllegalArgumentException>("Capacity must be a positive integer") {
+            UndoManager<Int>(capacity = -1)
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_undoStack() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 1,
+                totalStackSize = 2
+            )
+        ) {
+            UndoManager(
+                initialUndoStack = listOf(1, 2),
+                capacity = 1
+            )
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_redoStack() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 1,
+                totalStackSize = 2
+            )
+        ) {
+            UndoManager(
+                initialRedoStack = listOf(1, 2),
+                capacity = 1
+            )
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_bothStacks() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 3,
+                totalStackSize = 4
+            )
+        ) {
+            UndoManager(
+                initialUndoStack = listOf(1, 2),
+                initialRedoStack = listOf(1, 2),
+                capacity = 3
+            )
+        }
+    }
+
+    @Test
+    fun commitSingleItem_canUndo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isFalse()
+
+        undoManager.undo()
+        assertThat(undoManager.canUndo).isFalse()
+        assertThat(undoManager.canRedo).isTrue()
+    }
+
+    @Test
+    fun cannotRedoWithoutFirstUndo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        assertThat(undoManager.canRedo).isFalse()
+        assertFailsWith<IllegalStateException>(
+            "It's an error to call redo while there is nothing to redo. " +
+                "Please first check `canRedo` value before calling the `redo` function."
+        ) {
+            undoManager.redo()
+        }
+    }
+
+    @Test
+    fun commitItem_clearsRedoStack() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+        assertThat(undoManager.canRedo).isTrue()
+
+        undoManager.record(4)
+        assertThat(undoManager.canRedo).isFalse()
+    }
+
+    @Test
+    fun clearHistoryRemovesUndoAndRedo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isTrue()
+
+        undoManager.clearHistory()
+
+        assertThat(undoManager.canUndo).isFalse()
+        assertThat(undoManager.canRedo).isFalse()
+    }
+
+    @Test
+    fun capacityOverflow_removesFromTheBottomOfStack() {
+        val undoManager = UndoManager<Int>(capacity = 2)
+        undoManager.record(1)
+        undoManager.record(2)
+        // overflow the capacity, undo history should forget the first item
+        undoManager.record(3)
+
+        var item = undoManager.undo()
+        assertThat(item).isEqualTo(3)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(2)
+
+        assertThat(undoManager.canUndo).isFalse()
+    }
+
+    @Test
+    fun capacityOverflow_shouldRemoveRedoActionsFirst() {
+        val undoManager = UndoManager<Int>(capacity = 20)
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+        undoManager.record(4)
+
+        undoManager.undo() // total size does not change  undo; 1-2-3 redo; 4
+        undoManager.undo() // total size does not change  undo; 1-2 redo; 4-3
+
+        // this should not remove anything from the undo stack, auto removed items from redo should
+        // suffice
+        undoManager.record(5)
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isFalse()
+
+        var item = undoManager.undo()
+        assertThat(item).isEqualTo(5)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(2)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(1)
+    }
+
+    private fun getInitialCapacityErrorMessage(capacity: Int, totalStackSize: Int) =
+        "Initial list of undo and redo operations have a size=($totalStackSize) greater " +
+        "than the given capacity=($capacity)."
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.kt
deleted file mode 100644
index 1f20b17..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.intl.Locale
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class AllCapsTransformationTest {
-
-    @Test
-    fun allCapsTransformation_definesCharacterCapitalizationKeyboardOption() {
-        val transformation = InputTransformation.allCaps(Locale.current)
-        assertThat(transformation.keyboardOptions?.capitalization)
-            .isEqualTo(KeyboardCapitalization.Characters)
-    }
-
-    @Test
-    fun allNewTypedCharacters_convertedToUppercase() {
-        val transformation = InputTransformation.allCaps(Locale("en_US"))
-
-        val originalValue = TextFieldCharSequence("")
-        val buffer = TextFieldBuffer(originalValue).apply {
-            append("hello")
-        }
-
-        transformation.transformInput(originalValue, buffer)
-
-        assertThat(buffer.toString()).isEqualTo("HELLO")
-    }
-
-    @Test
-    fun oldCharacters_areNotConverted() {
-        val transformation = InputTransformation.allCaps(Locale("en_US"))
-
-        val originalValue = TextFieldCharSequence("hello")
-        val buffer = TextFieldBuffer(originalValue).apply {
-            append(" world")
-        }
-
-        transformation.transformInput(originalValue, buffer)
-
-        assertThat(buffer.toString()).isEqualTo("hello WORLD")
-    }
-
-    @Test
-    fun localeDifference_turkishI() {
-        val transformation = InputTransformation.allCaps(Locale("tr"))
-
-        val originalValue = TextFieldCharSequence("")
-        val buffer = TextFieldBuffer(originalValue).apply {
-            append("i")
-        }
-
-        transformation.transformInput(originalValue, buffer)
-
-        assertThat(buffer.toString()).isEqualTo("\u0130") // Turkish dotted capital i
-    }
-
-    @Test
-    fun multipleEdits() {
-        val transformation = InputTransformation.allCaps(Locale("en_US"))
-
-        var originalValue = TextFieldCharSequence("hello")
-        var buffer = TextFieldBuffer(originalValue)
-
-        with(buffer) {
-            delete(0, 3) // lo
-            replace(1, 1, "abc") // lABCo
-        }
-
-        transformation.transformInput(originalValue, buffer)
-
-        originalValue = buffer.toTextFieldCharSequence()
-        buffer = TextFieldBuffer(originalValue)
-
-        with(buffer) {
-            delete(2, 3) // lACo
-            append("xyz") // lACoXYZ
-        }
-
-        transformation.transformInput(originalValue, buffer)
-
-        assertThat(buffer.toString()).isEqualTo("lACoXYZ")
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt
deleted file mode 100644
index 21ef966..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.internal.OffsetMappingCalculator
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlin.test.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class CodepointTransformationTest {
-
-    @Test
-    fun toVisualText_codepointIndices() {
-        val source =
-            TextFieldCharSequence("a${SurrogateCodepointString}c$SurrogateCodepointString")
-        val offsetMapping = OffsetMappingCalculator()
-        val codepointTransformation = CodepointTransformation { i, codepoint ->
-            val expectedCodePoint = when (i) {
-                0 -> 'a'.code
-                1 -> SurrogateCodepoint
-                2 -> 'c'.code
-                3 -> SurrogateCodepoint
-                else -> fail("Invalid codepoint index: $i")
-            }
-            assertThat(codepoint).isEqualTo(expectedCodePoint)
-            codepoint
-        }
-
-        source.toVisualText(codepointTransformation, offsetMapping)
-    }
-
-    @Test
-    fun toVisualText_mapsOffsetsForward() {
-        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
-        val offsetMapping = OffsetMappingCalculator()
-        val codepointTransformation = CodepointTransformation { i, codepoint ->
-            when (codepoint) {
-                'a'.code, 'c'.code -> SurrogateCodepoint
-                SurrogateCodepoint -> 'b'.code
-                else -> fail(
-                    "codepointIndex=$i, codepoint=\"${
-                        String(intArrayOf(codepoint), 0, 1)
-                    }\""
-                )
-            }
-        }
-        val visual = source.toVisualText(codepointTransformation, offsetMapping)
-
-        assertThat(visual.toString())
-            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
-
-        listOf(
-            0 to TextRange(0),
-            1 to TextRange(2),
-            2 to TextRange(2, 3),
-            3 to TextRange(3),
-            4 to TextRange(5),
-        ).forEach { (source, dest) ->
-            assertWithMessage("Mapping from untransformed offset $source")
-                .that(offsetMapping.mapFromSource(source)).isEqualTo(dest)
-        }
-    }
-
-    @Test
-    fun toVisualText_mapsOffsetsBackward() {
-        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
-        val offsetMapping = OffsetMappingCalculator()
-        val codepointTransformation = CodepointTransformation { i, codepoint ->
-            when (codepoint) {
-                'a'.code, 'c'.code -> SurrogateCodepoint
-                SurrogateCodepoint -> 'b'.code
-                else -> fail(
-                    "codepointIndex=$i, codepoint=\"${
-                        String(intArrayOf(codepoint), 0, 1)
-                    }\""
-                )
-            }
-        }
-        val visual = source.toVisualText(codepointTransformation, offsetMapping)
-
-        assertThat(visual.toString())
-            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
-
-        listOf(
-            0 to TextRange(0),
-            1 to TextRange(0, 1),
-            2 to TextRange(1),
-            3 to TextRange(3),
-            4 to TextRange(3, 4),
-            5 to TextRange(4),
-        ).forEach { (dest, source) ->
-            assertWithMessage("Mapping from transformed offset $dest")
-                .that(offsetMapping.mapFromDest(dest)).isEqualTo(source)
-        }
-    }
-
-    private companion object {
-        /** This is "𐐷", a surrogate codepoint. */
-        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
-        const val SurrogateCodepointString = "\uD801\uDC37"
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/InputTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/InputTransformationTest.kt
deleted file mode 100644
index d25548f..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/InputTransformationTest.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.KeyboardOptions
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-class InputTransformationTest {
-
-    @Test
-    fun chainedFilters_areEqual() {
-        val filter1 = InputTransformation { _, _ ->
-            // Noop
-        }
-        val filter2 = InputTransformation { _, _ ->
-            // Noop
-        }
-
-        val chain1 = filter1.then(filter2)
-        val chain2 = filter1.then(filter2)
-
-        assertThat(chain1).isEqualTo(chain2)
-    }
-
-    @Test
-    fun chainedFilters_areNotEqual_whenFiltersAreDifferent() {
-        val filter1 = InputTransformation { _, _ ->
-            // Noop
-        }
-        val filter2 = InputTransformation { _, _ ->
-            // Noop
-        }
-        val filter3 = InputTransformation { _, _ ->
-            // Noop
-        }
-
-        val chain1 = filter1.then(filter2)
-        val chain2 = filter1.then(filter3)
-
-        assertThat(chain1).isNotEqualTo(chain2)
-    }
-
-    @Test
-    fun chainedFilters_haveNullKeyboardOptions_whenBothOptionsAreNull() {
-        val filter1 = object : InputTransformation {
-            override val keyboardOptions = null
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-        val filter2 = object : InputTransformation {
-            override val keyboardOptions = null
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-
-        val chain = filter1.then(filter2)
-
-        assertThat(chain.keyboardOptions).isNull()
-    }
-
-    @Test
-    fun chainedFilters_takeFirstKeyboardOptions_whenSecondOptionsAreNull() {
-        val options = KeyboardOptions()
-        val filter1 = object : InputTransformation {
-            override val keyboardOptions = options
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-        val filter2 = object : InputTransformation {
-            override val keyboardOptions = null
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-
-        val chain = filter1.then(filter2)
-
-        assertThat(chain.keyboardOptions).isSameInstanceAs(options)
-    }
-
-    @Test
-    fun chainedFilters_takeSecondKeyboardOptions_whenFirstOptionsAreNull() {
-        val options = KeyboardOptions()
-        val filter1 = object : InputTransformation {
-            override val keyboardOptions = null
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-        val filter2 = object : InputTransformation {
-            override val keyboardOptions = options
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-
-        val chain = filter1.then(filter2)
-
-        assertThat(chain.keyboardOptions).isSameInstanceAs(options)
-    }
-
-    @Test
-    fun chainedFilters_takeSecondKeyboardOptions_whenFirstOptionsAreNotNull() {
-        val options1 = KeyboardOptions()
-        val options2 = KeyboardOptions()
-        val filter1 = object : InputTransformation {
-            override val keyboardOptions = options1
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-        val filter2 = object : InputTransformation {
-            override val keyboardOptions = options2
-
-            override fun transformInput(
-                originalValue: TextFieldCharSequence,
-                valueWithChanges: TextFieldBuffer
-            ) {
-            }
-        }
-
-        val chain = filter1.then(filter2)
-
-        assertThat(chain.keyboardOptions).isSameInstanceAs(options2)
-    }
-
-    @Test
-    fun byValue_reverts_whenReturnsCurrent() {
-        val transformation = InputTransformation.byValue { current, _ -> current }
-        val current = TextFieldCharSequence("a")
-        val proposed = TextFieldCharSequence("ab")
-        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
-
-        transformation.transformInput(current, buffer)
-
-        assertThat(buffer.changes.changeCount).isEqualTo(0)
-        assertThat(buffer.toString()).isEqualTo(current.toString())
-    }
-
-    @Test
-    fun byValue_appliesChanges_whenReturnsSameContentAsCurrent() {
-        val transformation = InputTransformation.byValue { _, _ -> "a" }
-        val current = TextFieldCharSequence("a")
-        val proposed = TextFieldCharSequence("ab")
-        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
-
-        transformation.transformInput(current, buffer)
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo(current.toString())
-    }
-
-    @Test
-    fun byValue_noops_whenReturnsProposed() {
-        val transformation = InputTransformation.byValue { _, _ -> "ab" }
-        val current = TextFieldCharSequence("a")
-        val proposed = TextFieldCharSequence("ab")
-        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
-
-        transformation.transformInput(current, buffer)
-
-        assertThat(buffer.changes.changeCount).isEqualTo(0)
-        assertThat(buffer.toString()).isEqualTo(proposed.toString())
-    }
-
-    @Test
-    fun byValue_appliesChanges_whenDifferentCharSequenceReturned() {
-        val transformation = InputTransformation.byValue { _, _ -> "c" }
-        val current = TextFieldCharSequence("a")
-        val proposed = TextFieldCharSequence("ab")
-        val buffer = TextFieldBuffer(sourceValue = current, initialValue = proposed)
-
-        transformation.transformInput(current, buffer)
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("c")
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldBufferTest.kt
deleted file mode 100644
index 3eebbd5..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldBufferTest.kt
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.TextFieldValue
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import java.text.ParseException
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TextFieldBufferTest {
-
-    @Test
-    fun initialSelection() {
-        val state = TextFieldBuffer(TextFieldCharSequence())
-        assertThat(state.selectionInChars).isEqualTo(TextRange(0))
-        assertThat(state.hasSelection).isFalse()
-    }
-
-    @Test
-    fun selectionAdjusted_empty_textInserted() {
-        testSelectionAdjustment("", { append("hello") }, "hello_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtStart_textInsertedAtCursor() {
-        testSelectionAdjustment("_hello", { insert(0, "world") }, "world_hello")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtStart_textInsertedAfterCursor() {
-        testSelectionAdjustment("_hello", { append("world") }, "_helloworld")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtStart_textReplacedAroundCursor() {
-        testSelectionAdjustment("_hello", { replace(0, length, "foo") }, "_foo")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtEnd_textInsertedAtCursor() {
-        testSelectionAdjustment("hello_", { append("world") }, "helloworld_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtEnd_textInsertedBeforeCursor() {
-        testSelectionAdjustment("hello_", { insert(0, "world") }, "worldhello_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorAtEnd_textReplacedAroundCursor() {
-        testSelectionAdjustment("hello_", { replace(0, length, "foo") }, "foo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorInMiddle_textInsertedAtCursor() {
-        testSelectionAdjustment("he_llo", { insert(2, "foo") }, "hefoo_llo")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorInMiddle_textReplacedJustBeforeCursor() {
-        testSelectionAdjustment("he_llo", { replace(0, 2, "foo") }, "foo_llo")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorInMiddle_textReplacedJustAfterCursor() {
-        testSelectionAdjustment("he_llo", { replace(2, 3, "foo") }, "he_foolo")
-    }
-
-    @Test
-    fun selectionAdjusted_whenCursorInMiddle_textReplacedAroundCursor() {
-        testSelectionAdjustment("he_llo", { replace(1, 3, "foo") }, "hfoo_lo")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_allReplacedWithShorter() {
-        testSelectionAdjustment("_hello_", { replace(0, length, "foo") }, "_foo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_allReplacedWithLonger() {
-        testSelectionAdjustment("_hello_", { replace(0, length, "abracadabra") }, "_abracadabra_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideSelection_withLonger() {
-        testSelectionAdjustment("_hello_", { replace(1, 4, "world") }, "_hworldo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideSelection_withShorter() {
-        testSelectionAdjustment("_hello_", { replace(1, 4, "w") }, "_hwo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideFromStart_withLonger() {
-        testSelectionAdjustment("_hello_", { replace(0, 3, "world") }, "_worldlo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideFromStart_withShorter() {
-        testSelectionAdjustment("_hello_", { replace(1, 3, "w") }, "_hwlo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideToEnd_withLonger() {
-        testSelectionAdjustment("_hello_", { replace(length - 3, length, "world") }, "_heworld_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenAllSelected_textReplacedInsideToEnd_withShorter() {
-        testSelectionAdjustment("_hello_", { replace(length - 3, length, "w") }, "_hew_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenInsideSelected_textReplacedJustBeforeSelection() {
-        testSelectionAdjustment("hel_lo_", { replace(0, 3, "world") }, "world_lo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenInsideSelected_textReplacedJustAfterSelection() {
-        testSelectionAdjustment("_he_llo", { replace(2, length, "world") }, "_he_world")
-    }
-
-    @Test
-    fun selectionAdjusted_whenInsideSelected_textReplacedAroundStart() {
-        testSelectionAdjustment("h_ello_", { replace(0, 3, "world") }, "world_lo_")
-    }
-
-    @Test
-    fun selectionAdjusted_whenInsideSelected_textReplacedAroundEnd() {
-        testSelectionAdjustment("_hell_o", { replace(2, length, "world") }, "_he_world")
-    }
-
-    @Test
-    fun resetTo_copiesTextAndSelection() {
-        val expectedValue = TextFieldCharSequence("world", TextRange(5))
-        val state = TextFieldBuffer(
-            initialValue = TextFieldCharSequence("hello", TextRange(2)),
-            sourceValue = expectedValue
-        )
-        state.revertAllChanges()
-        assertThat(state.toTextFieldCharSequence()).isEqualTo(expectedValue)
-        assertThat(state.changes.changeCount).isEqualTo(0)
-    }
-
-    @Test
-    fun placeCursorBeforeCharAt_emptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorBeforeCharAt(-1)
-        }
-
-        buffer.placeCursorBeforeCharAt(0)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorBeforeCharAt(1)
-        }
-    }
-
-    @Test
-    fun placeCursorBeforeCharAt_nonEmptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorBeforeCharAt(-1)
-        }
-
-        buffer.placeCursorBeforeCharAt(0)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        buffer.placeCursorBeforeCharAt(1)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(1))
-
-        buffer.placeCursorBeforeCharAt(5)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorBeforeCharAt(6)
-        }
-    }
-
-    @Test
-    fun placeCursorAfterCharAt_emptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-
-        buffer.placeCursorAfterCharAt(-1)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorAfterCharAt(0)
-        }
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorAfterCharAt(1)
-        }
-    }
-
-    @Test
-    fun placeCursorAfterCharAt_nonEmptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
-
-        buffer.placeCursorAfterCharAt(-1)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        buffer.placeCursorAfterCharAt(0)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(1))
-
-        buffer.placeCursorAfterCharAt(1)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(2))
-
-        buffer.placeCursorAfterCharAt(4)
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.placeCursorAfterCharAt(5)
-        }
-    }
-
-    @Test
-    fun selectCharsIn_emptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-
-        buffer.selectCharsIn(TextRange(0))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.selectCharsIn(TextRange(0, 1))
-        }
-    }
-
-    @Test
-    fun selectCharsIn_nonEmptyBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
-
-        buffer.selectCharsIn(TextRange(0))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0))
-
-        buffer.selectCharsIn(TextRange(0, 1))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0, 1))
-
-        buffer.selectCharsIn(TextRange(0, 5))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(0, 5))
-
-        buffer.selectCharsIn(TextRange(4, 5))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(4, 5))
-
-        buffer.selectCharsIn(TextRange(5, 5))
-        assertThat(buffer.selectionInChars).isEqualTo(TextRange(5, 5))
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.selectCharsIn(TextRange(5, 6))
-        }
-
-        assertFailsWith<IllegalArgumentException> {
-            buffer.selectCharsIn(TextRange(6, 6))
-        }
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenChanged() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("world")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("world")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenPrefixChanged() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("1ello")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("1ello")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenPrefixAdded() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("1hello")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("1hello")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenPrefixRemoved() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("ello")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("ello")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenSuffixChanged() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("hell1")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("hell1")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(4, 5))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenSuffixAdded() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("hello1")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("hello1")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(5))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(5, 6))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenSuffixRemoved() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("hell")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("hell")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(4))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleChanged_once() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("h1llo")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("h1llo")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 2))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 2))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleAdded_once() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("he1llo")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("he1llo")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2, 3))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleRemoved_once() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("helo")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("helo")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2, 3))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleChanged_multiple() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("h1l2o")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("h1l2o")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 4))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleAdded_multiple() {
-        val text = "hello"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("he1ll2o")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("he1ll2o")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(2, 6))
-    }
-
-    @Test
-    fun setTextIfChanged_updatesText_whenMiddleRemoved_multiple() {
-        val text = "abcde"
-        val buffer = TextFieldBuffer(TextFieldCharSequence(text))
-
-        buffer.setTextIfChanged("ace")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.toString()).isEqualTo("ace")
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 2))
-    }
-
-    @Test
-    fun setTextIfChanged_doesNotUpdateTextIfEqual() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
-
-        buffer.setTextIfChanged("hello")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(0)
-    }
-
-    @Test
-    fun setTextIfChanged_doesNotUpdateTextIfEqual_afterChange() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("hello"))
-        buffer.append(" world")
-
-        buffer.setTextIfChanged("hello world")
-
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-    }
-
-    @Test
-    fun charAt_throws_whenEmpty() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence())
-
-        assertFailsWith<IndexOutOfBoundsException> {
-            buffer.charAt(0)
-        }
-    }
-
-    @Test
-    fun charAt_throws_whenOutOfBounds() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("a"))
-
-        assertFailsWith<IndexOutOfBoundsException> {
-            buffer.charAt(1)
-        }
-        assertFailsWith<IndexOutOfBoundsException> {
-            buffer.charAt(-1)
-        }
-    }
-
-    @Test
-    fun charAt_returnsChars() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence("ab"))
-        assertThat(buffer.charAt(0)).isEqualTo('a')
-        assertThat(buffer.charAt(1)).isEqualTo('b')
-    }
-
-    @Test
-    fun asCharSequence_isViewOfBuffer() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence())
-        val charSequence = buffer.asCharSequence()
-
-        assertThat(charSequence.toString()).isEmpty()
-
-        buffer.append("hello")
-
-        assertThat(charSequence.toString()).isEqualTo("hello")
-    }
-
-    @Test
-    fun replace_withSubSequence_crossedOffsets() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        val error = assertFailsWith<IllegalArgumentException> {
-            buffer.replace(0, 0, "hi", 2, 0)
-        }
-        assertThat(error.message).isEqualTo("Expected textStart=2 <= textEnd=0")
-    }
-
-    @Test
-    fun replace_withSubSequence_startTooSmall() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        assertFailsWith<IllegalArgumentException> {
-            buffer.replace(0, 0, "hi", -1, 0)
-        }
-    }
-
-    @Test
-    fun replace_withSubSequence_endTooBig() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        assertFailsWith<IndexOutOfBoundsException> {
-            buffer.replace(0, 0, "hi", 2, 3)
-        }
-    }
-
-    @Test
-    fun replace_withSubSequence_empty() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        buffer.replace(0, 0, "hi", 0, 0)
-        assertThat(buffer.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun replace_withSubSequence_singleCharFromStart() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        buffer.replace(0, 0, "hi", 0, 1)
-        assertThat(buffer.toString()).isEqualTo("h")
-    }
-
-    @Test
-    fun replace_withSubSequence_singleCharFromEnd() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        buffer.replace(0, 0, "hi", 1, 2)
-        assertThat(buffer.toString()).isEqualTo("i")
-    }
-
-    @Test
-    fun replace_withSubSequence_middle() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        buffer.replace(0, 0, "abcd", 1, 3)
-        assertThat(buffer.toString()).isEqualTo("bc")
-    }
-
-    @Test
-    fun replace_withSubSequence_full() {
-        val buffer = TextFieldBuffer(TextFieldCharSequence(""))
-        buffer.replace(0, 0, "abcd", 0, 4)
-        assertThat(buffer.toString()).isEqualTo("abcd")
-    }
-
-    @Test
-    fun findCommonPrefixAndSuffix_works() {
-        assertCommonPrefixAndSuffix("", "", null)
-        assertCommonPrefixAndSuffix("a", "a", null)
-        assertCommonPrefixAndSuffix("abc", "abc", null)
-        assertCommonPrefixAndSuffix("", "b", TextRange(0) to TextRange(0, 1))
-        assertCommonPrefixAndSuffix("a", "", TextRange(0, 1) to TextRange(0))
-        assertCommonPrefixAndSuffix("ab", "ac", TextRange(1, 2) to TextRange(1, 2))
-        assertCommonPrefixAndSuffix("abb", "ac", TextRange(1, 3) to TextRange(1, 2))
-        assertCommonPrefixAndSuffix("ab", "acc", TextRange(1, 2) to TextRange(1, 3))
-        assertCommonPrefixAndSuffix("az", "bz", TextRange(0, 1) to TextRange(0, 1))
-        assertCommonPrefixAndSuffix("cba", "za", TextRange(0, 2) to TextRange(0, 1))
-        assertCommonPrefixAndSuffix("za", "cba", TextRange(0, 1) to TextRange(0, 2))
-        assertCommonPrefixAndSuffix("aoz", "apz", TextRange(1, 2) to TextRange(1, 2))
-        assertCommonPrefixAndSuffix("amnoz", "az", TextRange(1, 4) to TextRange(1, 1))
-        assertCommonPrefixAndSuffix("az", "amnoz", TextRange(1, 1) to TextRange(1, 4))
-        assertCommonPrefixAndSuffix("amnoz", "axz", TextRange(1, 4) to TextRange(1, 2))
-        assertCommonPrefixAndSuffix("axz", "amnoz", TextRange(1, 2) to TextRange(1, 4))
-    }
-
-    /** Tests of private testing helper code. */
-    @Test
-    fun testConvertTextFieldValueToAndFromString() {
-        assertThat("".parseAsTextEditState()).isEqualTo(TextFieldCharSequence())
-        assertThat("hello".parseAsTextEditState()).isEqualTo(TextFieldCharSequence("hello"))
-        assertThat("_hello".parseAsTextEditState()).isEqualTo(TextFieldCharSequence("hello"))
-        assertThat("h_ello".parseAsTextEditState())
-            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(1)))
-        assertThat("hello_".parseAsTextEditState())
-            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(5)))
-        assertThat("_hello_".parseAsTextEditState())
-            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(0, 5)))
-        assertThat("he__llo".parseAsTextEditState())
-            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(2)))
-        assertThat("he_l_lo".parseAsTextEditState())
-            .isEqualTo(TextFieldCharSequence("hello", selection = TextRange(2, 3)))
-        assertFailsWith<ParseException> {
-            "_he_llo_".parseAsTextEditState()
-        }
-
-        listOf("", "_hello", "h_ello", "hello_", "_hello_", "he_ll_o").forEach {
-            val value = it.parseAsTextEditState()
-            assertThat(value.toParsableString()).isEqualTo(it)
-        }
-    }
-
-    private fun testSelectionAdjustment(
-        initial: String,
-        transform: TextFieldBuffer.() -> Unit,
-        expected: String
-    ) {
-        val state = TextFieldBuffer(initial.parseAsTextEditState())
-        state.transform()
-        assertThat(state.toTextFieldCharSequence().toParsableString()).isEqualTo(expected)
-    }
-
-    /**
-     * Parses this string into a [TextFieldValue], replacing a single underscore with the cursor, or
-     * two underscores with a selection.
-     */
-    private fun String.parseAsTextEditState(): TextFieldCharSequence {
-        var firstMark = -1
-        var secondMark = -1
-        val source = this
-        val text = buildString {
-            source.forEachIndexed { i, char ->
-                if (char == '_') {
-                    when {
-                        firstMark == -1 -> firstMark = i
-                        secondMark == -1 -> secondMark = i - 1
-                        else -> throw ParseException("Unexpected underscore in \"$this\"", i)
-                    }
-                } else {
-                    append(char)
-                }
-            }
-        }
-
-        return TextFieldCharSequence(
-            text = text,
-            selection = when {
-                firstMark == -1 -> TextRange.Zero
-                secondMark == -1 -> TextRange(firstMark)
-                else -> TextRange(firstMark, secondMark)
-            }
-        )
-    }
-
-    private fun TextFieldCharSequence.toParsableString(): String = buildString {
-        append(this@toParsableString)
-        if (isNotEmpty()) {
-            insert(selectionInChars.min, '_')
-            if (!selectionInChars.collapsed) {
-                insert(selectionInChars.max + 1, '_')
-            }
-        }
-    }
-
-    private fun assertCommonPrefixAndSuffix(
-        a: CharSequence,
-        b: CharSequence,
-        expectedRanges: Pair<TextRange, TextRange>?
-    ) {
-        var result: Pair<TextRange, TextRange>? = null
-        findCommonPrefixAndSuffix(a, b) { aStart, aEnd, bStart, bEnd ->
-            result = Pair(TextRange(aStart, aEnd), TextRange(bStart, bEnd))
-        }
-        assertWithMessage("Expected findCommonPrefixAndSuffix(\"$a\", \"$b\") to report")
-            .that(result).isEqualTo(expectedRanges)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequenceTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequenceTest.kt
deleted file mode 100644
index ed73ebc..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequenceTest.kt
+++ /dev/null
@@ -1,121 +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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TextFieldCharSequenceTest {
-    private val defaultSaverScope = SaverScope { true }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun throws_exception_for_negative_selection() {
-        TextFieldCharSequence(text = "", selection = TextRange(-1))
-    }
-
-    @Test
-    fun aligns_selection_to_the_text_length() {
-        val text = "a"
-        val textFieldValue =
-            TextFieldCharSequence(text = text, selection = TextRange(text.length + 1))
-        assertThat(textFieldValue.selectionInChars.collapsed).isTrue()
-        assertThat(textFieldValue.selectionInChars.max).isEqualTo(textFieldValue.length)
-    }
-
-    @Test
-    fun keep_selection_that_is_less_than_text_length() {
-        val text = "a bc"
-        val selection = TextRange(0, "a".length)
-
-        val textFieldValue = TextFieldCharSequence(text = text, selection = selection)
-
-        assertThat(textFieldValue.toString()).isEqualTo(text)
-        assertThat(textFieldValue.selectionInChars).isEqualTo(selection)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun throws_exception_for_negative_composition() {
-        TextEditState(text = "", composition = TextRange(-1))
-    }
-
-    @Test
-    fun aligns_composition_to_text_length() {
-        val text = "a"
-        val textFieldValue = TextEditState(text = text, composition = TextRange(text.length + 1))
-        assertThat(textFieldValue.compositionInChars?.collapsed).isTrue()
-        assertThat(textFieldValue.compositionInChars?.max).isEqualTo(textFieldValue.length)
-    }
-
-    @Test
-    fun keep_composition_that_is_less_than_text_length() {
-        val text = "a bc"
-        val composition = TextRange(0, "a".length)
-
-        val textFieldValue = TextEditState(text = text, composition = composition)
-
-        assertThat(textFieldValue.toString()).isEqualTo(text)
-        assertThat(textFieldValue.compositionInChars).isEqualTo(composition)
-    }
-
-    @Test
-    fun equals_returns_true_for_same_instance() {
-        val textFieldValue = TextFieldCharSequence(
-            text = "a",
-            selection = TextRange(1),
-            composition = TextRange(2)
-        )
-
-        assertThat(textFieldValue).isEqualTo(textFieldValue)
-    }
-
-    @Test
-    fun equals_returns_true_for_equivalent_object() {
-        val textFieldValue = TextFieldCharSequence(
-            text = "a",
-            selection = TextRange(1),
-            composition = TextRange(2)
-        )
-
-        assertThat(
-            TextFieldCharSequence(
-                textFieldValue,
-                textFieldValue.selectionInChars,
-                textFieldValue.compositionInChars
-            )
-        ).isEqualTo(textFieldValue)
-    }
-
-    @Test
-    fun text_and_selection_parameter_constructor_has_null_composition() {
-        val textFieldValue = TextFieldCharSequence(
-            text = "a",
-            selection = TextRange(1)
-        )
-
-        assertThat(textFieldValue.compositionInChars).isNull()
-    }
-
-    private fun TextEditState(text: String, composition: TextRange) =
-        TextFieldCharSequence(text, selection = TextRange.Zero, composition = composition)
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt
deleted file mode 100644
index ef655cf..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.internal.commitText
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertNotNull
-import org.junit.Test
-
-@OptIn(ExperimentalFoundationApi::class)
-class TextFieldStateSaverTest {
-
-    @Test
-    fun savesAndRestoresTextAndSelection() {
-        val state = TextFieldState("hello, world", initialSelectionInChars = TextRange(0, 5))
-
-        val saved = with(TextFieldState.Saver) { TestSaverScope.save(state) }
-        assertNotNull(saved)
-        val restoredState = TextFieldState.Saver.restore(saved)
-
-        assertNotNull(restoredState)
-        assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-        assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun savesAndRestoresUndo() {
-        val state = TextFieldState("hello, world", initialSelectionInChars = TextRange(0, 5))
-
-        state.editAsUser(null) {
-            commitText("hi", 1)
-        }
-
-        val saved = with(TextFieldState.Saver) { TestSaverScope.save(state) }
-        assertNotNull(saved)
-        val restoredState = TextFieldState.Saver.restore(saved)
-
-        assertNotNull(restoredState)
-        assertThat(restoredState.text.toString()).isEqualTo("hi, world")
-        assertThat(restoredState.undoState.canUndo).isTrue()
-        restoredState.undoState.undo()
-        assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-        assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
-    }
-
-    private object TestSaverScope : SaverScope {
-        override fun canBeSaved(value: Any): Boolean = true
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateTest.kt
deleted file mode 100644
index d46c060..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateTest.kt
+++ /dev/null
@@ -1,638 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.SnapshotStateObserver
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class, ExperimentalCoroutinesApi::class)
-@RunWith(JUnit4::class)
-class TextFieldStateTest {
-
-    private val state = TextFieldState()
-
-    @Test
-    fun defaultInitialTextAndSelection() {
-        val state = TextFieldState()
-        assertThat(state.text.toString()).isEqualTo("")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
-    }
-
-    @Test
-    fun customInitialTextAndDefaultSelection() {
-        val state = TextFieldState(initialText = "hello")
-        assertThat(state.text.toString()).isEqualTo("hello")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun customInitialTextAndSelection() {
-        val state = TextFieldState(initialText = "hello", initialSelectionInChars = TextRange(0, 1))
-        assertThat(state.text.toString()).isEqualTo("hello")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 1))
-    }
-
-    @Test
-    fun edit_doesNotChange_whenThrows() {
-        class ExpectedException : RuntimeException()
-
-        assertFailsWith<ExpectedException> {
-            state.edit {
-                replace(0, 0, "hello")
-                throw ExpectedException()
-            }
-        }
-
-        assertThat(state.text.toString()).isEmpty()
-    }
-
-    @Test
-    fun edit_invalidates_whenSelectionChanged() = runTestWithSnapshotsThenCancelChildren {
-        val text = "hello"
-        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
-        var invalidationCount = 0
-        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
-        val observeState: () -> Unit = { state.text }
-        observer.start()
-        try {
-            observer.observeReads(
-                scope = Unit,
-                onValueChangedForScope = {
-                    invalidationCount++
-                    observeState()
-                },
-                block = observeState
-            )
-            assertThat(invalidationCount).isEqualTo(0)
-
-            // Act.
-            state.edit {
-                selectCharsIn(TextRange(0, length))
-            }
-            advanceUntilIdle()
-            runCurrent()
-
-            // Assert.
-            assertThat(invalidationCount).isEqualTo(1)
-        } finally {
-            observer.stop()
-        }
-    }
-
-    @Test
-    fun edit_invalidates_whenTextChanged() = runTestWithSnapshotsThenCancelChildren {
-        val text = "hello"
-        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
-        var invalidationCount = 0
-        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
-        val observeState: () -> Unit = { state.text }
-        observer.start()
-        try {
-            observer.observeReads(
-                scope = Unit,
-                onValueChangedForScope = {
-                    invalidationCount++
-                    observeState()
-                },
-                block = observeState
-            )
-            assertThat(invalidationCount).isEqualTo(0)
-
-            // Act.
-            state.edit {
-                append("1")
-            }
-            advanceUntilIdle()
-            runCurrent()
-
-            // Assert.
-            assertThat(invalidationCount).isEqualTo(1)
-        } finally {
-            observer.stop()
-        }
-    }
-
-    @Test
-    fun edit_doesNotInvalidate_whenNoChangesMade() = runTestWithSnapshotsThenCancelChildren {
-        val text = "hello"
-        val state = TextFieldState(text, initialSelectionInChars = TextRange(0))
-        var invalidationCount = 0
-        val observer = SnapshotStateObserver(onChangedExecutor = { it() })
-        val observeState: () -> Unit = { state.text }
-        observer.start()
-        try {
-            observer.observeReads(
-                scope = Unit,
-                onValueChangedForScope = {
-                    invalidationCount++
-                    observeState()
-                },
-                block = observeState
-            )
-            assertThat(invalidationCount).isEqualTo(0)
-
-            // Act.
-            state.edit {
-                // Change the selection but restore it before returning.
-                val originalSelection = selectionInChars
-                selectCharsIn(TextRange(0, length))
-                selectCharsIn(originalSelection)
-
-                // This will be a no-op too.
-                setTextIfChanged(text)
-            }
-            advanceUntilIdle()
-            runCurrent()
-
-            // Assert.
-            assertThat(invalidationCount).isEqualTo(0)
-        } finally {
-            observer.stop()
-        }
-    }
-
-    @Test
-    fun edit_replace_changesValueInPlace() {
-        state.edit {
-            replace(0, 0, "hello")
-            assertThat(toString()).isEqualTo("hello")
-            assertThat(length).isEqualTo(5)
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_replace_changesStateAfterReturn() {
-        state.edit {
-            replace(0, 0, "hello")
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("hello")
-    }
-
-    @Test
-    fun edit_replace_doesNotChangeStateUntilReturn() {
-        state.edit {
-            replace(0, 0, "hello")
-            assertThat(state.text.toString()).isEmpty()
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_multipleOperations() {
-        state.edit {
-            replace(0, 0, "hello")
-            replace(5, 5, "world")
-            replace(5, 5, " ")
-            replace(6, 11, "Compose")
-            assertThat(toString()).isEqualTo("hello Compose")
-            assertThat(state.text.toString()).isEmpty()
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("hello Compose")
-    }
-
-    @Test
-    fun edit_placeCursorAtEnd() {
-        state.edit {
-            replace(0, 0, "hello")
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun edit_placeCursorBeforeChar_simpleCase() {
-        state.edit {
-            replace(0, 0, "hello")
-            placeCursorBeforeCharAt(2)
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun edit_placeCursorBeforeChar_throws_whenInvalid() {
-        state.edit {
-            assertFailsWith<IllegalArgumentException> {
-                placeCursorBeforeCharAt(500)
-            }
-            assertFailsWith<IllegalArgumentException> {
-                placeCursorBeforeCharAt(-1)
-            }
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_placeCursorBeforeCodepoint_simpleCase() {
-        state.edit {
-            replace(0, 0, "hello")
-            placeCursorBeforeCodepointAt(2)
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun edit_placeCursorBeforeCodepoint_throws_whenInvalid() {
-        state.edit {
-            assertFailsWith<IllegalArgumentException> {
-                placeCursorBeforeCodepointAt(500)
-            }
-            assertFailsWith<IllegalArgumentException> {
-                placeCursorBeforeCodepointAt(-1)
-            }
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_selectAll() {
-        state.edit {
-            replace(0, 0, "hello")
-            selectAll()
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun edit_selectChars_simpleCase() {
-        state.edit {
-            replace(0, 0, "hello")
-            selectCharsIn(TextRange(1, 4))
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 4))
-    }
-
-    @Test
-    fun edit_selectChars_throws_whenInvalid() {
-        state.edit {
-            assertFailsWith<IllegalArgumentException> {
-                selectCharsIn(TextRange(500, 501))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCharsIn(TextRange(-1, 500))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCharsIn(TextRange(500, -1))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCharsIn(TextRange(-500, -1))
-            }
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_selectCodepoints_simpleCase() {
-        state.edit {
-            replace(0, 0, "hello")
-            selectCodepointsIn(TextRange(1, 4))
-        }
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 4))
-    }
-
-    @Test
-    fun edit_selectCodepoints_throws_whenInvalid() {
-        state.edit {
-            assertFailsWith<IllegalArgumentException> {
-                selectCodepointsIn(TextRange(500, 501))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCodepointsIn(TextRange(-1, 500))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCodepointsIn(TextRange(500, -1))
-            }
-            assertFailsWith<IllegalArgumentException> {
-                selectCodepointsIn(TextRange(-500, -1))
-            }
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun edit_afterEdit() {
-        state.edit {
-            replace(0, 0, "hello")
-            placeCursorAtEnd()
-        }
-        state.edit {
-            assertThat(toString()).isEqualTo("hello")
-            replace(5, 5, " world")
-            assertThat(toString()).isEqualTo("hello world")
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("hello world")
-    }
-
-    @Test
-    fun append_char() {
-        state.edit {
-            append('c')
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("c")
-    }
-
-    @Test
-    fun append_charSequence() {
-        state.edit {
-            append("hello")
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("hello")
-    }
-
-    @Test
-    fun append_charSequence_range() {
-        state.edit {
-            append("hello world", 0, 5)
-            placeCursorAtEnd()
-        }
-        assertThat(state.text.toString()).isEqualTo("hello")
-    }
-
-    @Test
-    fun setTextAndPlaceCursorAtEnd_works() {
-        state.setTextAndPlaceCursorAtEnd("Hello")
-        assertThat(state.text.toString()).isEqualTo("Hello")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun setTextAndSelectAll_works() {
-        state.setTextAndSelectAll("Hello")
-        assertThat(state.text.toString()).isEqualTo("Hello")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun replace_changesAreTracked() {
-        val state = TextFieldState("hello world")
-        state.edit {
-            replace(6, 11, "Compose")
-            assertThat(toString()).isEqualTo("hello Compose")
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 13))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6, 11))
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun appendChar_changesAreTracked() {
-        val state = TextFieldState("hello ")
-        state.edit {
-            append('c')
-            assertThat(toString()).isEqualTo("hello c")
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 7))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun appendCharSequence_changesAreTracked() {
-        val state = TextFieldState("hello ")
-        state.edit {
-            append("world")
-            assertThat(toString()).isEqualTo("hello world")
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 11))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun appendCharSequenceRange_changesAreTracked() {
-        val state = TextFieldState("hello ")
-        state.edit {
-            append("hello world", 6, 11)
-            assertThat(toString()).isEqualTo("hello world")
-            assertThat(changes.changeCount).isEqualTo(1)
-            assertThat(changes.getRange(0)).isEqualTo(TextRange(6, 11))
-            assertThat(changes.getOriginalRange(0)).isEqualTo(TextRange(6))
-            placeCursorAtEnd()
-        }
-    }
-
-    @Test
-    fun forEachValues_fires_immediately() = runTestWithSnapshotsThenCancelChildren {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
-        val texts = mutableListOf<TextFieldCharSequence>()
-
-        launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
-        }
-
-        assertThat(texts).hasSize(1)
-        assertThat(texts.single()).isSameInstanceAs(state.text)
-        assertThat(texts.single().toString()).isEqualTo("hello")
-        assertThat(texts.single().selectionInChars).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun forEachValue_fires_whenTextChanged() = runTestWithSnapshotsThenCancelChildren {
-        val state = TextFieldState(initialSelectionInChars = TextRange(0))
-        val texts = mutableListOf<TextFieldCharSequence>()
-        val initialText = state.text
-
-        launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
-        }
-
-        state.edit {
-            append("hello")
-            placeCursorBeforeCharAt(0)
-        }
-
-        assertThat(texts).hasSize(2)
-        assertThat(texts.last()).isSameInstanceAs(state.text)
-        assertThat(texts.last().toString()).isEqualTo("hello")
-        assertThat(texts.last().selectionInChars).isEqualTo(initialText.selectionInChars)
-    }
-
-    @Test
-    fun forEachValue_fires_whenSelectionChanged() = runTestWithSnapshotsThenCancelChildren {
-        val state = TextFieldState("hello", initialSelectionInChars = TextRange(0))
-        val texts = mutableListOf<TextFieldCharSequence>()
-
-        launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
-        }
-
-        state.edit {
-            placeCursorAtEnd()
-        }
-
-        assertThat(texts).hasSize(2)
-        assertThat(texts.last()).isSameInstanceAs(state.text)
-        assertThat(texts.last().toString()).isEqualTo("hello")
-        assertThat(texts.last().selectionInChars).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun forEachValue_firesTwice_whenEditCalledTwice() = runTestWithSnapshotsThenCancelChildren {
-        val state = TextFieldState()
-        val texts = mutableListOf<TextFieldCharSequence>()
-
-        launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
-        }
-
-        state.edit {
-            append("hello")
-            placeCursorAtEnd()
-        }
-
-        state.edit {
-            append(" world")
-            placeCursorAtEnd()
-        }
-
-        assertThat(texts).hasSize(3)
-        assertThat(texts[1].toString()).isEqualTo("hello")
-        assertThat(texts[2]).isSameInstanceAs(state.text)
-        assertThat(texts[2].toString()).isEqualTo("hello world")
-    }
-
-    @Test
-    fun forEachValue_firesOnce_whenMultipleChangesMadeInSingleEdit() =
-        runTestWithSnapshotsThenCancelChildren {
-            val state = TextFieldState()
-            val texts = mutableListOf<TextFieldCharSequence>()
-
-            launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
-            }
-
-            state.edit {
-                append("hello")
-                append(" world")
-                placeCursorAtEnd()
-            }
-
-            assertThat(texts.last()).isSameInstanceAs(state.text)
-            assertThat(texts.last().toString()).isEqualTo("hello world")
-        }
-
-    @Test
-    fun forEachValue_fires_whenChangeMadeInSnapshotIsApplied() =
-        runTestWithSnapshotsThenCancelChildren {
-            val state = TextFieldState()
-            val texts = mutableListOf<TextFieldCharSequence>()
-
-            launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
-            }
-
-            val snapshot = Snapshot.takeMutableSnapshot()
-            snapshot.enter {
-                state.edit {
-                    append("hello")
-                    placeCursorAtEnd()
-                }
-                assertThat(texts.isEmpty())
-            }
-            assertThat(texts.isEmpty())
-
-            snapshot.apply()
-            snapshot.dispose()
-
-            assertThat(texts.last()).isSameInstanceAs(state.text)
-        }
-
-    @Test
-    fun forEachValue_notFired_whenChangeMadeInSnapshotThenDisposed() =
-        runTestWithSnapshotsThenCancelChildren {
-            val state = TextFieldState()
-            val texts = mutableListOf<TextFieldCharSequence>()
-
-            launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
-            }
-
-            val snapshot = Snapshot.takeMutableSnapshot()
-            snapshot.enter {
-                state.edit {
-                    append("hello")
-                    placeCursorAtEnd()
-                }
-            }
-            snapshot.dispose()
-
-            // Only contains initial value.
-            assertThat(texts).hasSize(1)
-            assertThat(texts.single().toString()).isEmpty()
-        }
-
-    @Test
-    fun forEachValue_cancelsPreviousHandler_whenChangeMadeWhileSuspended() =
-        runTestWithSnapshotsThenCancelChildren {
-            val state = TextFieldState()
-            val texts = mutableListOf<TextFieldCharSequence>()
-
-            launch(Dispatchers.Unconfined) {
-                state.forEachTextValue {
-                    texts += it
-                    awaitCancellation()
-                }
-            }
-
-            state.setTextAndPlaceCursorAtEnd("hello")
-            state.setTextAndPlaceCursorAtEnd("world")
-
-            assertThat(texts.map { it.toString() })
-                .containsExactly("", "hello", "world")
-                .inOrder()
-        }
-
-    private fun runTestWithSnapshotsThenCancelChildren(testBody: suspend TestScope.() -> Unit) {
-        val globalWriteObserverHandle = Snapshot.registerGlobalWriteObserver {
-            // This is normally done by the compose runtime.
-            Snapshot.sendApplyNotifications()
-        }
-        try {
-            runTest {
-                testBody()
-                coroutineContext.job.cancelChildren()
-            }
-        } finally {
-            globalWriteObserverHandle.dispose()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
deleted file mode 100644
index 5de9db6..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ChangeTrackerTest {
-
-    @Test
-    fun initialInsert() {
-        val buffer = SimpleBuffer()
-
-        buffer.append("hello")
-
-        assertThat(buffer.toString()).isEqualTo("hello")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 5))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun deleteAll() {
-        val buffer = SimpleBuffer("hello")
-
-        buffer.replace("hello", "")
-
-        assertThat(buffer.toString()).isEqualTo("")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 0))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
-    }
-
-    @Test
-    fun multipleDiscontinuousChanges() {
-        val buffer = SimpleBuffer("hello world")
-
-        buffer.replace("world", "Compose")
-        buffer.replace("hello", "goodbye")
-
-        assertThat(buffer.toString()).isEqualTo("goodbye Compose")
-        assertThat(buffer.changes.changeCount).isEqualTo(2)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 7))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 5))
-        assertThat(buffer.changes.getRange(1)).isEqualTo(TextRange(8, 15))
-        assertThat(buffer.changes.getOriginalRange(1)).isEqualTo(TextRange(6, 11))
-    }
-
-    @Test
-    fun twoAppends() {
-        val buffer = SimpleBuffer()
-
-        buffer.append("foo")
-        buffer.append("bar")
-
-        assertThat(buffer.toString()).isEqualTo("foobar")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun threeAppends() {
-        val buffer = SimpleBuffer()
-
-        buffer.append("foo")
-        buffer.append("bar")
-        buffer.append("baz")
-
-        assertThat(buffer.toString()).isEqualTo("foobarbaz")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 9))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun replaceWithReversedIndices() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace(2, 0, "e")
-
-        assertThat(buffer.toString()).isEqualTo("ecd")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 2))
-    }
-
-    @Test
-    fun multipleAdjacentReplaces_whenPerformedInOrder_replacementsShorter() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("ab", "e") // ecd
-        buffer.replace("cd", "f")
-
-        assertThat(buffer.toString()).isEqualTo("ef")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 2))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
-    }
-
-    @Test
-    fun multipleAdjacentReplaces_whenPerformedInOrder_replacementsLonger() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("ab", "efg") // efgcd
-        buffer.replace("cd", "hij")
-
-        assertThat(buffer.toString()).isEqualTo("efghij")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
-    }
-
-    @Test
-    fun multipleAdjacentReplaces_whenPerformedInReverseOrder_replacementsShorter() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("cd", "f") // abf
-        buffer.replace("ab", "e")
-
-        assertThat(buffer.toString()).isEqualTo("ef")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 2))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
-    }
-
-    @Test
-    fun multipleAdjacentReplaces_whenPerformedInReverseOrder_replacementsLonger() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("cd", "efg") // abhij
-        buffer.replace("ab", "hij")
-
-        assertThat(buffer.toString()).isEqualTo("hijefg")
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 6))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 4))
-    }
-
-    @Test
-    fun multiplePartiallyOverlappingChanges_atStart() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("bc", "ef") // aefd
-        buffer.replace("ae", "gh")
-
-        assertThat(buffer.toString()).isEqualTo("ghfd")
-        // Overlapping changes are merged.
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 3))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 3))
-    }
-
-    @Test
-    fun multiplePartiallyOverlappingChanges_atEnd() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("bc", "ef") // aefd
-        buffer.replace("fd", "gh")
-
-        assertThat(buffer.toString()).isEqualTo("aegh")
-        // Overlapping changes are merged.
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 4))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 4))
-    }
-
-    @Test
-    fun multipleFullyOverlappingChanges() {
-        val buffer = SimpleBuffer("abcd")
-
-        buffer.replace("bc", "ef") // aefd
-        buffer.replace("ef", "gh")
-
-        assertThat(buffer.toString()).isEqualTo("aghd")
-        // Overlapping changes are merged.
-        assertThat(buffer.changes.changeCount).isEqualTo(1)
-        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(1, 3))
-        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(1, 3))
-    }
-
-    private class SimpleBuffer(initialText: String = "") {
-        private val builder = StringBuilder(initialText)
-        val changes = ChangeTracker()
-
-        fun append(text: String) {
-            changes.trackChange(builder.length, builder.length, text.length)
-            builder.append(text)
-        }
-
-        fun replace(substring: String, text: String) {
-            val start = builder.indexOf(substring)
-            if (start != -1) {
-                val end = start + substring.length
-                changes.trackChange(start, end, text.length)
-                builder.replace(start, end, text)
-            }
-        }
-
-        fun replace(start: Int, end: Int, text: String) {
-            changes.trackChange(start, end, text.length)
-            builder.replace(minOf(start, end), maxOf(start, end), text)
-        }
-
-        override fun toString(): String = builder.toString()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/CommitTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/CommitTextCommandTest.kt
deleted file mode 100644
index 50b5115..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/CommitTextCommandTest.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class CommitTextCommandTest {
-
-    @Test
-    fun test_insert_empty() {
-        val eb = EditingBuffer("", TextRange.Zero)
-
-        eb.commitText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("X")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_cursor_tail() {
-        val eb = EditingBuffer("A", TextRange(1))
-
-        eb.commitText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AX")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_cursor_head() {
-        val eb = EditingBuffer("A", TextRange(1))
-
-        eb.commitText("X", 0)
-
-        assertThat(eb.toString()).isEqualTo("AX")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.commitText("X", 2)
-
-        assertThat(eb.toString()).isEqualTo("AXBCDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4))
-
-        eb.commitText("X", -2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.commitText("", 0)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.commitText("", 1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.commitText("", 2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4))
-
-        eb.commitText("", -2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_cancel_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 4) // Mark "BCD" as composition
-        eb.commitText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_replace_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 4)) // select "BCD"
-
-        eb.commitText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_composition_and_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 3)) // select "BC"
-
-        eb.setComposition(2, 4) // Mark "CD" as composition
-        eb.commitText("X", 1)
-
-        // If composition and selection exists at the same time, replace composition and cancel
-        // selection and place cursor.
-        assertThat(eb.toString()).isEqualTo("ABXE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_cursor_position_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(5))
-
-        eb.commitText("X", -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDEX")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_cursor_position_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(5))
-
-        eb.commitText("X", 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDEX")
-        assertThat(eb.cursor).isEqualTo(6)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextCommandTest.kt
deleted file mode 100644
index dfe37fc..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextCommandTest.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class DeleteSurroundingTextCommandTest {
-
-    @Test
-    fun test_delete_after() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.deleteSurroundingText(0, 1)
-
-        assertThat(eb.toString()).isEqualTo("ACDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.deleteSurroundingText(1, 0)
-
-        assertThat(eb.toString()).isEqualTo("BCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_after_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(2))
-
-        eb.deleteSurroundingText(0, 2)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(2, 0)
-
-        assertThat(eb.toString()).isEqualTo("ADE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(2, 2)
-
-        assertThat(eb.toString()).isEqualTo("A")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_selection_preserve() {
-        val eb = EditingBuffer("ABCDE", TextRange(2, 4))
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ACD")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(1000, 0)
-
-        assertThat(eb.toString()).isEqualTo("DE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_after_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(0, 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABC")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.deleteSurroundingText(1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_composition_no_intersection_preceding_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(0, 1)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(1)
-    }
-
-    @Test
-    fun test_delete_composition_no_intersection_trailing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(4, 5)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun test_delete_composition_intersection_preceding_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(0, 3)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_delete_composition_intersection_trailing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(3, 5)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun test_delete_covered_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(2, 3)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_composition_covered() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.setComposition(0, 5)
-
-        eb.deleteSurroundingText(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun throws_whenLengthBeforeInvalid() {
-        val eb = EditingBuffer("", TextRange(0))
-        val error = assertFailsWith<IllegalArgumentException> {
-            eb.deleteSurroundingText(lengthBeforeCursor = -42, lengthAfterCursor = 0)
-        }
-        assertThat(error).hasMessageThat().contains("-42")
-    }
-
-    @Test
-    fun throws_whenLengthAfterInvalid() {
-        val eb = EditingBuffer("", TextRange(0))
-        val error = assertFailsWith<IllegalArgumentException> {
-            eb.deleteSurroundingText(lengthBeforeCursor = 0, lengthAfterCursor = -42)
-        }
-        assertThat(error).hasMessageThat().contains("-42")
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt
deleted file mode 100644
index 83ea8eb..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/DeleteSurroundingTextInCodePointsCommandTest.kt
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class DeleteSurroundingTextInCodePointsCommandTest {
-    val CH1 = "\uD83D\uDE00" // U+1F600
-    val CH2 = "\uD83D\uDE01" // U+1F601
-    val CH3 = "\uD83D\uDE02" // U+1F602
-    val CH4 = "\uD83D\uDE03" // U+1F603
-    val CH5 = "\uD83D\uDE04" // U+1F604
-
-    @Test
-    fun test_delete_after() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
-
-        eb.deleteSurroundingTextInCodePoints(0, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
-
-        eb.deleteSurroundingTextInCodePoints(1, 0)
-
-        assertThat(eb.toString()).isEqualTo("$CH2$CH3$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_after_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4))
-
-        eb.deleteSurroundingTextInCodePoints(0, 2)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(2, 0)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(2, 2)
-
-        assertThat(eb.toString()).isEqualTo(CH1)
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_selection_preserve() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4, 8))
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH3$CH4")
-        assertThat(eb.selectionStart).isEqualTo(2)
-        assertThat(eb.selectionEnd).isEqualTo(6)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_before_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(1000, 0)
-
-        assertThat(eb.toString()).isEqualTo("$CH4$CH5")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_after_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(0, 1000)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH3")
-        assertThat(eb.cursor).isEqualTo(6)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_both_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.deleteSurroundingTextInCodePoints(1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_composition_no_intersection_preceding_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(0, 2)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_delete_composition_no_intersection_trailing_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(8, 10)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.compositionStart).isEqualTo(4)
-        assertThat(eb.compositionEnd).isEqualTo(6)
-    }
-
-    @Test
-    fun test_delete_composition_intersection_preceding_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(0, 6)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun test_delete_composition_intersection_trailing_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(6, 10)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.compositionStart).isEqualTo(4)
-        assertThat(eb.compositionEnd).isEqualTo(6)
-    }
-
-    @Test
-    fun test_delete_covered_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(4, 6)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_delete_composition_covered() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
-
-        eb.setComposition(0, 10)
-
-        eb.deleteSurroundingTextInCodePoints(1, 1)
-
-        assertThat(eb.toString()).isEqualTo("$CH1$CH2$CH5")
-        assertThat(eb.cursor).isEqualTo(4)
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(6)
-    }
-
-    @Test
-    fun throws_whenLengthBeforeInvalid() {
-        val eb = EditingBuffer("", TextRange(0))
-        val error = assertFailsWith<IllegalArgumentException> {
-            eb.deleteSurroundingTextInCodePoints(lengthBeforeCursor = 0, lengthAfterCursor = -42)
-        }
-        assertThat(error).hasMessageThat().contains("-42")
-    }
-
-    @Test
-    fun throws_whenLengthAfterInvalid() {
-        val eb = EditingBuffer("", TextRange(0))
-        val error = assertFailsWith<IllegalArgumentException> {
-            eb.deleteSurroundingTextInCodePoints(lengthBeforeCursor = -42, lengthAfterCursor = 0)
-        }
-        assertThat(error).hasMessageThat().contains("-42")
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferChangeTrackingTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferChangeTrackingTest.kt
deleted file mode 100644
index 5ae6b2c..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferChangeTrackingTest.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class EditingBufferChangeTrackingTest {
-
-    @Test
-    fun normalReplaceOperation_reportedAsReplace() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(2, 4, "bfghi")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2, 7))
-    }
-
-    @Test
-    fun tailInsertionReportedAsReplace_coercesToInsertion() {
-        val eb = EditingBuffer("abcd", TextRange.Zero)
-
-        eb.replace(2, 4, "cde")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(4))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(4, 5))
-    }
-
-    @Test
-    fun headInsertionReportedAsReplace_coercesToInsertion() {
-        val eb = EditingBuffer("abcd", TextRange.Zero)
-
-        eb.replace(0, 4, "eabcd")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(0))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(0, 1))
-    }
-
-    @Test
-    fun tailInsertionInTheMiddle_reportedAsReplace_coercesToInsertion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(1, 3, "bcef")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(3))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(3, 5))
-    }
-
-    @Test
-    fun headInsertionInTheMiddle_reportedAsReplace_coercesToInsertion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(2, 4, "fgcd")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2, 4))
-    }
-
-    @Test
-    fun tailDeletionReportedAsReplace_coercesToDeletion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(0, 5, "abcd")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(4, 5))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(4))
-    }
-
-    @Test
-    fun headDeletionReportedAsReplace_coercesToDeletion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(0, 5, "bcde")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(0, 1))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(0))
-    }
-
-    @Test
-    fun tailDeletionInTheMiddle_reportedAsReplace_coercesToDeletion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(1, 4, "b")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(2, 4))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun headDeletionInTheMiddle_reportedAsReplace_coercesToDeletion() {
-        val eb = EditingBuffer("abcde", TextRange.Zero)
-
-        eb.replace(1, 4, "d")
-
-        assertThat(eb.changeTracker.changeCount).isEqualTo(1)
-        assertThat(eb.changeTracker.getOriginalRange(0)).isEqualTo(TextRange(1, 3))
-        assertThat(eb.changeTracker.getRange(0)).isEqualTo(TextRange(1))
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferDeleteRangeTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferDeleteRangeTest.kt
deleted file mode 100644
index 5dd95b8..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferDeleteRangeTest.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class EditingBufferDeleteRangeTest {
-
-    @Test
-    fun test_does_not_intersect_deleted_is_after_the_target() {
-        val target = TextRange(0, 1)
-        val deleted = TextRange(2, 3)
-        assertThat(updateRangeAfterDelete(target, deleted))
-            .isEqualTo(TextRange(target.start, target.end))
-    }
-
-    @Test
-    fun test_does_not_intersect_deleted_is_before_the_target() {
-        val target = TextRange(4, 5)
-        val deleted = TextRange(0, 2)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(2, 3))
-    }
-
-    @Test
-    fun test_deleted_covers_target() {
-        val target = TextRange(1, 2)
-        val deleted = TextRange(0, 3)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 0))
-    }
-
-    @Test
-    fun test_target_covers_deleted() {
-        val target = TextRange(0, 3)
-        val deleted = TextRange(1, 2)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 2))
-    }
-
-    @Test
-    fun test_deleted_same_as_target() {
-        val target = TextRange(1, 2)
-        val deleted = TextRange(1, 2)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(1, 1))
-    }
-
-    @Test
-    fun test_deleted_covers_first_half_of_target() {
-        val target = TextRange(1, 4)
-        val deleted = TextRange(0, 2)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(0, 2))
-    }
-
-    @Test
-    fun test_deleted_covers_second_half_of_target() {
-        val target = TextRange(1, 4)
-        val deleted = TextRange(3, 5)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(1, 3))
-    }
-
-    @Test
-    fun test_delete_trailing_cursor() {
-        val target = TextRange(3, 3)
-        val deleted = TextRange(1, 2)
-        assertThat(updateRangeAfterDelete(target, deleted)).isEqualTo(TextRange(2, 2))
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt
deleted file mode 100644
index 6dca492..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.text2.input.internal.matchers.assertThat
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class EditingBufferTest {
-
-    @Test
-    fun insert() {
-        val eb = EditingBuffer("", TextRange.Zero)
-
-        eb.replace(0, 0, "A")
-
-        assertThat(eb).hasChars("A")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        // Keep inserting text to the end of string. Cursor should follow.
-        eb.replace(1, 1, "BC")
-        assertThat(eb).hasChars("ABC")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        // Insert into middle position. Cursor should be end of inserted text.
-        eb.replace(1, 1, "D")
-        assertThat(eb).hasChars("ADBC")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.selectionStart).isEqualTo(2)
-        assertThat(eb.selectionEnd).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-    }
-
-    @Test
-    fun delete() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.replace(0, 1, "")
-
-        // Delete the left character at the cursor.
-        assertThat(eb).hasChars("BCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        // Delete the text before the cursor
-        eb.replace(0, 2, "")
-        assertThat(eb).hasChars("DE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        // Delete end of the text.
-        eb.replace(1, 2, "")
-        assertThat(eb).hasChars("D")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-    }
-
-    @Test
-    fun setSelection() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 3))
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(-1)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.setSelection(0, 5) // Change the selection
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(-1)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.replace(0, 3, "X") // replace function cancel the selection and place cursor.
-        assertThat(eb).hasChars("XDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.setSelection(0, 2) // Set the selection again
-        assertThat(eb).hasChars("XDE")
-        assertThat(eb.cursor).isEqualTo(-1)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-    }
-
-    @Test fun setSelection_coerces_whenNegativeStart() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(-1, 1)
-
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-    }
-
-    @Test fun setSelection_coerces_whenNegativeEnd() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(1, -1)
-
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-    }
-
-    @Test
-    fun setSelection_allowReversedSelection() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-        eb.setSelection(4, 2)
-
-        assertThat(eb.selection).isEqualTo(TextRange(4, 2))
-    }
-
-    @Test
-    fun replace_reversedRegion() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-        eb.replace(3, 1, "FGHI")
-
-        assertThat(eb).hasChars("AFGHIDE")
-        assertThat(eb.cursor).isEqualTo(5)
-        assertThat(eb.selectionStart).isEqualTo(5)
-        assertThat(eb.selectionEnd).isEqualTo(5)
-    }
-
-    @Test
-    fun setComposition_and_cancelComposition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(0, 5) // Make all text as composition
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(5)
-
-        eb.replace(2, 3, "X") // replace function cancel the composition text.
-        assertThat(eb).hasChars("ABXDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.setComposition(2, 4) // set composition again
-        assertThat(eb).hasChars("ABXDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun setComposition_and_commitComposition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(0, 5) // Make all text as composition
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(5)
-
-        eb.replace(2, 3, "X") // replace function cancel the composition text.
-        assertThat(eb).hasChars("ABXDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.setComposition(2, 4) // set composition again
-        assertThat(eb).hasChars("ABXDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-
-        eb.commitComposition() // commit the composition
-        assertThat(eb).hasChars("ABXDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-    }
-
-    @Test
-    fun setCursor_and_get_cursor() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.cursor = 1
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.cursor = 2
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.selectionStart).isEqualTo(2)
-        assertThat(eb.selectionEnd).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-
-        eb.cursor = 5
-        assertThat(eb).hasChars("ABCDE")
-        assertThat(eb.cursor).isEqualTo(5)
-        assertThat(eb.selectionStart).isEqualTo(5)
-        assertThat(eb.selectionEnd).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-        assertThat(eb.compositionStart).isEqualTo(-1)
-        assertThat(eb.compositionEnd).isEqualTo(-1)
-    }
-
-    @Test
-    fun delete_preceding_cursor_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.delete(1, 2)
-        assertThat(eb).hasChars("ACDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun delete_trailing_cursor_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3))
-
-        eb.delete(1, 2)
-        assertThat(eb).hasChars("ACDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun delete_preceding_selection_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 1))
-
-        eb.delete(1, 2)
-        assertThat(eb).hasChars("ACDE")
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun delete_trailing_selection_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(4, 5))
-
-        eb.delete(1, 2)
-        assertThat(eb).hasChars("ACDE")
-        assertThat(eb.selectionStart).isEqualTo(3)
-        assertThat(eb.selectionEnd).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun delete_covered_cursor() {
-        // AB[]CDE
-        val eb = EditingBuffer("ABCDE", TextRange(2, 2))
-
-        eb.delete(1, 3)
-        // A[]DE
-        assertThat(eb).hasChars("ADE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-    }
-
-    @Test
-    fun delete_covered_selection() {
-        // A[BC]DE
-        val eb = EditingBuffer("ABCDE", TextRange(1, 3))
-
-        eb.delete(0, 4)
-        // []E
-        assertThat(eb).hasChars("E")
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-    }
-
-    @Test
-    fun delete_covered_reversedSelection() {
-        // A[BC]DE
-        val eb = EditingBuffer("ABCDE", TextRange(3, 1))
-
-        eb.delete(0, 4)
-        // []E
-        assertThat(eb).hasChars("E")
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-    }
-
-    @Test
-    fun delete_intersects_first_half_of_selection() {
-        // AB[CD]E
-        val eb = EditingBuffer("ABCDE", TextRange(2, 4))
-
-        eb.delete(1, 3)
-        // A[D]E
-        assertThat(eb).hasChars("ADE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun delete_intersects_first_half_of_reversedSelection() {
-        // AB[CD]E
-        val eb = EditingBuffer("ABCDE", TextRange(4, 2))
-
-        eb.delete(3, 1)
-        // A[D]E
-        assertThat(eb).hasChars("ADE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun delete_intersects_second_half_of_selection() {
-        // A[BCD]EFG
-        val eb = EditingBuffer("ABCDEFG", TextRange(1, 4))
-
-        eb.delete(3, 5)
-        // A[BC]FG
-        assertThat(eb).hasChars("ABCFG")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun delete_intersects_second_half_of_reversedSelection() {
-        // A[BCD]EFG
-        val eb = EditingBuffer("ABCDEFG", TextRange(4, 1))
-
-        eb.delete(5, 3)
-        // A[BC]FG
-        assertThat(eb).hasChars("ABCFG")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun delete_preceding_composition_no_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 2)
-        eb.delete(2, 3)
-
-        assertThat(eb).hasChars("ABDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun delete_trailing_composition_no_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(3, 4)
-        eb.delete(2, 3)
-
-        assertThat(eb).hasChars("ABDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun delete_preceding_composition_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 3)
-        eb.delete(2, 4)
-
-        assertThat(eb).hasChars("ABE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun delete_trailing_composition_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(3, 5)
-        eb.delete(2, 4)
-
-        assertThat(eb).hasChars("ABE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun delete_composition_contains_delrange() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(2, 5)
-        eb.delete(3, 4)
-
-        assertThat(eb).hasChars("ABCE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun delete_delrange_contains_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(3, 4)
-        eb.delete(2, 5)
-
-        assertThat(eb).hasChars("AB")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/FinishComposingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/FinishComposingTextCommandTest.kt
deleted file mode 100644
index ed69eea1..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/FinishComposingTextCommandTest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class FinishComposingTextCommandTest {
-
-    @Test
-    fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 4)
-        eb.finishComposingText()
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_preserve_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
-
-        eb.setComposition(2, 5)
-        eb.finishComposingText()
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/GapBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/GapBufferTest.kt
deleted file mode 100644
index 006c951..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/GapBufferTest.kt
+++ /dev/null
@@ -1,874 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.text2.input.internal.matchers.assertThat
-import com.google.common.truth.Truth.assertThat
-import kotlin.random.Random
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class GapBufferTest {
-
-    @Test
-    fun insertTest_insert_to_empty_string() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "A")
-            }
-        ).hasChars("A")
-    }
-
-    @Test
-    fun insertTest_insert_and_append() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "A")
-                replace(0, 0, "B")
-            }
-        ).hasChars("BA")
-    }
-
-    @Test
-    fun insertTest_insert_and_prepend() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "A")
-                replace(1, 1, "B")
-            }
-        ).hasChars("AB")
-    }
-
-    @Test
-    fun insertTest_insert_and_insert_into_middle() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "AA")
-                replace(1, 1, "B")
-            }
-        ).hasChars("ABA")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_prepend() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(0, 0, "A")
-            }
-        ).hasChars("AXX")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_insert_into_middle() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(1, 1, "A")
-            }
-        ).hasChars("XAX")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_append() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(2, 2, "A")
-            }
-        ).hasChars("XXA")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_prepend_and_prepend() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(0, 0, "A")
-                replace(0, 0, "B")
-            }
-        ).hasChars("BAXX")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_prepend_and_append() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(0, 0, "A")
-                replace(1, 1, "B")
-            }
-        ).hasChars("ABXX")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_prepend_and_insert_middle() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(0, 0, "A")
-                replace(2, 2, "B")
-            }
-        ).hasChars("AXBX")
-    }
-
-    @Test
-    fun insertTest_intoExistingText_insert_two_chars_and_append() {
-        assertThat(
-            PartialGapBuffer("XX").apply {
-                replace(0, 0, "AA")
-                replace(1, 1, "B")
-            }
-        ).hasChars("ABAXX")
-    }
-
-    @Test
-    fun insert_withSubRange_empty_fromHead() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 0)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_empty_fromTail() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 4, 4)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_empty_fromMiddle() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 1)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_singleChar_fromHead() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 1)
-
-        assertThat(buffer).hasChars("AXYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_singleChar_fromTail() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 3, 4)
-
-        assertThat(buffer).hasChars("DXYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_singleChar_fromMiddle() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 2)
-
-        assertThat(buffer).hasChars("BXYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_multipleChars_fromHead() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 2)
-
-        assertThat(buffer).hasChars("ABXYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_multipleChars_fromTail() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 2, 4)
-
-        assertThat(buffer).hasChars("CDXYZ")
-    }
-
-    @Test
-    fun insert_withSubRange_multipleChars_fromMiddle() {
-        val buffer = PartialGapBuffer("")
-        buffer.replace(0, 0, "XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 3)
-
-        assertThat(buffer).hasChars("BCXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_empty_fromHead() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 0)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_empty_fromTail() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 4, 4)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_empty_fromMiddle() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 1)
-
-        assertThat(buffer).hasChars("XYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_singleChar_fromHead() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 1)
-
-        assertThat(buffer).hasChars("AXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_singleChar_fromTail() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 3, 4)
-
-        assertThat(buffer).hasChars("DXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_singleChar_fromMiddle() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 2)
-
-        assertThat(buffer).hasChars("BXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_multipleChars_fromHead() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 0, 2)
-
-        assertThat(buffer).hasChars("ABXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_multipleChars_fromTail() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 2, 4)
-
-        assertThat(buffer).hasChars("CDXYZ")
-    }
-
-    @Test
-    fun insert_intoExistingText_withSubRange_multipleChars_fromMiddle() {
-        val buffer = PartialGapBuffer("XYZ")
-
-        buffer.replace(0, 0, "ABCD", 1, 3)
-
-        assertThat(buffer).hasChars("BCXYZ")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_from_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "")
-            }
-        ).hasChars("BC")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_middle() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 2, "")
-            }
-        ).hasChars("AC")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(2, 3, "")
-            }
-        ).hasChars("AB")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_two_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 2, "")
-            }
-        ).hasChars("C")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_two_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 3, "")
-            }
-        ).hasChars("A")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_with_two_instruction_from_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "")
-                replace(0, 1, "")
-            }
-        ).hasChars("C")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_with_two_instruction_from_head_and_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "")
-                replace(1, 2, "")
-            }
-        ).hasChars("B")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_with_two_instruction_from_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 2, "")
-                replace(1, 2, "")
-            }
-        ).hasChars("A")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_three_chars() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 3, "")
-            }
-        ).hasChars("")
-    }
-
-    @Test
-    fun deleteTest_insert_and_delete_three_chars_with_three_instructions() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "")
-                replace(0, 1, "")
-                replace(0, 1, "")
-            }
-        ).hasChars("")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "")
-            }
-        ).hasChars("BC")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_from_middle() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 2, "")
-            }
-        ).hasChars("AC")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(2, 3, "")
-            }
-        ).hasChars("AB")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_two_chars_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 2, "")
-            }
-        ).hasChars("C")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_two_chars_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 3, "")
-            }
-        ).hasChars("A")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "")
-                replace(0, 1, "")
-            }
-        ).hasChars("C")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head_and_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "")
-                replace(1, 2, "")
-            }
-        ).hasChars("B")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 2, "")
-                replace(1, 2, "")
-            }
-        ).hasChars("A")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_three_chars() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 3, "")
-            }
-        ).hasChars("")
-    }
-
-    @Test
-    fun deleteTest_fromExistingText_delete_three_chars_with_three_instructions() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "")
-                replace(0, 1, "")
-                replace(0, 1, "")
-            }
-        ).hasChars("")
-    }
-
-    @Test
-    fun replaceTest_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "X")
-            }
-        ).hasChars("XBC")
-    }
-
-    @Test
-    fun replaceTest_middle() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 2, "X")
-            }
-        ).hasChars("AXC")
-    }
-
-    @Test
-    fun replaceTest_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(2, 3, "X")
-            }
-        ).hasChars("ABX")
-    }
-
-    @Test
-    fun replaceTest_head_two_chars() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 2, "X")
-            }
-        ).hasChars("XC")
-    }
-
-    @Test
-    fun replaceTest_middle_two_chars() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 3, "X")
-            }
-        ).hasChars("AX")
-    }
-
-    @Test
-    fun replaceTest_three_chars() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 3, "X")
-            }
-        ).hasChars("X")
-    }
-
-    @Test
-    fun replaceTest_one_char_with_two_chars_from_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 1, "XY")
-            }
-        ).hasChars("XYBC")
-    }
-
-    @Test
-    fun replaceTest_one_char_with_two_chars_from_middle() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 2, "XY")
-            }
-        ).hasChars("AXYC")
-    }
-
-    @Test
-    fun replaceTest_one_char_with_two_chars_from_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(2, 3, "XY")
-            }
-        ).hasChars("ABXY")
-    }
-
-    @Test
-    fun replaceTest_two_chars_with_two_chars_from_head() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 2, "XY")
-            }
-        ).hasChars("XYC")
-    }
-
-    @Test
-    fun replaceTest_two_chars_with_two_chars_from_tail() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(1, 3, "XY")
-            }
-        ).hasChars("AXY")
-    }
-
-    @Test
-    fun replaceTest_three_chars_with_two_char() {
-        assertThat(
-            PartialGapBuffer("").apply {
-                replace(0, 0, "ABC")
-                replace(0, 3, "XY")
-            }
-        ).hasChars("XY")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "X")
-            }
-        ).hasChars("XBC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_middle() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 2, "X")
-            }
-        ).hasChars("AXC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(2, 3, "X")
-            }
-        ).hasChars("ABX")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_two_chars_with_one_char_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 2, "X")
-            }
-        ).hasChars("XC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_two_chars_with_one_char_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 3, "X")
-            }
-        ).hasChars("AX")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_three_chars() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 3, "X")
-            }
-        ).hasChars("X")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_one_char_with_two_chars_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 1, "XY")
-            }
-        ).hasChars("XYBC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_one_char_with_two_chars_from_middle() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 2, "XY")
-            }
-        ).hasChars("AXYC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_one_char_with_two_chars_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(2, 3, "XY")
-            }
-        ).hasChars("ABXY")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_two_chars_with_two_chars_from_head() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 2, "XY")
-            }
-        ).hasChars("XYC")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_two_chars_with_two_chars_from_tail() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(1, 3, "XY")
-            }
-        ).hasChars("AXY")
-    }
-
-    @Test
-    fun replaceTest_fromExistingText_three_chars_with_three_chars() {
-        assertThat(
-            PartialGapBuffer("ABC").apply {
-                replace(0, 3, "XY")
-            }
-        ).hasChars("XY")
-    }
-
-    @Test
-    fun replace_throws_whenStartGreaterThanEnd() {
-        val buffer = PartialGapBuffer("ABCD")
-
-        val error = assertFailsWith<IllegalArgumentException> {
-            buffer.replace(3, 2, "")
-        }
-        assertThat(error).hasMessageThat().contains("start=3 > end=2")
-    }
-
-    @Test
-    fun replace_throws_whenTextStartGreaterThanTextEnd() {
-        val buffer = PartialGapBuffer("ABCD")
-
-        val error = assertFailsWith<IllegalArgumentException> {
-            buffer.replace(2, 3, "", 1, 0)
-        }
-        assertThat(error).hasMessageThat().contains("textStart=1 > textEnd=0")
-    }
-
-    @Test
-    fun replace_throws_whenStartNegative() {
-        val buffer = PartialGapBuffer("ABCD")
-
-        val error = assertFailsWith<IllegalArgumentException> {
-            buffer.replace(-1, 2, "XY")
-        }
-        assertThat(error).hasMessageThat().contains("-1")
-    }
-
-    @Test
-    fun replace_throws_whenTextStartNegative() {
-        val buffer = PartialGapBuffer("ABCD")
-
-        val error = assertFailsWith<IllegalArgumentException> {
-            buffer.replace(1, 2, "XY", -1, 2)
-        }
-        assertThat(error).hasMessageThat().contains("-1")
-    }
-
-    /** Compare with the result of StringBuffer. We trust the StringBuffer works correctly. */
-    private fun assertReplace(
-        start: Int,
-        end: Int,
-        str: String,
-        sb: StringBuffer,
-        gb: PartialGapBuffer
-    ) {
-        sb.replace(start, end, str)
-        gb.replace(start, end, str)
-        assertThat(gb).hasChars(sb.toString())
-    }
-
-    private val LONG_INIT_TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(256)
-    private val SHORT_TEXT = "A"
-    private val MEDIUM_TEXT = "Hello, World"
-    private val LONG_TEXT = "abcdefghijklmnopqrstuvwxyz".repeat(16)
-
-    @Test
-    fun longTextTest_keep_insertion() {
-        val sb = StringBuffer(LONG_INIT_TEXT)
-        val gb = PartialGapBuffer(LONG_INIT_TEXT)
-
-        var c = 256 // cursor
-        assertReplace(c, c, SHORT_TEXT, sb, gb)
-        c += SHORT_TEXT.length
-        assertReplace(c, c, MEDIUM_TEXT, sb, gb)
-        c += MEDIUM_TEXT.length
-        assertReplace(c, c, LONG_TEXT, sb, gb)
-        c += LONG_TEXT.length
-        assertReplace(c, c, MEDIUM_TEXT, sb, gb)
-        c += MEDIUM_TEXT.length
-        assertReplace(c, c, SHORT_TEXT, sb, gb)
-    }
-
-    @Test
-    fun longTextTest_keep_deletion() {
-        val sb = StringBuffer(LONG_INIT_TEXT)
-        val gb = PartialGapBuffer(LONG_INIT_TEXT)
-
-        var c = 2048 // cursor
-        // Forward deletion
-        assertReplace(c, c + 10, "", sb, gb)
-        assertReplace(c, c + 100, "", sb, gb)
-        assertReplace(c, c + 1000, "", sb, gb)
-
-        // Backspacing
-        assertReplace(c - 10, c, "", sb, gb)
-        c -= 10
-        assertReplace(c - 100, c, "", sb, gb)
-        c -= 100
-        assertReplace(c - 1000, c, "", sb, gb)
-    }
-
-    @Test
-    fun longTextTest_farInput() {
-        val sb = StringBuffer(LONG_INIT_TEXT)
-        val gb = PartialGapBuffer(LONG_INIT_TEXT)
-
-        assertReplace(1024, 1024, "Hello, World", sb, gb)
-        assertReplace(128, 128, LONG_TEXT, sb, gb)
-    }
-
-    @Test
-    fun randomInsertDeleteStressTest() {
-        val sb = StringBuffer(LONG_INIT_TEXT)
-        val gb = PartialGapBuffer(LONG_INIT_TEXT)
-
-        val r = Random(10 /* fix the seed for reproduction */)
-
-        val insertTexts = arrayOf(SHORT_TEXT, MEDIUM_TEXT, LONG_TEXT)
-        val delLengths = arrayOf(1, 10, 100)
-
-        var c = LONG_INIT_TEXT.length / 2
-
-        for (i in 0..100) {
-            when (r.nextInt() % 4) {
-                0 -> { // insert
-                    val txt = insertTexts.random(r)
-                    assertReplace(c, c, txt, sb, gb)
-                    c += txt.length
-                }
-                1 -> { // forward delete
-                    assertReplace(c, c + delLengths.random(r), "", sb, gb)
-                }
-                2 -> { // backspacing
-                    val len = delLengths.random(r)
-                    assertReplace(c - len, c, "", sb, gb)
-                    c -= len
-                }
-                3 -> { // replacing
-                    val txt = insertTexts.random(r)
-                    val len = delLengths.random(r)
-
-                    assertReplace(c, c + len, txt, sb, gb)
-                }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/MathUtilsTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/MathUtilsTest.kt
deleted file mode 100644
index 157b8e3..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/MathUtilsTest.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.Size
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class MathUtilsTest {
-
-    @Test
-    fun findClosestRect_findsClosestRect_withZeroSize() {
-        val rect1 = Rect(Offset.Zero, Size.Zero)
-        val rect2 = Rect(Offset(4f, 0f), Size.Zero)
-
-        assertThat(Offset(1.9f, 0f).findClosestRect(rect1, rect2)).isEqualTo(-1)
-        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
-        assertThat(Offset(2.1f, 0f).findClosestRect(rect1, rect2)).isEqualTo(1)
-    }
-
-    @Test
-    fun findClosestRect_findsClosestRect_withZeroWidth() {
-        val rect1 = Rect(Offset.Zero, Size(0f, 10f))
-        val rect2 = Rect(Offset(4f, 0f), Size(0f, 10f))
-
-        assertThat(Offset(1.9f, 0f).findClosestRect(rect1, rect2)).isEqualTo(-1)
-        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
-        assertThat(Offset(2.1f, 0f).findClosestRect(rect1, rect2)).isEqualTo(1)
-    }
-
-    @Test
-    fun findClosestRect_findsClosestRect_withZeroWidth_differentY() {
-        val rect1 = Rect(Offset(0f, -10f), Size(0f, 9f))
-        val rect2 = Rect(Offset(4f, 1f), Size(0f, 9f))
-
-        assertThat(Offset(2f, -1f).findClosestRect(rect1, rect2)).isEqualTo(-1)
-        assertThat(Offset(2f, 0f).findClosestRect(rect1, rect2)).isEqualTo(0)
-        assertThat(Offset(2f, 1f).findClosestRect(rect1, rect2)).isEqualTo(1)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt
deleted file mode 100644
index 782f6b6..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt
+++ /dev/null
@@ -1,902 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class OffsetMappingCalculatorTest {
-
-    @Test
-    fun noChanges() {
-        val builder = TestEditBuffer()
-        builder.assertIdentityMapping()
-    }
-
-    @Test
-    fun insertCharIntoEmpty() {
-        val builder = TestEditBuffer()
-        builder.append("a")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 1),
-            1 to TextRange(2),
-            2 to TextRange(3),
-            3 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(1),
-        )
-    }
-
-    @Test
-    fun insertCharIntoMiddle() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(1, "c")
-        assertThat(builder.toString()).isEqualTo("acb")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1, 2),
-            2 to TextRange(3),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(2),
-        )
-    }
-
-    @Test
-    fun deleteCharFromMiddle() {
-        val builder = TestEditBuffer("abc")
-        builder.delete(1)
-        assertThat(builder.toString()).isEqualTo("ac")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(2),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1, 2),
-            2 to TextRange(3),
-            3 to TextRange(4),
-        )
-    }
-
-    @Test
-    fun replaceCharInMiddle() {
-        val builder = TestEditBuffer("abc")
-        builder.replace(1, "d")
-        assertThat(builder.toString()).isEqualTo("adc")
-        builder.assertIdentityMapping()
-    }
-
-    @Test
-    fun insertStringIntoEmpty() {
-        val builder = TestEditBuffer("")
-        builder.append("ab")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 2),
-            1 to TextRange(3),
-            2 to TextRange(4),
-            3 to TextRange(5),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(1),
-        )
-    }
-
-    @Test
-    fun insertStringIntoMiddle() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(1, "cd")
-        assertThat(builder.toString()).isEqualTo("acdb")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1, 3),
-            2 to TextRange(4),
-            3 to TextRange(5),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(1),
-            4 to TextRange(2),
-            5 to TextRange(3),
-        )
-    }
-
-    @Test
-    fun deleteStringFromMiddle() {
-        val builder = TestEditBuffer("abcd")
-        builder.delete(1, 3)
-        assertThat(builder.toString()).isEqualTo("ad")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(1),
-            4 to TextRange(2),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1, 3),
-            2 to TextRange(4),
-            3 to TextRange(5),
-        )
-    }
-
-    @Test
-    fun replaceStringWithEqualLengthInMiddle() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace(1, 3, "ef")
-        assertThat(builder.toString()).isEqualTo("aefd")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1, 3),
-            3 to TextRange(3),
-            4 to TextRange(4),
-            5 to TextRange(5),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1, 3),
-            3 to TextRange(3),
-            4 to TextRange(4),
-            5 to TextRange(5),
-        )
-    }
-
-    @Test
-    fun replaceStringWithLongerInMiddle() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace(1, 3, "efg")
-        assertThat(builder.toString()).isEqualTo("aefgd")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1, 4),
-            3 to TextRange(4),
-            4 to TextRange(5),
-            5 to TextRange(6),
-            6 to TextRange(7),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1, 3),
-            3 to TextRange(1, 3),
-            4 to TextRange(3),
-            5 to TextRange(4),
-        )
-    }
-
-    @Test
-    fun replaceStringWithShorterInMiddle() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace(1, 3, "e")
-        assertThat(builder.toString()).isEqualTo("aed")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1, 2),
-            3 to TextRange(2),
-            4 to TextRange(3),
-            5 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(3),
-            3 to TextRange(4),
-            4 to TextRange(5),
-            5 to TextRange(6),
-        )
-    }
-
-    @Test
-    fun replaceAllWithEqualLength() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace("efgh")
-        assertThat(builder.toString()).isEqualTo("efgh")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 4),
-            2 to TextRange(0, 4),
-            3 to TextRange(0, 4),
-            4 to TextRange(4),
-            5 to TextRange(5),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 4),
-            2 to TextRange(0, 4),
-            3 to TextRange(0, 4),
-            4 to TextRange(4),
-            5 to TextRange(5),
-        )
-    }
-
-    @Test
-    fun replaceAllWithLonger() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace("efghi")
-        assertThat(builder.toString()).isEqualTo("efghi")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 5),
-            2 to TextRange(0, 5),
-            3 to TextRange(0, 5),
-            4 to TextRange(5),
-            5 to TextRange(6),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 4),
-            2 to TextRange(0, 4),
-            3 to TextRange(0, 4),
-            4 to TextRange(0, 4),
-            5 to TextRange(4),
-            6 to TextRange(5),
-        )
-    }
-
-    @Test
-    fun replaceAllWithShorter() {
-        val builder = TestEditBuffer("abcd")
-        builder.replace("ef")
-        assertThat(builder.toString()).isEqualTo("ef")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 2),
-            2 to TextRange(0, 2),
-            3 to TextRange(0, 2),
-            4 to TextRange(2),
-            5 to TextRange(3),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 4),
-            2 to TextRange(4),
-            3 to TextRange(5),
-            4 to TextRange(6),
-            5 to TextRange(7),
-        )
-    }
-
-    @Test
-    fun prependCharToString() {
-        val builder = TestEditBuffer("a")
-        builder.insert(0, "b")
-        assertThat(builder.toString()).isEqualTo("ba")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 1),
-            1 to TextRange(2),
-            2 to TextRange(3),
-            3 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(1),
-        )
-    }
-
-    @Test
-    fun prependStringToString() {
-        val builder = TestEditBuffer("a")
-        builder.insert(0, "bc")
-        assertThat(builder.toString()).isEqualTo("bca")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 2),
-            1 to TextRange(3),
-            2 to TextRange(4),
-            3 to TextRange(5),
-            4 to TextRange(6),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(1),
-        )
-    }
-
-    @Test
-    fun appendCharToString() {
-        val builder = TestEditBuffer("a")
-        builder.append("b")
-        assertThat(builder.toString()).isEqualTo("ab")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1, 2),
-            2 to TextRange(3),
-            3 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(2),
-        )
-    }
-
-    @Test
-    fun appendStringToString() {
-        val builder = TestEditBuffer("a")
-        builder.append("bc")
-        assertThat(builder.toString()).isEqualTo("abc")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1, 3),
-            2 to TextRange(4),
-            3 to TextRange(5),
-            4 to TextRange(6),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(1),
-            4 to TextRange(2),
-        )
-    }
-
-    @Test
-    fun multiplePrepends() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(0, "c")
-        builder.insert(0, "d")
-        builder.insert(0, "ef")
-        assertThat(builder.toString()).isEqualTo("efdcab")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 4),
-            1 to TextRange(5),
-            2 to TextRange(6),
-            3 to TextRange(7),
-            4 to TextRange(8),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(0),
-            4 to TextRange(0),
-            5 to TextRange(1),
-            6 to TextRange(2),
-            7 to TextRange(3),
-        )
-    }
-
-    @Test
-    fun multipleAppends() {
-        val builder = TestEditBuffer("ab")
-        builder.append("c")
-        builder.append("d")
-        builder.append("ef")
-        assertThat(builder.toString()).isEqualTo("abcdef")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2, 6),
-            3 to TextRange(7),
-            4 to TextRange(8),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2),
-            3 to TextRange(2),
-            4 to TextRange(2),
-            5 to TextRange(2),
-            6 to TextRange(2),
-            7 to TextRange(3),
-        )
-    }
-
-    @Test
-    fun multiplePrependsThenDeletesCancellingOut() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(0, "cde") // cdeab
-        builder.delete(2) // cdab
-        builder.delete(0) // dab
-        builder.insert(0, "f") // fdab
-        builder.delete(0, 2)
-        assertThat(builder.toString()).isEqualTo("ab")
-        builder.assertIdentityMapping()
-    }
-
-    @Test
-    fun multipleAppendsThenDeletesCancellingOut() {
-        val builder = TestEditBuffer("ab")
-        builder.append("cde") // abcde
-        builder.delete(2) // abde
-        builder.delete(3) // abd
-        builder.append("f") // abdf
-        builder.delete(2, 4)
-        assertThat(builder.toString()).isEqualTo("ab")
-        builder.assertIdentityMapping()
-    }
-
-    @Test
-    fun multipleInsertsThenDeletesCancellingOut() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(1, "c")
-        builder.insert(2, "de")
-        builder.insert(1, "f")
-        builder.delete(1, 3)
-        builder.delete(1)
-        builder.delete(1)
-        assertThat(builder.toString()).isEqualTo("ab")
-        builder.assertIdentityMapping()
-    }
-
-    @Test
-    fun multipleContinuousDeletesAtStartInOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(0)
-        builder.delete(0)
-        builder.delete(0)
-        assertThat(builder.toString()).isEqualTo("def")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(0),
-            4 to TextRange(1),
-            5 to TextRange(2),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0, 3),
-            1 to TextRange(4),
-            2 to TextRange(5),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-    }
-
-    @Test
-    fun multipleContinuousDeletesAtStartOutOfOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(1)
-        builder.delete(1)
-        builder.delete(0)
-        assertThat(builder.toString()).isEqualTo("def")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(0),
-            4 to TextRange(1),
-            5 to TextRange(2),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0, 3),
-            1 to TextRange(4),
-            2 to TextRange(5),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-    }
-
-    @Test
-    fun multipleContinuousDeletesAtEndInOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(builder.length - 1)
-        builder.delete(builder.length - 1)
-        builder.delete(builder.length - 1)
-        assertThat(builder.toString()).isEqualTo("abc")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2),
-            3 to TextRange(3),
-            4 to TextRange(3),
-            5 to TextRange(3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2),
-            3 to TextRange(3, 6),
-            4 to TextRange(7),
-            5 to TextRange(8),
-        )
-    }
-
-    @Test
-    fun multipleContinuousDeletesAtEndOutOfOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(4)
-        builder.delete(3)
-        builder.delete(3)
-        assertThat(builder.toString()).isEqualTo("abc")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2),
-            3 to TextRange(3),
-            4 to TextRange(3),
-            5 to TextRange(3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(2),
-            3 to TextRange(3, 6),
-            4 to TextRange(7),
-            5 to TextRange(8),
-        )
-    }
-
-    @Test
-    fun multipleContinuousDeletesInMiddleInOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(1)
-        builder.delete(1, 3)
-        builder.delete(1)
-        assertThat(builder.toString()).isEqualTo("af")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(1),
-            4 to TextRange(1),
-            5 to TextRange(1),
-            6 to TextRange(2),
-            7 to TextRange(3),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1, 5),
-            2 to TextRange(6),
-            3 to TextRange(7),
-            4 to TextRange(8),
-            5 to TextRange(9),
-        )
-    }
-
-    @Test
-    fun multipleContinuousDeletesInMiddleOutOfOrder() {
-        val builder = TestEditBuffer("abcdef")
-        builder.delete(2)
-        builder.delete(2, 4)
-        builder.delete(1)
-        assertThat(builder.toString()).isEqualTo("af")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(1),
-            2 to TextRange(1),
-            3 to TextRange(1),
-            4 to TextRange(1),
-            5 to TextRange(1),
-            6 to TextRange(2),
-            7 to TextRange(3),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(1, 5),
-            2 to TextRange(6),
-            3 to TextRange(7),
-            4 to TextRange(8),
-            5 to TextRange(9),
-        )
-    }
-
-    @Test
-    fun discontinuousInsertsAndDeletes() {
-        val builder = TestEditBuffer("ab")
-        builder.insert(1, "cde") // acdeb
-        builder.delete(2) // aceb
-        builder.append("fgh") // acebfgh
-        builder.delete(4) // acebgh
-        builder.insert(0, "ijk") // ijkacebgh
-        builder.delete(2) // ijacebgh
-        assertThat(builder.toString()).isEqualTo("ijacebgh")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0, 2),
-            1 to TextRange(3, 5),
-            2 to TextRange(6, 8),
-            3 to TextRange(9),
-            4 to TextRange(10),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0),
-            2 to TextRange(0),
-            3 to TextRange(1),
-            4 to TextRange(1),
-            5 to TextRange(1),
-            6 to TextRange(2),
-            7 to TextRange(2),
-            8 to TextRange(2),
-            9 to TextRange(3),
-        )
-    }
-
-    @Test
-    fun multipleContinuousOneToOneReplacements() {
-        val builder = TestEditBuffer("abc")
-        builder.replace(0, "f")
-        builder.replace(1, "f")
-        builder.replace(2, "f")
-        assertThat(builder.toString()).isEqualTo("fff")
-        builder.assertIdentityMapping()
-    }
-
-    /** This simulates an expanding codepoint transform. */
-    @Test
-    fun multipleContinuousOneToManyReplacements() {
-        val builder = TestEditBuffer("abc")
-        builder.replace(0, "dd") // ddbc
-        builder.replace(2, "ee") // ddeec
-        builder.replace(4, "ff")
-        assertThat(builder.toString()).isEqualTo("ddeeff")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(2),
-            2 to TextRange(4),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 1),
-            2 to TextRange(1),
-            3 to TextRange(1, 2),
-            4 to TextRange(2),
-            5 to TextRange(2, 3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-    }
-
-    @Test
-    fun multipleContinuousOneToManyReplacementsReversed() {
-        val builder = TestEditBuffer("abc")
-        builder.replace(2, "dd") // abdd
-        builder.replace(1, "ee") // aeedd
-        builder.replace(0, "ff")
-        assertThat(builder.toString()).isEqualTo("ffeedd")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(2),
-            2 to TextRange(4),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 1),
-            2 to TextRange(1),
-            3 to TextRange(1, 2),
-            4 to TextRange(2),
-            5 to TextRange(2, 3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-    }
-
-    /** This simulates a contracting codepoint transform. */
-    @Test
-    fun multipleContinuousManyToOneReplacements() {
-        val builder = TestEditBuffer("abcdef")
-        builder.replace(0, 2, "g") // gcdef
-        builder.replace(1, 3, "h") // ghef
-        builder.replace(2, 4, "i")
-        assertThat(builder.toString()).isEqualTo("ghi")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 1),
-            2 to TextRange(1),
-            3 to TextRange(1, 2),
-            4 to TextRange(2),
-            5 to TextRange(2, 3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(2),
-            2 to TextRange(4),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-    }
-
-    @Test
-    fun multipleContinuousManyToOneReplacementsReversed() {
-        val builder = TestEditBuffer("abcdef")
-        builder.replace(4, 6, "g") // abcdg
-        builder.replace(2, 4, "h") // abhg
-        builder.replace(0, 2, "i")
-        assertThat(builder.toString()).isEqualTo("ihg")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 1),
-            2 to TextRange(1),
-            3 to TextRange(1, 2),
-            4 to TextRange(2),
-            5 to TextRange(2, 3),
-            6 to TextRange(3),
-            7 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(2),
-            2 to TextRange(4),
-            3 to TextRange(6),
-            4 to TextRange(7),
-        )
-    }
-
-    /**
-     * This sequence of operations is basically nonsense and so the mappings don't make much sense
-     * either. This test is just here to ensure the output is consistent and doesn't crash.
-     */
-    @Test
-    fun twoOverlappingReplacements() {
-        val builder = TestEditBuffer("abc")
-        builder.replace(0, 2, "wx") // wxc
-        builder.replace(1, 3, "yz")
-        assertThat(builder.toString()).isEqualTo("wyz")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 3),
-            2 to TextRange(1, 3),
-            3 to TextRange(3),
-            4 to TextRange(4),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 2),
-            2 to TextRange(0, 3),
-            3 to TextRange(3),
-            4 to TextRange(4),
-        )
-    }
-
-    /**
-     * This sequence of operations is basically nonsense and so the mappings don't make much sense
-     * either. This test is just here to ensure the output is consistent and doesn't crash.
-     */
-    @Test
-    fun fourOverlappingReplacementsReversed() {
-        val builder = TestEditBuffer("abcde")
-        builder.replace(1, 3, "fg") // afgde
-        builder.replace(2, 4, "hi") // afhie
-        builder.replace(0, 2, "jk") // jkhie
-        builder.replace(3, 5, "lm")
-        assertThat(builder.toString()).isEqualTo("jkhlm")
-        builder.assertMappingsFromSource(
-            0 to TextRange(0),
-            1 to TextRange(0, 2),
-            2 to TextRange(0, 5),
-            3 to TextRange(2, 5),
-            4 to TextRange(3, 5),
-            5 to TextRange(5),
-            6 to TextRange(6),
-            7 to TextRange(7),
-        )
-        builder.assertMappingsFromDest(
-            0 to TextRange(0),
-            1 to TextRange(0, 3),
-            2 to TextRange(1, 3),
-            3 to TextRange(1, 4),
-            4 to TextRange(1, 5),
-            5 to TextRange(5),
-            6 to TextRange(6),
-            7 to TextRange(7),
-        )
-    }
-
-    private fun TestEditBuffer.assertIdentityMapping() {
-        // Check well off the end of the valid index range just to be sure.
-        repeat(length + 2) {
-            assertWithMessage("Mapping from source offset $it")
-                .that(mapFromSource(it)).isEqualTo(TextRange(it))
-            assertWithMessage("Mapping from dest offset $it")
-                .that(mapFromDest(it)).isEqualTo(TextRange(it))
-        }
-    }
-
-    private fun TestEditBuffer.assertMappingsFromSource(
-        vararg expectedMappings: Pair<Int, TextRange>
-    ) {
-        expectedMappings.forEach { (srcOffset, dstRange) ->
-            assertWithMessage("Mapping from source offset $srcOffset")
-                .that(mapFromSource(srcOffset)).isEqualTo(dstRange)
-        }
-    }
-
-    private fun TestEditBuffer.assertMappingsFromDest(
-        vararg expectedMappings: Pair<Int, TextRange>
-    ) {
-        expectedMappings.forEach { (dstOffset, srcRange) ->
-            assertWithMessage("Mapping from dest offset $dstOffset")
-                .that(mapFromDest(dstOffset)).isEqualTo(srcRange)
-        }
-    }
-
-    /**
-     * Basic implementation of a text editing buffer that uses [OffsetMappingCalculator] to make
-     * testing easier.
-     */
-    private class TestEditBuffer private constructor(
-        private val builder: StringBuilder
-    ) : CharSequence by builder {
-        constructor(text: CharSequence = "") : this(StringBuilder(text))
-
-        private val tracker = OffsetMappingCalculator()
-
-        fun append(text: CharSequence) {
-            tracker.recordEditOperation(length, length, text.length)
-            builder.append(text)
-        }
-
-        fun insert(offset: Int, value: CharSequence) {
-            tracker.recordEditOperation(offset, offset, value.length)
-            builder.insert(offset, value)
-        }
-
-        fun delete(start: Int, end: Int = start + 1) {
-            tracker.recordEditOperation(start, end, 0)
-            builder.delete(minOf(start, end), maxOf(start, end))
-        }
-
-        fun replace(value: String) {
-            replace(0, length, value)
-        }
-
-        fun replace(start: Int, value: String) {
-            replace(start, start + 1, value)
-        }
-
-        fun replace(start: Int, end: Int, value: String) {
-            tracker.recordEditOperation(start, end, value.length)
-            builder.replace(minOf(start, end), maxOf(start, end), value)
-        }
-
-        fun mapFromSource(offset: Int): TextRange = tracker.mapFromSource(offset)
-        fun mapFromDest(offset: Int): TextRange = tracker.mapFromDest(offset)
-
-        override fun toString(): String = builder.toString()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingRegionCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingRegionCommandTest.kt
deleted file mode 100644
index 14409ea..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingRegionCommandTest.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class SetComposingRegionCommandTest {
-
-    @Test
-    fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(1, 4)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun test_preserve_ongoing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 3)
-
-        eb.setComposingRegion(2, 4)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun test_preserve_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
-
-        eb.setComposingRegion(2, 4)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(4)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun test_set_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(4, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(4)
-    }
-
-    @Test
-    fun test_set_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(-1000, -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_small_and_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(-1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(5)
-    }
-
-    @Test
-    fun test_set_too_small_and_too_large_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposingRegion(1000, -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(5)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingTextCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingTextCommandTest.kt
deleted file mode 100644
index a517276c..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetComposingTextCommandTest.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class SetComposingTextCommandTest {
-
-    @Test
-    fun test_insert_empty() {
-        val eb = EditingBuffer("", TextRange.Zero)
-
-        eb.setComposingText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("X")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(0)
-        assertThat(eb.compositionEnd).isEqualTo(1)
-    }
-
-    @Test
-    fun test_insert_cursor_tail() {
-        val eb = EditingBuffer("A", TextRange(1))
-
-        eb.setComposingText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AX")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_insert_cursor_head() {
-        val eb = EditingBuffer("A", TextRange(1))
-
-        eb.setComposingText("X", 0)
-
-        assertThat(eb.toString()).isEqualTo("AX")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_insert_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.setComposingText("X", 2)
-
-        assertThat(eb.toString()).isEqualTo("AXBCDE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_insert_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4))
-
-        eb.setComposingText("X", -2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(4)
-        assertThat(eb.compositionEnd).isEqualTo(5)
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.setComposingText("", 0)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.setComposingText("", 1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1))
-
-        eb.setComposingText("", 2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_insert_empty_text_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4))
-
-        eb.setComposingText("", -2)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_cancel_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 4) // Mark "BCD" as composition
-        eb.setComposingText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_replace_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 4)) // select "BCD"
-
-        eb.setComposingText("X", 1)
-
-        assertThat(eb.toString()).isEqualTo("AXE")
-        assertThat(eb.cursor).isEqualTo(2)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(2)
-    }
-
-    @Test
-    fun test_composition_and_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 3)) // select "BC"
-
-        eb.setComposition(2, 4) // Mark "CD" as composition
-        eb.setComposingText("X", 1)
-
-        // If composition and selection exists at the same time, replace composition and cancel
-        // selection and place cursor.
-        assertThat(eb.toString()).isEqualTo("ABXE")
-        assertThat(eb.cursor).isEqualTo(3)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(2)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun test_cursor_position_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(5))
-
-        eb.setComposingText("X", -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDEX")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(5)
-        assertThat(eb.compositionEnd).isEqualTo(6)
-    }
-
-    @Test
-    fun test_cursor_position_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(5))
-
-        eb.setComposingText("X", 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDEX")
-        assertThat(eb.cursor).isEqualTo(6)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(5)
-        assertThat(eb.compositionEnd).isEqualTo(6)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetSelectionCommandTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetSelectionCommandTest.kt
deleted file mode 100644
index 1070d9b..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/SetSelectionCommandTest.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class SetSelectionCommandTest {
-
-    @Test
-    fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(1, 4)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(1)
-        assertThat(eb.selectionEnd).isEqualTo(4)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_preserve_ongoing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setComposition(1, 3)
-
-        eb.setSelection(2, 4)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(2)
-        assertThat(eb.selectionEnd).isEqualTo(4)
-        assertThat(eb.hasComposition()).isTrue()
-        assertThat(eb.compositionStart).isEqualTo(1)
-        assertThat(eb.compositionEnd).isEqualTo(3)
-    }
-
-    @Test
-    fun test_cancel_ongoing_selection() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 4))
-
-        eb.setSelection(2, 5)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(2)
-        assertThat(eb.selectionEnd).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(4, 1)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(4)
-        assertThat(eb.selectionEnd).isEqualTo(1)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(-1000, -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.cursor).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_small_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(-1000, 1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(0)
-        assertThat(eb.selectionEnd).isEqualTo(5)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-
-    @Test
-    fun test_set_too_small_too_large_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange.Zero)
-
-        eb.setSelection(1000, -1000)
-
-        assertThat(eb.toString()).isEqualTo("ABCDE")
-        assertThat(eb.selectionStart).isEqualTo(5)
-        assertThat(eb.selectionEnd).isEqualTo(0)
-        assertThat(eb.hasComposition()).isFalse()
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt
deleted file mode 100644
index 656b1d3..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TextFieldStateInternalBufferTest {
-
-    @Test
-    fun initializeValue() {
-        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
-        val state = TextFieldState(firstValue)
-
-        assertThat(state.text).isEqualTo(firstValue)
-    }
-
-    @Test
-    fun apply_commitTextCommand_changesValue() {
-        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
-        val state = TextFieldState(firstValue)
-
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        state.editAsUser { commitText("X", 1) }
-        val newState = state.text
-
-        assertThat(newState.toString()).isEqualTo("XABCDE")
-        assertThat(newState.selectionInChars.min).isEqualTo(1)
-        assertThat(newState.selectionInChars.max).isEqualTo(1)
-        // edit command updates should not trigger reset listeners.
-        assertThat(resetCalled).isEqualTo(0)
-    }
-
-    @Test
-    fun apply_setSelectionCommand_changesValue() {
-        val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
-        val state = TextFieldState(firstValue)
-
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        state.editAsUser { setSelection(0, 2) }
-        val newState = state.text
-
-        assertThat(newState.toString()).isEqualTo("ABCDE")
-        assertThat(newState.selectionInChars.min).isEqualTo(0)
-        assertThat(newState.selectionInChars.max).isEqualTo(2)
-        // edit command updates should not trigger reset listeners.
-        assertThat(resetCalled).isEqualTo(0)
-    }
-
-    @Test
-    fun testNewState_bufferNotUpdated_ifSameModelStructurally() {
-        val state = TextFieldState()
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        val initialBuffer = state.mainBuffer
-        state.resetStateAndNotifyIme(
-            TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
-        )
-        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
-
-        val updatedBuffer = state.mainBuffer
-        state.resetStateAndNotifyIme(
-            TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
-        )
-        assertThat(state.mainBuffer).isSameInstanceAs(updatedBuffer)
-
-        assertThat(resetCalled).isEqualTo(2)
-    }
-
-    @Test
-    fun testNewState_new_buffer_created_if_text_is_different() {
-        val state = TextFieldState()
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
-        state.resetStateAndNotifyIme(textFieldValue)
-        val initialBuffer = state.mainBuffer
-
-        val newTextFieldValue = TextFieldCharSequence("abc")
-        state.resetStateAndNotifyIme(newTextFieldValue)
-
-        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
-        assertThat(resetCalled).isEqualTo(2)
-    }
-
-    @Test
-    fun testNewState_buffer_not_recreated_if_selection_is_different() {
-        val state = TextFieldState()
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
-        state.resetStateAndNotifyIme(textFieldValue)
-        val initialBuffer = state.mainBuffer
-
-        val newTextFieldValue = TextFieldCharSequence(textFieldValue, selection = TextRange(1))
-        state.resetStateAndNotifyIme(newTextFieldValue)
-
-        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
-        assertThat(newTextFieldValue.selectionInChars.start)
-            .isEqualTo(state.mainBuffer.selectionStart)
-        assertThat(newTextFieldValue.selectionInChars.end).isEqualTo(
-            state.mainBuffer.selectionEnd
-        )
-        assertThat(resetCalled).isEqualTo(2)
-    }
-
-    @Test
-    fun testNewState_buffer_not_recreated_if_composition_is_different() {
-        val state = TextFieldState()
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange(1))
-        state.resetStateAndNotifyIme(textFieldValue)
-        val initialBuffer = state.mainBuffer
-
-        // composition can not be set from app, IME owns it.
-        assertThat(EditingBuffer.NOWHERE).isEqualTo(initialBuffer.compositionStart)
-        assertThat(EditingBuffer.NOWHERE).isEqualTo(initialBuffer.compositionEnd)
-
-        val newTextFieldValue = TextFieldCharSequence(
-            textFieldValue,
-            textFieldValue.selectionInChars,
-            composition = null
-        )
-        state.resetStateAndNotifyIme(newTextFieldValue)
-
-        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
-        assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionStart)
-        assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionEnd)
-        assertThat(resetCalled).isEqualTo(2)
-    }
-
-    @Test
-    fun testNewState_reversedSelection_setsTheSelection() {
-        val initialSelection = TextRange(2, 1)
-        val textFieldValue = TextFieldCharSequence("qwerty", initialSelection, TextRange(1))
-        val state = TextFieldState(textFieldValue)
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        val initialBuffer = state.mainBuffer
-
-        assertThat(initialSelection.start).isEqualTo(initialBuffer.selectionStart)
-        assertThat(initialSelection.end).isEqualTo(initialBuffer.selectionEnd)
-
-        val updatedSelection = TextRange(3, 0)
-        val newTextFieldValue = TextFieldCharSequence(textFieldValue, selection = updatedSelection)
-        // set the new selection
-        state.resetStateAndNotifyIme(newTextFieldValue)
-
-        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
-        assertThat(updatedSelection.start).isEqualTo(initialBuffer.selectionStart)
-        assertThat(updatedSelection.end).isEqualTo(initialBuffer.selectionEnd)
-        assertThat(resetCalled).isEqualTo(1)
-    }
-
-    @Test
-    fun compositionIsCleared_when_textChanged() {
-        val state = TextFieldState()
-        var resetCalled = 0
-        state.addNotifyImeListener { _, _ -> resetCalled++ }
-
-        // set the initial value
-        state.editAsUser {
-            commitText("ab", 0)
-            setComposingRegion(0, 2)
-        }
-
-        // change the text
-        val newValue =
-            TextFieldCharSequence(
-                "cd",
-                state.text.selectionInChars,
-                state.text.compositionInChars
-            )
-        state.resetStateAndNotifyIme(newValue)
-
-        assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.compositionInChars).isNull()
-    }
-
-    @Test
-    fun compositionIsNotCleared_when_textIsSame() {
-        val state = TextFieldState()
-        val composition = TextRange(0, 2)
-
-        // set the initial value
-        state.editAsUser {
-            commitText("ab", 0)
-            setComposingRegion(composition.start, composition.end)
-        }
-
-        // use the same TextFieldValue
-        val newValue =
-            TextFieldCharSequence(
-                state.text,
-                state.text.selectionInChars,
-                state.text.compositionInChars
-            )
-        state.resetStateAndNotifyIme(newValue)
-
-        assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.compositionInChars).isEqualTo(composition)
-    }
-
-    @Test
-    fun compositionIsCleared_when_compositionReset() {
-        val state = TextFieldState()
-
-        // set the initial value
-        state.editAsUser {
-            commitText("ab", 0)
-            setComposingRegion(-1, -1)
-        }
-
-        // change the composition
-        val newValue =
-            TextFieldCharSequence(
-                state.text,
-                state.text.selectionInChars,
-                composition = TextRange(0, 2)
-            )
-        state.resetStateAndNotifyIme(newValue)
-
-        assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.compositionInChars).isNull()
-    }
-
-    @Test
-    fun compositionIsCleared_when_compositionChanged() {
-        val state = TextFieldState()
-
-        // set the initial value
-        state.editAsUser {
-            commitText("ab", 0)
-            setComposingRegion(0, 2)
-        }
-
-        // change the composition
-        val newValue = TextFieldCharSequence(
-            state.text,
-            state.text.selectionInChars,
-            composition = TextRange(0, 1)
-        )
-        state.resetStateAndNotifyIme(newValue)
-
-        assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.compositionInChars).isNull()
-    }
-
-    @Test
-    fun compositionIsNotCleared_when_onlySelectionChanged() {
-        val state = TextFieldState()
-
-        val composition = TextRange(0, 2)
-        val selection = TextRange(0, 2)
-
-        // set the initial value
-        state.editAsUser {
-            commitText("ab", 0)
-            setComposingRegion(composition.start, composition.end)
-            setSelection(selection.start, selection.end)
-        }
-
-        // change selection
-        val newSelection = TextRange(1)
-        val newValue = TextFieldCharSequence(
-            state.text,
-            selection = newSelection,
-            composition = state.text.compositionInChars
-        )
-        state.resetStateAndNotifyIme(newValue)
-
-        assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.compositionInChars).isEqualTo(composition)
-        assertThat(state.text.selectionInChars).isEqualTo(newSelection)
-    }
-
-    @Test
-    fun filterThatDoesNothing_doesNotResetBuffer() {
-        val state = TextFieldState(
-            TextFieldCharSequence(
-                "abc",
-                selection = TextRange(3),
-                composition = TextRange(0, 3)
-            )
-        )
-
-        val initialBuffer = state.mainBuffer
-
-        state.editAsUser { commitText("d", 4) }
-
-        val value = state.text
-
-        assertThat(value.toString()).isEqualTo("abcd")
-        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
-    }
-
-    @Test
-    fun returningTheEquivalentValueFromFilter_doesNotResetBuffer() {
-        val state = TextFieldState(
-            TextFieldCharSequence(
-                "abc",
-                selection = TextRange(3),
-                composition = TextRange(0, 3)
-            )
-        )
-
-        val initialBuffer = state.mainBuffer
-
-        state.editAsUser { commitText("d", 4) }
-
-        val value = state.text
-
-        assertThat(value.toString()).isEqualTo("abcd")
-        assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
-    }
-
-    @Test
-    fun returningOldValueFromFilter_resetsTheBuffer() {
-        val state = TextFieldState(
-            TextFieldCharSequence(
-                "abc",
-                selection = TextRange(3),
-                composition = TextRange(0, 3)
-            )
-        )
-
-        var resetCalledOld: TextFieldCharSequence? = null
-        var resetCalledNew: TextFieldCharSequence? = null
-        state.addNotifyImeListener { old, new ->
-            resetCalledOld = old
-            resetCalledNew = new
-        }
-
-        val initialBuffer = state.mainBuffer
-
-        state.editAsUser(
-            inputTransformation = { _, new -> new.revertAllChanges() },
-            notifyImeOfChanges = false
-        ) {
-            commitText("d", 4)
-        }
-
-        val value = state.text
-
-        assertThat(value.toString()).isEqualTo("abc")
-        assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
-        assertThat(resetCalledOld?.toString()).isEqualTo("abcd") // what IME applied
-        assertThat(resetCalledNew?.toString()).isEqualTo("abc") // what is decided by filter
-    }
-
-    @Test
-    fun filterNotRan_whenNoCommands() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            fail("filter ran, old=\"$old\", new=\"$new\"")
-        }
-
-        state.editAsUser(inputTransformation, notifyImeOfChanges = false) {}
-    }
-
-    @Test
-    fun filterNotRan_whenOnlyFinishComposingTextCommand_noComposition() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            fail("filter ran, old=\"$old\", new=\"$new\"")
-        }
-
-        state.editAsUser(
-            inputTransformation = inputTransformation,
-            notifyImeOfChanges = false
-        ) { finishComposingText() }
-    }
-
-    @Test
-    fun filterNotRan_whenOnlyFinishComposingTextCommand_withComposition() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = TextRange(0, 5))
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            fail("filter ran, old=\"$old\", new=\"$new\"")
-        }
-
-        state.editAsUser(
-            inputTransformation = inputTransformation,
-            notifyImeOfChanges = false
-        ) { finishComposingText() }
-    }
-
-    @Test
-    fun filterNotRan_whenCommandsResultInInitialValue() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = TextRange(0, 5))
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            fail(
-                "filter ran, old=\"$old\" (${old.selectionInChars}), " +
-                    "new=\"$new\" (${new.selectionInChars})"
-            )
-        }
-
-        state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
-            setComposingRegion(0, 5)
-            commitText("hello", 1)
-            setSelection(2, 2)
-        }
-    }
-
-    @Test
-    fun filterRan_whenOnlySelectionChanges() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
-        var filterRan = false
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            // Filter should only run once.
-            assertThat(filterRan).isFalse()
-            filterRan = true
-            assertThat(new.toString()).isEqualTo(old.toString())
-            assertThat(old.selectionInChars).isEqualTo(TextRange(2))
-            assertThat(new.selectionInChars).isEqualTo(TextRange(0, 5))
-        }
-
-        state.editAsUser(
-            inputTransformation = inputTransformation,
-            notifyImeOfChanges = false
-        ) { setSelection(0, 5) }
-    }
-
-    @Test
-    fun filterRan_whenOnlyTextChanges() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(2), composition = null)
-        var filterRan = false
-        val state = TextFieldState(initialValue)
-        val inputTransformation = InputTransformation { old, new ->
-            // Filter should only run once.
-            assertThat(filterRan).isFalse()
-            filterRan = true
-            assertThat(new.selectionInChars).isEqualTo(old.selectionInChars)
-            assertThat(old.toString()).isEqualTo("hello")
-            assertThat(new.toString()).isEqualTo("world")
-        }
-
-        state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
-            deleteAll()
-            commitText("world", 1)
-            setSelection(2, 2)
-        }
-    }
-
-    @Test
-    fun stateUpdated_whenOnlyCompositionChanges_noFilter() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(5), composition = TextRange(0, 5))
-        val state = TextFieldState(initialValue)
-
-        state.editAsUser { setComposingRegion(2, 3) }
-
-        assertThat(state.text.compositionInChars).isEqualTo(TextRange(2, 3))
-    }
-
-    @Test
-    fun stateUpdated_whenOnlyCompositionChanges_withFilter() {
-        val initialValue =
-            TextFieldCharSequence("hello", selection = TextRange(5), composition = TextRange(0, 5))
-        val state = TextFieldState(initialValue)
-
-        state.editAsUser { setComposingRegion(2, 3) }
-
-        assertThat(state.text.compositionInChars).isEqualTo(TextRange(2, 3))
-    }
-
-    private fun TextFieldState(
-        value: TextFieldCharSequence
-    ) = TextFieldState(value.toString(), value.selectionInChars)
-
-    private fun TextFieldState.editAsUser(block: EditingBuffer.() -> Unit) {
-        editAsUser(inputTransformation = null, notifyImeOfChanges = false, block = block)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArrayTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArrayTest.kt
deleted file mode 100644
index 46be131..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArrayTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFails
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class ToCharArrayTest {
-
-    private val sources = listOf(
-        "hello",
-        TextFieldCharSequence("hello"),
-        CustomCharSequence("hello"),
-    )
-
-    private val dest = CharArray(10)
-
-    @Test
-    fun toCharArray_invalidSourceStartIndex() {
-        sources.forEach { source ->
-            assertFails {
-                source.toCharArray(dest, 0, source.length + 1, source.length + 2)
-            }
-        }
-    }
-
-    @Test
-    fun toCharArray_invalidSourceEndIndex() {
-        sources.forEach { source ->
-            assertFails {
-                source.toCharArray(dest, 0, 0, source.length + 1)
-            }
-        }
-    }
-
-    @Test
-    fun toCharArray_invalidDestStartIndex() {
-        sources.forEach { source ->
-            assertFails {
-                source.toCharArray(dest, dest.size + 1, 0, 1)
-            }
-        }
-    }
-
-    @Test
-    fun toCharArray_invalidDestEndIndex() {
-        sources.forEach { source ->
-            assertFails {
-                source.toCharArray(dest, dest.size, 0, 1)
-            }
-        }
-    }
-
-    @Test
-    fun toCharArray_copiesChars_toStartOfDest() {
-        sources.forEach { source ->
-            dest.fill(Char(0))
-            source.toCharArray(dest, 0, 0, source.length)
-            assertThat(dest).asList().containsExactly(
-                'h', 'e', 'l', 'l', 'o', Char(0), Char(0), Char(0), Char(0), Char(0)
-            ).inOrder()
-        }
-    }
-
-    @Test
-    fun toCharArray_copiesChars_toEndOfDest() {
-        sources.forEach { source ->
-            dest.fill(Char(0))
-            source.toCharArray(dest, 5, 0, source.length)
-            assertThat(dest).asList().containsExactly(
-                Char(0), Char(0), Char(0), Char(0), Char(0), 'h', 'e', 'l', 'l', 'o'
-            ).inOrder()
-        }
-    }
-
-    private class CustomCharSequence(text: String) : CharSequence by text
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldStateTest.kt
deleted file mode 100644
index a4e9310..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldStateTest.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.OutputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.delete
-import androidx.compose.foundation.text2.input.insert
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TransformedTextFieldStateTest {
-
-    @Test
-    fun outputTransformationAffectsPresentedAndVisualText() {
-        val state = TextFieldState("hello")
-        val outputTransformation = OutputTransformation {
-            append("world")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.untransformedText.toString()).isEqualTo("hello")
-        assertThat(transformedState.outputText.toString()).isEqualTo("helloworld")
-        assertThat(transformedState.visualText.toString()).isEqualTo("helloworld")
-    }
-
-    @Test
-    fun mapToTransformed_insertions() {
-        val state = TextFieldState("zzzz")
-        val outputTransformation = OutputTransformation {
-            insert(2, "bb")
-            insert(0, "aa")
-            append("cc")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("aazzbbzzcc")
-        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0, 2))
-        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(3))
-        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(4, 6))
-        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(7))
-        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(8, 10))
-        // Past the end.
-        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(11))
-    }
-
-    @Test
-    fun mapToTransformed_deletions() {
-        val state = TextFieldState("aazzbbzzcc")
-        val outputTransformation = OutputTransformation {
-            delete(8, 10)
-            delete(4, 6)
-            delete(0, 2)
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("zzzz")
-        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(1))
-        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapToTransformed(6)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapToTransformed(7)).isEqualTo(TextRange(3))
-        assertThat(transformedState.mapToTransformed(8)).isEqualTo(TextRange(4))
-        assertThat(transformedState.mapToTransformed(9)).isEqualTo(TextRange(4))
-        assertThat(transformedState.mapToTransformed(10)).isEqualTo(TextRange(4))
-        // Past the end.
-        assertThat(transformedState.mapToTransformed(11)).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun mapToTransformed_replacements() {
-        val state = TextFieldState("aabb")
-        val outputTransformation = OutputTransformation {
-            replace(2, 4, "ddd")
-            replace(0, 2, "c")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("cddd")
-        assertThat(transformedState.mapToTransformed(0)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapToTransformed(1)).isEqualTo(TextRange(0, 1))
-        assertThat(transformedState.mapToTransformed(2)).isEqualTo(TextRange(1))
-        assertThat(transformedState.mapToTransformed(3)).isEqualTo(TextRange(1, 4))
-        assertThat(transformedState.mapToTransformed(4)).isEqualTo(TextRange(4))
-        // Past the end.
-        assertThat(transformedState.mapToTransformed(5)).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun mapFromTransformed_insertions() {
-        val state = TextFieldState("zzzz")
-        val outputTransformation = OutputTransformation {
-            insert(2, "bb")
-            insert(0, "aa")
-            append("cc")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("aazzbbzzcc")
-        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(1))
-        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapFromTransformed(6)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapFromTransformed(7)).isEqualTo(TextRange(3))
-        assertThat(transformedState.mapFromTransformed(8)).isEqualTo(TextRange(4))
-        assertThat(transformedState.mapFromTransformed(9)).isEqualTo(TextRange(4))
-        assertThat(transformedState.mapFromTransformed(10)).isEqualTo(TextRange(4))
-        // Past the end.
-        assertThat(transformedState.mapFromTransformed(11)).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun mapFromTransformed_deletions() {
-        val state = TextFieldState("aazzbbzzcc")
-        val outputTransformation = OutputTransformation {
-            delete(8, 10)
-            delete(4, 6)
-            delete(0, 2)
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("zzzz")
-        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0, 2))
-        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(3))
-        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(4, 6))
-        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(7))
-        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(8, 10))
-        // Past the end.
-        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(11))
-    }
-
-    @Test
-    fun mapFromTransformed_replacements() {
-        val state = TextFieldState("aabb")
-        val outputTransformation = OutputTransformation {
-            replace(2, 4, "ddd")
-            replace(0, 2, "c")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-
-        assertThat(transformedState.outputText.toString()).isEqualTo("cddd")
-        assertThat(transformedState.mapFromTransformed(0)).isEqualTo(TextRange(0))
-        assertThat(transformedState.mapFromTransformed(1)).isEqualTo(TextRange(2))
-        assertThat(transformedState.mapFromTransformed(2)).isEqualTo(TextRange(2, 4))
-        assertThat(transformedState.mapFromTransformed(3)).isEqualTo(TextRange(2, 4))
-        assertThat(transformedState.mapFromTransformed(4)).isEqualTo(TextRange(4))
-        // Past the end.
-        assertThat(transformedState.mapFromTransformed(5)).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun textFieldStateSelection_isTransformed_toPresentedText() {
-        val state = TextFieldState("hello")
-        val outputTransformation = OutputTransformation {
-            insert(0, "aa")
-        }
-        val transformedState = TransformedTextFieldState(
-            textFieldState = state,
-            outputTransformation = outputTransformation
-        )
-        assertThat(transformedState.outputText.toString()).isEqualTo("aahello")
-
-        state.edit { selectCharsIn(TextRange(0, 2)) }
-        assertThat(transformedState.outputText.selectionInChars).isEqualTo(TextRange(0, 4))
-        // Rest of indices and wedge affinity are covered by mapToTransformed tests.
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextSelectionMovementTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextSelectionMovementTest.kt
deleted file mode 100644
index d112cf7a..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextSelectionMovementTest.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.OutputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.delete
-import androidx.compose.foundation.text2.input.insert
-import androidx.compose.foundation.text2.input.internal.selection.calculateAdjacentCursorPosition
-import androidx.compose.ui.text.TextRange
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TransformedTextSelectionMovementTest {
-
-    @Test
-    fun calculateNextCursorPosition_aroundReplacement() {
-        val state = TextFieldState("abc", initialSelectionInChars = TextRange(1))
-        val outputTransformation = OutputTransformation {
-            replace(1, 2, "zz") // "azzc"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-
-        calculateNextCursorPosition(transformedState)
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
-    }
-
-    @Test
-    fun calculatePreviousCursorPosition_aroundReplacement() {
-        val state = TextFieldState("abc", initialSelectionInChars = TextRange(2))
-        val outputTransformation = OutputTransformation {
-            replace(1, 2, "zz") // "azzc"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
-
-        calculatePreviousCursorPosition(transformedState)
-
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
-    }
-
-    @Test
-    fun calculateNextCursorPosition_aroundInsertion() {
-        val state = TextFieldState("ab", initialSelectionInChars = TextRange(0))
-        val outputTransformation = OutputTransformation {
-            insert(1, "zz") // "azzb"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-
-        calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
-
-        calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
-
-        calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(4))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
-    }
-
-    @Test
-    fun calculatePreviousCursorPosition_aroundInsertion() {
-        val state = TextFieldState("ab", initialSelectionInChars = TextRange(2))
-        val outputTransformation = OutputTransformation {
-            insert(1, "zz") // "azzb"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(4))
-
-        calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(3))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
-
-        calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
-
-        calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(0))
-        assertThat(transformedState.selectionWedgeAffinity)
-            .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
-    }
-
-    @Test
-    fun calculateNextCursorPosition_aroundDeletion() {
-        val state = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
-        val outputTransformation = OutputTransformation {
-            delete(1, 3) // "ad"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-
-        calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 3))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
-
-        calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun calculatePreviousCursorPosition_aroundDeletion() {
-        val state = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
-        val outputTransformation = OutputTransformation {
-            delete(1, 3) // "ad"
-        }
-        val transformedState =
-            TransformedTextFieldState(state, outputTransformation = outputTransformation)
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(2))
-
-        calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1, 3))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(1))
-
-        calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-        assertThat(transformedState.visualText.selectionInChars).isEqualTo(TextRange(0))
-    }
-
-    private fun calculateNextCursorPosition(state: TransformedTextFieldState) {
-        val newCursor = calculateAdjacentCursorPosition(
-            state.visualText.toString(),
-            state.visualText.selectionInChars.end,
-            forward = true,
-            state
-        )
-        state.placeCursorBeforeCharAt(newCursor)
-    }
-
-    private fun calculatePreviousCursorPosition(state: TransformedTextFieldState) {
-        val newCursor = calculateAdjacentCursorPosition(
-            state.visualText.toString(),
-            state.visualText.selectionInChars.end,
-            forward = false,
-            state
-        )
-        state.placeCursorBeforeCharAt(newCursor)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/matchers/EditBufferSubject.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/matchers/EditBufferSubject.kt
deleted file mode 100644
index d0ff952..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/matchers/EditBufferSubject.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-@file:OptIn(InternalFoundationTextApi::class)
-
-package androidx.compose.foundation.text2.input.internal.matchers
-
-import androidx.compose.foundation.text.InternalFoundationTextApi
-import androidx.compose.foundation.text2.input.internal.EditingBuffer
-import androidx.compose.foundation.text2.input.internal.PartialGapBuffer
-import com.google.common.truth.FailureMetadata
-import com.google.common.truth.Subject
-import com.google.common.truth.Subject.Factory
-import com.google.common.truth.Truth.assertAbout
-import com.google.common.truth.Truth.assertThat
-
-@OptIn(InternalFoundationTextApi::class)
-internal fun assertThat(buffer: PartialGapBuffer): EditBufferSubject {
-    return assertAbout(EditBufferSubject.SUBJECT_FACTORY)
-        .that(GapBufferWrapper(buffer))!!
-}
-
-internal fun assertThat(buffer: EditingBuffer): EditBufferSubject {
-    return assertAbout(EditBufferSubject.SUBJECT_FACTORY)
-        .that(EditingBufferWrapper(buffer))!!
-}
-
-internal abstract class GetOperatorWrapper(val buffer: Any) {
-    abstract operator fun get(index: Int): Char
-    override fun toString(): String = buffer.toString()
-}
-
-private class EditingBufferWrapper(buffer: EditingBuffer) : GetOperatorWrapper(buffer) {
-    override fun get(index: Int): Char = (buffer as EditingBuffer)[index]
-}
-
-@OptIn(InternalFoundationTextApi::class)
-private class GapBufferWrapper(buffer: PartialGapBuffer) : GetOperatorWrapper(buffer) {
-    override fun get(index: Int): Char = (buffer as PartialGapBuffer)[index]
-}
-
-/**
- * Truth extension for Editing Buffers.
- */
-internal class EditBufferSubject private constructor(
-    failureMetadata: FailureMetadata?,
-    private val subject: GetOperatorWrapper
-) : Subject(failureMetadata, subject) {
-
-    companion object {
-        internal val SUBJECT_FACTORY: Factory<EditBufferSubject, GetOperatorWrapper> =
-            Factory { failureMetadata, subject -> EditBufferSubject(failureMetadata, subject) }
-    }
-
-    fun hasChars(expected: String) {
-        assertThat(subject.buffer.toString()).isEqualTo(expected)
-        for (i in expected.indices) {
-            assertThat(subject[i]).isEqualTo(expected[i])
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.kt
deleted file mode 100644
index 853fe34..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.allCaps
-import androidx.compose.foundation.text2.input.internal.commitText
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.intl.Locale
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalFoundationApi::class)
-@RunWith(JUnit4::class)
-class TextUndoTest {
-
-    @Test
-    fun insertionFromEndPointCanMerge() {
-        val state = TextFieldState()
-        state.typeAtEnd("a")
-        state.typeAtEnd("b")
-        state.typeAtEnd("c")
-
-        assertThat(state.text.toString()).isEqualTo("abc")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun insertionFromStartPointCannotMerge() {
-        val state = TextFieldState()
-        state.typeAtStart("a")
-        state.typeAtStart("b")
-        state.typeAtStart("c")
-
-        assertThat(state.text.toString()).isEqualTo("cba")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ba")
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("a")
-    }
-
-    @Test
-    fun insertionFromMiddleCannotMerge() {
-        val state = TextFieldState()
-        state.typeAtEnd("a")
-        state.typeAtEnd("c")
-        state.typeAt(1, "b")
-
-        assertThat(state.text.toString()).isEqualTo("abc")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ac")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun deletionFromEndPointCanMerge() {
-        val state = TextFieldState("abc")
-        state.placeCursorAt(3)
-        state.deleteAt(2)
-        state.deleteAt(1)
-        state.deleteAt(0)
-
-        assertThat(state.text.toString()).isEqualTo("")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-
-        assertThat(state.text.toString()).isEqualTo("abc")
-    }
-
-    @Test
-    fun deletionFromStartPointCanMerge() {
-        val state = TextFieldState("abc")
-        state.placeCursorAt(0)
-        state.deleteAt(0)
-        state.deleteAt(0)
-        state.deleteAt(0)
-
-        assertThat(state.text.toString()).isEqualTo("")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abc")
-    }
-
-    @Test
-    fun deletionFromMiddleCannotMerge() {
-        val state = TextFieldState("abc")
-        state.placeCursorAt(2) // "ab|c"
-        state.deleteAt(1) // "a|c"
-        state.deleteAt(1) // "a|"
-        state.deleteAt(0) // "|"
-
-        assertThat(state.text.toString()).isEqualTo("")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("a")
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ac")
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abc")
-    }
-
-    @Test
-    fun deletionsWithDifferentDirectionsCannotMerge() {
-        val state = TextFieldState("abcdef")
-        state.placeCursorAt(3) // "abc|def"
-        state.deleteAt(2) // "ab|def"
-        state.deleteAt(2) // "ab|ef"
-
-        assertThat(state.text.toString()).isEqualTo("abef")
-        assertThat(state.undoState.canUndo).isTrue()
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abdef")
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abcdef")
-    }
-
-    @Test
-    fun insertionAndDeletionNeverMerge() {
-        val state = TextFieldState()
-        state.typeAtEnd("a") // "a|"
-        state.typeAtEnd("b") // "ab|"
-        state.deleteAt(1) // "a|"
-        state.typeAtStart("c") // "|a" -> "c|a"
-
-        assertThat(state.text.toString()).isEqualTo("ca")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("a")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun replaceDoesNotMergeWithInsertion() {
-        val state = TextFieldState("abc")
-        state.typeAtEnd("d") // "abcd|"
-        state.replaceAt(0, 4, "def") // "def|"
-        state.typeAtEnd("g") // "defg|"
-
-        assertThat(state.text.toString()).isEqualTo("defg")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("def")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abcd")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-    }
-
-    @Test
-    fun replaceDoesNotMergeWithDeletion() {
-        val state = TextFieldState("abc")
-        state.placeCursorAt(3)
-        state.deleteAt(2) // "ab|"
-        state.replaceAt(0, 2, "def") // "def|"
-        state.deleteAt(2) // "de|"
-
-        assertThat(state.text.toString()).isEqualTo("de")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("def")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-    }
-
-    @Test
-    fun newLineInsert_doesNotMerge() {
-        val state = TextFieldState()
-        state.typeAtEnd("a") // "a|"
-        state.typeAtEnd("b") // "ab|"
-        state.typeAtEnd("\n") // "ab\n|"
-        state.typeAtEnd("c") // "ab\nc|"
-
-        assertThat(state.text.toString()).isEqualTo("ab\nc")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ab\n")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("")
-    }
-
-    @Test
-    fun undoRecoversSelectionState() {
-        val state = TextFieldState("abc")
-        state.select(2, 0) // "|ab|c"
-        state.type("d") // "d|c"
-
-        assertThat(state.text.toString()).isEqualTo("dc")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-
-        state.undoState.undo()
-        assertThat(state.text.toString()).isEqualTo("abc")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2, 0))
-    }
-
-    @Test
-    fun redoDoesNotRecoverSelectionState() {
-        val state = TextFieldState("abc")
-        state.typeAtEnd("d") // "abcd|"
-        state.select(2, 0) // "|ab|cd"
-        state.type("e") // "e|cd"
-
-        assertThat(state.text.toString()).isEqualTo("ecd")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
-
-        state.undoState.undo() // "|ab|cd"
-        state.undoState.undo() // "|abc"
-        state.undoState.redo() // "abcd|"
-
-        assertThat(state.text.toString()).isEqualTo("abcd")
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 4))
-    }
-
-    @Test
-    fun undoHistoryIncludesInputTransformation() {
-        val allCapsTransformation = InputTransformation.allCaps(Locale.current)
-        val state = TextFieldState("abc", TextRange(3))
-
-        // this test also tests for AllCapsTransformation
-        state.editAsUser(inputTransformation = allCapsTransformation) {
-            commitComposition()
-            commitText("d", 1)
-        } // "abcD|"
-        state.editAsUser(inputTransformation = allCapsTransformation) {
-            commitComposition()
-            commitText("e", 1)
-        } // "abcDE|"
-
-        state.undoState.undo() // "abc|"
-        assertThat(state.text.toString()).isEqualTo("abc")
-
-        state.undoState.redo() // "abcDE|"
-        assertThat(state.text.toString()).isEqualTo("abcDE")
-    }
-
-    @Test
-    fun directEditsClearTheUndoHistory() {
-        val state = TextFieldState("abc")
-        state.typeAtEnd("d")
-        state.typeAtStart("e")
-        state.typeAtEnd("f")
-
-        state.edit { replace(0, 1, "x") }
-
-        assertThat(state.undoState.canUndo).isEqualTo(false)
-        assertThat(state.undoState.canRedo).isEqualTo(false)
-    }
-
-    companion object {
-
-        private fun TextFieldState.typeAtEnd(text: String) {
-            placeCursorAt(this.text.length)
-            typeAt(this.text.length, text)
-        }
-
-        private fun TextFieldState.typeAtStart(text: String) {
-            placeCursorAt(0)
-            typeAt(0, text)
-        }
-
-        private fun TextFieldState.typeAt(index: Int, text: String) {
-            placeCursorAt(index)
-            editAsUser(inputTransformation = null) {
-                replace(index, index, text)
-            }
-        }
-
-        private fun TextFieldState.type(text: String) {
-            editAsUser(inputTransformation = null) {
-                commitComposition()
-                commitText(text, 1)
-            }
-        }
-
-        private fun TextFieldState.deleteAt(index: Int) {
-            editAsUser(inputTransformation = null) {
-                delete(index, index + 1)
-            }
-        }
-
-        private fun TextFieldState.placeCursorAt(index: Int) {
-            select(index, index)
-        }
-
-        private fun TextFieldState.select(start: Int, end: Int) {
-            editAsUser(inputTransformation = null) {
-                setSelection(start, end)
-            }
-        }
-
-        private fun TextFieldState.replaceAt(start: Int, end: Int, newText: String) {
-            editAsUser(inputTransformation = null) {
-                replace(start, end, newText)
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt
deleted file mode 100644
index 750fbe6..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.runtime.saveable.autoSaver
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertNotNull
-import org.junit.Test
-
-class UndoManagerSaverTest {
-
-    @Test
-    fun savesAndRestoresTextAndSelection() {
-        val undoManager = UndoManager<Int>()
-
-        undoManager.record(1)
-        undoManager.record(2)
-        undoManager.record(3)
-
-        undoManager.undo()
-
-        // undoStack; 1-2 redoStack; 3
-
-        val saver = UndoManager.createSaver(autoSaver<Int>())
-        val saved = with(saver) {
-            TestSaverScope.save(undoManager)
-        }
-        assertNotNull(saved)
-        val restoredState = saver.restore(saved)
-
-        assertNotNull(restoredState)
-        assertThat(restoredState.canUndo).isTrue()
-        assertThat(restoredState.canRedo).isTrue()
-
-        var redoValue = undoManager.redo()
-
-        assertThat(redoValue).isEqualTo(3)
-
-        val undoValues = mutableListOf<Int>()
-        while (undoManager.canUndo) {
-            undoValues += undoManager.undo()
-        }
-
-        assertThat(undoValues).containsExactly(3, 2, 1)
-    }
-
-    private object TestSaverScope : SaverScope {
-        override fun canBeSaved(value: Any): Boolean = true
-    }
-}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt
deleted file mode 100644
index cbae21e..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class UndoManagerTest {
-
-    @Test
-    fun negativeCapacityThrows() {
-        assertFailsWith<IllegalArgumentException>("Capacity must be a positive integer") {
-            UndoManager<Int>(capacity = -1)
-        }
-    }
-
-    @Test
-    fun initialLowCapacityThrows_undoStack() {
-        assertFailsWith<IllegalArgumentException>(
-            getInitialCapacityErrorMessage(
-                capacity = 1,
-                totalStackSize = 2
-            )
-        ) {
-            UndoManager(
-                initialUndoStack = listOf(1, 2),
-                capacity = 1
-            )
-        }
-    }
-
-    @Test
-    fun initialLowCapacityThrows_redoStack() {
-        assertFailsWith<IllegalArgumentException>(
-            getInitialCapacityErrorMessage(
-                capacity = 1,
-                totalStackSize = 2
-            )
-        ) {
-            UndoManager(
-                initialRedoStack = listOf(1, 2),
-                capacity = 1
-            )
-        }
-    }
-
-    @Test
-    fun initialLowCapacityThrows_bothStacks() {
-        assertFailsWith<IllegalArgumentException>(
-            getInitialCapacityErrorMessage(
-                capacity = 3,
-                totalStackSize = 4
-            )
-        ) {
-            UndoManager(
-                initialUndoStack = listOf(1, 2),
-                initialRedoStack = listOf(1, 2),
-                capacity = 3
-            )
-        }
-    }
-
-    @Test
-    fun commitSingleItem_canUndo() {
-        val undoManager = UndoManager<Int>()
-        undoManager.record(1)
-
-        assertThat(undoManager.canUndo).isTrue()
-        assertThat(undoManager.canRedo).isFalse()
-
-        undoManager.undo()
-        assertThat(undoManager.canUndo).isFalse()
-        assertThat(undoManager.canRedo).isTrue()
-    }
-
-    @Test
-    fun cannotRedoWithoutFirstUndo() {
-        val undoManager = UndoManager<Int>()
-        undoManager.record(1)
-        undoManager.record(2)
-        undoManager.record(3)
-
-        assertThat(undoManager.canRedo).isFalse()
-        assertFailsWith<IllegalStateException>(
-            "It's an error to call redo while there is nothing to redo. " +
-                "Please first check `canRedo` value before calling the `redo` function."
-        ) {
-            undoManager.redo()
-        }
-    }
-
-    @Test
-    fun commitItem_clearsRedoStack() {
-        val undoManager = UndoManager<Int>()
-        undoManager.record(1)
-        undoManager.record(2)
-        undoManager.record(3)
-
-        undoManager.undo()
-        assertThat(undoManager.canRedo).isTrue()
-
-        undoManager.record(4)
-        assertThat(undoManager.canRedo).isFalse()
-    }
-
-    @Test
-    fun clearHistoryRemovesUndoAndRedo() {
-        val undoManager = UndoManager<Int>()
-        undoManager.record(1)
-        undoManager.record(2)
-        undoManager.record(3)
-
-        undoManager.undo()
-
-        assertThat(undoManager.canUndo).isTrue()
-        assertThat(undoManager.canRedo).isTrue()
-
-        undoManager.clearHistory()
-
-        assertThat(undoManager.canUndo).isFalse()
-        assertThat(undoManager.canRedo).isFalse()
-    }
-
-    @Test
-    fun capacityOverflow_removesFromTheBottomOfStack() {
-        val undoManager = UndoManager<Int>(capacity = 2)
-        undoManager.record(1)
-        undoManager.record(2)
-        // overflow the capacity, undo history should forget the first item
-        undoManager.record(3)
-
-        var item = undoManager.undo()
-        assertThat(item).isEqualTo(3)
-
-        item = undoManager.undo()
-        assertThat(item).isEqualTo(2)
-
-        assertThat(undoManager.canUndo).isFalse()
-    }
-
-    @Test
-    fun capacityOverflow_shouldRemoveRedoActionsFirst() {
-        val undoManager = UndoManager<Int>(capacity = 20)
-        undoManager.record(1)
-        undoManager.record(2)
-        undoManager.record(3)
-        undoManager.record(4)
-
-        undoManager.undo() // total size does not change  undo; 1-2-3 redo; 4
-        undoManager.undo() // total size does not change  undo; 1-2 redo; 4-3
-
-        // this should not remove anything from the undo stack, auto removed items from redo should
-        // suffice
-        undoManager.record(5)
-
-        assertThat(undoManager.canUndo).isTrue()
-        assertThat(undoManager.canRedo).isFalse()
-
-        var item = undoManager.undo()
-        assertThat(item).isEqualTo(5)
-
-        item = undoManager.undo()
-        assertThat(item).isEqualTo(2)
-
-        item = undoManager.undo()
-        assertThat(item).isEqualTo(1)
-    }
-
-    private fun getInitialCapacityErrorMessage(capacity: Int, totalStackSize: Int) =
-        "Initial list of undo and redo operations have a size=($totalStackSize) greater " +
-        "than the given capacity=($capacity)."
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index b0a251e..1fabc9c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -51,7 +51,6 @@
 import androidx.compose.ui.node.invalidateSemantics
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.disabled
@@ -181,31 +180,19 @@
     onClickLabel: String? = null,
     role: Role? = null,
     onClick: () -> Unit
-) = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "clickable"
-        properties["interactionSource"] = interactionSource
-        properties["indication"] = indication
-        properties["enabled"] = enabled
-        properties["onClickLabel"] = onClickLabel
-        properties["role"] = role
-        properties["onClick"] = onClick
-    }
-) {
-    clickableWithIndicationIfNeeded(
+) = clickableWithIndicationIfNeeded(
+    enabled = enabled,
+    interactionSource = interactionSource,
+    indication = indication
+) { intSource, indicationNodeFactory ->
+    ClickableElement(
+        interactionSource = intSource,
+        indicationNodeFactory = indicationNodeFactory,
         enabled = enabled,
-        interactionSource = interactionSource,
-        indication = indication
-    ) { interactionSource, indicationNodeFactory ->
-        ClickableElement(
-            interactionSource = interactionSource,
-            indicationNodeFactory = indicationNodeFactory,
-            enabled = enabled,
-            onClickLabel = onClickLabel,
-            role = role,
-            onClick = onClick
-        )
-    }
+        onClickLabel = onClickLabel,
+        role = role,
+        onClick = onClick
+    )
 }
 
 /**
@@ -348,37 +335,22 @@
     onLongClick: (() -> Unit)? = null,
     onDoubleClick: (() -> Unit)? = null,
     onClick: () -> Unit
-) = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "combinedClickable"
-        properties["indication"] = indication
-        properties["interactionSource"] = interactionSource
-        properties["enabled"] = enabled
-        properties["onClickLabel"] = onClickLabel
-        properties["role"] = role
-        properties["onClick"] = onClick
-        properties["onDoubleClick"] = onDoubleClick
-        properties["onLongClick"] = onLongClick
-        properties["onLongClickLabel"] = onLongClickLabel
-    }
-) {
-    clickableWithIndicationIfNeeded(
+) = clickableWithIndicationIfNeeded(
+    enabled = enabled,
+    interactionSource = interactionSource,
+    indication = indication
+) { intSource, indicationNodeFactory ->
+    CombinedClickableElement(
+        interactionSource = intSource,
+        indicationNodeFactory = indicationNodeFactory,
         enabled = enabled,
-        interactionSource = interactionSource,
-        indication = indication
-    ) { interactionSource, indicationNodeFactory ->
-        CombinedClickableElement(
-            interactionSource = interactionSource,
-            indicationNodeFactory = indicationNodeFactory,
-            enabled = enabled,
-            onClickLabel = onClickLabel,
-            role = role,
-            onClick = onClick,
-            onLongClickLabel = onLongClickLabel,
-            onLongClick = onLongClick,
-            onDoubleClick = onDoubleClick
-        )
-    }
+        onClickLabel = onClickLabel,
+        role = role,
+        onClick = onClick,
+        onLongClickLabel = onLongClickLabel,
+        onLongClick = onLongClick,
+        onDoubleClick = onDoubleClick
+    )
 }
 
 /**
@@ -525,8 +497,15 @@
         )
     }
 
-    // Defined in the factory functions with inspectable
-    override fun InspectorInfo.inspectableProperties() = Unit
+    override fun InspectorInfo.inspectableProperties() {
+        name = "clickable"
+        properties["enabled"] = enabled
+        properties["onClick"] = onClick
+        properties["onClickLabel"] = onClickLabel
+        properties["role"] = role
+        properties["interactionSource"] = interactionSource
+        properties["indicationNodeFactory"] = indicationNodeFactory
+    }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
@@ -593,8 +572,18 @@
         )
     }
 
-    // Defined in the factory functions with inspectable
-    override fun InspectorInfo.inspectableProperties() = Unit
+    override fun InspectorInfo.inspectableProperties() {
+        name = "combinedClickable"
+        properties["indicationNodeFactory"] = indicationNodeFactory
+        properties["interactionSource"] = interactionSource
+        properties["enabled"] = enabled
+        properties["onClickLabel"] = onClickLabel
+        properties["role"] = role
+        properties["onClick"] = onClick
+        properties["onDoubleClick"] = onDoubleClick
+        properties["onLongClick"] = onLongClick
+        properties["onLongClickLabel"] = onLongClickLabel
+    }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index 41e877f..cd801d5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -39,7 +39,6 @@
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.SemanticsModifierNode
@@ -50,7 +49,6 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.requestFocus
@@ -67,6 +65,7 @@
  * @param interactionSource [MutableInteractionSource] that will be used to emit
  * [FocusInteraction.Focus] when this element is being focused.
  */
+@Stable
 fun Modifier.focusable(
     enabled: Boolean = true,
     interactionSource: MutableInteractionSource? = null,
@@ -122,16 +121,8 @@
 internal fun Modifier.focusableInNonTouchMode(
     enabled: Boolean,
     interactionSource: MutableInteractionSource?
-) = inspectable(inspectorInfo = {
-    name = "focusableInNonTouchMode"
-    properties["enabled"] = enabled
-    properties["interactionSource"] = interactionSource
-},
-    factory = {
-        Modifier
-            .then(if (enabled) FocusableInNonTouchModeElement else Modifier)
-            .focusable(enabled, interactionSource)
-    })
+) = then(if (enabled) FocusableInNonTouchModeElement else Modifier)
+    .focusable(enabled, interactionSource)
 
 private val FocusableInNonTouchModeElement =
     object : ModifierNodeElement<FocusableInNonTouchMode>() {
@@ -195,7 +186,7 @@
 @OptIn(ExperimentalFoundationApi::class)
 internal class FocusableNode(
     interactionSource: MutableInteractionSource?
-) : DelegatingNode(), FocusEventModifierNode, LayoutAwareModifierNode, SemanticsModifierNode,
+) : DelegatingNode(), FocusEventModifierNode, SemanticsModifierNode,
     GlobalPositionAwareModifierNode, FocusRequesterModifierNode {
     override val shouldAutoInvalidate: Boolean = false
 
@@ -218,14 +209,12 @@
     //    See aosp/1964580.
     private val bringIntoViewRequester = BringIntoViewRequester()
 
+    /** This is just needed for the delegate, it's not referenced anywhere directly. */
+    @Suppress("unused")
     private val bringIntoViewRequesterNode = delegate(
         BringIntoViewRequesterNode(bringIntoViewRequester)
     )
 
-    // TODO(levima) Remove this once delegation can propagate this events on its own
-    override fun onPlaced(coordinates: LayoutCoordinates) =
-        bringIntoViewRequesterNode.onPlaced(coordinates)
-
     fun update(interactionSource: MutableInteractionSource?) =
         focusableInteractionNode.update(interactionSource)
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
index 195b82d..2dc3a46f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
@@ -83,7 +83,7 @@
     }
 
     override val providedValues: ModifierLocalMap =
-        modifierLocalMapOf(entry = ModifierLocalFocusedBoundsObserver to focusBoundsObserver)
+        modifierLocalMapOf(ModifierLocalFocusedBoundsObserver to focusBoundsObserver)
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
index c0180eb..00adcf2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -37,6 +38,7 @@
  * [HoverInteraction.Enter] when this element is being hovered.
  * @param enabled Controls the enabled state. When `false`, hover events will be ignored.
  */
+@Stable
 fun Modifier.hoverable(
     interactionSource: MutableInteractionSource,
     enabled: Boolean = true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
index b2b172a..46d913c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.requireLayoutCoordinates
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.toSize
 import kotlin.math.abs
@@ -82,8 +83,6 @@
      */
     private val bringIntoViewRequests = BringIntoViewRequestPriorityQueue()
 
-    /** The [LayoutCoordinates] of this modifier (i.e. the scrollable container). */
-    private var coordinates: LayoutCoordinates? = null
     private var focusedChild: LayoutCoordinates? = null
 
     /**
@@ -134,10 +133,6 @@
         focusedChild = newBounds
     }
 
-    override fun onPlaced(coordinates: LayoutCoordinates) {
-        this.coordinates = coordinates
-    }
-
     override fun onRemeasured(size: IntSize) {
         val oldSize = viewportSize
         viewportSize = size
@@ -171,7 +166,8 @@
     }
 
     private fun getFocusedChildBounds(): Rect? {
-        val coordinates = this.coordinates?.takeIf { it.isAttached } ?: return null
+        if (!isAttached) return null
+        val coordinates = requireLayoutCoordinates()
         val focusedChild = this.focusedChild?.takeIf { it.isAttached } ?: return null
         return coordinates.localBoundingBoxOf(focusedChild, clipBounds = false)
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
index b085f24..e89f4ab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
@@ -156,32 +157,31 @@
  * behave like bottom to top and left to right will behave like right to left.
  */
 @ExperimentalFoundationApi
+@Stable
 fun Modifier.draggable2D(
     state: Draggable2DState,
     enabled: Boolean = true,
     interactionSource: MutableInteractionSource? = null,
     startDragImmediately: Boolean = false,
-    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
-    onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit = {},
+    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = NoOpOnDragStarted,
+    onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit = NoOpOnDragStopped,
     reverseDirection: Boolean = false
 ): Modifier = this then Draggable2DElement(
     state = state,
     enabled = enabled,
     interactionSource = interactionSource,
-    startDragImmediately = { startDragImmediately },
+    startDragImmediately = startDragImmediately,
     onDragStarted = onDragStarted,
     onDragStopped = onDragStopped,
-    reverseDirection = reverseDirection,
-    canDrag = { true }
+    reverseDirection = reverseDirection
 )
 
 @OptIn(ExperimentalFoundationApi::class)
 internal class Draggable2DElement(
     private val state: Draggable2DState,
-    private val canDrag: (PointerInputChange) -> Boolean,
     private val enabled: Boolean,
     private val interactionSource: MutableInteractionSource?,
-    private val startDragImmediately: () -> Boolean,
+    private val startDragImmediately: Boolean,
     private val onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
     private val onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit,
     private val reverseDirection: Boolean,
@@ -189,10 +189,10 @@
     ) : ModifierNodeElement<Draggable2DNode>() {
     override fun create(): Draggable2DNode = Draggable2DNode(
         state,
-        canDrag,
+        CanDrag,
         enabled,
         interactionSource,
-        startDragImmediately,
+        if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
         onDragStarted,
         onDragStopped,
         reverseDirection
@@ -201,10 +201,10 @@
     override fun update(node: Draggable2DNode) {
         node.update(
             state,
-            canDrag,
+            CanDrag,
             enabled,
             interactionSource,
-            startDragImmediately,
+            if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
             onDragStarted,
             onDragStopped,
             reverseDirection
@@ -219,7 +219,6 @@
         other as Draggable2DElement
 
         if (state != other.state) return false
-        if (canDrag != other.canDrag) return false
         if (enabled != other.enabled) return false
         if (interactionSource != other.interactionSource) return false
         if (startDragImmediately != other.startDragImmediately) return false
@@ -232,7 +231,6 @@
 
     override fun hashCode(): Int {
         var result = state.hashCode()
-        result = 31 * result + canDrag.hashCode()
         result = 31 * result + enabled.hashCode()
         result = 31 * result + (interactionSource?.hashCode() ?: 0)
         result = 31 * result + startDragImmediately.hashCode()
@@ -244,7 +242,6 @@
 
     override fun InspectorInfo.inspectableProperties() {
         name = "draggable2D"
-        properties["canDrag"] = canDrag
         properties["enabled"] = enabled
         properties["interactionSource"] = interactionSource
         properties["startDragImmediately"] = startDragImmediately
@@ -253,6 +250,12 @@
         properties["reverseDirection"] = reverseDirection
         properties["state"] = state
     }
+
+    companion object {
+        val StartDragImmediately = { true }
+        val DoNotStartDragImmediately = { false }
+        val CanDrag: (PointerInputChange) -> Boolean = { true }
+    }
 }
 
 @OptIn(ExperimentalFoundationApi::class)
@@ -339,3 +342,6 @@
         return onDelta(delta)
     }
 }
+
+private val NoOpOnDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {}
+private val NoOpOnDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit = {}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index da8ca4d..08f3e2c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -34,7 +34,6 @@
 import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.MotionDurationScale
@@ -408,7 +407,7 @@
             (event.key == Key.PageDown || event.key == Key.PageUp) &&
             (event.type == KeyEventType.KeyDown) &&
             (!event.isCtrlPressed)
-            ) {
+        ) {
             with(scrollingLogic) {
                 val scrollAmount: Offset = if (orientation == Orientation.Vertical) {
                     val viewportHeight = contentInViewNode.viewportSize.height
@@ -508,7 +507,7 @@
                 containerSize: Float
             ): Float {
                 val trailingEdge = offset + size
-                val leadingEdge = offset
+                @Suppress("UnnecessaryVariable") val leadingEdge = offset
                 return when {
 
                     // If the item is already visible, no need to scroll.
@@ -603,7 +602,9 @@
     val nestedScrollDispatcher: NestedScrollDispatcher,
     val interactionSource: MutableInteractionSource?
 ) : DelegatingNode() {
-    init { delegate(MouseWheelScrollNode(scrollLogic)) }
+    init {
+        delegate(MouseWheelScrollNode(scrollLogic))
+    }
 
     val draggableState = ScrollDraggableState(scrollLogic)
     private val startDragImmediately = { scrollLogic.shouldScrollImmediately() }
@@ -716,7 +717,7 @@
     private var flingBehavior: FlingBehavior,
     private var nestedScrollDispatcher: NestedScrollDispatcher,
 ) {
-    private val isNestedFlinging = mutableStateOf(false)
+
     fun Float.toOffset(): Offset = when {
         this == 0f -> Offset.Zero
         orientation == Horizontal -> Offset(this, 0f)
@@ -799,9 +800,6 @@
     }
 
     suspend fun onDragStopped(initialVelocity: Float) {
-        // Self started flinging, set
-        registerNestedFling(true)
-
         val availableVelocity = initialVelocity.toVelocity()
 
         val performFling: suspend (Velocity) -> Velocity = { velocity ->
@@ -826,9 +824,6 @@
         } else {
             performFling(availableVelocity)
         }
-
-        // Self stopped flinging, reset
-        registerNestedFling(false)
     }
 
     suspend fun doFlingAnimation(available: Velocity): Velocity {
@@ -855,14 +850,10 @@
     }
 
     fun shouldScrollImmediately(): Boolean {
-        return scrollableState.isScrollInProgress || isNestedFlinging.value ||
+        return scrollableState.isScrollInProgress ||
             overscrollEffect?.isInProgress ?: false
     }
 
-    fun registerNestedFling(isFlinging: Boolean) {
-        isNestedFlinging.value = isFlinging
-    }
-
     fun update(
         scrollableState: ScrollableState,
         orientation: Orientation,
@@ -913,13 +904,6 @@
     val scrollingLogic: ScrollingLogic,
     var enabled: Boolean
 ) : NestedScrollConnection {
-    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
-        // child will fling, set
-        if (source == Fling) {
-            scrollingLogic.registerNestedFling(true)
-        }
-        return Offset.Zero
-    }
 
     override fun onPostScroll(
         consumed: Offset,
@@ -940,9 +924,6 @@
             available - velocityLeft
         } else {
             Velocity.Zero
-        }.also {
-            // Flinging child finished flinging, reset
-            scrollingLogic.registerNestedFling(false)
         }
     }
 }
@@ -1007,8 +988,7 @@
 private class ModifierLocalScrollableContainerProvider(var enabled: Boolean) :
     ModifierLocalModifierNode,
     Modifier.Node() {
-    private val modifierLocalMap =
-        modifierLocalMapOf(entry = ModifierLocalScrollableContainer to true)
+    private val modifierLocalMap = modifierLocalMapOf(ModifierLocalScrollableContainer to true)
     override val providedValues: ModifierLocalMap
         get() = if (enabled) {
             modifierLocalMap
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPosition.kt
index 691bedf..b592443 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPosition.kt
@@ -46,6 +46,9 @@
      * content.
      * @param itemIndex The index of the item being positioned.
      * @param itemCount The total amount of items in the snapping container.
+     *
+     * @return The offset of the snap position where items will be aligned to in a snapping
+     * container.
      */
     fun position(
         layoutSize: Int,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
index 90894eb..7b75096 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
@@ -74,15 +74,13 @@
 
     private val localParent: BringIntoViewParent? get() = ModifierLocalBringIntoViewParent.current
 
-    /** The [LayoutCoordinates] of this modifier, if attached. */
-    protected var layoutCoordinates: LayoutCoordinates? = null
-        get() = field?.takeIf { it.isAttached }
+    protected var hasBeenPlaced = false
         private set
 
     protected val parent: BringIntoViewParent
         get() = localParent ?: defaultParent
 
     override fun onPlaced(coordinates: LayoutCoordinates) {
-        layoutCoordinates = coordinates
+        hasBeenPlaced = true
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
index 30cfa28..75243ce 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireLayoutCoordinates
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.toSize
 
@@ -186,12 +187,13 @@
      * is null) be brought into view by the [parent]&nbsp;[BringIntoViewParent].
      */
     suspend fun bringIntoView(rect: Rect?) {
-        parent.bringChildIntoView(layoutCoordinates ?: return) {
+        if (!isAttached) return
+        parent.bringChildIntoView(requireLayoutCoordinates()) {
             // If the rect is not specified, use a rectangle representing the entire composable.
             // If the coordinates are detached when this call is made, we don't bother even
             // submitting the request, but if the coordinates become detached while the request
             // is being handled we just return a null Rect.
-            rect ?: layoutCoordinates?.size?.toSize()?.toRect()
+            rect ?: if (isAttached) requireLayoutCoordinates().size.toSize().toRect() else null
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
index b4ceb1a..11157b0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.modifier.ModifierLocalMap
 import androidx.compose.ui.modifier.modifierLocalMapOf
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireLayoutCoordinates
 import androidx.compose.ui.platform.InspectorInfo
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
@@ -144,7 +145,7 @@
     override val shouldAutoInvalidate: Boolean = false
 
     override val providedValues: ModifierLocalMap =
-        modifierLocalMapOf(entry = ModifierLocalBringIntoViewParent to this)
+        modifierLocalMapOf(ModifierLocalBringIntoViewParent to this)
 
     /**
      * Responds to a child's request by first converting [boundsProvider] into this node's [LayoutCoordinates]
@@ -156,9 +157,13 @@
     ) {
         @Suppress("NAME_SHADOWING")
         fun localRect(): Rect? {
+            if (!isAttached) return null
+            // Can't do any calculations before the node is initially placed.
+            if (!hasBeenPlaced) return null
+
             // Either coordinates can become detached at any time, so we have to check before every
             // calculation.
-            val layoutCoordinates = layoutCoordinates ?: return null
+            val layoutCoordinates = requireLayoutCoordinates()
             val childCoordinates = childCoordinates.takeIf { it.isAttached } ?: return null
             val rect = boundsProvider() ?: return null
             return layoutCoordinates.localRectOf(childCoordinates, rect)
@@ -183,7 +188,12 @@
             // CancellationException, it will cancel this coroutineScope, which will also cancel the
             // responder's coroutine.
             launch {
-                parent.bringChildIntoView(layoutCoordinates ?: return@launch, parentRect)
+                if (isAttached) {
+                    parent.bringChildIntoView(
+                        childCoordinates = requireLayoutCoordinates(),
+                        boundsProvider = parentRect
+                    )
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
index 2f512c5..ed4711b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
@@ -29,7 +29,6 @@
 import androidx.compose.ui.node.invalidateSemantics
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.selected
@@ -140,33 +139,20 @@
     enabled: Boolean = true,
     role: Role? = null,
     onClick: () -> Unit
-) = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "selectable"
-        properties["selected"] = selected
-        properties["interactionSource"] = interactionSource
-        properties["indication"] = indication
-        properties["enabled"] = enabled
-        properties["role"] = role
-        properties["onClick"] = onClick
-    },
-    factory = {
-        clickableWithIndicationIfNeeded(
-            enabled = enabled,
-            interactionSource = interactionSource,
-            indication = indication
-        ) { interactionSource, indicationNodeFactory ->
-            SelectableElement(
-                selected = selected,
-                interactionSource = interactionSource,
-                indicationNodeFactory = indicationNodeFactory,
-                enabled = enabled,
-                role = role,
-                onClick = onClick
-            )
-        }
-    }
-)
+) = clickableWithIndicationIfNeeded(
+    enabled = enabled,
+    interactionSource = interactionSource,
+    indication = indication
+) { intSource, indicationNodeFactory ->
+    SelectableElement(
+        selected = selected,
+        interactionSource = intSource,
+        indicationNodeFactory = indicationNodeFactory,
+        enabled = enabled,
+        role = role,
+        onClick = onClick
+    )
+}
 
 private class SelectableElement(
     private val selected: Boolean,
@@ -196,8 +182,15 @@
         )
     }
 
-    // Defined in the factory functions with inspectable
-    override fun InspectorInfo.inspectableProperties() = Unit
+    override fun InspectorInfo.inspectableProperties() {
+        name = "selectable"
+        properties["selected"] = selected
+        properties["interactionSource"] = interactionSource
+        properties["indicationNodeFactory"] = indicationNodeFactory
+        properties["enabled"] = enabled
+        properties["role"] = role
+        properties["onClick"] = onClick
+    }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index ae08126..c98b15a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.node.invalidateSemantics
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.toggleableState
@@ -134,31 +133,19 @@
     enabled: Boolean = true,
     role: Role? = null,
     onValueChange: (Boolean) -> Unit
-) = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "toggleable"
-        properties["value"] = value
-        properties["interactionSource"] = interactionSource
-        properties["indication"] = indication
-        properties["enabled"] = enabled
-        properties["role"] = role
-        properties["onValueChange"] = onValueChange
-    }
-) {
-    clickableWithIndicationIfNeeded(
+) = clickableWithIndicationIfNeeded(
+    enabled = enabled,
+    interactionSource = interactionSource,
+    indication = indication
+) { intSource, indicationNodeFactory ->
+    ToggleableElement(
+        value = value,
+        interactionSource = intSource,
+        indicationNodeFactory = indicationNodeFactory,
         enabled = enabled,
-        interactionSource = interactionSource,
-        indication = indication
-    ) { interactionSource, indicationNodeFactory ->
-        ToggleableElement(
-            value = value,
-            interactionSource = interactionSource,
-            indicationNodeFactory = indicationNodeFactory,
-            enabled = enabled,
-            role = role,
-            onValueChange = onValueChange
-        )
-    }
+        role = role,
+        onValueChange = onValueChange
+    )
 }
 
 private class ToggleableElement(
@@ -189,8 +176,15 @@
         )
     }
 
-    // Defined in the factory functions with inspectable
-    override fun InspectorInfo.inspectableProperties() = Unit
+    override fun InspectorInfo.inspectableProperties() {
+        name = "toggleable"
+        properties["value"] = value
+        properties["interactionSource"] = interactionSource
+        properties["indicationNodeFactory"] = indicationNodeFactory
+        properties["enabled"] = enabled
+        properties["role"] = role
+        properties["onValueChange"] = onValueChange
+    }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
@@ -375,31 +369,19 @@
     enabled: Boolean = true,
     role: Role? = null,
     onClick: () -> Unit
-) = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "triStateToggleable"
-        properties["state"] = state
-        properties["interactionSource"] = interactionSource
-        properties["indication"] = indication
-        properties["enabled"] = enabled
-        properties["role"] = role
-        properties["onClick"] = onClick
-    }
-) {
-    clickableWithIndicationIfNeeded(
+) = clickableWithIndicationIfNeeded(
+    enabled = enabled,
+    interactionSource = interactionSource,
+    indication = indication
+) { intSource, indicationNodeFactory ->
+    TriStateToggleableElement(
+        state = state,
+        interactionSource = intSource,
+        indicationNodeFactory = indicationNodeFactory,
         enabled = enabled,
-        interactionSource = interactionSource,
-        indication = indication
-    ) { interactionSource, indicationNodeFactory ->
-        TriStateToggleableElement(
-            state = state,
-            interactionSource = interactionSource,
-            indicationNodeFactory = indicationNodeFactory,
-            enabled = enabled,
-            role = role,
-            onClick = onClick
-        )
-    }
+        role = role,
+        onClick = onClick
+    )
 }
 
 private class TriStateToggleableElement(
@@ -431,7 +413,15 @@
     }
 
     // Defined in the factory functions with inspectable
-    override fun InspectorInfo.inspectableProperties() = Unit
+    override fun InspectorInfo.inspectableProperties() {
+        name = "triStateToggleable"
+        properties["state"] = state
+        properties["interactionSource"] = interactionSource
+        properties["indicationNodeFactory"] = indicationNodeFactory
+        properties["enabled"] = enabled
+        properties["role"] = role
+        properties["onClick"] = onClick
+    }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
new file mode 100644
index 0000000..ac97ee0
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2023 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.compose.foundation.text
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.input.ImeActionHandler
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldDecorator
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.TextObfuscationMode
+import androidx.compose.foundation.text.input.internal.CodepointTransformation
+import androidx.compose.foundation.text.input.internal.mask
+import androidx.compose.foundation.text.input.internal.syncTextFieldState
+import androidx.compose.foundation.text.input.then
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.semantics.copyText
+import androidx.compose.ui.semantics.cutText
+import androidx.compose.ui.semantics.password
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.Density
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.consumeAsFlow
+import kotlinx.coroutines.launch
+
+/**
+ * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
+ * alternative to [BasicTextField2]. It only supports a single line of content and comes with
+ * default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
+ * are appropriate for entering secure content. Additionally, some context menu actions like cut,
+ * copy, and drag are disabled for added security.
+ *
+ * Whenever the user edits the text, [onValueChange] is called with the most up to date state
+ * represented by [String] with which developer is expected to update their state.
+ *
+ * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
+ * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
+ * the contents of the field will _not_ be updated to reflect the value until editing is done. When
+ * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
+ * an [inputTransformation] to accept or reject changes during editing. For more direct control of
+ * the field contents use the [BasicSecureTextField] overload that accepts a [TextFieldState].
+ *
+ * @param value The input [String] text to be shown in the text field.
+ * @param onValueChange The callback that is triggered when the user or the system updates the
+ * text. The updated text is passed as a parameter of the callback. The value passed to the callback
+ * will already have had the [inputTransformation] applied.
+ * @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
+ * @param onSubmit Called when the user submits a form either by pressing the action button in the
+ * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
+ * submits the form by pressing the action button in the IME, the provided IME action is passed to
+ * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
+ * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
+ * action has been handled completely, which will skip the default behavior, such as hiding the
+ * keyboard for the [ImeAction.Done] action.
+ * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
+ * icons on the keyboard.
+ * @param textObfuscationMode Determines the method used to obscure the input text.
+ * @param keyboardType The keyboard type to be used in this text field. It is set to
+ * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
+ * fields.
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when changing the [value] programmatically, or
+ * when the transformation is changed. If the transformation is changed on an existing text field,
+ * it will be applied to the next user edit. The transformation will not immediately affect the
+ * current [value].
+ * @param textStyle Style configuration for text content that's displayed in the editor.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, there will be no cursor drawn.
+ * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
+ * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
+ * or null if it cannot. The function reads the layout result from a snapshot state object, and will
+ * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
+ * paragraph information, size of the text, baselines and other details. The callback can be used to
+ * add additional decoration or functionality to the text. For example, to draw a cursor or
+ * selection around the text. [Density] scope is the one that was used while creating the given text
+ * layout.
+ * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
+ * messages or similar, and automatically increase the hit target area of the text field.
+ * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
+ * bounds of the text field. It controls the state of the scroll for the text field.
+ */
+@ExperimentalFoundationApi
+@Composable
+fun BasicSecureTextField(
+    value: String,
+    onValueChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+    // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
+    onSubmit: ImeActionHandler? = null,
+    imeAction: ImeAction = ImeAction.Default,
+    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
+    keyboardType: KeyboardType = KeyboardType.Password,
+    enabled: Boolean = true,
+    inputTransformation: InputTransformation? = null,
+    textStyle: TextStyle = TextStyle.Default,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    decorator: TextFieldDecorator? = null,
+    scrollState: ScrollState = rememberScrollState(),
+) {
+    val state = remember {
+        TextFieldState(
+            initialText = value,
+            // Initialize the cursor to be at the end of the field.
+            initialSelectionInChars = TextRange(value.length)
+        )
+    }
+
+    // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
+    // some state that is preserved across updates (selection).
+    var valueWithSelection by remember {
+        mutableStateOf(
+            TextFieldValue(
+                text = value,
+                selection = TextRange(value.length)
+            )
+        )
+    }
+    valueWithSelection = valueWithSelection.copy(text = value)
+
+    BasicSecureTextField(
+        state = state,
+        modifier = modifier.syncTextFieldState(
+            state = state,
+            value = valueWithSelection,
+            onValueChanged = {
+                // Don't fire the callback if only the selection/cursor changed.
+                if (it.text != valueWithSelection.text) {
+                    onValueChange(it.text)
+                }
+                valueWithSelection = it
+            },
+            writeSelectionFromTextFieldValue = false
+        ),
+        onSubmit = onSubmit,
+        imeAction = imeAction,
+        textObfuscationMode = textObfuscationMode,
+        keyboardType = keyboardType,
+        enabled = enabled,
+        inputTransformation = inputTransformation,
+        textStyle = textStyle,
+        interactionSource = interactionSource,
+        cursorBrush = cursorBrush,
+        scrollState = scrollState,
+        onTextLayout = onTextLayout,
+        decorator = decorator,
+    )
+}
+
+/**
+ * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
+ * alternative to [BasicTextField2]. It only supports a single line of content and comes with
+ * default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
+ * are appropriate for entering secure content. Additionally, some context menu actions like cut,
+ * copy, and drag are disabled for added security.
+ *
+ * @param state [TextFieldState] object that holds the internal state of a [BasicTextField2].
+ * @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
+ * @param onSubmit Called when the user submits a form either by pressing the action button in the
+ * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
+ * submits the form by pressing the action button in the IME, the provided IME action is passed to
+ * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
+ * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
+ * action has been handled completely, which will skip the default behavior, such as hiding the
+ * keyboard for the [ImeAction.Done] action.
+ * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
+ * icons on the keyboard.
+ * @param textObfuscationMode Determines the method used to obscure the input text.
+ * @param keyboardType The keyboard type to be used in this text field. It is set to
+ * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
+ * fields.
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
+ * when the transformation is changed. If the transformation is changed on an existing text field,
+ * it will be applied to the next user edit. The transformation will not immediately affect the
+ * current [state].
+ * @param textStyle Style configuration for text content that's displayed in the editor.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, there will be no cursor drawn.
+ * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
+ * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
+ * or null if it cannot. The function reads the layout result from a snapshot state object, and will
+ * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
+ * paragraph information, size of the text, baselines and other details. The callback can be used to
+ * add additional decoration or functionality to the text. For example, to draw a cursor or
+ * selection around the text. [Density] scope is the one that was used while creating the given text
+ * layout.
+ * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
+ * messages or similar, and automatically increase the hit target area of the text field.
+ * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
+ * bounds of the text field. It controls the state of the scroll for the text field.
+ */
+@ExperimentalFoundationApi
+// This takes a composable lambda, but it is not primarily a container.
+@Suppress("ComposableLambdaParameterPosition")
+@Composable
+fun BasicSecureTextField(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
+    onSubmit: ImeActionHandler? = null,
+    imeAction: ImeAction = ImeAction.Default,
+    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
+    keyboardType: KeyboardType = KeyboardType.Password,
+    enabled: Boolean = true,
+    inputTransformation: InputTransformation? = null,
+    textStyle: TextStyle = TextStyle.Default,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    decorator: TextFieldDecorator? = null,
+    scrollState: ScrollState = rememberScrollState(),
+    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
+    // lambda.
+) {
+    val coroutineScope = rememberCoroutineScope()
+    val secureTextFieldController = remember(coroutineScope) {
+        SecureTextFieldController(coroutineScope)
+    }
+
+    // revealing last typed character depends on two conditions;
+    // 1 - Requested Obfuscation method
+    // 2 - if the system allows it
+    val revealLastTypedEnabled = textObfuscationMode == TextObfuscationMode.RevealLastTyped
+
+    // while toggling between obfuscation methods if the revealing gets disabled, reset the reveal.
+    if (!revealLastTypedEnabled) {
+        secureTextFieldController.passwordRevealFilter.hide()
+    }
+
+    val codepointTransformation = when {
+        revealLastTypedEnabled -> {
+            secureTextFieldController.codepointTransformation
+        }
+
+        textObfuscationMode == TextObfuscationMode.Hidden -> {
+            CodepointTransformation.mask('\u2022')
+        }
+
+        else -> null
+    }
+
+    val secureTextFieldModifier = modifier
+        .semantics(mergeDescendants = true) {
+            password()
+            copyText { false }
+            cutText { false }
+        }
+        .then(
+            if (revealLastTypedEnabled) {
+                secureTextFieldController.focusChangeModifier
+            } else {
+                Modifier
+            }
+        )
+
+    DisableCutCopy {
+        BasicTextField2(
+            state = state,
+            modifier = secureTextFieldModifier,
+            enabled = enabled,
+            readOnly = false,
+            inputTransformation = if (revealLastTypedEnabled) {
+                inputTransformation.then(secureTextFieldController.passwordRevealFilter)
+            } else inputTransformation,
+            textStyle = textStyle,
+            interactionSource = interactionSource,
+            cursorBrush = cursorBrush,
+            lineLimits = TextFieldLineLimits.SingleLine,
+            scrollState = scrollState,
+            keyboardOptions = KeyboardOptions(
+                autoCorrect = false,
+                keyboardType = keyboardType,
+                imeAction = imeAction
+            ),
+            keyboardActions = onSubmit?.let { KeyboardActions(onSubmit = it::onImeAction) }
+                ?: KeyboardActions.Default,
+            onTextLayout = onTextLayout,
+            codepointTransformation = codepointTransformation,
+            decorator = decorator,
+        )
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class SecureTextFieldController(
+    coroutineScope: CoroutineScope
+) {
+    /**
+     * A special [InputTransformation] that tracks changes to the content to identify the last typed
+     * character to reveal. `scheduleHide` lambda is delegated to a member function to be able to
+     * use [passwordRevealFilter] instance.
+     */
+    val passwordRevealFilter = PasswordRevealFilter(::scheduleHide)
+
+    /**
+     * Pass to [BasicTextField2] for obscuring text input.
+     */
+    val codepointTransformation = CodepointTransformation { codepointIndex, codepoint ->
+        if (codepointIndex == passwordRevealFilter.revealCodepointIndex) {
+            // reveal the last typed character by not obscuring it
+            codepoint
+        } else {
+            0x2022
+        }
+    }
+
+    val focusChangeModifier = Modifier.onFocusChanged {
+        if (!it.isFocused) passwordRevealFilter.hide()
+    }
+
+    private val resetTimerSignal = Channel<Unit>(Channel.UNLIMITED)
+
+    init {
+        // start a coroutine that listens for scheduled hide events.
+        coroutineScope.launch {
+            resetTimerSignal.consumeAsFlow()
+                .collectLatest {
+                    delay(LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS)
+                    passwordRevealFilter.hide()
+                }
+        }
+    }
+
+    private fun scheduleHide() {
+        // signal the listener that a new hide call is scheduled.
+        val result = resetTimerSignal.trySend(Unit)
+        if (!result.isSuccess) {
+            passwordRevealFilter.hide()
+        }
+    }
+}
+
+/**
+ * Special filter that tracks the changes in a TextField to identify the last typed character and
+ * mark it for reveal in password fields.
+ *
+ * @param scheduleHide A lambda that schedules a [hide] call into future after a new character is
+ * typed.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class PasswordRevealFilter(
+    val scheduleHide: () -> Unit
+) : InputTransformation {
+    // TODO: Consider setting this as a tracking annotation in AnnotatedString.
+    internal var revealCodepointIndex by mutableIntStateOf(-1)
+        private set
+
+    override fun transformInput(
+        originalValue: TextFieldCharSequence,
+        valueWithChanges: TextFieldBuffer
+    ) {
+        // We only care about a single character insertion changes
+        val singleCharacterInsertion = valueWithChanges.changes.changeCount == 1 &&
+            valueWithChanges.changes.getRange(0).length == 1 &&
+            valueWithChanges.changes.getOriginalRange(0).length == 0
+
+        // if there is an expanded selection, don't reveal anything
+        if (!singleCharacterInsertion || valueWithChanges.hasSelection) {
+            revealCodepointIndex = -1
+            return
+        }
+
+        val insertionPoint = valueWithChanges.changes.getRange(0).min
+        if (revealCodepointIndex != insertionPoint) {
+            // start the timer for auto hide
+            scheduleHide()
+            revealCodepointIndex = insertionPoint
+        }
+    }
+
+    /**
+     * Removes any revealed character index. Everything goes back into hiding.
+     */
+    fun hide() {
+        revealCodepointIndex = -1
+    }
+}
+
+// adopted from PasswordTransformationMethod from Android platform.
+private const val LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS = 1500L
+
+// TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
+@OptIn(ExperimentalFoundationApi::class)
+private fun KeyboardActions(onSubmit: ImeActionHandler) = KeyboardActions(
+    onDone = {
+        if (!onSubmit.onImeAction(ImeAction.Done)) {
+            defaultKeyboardAction(ImeAction.Done)
+        }
+    },
+    onGo = {
+        if (!onSubmit.onImeAction(ImeAction.Go)) {
+            defaultKeyboardAction(ImeAction.Go)
+        }
+    },
+    onNext = {
+        if (!onSubmit.onImeAction(ImeAction.Next)) {
+            defaultKeyboardAction(ImeAction.Next)
+        }
+    },
+    onPrevious = {
+        if (!onSubmit.onImeAction(ImeAction.Previous)) {
+            defaultKeyboardAction(ImeAction.Previous)
+        }
+    },
+    onSearch = {
+        if (!onSubmit.onImeAction(ImeAction.Search)) {
+            defaultKeyboardAction(ImeAction.Search)
+        }
+    },
+    onSend = {
+        if (!onSubmit.onImeAction(ImeAction.Send)) {
+            defaultKeyboardAction(ImeAction.Send)
+        }
+    },
+)
+
+/**
+ * Overrides the TextToolbar and keyboard shortcuts to never allow copy or cut options by the
+ * composables inside [content].
+ */
+@Composable
+private fun DisableCutCopy(
+    content: @Composable () -> Unit
+) {
+    val currentToolbar = LocalTextToolbar.current
+    val copyDisabledToolbar = remember(currentToolbar) {
+        object : TextToolbar by currentToolbar {
+            override fun showMenu(
+                rect: Rect,
+                onCopyRequested: (() -> Unit)?,
+                onPasteRequested: (() -> Unit)?,
+                onCutRequested: (() -> Unit)?,
+                onSelectAllRequested: (() -> Unit)?
+            ) {
+                currentToolbar.showMenu(
+                    rect = rect,
+                    onPasteRequested = onPasteRequested,
+                    onSelectAllRequested = onSelectAllRequested,
+                    onCopyRequested = null,
+                    onCutRequested = null
+                )
+            }
+        }
+    }
+    CompositionLocalProvider(LocalTextToolbar provides copyDisabledToolbar) {
+        Box(modifier = Modifier.onPreviewKeyEvent { keyEvent ->
+            // BasicTextField2 uses this static mapping
+            val command = platformDefaultKeyMapping.map(keyEvent)
+            // do not propagate copy and cut operations
+            command == KeyCommand.COPY || command == KeyCommand.CUT
+        }) {
+            content()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt
new file mode 100644
index 0000000..c18563b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt
@@ -0,0 +1,642 @@
+/*
+ * Copyright 2023 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.compose.foundation.text
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.relocation.bringIntoViewRequester
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldDecorator
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.internal.CodepointTransformation
+import androidx.compose.foundation.text.input.internal.SingleLineCodepointTransformation
+import androidx.compose.foundation.text.input.internal.TextFieldCoreModifier
+import androidx.compose.foundation.text.input.internal.TextFieldDecoratorModifier
+import androidx.compose.foundation.text.input.internal.TextFieldTextLayoutModifier
+import androidx.compose.foundation.text.input.internal.TextLayoutState
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
+import androidx.compose.foundation.text.input.internal.syncTextFieldState
+import androidx.compose.foundation.text.selection.SelectionHandle
+import androidx.compose.foundation.text.selection.SelectionHandleAnchor
+import androidx.compose.foundation.text.selection.SelectionHandleInfo
+import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalHapticFeedback
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+
+/**
+ * Basic text composable that provides an interactive box that accepts text input through software
+ * or hardware keyboard, but provides no decorations like hint or placeholder.
+ *
+ * Whenever the user edits the text, [onValueChange] is called with the most up to date state
+ * represented by [String] with which developer is expected to update their state.
+ *
+ * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
+ * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
+ * the contents of the field will _not_ be updated to reflect the value until editing is done. When
+ * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
+ * a [inputTransformation] to accept or reject changes during editing. For more direct control of
+ * the field contents use the [BasicTextField2] overload that accepts a [TextFieldState].
+ *
+ * Unlike [TextFieldState] overload, this composable does not let the developer control selection,
+ * cursor, and observe text composition information. Please check [TextFieldState] and corresponding
+ * [BasicTextField2] overload for more information.
+ *
+ * If you want to add decorations to your text field, such as icon or similar, and increase the
+ * hit target area, use the decorator:
+ * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
+ *
+ * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
+ * convert every character to uppercase) the input received from the user, use an
+ * [InputTransformation].
+ * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
+ *
+ * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
+ * direction can be achieved by using [TextFieldLineLimits].
+ *
+ * Scroll state of the composable is also hoisted to enable observation and manipulation of the
+ * scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
+ * position without focusing, or changing selection.
+ *
+ * @param value The input [String] text to be shown in the text field.
+ * @param onValueChange The callback that is triggered when the user or the system updates the
+ * text. The updated text is passed as a parameter of the callback. The value passed to the callback
+ * will already have had the [inputTransformation] applied.
+ * @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
+ * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
+ * field can not be modified, however, a user can focus it and copy text from it. Read-only text
+ * fields are usually used to display pre-filled forms that user can not edit.
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when a new [value] is passed in, or when the
+ * transformation is changed. If the transformation is changed on an existing text field, it will be
+ * applied to the next user edit, it will not immediately affect the current [value].
+ * @param textStyle Typographic and graphic style configuration for text content that's displayed
+ * in the editor.
+ * @param keyboardOptions Software keyboard options that contain configurations such as
+ * [KeyboardType] and [ImeAction].
+ * @param keyboardActions When the input service emits an IME action, the corresponding callback
+ * is called. Note that this IME action may be different from what you specified in
+ * [KeyboardOptions.imeAction].
+ * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and
+ * ignore newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed, all
+ * newline characters ('\n') within the text will be replaced with regular whitespace (' '),
+ * ensuring that the contents of the text field are presented in a single line.
+ * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
+ * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
+ * or null if it cannot. The function reads the layout result from a snapshot state object, and will
+ * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
+ * paragraph information, size of the text, baselines and other details. The callback can be used to
+ * add additional decoration or functionality to the text. For example, to draw a cursor or
+ * selection around the text. [Density] scope is the one that was used while creating the given text
+ * layout.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, then no cursor will be drawn.
+ * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
+ * text field are presented.
+ * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
+ * messages or similar, and automatically increase the hit target area of the text field.
+ * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
+ * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
+ * scroll behavior. In other cases the text field becomes vertically scrollable.
+ * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
+ * text field are presented.
+ */
+@ExperimentalFoundationApi
+// This takes a composable lambda, but it is not primarily a container.
+@Suppress("ComposableLambdaParameterPosition")
+@Composable
+fun BasicTextField2(
+    value: String,
+    onValueChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    inputTransformation: InputTransformation? = null,
+    textStyle: TextStyle = TextStyle.Default,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    keyboardActions: KeyboardActions = KeyboardActions.Default,
+    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    outputTransformation: OutputTransformation? = null,
+    decorator: TextFieldDecorator? = null,
+    scrollState: ScrollState = rememberScrollState(),
+    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
+    // lambda.
+) {
+    val state = remember {
+        TextFieldState(
+            initialText = value,
+            // Initialize the cursor to be at the end of the field.
+            initialSelectionInChars = TextRange(value.length)
+        )
+    }
+
+    // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
+    // some state that is preserved across updates (selection).
+    var valueWithSelection by remember {
+        mutableStateOf(
+            TextFieldValue(
+                text = value,
+                selection = TextRange(value.length)
+            )
+        )
+    }
+    valueWithSelection = valueWithSelection.copy(text = value)
+
+    BasicTextField2(
+        state = state,
+        modifier = modifier.syncTextFieldState(
+            state = state,
+            value = valueWithSelection,
+            onValueChanged = {
+                // Don't fire the callback if only the selection/cursor changed.
+                if (it.text != valueWithSelection.text) {
+                    onValueChange(it.text)
+                }
+                valueWithSelection = it
+            },
+            writeSelectionFromTextFieldValue = false
+        ),
+        enabled = enabled,
+        readOnly = readOnly,
+        inputTransformation = inputTransformation,
+        textStyle = textStyle,
+        keyboardOptions = keyboardOptions,
+        keyboardActions = keyboardActions,
+        lineLimits = lineLimits,
+        onTextLayout = onTextLayout,
+        interactionSource = interactionSource,
+        cursorBrush = cursorBrush,
+        scrollState = scrollState,
+        outputTransformation = outputTransformation,
+        decorator = decorator,
+    )
+}
+
+/**
+ * Basic text composable that provides an interactive box that accepts text input through software
+ * or hardware keyboard, but provides no decorations like hint or placeholder.
+ *
+ * All the editing state of this composable is hoisted through [state]. Whenever the contents of
+ * this composable change via user input or semantics, [TextFieldState.text] gets updated.
+ * Similarly, all the programmatic updates made to [state] also reflect on this composable.
+ *
+ * If you want to add decorations to your text field, such as icon or similar, and increase the
+ * hit target area, use the decorator:
+ * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
+ *
+ * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
+ * convert every character to uppercase) the input received from the user, use an
+ * [InputTransformation].
+ * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
+ *
+ * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
+ * direction can be achieved by using [TextFieldLineLimits].
+ *
+ * Scroll state of the composable is also hoisted to enable observation and manipulation of the
+ * scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
+ * position without focusing, or changing selection.
+ *
+ * @param state [TextFieldState] object that holds the internal editing state of [BasicTextField2].
+ * @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
+ * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
+ * field can not be modified, however, a user can focus it and copy text from it. Read-only text
+ * fields are usually used to display pre-filled forms that user can not edit.
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
+ * when the transformation is changed. If the transformation is changed on an existing text field,
+ * it will be applied to the next user edit. the transformation will not immediately affect the
+ * current [state].
+ * @param textStyle Typographic and graphic style configuration for text content that's displayed
+ * in the editor.
+ * @param keyboardOptions Software keyboard options that contain configurations such as
+ * [KeyboardType] and [ImeAction].
+ * @param keyboardActions When the input service emits an IME action, the corresponding callback
+ * is called. Note that this IME action may be different from what you specified in
+ * [KeyboardOptions.imeAction].
+ * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and
+ * ignore newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed, all
+ * newline characters ('\n') within the text will be replaced with regular whitespace (' '),
+ * ensuring that the contents of the text field are presented in a single line.
+ * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
+ * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
+ * or null if it cannot. The function reads the layout result from a snapshot state object, and will
+ * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
+ * paragraph information, size of the text, baselines and other details. The callback can be used to
+ * add additional decoration or functionality to the text. For example, to draw a cursor or
+ * selection around the text. [Density] scope is the one that was used while creating the given text
+ * layout.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, then no cursor will be drawn.
+ * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
+ * text field are presented.
+ * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
+ * messages or similar, and automatically increase the hit target area of the text field.
+ * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
+ * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
+ * scroll behavior. In other cases the text field becomes vertically scrollable.
+ */
+@ExperimentalFoundationApi
+// This takes a composable lambda, but it is not primarily a container.
+@Suppress("ComposableLambdaParameterPosition")
+@Composable
+fun BasicTextField2(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    inputTransformation: InputTransformation? = null,
+    textStyle: TextStyle = TextStyle.Default,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    keyboardActions: KeyboardActions = KeyboardActions.Default,
+    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    outputTransformation: OutputTransformation? = null,
+    decorator: TextFieldDecorator? = null,
+    scrollState: ScrollState = rememberScrollState(),
+    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
+    // lambda.
+) {
+    BasicTextField2(
+        state = state,
+        modifier = modifier,
+        enabled = enabled,
+        readOnly = readOnly,
+        inputTransformation = inputTransformation,
+        textStyle = textStyle,
+        keyboardOptions = keyboardOptions,
+        keyboardActions = keyboardActions,
+        lineLimits = lineLimits,
+        onTextLayout = onTextLayout,
+        interactionSource = interactionSource,
+        cursorBrush = cursorBrush,
+        codepointTransformation = null,
+        outputTransformation = outputTransformation,
+        decorator = decorator,
+        scrollState = scrollState,
+    )
+}
+
+/**
+ * Internal core text field that accepts a [CodepointTransformation].
+ *
+ * @param codepointTransformation Visual transformation interface that provides a 1-to-1 mapping of
+ * codepoints.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+// This takes a composable lambda, but it is not primarily a container.
+@Suppress("ComposableLambdaParameterPosition")
+@Composable
+internal fun BasicTextField2(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    inputTransformation: InputTransformation? = null,
+    textStyle: TextStyle = TextStyle.Default,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    keyboardActions: KeyboardActions = KeyboardActions.Default,
+    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    codepointTransformation: CodepointTransformation? = null,
+    outputTransformation: OutputTransformation? = null,
+    decorator: TextFieldDecorator? = null,
+    scrollState: ScrollState = rememberScrollState(),
+    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
+    // lambda.
+) {
+    val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
+    val windowInfo = LocalWindowInfo.current
+    val singleLine = lineLimits == SingleLine
+    // We're using this to communicate focus state to cursor for now.
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+    val orientation = if (singleLine) Orientation.Horizontal else Orientation.Vertical
+    val isFocused = interactionSource.collectIsFocusedAsState().value
+    val isDragHovered = interactionSource.collectIsHoveredAsState().value
+    val isWindowFocused = windowInfo.isWindowFocused
+
+    val transformedState = remember(
+        state,
+        inputTransformation,
+        codepointTransformation,
+        outputTransformation
+    ) {
+        // First prefer provided codepointTransformation if not null, e.g. BasicSecureTextField
+        // would send PasswordTransformation. Second, apply a SingleLineCodepointTransformation if
+        // text field is configured to be single line. Else, don't apply any visual transformation.
+        val appliedCodepointTransformation = codepointTransformation
+            ?: SingleLineCodepointTransformation.takeIf { singleLine }
+        TransformedTextFieldState(
+            textFieldState = state,
+            inputTransformation = inputTransformation,
+            codepointTransformation = appliedCodepointTransformation,
+            outputTransformation = outputTransformation
+        )
+    }
+
+    // Invalidate textLayoutState if TextFieldState itself has changed, since TextLayoutState
+    // would be carrying an invalid TextFieldState in its nonMeasureInputs.
+    val textLayoutState = remember(transformedState) { TextLayoutState() }
+
+    val textFieldSelectionState = remember(transformedState) {
+        TextFieldSelectionState(
+            textFieldState = transformedState,
+            textLayoutState = textLayoutState,
+            density = density,
+            enabled = enabled,
+            readOnly = readOnly,
+            isFocused = isFocused && isWindowFocused
+        )
+    }
+    val currentHapticFeedback = LocalHapticFeedback.current
+    val currentClipboardManager = LocalClipboardManager.current
+    val currentTextToolbar = LocalTextToolbar.current
+    SideEffect {
+        // These properties are not backed by snapshot state, so they can't be updated directly in
+        // composition.
+        textFieldSelectionState.update(
+            hapticFeedBack = currentHapticFeedback,
+            clipboardManager = currentClipboardManager,
+            textToolbar = currentTextToolbar,
+            density = density,
+            enabled = enabled,
+            readOnly = readOnly,
+        )
+    }
+
+    DisposableEffect(textFieldSelectionState) {
+        onDispose {
+            textFieldSelectionState.dispose()
+        }
+    }
+
+    val decorationModifiers = modifier
+        .then(
+            // semantics + some focus + input session + touch to focus
+            TextFieldDecoratorModifier(
+                textFieldState = transformedState,
+                textLayoutState = textLayoutState,
+                textFieldSelectionState = textFieldSelectionState,
+                filter = inputTransformation,
+                enabled = enabled,
+                readOnly = readOnly,
+                keyboardOptions = keyboardOptions,
+                keyboardActions = keyboardActions,
+                singleLine = singleLine,
+                interactionSource = interactionSource
+            )
+        )
+        .focusable(interactionSource = interactionSource, enabled = enabled)
+        .scrollable(
+            state = scrollState,
+            orientation = orientation,
+            // Disable scrolling when textField is disabled, there is no where to scroll, and
+            // another dragging gesture is taking place
+            enabled = enabled &&
+                scrollState.maxValue > 0 &&
+                textFieldSelectionState.draggingHandle == null,
+            reverseDirection = ScrollableDefaults.reverseDirection(
+                layoutDirection = layoutDirection,
+                orientation = orientation,
+                reverseScrolling = false
+            ),
+            interactionSource = interactionSource,
+        )
+
+    Box(decorationModifiers, propagateMinConstraints = true) {
+        val nonNullDecorator = decorator ?: DefaultTextFieldDecorator
+        nonNullDecorator.Decoration {
+            val minLines: Int
+            val maxLines: Int
+            if (lineLimits is MultiLine) {
+                minLines = lineLimits.minHeightInLines
+                maxLines = lineLimits.maxHeightInLines
+            } else {
+                minLines = 1
+                maxLines = 1
+            }
+
+            Box(
+                propagateMinConstraints = true,
+                modifier = Modifier
+                    .heightIn(min = textLayoutState.minHeightForSingleLineField)
+                    .heightInLines(
+                        textStyle = textStyle,
+                        minLines = minLines,
+                        maxLines = maxLines
+                    )
+                    .textFieldMinSize(textStyle)
+                    .clipToBounds()
+                    .then(
+                        TextFieldCoreModifier(
+                            isFocused = isFocused && isWindowFocused,
+                            isDragHovered = isDragHovered,
+                            textLayoutState = textLayoutState,
+                            textFieldState = transformedState,
+                            textFieldSelectionState = textFieldSelectionState,
+                            cursorBrush = cursorBrush,
+                            writeable = enabled && !readOnly,
+                            scrollState = scrollState,
+                            orientation = orientation
+                        )
+                    )
+            ) {
+                Box(
+                    modifier = Modifier
+                        .bringIntoViewRequester(textLayoutState.bringIntoViewRequester)
+                        .then(
+                            TextFieldTextLayoutModifier(
+                                textLayoutState = textLayoutState,
+                                textFieldState = transformedState,
+                                textStyle = textStyle,
+                                singleLine = singleLine,
+                                onTextLayout = onTextLayout
+                            )
+                        )
+                )
+
+                if (enabled && isFocused &&
+                    isWindowFocused && textFieldSelectionState.isInTouchMode
+                ) {
+                    TextFieldSelectionHandles(
+                        selectionState = textFieldSelectionState
+                    )
+                    if (!readOnly) {
+                        TextFieldCursorHandle(
+                            selectionState = textFieldSelectionState
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+internal fun TextFieldCursorHandle(selectionState: TextFieldSelectionState) {
+    val cursorHandleState = selectionState.cursorHandle
+    if (cursorHandleState.visible) {
+        CursorHandle(
+            handlePosition = cursorHandleState.position,
+            modifier = Modifier
+                .semantics {
+                    this[SelectionHandleInfoKey] = SelectionHandleInfo(
+                        handle = Handle.Cursor,
+                        position = cursorHandleState.position,
+                        anchor = SelectionHandleAnchor.Middle,
+                        visible = true,
+                    )
+                }
+                .pointerInput(selectionState) {
+                    with(selectionState) { cursorHandleGestures() }
+                },
+            minTouchTargetSize = MinTouchTargetSizeForHandles,
+        )
+    }
+}
+
+@Composable
+internal fun TextFieldSelectionHandles(
+    selectionState: TextFieldSelectionState
+) {
+    // Does not recompose if only position of the handle changes.
+    val startHandleState by remember {
+        derivedStateOf {
+            selectionState.getSelectionHandleState(isStartHandle = true, includePosition = false)
+        }
+    }
+    if (startHandleState.visible) {
+        SelectionHandle(
+            offsetProvider = {
+                selectionState
+                    .getSelectionHandleState(isStartHandle = true, includePosition = true)
+                    .position
+            },
+            isStartHandle = true,
+            direction = startHandleState.direction,
+            handlesCrossed = startHandleState.handlesCrossed,
+            modifier = Modifier.pointerInput(selectionState) {
+                with(selectionState) { selectionHandleGestures(true) }
+            },
+            minTouchTargetSize = MinTouchTargetSizeForHandles,
+        )
+    }
+
+    // Does not recompose if only position of the handle changes.
+    val endHandleState by remember {
+        derivedStateOf {
+            selectionState.getSelectionHandleState(isStartHandle = false, includePosition = false)
+        }
+    }
+    if (endHandleState.visible) {
+        SelectionHandle(
+            offsetProvider = {
+                selectionState
+                    .getSelectionHandleState(isStartHandle = false, includePosition = true)
+                    .position
+            },
+            isStartHandle = false,
+            direction = endHandleState.direction,
+            handlesCrossed = endHandleState.handlesCrossed,
+            modifier = Modifier.pointerInput(selectionState) {
+                with(selectionState) { selectionHandleGestures(false) }
+            },
+            minTouchTargetSize = MinTouchTargetSizeForHandles,
+        )
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private val DefaultTextFieldDecorator = TextFieldDecorator { it() }
+
+/**
+ * Defines a minimum touch target area size for Selection and Cursor handles.
+ *
+ * Although BasicTextField is not part of Material spec, this accessibility feature is important
+ * enough to be included at foundation layer, and also TextField cannot change selection handles
+ * provided by BasicTextField to somehow achieve this accessibility requirement.
+ *
+ * This value is adopted from Android platform's TextView implementation.
+ */
+private val MinTouchTargetSizeForHandles = DpSize(40.dp, 40.dp)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
index 4fda9bf..68272fc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
@@ -16,10 +16,10 @@
 
 package androidx.compose.foundation.text
 
-import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.keyframes
+import androidx.compose.foundation.text.input.internal.CursorAnimationState
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
@@ -31,11 +31,10 @@
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.isUnspecified
+import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastCoerceIn
-import kotlinx.coroutines.withContext
 
 internal fun Modifier.cursor(
     state: LegacyTextFieldState,
@@ -44,21 +43,20 @@
     cursorBrush: Brush,
     enabled: Boolean
 ) = if (enabled) composed {
-    val cursorAlpha = remember { Animatable(1f) }
+    val cursorAnimation = remember { CursorAnimationState() }
+    // Don't bother animating the cursor if it wouldn't draw any pixels.
     val isBrushSpecified = !(cursorBrush is SolidColor && cursorBrush.value.isUnspecified)
-    if (state.hasFocus && value.selection.collapsed && isBrushSpecified) {
+    // Only animate the cursor when its window is actually focused. This also disables the cursor
+    // animation when the screen is off.
+    // TODO confirm screen-off behavior.
+    val isWindowFocused = LocalWindowInfo.current.isWindowFocused
+    if (isWindowFocused && state.hasFocus && value.selection.collapsed && isBrushSpecified) {
         LaunchedEffect(value.annotatedString, value.selection) {
-            // Animate the cursor even when animations are disabled by the system.
-            withContext(FixedMotionDurationScale) {
-                // ensure that the value is always 1f _this_ frame by calling snapTo
-                cursorAlpha.snapTo(1f)
-                // then start the cursor blinking on animation clock (500ms on to start)
-                cursorAlpha.animateTo(0f, cursorAnimationSpec)
-            }
+            cursorAnimation.snapToVisibleAndAnimate()
         }
         drawWithContent {
             this.drawContent()
-            val cursorAlphaValue = cursorAlpha.value.fastCoerceIn(0f, 1f)
+            val cursorAlphaValue = cursorAnimation.cursorAlpha
             if (cursorAlphaValue != 0f) {
                 val transformedOffset = offsetMapping
                     .originalToTransformed(value.selection.start)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/ImeActionHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/ImeActionHandler.kt
new file mode 100644
index 0000000..c55464d
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/ImeActionHandler.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.input.ImeAction
+
+// TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
+@ExperimentalFoundationApi
+@Stable
+fun interface ImeActionHandler {
+    fun onImeAction(action: ImeAction): Boolean
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
new file mode 100644
index 0000000..563832c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.substring
+import androidx.compose.ui.text.toUpperCase
+
+/**
+ * A function that is ran after every change made to a [TextFieldState] by user input and can change
+ * or reject that input.
+ *
+ * Input transformations are ran after hardware and software keyboard events, when text is pasted or
+ * dropped into the field, or when an accessibility service changes the text.
+ *
+ * To chain filters together, call [then].
+ *
+ * Prebuilt filters are provided for common filter operations. See:
+ *  - [InputTransformation].[maxLengthInChars]`()`
+ *  - [InputTransformation].[maxLengthInCodepoints]`()`
+ *  - [InputTransformation].[allCaps]`()`
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
+ */
+@ExperimentalFoundationApi
+@Stable
+fun interface InputTransformation {
+
+    /**
+     * Optional [KeyboardOptions] that will be used as the default keyboard options for configuring
+     * the IME. The options passed directly to the text field composable will always override this.
+     */
+    val keyboardOptions: KeyboardOptions? get() = null
+
+    /**
+     * The transform operation. For more information see the documentation on [InputTransformation].
+     *
+     * To reject all changes in [valueWithChanges], call
+     * `valueWithChanges.`[revertAllChanges][TextFieldBuffer.revertAllChanges].
+     *
+     * @param originalValue The value of the field before the change was performed.
+     * @param valueWithChanges The value of the field after the change. This value can be changed
+     * in-place to alter or reject the changes or set the selection.
+     */
+    fun transformInput(originalValue: TextFieldCharSequence, valueWithChanges: TextFieldBuffer)
+
+    companion object : InputTransformation {
+        override fun transformInput(
+            originalValue: TextFieldCharSequence,
+            valueWithChanges: TextFieldBuffer
+        ) {
+            // Noop.
+        }
+    }
+}
+
+// region Pre-built transformations
+
+/**
+ * Creates a filter chain that will run [next] after this. Filters are applied sequentially, so any
+ * changes made by this filter will be visible to [next].
+ *
+ * The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
+ * use the options from this transformation.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
+ *
+ * @param next The [InputTransformation] that will be ran after this one.
+ */
+@ExperimentalFoundationApi
+@Stable
[email protected]("thenOrNull")
+fun InputTransformation?.then(next: InputTransformation?): InputTransformation? = when {
+    this == null -> next
+    next == null -> this
+    else -> this.then(next)
+}
+
+/**
+ * Creates a filter chain that will run [next] after this. Filters are applied sequentially, so any
+ * changes made by this filter will be visible to [next].
+ *
+ * The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
+ * use the options from this transformation.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
+ *
+ * @param next The [InputTransformation] that will be ran after this one.
+ */
+@ExperimentalFoundationApi
+@Stable
+fun InputTransformation.then(next: InputTransformation): InputTransformation =
+    FilterChain(this, next)
+
+/**
+ * Creates an [InputTransformation] from a function that accepts both the old and proposed
+ * [TextFieldCharSequence] and returns the [TextFieldCharSequence] to use for the field.
+ *
+ * [transformation] can return either `old`, `proposed`, or a completely different value.
+ *
+ * The selection or cursor will be updated automatically. For more control of selection
+ * implement [InputTransformation] directly.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueChooseSample
+ * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueReplaceSample
+ */
+@ExperimentalFoundationApi
+@Stable
+fun InputTransformation.byValue(
+    transformation: (
+        current: CharSequence,
+        proposed: CharSequence
+    ) -> CharSequence
+): InputTransformation = this.then(InputTransformationByValue(transformation))
+
+/**
+ * Returns a [InputTransformation] that forces all text to be uppercase.
+ *
+ * This transformation automatically configures the keyboard to capitalize all characters.
+ *
+ * @param locale The [Locale] in which to perform the case conversion.
+ */
+@ExperimentalFoundationApi
+@Stable
+fun InputTransformation.allCaps(locale: Locale): InputTransformation =
+    this.then(AllCapsTransformation(locale))
+
+/**
+ * Returns [InputTransformation] that rejects input which causes the total length of the text field to be
+ * more than [maxLength] characters.
+ *
+ * @see maxLengthInCodepoints
+ */
+@ExperimentalFoundationApi
+@Stable
+fun InputTransformation.maxLengthInChars(maxLength: Int): InputTransformation =
+    this.then(MaxLengthFilter(maxLength, inCodepoints = false))
+
+/**
+ * Returns a [InputTransformation] that rejects input which causes the total length of the text field to
+ * be more than [maxLength] codepoints.
+ *
+ * @see maxLengthInChars
+ */
+@ExperimentalFoundationApi
+@Stable
+fun InputTransformation.maxLengthInCodepoints(maxLength: Int): InputTransformation =
+    this.then(MaxLengthFilter(maxLength, inCodepoints = true))
+
+// endregion
+// region Transformation implementations
+
+@OptIn(ExperimentalFoundationApi::class)
+private class FilterChain(
+    private val first: InputTransformation,
+    private val second: InputTransformation,
+) : InputTransformation {
+
+    override val keyboardOptions: KeyboardOptions?
+        // TODO(b/295951492) Do proper merging.
+        get() = second.keyboardOptions ?: first.keyboardOptions
+
+    override fun transformInput(
+        originalValue: TextFieldCharSequence,
+        valueWithChanges: TextFieldBuffer
+    ) {
+        first.transformInput(originalValue, valueWithChanges)
+        second.transformInput(originalValue, valueWithChanges)
+    }
+
+    override fun toString(): String = "$first.then($second)"
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other === null) return false
+        if (this::class != other::class) return false
+
+        other as FilterChain
+
+        if (first != other.first) return false
+        if (second != other.second) return false
+        if (keyboardOptions != other.keyboardOptions) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = first.hashCode()
+        result = 31 * result + second.hashCode()
+        result = 32 * result + keyboardOptions.hashCode()
+        return result
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private data class InputTransformationByValue(
+    val transformation: (
+        old: CharSequence,
+        proposed: CharSequence
+    ) -> CharSequence
+) : InputTransformation {
+    override fun transformInput(
+        originalValue: TextFieldCharSequence,
+        valueWithChanges: TextFieldBuffer
+    ) {
+        val proposed = valueWithChanges.toTextFieldCharSequence()
+        val accepted = transformation(originalValue, proposed)
+        when {
+            // These are reference comparisons – text comparison will be done by setTextIfChanged.
+            accepted === proposed -> return
+            accepted === originalValue -> valueWithChanges.revertAllChanges()
+            else -> {
+                valueWithChanges.setTextIfChanged(accepted)
+            }
+        }
+    }
+
+    override fun toString(): String = "InputTransformation.byValue(transformation=$transformation)"
+}
+
+// This is a very naive implementation for now, not intended to be production-ready.
+@OptIn(ExperimentalFoundationApi::class)
+private data class AllCapsTransformation(private val locale: Locale) : InputTransformation {
+    override val keyboardOptions = KeyboardOptions(
+        capitalization = KeyboardCapitalization.Characters
+    )
+
+    override fun transformInput(
+        originalValue: TextFieldCharSequence,
+        valueWithChanges: TextFieldBuffer
+    ) {
+        // only update inserted content
+        valueWithChanges.changes.forEachChange { range, _ ->
+            if (!range.collapsed) {
+                valueWithChanges.replace(
+                    range.min,
+                    range.max,
+                    valueWithChanges.asCharSequence().substring(range).toUpperCase(locale)
+                )
+            }
+        }
+    }
+
+    override fun toString(): String = "InputTransformation.allCaps(locale=$locale)"
+}
+
+// This is a very naive implementation for now, not intended to be production-ready.
+@OptIn(ExperimentalFoundationApi::class)
+private data class MaxLengthFilter(
+    private val maxLength: Int,
+    private val inCodepoints: Boolean
+) : InputTransformation {
+
+    init {
+        require(maxLength >= 0) { "maxLength must be at least zero, was $maxLength" }
+    }
+
+    override fun transformInput(
+        originalValue: TextFieldCharSequence,
+        valueWithChanges: TextFieldBuffer
+    ) {
+        val newLength =
+            if (inCodepoints) valueWithChanges.codepointLength else valueWithChanges.length
+        if (newLength > maxLength) {
+            valueWithChanges.revertAllChanges()
+        }
+    }
+
+    override fun toString(): String {
+        val name = if (inCodepoints) "maxLengthInCodepoints" else "maxLengthInChars"
+        return "InputTransformation.$name(maxLength=$maxLength)"
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt
new file mode 100644
index 0000000..36089f1
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.runtime.Stable
+
+/**
+ * A function ([transformOutput]) that transforms the text presented to a user by a
+ * [BasicTextField2].
+ */
+@ExperimentalFoundationApi
+@Stable
+fun interface OutputTransformation {
+
+    /**
+     * Given a [TextFieldBuffer] that contains the contents of a [TextFieldState], modifies the
+     * text. After this function returns, the contents of the buffer will be presented to the user
+     * as the contents of the text field instead of the raw contents of the [TextFieldState].
+     *
+     * Note that the contents of the [TextFieldState] remain completely unchanged. This is a one-way
+     * transformation that only affects what is presented to the user.
+     */
+    fun TextFieldBuffer.transformOutput()
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
new file mode 100644
index 0000000..6e20f5c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
@@ -0,0 +1,658 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList
+import androidx.compose.foundation.text.input.internal.ChangeTracker
+import androidx.compose.foundation.text.input.internal.OffsetMappingCalculator
+import androidx.compose.foundation.text.input.internal.PartialGapBuffer
+import androidx.compose.ui.text.TextRange
+
+/**
+ * A text buffer that can be edited, similar to [StringBuilder].
+ *
+ * This class provides methods for changing the text, such as:
+ *  - [replace]
+ *  - [append]
+ *  - [insert]
+ *  - [delete]
+ *
+ * This class also stores and tracks the cursor position or selection range. The cursor position is
+ * just a selection range with zero length. The cursor and selection can be changed using methods
+ * such as:
+ *  - [placeCursorAfterCodepointAt]
+ *  - [placeCursorAfterCharAt]
+ *  - [placeCursorBeforeCodepointAt]
+ *  - [placeCursorBeforeCharAt]
+ *  - [placeCursorAtEnd]
+ *  - [selectAll]
+ *
+ * To get one of these, and for usage samples, see [TextFieldState.edit]. Every change to the buffer
+ * is tracked in a [ChangeList] which you can access via the [changes] property.
+ */
+@ExperimentalFoundationApi
+class TextFieldBuffer internal constructor(
+    initialValue: TextFieldCharSequence,
+    initialChanges: ChangeTracker? = null,
+    /**
+     * The value reverted to when [revertAllChanges] is called. This is not necessarily
+     * [initialValue] since the initial value may have already have had some intermediate changes
+     * applied to it.
+     */
+    private val sourceValue: TextFieldCharSequence = initialValue,
+    private val offsetMappingCalculator: OffsetMappingCalculator? = null,
+) : Appendable {
+
+    private val buffer = PartialGapBuffer(initialValue)
+
+    /**
+     * Lazily-allocated [ChangeTracker], initialized on the first text change.
+     */
+    private var changeTracker: ChangeTracker? =
+        initialChanges?.let { ChangeTracker(initialChanges) }
+
+    /**
+     * The number of characters in the text field. This will be equal to or greater than
+     * [codepointLength].
+     */
+    val length: Int get() = buffer.length
+
+    /**
+     * The number of codepoints in the text field. This will be equal to or less than [length].
+     */
+    val codepointLength: Int get() = Character.codePointCount(buffer, 0, length)
+
+    /**
+     * The [ChangeList] represents the changes made to this value and is inherently mutable. This
+     * means that the returned [ChangeList] always reflects the complete list of changes made to
+     * this value at any given time, even those made after reading this property.
+     *
+     * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
+     * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
+     */
+    val changes: ChangeList get() = changeTracker ?: EmptyChangeList
+
+    /**
+     * True if the selection range has non-zero length. If this is false, then the selection
+     * represents the cursor.
+     *
+     * @see selectionInChars
+     */
+    @get:JvmName("hasSelection")
+    val hasSelection: Boolean
+        get() = !selectionInChars.collapsed
+
+    /**
+     * The selected range of characters.
+     *
+     * @see selectionInCodepoints
+     */
+    var selectionInChars: TextRange = initialValue.selectionInChars
+        private set
+
+    /**
+     * The selected range of codepoints.
+     *
+     * @see selectionInChars
+     */
+    val selectionInCodepoints: TextRange
+        get() = charsToCodepoints(selectionInChars)
+
+    /**
+     * Replaces the text between [start] (inclusive) and [end] (exclusive) in this value with
+     * [text], and records the change in [changes].
+     *
+     * @param start The character offset of the first character to replace.
+     * @param end The character offset of the first character after the text to replace.
+     * @param text The text to replace the range `[start, end)` with.
+     *
+     * @see append
+     * @see insert
+     * @see delete
+     */
+    fun replace(start: Int, end: Int, text: CharSequence) {
+        replace(start, end, text, 0, text.length)
+    }
+
+    /**
+     * Replaces the text between [start] (inclusive) and [end] (exclusive) in this value with
+     * [text], and records the change in [changes].
+     *
+     * @param start The character offset of the first character to replace.
+     * @param end The character offset of the first character after the text to replace.
+     * @param text The text to replace the range `[start, end)` with.
+     * @param textStart The character offset of the first character in [text] to copy.
+     * @param textEnd The character offset after the last character in [text] to copy.
+     *
+     * @see append
+     * @see insert
+     * @see delete
+     */
+    internal fun replace(
+        start: Int,
+        end: Int,
+        text: CharSequence,
+        textStart: Int = 0,
+        textEnd: Int = text.length
+    ) {
+        require(start <= end) { "Expected start=$start <= end=$end" }
+        require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
+        onTextWillChange(start, end, textEnd - textStart)
+        buffer.replace(start, end, text, textStart, textEnd)
+    }
+
+    /**
+     * Similar to `replace(0, length, newText)` but only records a change if [newText] is actually
+     * different from the current buffer value.
+     */
+    internal fun setTextIfChanged(newText: CharSequence) {
+        findCommonPrefixAndSuffix(buffer, newText) { thisStart, thisEnd, newStart, newEnd ->
+            replace(thisStart, thisEnd, newText, newStart, newEnd)
+        }
+    }
+
+    // Doc inherited from Appendable.
+    // This append overload should be first so it ends up being the target of links to this method.
+    override fun append(text: CharSequence?): Appendable = apply {
+        if (text != null) {
+            onTextWillChange(length, length, text.length)
+            buffer.replace(buffer.length, buffer.length, text)
+        }
+    }
+
+    // Doc inherited from Appendable.
+    override fun append(text: CharSequence?, start: Int, end: Int): Appendable = apply {
+        if (text != null) {
+            onTextWillChange(length, length, end - start)
+            buffer.replace(buffer.length, buffer.length, text.subSequence(start, end))
+        }
+    }
+
+    // Doc inherited from Appendable.
+    override fun append(char: Char): Appendable = apply {
+        onTextWillChange(length, length, 1)
+        buffer.replace(buffer.length, buffer.length, char.toString())
+    }
+
+    /**
+     * Called just before the text contents are about to change.
+     *
+     * @param replaceStart The first offset to be replaced (inclusive).
+     * @param replaceEnd The last offset to be replaced (exclusive).
+     * @param newLength The length of the replacement.
+     */
+    private fun onTextWillChange(replaceStart: Int, replaceEnd: Int, newLength: Int) {
+        (changeTracker ?: ChangeTracker().also { changeTracker = it })
+            .trackChange(replaceStart, replaceEnd, newLength)
+        offsetMappingCalculator?.recordEditOperation(replaceStart, replaceEnd, newLength)
+
+        // Adjust selection.
+        val start = minOf(replaceStart, replaceEnd)
+        val end = maxOf(replaceStart, replaceEnd)
+        var selStart = selectionInChars.min
+        var selEnd = selectionInChars.max
+
+        if (selEnd < start) {
+            // The entire selection is before the insertion point – we don't have to adjust the
+            // mark at all, so skip the math.
+            return
+        }
+
+        if (selStart <= start && end <= selEnd) {
+            // The insertion is entirely inside the selection, move the end only.
+            val diff = newLength - (end - start)
+            // Preserve "cursorness".
+            if (selStart == selEnd) {
+                selStart += diff
+            }
+            selEnd += diff
+        } else if (selStart > start && selEnd < end) {
+            // Selection is entirely inside replacement, move it to the end.
+            selStart = start + newLength
+            selEnd = start + newLength
+        } else if (selStart >= end) {
+            // The entire selection is after the insertion, so shift everything forward.
+            val diff = newLength - (end - start)
+            selStart += diff
+            selEnd += diff
+        } else if (start < selStart) {
+            // Insertion is around start of selection, truncate start of selection.
+            selStart = start + newLength
+            selEnd += newLength - (end - start)
+        } else {
+            // Insertion is around end of selection, truncate end of selection.
+            selEnd = start
+        }
+        selectionInChars = TextRange(selStart, selEnd)
+    }
+
+    /**
+     * Returns the [Char] at [index] in this buffer.
+     */
+    fun charAt(index: Int): Char = buffer[index]
+
+    override fun toString(): String = buffer.toString()
+
+    /**
+     * Returns a [CharSequence] backed by this buffer. Any subsequent changes to this buffer will
+     * be visible in the returned sequence as well.
+     */
+    fun asCharSequence(): CharSequence = buffer
+
+    private fun clearChangeList() {
+        changeTracker?.clearChanges()
+    }
+
+    /**
+     * Revert all changes made to this value since it was created.
+     *
+     * After calling this method, this object will be in the same state it was when it was initially
+     * created, and [changes] will be empty.
+     */
+    fun revertAllChanges() {
+        replace(0, length, sourceValue.toString())
+        selectionInChars = sourceValue.selectionInChars
+        clearChangeList()
+    }
+
+    /**
+     * Places the cursor before the codepoint at the given index.
+     *
+     * If [index] is inside an invalid run, the cursor will be placed at the nearest earlier index.
+     *
+     * To place the cursor at the beginning of the field, pass index 0. To place the cursor at the
+     * end of the field, after the last character, pass index
+     * [TextFieldBuffer.codepointLength] or call [placeCursorAtEnd].
+     *
+     * @param index Codepoint index to place cursor before, should be in range 0 to
+     * [TextFieldBuffer.codepointLength], inclusive.
+     *
+     * @see placeCursorBeforeCharAt
+     * @see placeCursorAfterCodepointAt
+     */
+    fun placeCursorBeforeCodepointAt(index: Int) {
+        requireValidIndex(index, startExclusive = true, endExclusive = false, inCodepoints = true)
+        val charIndex = codepointIndexToCharIndex(index)
+        selectionInChars = TextRange(charIndex)
+    }
+
+    /**
+     * Places the cursor before the character at the given index.
+     *
+     * If [index] is inside a surrogate pair or other invalid run, the cursor will be placed at the
+     * nearest earlier index.
+     *
+     * To place the cursor at the beginning of the field, pass index 0. To place the cursor at the
+     * end of the field, after the last character, pass index [TextFieldBuffer.length] or call
+     * [placeCursorAtEnd].
+     *
+     * @param index Character index to place cursor before, should be in range 0 to
+     * [TextFieldBuffer.length], inclusive.
+     *
+     * @see placeCursorBeforeCodepointAt
+     * @see placeCursorAfterCharAt
+     */
+    fun placeCursorBeforeCharAt(index: Int) {
+        requireValidIndex(index, startExclusive = true, endExclusive = false, inCodepoints = false)
+        selectionInChars = TextRange(index)
+    }
+
+    /**
+     * Places the cursor after the codepoint at the given index.
+     *
+     * If [index] is inside an invalid run, the cursor will be placed at the nearest later index.
+     *
+     * To place the cursor at the end of the field, after the last character, pass index
+     * [TextFieldBuffer.codepointLength] or call [placeCursorAtEnd].
+     *
+     * @param index Codepoint index to place cursor after, should be in range 0 (inclusive) to
+     * [TextFieldBuffer.codepointLength] (exclusive).
+     *
+     * @see placeCursorAfterCharAt
+     * @see placeCursorBeforeCodepointAt
+     */
+    fun placeCursorAfterCodepointAt(index: Int) {
+        requireValidIndex(index, startExclusive = false, endExclusive = true, inCodepoints = true)
+        val charIndex = codepointIndexToCharIndex((index + 1).coerceAtMost(codepointLength))
+        selectionInChars = TextRange(charIndex)
+    }
+
+    /**
+     * Places the cursor after the character at the given index.
+     *
+     * If [index] is inside a surrogate pair or other invalid run, the cursor will be placed at the
+     * nearest later index.
+     *
+     * To place the cursor at the end of the field, after the last character, pass index
+     * [TextFieldBuffer.length] or call [placeCursorAtEnd].
+     *
+     * @param index Character index to place cursor after, should be in range 0 (inclusive) to
+     * [TextFieldBuffer.length] (exclusive).
+     *
+     * @see placeCursorAfterCodepointAt
+     * @see placeCursorBeforeCharAt
+     */
+    fun placeCursorAfterCharAt(index: Int) {
+        requireValidIndex(index, startExclusive = false, endExclusive = true, inCodepoints = false)
+        selectionInChars = TextRange((index + 1).coerceAtMost(length))
+    }
+
+    /**
+     * Places the selection around the given [range] in codepoints.
+     *
+     * If the start or end of [range] fall inside invalid runs, the values will be adjusted to the
+     * nearest earlier and later codepoints, respectively.
+     *
+     * To place the start of the selection at the beginning of the field, pass index 0. To place the
+     * end of the selection at the end of the field, after the last codepoint, pass index
+     * [TextFieldBuffer.codepointLength]. Passing a zero-length range is the same as calling
+     * [placeCursorBeforeCodepointAt].
+     *
+     * @param range Codepoint range of the selection, should be in range 0 to
+     * [TextFieldBuffer.codepointLength], inclusive.
+     *
+     * @see selectCharsIn
+     */
+    fun selectCodepointsIn(range: TextRange) {
+        requireValidRange(range, inCodepoints = true)
+        selectionInChars = codepointsToChars(range)
+    }
+
+    /**
+     * Places the selection around the given [range] in characters.
+     *
+     * If the start or end of [range] fall inside surrogate pairs or other invalid runs, the values will
+     * be adjusted to the nearest earlier and later characters, respectively.
+     *
+     * To place the start of the selection at the beginning of the field, pass index 0. To place the end
+     * of the selection at the end of the field, after the last character, pass index
+     * [TextFieldBuffer.length]. Passing a zero-length range is the same as calling
+     * [placeCursorBeforeCharAt].
+     *
+     * @param range Codepoint range of the selection, should be in range 0 to
+     * [TextFieldBuffer.length], inclusive.
+     *
+     * @see selectCodepointsIn
+     */
+    fun selectCharsIn(range: TextRange) {
+        requireValidRange(range, inCodepoints = false)
+        selectionInChars = range
+    }
+
+    /**
+     * Returns an immutable [TextFieldCharSequence] that has the same contents of this buffer.
+     *
+     * @param selection The selection for the returned [TextFieldCharSequence]. Default value is
+     * this buffer's selection. Passing a different value in here _only_ affects the return value,
+     * it does not change the current selection in the buffer.
+     * @param composition The composition range for the returned [TextFieldCharSequence]. Default
+     * value is no composition (null).
+     */
+    internal fun toTextFieldCharSequence(
+        selection: TextRange = selectionInChars,
+        composition: TextRange? = null
+    ): TextFieldCharSequence = TextFieldCharSequence(
+        buffer.toString(),
+        selection = selection,
+        composition = composition
+    )
+
+    private fun requireValidIndex(
+        index: Int,
+        startExclusive: Boolean,
+        endExclusive: Boolean,
+        inCodepoints: Boolean
+    ) {
+        var start = if (startExclusive) 0 else -1
+        var end = if (endExclusive) length else length + 1
+
+        // The "units" of the range in the error message should match the units passed in.
+        // If the input was in codepoint indices, the output should be in codepoint indices.
+        if (inCodepoints) {
+            start = charIndexToCodepointIndex(start)
+            end = charIndexToCodepointIndex(end)
+        }
+
+        require(index in start until end) {
+            val unit = if (inCodepoints) "codepoints" else "chars"
+            "Expected $index to be in [$start, $end) $unit"
+        }
+    }
+
+    private fun requireValidRange(range: TextRange, inCodepoints: Boolean) {
+        // The "units" of the range in the error message should match the units passed in.
+        // If the input was in codepoint indices, the output should be in codepoint indices.
+        val validRange = TextRange(0, length)
+            .let { if (inCodepoints) charsToCodepoints(it) else it }
+        require(range in validRange) {
+            val unit = if (inCodepoints) "codepoints" else "chars"
+            "Expected $range to be in $validRange ($unit)"
+        }
+    }
+
+    private fun codepointsToChars(range: TextRange): TextRange = TextRange(
+        codepointIndexToCharIndex(range.start),
+        codepointIndexToCharIndex(range.end)
+    )
+
+    private fun charsToCodepoints(range: TextRange): TextRange = TextRange(
+        charIndexToCodepointIndex(range.start),
+        charIndexToCodepointIndex(range.end),
+    )
+
+    // TODO Support actual codepoints.
+    private fun codepointIndexToCharIndex(index: Int): Int = index
+    private fun charIndexToCodepointIndex(index: Int): Int = index
+
+    /**
+     * The ordered list of non-overlapping and discontinuous changes performed on a
+     * [TextFieldBuffer] during the current [edit][TextFieldState.edit] or
+     * [filter][InputTransformation.transformInput] operation. Changes are listed in the order they appear in the
+     * text, not the order in which they were made. Overlapping changes are represented as a single
+     * change.
+     */
+    @ExperimentalFoundationApi
+    interface ChangeList {
+        /**
+         * The number of changes that have been performed.
+         */
+        val changeCount: Int
+
+        /**
+         * Returns the range in the [TextFieldBuffer] that was changed.
+         *
+         * @throws IndexOutOfBoundsException If [changeIndex] is not in [0, [changeCount]).
+         */
+        fun getRange(changeIndex: Int): TextRange
+
+        /**
+         * Returns the range in the original text that was replaced.
+         *
+         * @throws IndexOutOfBoundsException If [changeIndex] is not in [0, [changeCount]).
+         */
+        fun getOriginalRange(changeIndex: Int): TextRange
+    }
+}
+
+/**
+ * Insert [text] at the given [index] in this value. Pass 0 to insert [text] at the beginning of
+ * this buffer, and pass [TextFieldBuffer.length] to insert [text] at the end of this buffer.
+ *
+ * This is equivalent to calling `replace(index, index, text)`.
+ *
+ * @param index The character offset at which to insert [text].
+ * @param text The text to insert.
+ *
+ * @see TextFieldBuffer.replace
+ * @see TextFieldBuffer.append
+ * @see TextFieldBuffer.delete
+ */
+@ExperimentalFoundationApi
+fun TextFieldBuffer.insert(index: Int, text: String) {
+    replace(index, index, text)
+}
+
+/**
+ * Delete the text between [start] (inclusive) and [end] (exclusive). Pass 0 as [start] and
+ * [TextFieldBuffer.length] as [end] to delete everything in this buffer.
+ *
+ * @param start The character offset of the first character to delete.
+ * @param end The character offset of the first character after the deleted range.
+ *
+ * @see TextFieldBuffer.replace
+ * @see TextFieldBuffer.append
+ * @see TextFieldBuffer.insert
+ */
+@ExperimentalFoundationApi
+fun TextFieldBuffer.delete(start: Int, end: Int) {
+    replace(start, end, "")
+}
+
+/**
+ * Places the cursor at the end of the text.
+ */
+@ExperimentalFoundationApi
+fun TextFieldBuffer.placeCursorAtEnd() {
+    placeCursorBeforeCharAt(length)
+}
+
+/**
+ * Places the selection around all the text.
+ */
+@ExperimentalFoundationApi
+fun TextFieldBuffer.selectAll() {
+    selectCharsIn(TextRange(0, length))
+}
+
+/**
+ * Iterates over all the changes in this [ChangeList].
+ *
+ * Changes are iterated by index, so any changes made by [block] after the current one will be
+ * visited by [block]. [block] should not make any new changes _before_ the current one or changes
+ * will be visited more than once. If you need to make changes, consider using
+ * [forEachChangeReversed].
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
+ *
+ * @see forEachChangeReversed
+ */
+@ExperimentalFoundationApi
+inline fun ChangeList.forEachChange(
+    block: (range: TextRange, originalRange: TextRange) -> Unit
+) {
+    var i = 0
+    // Check the size every iteration in case more changes were performed.
+    while (i < changeCount) {
+        block(getRange(i), getOriginalRange(i))
+        i++
+    }
+}
+
+/**
+ * Iterates over all the changes in this [ChangeList] in reverse order.
+ *
+ * Changes are iterated by index, so [block] should not perform any new changes before the current
+ * one or changes may be skipped. [block] may make non-overlapping changes after the current one
+ * safely, such changes will not be visited.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
+ *
+ * @see forEachChange
+ */
+@ExperimentalFoundationApi
+inline fun ChangeList.forEachChangeReversed(
+    block: (range: TextRange, originalRange: TextRange) -> Unit
+) {
+    var i = changeCount - 1
+    while (i >= 0) {
+        block(getRange(i), getOriginalRange(i))
+        i--
+    }
+}
+
+/**
+ * Finds the common prefix and suffix between [a] and [b] and then reports the ranges of each that
+ * excludes those. The values are reported via an (inline) callback instead of a return value to
+ * avoid having to allocate something to hold them. If the [CharSequence]s are identical, the
+ * callback is not invoked.
+ *
+ * E.g. given `a="abcde"` and `b="abbbdefe"`, the middle diff for `a` is `"ab[cd]e"` and for `b` is
+ * `ab[bbdef]e`, so reports `aMiddle=TextRange(2, 4)` and `bMiddle=TextRange(2, 7)`.
+ */
+internal inline fun findCommonPrefixAndSuffix(
+    a: CharSequence,
+    b: CharSequence,
+    onFound: (aPrefixStart: Int, aSuffixStart: Int, bPrefixStart: Int, bSuffixStart: Int) -> Unit
+) {
+    var aStart = 0
+    var aEnd = a.length
+    var bStart = 0
+    var bEnd = b.length
+
+    // If either one is empty, the diff range is the entire non-empty one.
+    if (a.isNotEmpty() && b.isNotEmpty()) {
+        var prefixFound = false
+        var suffixFound = false
+
+        do {
+            if (!prefixFound) {
+                if (a[aStart] == b[bStart]) {
+                    aStart += 1
+                    bStart += 1
+                } else {
+                    prefixFound = true
+                }
+            }
+            if (!suffixFound) {
+                if (a[aEnd - 1] == b[bEnd - 1]) {
+                    aEnd -= 1
+                    bEnd -= 1
+                } else {
+                    suffixFound = true
+                }
+            }
+        } while (
+        // As soon as we've completely traversed one of the strings, if the other hasn't also
+        // finished being traversed then we've found the diff region.
+            aStart < aEnd && bStart < bEnd &&
+            // If we've found the end of the common prefix and the start of the common suffix we're
+            // done.
+            !(prefixFound && suffixFound)
+        )
+    }
+
+    if (aStart >= aEnd && bStart >= bEnd) {
+        return
+    }
+
+    onFound(aStart, aEnd, bStart, bEnd)
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private object EmptyChangeList : ChangeList {
+    override val changeCount: Int
+        get() = 0
+
+    override fun getRange(changeIndex: Int): TextRange {
+        throw IndexOutOfBoundsException()
+    }
+
+    override fun getOriginalRange(changeIndex: Int): TextRange {
+        throw IndexOutOfBoundsException()
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt
new file mode 100644
index 0000000..d7cae25
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.internal.toCharArray
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.coerceIn
+
+/**
+ * An immutable snapshot of the contents of a [TextFieldState].
+ *
+ * This class is a [CharSequence] and directly represents the text being edited. It also stores
+ * the current [selectionInChars] of the field, which may either represent the cursor (if the
+ * selection is [collapsed][TextRange.collapsed]) or the selection range.
+ *
+ * This class also may contain the range being composed by the IME, if any, although this is not
+ * exposed.
+ *
+ * @see TextFieldBuffer
+ */
+@ExperimentalFoundationApi
+sealed interface TextFieldCharSequence : CharSequence {
+    /**
+     * The selection range. If the selection is collapsed, it represents cursor
+     * location. When selection range is out of bounds, it is constrained with the text length.
+     */
+    val selectionInChars: TextRange
+
+    /**
+     * Composition range created by IME. If null, there is no composition range.
+     *
+     * Input service composition is an instance of text produced by IME. An example visual for the
+     * composition is that the currently composed word is visually separated from others with
+     * underline, or text background. For description of composition please check
+     * [W3C IME Composition](https://www.w3.org/TR/ime-api/#ime-composition)
+     *
+     * Composition can only be set by the system.
+     */
+    val compositionInChars: TextRange?
+
+    /**
+     * Returns true if the text in this object is equal to the text in [other], disregarding any
+     * other properties of this (such as selection) or [other].
+     */
+    fun contentEquals(other: CharSequence): Boolean
+
+    abstract override fun toString(): String
+    abstract override fun equals(other: Any?): Boolean
+    abstract override fun hashCode(): Int
+}
+
+@ExperimentalFoundationApi
+fun TextFieldCharSequence(
+    text: String = "",
+    selection: TextRange = TextRange.Zero
+): TextFieldCharSequence = TextFieldCharSequenceWrapper(text, selection, composition = null)
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence(
+    text: CharSequence,
+    selection: TextRange,
+    composition: TextRange? = null
+): TextFieldCharSequence = TextFieldCharSequenceWrapper(text, selection, composition)
+
+/**
+ * Copies the contents of this sequence from [[sourceStartIndex], [sourceEndIndex]) into
+ * [destination] starting at [destinationOffset].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.toCharArray(
+    destination: CharArray,
+    destinationOffset: Int,
+    sourceStartIndex: Int,
+    sourceEndIndex: Int
+) = (this as TextFieldCharSequenceWrapper).toCharArray(
+    destination,
+    destinationOffset,
+    sourceStartIndex,
+    sourceEndIndex
+)
+
+@OptIn(ExperimentalFoundationApi::class)
+private class TextFieldCharSequenceWrapper(
+    private val text: CharSequence,
+    selection: TextRange,
+    composition: TextRange?
+) : TextFieldCharSequence {
+
+    override val length: Int
+        get() = text.length
+
+    override val selectionInChars: TextRange = selection.coerceIn(0, text.length)
+
+    override val compositionInChars: TextRange? = composition?.coerceIn(0, text.length)
+
+    override operator fun get(index: Int): Char = text[index]
+
+    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
+        text.subSequence(startIndex, endIndex)
+
+    override fun toString(): String = text.toString()
+
+    override fun contentEquals(other: CharSequence): Boolean = text.contentEquals(other)
+
+    fun toCharArray(
+        destination: CharArray,
+        destinationOffset: Int,
+        sourceStartIndex: Int,
+        sourceEndIndex: Int
+    ) {
+        text.toCharArray(destination, destinationOffset, sourceStartIndex, sourceEndIndex)
+    }
+
+    /**
+     * Returns true if [other] is a [TextFieldCharSequence] with the same contents, text, and composition.
+     * To compare just the text, call [contentEquals].
+     */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other === null) return false
+        if (this::class != other::class) return false
+
+        other as TextFieldCharSequenceWrapper
+
+        if (selectionInChars != other.selectionInChars) return false
+        if (compositionInChars != other.compositionInChars) return false
+        if (!contentEquals(other.text)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = text.hashCode()
+        result = 31 * result + selectionInChars.hashCode()
+        result = 31 * result + (compositionInChars?.hashCode() ?: 0)
+        return result
+    }
+}
+
+/**
+ * Returns the text before the selection.
+ *
+ * @param maxChars maximum number of characters (inclusive) before the minimum value in
+ * [TextFieldCharSequence.selectionInChars].
+ *
+ * @see TextRange.min
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getTextBeforeSelection(maxChars: Int): CharSequence =
+    subSequence(kotlin.math.max(0, selectionInChars.min - maxChars), selectionInChars.min)
+
+/**
+ * Returns the text after the selection.
+ *
+ * @param maxChars maximum number of characters (exclusive) after the maximum value in
+ * [TextFieldCharSequence.selectionInChars].
+ *
+ * @see TextRange.max
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getTextAfterSelection(maxChars: Int): CharSequence =
+    subSequence(selectionInChars.max, kotlin.math.min(selectionInChars.max + maxChars, length))
+
+/**
+ * Returns the currently selected text.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getSelectedText(): CharSequence =
+    subSequence(selectionInChars.min, selectionInChars.max)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt
new file mode 100644
index 0000000..481226b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Composable
+
+/**
+ * Composable interface that allows to add decorations around text field, such as icon,
+ * placeholder, helper messages or similar, and automatically increase the hit target area
+ * of the text field.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
+ */
+@ExperimentalFoundationApi
+fun interface TextFieldDecorator {
+
+    /**
+     * To allow you to control the placement of the inner text field relative to your decorations,
+     * the text field implementation will pass in a framework-controlled composable parameter
+     * [innerTextField] to this method. You must not call [innerTextField] more than once.
+     */
+    // Composable parameters of Composable functions are normally slots for callers to inject
+    // their content but this one is a special inverted-slot API. It's better to be explicit
+    // with the naming.
+    @Suppress("ComposableLambdaParameterNaming")
+    @Composable
+    fun Decoration(innerTextField: @Composable () -> Unit)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
new file mode 100644
index 0000000..6a688a1
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
+import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+
+/**
+ * Values that specify the text wrapping, scrolling, and height measurement behavior for
+ * text fields.
+ *
+ * @see SingleLine
+ * @see MultiLine
+ */
+@ExperimentalFoundationApi
+@Stable
+sealed interface TextFieldLineLimits {
+
+    /**
+     * The text field is always a single line tall, ignores newlines in the text, and scrolls
+     * horizontally when the text overflows.
+     */
+    object SingleLine : TextFieldLineLimits
+
+    /**
+     * The text field will be at least [minHeightInLines] tall, if the text overflows it will wrap,
+     * and if the text ends up being more than one line the field will grow until it is
+     * [maxHeightInLines] tall and then start scrolling vertically.
+     *
+     * It is required that 1 ≤ [minHeightInLines] ≤ [maxHeightInLines].
+     *
+     * To specify the minimum and/or maximum height of the field in non-text units, such as dps, use
+     * the [heightIn] modifier.
+     */
+    @Immutable
+    class MultiLine(
+        val minHeightInLines: Int = 1,
+        val maxHeightInLines: Int = Int.MAX_VALUE
+    ) : TextFieldLineLimits {
+        init {
+            require(minHeightInLines in 1..maxHeightInLines) {
+                "Expected 1 ≤ minHeightInLines ≤ maxHeightInLines, were " +
+                    "$minHeightInLines, $maxHeightInLines"
+            }
+        }
+
+        override fun toString(): String =
+            "MultiLine(minHeightInLines=$minHeightInLines, maxHeightInLines=$maxHeightInLines)"
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other === null) return false
+            if (this::class != other::class) return false
+            other as MultiLine
+            if (minHeightInLines != other.minHeightInLines) return false
+            if (maxHeightInLines != other.maxHeightInLines) return false
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = minHeightInLines
+            result = 31 * result + maxHeightInLines
+            return result
+        }
+    }
+
+    companion object {
+        val Default: TextFieldLineLimits = MultiLine()
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
new file mode 100644
index 0000000..8272b3b2
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -0,0 +1,572 @@
+/*
+ * Copyright 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text.input
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.internal.EditingBuffer
+import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.coerceIn
+import androidx.compose.ui.text.input.TextFieldValue
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+
+internal fun TextFieldState(initialValue: TextFieldValue): TextFieldState {
+    return TextFieldState(
+        initialText = initialValue.text,
+        initialSelectionInChars = initialValue.selection
+    )
+}
+
+/**
+ * The editable text state of a text field, including both the [text] itself and position of the
+ * cursor or selection.
+ *
+ * To change the text field contents programmatically, call [edit], [setTextAndSelectAll],
+ * [setTextAndPlaceCursorAtEnd], or [clearText]. To observe the value of the field over time, call
+ * [forEachTextValue] or [textAsFlow].
+ *
+ * When instantiating this class from a composable, use [rememberTextFieldState] to automatically
+ * save and restore the field state. For more advanced use cases, pass [TextFieldState.Saver] to
+ * [rememberSaveable].
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2StateCompleteSample
+ */
+@ExperimentalFoundationApi
+@Stable
+class TextFieldState internal constructor(
+    initialText: String,
+    initialSelectionInChars: TextRange,
+    initialTextUndoManager: TextUndoManager
+) {
+
+    constructor(
+        initialText: String = "",
+        initialSelectionInChars: TextRange = TextRange(initialText.length)
+    ) : this(initialText, initialSelectionInChars, TextUndoManager())
+
+    /**
+     * Manages the history of edit operations that happen in this [TextFieldState].
+     */
+    internal val textUndoManager: TextUndoManager = initialTextUndoManager
+
+    /**
+     * The editing buffer used for applying editor commands from IME. All edits coming from gestures
+     * or IME commands, must be reflected on this buffer eventually.
+     */
+    @VisibleForTesting
+    internal var mainBuffer: EditingBuffer = EditingBuffer(
+        text = initialText,
+        selection = initialSelectionInChars.coerceIn(0, initialText.length)
+    )
+
+    /**
+     * The current text and selection. This value will automatically update when the user enters
+     * text or otherwise changes the text field contents. To change it programmatically, call
+     * [edit].
+     *
+     * This is backed by snapshot state, so reading this property in a restartable function (e.g.
+     * a composable function) will cause the function to restart when the text field's value
+     * changes.
+     *
+     * To observe changes to this property outside a restartable function, see [forEachTextValue]
+     * and [textAsFlow].
+     *
+     * @sample androidx.compose.foundation.samples.BasicTextField2TextDerivedStateSample
+     *
+     * @see edit
+     * @see forEachTextValue
+     * @see textAsFlow
+     */
+    var text: TextFieldCharSequence by mutableStateOf(
+        TextFieldCharSequence(initialText, initialSelectionInChars)
+    )
+        private set
+
+    /**
+     * Runs [block] with a mutable version of the current state. The block can make changes to the
+     * text and cursor/selection. See the documentation on [TextFieldBuffer] for a more detailed
+     * description of the available operations.
+     *
+     * @sample androidx.compose.foundation.samples.BasicTextField2StateEditSample
+     *
+     * @see setTextAndPlaceCursorAtEnd
+     * @see setTextAndSelectAll
+     */
+    inline fun edit(block: TextFieldBuffer.() -> Unit) {
+        val mutableValue = startEdit(text)
+        mutableValue.block()
+        commitEdit(mutableValue)
+    }
+
+    override fun toString(): String =
+        "TextFieldState(selectionInChars=${text.selectionInChars}, text=\"$text\")"
+
+    /**
+     * Undo history controller for this TextFieldState.
+     *
+     * @sample androidx.compose.foundation.samples.BasicTextField2UndoSample
+     */
+    // TextField does not implement UndoState because Undo related APIs should be able to remain
+    // separately experimental than TextFieldState
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @ExperimentalFoundationApi
+    @get:ExperimentalFoundationApi
+    val undoState: UndoState = UndoState(this)
+
+    @Suppress("ShowingMemberInHiddenClass")
+    @PublishedApi
+    internal fun startEdit(value: TextFieldCharSequence): TextFieldBuffer =
+        TextFieldBuffer(value)
+
+    /**
+     * If the text or selection in [newValue] was actually modified, updates this state's internal
+     * values. If [newValue] was not modified at all, the state is not updated, and this will not
+     * invalidate anyone who is observing this state.
+     *
+     * @param newValue [TextFieldBuffer] that contains the latest updates
+     */
+    @Suppress("ShowingMemberInHiddenClass")
+    @PublishedApi
+    internal fun commitEdit(newValue: TextFieldBuffer) {
+        val textChanged = newValue.changes.changeCount > 0
+        val selectionChanged = newValue.selectionInChars != mainBuffer.selection
+        if (textChanged || selectionChanged) {
+            val finalValue = newValue.toTextFieldCharSequence()
+            resetStateAndNotifyIme(finalValue)
+        }
+        textUndoManager.clearHistory()
+    }
+
+    /**
+     * An edit block that updates [TextFieldState] on behalf of user actions such as gestures,
+     * IME commands, hardware keyboard events, clipboard actions, and more. These modifications
+     * must also run through the given [filter] since they are user actions.
+     *
+     * Be careful that this method is not snapshot aware. It is only safe to call this from main
+     * thread, or global snapshot. Also, this function is defined as inline for performance gains,
+     * and it's not actually safe to early return from [block].
+     *
+     * Also all user edits should be recorded by [textUndoManager] since reverting to a previous
+     * state requires all edit operations to be executed in reverse. However, some commands like
+     * cut, and paste should be atomic operations that do not merge with previous or next operations
+     * in the Undo stack. This can be controlled by [undoBehavior].
+     *
+     * @param inputTransformation [InputTransformation] to run after [block] is applied
+     * @param notifyImeOfChanges Whether IME should be notified of these changes. Only pass false to
+     * this argument if the source of the changes is IME itself.
+     * @param block The function that updates the current editing buffer.
+     */
+    internal inline fun editAsUser(
+        inputTransformation: InputTransformation?,
+        notifyImeOfChanges: Boolean = true,
+        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
+        block: EditingBuffer.() -> Unit
+    ) {
+        val previousValue = text
+
+        mainBuffer.changeTracker.clearChanges()
+        mainBuffer.block()
+
+        if (mainBuffer.changeTracker.changeCount == 0 &&
+            previousValue.selectionInChars == mainBuffer.selection &&
+            previousValue.compositionInChars == mainBuffer.composition) {
+            // nothing has changed after applying block.
+            return
+        }
+
+        commitEditAsUser(previousValue, inputTransformation, notifyImeOfChanges, undoBehavior)
+    }
+
+    /**
+     * Edits the contents of this [TextFieldState] without going through an [InputTransformation],
+     * or recording the changes to the [textUndoManager]. IME would still be notified of any changes
+     * committed by [block].
+     *
+     * This method of editing is not recommended for majority of use cases. It is originally added
+     * to support applying of undo/redo actions without clearing the history. Also, it doesn't
+     * allocate an additional buffer like [edit] method because changes are ignored and it's not
+     * a public API.
+     */
+    internal inline fun editWithNoSideEffects(block: EditingBuffer.() -> Unit) {
+        val previousValue = text
+
+        mainBuffer.changeTracker.clearChanges()
+        mainBuffer.block()
+
+        val afterEditValue = TextFieldCharSequence(
+            text = mainBuffer.toString(),
+            selection = mainBuffer.selection,
+            composition = mainBuffer.composition
+        )
+
+        text = afterEditValue
+        notifyIme(previousValue, afterEditValue)
+    }
+
+    private fun commitEditAsUser(
+        previousValue: TextFieldCharSequence,
+        inputTransformation: InputTransformation?,
+        notifyImeOfChanges: Boolean,
+        undoBehavior: TextFieldEditUndoBehavior
+    ) {
+        val afterEditValue = TextFieldCharSequence(
+            text = mainBuffer.toString(),
+            selection = mainBuffer.selection,
+            composition = mainBuffer.composition
+        )
+
+        if (inputTransformation == null) {
+            val oldValue = text
+            text = afterEditValue
+            if (notifyImeOfChanges) {
+                notifyIme(oldValue, afterEditValue)
+            }
+            recordEditForUndo(previousValue, text, mainBuffer.changeTracker, undoBehavior)
+            return
+        }
+
+        val oldValue = text
+
+        // if only difference is composition, don't run filter, don't send it to undo manager
+        if (afterEditValue.contentEquals(oldValue) &&
+            afterEditValue.selectionInChars == oldValue.selectionInChars
+        ) {
+            text = afterEditValue
+            if (notifyImeOfChanges) {
+                notifyIme(oldValue, afterEditValue)
+            }
+            return
+        }
+
+        val mutableValue = TextFieldBuffer(
+            initialValue = afterEditValue,
+            sourceValue = oldValue,
+            initialChanges = mainBuffer.changeTracker
+        )
+        inputTransformation.transformInput(
+            originalValue = oldValue,
+            valueWithChanges = mutableValue
+        )
+        // If neither the text nor the selection changed, we want to preserve the composition.
+        // Otherwise, the IME will reset it anyway.
+        val afterFilterValue = mutableValue.toTextFieldCharSequence(
+            composition = afterEditValue.compositionInChars
+        )
+        if (afterFilterValue == afterEditValue) {
+            text = afterFilterValue
+            if (notifyImeOfChanges) {
+                notifyIme(oldValue, afterEditValue)
+            }
+        } else {
+            resetStateAndNotifyIme(afterFilterValue)
+        }
+        // mutableValue contains all the changes from user and the filter.
+        recordEditForUndo(previousValue, text, mutableValue.changes, undoBehavior)
+    }
+
+    /**
+     * Records the difference between [previousValue] and [postValue], defined by [changes],
+     * into [textUndoManager] according to the strategy defined by [undoBehavior].
+     */
+    private fun recordEditForUndo(
+        previousValue: TextFieldCharSequence,
+        postValue: TextFieldCharSequence,
+        changes: TextFieldBuffer.ChangeList,
+        undoBehavior: TextFieldEditUndoBehavior
+    ) {
+        when (undoBehavior) {
+            TextFieldEditUndoBehavior.ClearHistory -> {
+                textUndoManager.clearHistory()
+            }
+            TextFieldEditUndoBehavior.MergeIfPossible -> {
+                textUndoManager.recordChanges(
+                    pre = previousValue,
+                    post = postValue,
+                    changes = changes,
+                    allowMerge = true
+                )
+            }
+            TextFieldEditUndoBehavior.NeverMerge -> {
+                textUndoManager.recordChanges(
+                    pre = previousValue,
+                    post = postValue,
+                    changes = changes,
+                    allowMerge = false
+                )
+            }
+        }
+    }
+
+    internal fun addNotifyImeListener(notifyImeListener: NotifyImeListener) {
+        notifyImeListeners.add(notifyImeListener)
+    }
+
+    internal fun removeNotifyImeListener(notifyImeListener: NotifyImeListener) {
+        notifyImeListeners.remove(notifyImeListener)
+    }
+
+    /**
+     * A listener that can be attached to a [TextFieldState] to listen for change events that may
+     * interest IME.
+     *
+     * State in [TextFieldState] can change through various means but categorically there are two
+     * sources; Developer([TextFieldState.edit]) and User([TextFieldState.editAsUser]). Only
+     * non-filtered IME sourced changes can skip updating the IME. Otherwise, all changes must be
+     * contacted to IME to let it synchronize its state with the [TextFieldState]. Such
+     * communication is built by IME registering a [NotifyImeListener] on a [TextFieldState].
+     */
+    internal fun interface NotifyImeListener {
+
+        fun onChange(oldValue: TextFieldCharSequence, newValue: TextFieldCharSequence)
+    }
+
+    /**
+     * Must be called whenever [text] needs to change but the content of the changes are not yet
+     * replicated on [mainBuffer].
+     *
+     * This method updates the internal editing buffer with the given [TextFieldCharSequence], it
+     * also notifies the IME about the selection or composition changes.
+     */
+    @VisibleForTesting
+    internal fun resetStateAndNotifyIme(newValue: TextFieldCharSequence) {
+        val bufferState = TextFieldCharSequence(
+            mainBuffer.toString(),
+            mainBuffer.selection,
+            mainBuffer.composition
+        )
+
+        var textChanged = false
+        var selectionChanged = false
+        val compositionChanged = newValue.compositionInChars != mainBuffer.composition
+
+        if (!bufferState.contentEquals(newValue)) {
+            // reset the buffer in its entirety
+            mainBuffer = EditingBuffer(
+                text = newValue.toString(),
+                selection = newValue.selectionInChars
+            )
+            textChanged = true
+        } else if (bufferState.selectionInChars != newValue.selectionInChars) {
+            mainBuffer.setSelection(newValue.selectionInChars.start, newValue.selectionInChars.end)
+            selectionChanged = true
+        }
+
+        val composition = newValue.compositionInChars
+        if (composition == null || composition.collapsed) {
+            mainBuffer.commitComposition()
+        } else {
+            mainBuffer.setComposition(composition.min, composition.max)
+        }
+
+        if (textChanged || (!selectionChanged && compositionChanged)) {
+            mainBuffer.commitComposition()
+        }
+
+        val finalValue = TextFieldCharSequence(
+            if (textChanged) newValue else bufferState,
+            mainBuffer.selection,
+            mainBuffer.composition
+        )
+
+        // value must be set before notifyImeListeners are called. Even though we are sending the
+        // previous and current values, a system callback may request the latest state e.g. IME
+        // restartInput call is handled before notifyImeListeners return.
+        text = finalValue
+
+        notifyIme(bufferState, finalValue)
+    }
+
+    private val notifyImeListeners = mutableVectorOf<NotifyImeListener>()
+
+    private fun notifyIme(
+        oldValue: TextFieldCharSequence,
+        newValue: TextFieldCharSequence
+    ) {
+        notifyImeListeners.forEach { it.onChange(oldValue, newValue) }
+    }
+
+    /**
+     * Saves and restores a [TextFieldState] for [rememberSaveable].
+     *
+     * @see rememberTextFieldState
+     */
+    // Preserve nullability since this is public API.
+    @Suppress("RedundantNullableReturnType")
+    object Saver : androidx.compose.runtime.saveable.Saver<TextFieldState, Any> {
+
+        override fun SaverScope.save(value: TextFieldState): Any? {
+            return listOf(
+                value.text.toString(),
+                value.text.selectionInChars.start,
+                value.text.selectionInChars.end,
+                with(TextUndoManager.Companion.Saver) {
+                    save(value.textUndoManager)
+                }
+            )
+        }
+
+        override fun restore(value: Any): TextFieldState? {
+            val (text, selectionStart, selectionEnd, savedTextUndoManager) = value as List<*>
+            return TextFieldState(
+                initialText = text as String,
+                initialSelectionInChars = TextRange(
+                    start = selectionStart as Int,
+                    end = selectionEnd as Int
+                ),
+                initialTextUndoManager = with(TextUndoManager.Companion.Saver) {
+                    restore(savedTextUndoManager!!)
+                }!!
+            )
+        }
+    }
+}
+
+/**
+ * Returns a [Flow] of the values of [TextFieldState.text] as seen from the global snapshot.
+ * The initial value is emitted immediately when the flow is collected.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2TextValuesSample
+ */
+@ExperimentalFoundationApi
+fun TextFieldState.textAsFlow(): Flow<TextFieldCharSequence> = snapshotFlow { text }
+
+/**
+ * Create and remember a [TextFieldState]. The state is remembered using [rememberSaveable] and so
+ * will be saved and restored with the composition.
+ *
+ * If you need to store a [TextFieldState] in another object, use the [TextFieldState.Saver] object
+ * to manually save and restore the state.
+ */
+@ExperimentalFoundationApi
+@Composable
+fun rememberTextFieldState(
+    initialText: String = "",
+    initialSelectionInChars: TextRange = TextRange(initialText.length)
+): TextFieldState = rememberSaveable(saver = TextFieldState.Saver) {
+    TextFieldState(initialText, initialSelectionInChars)
+}
+
+/**
+ * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
+ * and places the cursor at the end of the new text.
+ *
+ * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
+ * equivalent to calling:
+ * ```
+ * edit {
+ *   replace(0, length, text)
+ *   placeCursorAtEnd()
+ * }
+ * ```
+ *
+ * @see setTextAndSelectAll
+ * @see clearText
+ * @see TextFieldBuffer.placeCursorAtEnd
+ */
+@ExperimentalFoundationApi
+fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String) {
+    edit {
+        replace(0, length, text)
+        placeCursorAtEnd()
+    }
+}
+
+/**
+ * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
+ * and selects all the text.
+ *
+ * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
+ * equivalent to calling:
+ * ```
+ * edit {
+ *   replace(0, length, text)
+ *   selectAll()
+ * }
+ * ```
+ *
+ * @see setTextAndPlaceCursorAtEnd
+ * @see clearText
+ * @see TextFieldBuffer.selectAll
+ */
+@ExperimentalFoundationApi
+fun TextFieldState.setTextAndSelectAll(text: String) {
+    edit {
+        replace(0, length, text)
+        selectAll()
+    }
+}
+
+/**
+ * Deletes all the text in the state.
+ *
+ * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
+ * equivalent to calling:
+ * ```
+ * edit {
+ *   delete(0, length)
+ *   placeCursorAtEnd()
+ * }
+ * ```
+ *
+ * @see setTextAndPlaceCursorAtEnd
+ * @see setTextAndSelectAll
+ */
+@ExperimentalFoundationApi
+fun TextFieldState.clearText() {
+    edit {
+        delete(0, length)
+        placeCursorAtEnd()
+    }
+}
+
+/**
+ * Invokes [block] with the value of [TextFieldState.text], and every time the value is changed.
+ *
+ * The caller will be suspended until its coroutine is cancelled. If the text is changed while
+ * [block] is suspended, [block] will be cancelled and re-executed with the new value immediately.
+ * [block] will never be executed concurrently with itself.
+ *
+ * To get access to a [Flow] of [TextFieldState.text] over time, use [textAsFlow].
+ *
+ * Warning: Do not update the value of the [TextFieldState] from [block]. If you want to perform
+ * either a side effect when text is changed, or filter it in some way, use an
+ * [InputTransformation].
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextField2ForEachTextValueSample
+ *
+ * @see textAsFlow
+ */
+@ExperimentalFoundationApi
+suspend fun TextFieldState.forEachTextValue(
+    block: suspend (TextFieldCharSequence) -> Unit
+): Nothing {
+    textAsFlow().collectLatest(block)
+    error("textAsFlow expected not to complete without exception")
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextObfuscationMode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextObfuscationMode.kt
new file mode 100644
index 0000000..7f14ab7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextObfuscationMode.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+
+/**
+ * Defines how the text will be obscured in secure text fields.
+ *
+ * Text obscuring refers to replacing the original text content with a mask via various methods.
+ * It is most common in password fields.
+ *
+ * The default behavior for typing input on Desktop has always been to keep it completely hidden.
+ * However, on mobile devices, the default behavior is to briefly reveal the last typed character
+ * for a short period or until another character is typed. This helps the user to follow the text
+ * input while also protecting their privacy by not revealing too much information to others.
+ */
+@ExperimentalFoundationApi
+@JvmInline
+value class TextObfuscationMode internal constructor(val value: Int) {
+    companion object {
+        /**
+         * Do not obscure any content, making all the content visible.
+         *
+         * It can be useful when you want to briefly reveal the content by clicking a reveal button.
+         */
+        val Visible = TextObfuscationMode(0)
+
+        /**
+         * Default behavior on mobile devices. Reveals the last typed character for a short amount
+         * of time.
+         *
+         * Note; on Android this feature also depends on a system setting called
+         * `Settings.System.TEXT_SHOW_PASSWORD`. If the system setting is disabled, this option
+         * behaves exactly as [Hidden].
+         */
+        val RevealLastTyped = TextObfuscationMode(1)
+
+        /**
+         * Default behavior on desktop platforms. All characters are hidden.
+         */
+        val Hidden = TextObfuscationMode(2)
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextUndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextUndoManager.kt
new file mode 100644
index 0000000..39735ab
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextUndoManager.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.SNAPSHOTS_INTERVAL_MILLIS
+import androidx.compose.foundation.text.input.internal.undo.TextDeleteType
+import androidx.compose.foundation.text.input.internal.undo.TextEditType
+import androidx.compose.foundation.text.input.internal.undo.TextUndoOperation
+import androidx.compose.foundation.text.input.internal.undo.UndoManager
+import androidx.compose.foundation.text.input.internal.undo.redo
+import androidx.compose.foundation.text.input.internal.undo.undo
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.text.substring
+
+/**
+ * An undo manager designed specifically for text editing. This class is mostly responsible for
+ * delegating the incoming undo/redo/record/clear requests to its own [undoManager]. Its most
+ * important task is to keep a separate staging area for incoming text edit operations to possibly
+ * merge them before committing as a single undoable action.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextUndoManager(
+    initialStagingUndo: TextUndoOperation? = null,
+    private val undoManager: UndoManager<TextUndoOperation> = UndoManager(
+        capacity = TEXT_UNDO_CAPACITY
+    )
+) {
+    private var stagingUndo by mutableStateOf<TextUndoOperation?>(initialStagingUndo)
+
+    val canUndo: Boolean
+        get() = undoManager.canUndo || stagingUndo != null
+
+    val canRedo: Boolean
+        get() = undoManager.canRedo && stagingUndo == null
+
+    fun undo(state: TextFieldState) {
+        if (!canUndo) {
+            return
+        }
+
+        flush()
+        state.undo(undoManager.undo())
+    }
+
+    fun redo(state: TextFieldState) {
+        if (!canRedo) {
+            return
+        }
+
+        state.redo(undoManager.redo())
+    }
+
+    fun record(op: TextUndoOperation) {
+        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
+        if (unobservedStagingUndo == null) {
+            stagingUndo = op
+            return
+        }
+
+        val mergedOp = unobservedStagingUndo.merge(op)
+
+        if (mergedOp != null) {
+            // merged operation should replace the top op
+            stagingUndo = mergedOp
+            return
+        }
+
+        // no merge, flush the staging area and put the new operation in
+        flush()
+        stagingUndo = op
+    }
+
+    fun clearHistory() {
+        stagingUndo = null
+        undoManager.clearHistory()
+    }
+
+    private fun flush() {
+        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
+        unobservedStagingUndo?.let { undoManager.record(it) }
+        stagingUndo = null
+    }
+
+    companion object {
+        object Saver : androidx.compose.runtime.saveable.Saver<TextUndoManager, Any> {
+            private val undoManagerSaver = UndoManager.createSaver(TextUndoOperation.Saver)
+
+            override fun SaverScope.save(value: TextUndoManager): Any {
+                return listOf(
+                    value.stagingUndo?.let {
+                        with(TextUndoOperation.Saver) {
+                            save(it)
+                        }
+                    },
+                    with(undoManagerSaver) {
+                        save(value.undoManager)
+                    }
+                )
+            }
+
+            override fun restore(value: Any): TextUndoManager? {
+                val (savedStagingUndo, savedUndoManager) = value as List<*>
+                return TextUndoManager(
+                    savedStagingUndo?.let {
+                        with(TextUndoOperation.Saver) {
+                            restore(it)
+                        }
+                    },
+                    with(undoManagerSaver) {
+                        restore(savedUndoManager!!)
+                    }!!
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Try to merge this [TextUndoOperation] with the [next]. Chronologically the [next] op must
+ * come after this one. If merge is not possible, this function returns null.
+ *
+ * There are many rules that govern the grouping logic of successive undo operations. Here we try
+ * to cover the most basic requirements but this is certainly not an exhaustive list.
+ *
+ * 1. Each action defines whether they can be merged at all. For example, text edits that are
+ * caused by cut or paste define themselves as unmergeable no matter what comes before or next.
+ * 2. If certain amount of time has passed since the latest grouping has begun.
+ * 3. Enter key (hard line break) is unmergeable.
+ * 4. Only same type of text edits can be merged. An insertion must be grouped by other insertions,
+ * a deletion by other deletions. Replace type of edit is never mergeable.
+ *   4.a. Two insertions can only be merged if the chronologically next one is a suffix of the
+ *   previous insertion. In other words, cursor should always be moving forwards.
+ *   4.b. Deletions have directionality. Cursor can only insert in place and move forwards but
+ *   deletion can be requested either forwards (delete) or backwards (backspace). Only deletions
+ *   that have the same direction can be merged. They also have to share a boundary.
+ */
+internal fun TextUndoOperation.merge(next: TextUndoOperation): TextUndoOperation? {
+    if (!canMerge || !next.canMerge) return null
+    // Do not merge if [other] came before this op, or if certain amount of time has passed
+    // between these ops
+    if (
+        next.timeInMillis < timeInMillis ||
+        next.timeInMillis - timeInMillis >= SNAPSHOTS_INTERVAL_MILLIS
+    ) return null
+    // Do not merge undo history when one of the ops is a new line insertion
+    if (isNewLineInsert || next.isNewLineInsert) return null
+    // Only same type of ops can be merged together
+    if (textEditType != next.textEditType) return null
+
+    // only merge insertions if the chronologically next one continuous from the end of
+    // this previous insertion
+    if (textEditType == TextEditType.Insert && index + postText.length == next.index) {
+        return TextUndoOperation(
+            index = index,
+            preText = "",
+            postText = postText + next.postText,
+            preSelection = this.preSelection,
+            postSelection = next.postSelection,
+            timeInMillis = timeInMillis
+        )
+    } else if (textEditType == TextEditType.Delete) {
+        // only merge consecutive deletions if both have the same directionality
+        if (
+            deletionType == next.deletionType &&
+            (deletionType == TextDeleteType.Start || deletionType == TextDeleteType.End)
+        ) {
+            // This op deletes
+            if (index == next.index + next.preText.length) {
+                return TextUndoOperation(
+                    index = next.index,
+                    preText = next.preText + preText,
+                    postText = "",
+                    preSelection = preSelection,
+                    postSelection = next.postSelection,
+                    timeInMillis = timeInMillis
+                )
+            } else if (index == next.index) {
+                return TextUndoOperation(
+                    index = index,
+                    preText = preText + next.preText,
+                    postText = "",
+                    preSelection = preSelection,
+                    postSelection = next.postSelection,
+                    timeInMillis = timeInMillis
+                )
+            }
+        }
+    }
+    return null
+}
+
+/**
+ * Adds the [changes] to this [UndoManager] by converting from [TextFieldBuffer.ChangeList] space
+ * to [TextUndoOperation] space.
+ *
+ * @param pre State of the [TextFieldBuffer] before any changes are applied
+ * @param post State of the [TextFieldBuffer] after all the changes are applied
+ * @param changes List of changes that are applied on [pre] that transforms it to [post].
+ * @param allowMerge Whether to allow merging the calculated operation with the last operation
+ * in the stack.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextUndoManager.recordChanges(
+    pre: TextFieldCharSequence,
+    post: TextFieldCharSequence,
+    changes: TextFieldBuffer.ChangeList,
+    allowMerge: Boolean = true
+) {
+    // if there are unmerged changes coming from a single edit, force merge all of them to
+    // create a single replace undo operation
+    if (changes.changeCount > 1) {
+        record(
+            TextUndoOperation(
+                index = 0,
+                preText = pre.toString(),
+                postText = post.toString(),
+                preSelection = pre.selectionInChars,
+                postSelection = post.selectionInChars,
+                canMerge = false
+            )
+        )
+    } else if (changes.changeCount == 1) {
+        val preRange = changes.getOriginalRange(0)
+        val postRange = changes.getRange(0)
+        if (!preRange.collapsed || !postRange.collapsed) {
+            record(
+                TextUndoOperation(
+                    index = preRange.min,
+                    preText = pre.substring(preRange),
+                    postText = post.substring(postRange),
+                    preSelection = pre.selectionInChars,
+                    postSelection = post.selectionInChars,
+                    canMerge = allowMerge
+                )
+            )
+        }
+    }
+}
+
+/**
+ * Determines whether this operation is adding a new hard line break. This type of change produces
+ * unmergable [TextUndoOperation].
+ */
+private val TextUndoOperation.isNewLineInsert
+    get() = this.postText == "\n" || this.postText == "\r\n"
+
+private const val TEXT_UNDO_CAPACITY = 100
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/UndoState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/UndoState.kt
new file mode 100644
index 0000000..5346df7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/UndoState.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+
+/**
+ * Defines an interactable undo history.
+ */
+@ExperimentalFoundationApi
+class UndoState internal constructor(private val state: TextFieldState) {
+
+    /**
+     * Whether it is possible to execute a meaningful undo action right now. If this value is false,
+     * calling `undo` would be a no-op.
+     */
+    @Suppress("GetterSetterNames")
+    @get:Suppress("GetterSetterNames")
+    val canUndo: Boolean
+        get() = state.textUndoManager.canUndo
+
+    /**
+     * Whether it is possible to execute a meaningful redo action right now. If this value is false,
+     * calling `redo` would be a no-op.
+     */
+    @Suppress("GetterSetterNames")
+    @get:Suppress("GetterSetterNames")
+    val canRedo: Boolean
+        get() = state.textUndoManager.canRedo
+
+    /**
+     * Reverts the latest edit action or a group of actions that are merged together. Calling it
+     * repeatedly can continue undoing the previous actions.
+     */
+    fun undo() {
+        state.textUndoManager.undo(state)
+    }
+
+    /**
+     * Re-applies a change that was previously reverted via [undo].
+     */
+    fun redo() {
+        state.textUndoManager.redo(state)
+    }
+
+    /**
+     * Clears all undo and redo history up to this point.
+     */
+    fun clearHistory() {
+        state.textUndoManager.clearHistory()
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ChangeTracker.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ChangeTracker.kt
new file mode 100644
index 0000000..2952231
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ChangeTracker.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Keeps track of changes made to a text buffer via [trackChange], and reports changes as a
+ * [ChangeList].
+ *
+ * @param initialChanges If non-null, used to initialize this tracker by copying changes.
+ */
+internal class ChangeTracker(initialChanges: ChangeTracker? = null) : ChangeList {
+
+    private var _changes = mutableVectorOf<Change>()
+    private var _changesTemp = mutableVectorOf<Change>()
+
+    init {
+        initialChanges?._changes?.forEach {
+            _changes += Change(it.preStart, it.preEnd, it.originalStart, it.originalEnd)
+        }
+    }
+
+    override val changeCount: Int
+        get() = _changes.size
+
+    /**
+     * This function deals with three "coordinate spaces":
+     *  - `original`: The text before any changes were made.
+     *  - `pre`: The text before this change is applied, but with all previous changes applied.
+     *  - `post`: The text after this change is applied, including all the previous changes.
+     *
+     * When this function is called, the existing changes map ranges in `original` to ranges in
+     * `pre`. The new change is a mapping from `pre` to `post`. This function must determine the
+     * corresponding range in `original` for this change, and convert all other changes' `pre`
+     * ranges to `post`. It must also ensure that any adjacent or overlapping ranges are merged to
+     * ensure the [ChangeList] invariant that all changes are non-overlapping.
+     *
+     * The algorithm works as follows:
+     *  1. Find all the changes that are adjacent to or overlap with this one. This search is
+     *     performed in the `pre` space since that's the space the new change shares with the
+     *     existing changes.
+     *  2. Merge all the changes from (1) into a single range in the `original` and `pre` spaces.
+     *  3. Merge the new change with the change from (2), updating the end of the range to account
+     *     for the new text.
+     *  3. Offset all remaining changes are to account for the new text.
+     */
+    fun trackChange(preStart: Int, preEnd: Int, postLength: Int) {
+        if (preStart == preEnd && postLength == 0) {
+            // Ignore noop changes.
+            return
+        }
+        val preMin = minOf(preStart, preEnd)
+        val preMax = maxOf(preStart, preEnd)
+
+        var i = 0
+        var recordedNewChange = false
+        val postDelta = postLength - (preMax - preMin)
+
+        var mergedOverlappingChange: Change? = null
+        while (i < _changes.size) {
+            val change = _changes[i]
+
+            // Merge adjacent and overlapping changes as we go.
+            if (
+                change.preStart in preMin..preMax ||
+                change.preEnd in preMin..preMax
+            ) {
+                if (mergedOverlappingChange == null) {
+                    mergedOverlappingChange = change
+                } else {
+                    mergedOverlappingChange.preEnd = change.preEnd
+                    mergedOverlappingChange.originalEnd = change.originalEnd
+                }
+                // Don't append overlapping changes to the temp list until we're finished merging.
+                i++
+                continue
+            }
+
+            if (change.preStart > preMax && !recordedNewChange) {
+                // First non-overlapping change after the new one – record the change before
+                // proceeding.
+                appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
+                recordedNewChange = true
+            }
+
+            if (recordedNewChange) {
+                change.preStart += postDelta
+                change.preEnd += postDelta
+            }
+            _changesTemp += change
+            i++
+        }
+
+        if (!recordedNewChange) {
+            // The new change is after or overlapping all previous changes so it hasn't been
+            // appended yet.
+            appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
+        }
+
+        // Swap the lists.
+        val oldChanges = _changes
+        _changes = _changesTemp
+        _changesTemp = oldChanges
+        _changesTemp.clear()
+    }
+
+    fun clearChanges() {
+        _changes.clear()
+    }
+
+    override fun getRange(changeIndex: Int): TextRange =
+        _changes[changeIndex].let { TextRange(it.preStart, it.preEnd) }
+
+    override fun getOriginalRange(changeIndex: Int): TextRange =
+        _changes[changeIndex].let { TextRange(it.originalStart, it.originalEnd) }
+
+    override fun toString(): String = buildString {
+        append("ChangeList(changes=[")
+        _changes.forEachIndexed { i, change ->
+            append(
+                "(${change.originalStart},${change.originalEnd})->" +
+                    "(${change.preStart},${change.preEnd})"
+            )
+            if (i < changeCount - 1) append(", ")
+        }
+        append("])")
+    }
+
+    private fun appendNewChange(
+        mergedOverlappingChange: Change?,
+        preMin: Int,
+        preMax: Int,
+        postDelta: Int
+    ) {
+        var originalDelta = if (_changesTemp.isEmpty()) 0 else {
+            _changesTemp.last().let { it.preEnd - it.originalEnd }
+        }
+        val newChange: Change
+        if (mergedOverlappingChange == null) {
+            // There were no overlapping changes, so allocate a new one.
+            val originalStart = preMin - originalDelta
+            val originalEnd = originalStart + (preMax - preMin)
+            newChange = Change(
+                preStart = preMin,
+                preEnd = preMax + postDelta,
+                originalStart = originalStart,
+                originalEnd = originalEnd
+            )
+        } else {
+            newChange = mergedOverlappingChange
+            // Convert the merged overlapping changes to the `post` space.
+            // Merge the new changed with the merged overlapping changes.
+            if (newChange.preStart > preMin) {
+                // The new change starts before the merged overlapping set.
+                newChange.preStart = preMin
+                newChange.originalStart = preMin
+            }
+            if (preMax > newChange.preEnd) {
+                // The new change ends after the merged overlapping set.
+                originalDelta = newChange.preEnd - newChange.originalEnd
+                newChange.preEnd = preMax
+                newChange.originalEnd = preMax - originalDelta
+            }
+            newChange.preEnd += postDelta
+        }
+        _changesTemp += newChange
+    }
+
+    private data class Change(
+        var preStart: Int,
+        var preEnd: Int,
+        var originalStart: Int,
+        var originalEnd: Int
+    )
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.kt
new file mode 100644
index 0000000..efac719
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+internal expect fun CharSequence.codePointAt(index: Int): Int
+internal expect fun CharSequence.codePointCount(): Int
+internal expect fun charCount(codePoint: Int): Int
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformation.kt
new file mode 100644
index 0000000..fcad583
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointTransformation.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.appendCodePointX
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.runtime.Stable
+
+/**
+ * Visual transformation interface for input fields.
+ *
+ * This interface is responsible for 1-to-1 mapping of every codepoint in input state to another
+ * codepoint before text is rendered. Visual transformation is useful when the underlying source
+ * of input needs to remain but rendered content should look different, e.g. password obscuring.
+ */
+@ExperimentalFoundationApi
+@Stable
+internal fun interface CodepointTransformation {
+
+    /**
+     * Transforms a single [codepoint] located at [codepointIndex] to another codepoint.
+     *
+     * A codepoint is an integer that always maps to a single character. Every codepoint in Unicode
+     * is comprised of 16 bits, 2 bytes.
+     */
+    // TODO: add more codepoint explanation or doc referral
+    fun transform(codepointIndex: Int, codepoint: Int): Int
+
+    companion object
+}
+
+/**
+ * Creates a masking [CodepointTransformation] that maps all codepoints to a specific [character].
+ */
+@ExperimentalFoundationApi
+@Stable
+internal fun CodepointTransformation.Companion.mask(character: Char): CodepointTransformation =
+    MaskCodepointTransformation(character)
+
+@OptIn(ExperimentalFoundationApi::class)
+private data class MaskCodepointTransformation(val character: Char) : CodepointTransformation {
+    override fun transform(codepointIndex: Int, codepoint: Int): Int {
+        return character.code
+    }
+}
+
+/**
+ * [CodepointTransformation] that converts all line breaks (\n) into white space(U+0020) and
+ * carriage returns(\r) to zero-width no-break space (U+FEFF). This transformation forces any
+ * content to appear as single line.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal object SingleLineCodepointTransformation : CodepointTransformation {
+
+    private const val LINE_FEED = '\n'.code
+    private const val CARRIAGE_RETURN = '\r'.code
+
+    private const val WHITESPACE = ' '.code
+    private const val ZERO_WIDTH_SPACE = '\uFEFF'.code
+
+    override fun transform(codepointIndex: Int, codepoint: Int): Int {
+        if (codepoint == LINE_FEED) return WHITESPACE
+        if (codepoint == CARRIAGE_RETURN) return ZERO_WIDTH_SPACE
+        return codepoint
+    }
+
+    override fun toString(): String {
+        return "SingleLineCodepointTransformation"
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.toVisualText(
+    codepointTransformation: CodepointTransformation,
+    offsetMappingCalculator: OffsetMappingCalculator
+): CharSequence {
+    val text = this
+    var changed = false
+    val newText = buildString {
+        var charOffset = 0
+        var codePointOffset = 0
+        while (charOffset < text.length) {
+            val codePoint = text.codePointAt(charOffset)
+            val newCodePoint = codepointTransformation.transform(codePointOffset, codePoint)
+            val charCount = charCount(codePoint)
+            if (newCodePoint != codePoint) {
+                changed = true
+                val newCharCount = charCount(newCodePoint)
+                offsetMappingCalculator.recordEditOperation(
+                    sourceStart = length,
+                    sourceEnd = length + charCount,
+                    newLength = newCharCount
+                )
+            }
+            appendCodePointX(newCodePoint)
+
+            charOffset += charCount
+            codePointOffset += 1
+        }
+    }
+
+    // Return the same instance if nothing changed, which signals to the caller that nothing changed
+    // and allows the new string to be GC'd earlier.
+    return if (changed) newText else this
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationState.kt
new file mode 100644
index 0000000..e9898ab
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnimationState.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.AtomicReference
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.setValue
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * Holds the state of the animation that blinks the cursor.
+ *
+ * We can't use the Compose Animation APIs because they busy-loop on delays, and this animation
+ * spends most of its time delayed so that's a ton of wasted frames. Pure coroutine delays, however,
+ * will not cause any work to be done until the delay is over.
+ */
+internal class CursorAnimationState {
+
+    private var animationJob = AtomicReference<Job?>(null)
+
+    /**
+     * The alpha value that should be used to draw the cursor.
+     * Will always be in the range [0, 1].
+     */
+    var cursorAlpha by mutableFloatStateOf(0f)
+        private set
+
+    /**
+     * Immediately shows the cursor (sets [cursorAlpha] to 1f) and starts blinking it on and off
+     * every 500ms. If a previous animation was running, it will be cancelled before the new one
+     * starts.
+     *
+     * Won't return until the animation cancelled via [cancelAndHide] or this coroutine's [Job] is
+     * cancelled. In both cases, the cursor will always end up hidden.
+     */
+    suspend fun snapToVisibleAndAnimate() {
+        coroutineScope {
+            // Can't do a single atomic update because we need to get the old value before launching
+            // the new coroutine. So we set to null first, and then launch only if still null (i.e.
+            // no other caller beat us to starting a new animation).
+            val oldJob = animationJob.getAndSet(null)
+
+            // Even though we're launching a new coroutine, because of structured concurrency, the
+            // restart function won't return until the animation is finished, and cancelling the
+            // calling coroutine will cancel the animation.
+            animationJob.compareAndSet(null, launch {
+                // Join the old job after cancelling to ensure it finishes its finally block before
+                // we start changing the cursor alpha, so we don't end up interleaving alpha
+                // updates.
+                oldJob?.cancelAndJoin()
+
+                // Start the new animation and run until cancelled.
+                try {
+                    while (true) {
+                        cursorAlpha = 1f
+                        // Ignore MotionDurationScale – the cursor should blink even when animations
+                        // are disabled by the system.
+                        delay(500)
+                        cursorAlpha = 0f
+                        delay(500)
+                    }
+                } finally {
+                    // Hide cursor when the animation is cancelled.
+                    cursorAlpha = 0f
+                }
+            })
+        }
+    }
+
+    /**
+     * Immediately cancels the cursor animation and hides the cursor (sets [cursorAlpha] to 0f).
+     */
+    fun cancelAndHide() {
+        val job = animationJob.getAndSet(null)
+        job?.cancel()
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
new file mode 100644
index 0000000..2859b31
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.text.findFollowingBreak
+import androidx.compose.foundation.text.findPrecedingBreak
+
+/**
+ * Commit final [text] to the text box and set the new cursor position.
+ *
+ * See [`commitText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#commitText(java.lang.CharSequence,%20int)).
+ *
+ * @param text The text to commit.
+ * @param newCursorPosition The cursor position after inserted text.
+ */
+internal fun EditingBuffer.commitText(
+    text: String,
+    newCursorPosition: Int
+) {
+    // API description says replace ongoing composition text if there. Then, if there is no
+    // composition text, insert text into cursor position or replace selection.
+    if (hasComposition()) {
+        replace(compositionStart, compositionEnd, text)
+    } else {
+        // In this editing buffer, insert into cursor or replace selection are equivalent.
+        replace(selectionStart, selectionEnd, text)
+    }
+
+    // After replace function is called, the editing buffer places the cursor at the end of the
+    // modified range.
+    val newCursor = cursor
+
+    // See above API description for the meaning of newCursorPosition.
+    val newCursorInBuffer = if (newCursorPosition > 0) {
+        newCursor + newCursorPosition - 1
+    } else {
+        newCursor + newCursorPosition - text.length
+    }
+
+    cursor = newCursorInBuffer.coerceIn(0, length)
+}
+
+/**
+ * Mark a certain region of text as composing text.
+ *
+ * See [`setComposingRegion`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setComposingRegion(int,%2520int)).
+ *
+ * @param start The inclusive start offset of the composing region.
+ * @param end The exclusive end offset of the composing region
+ */
+internal fun EditingBuffer.setComposingRegion(
+    start: Int,
+    end: Int
+) {
+    // The API description says, different from SetComposingText, SetComposingRegion must
+    // preserve the ongoing composition text and set new composition.
+    if (hasComposition()) {
+        commitComposition()
+    }
+
+    // Sanitize the input: reverse if reversed, clamped into valid range, ignore empty range.
+    val clampedStart = start.coerceIn(0, length)
+    val clampedEnd = end.coerceIn(0, length)
+    if (clampedStart == clampedEnd) {
+        // do nothing. empty composition range is not allowed.
+    } else if (clampedStart < clampedEnd) {
+        setComposition(clampedStart, clampedEnd)
+    } else {
+        setComposition(clampedEnd, clampedStart)
+    }
+}
+
+/**
+ * Replace the currently composing text with the given text, and set the new cursor position. Any
+ * composing text set previously will be removed automatically.
+ *
+ * See [`setComposingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setComposingText(java.lang.CharSequence,%2520int)).
+ *
+ * @param text The composing text.
+ * @param newCursorPosition The cursor position after setting composing text.
+ */
+internal fun EditingBuffer.setComposingText(
+    text: String,
+    newCursorPosition: Int
+) {
+    if (hasComposition()) {
+        // API doc says, if there is ongoing composing text, replace it with new text.
+        val compositionStart = compositionStart
+        replace(compositionStart, compositionEnd, text)
+        if (text.isNotEmpty()) {
+            setComposition(compositionStart, compositionStart + text.length)
+        }
+    } else {
+        // If there is no composing text, insert composing text into cursor position with
+        // removing selected text if any.
+        val selectionStart = selectionStart
+        replace(selectionStart, selectionEnd, text)
+        if (text.isNotEmpty()) {
+            setComposition(selectionStart, selectionStart + text.length)
+        }
+    }
+
+    // After replace function is called, the editing buffer places the cursor at the end of the
+    // modified range.
+    val newCursor = cursor
+
+    // See above API description for the meaning of newCursorPosition.
+    val newCursorInBuffer = if (newCursorPosition > 0) {
+        newCursor + newCursorPosition - 1
+    } else {
+        newCursor + newCursorPosition - text.length
+    }
+
+    cursor = newCursorInBuffer.coerceIn(0, length)
+}
+
+/**
+ * Delete [lengthBeforeCursor] characters of text before the current cursor position, and delete
+ * [lengthAfterCursor] characters of text after the current cursor position, excluding the selection.
+ *
+ * Before and after refer to the order of the characters in the string, not to their visual
+ * representation.
+ *
+ * See [`deleteSurroundingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingText(int,%2520int)).
+ *
+ * @param lengthBeforeCursor The number of characters in UTF-16 before the cursor to be deleted.
+ * Must be non-negative.
+ * @param lengthAfterCursor The number of characters in UTF-16 after the cursor to be deleted.
+ * Must be non-negative.
+ */
+internal fun EditingBuffer.deleteSurroundingText(
+    lengthBeforeCursor: Int,
+    lengthAfterCursor: Int
+) {
+    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+        "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
+            "$lengthBeforeCursor and $lengthAfterCursor respectively."
+    }
+
+    // calculate the end with safe addition since lengthAfterCursor can be set to e.g. Int.MAX
+    // by the input
+    val end = selectionEnd.addExactOrElse(lengthAfterCursor) { length }
+    delete(selectionEnd, minOf(end, length))
+
+    // calculate the start with safe subtraction since lengthBeforeCursor can be set to e.g.
+    // Int.MAX by the input
+    val start = selectionStart.subtractExactOrElse(lengthBeforeCursor) { 0 }
+    delete(maxOf(0, start), selectionStart)
+}
+
+/**
+ * A variant of [deleteSurroundingText]. The difference is that
+ * * The lengths are supplied in code points, not in chars.
+ * * This command does nothing if there are one or more invalid surrogate pairs
+ * in the requested range.
+ *
+ * See [`deleteSurroundingTextInCodePoints`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingTextInCodePoints(int,%2520int)).
+ *
+ * @param lengthBeforeCursor The number of characters in Unicode code points before the cursor to be
+ * deleted. Must be non-negative.
+ * @param lengthAfterCursor The number of characters in Unicode code points after the cursor to be
+ * deleted. Must be non-negative.
+ */
+internal fun EditingBuffer.deleteSurroundingTextInCodePoints(
+    lengthBeforeCursor: Int,
+    lengthAfterCursor: Int
+) {
+    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+        "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
+            "$lengthBeforeCursor and $lengthAfterCursor respectively."
+    }
+
+    // Convert code point length into character length. Then call the common logic of the
+    // DeleteSurroundingTextEditOp
+    var beforeLenInChars = 0
+    for (i in 0 until lengthBeforeCursor) {
+        beforeLenInChars++
+        if (selectionStart > beforeLenInChars) {
+            val lead = this[selectionStart - beforeLenInChars - 1]
+            val trail = this[selectionStart - beforeLenInChars]
+
+            if (isSurrogatePair(lead, trail)) {
+                beforeLenInChars++
+            }
+        }
+        if (beforeLenInChars == selectionStart) break
+    }
+
+    var afterLenInChars = 0
+    for (i in 0 until lengthAfterCursor) {
+        afterLenInChars++
+        if (selectionEnd + afterLenInChars < length) {
+            val lead = this[selectionEnd + afterLenInChars - 1]
+            val trail = this[selectionEnd + afterLenInChars]
+
+            if (isSurrogatePair(lead, trail)) {
+                afterLenInChars++
+            }
+        }
+        if (selectionEnd + afterLenInChars == length) break
+    }
+
+    delete(selectionEnd, selectionEnd + afterLenInChars)
+    delete(selectionStart - beforeLenInChars, selectionStart)
+}
+
+/**
+ * Finishes the composing text that is currently active. This simply leaves the text as-is,
+ * removing any special composing styling or other state that was around it. The cursor position
+ * remains unchanged.
+ *
+ * See [`finishComposingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#finishComposingText()).
+ */
+internal fun EditingBuffer.finishComposingText() {
+    commitComposition()
+}
+
+/**
+ * Represents a backspace operation at the cursor position.
+ *
+ * If there is composition, delete the text in the composition range.
+ * If there is no composition but there is selection, delete whole selected range.
+ * If there is no composition and selection, perform backspace key event at the cursor position.
+ */
+internal fun EditingBuffer.backspace() {
+    if (hasComposition()) {
+        delete(compositionStart, compositionEnd)
+    } else if (cursor == -1) {
+        val delStart = selectionStart
+        val delEnd = selectionEnd
+        cursor = selectionStart
+        delete(delStart, delEnd)
+    } else if (cursor != 0) {
+        val prevCursorPos = toString().findPrecedingBreak(cursor)
+        delete(prevCursorPos, cursor)
+    }
+}
+
+/**
+ * Moves the cursor with [amount] characters.
+ *
+ * If there is selection, cancel the selection first and move the cursor to the selection start
+ * position. Then perform the cursor movement.
+ *
+ * @param amount The amount of cursor movement. If you want to move backward, pass negative value.
+ */
+internal fun EditingBuffer.moveCursor(amount: Int) {
+    if (cursor == -1) {
+        cursor = selectionStart
+    }
+
+    var newCursor = selectionStart
+    val bufferText = toString()
+    if (amount > 0) {
+        for (i in 0 until amount) {
+            val next = bufferText.findFollowingBreak(newCursor)
+            if (next == -1) break
+            newCursor = next
+        }
+    } else {
+        for (i in 0 until -amount) {
+            val prev = bufferText.findPrecedingBreak(newCursor)
+            if (prev == -1) break
+            newCursor = prev
+        }
+    }
+
+    cursor = newCursor
+}
+
+/**
+ * Deletes all the text in the buffer.
+ */
+internal fun EditingBuffer.deleteAll() {
+    replace(0, length, "")
+}
+
+/**
+ * Helper function that returns true when [high] is a Unicode high-surrogate code unit and [low]
+ * is a Unicode low-surrogate code unit.
+ */
+private fun isSurrogatePair(high: Char, low: Char): Boolean =
+    high.isHighSurrogate() && low.isLowSurrogate()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
new file mode 100644
index 0000000..c3f6e46
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
@@ -0,0 +1,382 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+
+/**
+ * The editing buffer
+ *
+ * This class manages the all editing relate states, editing buffers, selection, styles, etc.
+ */
+internal class EditingBuffer(
+    /**
+     * The initial text of this editing buffer
+     */
+    text: AnnotatedString,
+    /**
+     * The initial selection range of this buffer.
+     * If you provide collapsed selection, it is treated as the cursor position. The cursor and
+     * selection cannot exists at the same time.
+     * The selection must points the valid index of the initialText, otherwise
+     * IndexOutOfBoundsException will be thrown.
+     */
+    selection: TextRange
+) {
+    internal companion object {
+        const val NOWHERE = -1
+    }
+
+    private val gapBuffer = PartialGapBuffer(text.text)
+
+    val changeTracker = ChangeTracker()
+
+    /**
+     * The inclusive selection start offset
+     */
+    var selectionStart = selection.start
+        private set(value) {
+            require(value >= 0) { "Cannot set selectionStart to a negative value: $value" }
+            field = value
+        }
+
+    /**
+     * The exclusive selection end offset
+     */
+    var selectionEnd = selection.end
+        private set(value) {
+            require(value >= 0) { "Cannot set selectionEnd to a negative value: $value" }
+            field = value
+        }
+
+    /**
+     * The inclusive composition start offset
+     *
+     * If there is no composing text, returns -1
+     */
+    var compositionStart = NOWHERE
+        private set
+
+    /**
+     * The exclusive composition end offset
+     *
+     * If there is no composing text, returns -1
+     */
+    var compositionEnd = NOWHERE
+        private set
+
+    /**
+     * Helper function that returns true if the editing buffer has composition text
+     */
+    fun hasComposition(): Boolean = compositionStart != NOWHERE
+
+    /**
+     * Returns the composition information as TextRange. Returns null if no
+     * composition is set.
+     */
+    val composition: TextRange?
+        get() = if (hasComposition()) {
+            TextRange(compositionStart, compositionEnd)
+        } else null
+
+    /**
+     * Returns the selection information as TextRange
+     */
+    val selection: TextRange
+        get() = TextRange(selectionStart, selectionEnd)
+
+    /**
+     * Helper accessor for cursor offset
+     */
+    /*VisibleForTesting*/
+    var cursor: Int
+        /**
+         * Return the cursor offset.
+         *
+         * Since selection and cursor cannot exist at the same time, return -1 if there is a
+         * selection.
+         */
+        get() = if (selectionStart == selectionEnd) selectionEnd else -1
+        /**
+         * Set the cursor offset.
+         *
+         * Since selection and cursor cannot exist at the same time, cancel selection if there is.
+         */
+        set(cursor) = setSelection(cursor, cursor)
+
+    /**
+     * [] operator for the character at the index.
+     */
+    operator fun get(index: Int): Char = gapBuffer[index]
+
+    /**
+     * Returns the length of the buffer.
+     */
+    val length: Int get() = gapBuffer.length
+
+    constructor(
+        text: String,
+        selection: TextRange
+    ) : this(AnnotatedString(text), selection)
+
+    init {
+        checkRange(selection.start, selection.end)
+    }
+
+    /**
+     * Replace the text and move the cursor to the end of inserted text.
+     *
+     * This function cancels selection if there is any.
+     *
+     * @throws IndexOutOfBoundsException if start or end offset is outside of current buffer
+     */
+    fun replace(start: Int, end: Int, text: CharSequence) {
+        checkRange(start, end)
+        val min = minOf(start, end)
+        val max = maxOf(start, end)
+
+        // coerce the replacement bounds before tracking change. This is mostly necessary for
+        // composition based typing when each keystroke may trigger a replace function that looks
+        // like "abcd" => "abcde".
+
+        // coerce min
+        var i = 0
+        var cMin = min
+        while (cMin < max && i < text.length && text[i] == gapBuffer[cMin]) {
+            i++
+            cMin++
+        }
+        // coerce max
+        var j = text.length
+        var cMax = max
+        while (cMax > min && j > i && text[j - 1] == gapBuffer[cMax - 1]) {
+            j--
+            cMax--
+        }
+
+        changeTracker.trackChange(cMin, cMax, j - i)
+
+        gapBuffer.replace(min, max, text)
+
+        // On Android, all text modification APIs also provides explicit cursor location. On the
+        // other hand, desktop applications usually don't. So, here tentatively move the cursor to
+        // the end offset of the editing area for desktop like application. In case of Android,
+        // implementation will call setSelection immediately after replace function to update this
+        // tentative cursor location.
+        selectionStart = min + text.length
+        selectionEnd = min + text.length
+
+        // Similarly, if text modification happens, cancel ongoing composition. If caller wants to
+        // change the composition text, it is caller's responsibility to call setComposition again
+        // to set composition range after replace function.
+        compositionStart = NOWHERE
+        compositionEnd = NOWHERE
+    }
+
+    /**
+     * Remove the given range of text.
+     *
+     * Different from replace method, this doesn't move cursor location to the end of modified text.
+     * Instead, preserve the selection with adjusting the deleted text.
+     */
+    fun delete(start: Int, end: Int) {
+        checkRange(start, end)
+        val deleteRange = TextRange(start, end)
+
+        changeTracker.trackChange(start, end, 0)
+
+        gapBuffer.replace(deleteRange.min, deleteRange.max, "")
+
+        val newSelection = updateRangeAfterDelete(
+            TextRange(selectionStart, selectionEnd),
+            deleteRange
+        )
+        selectionStart = newSelection.start
+        selectionEnd = newSelection.end
+
+        if (hasComposition()) {
+            val compositionRange = TextRange(compositionStart, compositionEnd)
+            val newComposition = updateRangeAfterDelete(compositionRange, deleteRange)
+            if (newComposition.collapsed) {
+                commitComposition()
+            } else {
+                compositionStart = newComposition.min
+                compositionEnd = newComposition.max
+            }
+        }
+    }
+
+    /**
+     * Mark the specified area of the text as selected text.
+     *
+     * You can set cursor by specifying the same value to `start` and `end`.
+     * The reversed range is not allowed.
+     * @param start the inclusive start offset of the selection
+     * @param end the exclusive end offset of the selection
+     */
+    fun setSelection(start: Int, end: Int) {
+        val clampedStart = start.coerceIn(0, length)
+        val clampedEnd = end.coerceIn(0, length)
+
+        selectionStart = clampedStart
+        selectionEnd = clampedEnd
+    }
+
+    /**
+     * Mark the specified area of the text as composition text.
+     *
+     * The empty range or reversed range is not allowed.
+     * Use clearComposition in case of clearing composition.
+     *
+     * @param start the inclusive start offset of the composition
+     * @param end the exclusive end offset of the composition
+     *
+     * @throws IndexOutOfBoundsException if start or end offset is ouside of current buffer
+     * @throws IllegalArgumentException if start is larger than or equal to end. (reversed or
+     *                                  collapsed range)
+     */
+    fun setComposition(start: Int, end: Int) {
+        if (start < 0 || start > gapBuffer.length) {
+            throw IndexOutOfBoundsException(
+                "start ($start) offset is outside of text region ${gapBuffer.length}"
+            )
+        }
+        if (end < 0 || end > gapBuffer.length) {
+            throw IndexOutOfBoundsException(
+                "end ($end) offset is outside of text region ${gapBuffer.length}"
+            )
+        }
+        if (start >= end) {
+            throw IllegalArgumentException("Do not set reversed or empty range: $start > $end")
+        }
+
+        compositionStart = start
+        compositionEnd = end
+    }
+
+    /**
+     * Commits the ongoing composition text and reset the composition range.
+     */
+    fun commitComposition() {
+        compositionStart = NOWHERE
+        compositionEnd = NOWHERE
+    }
+
+    override fun toString(): String = gapBuffer.toString()
+
+    fun toAnnotatedString(): AnnotatedString = AnnotatedString(toString())
+
+    private fun checkRange(start: Int, end: Int) {
+        if (start < 0 || start > gapBuffer.length) {
+            throw IndexOutOfBoundsException(
+                "start ($start) offset is outside of text region ${gapBuffer.length}"
+            )
+        }
+
+        if (end < 0 || end > gapBuffer.length) {
+            throw IndexOutOfBoundsException(
+                "end ($end) offset is outside of text region ${gapBuffer.length}"
+            )
+        }
+    }
+}
+
+/**
+ * Returns the updated TextRange for [target] after the [deleted] TextRange is deleted as a Pair.
+ *
+ * If the [deleted] Range covers the whole target, Pair(-1,-1) is returned.
+ */
+/*@VisibleForTesting*/
+internal fun updateRangeAfterDelete(target: TextRange, deleted: TextRange): TextRange {
+    var targetMin = target.min
+    var targetMax = target.max
+
+    // Following figure shows the deletion range and composition range.
+    // |---| represents deleted range.
+    // |===| represents target range.
+    if (deleted.intersects(target)) {
+        if (deleted.contains(target)) {
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :      |-------------|
+            //   Target     :          |======|
+            //
+            // Result:
+            //   Buffer     : ABCDETUVWXYZ
+            //   Target     :
+            targetMin = deleted.min
+            targetMax = targetMin
+        } else if (target.contains(deleted)) {
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :          |------|
+            //   Target     :        |==========|
+            //
+            // Result:
+            //   Buffer     : ABCDEFGHIQRSTUVWXYZ
+            //   Target     :        |===|
+            targetMax -= deleted.length
+        } else if (deleted.contains(targetMin)) {
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :      |---------|
+            //   Target     :            |========|
+            //
+            // Result:
+            //   Buffer     : ABCDEFPQRSTUVWXYZ
+            //   Target     :       |=====|
+            targetMin = deleted.min
+            targetMax -= deleted.length
+        } else { // deleteRange contains myMax
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :         |---------|
+            //   Target     :    |=======|
+            //
+            // Result:
+            //   Buffer     : ABCDEFGHSTUVWXYZ
+            //   Target     :    |====|
+            targetMax = deleted.min
+        }
+    } else {
+        if (targetMax <= deleted.min) {
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :            |-------|
+            //   Target     :  |=======|
+            //
+            // Result:
+            //   Buffer     : ABCDEFGHIJKLTUVWXYZ
+            //   Target     :  |=======|
+            // do nothing
+        } else {
+            // Input:
+            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
+            //   Deleted    :  |-------|
+            //   Target     :            |=======|
+            //
+            // Result:
+            //   Buffer     : AJKLMNOPQRSTUVWXYZ
+            //   Target     :    |=======|
+            targetMin -= deleted.length
+            targetMax -= deleted.length
+        }
+    }
+
+    return TextRange(targetMin, targetMax)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
new file mode 100644
index 0000000..6f82e52
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+/**
+ * The gap buffer implementation
+ *
+ * @param initBuffer An initial buffer. This class takes ownership of this object, so do not modify
+ *                   array after passing to this constructor
+ * @param initGapStart An initial inclusive gap start offset of the buffer
+ * @param initGapEnd An initial exclusive gap end offset of the buffer
+ */
+private class GapBuffer(initBuffer: CharArray, initGapStart: Int, initGapEnd: Int) {
+
+    /**
+     * The current capacity of the buffer
+     */
+    private var capacity = initBuffer.size
+
+    /**
+     * The buffer
+     */
+    private var buffer = initBuffer
+
+    /**
+     * The inclusive start offset of the gap
+     */
+    private var gapStart = initGapStart
+
+    /**
+     * The exclusive end offset of the gap
+     */
+    private var gapEnd = initGapEnd
+
+    /**
+     * The length of the gap.
+     */
+    private fun gapLength(): Int = gapEnd - gapStart
+
+    /**
+     * [] operator for the character at the index.
+     */
+    operator fun get(index: Int): Char {
+        if (index < gapStart) {
+            return buffer[index]
+        } else {
+            return buffer[index - gapStart + gapEnd]
+        }
+    }
+
+    /**
+     * Check if the gap has a requested size, and allocate new buffer if there is enough space.
+     */
+    private fun makeSureAvailableSpace(requestSize: Int) {
+        if (requestSize <= gapLength()) {
+            return
+        }
+
+        // Allocating necessary memory space by doubling the array size.
+        val necessarySpace = requestSize - gapLength()
+        var newCapacity = capacity * 2
+        while ((newCapacity - capacity) < necessarySpace) {
+            newCapacity *= 2
+        }
+
+        val newBuffer = CharArray(newCapacity)
+        buffer.copyInto(newBuffer, 0, 0, gapStart)
+        val tailLength = capacity - gapEnd
+        val newEnd = newCapacity - tailLength
+        buffer.copyInto(newBuffer, newEnd, gapEnd, gapEnd + tailLength)
+
+        buffer = newBuffer
+        capacity = newCapacity
+        gapEnd = newEnd
+    }
+
+    /**
+     * Delete the given range of the text.
+     */
+    private fun delete(start: Int, end: Int) {
+        if (start < gapStart && end <= gapStart) {
+            // The remove happens in the head buffer. Copy the tail part of the head buffer to the
+            // tail buffer.
+            //
+            // Example:
+            // Input:
+            //   buffer:     ABCDEFGHIJKLMNOPQ*************RSTUVWXYZ
+            //   del region:     |-----|
+            //
+            // First, move the remaining part of the head buffer to the tail buffer.
+            //   buffer:     ABCDEFGHIJKLMNOPQ*****KLKMNOPQRSTUVWXYZ
+            //   move data:            ^^^^^^^ =>  ^^^^^^^^
+            //
+            // Then, delete the given range. (just updating gap positions)
+            //   buffer:     ABCD******************KLKMNOPQRSTUVWXYZ
+            //   del region:     |-----|
+            //
+            // Output:       ABCD******************KLKMNOPQRSTUVWXYZ
+            val copyLen = gapStart - end
+            buffer.copyInto(buffer, gapEnd - copyLen, end, gapStart)
+            gapStart = start
+            gapEnd -= copyLen
+        } else if (start < gapStart && end >= gapStart) {
+            // The remove happens with accrossing the gap region. Just update the gap position
+            //
+            // Example:
+            // Input:
+            //   buffer:     ABCDEFGHIJKLMNOPQ************RSTUVWXYZ
+            //   del region:             |-------------------|
+            //
+            // Output:       ABCDEFGHIJKL********************UVWXYZ
+            gapEnd = end + gapLength()
+            gapStart = start
+        } else { // start > gapStart && end > gapStart
+            // The remove happens in the tail buffer. Copy the head part of the tail buffer to the
+            // head buffer.
+            //
+            // Example:
+            // Input:
+            //   buffer:     ABCDEFGHIJKL************MNOPQRSTUVWXYZ
+            //   del region:                            |-----|
+            //
+            // First, move the remaining part in the tail buffer to the head buffer.
+            //   buffer:     ABCDEFGHIJKLMNO*********MNOPQRSTUVWXYZ
+            //   move dat:               ^^^    <=   ^^^
+            //
+            // Then, delete the given range. (just updating gap positions)
+            //   buffer:     ABCDEFGHIJKLMNO******************VWXYZ
+            //   del region:                            |-----|
+            //
+            // Output:       ABCDEFGHIJKLMNO******************VWXYZ
+            val startInBuffer = start + gapLength()
+            val endInBuffer = end + gapLength()
+            val copyLen = startInBuffer - gapEnd
+            buffer.copyInto(buffer, gapStart, gapEnd, startInBuffer)
+            gapStart += copyLen
+            gapEnd = endInBuffer
+        }
+    }
+
+    /**
+     * Replace a region of this buffer with given text.
+     *
+     * @param start The index of the first character in this buffer to replace.
+     * @param end The index after the last character in this buffer to replace.
+     * @param text The new text to insert into the buffer.
+     * @param textStart The index of the first character in [text] to copy.
+     * @param textEnd The index after the last character in [text] to copy.
+     */
+    fun replace(
+        start: Int,
+        end: Int,
+        text: CharSequence,
+        textStart: Int = 0,
+        textEnd: Int = text.length
+    ) {
+        val textLength = textEnd - textStart
+        makeSureAvailableSpace(textLength - (end - start))
+
+        delete(start, end)
+
+        text.toCharArray(buffer, gapStart, textStart, textEnd)
+        gapStart += textLength
+    }
+
+    /**
+     * Write the current text into outBuf.
+     * @param builder The output string builder
+     */
+    fun append(builder: StringBuilder) {
+        builder.append(buffer, 0, gapStart)
+        builder.append(buffer, gapEnd, capacity - gapEnd)
+    }
+
+    /**
+     * The lengh of this gap buffer.
+     *
+     * This doesn't include internal hidden gap length.
+     */
+    fun length() = capacity - gapLength()
+
+    override fun toString(): String = StringBuilder().apply { append(this) }.toString()
+}
+
+/**
+ * An editing buffer that uses Gap Buffer only around the cursor location.
+ *
+ * Different from the original gap buffer, this gap buffer doesn't convert all given text into
+ * mutable buffer. Instead, this gap buffer converts cursor around text into mutable gap buffer
+ * for saving construction time and memory space. If text modification outside of the gap buffer
+ * is requested, this class flush the buffer and create new String, then start new gap buffer.
+ *
+ * @param text The initial text
+ */
+internal class PartialGapBuffer(text: CharSequence) : CharSequence {
+    internal companion object {
+        const val BUF_SIZE = 255
+        const val SURROUNDING_SIZE = 64
+        const val NOWHERE = -1
+    }
+
+    private var text: CharSequence = text
+    private var buffer: GapBuffer? = null
+    private var bufStart = NOWHERE
+    private var bufEnd = NOWHERE
+
+    /**
+     * The text length
+     */
+    override val length: Int
+        get() {
+            val buffer = buffer ?: return text.length
+            return text.length - (bufEnd - bufStart) + buffer.length()
+        }
+
+    /**
+     * Replace a region of this buffer with given text.
+     *
+     * @param start The index of the first character in this buffer to replace.
+     * @param end The index after the last character in this buffer to replace.
+     * @param text The new text to insert into the buffer.
+     * @param textStart The index of the first character in [text] to copy.
+     * @param textEnd The index after the last character in [text] to copy.
+     */
+    fun replace(
+        start: Int,
+        end: Int,
+        text: CharSequence,
+        textStart: Int = 0,
+        textEnd: Int = text.length
+    ) {
+        require(start <= end) { "start=$start > end=$end" }
+        require(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
+        require(start >= 0) { "start must be non-negative, but was $start" }
+        require(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
+
+        val buffer = buffer
+        val textLength = textEnd - textStart
+        if (buffer == null) { // First time to create gap buffer
+            val charArray = CharArray(maxOf(BUF_SIZE, textLength + 2 * SURROUNDING_SIZE))
+
+            // Convert surrounding text into buffer.
+            val leftCopyCount = minOf(start, SURROUNDING_SIZE)
+            val rightCopyCount = minOf(this.text.length - end, SURROUNDING_SIZE)
+
+            // Copy left surrounding
+            this.text.toCharArray(charArray, 0, start - leftCopyCount, start)
+
+            // Copy right surrounding
+            this.text.toCharArray(
+                charArray,
+                charArray.size - rightCopyCount,
+                end,
+                end + rightCopyCount
+            )
+
+            // Copy given text into buffer
+            text.toCharArray(charArray, leftCopyCount, textStart, textEnd)
+
+            this.buffer = GapBuffer(
+                charArray,
+                initGapStart = leftCopyCount + textLength,
+                initGapEnd = charArray.size - rightCopyCount
+            )
+            bufStart = start - leftCopyCount
+            bufEnd = end + rightCopyCount
+            return
+        }
+
+        // Convert user space offset into buffer space offset
+        val bufferStart = start - bufStart
+        val bufferEnd = end - bufStart
+        if (bufferStart < 0 || bufferEnd > buffer.length()) {
+            // Text modification outside of gap buffer has requested. Flush the buffer and try it
+            // again.
+            this.text = toString()
+            this.buffer = null
+            bufStart = NOWHERE
+            bufEnd = NOWHERE
+            return replace(start, end, text, textStart, textEnd)
+        }
+
+        buffer.replace(bufferStart, bufferEnd, text, textStart, textEnd)
+    }
+
+    /**
+     * [] operator for the character at the index.
+     */
+    override operator fun get(index: Int): Char {
+        val buffer = buffer ?: return text[index]
+        if (index < bufStart) {
+            return text[index]
+        }
+        val gapBufLength = buffer.length()
+        if (index < gapBufLength + bufStart) {
+            return buffer[index - bufStart]
+        }
+        return text[index - (gapBufLength - bufEnd + bufStart)]
+    }
+
+    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
+        toString().subSequence(startIndex, endIndex)
+
+    override fun toString(): String {
+        val b = buffer ?: return text.toString()
+        val sb = StringBuilder()
+        sb.append(text, 0, bufStart)
+        b.append(sb)
+        sb.append(text, bufEnd, text.length)
+        return sb.toString()
+    }
+
+    /**
+     * Compares the contents of this buffer with the contents of [other].
+     */
+    fun contentEquals(other: CharSequence): Boolean {
+        return toString() == other.toString()
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyAdaptingPlatformTextInputModifierNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyAdaptingPlatformTextInputModifierNode.kt
index 000655e..57c3a12 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyAdaptingPlatformTextInputModifierNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyAdaptingPlatformTextInputModifierNode.kt
@@ -108,9 +108,7 @@
     ): Job? {
         if (!isAttached) return null
         return coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
-            establishTextInputSession {
-                block()
-            }
+            establishTextInputSession(block)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/MathUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/MathUtils.kt
new file mode 100644
index 0000000..d8e2f98
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/MathUtils.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.text.selection.containsInclusive
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+
+/**
+ * Adds [this] and [right], and if an overflow occurs returns result of [defaultValue].
+ */
+internal inline fun Int.addExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this + right
+    // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+    return if (this xor result and (right xor result) < 0) defaultValue() else result
+}
+
+/**
+ * Subtracts [right] from [this], and if an overflow occurs returns result of [defaultValue].
+ */
+internal inline fun Int.subtractExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this - right
+    // HD 2-12 Overflow iff the arguments have different signs and
+    // the sign of the result is different from the sign of x
+    return if (this xor right and (this xor result) < 0) defaultValue() else result
+}
+
+/**
+ * Returns -1 if this [Offset] is closer to [rect1], 1 if it's closer to [rect2], or 0 if it's
+ * equidistant to both. If the point is inside either rectangle, the distance is calculated as zero.
+ */
+internal fun Offset.findClosestRect(rect1: Rect, rect2: Rect): Int {
+    val comparativeDistTo1 = distanceSquaredToClosestCornerFromOutside(rect1)
+    val comparativeDistTo2 = distanceSquaredToClosestCornerFromOutside(rect2)
+    if (comparativeDistTo1 == comparativeDistTo2) return 0
+    return if (comparativeDistTo1 < comparativeDistTo2) -1 else 1
+}
+
+/**
+ * Calculates the distance from this [Offset] to the nearest point on [rect].
+ * Returns 0 if the offset is within [rect].
+ */
+private fun Offset.distanceSquaredToClosestCornerFromOutside(rect: Rect): Float {
+    if (rect.containsInclusive(this)) return 0f
+    var distance = Float.MAX_VALUE
+    (rect.topLeft - this).getDistanceSquared().let { if (it < distance) distance = it }
+    (rect.topRight - this).getDistanceSquared().let { if (it < distance) distance = it }
+    (rect.bottomLeft - this).getDistanceSquared().let { if (it < distance) distance = it }
+    (rect.bottomRight - this).getDistanceSquared().let { if (it < distance) distance = it }
+    return distance
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
new file mode 100644
index 0000000..5d55fc5
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
@@ -0,0 +1,415 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Builds up bidirectional mapping functions to map offsets from an original string to corresponding
+ * in a string that has some edit operations applied.
+ *
+ * Edit operations must be reported via [recordEditOperation]. Offsets can be mapped
+ * [from the source string][mapFromSource] and [back to the source][mapFromDest]. Mapping between
+ * source and transformed offsets is a symmetric operation – given a sequence of edit operations,
+ * the result of mapping each offset from the source to transformed strings will be the same as
+ * mapping backwards on the inverse sequence of edit operations.
+ *
+ * By default, offsets map one-to-one. However, around an edit operation, some alternative rules
+ * apply. In general, offsets are mapped one-to-one where they can be unambiguously. When there
+ * isn't enough information, the mapping is ambiguous, and the mapping result will be a [TextRange]
+ * instead of a single value, where the range represents all the possible offsets that it could map
+ * to.
+ *
+ * _Note: In the following discussion, `I` refers to the start offset in the source text, `N` refers
+ * to the length of the range in the source text, and `N'` refers to the length of the replacement
+ * text. So given `"abc"` and replacing `"bc"` with `"xyz"`, `I=1`, `N=2`, and `N'=3`._
+ *
+ * ### Insertions
+ * When text is inserted (i.e. zero-length range is replaced with non-zero-length range), the
+ * mapping is ambiguous because all of the offsets in the inserted text map back to the same offset
+ * in the source text - the offset where the text was inserted. That means the insertion point can
+ * map to any of the offsets in the inserted text. I.e. I -> I..N'
+ *
+ * - This is slightly different than the replacement case, because the offset of the start of
+ *   the operation and the offset of the end of the operation (which are the same) map to a
+ *   range instead of a scalar. This is because there is not enough information to map the start
+ *   and end offsets 1-to-1 to offsets in the transformed text.
+ * - This is symmetric with deletion: Mapping backward from an insertion is the same as mapping
+ *   forward from a deletion.
+ *
+ * ### Deletions
+ * In the inverse case, when text is deleted, mapping is unambiguous. All the offsets in the
+ * deleted range map to the start of the deleted range. I.e. I..N -> I
+ *
+ * - This is symmetric with insertion: Mapping backward from a deletion is the same as mapping
+ *   forward from an insertion.
+ *
+ * ### Replacements
+ * When text is replaced, there are both ambiguous and unambiguous mappings:
+ * - The offsets at the _ends_ of the replaced range map unambiguously to the offsets at the
+ *   corresponding edges of the replaced text. I -> I and I+1 -> I+N
+ * - The offsets _inside_ the replaced range (exclusive) map ambiguously to the entire replaced
+ *   range. I+1..I+N-1 -> I+1..I+N'-1
+ * - Note that this means that when a string with length >1 is replaced by a single character,
+ *   all the offsets inside that string will map not to the index of the replacement character
+ *   but to a single-char _range_ containing that character.
+ *
+ * ### Examples
+ *
+ * #### Inserting text
+ * ```
+ *     012
+ * A: "ab"
+ *     | \
+ *     |  \
+ * B: "azzzb"
+ *     012345
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 |
+ * |--------:|:-:|:-:|:-:|
+ * |   to B: | 0 |1-4| 5 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 1 | 1 | 2 |
+ *
+ * #### Deleting text
+ * ```
+ *     012345
+ * A: "azzzb"
+ *     |  /
+ *     | /
+ * B: "ab"
+ *     012
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 | 1 | 1 | 1 | 2 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 |
+ * |--------:|:-:|:-:|:-:|
+ * |   to A: | 0 |1-4| 5 |
+ *
+ * #### Replacing text: single char with char
+ * ```
+ *     0123
+ * A: "abc"
+ *      |
+ *      |
+ * B: "azc"
+ *     0123
+ * ```
+ *
+ * Forward/reverse mapping: identity
+ *
+ * | from: | 0 | 1 | 2 | 3 |
+ * |------:|:-:|:-:|:-:|:-:|
+ * |   to: | 0 | 1 | 2 | 3 |
+ *
+ * #### Replacing text: char with chars
+ * ```
+ *     0123
+ * A: "abc"
+ *      |
+ *      |\
+ * B: "azzc"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 |
+ * |--------:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 | 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 2 | 3 |
+ *
+ * #### Replacing text: chars with chars
+ * ```
+ *     012345
+ * A: "abcde"
+ *      | |
+ *      | /
+ * B: "azze"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 |1-3|1-3| 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 |1-4| 4 | 5 |
+ *
+ * ### Multiple operations
+ *
+ * While the above apply to single edit operations, when multiple edit operations are recorded the
+ * same rules apply. The rules are applied to the first operation, then the result of that is
+ * effectively used as the input text for the next operation, etc. Because an offset can map to a
+ * range at each step, we track both a start and end offset (which start as the same value), and
+ * at each step combine the start and end _ranges_ by taking their union.
+ *
+ * #### Multiple char-to-char replacements (codepoint transformation):
+ * ```
+ *     0123
+ * A: "abc"
+ *     |
+ *    "•bc"
+ *      |
+ *    "••c"
+ *       |
+ * B: "•••"
+ *     0123
+ * ```
+ *
+ * Forward/reverse mapping: identity
+ *
+ * | from: | 0 | 1 | 2 | 3 |
+ * |------:|:-:|:-:|:-:|:-:|
+ * |   to: | 0 | 1 | 2 | 3 |
+ *
+ * #### Multiple inserts:
+ * ```
+ *     01234
+ * A: "abcd"
+ *      |
+ *    "a(bcd"
+ *         |
+ * B: "a(bc)d"
+ *     0123456
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 |1-2| 3 |4-5| 6 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 2 | 3 | 3 | 4 |
+ *
+ * #### Multiple replacements of the same range:
+ * ```
+ *     01234
+ * A: "abcd"
+ *      ||
+ *    "awxd"
+ *      ||
+ * B: "ayzd"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 |1-3| 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 |1-3| 3 | 4 |
+ *
+ * For other edge cases, including overlapping replacements, see `OffsetMappingCalculatorTest`.
+ */
+internal class OffsetMappingCalculator {
+    /** Resizable array of edit operations, size is defined by [opsSize]. */
+    private var ops = OpArray(size = 10)
+    private var opsSize = 0
+
+    /**
+     * Records an edit operation that replaces the range from [sourceStart] (inclusive) to
+     * [sourceEnd] (exclusive) in the original text with some text with length [newLength].
+     */
+    fun recordEditOperation(sourceStart: Int, sourceEnd: Int, newLength: Int) {
+        require(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
+        val sourceMin = minOf(sourceStart, sourceEnd)
+        val sourceMax = maxOf(sourceMin, sourceEnd)
+        val sourceLength = sourceMax - sourceMin
+
+        // 0,0 is a noop, and 1,1 is always a 1-to-1 mapping so we don't need to record it.
+        if (sourceLength < 2 && sourceLength == newLength) return
+
+        // Append the operation to the array, growing it if necessary.
+        val newSize = opsSize + 1
+        if (newSize > ops.size) {
+            val newCapacity = maxOf(newSize * 2, ops.size * 2)
+            ops = ops.copyOf(newCapacity)
+        }
+        ops.set(opsSize, sourceMin, sourceLength, newLength)
+        opsSize = newSize
+    }
+
+    /**
+     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
+     * in the transformed string.
+     */
+    fun mapFromSource(offset: Int): TextRange = map(offset, fromSource = true)
+
+    /**
+     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
+     * in the transformed string.
+     */
+    fun mapFromDest(offset: Int): TextRange = map(offset, fromSource = false)
+
+    private fun map(offset: Int, fromSource: Boolean): TextRange {
+        var start = offset
+        var end = offset
+
+        // This algorithm works for both forward and reverse mapping, we just need to iterate
+        // backwards to do reverse mapping.
+        ops.forEach(max = opsSize, reversed = !fromSource) { opOffset, opSrcLen, opDestLen ->
+            val newStart = mapStep(
+                offset = start,
+                opOffset = opOffset,
+                untransformedLen = opSrcLen,
+                transformedLen = opDestLen,
+                fromSource = fromSource
+            )
+            val newEnd = mapStep(
+                offset = end,
+                opOffset = opOffset,
+                untransformedLen = opSrcLen,
+                transformedLen = opDestLen,
+                fromSource = fromSource
+            )
+            // range = newStart ∪ newEnd
+            // Note we don't read TextRange.min/max here because the above code always returns
+            // normalized ranges. It's no less correct, but there's no need to do the additional
+            // min/max calls inside the min/max properties.
+            start = minOf(newStart.start, newEnd.start)
+            end = maxOf(newStart.end, newEnd.end)
+        }
+
+        return TextRange(start, end)
+    }
+
+    private fun mapStep(
+        offset: Int,
+        opOffset: Int,
+        untransformedLen: Int,
+        transformedLen: Int,
+        fromSource: Boolean
+    ): TextRange {
+        val srcLen = if (fromSource) untransformedLen else transformedLen
+        val destLen = if (fromSource) transformedLen else untransformedLen
+        return when {
+            // Before the operation, no change.
+            offset < opOffset -> TextRange(offset)
+
+            offset == opOffset -> {
+                if (srcLen == 0) {
+                    // On insertion point, map to inserted range.
+                    TextRange(opOffset, opOffset + destLen)
+                } else {
+                    // On start of replacement, map to start of replaced range.
+                    TextRange(opOffset)
+                }
+            }
+
+            offset < opOffset + srcLen -> {
+                if (destLen == 0) {
+                    // In deleted range, map to start of deleted range.
+                    TextRange(opOffset)
+                } else {
+                    // In replaced range, map to transformed range.
+                    TextRange(opOffset, opOffset + destLen)
+                }
+            }
+
+            // On end of or after replaced range, offset the offset.
+            else -> TextRange(offset - srcLen + destLen)
+        }
+    }
+}
+
+/**
+ * An array of 3-tuples of ints. Each element's values are stored as individual values in the
+ * underlying array.
+ */
[email protected]
+private value class OpArray private constructor(private val values: IntArray) {
+    constructor(size: Int) : this(IntArray(size * ElementSize))
+
+    val size: Int get() = values.size / ElementSize
+
+    fun set(index: Int, offset: Int, srcLen: Int, destLen: Int) {
+        values[index * ElementSize] = offset
+        values[index * ElementSize + 1] = srcLen
+        values[index * ElementSize + 2] = destLen
+    }
+
+    fun copyOf(newSize: Int) = OpArray(values.copyOf(newSize * ElementSize))
+
+    /**
+     * Loops through the array between 0 and [max] (exclusive). If [reversed] is false (the
+     * default), iterates forward from 0. When it's true, iterates backwards from `max-1`.
+     */
+    inline fun forEach(
+        max: Int,
+        reversed: Boolean = false,
+        block: (offset: Int, srcLen: Int, destLen: Int) -> Unit
+    ) {
+        if (max < 0) return
+        // Note: This stamps out block twice at the callsite, which is normally bad for an inline
+        // function to do. However, this is a file-private function which is only called in one
+        // place that would need to have two copies of mostly-identical code anyway. Doing the
+        // duplication here keeps the more complicated logic at the callsite more readable.
+        if (reversed) {
+            for (i in max - 1 downTo 0) {
+                val offset = values[i * ElementSize]
+                val srcLen = values[i * ElementSize + 1]
+                val destLen = values[i * ElementSize + 2]
+                block(offset, srcLen, destLen)
+            }
+        } else {
+            for (i in 0 until max) {
+                val offset = values[i * ElementSize]
+                val srcLen = values[i * ElementSize + 1]
+                val destLen = values[i * ElementSize + 2]
+                block(offset, srcLen, destLen)
+            }
+        }
+    }
+
+    private companion object {
+        const val ElementSize = 3
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt
new file mode 100644
index 0000000..e592ca7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.text.input.TextFieldValue
+
+/**
+ * Synchronizes between [TextFieldState], immutable values, and value change callbacks for
+ * [BasicTextField2] overloads that take a value+callback for state instead of taking a
+ * [TextFieldState] directly. Effectively a fancy `rememberUpdatedState`.
+ *
+ * Only intended for use from [BasicTextField2].
+ *
+ * @param writeSelectionFromTextFieldValue If true, [update] will synchronize the selection from the
+ * [TextFieldValue] to the [TextFieldState]. The text will be synchronized regardless.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun Modifier.syncTextFieldState(
+    state: TextFieldState,
+    value: TextFieldValue,
+    onValueChanged: (TextFieldValue) -> Unit,
+    writeSelectionFromTextFieldValue: Boolean,
+): Modifier = this.then(
+    StateSyncingModifier(
+        state = state,
+        value = value,
+        onValueChanged = onValueChanged,
+        writeSelectionFromTextFieldValue = writeSelectionFromTextFieldValue
+    )
+)
+
+@OptIn(ExperimentalFoundationApi::class)
+private class StateSyncingModifier(
+    private val state: TextFieldState,
+    private val value: TextFieldValue,
+    private val onValueChanged: (TextFieldValue) -> Unit,
+    private val writeSelectionFromTextFieldValue: Boolean,
+) : ModifierNodeElement<StateSyncingModifierNode>() {
+
+    override fun create(): StateSyncingModifierNode =
+        StateSyncingModifierNode(state, onValueChanged, writeSelectionFromTextFieldValue)
+
+    override fun update(node: StateSyncingModifierNode) {
+        node.update(value, onValueChanged)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        // Always call update, without comparing the text. Update can compare more efficiently.
+        return false
+    }
+
+    override fun hashCode(): Int {
+        // Avoid calculating hash from values that can change on every recomposition.
+        return state.hashCode()
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // no inspector properties
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class StateSyncingModifierNode(
+    private val state: TextFieldState,
+    private var onValueChanged: (TextFieldValue) -> Unit,
+    private val writeSelectionFromTextFieldValue: Boolean,
+) : Modifier.Node(), ObserverModifierNode, FocusEventModifierNode {
+
+    private var isFocused = false
+    private var lastValueWhileFocused: TextFieldValue? = null
+
+    override val shouldAutoInvalidate: Boolean
+        get() = false
+
+    /**
+     * Synchronizes the latest [value] to the [TextFieldState] and updates our [onValueChanged]
+     * callback. Should be called from [ModifierNodeElement.update].
+     */
+    fun update(value: TextFieldValue, onValueChanged: (TextFieldValue) -> Unit) {
+        this.onValueChanged = onValueChanged
+
+        // Don't modify the text programmatically while an edit session is in progress.
+        // WARNING: While editing, the code that holds the external state is temporarily not the
+        // actual source of truth. This "stealing" of control is generally an anti-pattern. We do it
+        // intentionally here because text field state is very sensitive to timing, and if a state
+        // update is delivered a frame late, it breaks text input. It is very easy to accidentally
+        // introduce small bits of asynchrony in real-world scenarios, e.g. with Flow-based reactive
+        // architectures. The benefit of avoiding that easy pitfall outweighs the weirdness in this
+        // case.
+        if (!isFocused) {
+            updateState(value)
+        } else {
+            this.lastValueWhileFocused = value
+        }
+    }
+
+    override fun onAttach() {
+        // Don't fire the callback on first frame.
+        observeTextState(fireOnValueChanged = false)
+    }
+
+    override fun onFocusEvent(focusState: FocusState) {
+        if (this.isFocused && !focusState.isFocused) {
+            // Lost focus, perform deferred synchronization.
+            lastValueWhileFocused?.let(::updateState)
+            lastValueWhileFocused = null
+        }
+        this.isFocused = focusState.isFocused
+    }
+
+    /** Called by the modifier system when the [TextFieldState] has changed. */
+    override fun onObservedReadsChanged() {
+        observeTextState()
+    }
+
+    private fun updateState(value: TextFieldValue) {
+        state.edit {
+            // Avoid registering a state change if the text isn't actually different.
+            setTextIfChanged(value.text)
+
+            // The BasicTextField2(String) variant can't push a selection value, so ignore it.
+            if (writeSelectionFromTextFieldValue) {
+                selectCharsIn(value.selection)
+            }
+        }
+    }
+
+    private fun observeTextState(fireOnValueChanged: Boolean = true) {
+        lateinit var text: TextFieldCharSequence
+        observeReads {
+            text = state.text
+        }
+
+        // This code is outside of the observeReads lambda so we don't observe any state reads the
+        // callback happens to do.
+        if (fireOnValueChanged) {
+            val newValue = TextFieldValue(
+                text = text.toString(),
+                selection = text.selectionInChars,
+                composition = text.compositionInChars
+            )
+            onValueChanged(newValue)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt
new file mode 100644
index 0000000..48d9bbd
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt
@@ -0,0 +1,554 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
+import androidx.compose.foundation.text.input.internal.selection.textFieldMagnifierNode
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.isUnspecified
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.invalidateMeasurement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextPainter
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.math.min
+import kotlin.math.truncate
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Modifier element for the core functionality of [BasicTextField2] that is passed as inner
+ * TextField to the decoration box. This is only half the actual modifiers for the field, the other
+ * half are only attached to the decorated text field.
+ *
+ * This modifier mostly handles layout and draw.
+ */
+internal data class TextFieldCoreModifier(
+    private val isFocused: Boolean, /* true iff component is focused and the window in focus */
+    private val isDragHovered: Boolean,
+    private val textLayoutState: TextLayoutState,
+    private val textFieldState: TransformedTextFieldState,
+    private val textFieldSelectionState: TextFieldSelectionState,
+    private val cursorBrush: Brush,
+    private val writeable: Boolean,
+    private val scrollState: ScrollState,
+    private val orientation: Orientation,
+) : ModifierNodeElement<TextFieldCoreModifierNode>() {
+
+    override fun create(): TextFieldCoreModifierNode = TextFieldCoreModifierNode(
+        isFocused = isFocused,
+        isDragHovered = isDragHovered,
+        textLayoutState = textLayoutState,
+        textFieldState = textFieldState,
+        textFieldSelectionState = textFieldSelectionState,
+        cursorBrush = cursorBrush,
+        writeable = writeable,
+        scrollState = scrollState,
+        orientation = orientation,
+    )
+
+    override fun update(node: TextFieldCoreModifierNode) {
+        node.updateNode(
+            isFocused = isFocused,
+            isDragHovered = isDragHovered,
+            textLayoutState = textLayoutState,
+            textFieldState = textFieldState,
+            textFieldSelectionState = textFieldSelectionState,
+            cursorBrush = cursorBrush,
+            writeable = writeable,
+            scrollState = scrollState,
+            orientation = orientation,
+        )
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // no inspector info
+    }
+}
+
+/** Modifier node for [TextFieldCoreModifier]. */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextFieldCoreModifierNode(
+    // true iff this component is focused and the window is focused
+    private var isFocused: Boolean,
+    private var isDragHovered: Boolean,
+    private var textLayoutState: TextLayoutState,
+    private var textFieldState: TransformedTextFieldState,
+    private var textFieldSelectionState: TextFieldSelectionState,
+    private var cursorBrush: Brush,
+    private var writeable: Boolean,
+    private var scrollState: ScrollState,
+    private var orientation: Orientation,
+) : DelegatingNode(),
+    LayoutModifierNode,
+    DrawModifierNode,
+    CompositionLocalConsumerModifierNode,
+    GlobalPositionAwareModifierNode,
+    SemanticsModifierNode {
+
+    /**
+     * Animatable object for cursor's alpha value. It becomes 1f for half a second and 0f for
+     * another half a second when TextField is focused and editable. Initial value should be 0f
+     * so that when cursor needs to be drawn for the first time, change to 1f invalidates draw.
+     */
+    private val cursorAnimation = CursorAnimationState()
+
+    /**
+     * Whether to show cursor at all when TextField has focus. This depends on enabled, read only,
+     * and brush at a given time.
+     */
+    private val showCursor: Boolean
+        get() = writeable && (isFocused || isDragHovered) && cursorBrush.isSpecified
+
+    /**
+     * Observes the [textFieldState] for any changes to content or selection. If a change happens,
+     * cursor blink animation gets reset.
+     */
+    private var changeObserverJob: Job? = null
+
+    /**
+     * When selection/cursor changes its position, it may go out of the visible area. When that
+     * happens, ideally we would want to scroll the TextField to keep the changing handle in the
+     * visible area. The following member variables keep track of the latest selection and cursor
+     * positions that we have adjusted for. When we detect a change to both of them during the
+     * layout phase, ScrollState gets adjusted.
+     */
+    private var previousSelection: TextRange? = null
+    private var previousCursorRect: Rect = Rect(-1f, -1f, -1f, -1f)
+
+    private val textFieldMagnifierNode = delegate(
+        textFieldMagnifierNode(
+            textFieldState = textFieldState,
+            textFieldSelectionState = textFieldSelectionState,
+            textLayoutState = textLayoutState,
+            visible = isFocused || isDragHovered
+        )
+    )
+
+    /**
+     * Updates all the related properties and invalidates internal state based on the changes.
+     */
+    fun updateNode(
+        isFocused: Boolean,
+        isDragHovered: Boolean,
+        textLayoutState: TextLayoutState,
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        cursorBrush: Brush,
+        writeable: Boolean,
+        scrollState: ScrollState,
+        orientation: Orientation,
+    ) {
+        val previousShowCursor = this.showCursor
+        val wasFocused = this.isFocused
+        val previousTextFieldState = this.textFieldState
+        val previousTextLayoutState = this.textLayoutState
+        val previousTextFieldSelectionState = this.textFieldSelectionState
+        val previousScrollState = this.scrollState
+
+        this.isFocused = isFocused
+        this.isDragHovered = isDragHovered
+        this.textLayoutState = textLayoutState
+        this.textFieldState = textFieldState
+        this.textFieldSelectionState = textFieldSelectionState
+        this.cursorBrush = cursorBrush
+        this.writeable = writeable
+        this.scrollState = scrollState
+        this.orientation = orientation
+
+        textFieldMagnifierNode.update(
+            textFieldState = textFieldState,
+            textFieldSelectionState = textFieldSelectionState,
+            textLayoutState = textLayoutState,
+            visible = isFocused || isDragHovered
+        )
+
+        if (!showCursor) {
+            changeObserverJob?.cancel()
+            changeObserverJob = null
+            cursorAnimation.cancelAndHide()
+        } else if (!wasFocused ||
+            previousTextFieldState != textFieldState ||
+            !previousShowCursor
+        ) {
+            // this node is writeable, focused and gained that focus just now.
+            // start the state value observation
+            changeObserverJob = coroutineScope.launch {
+                snapshotFlow {
+                    // Read the text state, so the animation restarts when the text or cursor
+                    // position change.
+                    textFieldState.visualText
+                    // Only animate the cursor when its window is actually focused. This also
+                    // disables the cursor animation when the screen is off.
+                    currentValueOf(LocalWindowInfo).isWindowFocused
+                }.collectLatest { isWindowFocused ->
+                    if (isWindowFocused) {
+                        cursorAnimation.snapToVisibleAndAnimate()
+                    }
+                }
+            }
+        }
+
+        if (previousTextFieldState != textFieldState ||
+            previousTextLayoutState != textLayoutState ||
+            previousTextFieldSelectionState != textFieldSelectionState ||
+            previousScrollState != scrollState) {
+            invalidateMeasurement()
+        }
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ) = if (orientation == Orientation.Vertical) {
+        measureVerticalScroll(measurable, constraints)
+    } else {
+        measureHorizontalScroll(measurable, constraints)
+    }
+
+    override fun ContentDrawScope.draw() {
+        drawContent()
+        val value = textFieldState.visualText
+        val textLayoutResult = textLayoutState.layoutResult ?: return
+
+        if (value.selectionInChars.collapsed) {
+            drawText(textLayoutResult)
+            drawCursor()
+        } else {
+            drawSelection(value.selectionInChars, textLayoutResult)
+            drawText(textLayoutResult)
+        }
+
+        with(textFieldMagnifierNode) { draw() }
+    }
+
+    private fun MeasureScope.measureVerticalScroll(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        // remove any height constraints for TextField since it'll be able to scroll vertically.
+        val childConstraints = constraints.copy(maxHeight = Constraints.Infinity)
+        val placeable = measurable.measure(childConstraints)
+        // final height is the minimum of calculated or constrained maximum height.
+        val height = min(placeable.height, constraints.maxHeight)
+
+        return layout(placeable.width, height) {
+            // we may need to update the scroll state to bring the cursor back into view after
+            // layout is completed.
+            val currSelection = textFieldState.visualText.selectionInChars
+            val offsetToFollow = calculateOffsetToFollow(currSelection)
+
+            updateScrollState(
+                offsetToFollow = offsetToFollow,
+                containerSize = height,
+                textFieldSize = placeable.height,
+                layoutDirection = layoutDirection
+            )
+
+            // only update the previous selection if this node is focused.
+            if (isFocused) {
+                previousSelection = currSelection
+            }
+
+            placeable.placeRelative(0, -scrollState.value)
+        }
+    }
+
+    private fun MeasureScope.measureHorizontalScroll(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        // remove any width constraints for TextField since it'll be able to scroll horizontally.
+        val placeable = measurable.measure(constraints.copy(maxWidth = Constraints.Infinity))
+        val width = min(placeable.width, constraints.maxWidth)
+
+        return layout(width, placeable.height) {
+            // we may need to update the scroll state to bring the cursor back into view before
+            // layout is updated.
+            val currSelection = textFieldState.visualText.selectionInChars
+            val offsetToFollow = calculateOffsetToFollow(currSelection)
+
+            updateScrollState(
+                offsetToFollow = offsetToFollow,
+                containerSize = width,
+                textFieldSize = placeable.width,
+                layoutDirection = layoutDirection
+            )
+
+            // only update the previous selection if this node is focused.
+            if (isFocused) {
+                previousSelection = currSelection
+            }
+
+            placeable.placeRelative(-scrollState.value, 0)
+        }
+    }
+
+    private fun calculateOffsetToFollow(currSelection: TextRange): Int {
+        return when {
+            currSelection.end != previousSelection?.end -> currSelection.end
+            currSelection.start != previousSelection?.start -> currSelection.start
+            else -> -1
+        }
+    }
+
+    /**
+     * Updates the scroll state to make sure cursor is visible after text content, selection, or
+     * layout changes. Only scroll changes won't trigger this.
+     *
+     * @param offsetToFollow The index of the character that needs to be followed and scrolled into
+     * view.
+     * @param containerSize Either height or width of scrollable host, depending on scroll
+     * orientation.
+     * @param textFieldSize Either height or width of scrollable text field content, depending on
+     * scroll orientation.
+     */
+    private fun Density.updateScrollState(
+        offsetToFollow: Int,
+        containerSize: Int,
+        textFieldSize: Int,
+        layoutDirection: LayoutDirection
+    ) {
+        val layoutResult = textLayoutState.layoutResult ?: return
+        val rawCursorRect = layoutResult.getCursorRect(
+            offsetToFollow.coerceIn(0..layoutResult.layoutInput.text.length)
+        )
+
+        val cursorRect = if (offsetToFollow >= 0) {
+            getCursorRectInScroller(
+                cursorRect = rawCursorRect,
+                rtl = layoutDirection == LayoutDirection.Rtl,
+                textFieldWidth = textFieldSize
+            )
+        } else {
+            null
+        }
+
+        // update the maximum scroll value
+        val difference = textFieldSize - containerSize
+        scrollState.maxValue = difference
+
+        // if the cursor is not showing, we don't have to update the scroll state for the cursor
+        // if there is no rect area to bring into view, we can early return.
+        if (!showCursor || cursorRect == null) return
+
+        // Check if cursor has actually changed its location
+        if (cursorRect.left != previousCursorRect.left ||
+            cursorRect.top != previousCursorRect.top) {
+            val vertical = orientation == Orientation.Vertical
+            val cursorStart = if (vertical) cursorRect.top else cursorRect.left
+            val cursorEnd = if (vertical) cursorRect.bottom else cursorRect.right
+
+            val startVisibleBound = scrollState.value
+            val endVisibleBound = startVisibleBound + containerSize
+            val offsetDifference = when {
+                // make bottom/end of the cursor visible
+                //
+                // text box
+                // +----------------------+
+                // |                      |
+                // |                      |
+                // |          cursor      |
+                // |             |        |
+                // +-------------|--------+
+                //               |
+                //
+                cursorEnd > endVisibleBound -> cursorEnd - endVisibleBound
+
+                // in rare cases when there's not enough space to fit the whole cursor, prioritise
+                // the bottom/end of the cursor
+                //
+                //             cursor
+                // text box      |
+                // +-------------|--------+
+                // |             |        |
+                // +-------------|--------+
+                //               |
+                //
+                cursorStart < startVisibleBound && cursorEnd - cursorStart > containerSize ->
+                    cursorEnd - endVisibleBound
+
+                // make top/start of the cursor visible if there's enough space to fit the whole
+                // cursor
+                //
+                //               cursor
+                // text box       |
+                // +--------------|-------+
+                // |              |       |
+                // |                      |
+                // |                      |
+                // |                      |
+                // +----------------------+
+                //
+                cursorStart < startVisibleBound && cursorEnd - cursorStart <= containerSize ->
+                    cursorStart - startVisibleBound
+
+                // otherwise keep current offset
+                else -> 0f
+            }
+            previousCursorRect = cursorRect
+            // this call will respect the earlier set maxValue
+            // no need to coerce again.
+            // prefer to use immediate dispatch instead of suspending scroll calls
+            coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+                scrollState.scrollBy(offsetDifference.roundToNext())
+                // make sure to use the cursor rect from text layout since bringIntoView does its
+                // own checks for RTL layouts.
+                textLayoutState.bringIntoViewRequester.bringIntoView(rawCursorRect)
+            }
+        }
+    }
+
+    /**
+     * Draws the selection highlight.
+     */
+    private fun DrawScope.drawSelection(
+        selection: TextRange,
+        textLayoutResult: TextLayoutResult
+    ) {
+        val start = selection.min
+        val end = selection.max
+        if (start != end) {
+            val selectionBackgroundColor = currentValueOf(LocalTextSelectionColors)
+                .backgroundColor
+            val selectionPath = textLayoutResult.getPathForRange(start, end)
+            drawPath(selectionPath, color = selectionBackgroundColor)
+        }
+    }
+
+    /**
+     * Draws the text content.
+     */
+    private fun DrawScope.drawText(textLayoutResult: TextLayoutResult) {
+        drawIntoCanvas { canvas ->
+            TextPainter.paint(canvas, textLayoutResult)
+        }
+    }
+
+    /**
+     * Draws the cursor indicator. Do not confuse it with cursor handle which is a popup that
+     * carries the cursor movement gestures.
+     */
+    private fun DrawScope.drawCursor() {
+        // Only draw cursor if it can be shown and its alpha is higher than 0f
+        // Alpha is checked before showCursor purposefully to make sure that we read
+        // cursorAlpha in draw phase. So, when the alpha value changes, draw phase invalidates.
+        val cursorAlphaValue = cursorAnimation.cursorAlpha
+        if (cursorAlphaValue == 0f || !showCursor) return
+
+        val cursorRect = textFieldSelectionState.cursorRect
+
+        drawLine(
+            cursorBrush,
+            cursorRect.topCenter,
+            cursorRect.bottomCenter,
+            alpha = cursorAlphaValue,
+            strokeWidth = cursorRect.width
+        )
+    }
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        this.textLayoutState.coreNodeCoordinates = coordinates
+        textFieldMagnifierNode.onGloballyPositioned(coordinates)
+    }
+
+    override fun SemanticsPropertyReceiver.applySemantics() {
+        with(textFieldMagnifierNode) { applySemantics() }
+    }
+}
+
+private val DefaultCursorThickness = 2.dp
+
+/**
+ * If brush has a specified color. It's possible that [SolidColor] contains [Color.Unspecified].
+ */
+private val Brush.isSpecified: Boolean
+    get() = !(this is SolidColor && this.value.isUnspecified)
+
+/**
+ * Converts cursorRect in text layout coordinates to scroller coordinates by adding the default
+ * cursor thickness and calculating the relative positioning caused by the layout direction.
+ *
+ * @param cursorRect Reported cursor rect by the text layout.
+ * @param rtl True if layout direction is RightToLeft
+ * @param textFieldWidth Total width of TextField composable
+ */
+private fun Density.getCursorRectInScroller(
+    cursorRect: Rect,
+    rtl: Boolean,
+    textFieldWidth: Int
+): Rect {
+    val thickness = DefaultCursorThickness.roundToPx()
+
+    val cursorLeft = if (rtl) {
+        textFieldWidth - cursorRect.right
+    } else {
+        cursorRect.left
+    }
+
+    val cursorRight = if (rtl) {
+        textFieldWidth - cursorRect.right + thickness
+    } else {
+        cursorRect.left + thickness
+    }
+    return cursorRect.copy(left = cursorLeft, right = cursorRight)
+}
+
+/**
+ * Rounds a negative number to floor, and a positive number to ceil. This is essentially the
+ * opposite of [truncate].
+ */
+private fun Float.roundToNext(): Float = when {
+    this.isNaN() || this.isInfinite() -> this
+    this > 0 -> ceil(this)
+    else -> floor(this)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
new file mode 100644
index 0000000..e720d7b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
@@ -0,0 +1,709 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
+import androidx.compose.foundation.content.internal.dragAndDropRequestPermission
+import androidx.compose.foundation.content.internal.getReceiveContentConfiguration
+import androidx.compose.foundation.content.readPlainText
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.KeyboardActionScope
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
+import androidx.compose.foundation.text.input.internal.selection.TextToolbarState
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequesterModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.requestFocus
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyInputModifierNode
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.invalidateSemantics
+import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.platform.PlatformTextInputModifierNode
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.platform.establishTextInputSession
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.copyText
+import androidx.compose.ui.semantics.cutText
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.editable
+import androidx.compose.ui.semantics.editableText
+import androidx.compose.ui.semantics.getTextLayoutResult
+import androidx.compose.ui.semantics.insertTextAtCursor
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.onImeAction
+import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.pasteText
+import androidx.compose.ui.semantics.setSelection
+import androidx.compose.ui.semantics.setText
+import androidx.compose.ui.semantics.textSelectionRange
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.IntSize
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalFoundationApi::class)
+private val MediaTypesText = setOf(MediaType.Text)
+
+@OptIn(ExperimentalFoundationApi::class)
+private val MediaTypesAll = setOf(MediaType.All)
+
+/**
+ * Modifier element for most of the functionality of [BasicTextField2] that is attached to the
+ * decoration box. This is only half the actual modifiers for the field, the other half are only
+ * attached to the internal text field.
+ *
+ * This modifier handles input events (both key and pointer), semantics, and focus.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal data class TextFieldDecoratorModifier(
+    private val textFieldState: TransformedTextFieldState,
+    private val textLayoutState: TextLayoutState,
+    private val textFieldSelectionState: TextFieldSelectionState,
+    private val filter: InputTransformation?,
+    private val enabled: Boolean,
+    private val readOnly: Boolean,
+    private val keyboardOptions: KeyboardOptions,
+    private val keyboardActions: KeyboardActions,
+    private val singleLine: Boolean,
+    private val interactionSource: MutableInteractionSource
+) : ModifierNodeElement<TextFieldDecoratorModifierNode>() {
+    override fun create(): TextFieldDecoratorModifierNode = TextFieldDecoratorModifierNode(
+        textFieldState = textFieldState,
+        textLayoutState = textLayoutState,
+        textFieldSelectionState = textFieldSelectionState,
+        filter = filter,
+        enabled = enabled,
+        readOnly = readOnly,
+        keyboardOptions = keyboardOptions,
+        keyboardActions = keyboardActions,
+        singleLine = singleLine,
+        interactionSource = interactionSource,
+    )
+
+    override fun update(node: TextFieldDecoratorModifierNode) {
+        node.updateNode(
+            textFieldState = textFieldState,
+            textLayoutState = textLayoutState,
+            textFieldSelectionState = textFieldSelectionState,
+            filter = filter,
+            enabled = enabled,
+            readOnly = readOnly,
+            keyboardOptions = keyboardOptions,
+            keyboardActions = keyboardActions,
+            singleLine = singleLine,
+            interactionSource = interactionSource,
+        )
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // Show nothing in the inspector.
+    }
+}
+
+/** Modifier node for [TextFieldDecoratorModifier]. */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextFieldDecoratorModifierNode(
+    var textFieldState: TransformedTextFieldState,
+    var textLayoutState: TextLayoutState,
+    var textFieldSelectionState: TextFieldSelectionState,
+    var filter: InputTransformation?,
+    var enabled: Boolean,
+    var readOnly: Boolean,
+    keyboardOptions: KeyboardOptions,
+    var keyboardActions: KeyboardActions,
+    var singleLine: Boolean,
+    var interactionSource: MutableInteractionSource
+) : DelegatingNode(),
+    PlatformTextInputModifierNode,
+    SemanticsModifierNode,
+    FocusRequesterModifierNode,
+    FocusEventModifierNode,
+    GlobalPositionAwareModifierNode,
+    PointerInputModifierNode,
+    KeyInputModifierNode,
+    CompositionLocalConsumerModifierNode,
+    ModifierLocalModifierNode,
+    ObserverModifierNode,
+    LayoutAwareModifierNode {
+
+    private val editable get() = enabled && !readOnly
+
+    private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
+        with(textFieldSelectionState) {
+            textFieldGestures(
+                requestFocus = {
+                    if (!isFocused) requestFocus()
+                },
+                showKeyboard = {
+                    if (inputSessionJob != null) {
+                        // just reshow the keyboard in existing session
+                        requireKeyboardController().show()
+                    } else {
+                        startInputSession(fromTap = true)
+                    }
+                }
+            )
+        }
+    })
+
+    /**
+     * The last enter event that was submitted to [interactionSource] from [dragAndDropNode]. We
+     * need to keep a reference to this event to send a follow-up exit event.
+     *
+     * We are using interaction source hover state as a hacky capsule to carry dragging events to
+     * core modifier node which draws the cursor and shows the magnifier. TextFields are not
+     * really focused when a dragging text hovers over them. Focused TextFields should have active
+     * input connections that is not required in a drag and drop scenario.
+     *
+     * When proper hover events are implemented for [interactionSource], the below code in
+     * [dragAndDropNode] should be revised.
+     */
+    private var dragEnterEvent: HoverInteraction.Enter? = null
+
+    /**
+     * Special Drag and Drop node for BasicTextField2 that is also aware of `receiveContent` API.
+     */
+    private val dragAndDropNode = delegate(
+        textFieldDragAndDropNode(
+            hintMediaTypes = {
+                val receiveContentConfiguration = getReceiveContentConfiguration()
+                // if receiveContent configuration is set, all drag operations should be
+                // accepted. ReceiveContent handler should evaluate the incoming content.
+                if (receiveContentConfiguration != null) {
+                    MediaTypesAll
+                } else {
+                    MediaTypesText
+                }
+            },
+            dragAndDropRequestPermission = {
+                if (getReceiveContentConfiguration() != null) {
+                    dragAndDropRequestPermission(it)
+                }
+            },
+            onEntered = {
+                dragEnterEvent = HoverInteraction.Enter().also {
+                    interactionSource.tryEmit(it)
+                }
+                // Although BasicTextField2 itself is not a `receiveContent` node, it should
+                // behave like one. Delegate the enter event to the ancestor nodes just like
+                // `receiveContent` itself would.
+                getReceiveContentConfiguration()?.receiveContentListener?.onDragEnter()
+            },
+            onMoved = { position ->
+                val positionOnTextField = textLayoutState.fromWindowToDecoration(position)
+                val cursorPosition = textLayoutState.getOffsetForPosition(positionOnTextField)
+                textFieldState.selectCharsIn(TextRange(cursorPosition))
+                textFieldSelectionState.updateHandleDragging(Handle.Cursor, positionOnTextField)
+            },
+            onDrop = { clipEntry, clipMetadata ->
+                emitDragExitEvent()
+                textFieldSelectionState.clearHandleDragging()
+                var plainText = clipEntry.readPlainText()
+
+                val receiveContentConfiguration = getReceiveContentConfiguration()
+                // if receiveContent configuration is set, all drag operations should be
+                // accepted. ReceiveContent handler should evaluate the incoming content.
+                if (receiveContentConfiguration != null) {
+                    val transferableContent = TransferableContent(
+                        clipEntry,
+                        clipMetadata,
+                        TransferableContent.Source.DragAndDrop
+                    )
+
+                    val remaining = receiveContentConfiguration
+                        .receiveContentListener
+                        .onReceive(transferableContent)
+                    plainText = remaining?.clipEntry?.readPlainText()
+                }
+                plainText?.let(textFieldState::replaceSelectedText)
+                true
+            },
+            onExited = {
+                emitDragExitEvent()
+                textFieldSelectionState.clearHandleDragging()
+                // Although BasicTextField2 itself is not a `receiveContent` node, it should
+                // behave like one. Delegate the exit event to the ancestor nodes just like
+                // `receiveContent` itself would.
+                getReceiveContentConfiguration()?.receiveContentListener?.onDragExit()
+            },
+            onEnded = {
+                emitDragExitEvent()
+            })
+    )
+
+    var keyboardOptions: KeyboardOptions = keyboardOptions.withDefaultsFrom(filter?.keyboardOptions)
+        private set
+
+    /**
+     * Needs to be kept separate from a window focus so we can restart an input session when the
+     * window receives the focus back. Element can stay focused even if the window loses its focus.
+     */
+    private var isElementFocused: Boolean = false
+
+    /**
+     * Keeps focus state of the window
+     */
+    private var windowInfo: WindowInfo? = null
+
+    private val isFocused: Boolean get() = isElementFocused && windowInfo?.isWindowFocused == true
+
+    /**
+     * Manages key events. These events often are sourced by a hardware keyboard but it's also
+     * possible that IME or some other platform system simulates a KeyEvent.
+     */
+    private val textFieldKeyEventHandler = createTextFieldKeyEventHandler()
+
+    private val keyboardActionScope = object : KeyboardActionScope {
+        private val focusManager: FocusManager
+            get() = currentValueOf(LocalFocusManager)
+
+        override fun defaultKeyboardAction(imeAction: ImeAction) {
+            when (imeAction) {
+                ImeAction.Next -> {
+                    focusManager.moveFocus(FocusDirection.Next)
+                }
+                ImeAction.Previous -> {
+                    focusManager.moveFocus(FocusDirection.Previous)
+                }
+                ImeAction.Done -> {
+                    requireKeyboardController().hide()
+                }
+                ImeAction.Go, ImeAction.Search, ImeAction.Send,
+                ImeAction.Default, ImeAction.None -> Unit
+            }
+        }
+    }
+
+    private val onImeActionPerformed: (ImeAction) -> Unit = { imeAction ->
+        val keyboardAction = when (imeAction) {
+            ImeAction.Done -> keyboardActions.onDone
+            ImeAction.Go -> keyboardActions.onGo
+            ImeAction.Next -> keyboardActions.onNext
+            ImeAction.Previous -> keyboardActions.onPrevious
+            ImeAction.Search -> keyboardActions.onSearch
+            ImeAction.Send -> keyboardActions.onSend
+            ImeAction.Default, ImeAction.None -> null
+            else -> error("invalid ImeAction")
+        }
+        keyboardAction?.invoke(keyboardActionScope)
+            ?: keyboardActionScope.defaultKeyboardAction(imeAction)
+    }
+
+    /**
+     * A coroutine job that observes text and layout changes in selection state to react to those
+     * changes.
+     */
+    private var inputSessionJob: Job? = null
+
+    private val receiveContentConfigurationProvider: () -> ReceiveContentConfiguration? = {
+        getReceiveContentConfiguration()
+    }
+
+    /**
+     * Updates all the related properties and invalidates internal state based on the changes.
+     */
+    fun updateNode(
+        textFieldState: TransformedTextFieldState,
+        textLayoutState: TextLayoutState,
+        textFieldSelectionState: TextFieldSelectionState,
+        filter: InputTransformation?,
+        enabled: Boolean,
+        readOnly: Boolean,
+        keyboardOptions: KeyboardOptions,
+        keyboardActions: KeyboardActions,
+        singleLine: Boolean,
+        interactionSource: MutableInteractionSource
+    ) {
+        // Find the diff: current previous and new values before updating current.
+        val previousWriteable = this.enabled && !this.readOnly
+        val writeable = enabled && !readOnly
+
+        val previousEnabled = this.enabled
+        val previousTextFieldState = this.textFieldState
+        val previousKeyboardOptions = this.keyboardOptions
+        val previousTextFieldSelectionState = this.textFieldSelectionState
+        val previousFilter = this.filter
+
+        // Apply the diff.
+        this.textFieldState = textFieldState
+        this.textLayoutState = textLayoutState
+        this.textFieldSelectionState = textFieldSelectionState
+        this.filter = filter
+        this.enabled = enabled
+        this.readOnly = readOnly
+        this.keyboardOptions = keyboardOptions.withDefaultsFrom(filter?.keyboardOptions)
+        this.keyboardActions = keyboardActions
+        this.singleLine = singleLine
+        this.interactionSource = interactionSource
+
+        // React to diff.
+        // Something about the session changed, restart the session.
+        if (writeable != previousWriteable ||
+            textFieldState != previousTextFieldState ||
+            keyboardOptions != previousKeyboardOptions ||
+            filter != previousFilter
+        ) {
+            if (writeable && isFocused) {
+                // The old session will be implicitly disposed.
+                startInputSession(fromTap = false)
+            } else if (!writeable) {
+                // We were made read-only or disabled, hide the keyboard.
+                disposeInputSession()
+            }
+        }
+
+        if (previousEnabled != enabled) {
+            invalidateSemantics()
+        }
+
+        if (textFieldSelectionState != previousTextFieldSelectionState) {
+            pointerInputNode.resetPointerInputHandler()
+            if (isAttached) {
+                textFieldSelectionState.receiveContentConfiguration =
+                    receiveContentConfigurationProvider
+            }
+        }
+    }
+
+    override val shouldMergeDescendantSemantics: Boolean
+        get() = true
+
+    // This function is called inside a snapshot observer.
+    override fun SemanticsPropertyReceiver.applySemantics() {
+        val text = textFieldState.outputText
+        val selection = text.selectionInChars
+        editableText = AnnotatedString(text.toString())
+        textSelectionRange = selection
+
+        if (!enabled) disabled()
+        if (editable) editable()
+
+        getTextLayoutResult {
+            textLayoutState.layoutResult?.let { result -> it.add(result) } ?: false
+        }
+        setText { newText ->
+            if (!editable) return@setText false
+
+            textFieldState.replaceAll(newText)
+            true
+        }
+        @Suppress("NAME_SHADOWING")
+        setSelection { start, end, relativeToOriginal ->
+            // in traversal mode (relativeToOriginal=true) we get selection from the
+            // `textSelectionRange` semantics which is selection in original text. In non-traversal
+            // mode selection comes from the Talkback and indices are relative to the transformed
+            // text
+            val text = if (relativeToOriginal) {
+                textFieldState.untransformedText
+            } else {
+                textFieldState.visualText
+            }
+            val selection = text.selectionInChars
+
+            if (!enabled ||
+                minOf(start, end) < 0 ||
+                maxOf(start, end) > text.length
+            ) {
+                return@setSelection false
+            }
+
+            // Selection is already selected, don't need to do any work.
+            if (start == selection.start && end == selection.end) {
+                return@setSelection true
+            }
+
+            val selectionRange = TextRange(start, end)
+            // Do not show toolbar if it's a traversal mode (with the volume keys), or if the
+            // selection is collapsed.
+            if (relativeToOriginal || start == end) {
+                textFieldSelectionState.updateTextToolbarState(TextToolbarState.None)
+            } else {
+                textFieldSelectionState.updateTextToolbarState(TextToolbarState.Selection)
+            }
+            if (relativeToOriginal) {
+                textFieldState.selectUntransformedCharsIn(selectionRange)
+            } else {
+                textFieldState.selectCharsIn(selectionRange)
+            }
+            return@setSelection true
+        }
+        insertTextAtCursor { newText ->
+            if (!editable) return@insertTextAtCursor false
+
+            // Finish composing text first because when the field is focused the IME
+            // might set composition.
+            textFieldState.replaceSelectedText(newText, clearComposition = true)
+            true
+        }
+        onImeAction(keyboardOptions.imeAction) {
+            onImeActionPerformed(keyboardOptions.imeAction)
+            true
+        }
+        onClick {
+            // according to the documentation, we still need to provide proper semantics actions
+            // even if the state is 'disabled'
+            if (!isFocused) {
+                requestFocus()
+            } else if (!readOnly) {
+                requireKeyboardController().show()
+            }
+            true
+        }
+        onLongClick {
+            if (!isFocused) {
+                requestFocus()
+            }
+            textFieldSelectionState.updateTextToolbarState(TextToolbarState.Selection)
+            true
+        }
+        if (!selection.collapsed) {
+            copyText {
+                textFieldSelectionState.copy()
+                true
+            }
+            if (enabled && !readOnly) {
+                cutText {
+                    textFieldSelectionState.cut()
+                    true
+                }
+            }
+        }
+        if (editable) {
+            pasteText {
+                textFieldSelectionState.paste()
+                true
+            }
+        }
+    }
+
+    override fun onFocusEvent(focusState: FocusState) {
+        if (isElementFocused == focusState.isFocused) {
+            return
+        }
+        isElementFocused = focusState.isFocused
+        textFieldSelectionState.isFocused = this.isFocused
+
+        if (focusState.isFocused) {
+            // Deselect when losing focus even if readonly.
+            if (editable) {
+                startInputSession(fromTap = false)
+            }
+        } else {
+            disposeInputSession()
+            textFieldState.collapseSelectionToMax()
+        }
+    }
+
+    override fun onAttach() {
+        onObservedReadsChanged()
+        textFieldSelectionState.receiveContentConfiguration = receiveContentConfigurationProvider
+    }
+
+    override fun onDetach() {
+        disposeInputSession()
+        textFieldSelectionState.receiveContentConfiguration = null
+    }
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        textLayoutState.decoratorNodeCoordinates = coordinates
+    }
+
+    override fun onRemeasured(size: IntSize) {
+        if (!isFocused) return
+
+        // Ensure that the cursor is kept in view if the decoration box is resized while focused.
+        // This handles the case where a multi-line text field sitting right above the keyboard
+        // grows due to a newline entered while typing, which isn't handled by the cursor moving yet
+        // because the resize happens after the text state change, and the resize moves the cursor
+        // under the keyboard. This also covers the case where the field shrinks while focused.
+        val selection = textFieldState.visualText.selectionInChars
+        if (selection.collapsed) {
+            coroutineScope.launch {
+                textLayoutState.bringCursorIntoView(cursorIndex = selection.start)
+            }
+        }
+    }
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize
+    ) {
+        pointerInputNode.onPointerEvent(pointerEvent, pass, bounds)
+    }
+
+    override fun onCancelPointerInput() {
+        pointerInputNode.onCancelPointerInput()
+    }
+
+    override fun onPreKeyEvent(event: KeyEvent): Boolean {
+        return textFieldKeyEventHandler.onPreKeyEvent(
+            event = event,
+            textFieldState = textFieldState,
+            textFieldSelectionState = textFieldSelectionState,
+            focusManager = currentValueOf(LocalFocusManager),
+            keyboardController = requireKeyboardController()
+        )
+    }
+
+    override fun onKeyEvent(event: KeyEvent): Boolean {
+        return textFieldKeyEventHandler.onKeyEvent(
+            event = event,
+            textFieldState = textFieldState,
+            textLayoutState = textLayoutState,
+            textFieldSelectionState = textFieldSelectionState,
+            editable = enabled && !readOnly,
+            singleLine = singleLine,
+            onSubmit = { onImeActionPerformed(keyboardOptions.imeAction) }
+        )
+    }
+
+    override fun onObservedReadsChanged() {
+        observeReads {
+            windowInfo = currentValueOf(LocalWindowInfo)
+            startOrDisposeInputSessionOnWindowFocusChange()
+        }
+    }
+
+    private fun startInputSession(fromTap: Boolean) {
+        if (!fromTap && !keyboardOptions.shouldShowKeyboardOnFocus) return
+
+        val receiveContentConfiguration = getReceiveContentConfiguration()
+
+        inputSessionJob = coroutineScope.launch {
+            // This will automatically cancel the previous session, if any, so we don't need to
+            // cancel the inputSessionJob ourselves.
+            establishTextInputSession {
+                // Re-start observing changes in case our TextFieldState instance changed.
+                launch(start = CoroutineStart.UNDISPATCHED) {
+                    textFieldSelectionState.observeChanges()
+                }
+
+                platformSpecificTextInputSession(
+                    state = textFieldState,
+                    layoutState = textLayoutState,
+                    imeOptions = keyboardOptions.toImeOptions(singleLine),
+                    receiveContentConfiguration = receiveContentConfiguration,
+                    onImeAction = onImeActionPerformed
+                )
+            }
+        }
+    }
+
+    private fun disposeInputSession() {
+        inputSessionJob?.cancel()
+        inputSessionJob = null
+    }
+
+    private fun startOrDisposeInputSessionOnWindowFocusChange() {
+        if (windowInfo == null) return
+        if (windowInfo?.isWindowFocused == true && isElementFocused) {
+            startInputSession(fromTap = false)
+        } else {
+            disposeInputSession()
+        }
+    }
+
+    private fun requireKeyboardController(): SoftwareKeyboardController =
+        currentValueOf(LocalSoftwareKeyboardController)
+            ?: error("No software keyboard controller")
+
+    private fun emitDragExitEvent() {
+        dragEnterEvent?.let {
+            interactionSource.tryEmit(HoverInteraction.Exit(it))
+            dragEnterEvent = null
+        }
+    }
+}
+
+/**
+ * Runs platform-specific text input logic.
+ */
+internal expect suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
+    state: TransformedTextFieldState,
+    layoutState: TextLayoutState,
+    imeOptions: ImeOptions,
+    receiveContentConfiguration: ReceiveContentConfiguration?,
+    onImeAction: ((ImeAction) -> Unit)?
+): Nothing
+
+/**
+ * Returns a [KeyboardOptions] that is merged with [defaults], with this object's values taking
+ * precedence.
+ */
+// TODO(b/295951492) KeyboardOptions can't actually be merged correctly in all cases, because its
+//  properties don't all have proper "unspecified" values. I think we can fix that in a
+//  backwards-compatible way, but it will require adding new API outside of the text2 package so we
+//  should hold off on making them until after the study.
+internal fun KeyboardOptions.withDefaultsFrom(defaults: KeyboardOptions?): KeyboardOptions {
+    if (defaults == null) return this
+    return KeyboardOptions(
+        capitalization = if (this.capitalization != KeyboardCapitalization.None) {
+            this.capitalization
+        } else {
+            defaults.capitalization
+        },
+        autoCorrect = this.autoCorrect && defaults.autoCorrect,
+        keyboardType = if (this.keyboardType != KeyboardType.Text) {
+            this.keyboardType
+        } else {
+            defaults.keyboardType
+        },
+        imeAction = if (this.imeAction != ImeAction.Default) {
+            this.imeAction
+        } else {
+            defaults.imeAction
+        }
+    )
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.kt
new file mode 100644
index 0000000..fd46998
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropModifierNode
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.ClipMetadata
+
+@OptIn(ExperimentalFoundationApi::class)
+internal expect fun textFieldDragAndDropNode(
+    hintMediaTypes: () -> Set<MediaType>,
+    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
+    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
+    onStarted: ((event: DragAndDropEvent) -> Unit)? = null,
+    onEntered: ((event: DragAndDropEvent) -> Unit)? = null,
+    onMoved: ((position: Offset) -> Unit)? = null,
+    onChanged: ((event: DragAndDropEvent) -> Unit)? = null,
+    onExited: ((event: DragAndDropEvent) -> Unit)? = null,
+    onEnded: ((event: DragAndDropEvent) -> Unit)? = null,
+): DragAndDropModifierNode
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt
new file mode 100644
index 0000000..5ec5a1c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.DeadKeyCombiner
+import androidx.compose.foundation.text.KeyCommand
+import androidx.compose.foundation.text.appendCodePointX
+import androidx.compose.foundation.text.cancelsTextSelection
+import androidx.compose.foundation.text.input.internal.selection.TextFieldPreparedSelection
+import androidx.compose.foundation.text.input.internal.selection.TextFieldPreparedSelection.Companion.NoCharacterFound
+import androidx.compose.foundation.text.input.internal.selection.TextFieldPreparedSelectionState
+import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
+import androidx.compose.foundation.text.isTypedEvent
+import androidx.compose.foundation.text.platformDefaultKeyMapping
+import androidx.compose.foundation.text.showCharacterPalette
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Factory function to create a platform specific [TextFieldKeyEventHandler].
+ */
+internal expect fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler
+
+/**
+ * Handles KeyEvents coming to a BasicTextField2. This is mostly to support hardware keyboard but
+ * any KeyEvent can also be sent by the IME or other platform systems.
+ *
+ * This class is left abstract to make sure that each platform extends from it. Platforms can
+ * decide to extend or completely override KeyEvent actions defined here.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal abstract class TextFieldKeyEventHandler {
+    private val preparedSelectionState = TextFieldPreparedSelectionState()
+    private val deadKeyCombiner = DeadKeyCombiner()
+    private val keyMapping = platformDefaultKeyMapping
+
+    open fun onPreKeyEvent(
+        event: KeyEvent,
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        focusManager: FocusManager,
+        keyboardController: SoftwareKeyboardController
+    ): Boolean {
+        val selection = textFieldState.visualText.selectionInChars
+        return if (!selection.collapsed && event.cancelsTextSelection()) {
+            textFieldSelectionState.deselect()
+            true
+        } else {
+            false
+        }
+    }
+
+    open fun onKeyEvent(
+        event: KeyEvent,
+        textFieldState: TransformedTextFieldState,
+        textLayoutState: TextLayoutState,
+        textFieldSelectionState: TextFieldSelectionState,
+        editable: Boolean,
+        singleLine: Boolean,
+        onSubmit: () -> Unit
+    ): Boolean {
+        if (event.type != KeyEventType.KeyDown) {
+            return false
+        }
+
+        if (event.isTypedEvent) {
+            val codePoint = deadKeyCombiner.consume(event)
+            if (codePoint != null) {
+                val text = StringBuilder(2).appendCodePointX(codePoint).toString()
+                return if (editable) {
+                    textFieldState.editUntransformedTextAsUser {
+                        commitComposition()
+                        commitText(text, 1)
+                    }
+                    preparedSelectionState.resetCachedX()
+                    true
+                } else {
+                    false
+                }
+            }
+        }
+
+        val command = keyMapping.map(event)
+        if (command == null || (command.editsText && !editable)) {
+            return false
+        }
+        var consumed = true
+        preparedSelectionContext(textFieldState, textLayoutState) {
+            when (command) {
+                KeyCommand.COPY -> textFieldSelectionState.copy(false)
+                KeyCommand.PASTE -> textFieldSelectionState.paste()
+                KeyCommand.CUT -> textFieldSelectionState.cut()
+                KeyCommand.LEFT_CHAR -> collapseLeftOr { moveCursorLeft() }
+                KeyCommand.RIGHT_CHAR -> collapseRightOr { moveCursorRight() }
+                KeyCommand.LEFT_WORD -> moveCursorLeftByWord()
+                KeyCommand.RIGHT_WORD -> moveCursorRightByWord()
+                KeyCommand.PREV_PARAGRAPH -> moveCursorPrevByParagraph()
+                KeyCommand.NEXT_PARAGRAPH -> moveCursorNextByParagraph()
+                KeyCommand.UP -> moveCursorUpByLine()
+                KeyCommand.DOWN -> moveCursorDownByLine()
+                KeyCommand.PAGE_UP -> moveCursorUpByPage()
+                KeyCommand.PAGE_DOWN -> moveCursorDownByPage()
+                KeyCommand.LINE_START -> moveCursorToLineStart()
+                KeyCommand.LINE_END -> moveCursorToLineEnd()
+                KeyCommand.LINE_LEFT -> moveCursorToLineLeftSide()
+                KeyCommand.LINE_RIGHT -> moveCursorToLineRightSide()
+                KeyCommand.HOME -> moveCursorToHome()
+                KeyCommand.END -> moveCursorToEnd()
+                KeyCommand.DELETE_PREV_CHAR -> {
+                    deleteIfSelectedOr {
+                        getPrecedingCharacterIndex().takeIf { it != NoCharacterFound }?.let {
+                            TextRange(it, selection.end)
+                        }
+                    }
+                }
+
+                KeyCommand.DELETE_NEXT_CHAR -> {
+                    // Note that some software keyboards, such as Samsung, go through this code
+                    // path instead of making calls on the InputConnection directly.
+                    deleteIfSelectedOr {
+                        getNextCharacterIndex().takeIf { it != NoCharacterFound }?.let {
+                            TextRange(selection.start, it)
+                        }
+                    }
+                }
+
+                KeyCommand.DELETE_PREV_WORD -> {
+                    deleteIfSelectedOr {
+                        TextRange(getPreviousWordOffset(), selection.end)
+                    }
+                }
+
+                KeyCommand.DELETE_NEXT_WORD -> {
+                    deleteIfSelectedOr {
+                        TextRange(selection.start, getNextWordOffset())
+                    }
+                }
+
+                KeyCommand.DELETE_FROM_LINE_START -> {
+                    deleteIfSelectedOr {
+                        TextRange(getLineStartByOffset(), selection.end)
+                    }
+                }
+
+                KeyCommand.DELETE_TO_LINE_END -> {
+                    deleteIfSelectedOr {
+                        TextRange(selection.start, getLineEndByOffset())
+                    }
+                }
+
+                KeyCommand.NEW_LINE -> {
+                    if (!singleLine) {
+                        textFieldState.editUntransformedTextAsUser {
+                            commitComposition()
+                            commitText("\n", 1)
+                        }
+                    } else {
+                        onSubmit()
+                    }
+                }
+
+                KeyCommand.TAB -> {
+                    if (!singleLine) {
+                        textFieldState.editUntransformedTextAsUser {
+                            commitComposition()
+                            commitText("\t", 1)
+                        }
+                    } else {
+                        consumed = false // let propagate to focus system
+                    }
+                }
+
+                KeyCommand.SELECT_ALL -> selectAll()
+                KeyCommand.SELECT_LEFT_CHAR -> moveCursorLeft().selectMovement()
+                KeyCommand.SELECT_RIGHT_CHAR -> moveCursorRight().selectMovement()
+                KeyCommand.SELECT_LEFT_WORD -> moveCursorLeftByWord().selectMovement()
+                KeyCommand.SELECT_RIGHT_WORD -> moveCursorRightByWord().selectMovement()
+                KeyCommand.SELECT_PREV_PARAGRAPH -> moveCursorPrevByParagraph().selectMovement()
+                KeyCommand.SELECT_NEXT_PARAGRAPH -> moveCursorNextByParagraph().selectMovement()
+                KeyCommand.SELECT_LINE_START -> moveCursorToLineStart().selectMovement()
+                KeyCommand.SELECT_LINE_END -> moveCursorToLineEnd().selectMovement()
+                KeyCommand.SELECT_LINE_LEFT -> moveCursorToLineLeftSide().selectMovement()
+                KeyCommand.SELECT_LINE_RIGHT -> moveCursorToLineRightSide().selectMovement()
+                KeyCommand.SELECT_UP -> moveCursorUpByLine().selectMovement()
+                KeyCommand.SELECT_DOWN -> moveCursorDownByLine().selectMovement()
+                KeyCommand.SELECT_PAGE_UP -> moveCursorUpByPage().selectMovement()
+                KeyCommand.SELECT_PAGE_DOWN -> moveCursorDownByPage().selectMovement()
+                KeyCommand.SELECT_HOME -> moveCursorToHome().selectMovement()
+                KeyCommand.SELECT_END -> moveCursorToEnd().selectMovement()
+                KeyCommand.DESELECT -> deselect()
+                KeyCommand.UNDO -> {
+                    textFieldState.undo()
+                }
+
+                KeyCommand.REDO -> {
+                    textFieldState.redo()
+                }
+
+                KeyCommand.CHARACTER_PALETTE -> {
+                    showCharacterPalette()
+                }
+            }
+        }
+        return consumed
+    }
+
+    private inline fun preparedSelectionContext(
+        state: TransformedTextFieldState,
+        textLayoutState: TextLayoutState,
+        block: TextFieldPreparedSelection.() -> Unit
+    ) {
+        val layoutResult = textLayoutState.layoutResult ?: return
+        val visibleTextLayoutHeight = textLayoutState.getVisibleTextLayoutHeight() ?: return
+        val preparedSelection = TextFieldPreparedSelection(
+            state = state,
+            textLayoutResult = layoutResult,
+            visibleTextLayoutHeight = visibleTextLayoutHeight,
+            textPreparedSelectionState = preparedSelectionState
+        )
+        preparedSelection.block()
+        if (preparedSelection.selection != preparedSelection.initialValue.selectionInChars) {
+            // selection changes are applied atomically at the end of context evaluation
+            state.selectCharsIn(preparedSelection.selection)
+        }
+    }
+
+    /**
+     * Returns the current viewport height of TextField to help calculate where cursor should travel
+     * when page down and up events are received.
+     */
+    private fun TextLayoutState.getVisibleTextLayoutHeight(): Float? {
+        return textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { textLayoutCoordinates ->
+            decoratorNodeCoordinates?.takeIf { it.isAttached }?.let { decoratorCoordinates ->
+                decoratorCoordinates.localBoundingBoxOf(textLayoutCoordinates)
+            }
+        }?.size?.height
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
new file mode 100644
index 0000000..547eeac
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
@@ -0,0 +1,411 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.InternalFoundationTextApi
+import androidx.compose.foundation.text.TextDelegate
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.internal.TextFieldLayoutStateCache.MeasureInputs
+import androidx.compose.foundation.text.input.internal.TextFieldLayoutStateCache.NonMeasureInputs
+import androidx.compose.runtime.SnapshotMutationPolicy
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.StateObject
+import androidx.compose.runtime.snapshots.StateRecord
+import androidx.compose.runtime.snapshots.withCurrent
+import androidx.compose.runtime.snapshots.writable
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutInput
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Performs text layout lazily, on-demand for text fields with snapshot-aware caching.
+ *
+ * You can basically think of this as a `derivedStateOf` that combines all the inputs to text layout
+ * — the text itself, configuration parameters, and layout inputs — and spits out a
+ * [TextLayoutResult]. The [value] property will register snapshot reads for all the inputs and
+ * either return a cached result or re-compute the result and cache it in the current snapshot.
+ * The cache is snapshot aware: when a new layout is computed, it will only be cached in the current
+ * snapshot. When the snapshot with the new result is applied, its cache will also be visible to the
+ * parent snapshot.
+ *
+ * All the possible inputs to text layout are grouped into two groups: those that come from the
+ * layout system ([MeasureInputs]) and those that are passed explicitly to the text field composable
+ * ([NonMeasureInputs]). Each of these groups can only be updated in bulk, and each group is stored
+ * in an instance of a dedicated class. This means for each type of update, only one state object
+ * is needed.
+ */
+@OptIn(ExperimentalFoundationApi::class, InternalFoundationTextApi::class)
+internal class TextFieldLayoutStateCache : State<TextLayoutResult?>, StateObject {
+    private var nonMeasureInputs: NonMeasureInputs? by mutableStateOf(
+        value = null,
+        policy = NonMeasureInputs.mutationPolicy
+    )
+    private var measureInputs: MeasureInputs? by mutableStateOf(
+        value = null,
+        policy = MeasureInputs.mutationPolicy
+    )
+
+    /**
+     * Returns the [TextLayoutResult] for the current text field state and layout inputs, or null
+     * if the layout cannot be computed at this time.
+     *
+     * This method will re-calculate the text layout if the text or any of the other layout inputs
+     * have changed, otherwise it will return a cached value.
+     *
+     * [updateNonMeasureInputs] and [layoutWithNewMeasureInputs] must both be called before this
+     * to initialize all the inputs, or it will return null.
+     */
+    override val value: TextLayoutResult?
+        get() {
+            // If this is called from the global snapshot, there is technically a race between
+            // reading each of our input state objects. That's fine because worst case we'll just
+            // re-compute the layout on the next read anyway.
+            val nonMeasureInputs = nonMeasureInputs ?: return null
+            val measureInputs = measureInputs ?: return null
+            return getOrComputeLayout(nonMeasureInputs, measureInputs)
+        }
+
+    /**
+     * Updates the inputs that aren't from the measure phase.
+     *
+     * If any of the inputs changed, this method will invalidate any callers of [value]. If the
+     * inputs did not change it will not invalidate callers of [value].
+     *
+     * Note: This will register a snapshot read of [TextFieldState.text] if called from a snapshot
+     * observer.
+     *
+     * @see layoutWithNewMeasureInputs
+     */
+    fun updateNonMeasureInputs(
+        textFieldState: TransformedTextFieldState,
+        textStyle: TextStyle,
+        singleLine: Boolean,
+        softWrap: Boolean,
+    ) {
+        nonMeasureInputs = NonMeasureInputs(
+            textFieldState = textFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            softWrap = softWrap,
+        )
+    }
+
+    /**
+     * Updates the inputs from the measure phase and returns the most up-to-date [TextLayoutResult].
+     *
+     * If any of the inputs changed, this method will invalidate any callers of [value], re-compute
+     * the text layout, and return the new layout result. If the inputs did not change, it will
+     * return a cached value without invalidating callers of [value].
+     *
+     * @see updateNonMeasureInputs
+     */
+    fun layoutWithNewMeasureInputs(
+        density: Density,
+        layoutDirection: LayoutDirection,
+        fontFamilyResolver: FontFamily.Resolver,
+        constraints: Constraints,
+    ): TextLayoutResult {
+        val measureInputs = MeasureInputs(
+            density = density,
+            layoutDirection = layoutDirection,
+            fontFamilyResolver = fontFamilyResolver,
+            constraints = constraints,
+        )
+        this.measureInputs = measureInputs
+        val nonMeasureInputs = checkNotNull(nonMeasureInputs) {
+            "Called layoutWithNewMeasureInputs before updateNonMeasureInputs"
+        }
+        return getOrComputeLayout(nonMeasureInputs, measureInputs)
+    }
+
+    private fun getOrComputeLayout(
+        nonMeasureInputs: NonMeasureInputs,
+        measureInputs: MeasureInputs
+    ): TextLayoutResult {
+        val visualText = nonMeasureInputs.textFieldState.visualText
+
+        // Use withCurrent here so the cache itself is never reported as a read state object. It
+        // doesn't need to be, because it's always guaranteed to return the same value for the same
+        // inputs, so it's good enough to read the input states and those will invalidate the
+        // caller when they change.
+        record.withCurrent { cachedRecord ->
+            val cachedResult = cachedRecord.layoutResult
+
+            if (cachedResult != null &&
+                cachedRecord.visualText?.contentEquals(visualText) == true &&
+                cachedRecord.singleLine == nonMeasureInputs.singleLine &&
+                cachedRecord.softWrap == nonMeasureInputs.softWrap &&
+                cachedRecord.layoutDirection == measureInputs.layoutDirection &&
+                cachedRecord.densityValue == measureInputs.density.density &&
+                cachedRecord.fontScale == measureInputs.density.fontScale &&
+                cachedRecord.constraints == measureInputs.constraints &&
+                cachedRecord.fontFamilyResolver == measureInputs.fontFamilyResolver
+            ) {
+                // Fast path: None of the inputs changed.
+                if (cachedRecord.textStyle == nonMeasureInputs.textStyle) return cachedResult
+                // Slightly slower than fast path: Layout did not change but TextLayoutInput did
+                if (cachedRecord.textStyle
+                        ?.hasSameDrawAffectingAttributes(nonMeasureInputs.textStyle) == true
+                ) {
+                    return cachedResult.copy(
+                        layoutInput = TextLayoutInput(
+                            cachedResult.layoutInput.text,
+                            nonMeasureInputs.textStyle,
+                            cachedResult.layoutInput.placeholders,
+                            cachedResult.layoutInput.maxLines,
+                            cachedResult.layoutInput.softWrap,
+                            cachedResult.layoutInput.overflow,
+                            cachedResult.layoutInput.density,
+                            cachedResult.layoutInput.layoutDirection,
+                            cachedResult.layoutInput.fontFamilyResolver,
+                            cachedResult.layoutInput.constraints
+                        )
+                    )
+                }
+            }
+
+            // Slow path: Some input changed, need to re-layout.
+            return computeLayout(visualText, nonMeasureInputs, measureInputs, cachedResult)
+                .also { newResult ->
+                    // TODO(b/294403840) TextDelegate does its own caching and may return the same
+                    //  TextLayoutResult object. We should inline that so we don't check twice.
+                    if (newResult != cachedResult) {
+                        updateCacheIfWritable {
+                            this.visualText = visualText
+                            this.singleLine = nonMeasureInputs.singleLine
+                            this.softWrap = nonMeasureInputs.softWrap
+                            this.textStyle = nonMeasureInputs.textStyle
+                            this.layoutDirection = measureInputs.layoutDirection
+                            this.densityValue = measureInputs.densityValue
+                            this.fontScale = measureInputs.fontScale
+                            this.constraints = measureInputs.constraints
+                            this.fontFamilyResolver = measureInputs.fontFamilyResolver
+                            this.layoutResult = newResult
+                        }
+                    }
+                }
+        }
+    }
+
+    private inline fun updateCacheIfWritable(block: CacheRecord.() -> Unit) {
+        val snapshot = Snapshot.current
+        // We can't write to the cache when called from a read-only snapshot.
+        if (!snapshot.readOnly) {
+            record.writable(this, snapshot, block)
+        }
+    }
+
+    private fun computeLayout(
+        visualText: CharSequence,
+        nonMeasureInputs: NonMeasureInputs,
+        measureInputs: MeasureInputs,
+        prevResult: TextLayoutResult?
+    ): TextLayoutResult {
+        // TODO(b/294403840) Don't use TextDelegate – it is not designed for this use case,
+        //  optimized for re-use which we don't take advantage of here, and does its own caching
+        //  checks. Maybe we can use MultiParagraphLayoutCache like BasicText?
+
+        // We have to always create a new TextDelegate since it contains internal state that is
+        // not snapshot-aware.
+        val textDelegate = TextDelegate(
+            text = AnnotatedString(visualText.toString()),
+            style = nonMeasureInputs.textStyle,
+            density = measureInputs.density,
+            fontFamilyResolver = measureInputs.fontFamilyResolver,
+            softWrap = nonMeasureInputs.softWrap,
+            placeholders = emptyList()
+        )
+
+        return textDelegate.layout(
+            layoutDirection = measureInputs.layoutDirection,
+            constraints = measureInputs.constraints,
+            prevResult = prevResult
+        )
+    }
+
+    // region StateObject
+    private var record = CacheRecord()
+    override val firstStateRecord: StateRecord
+        get() = record
+
+    override fun prependStateRecord(value: StateRecord) {
+        this.record = value as CacheRecord
+    }
+
+    override fun mergeRecords(
+        previous: StateRecord,
+        current: StateRecord,
+        applied: StateRecord
+    ): StateRecord {
+        // This is just a cache, so it's safe to always take the most recent record – worst case
+        // we'll just re-compute the layout.
+        // However, if we needed to, we could increase the chance of a cache hit by comparing
+        // property-by-property and taking the latest version of each property.
+        return applied
+    }
+
+    /**
+     * State record that stores the cached [TextLayoutResult], as well as all the inputs used to
+     * generate that result.
+     */
+    private class CacheRecord : StateRecord() {
+        // Inputs. These are slightly different from the values in (Non)MeasuredInputs because they
+        // represent the values read from objects in the inputs that are relevant to layout, whereas
+        // the Inputs classes contain objects where we don't always care about the entire object.
+        // E.g. text layout doesn't care about TextFieldState instances, it only cares about the
+        // actual text. If the TFS instance changes but has the same text, we don't need to
+        // re-layout. Also if the TFS object _doesn't_ change but its text _does_, we do need to
+        // re-layout. That state read happens in getOrComputeLayout to invalidate correctly.
+        var visualText: CharSequence? = null
+        var textStyle: TextStyle? = null
+        var singleLine: Boolean = false
+        var softWrap: Boolean = false
+        var densityValue: Float = Float.NaN
+        var fontScale: Float = Float.NaN
+        var layoutDirection: LayoutDirection? = null
+        var fontFamilyResolver: FontFamily.Resolver? = null
+
+        /** Not nullable to avoid boxing. */
+        var constraints: Constraints = Constraints()
+
+        // Outputs.
+        var layoutResult: TextLayoutResult? = null
+
+        override fun create(): StateRecord = CacheRecord()
+
+        override fun assign(value: StateRecord) {
+            value as CacheRecord
+            visualText = value.visualText
+            textStyle = value.textStyle
+            singleLine = value.singleLine
+            softWrap = value.softWrap
+            densityValue = value.densityValue
+            fontScale = value.fontScale
+            layoutDirection = value.layoutDirection
+            fontFamilyResolver = value.fontFamilyResolver
+            constraints = value.constraints
+            layoutResult = value.layoutResult
+        }
+
+        override fun toString(): String = "CacheRecord(" +
+            "visualText=$visualText, " +
+            "textStyle=$textStyle, " +
+            "singleLine=$singleLine, " +
+            "softWrap=$softWrap, " +
+            "densityValue=$densityValue, " +
+            "fontScale=$fontScale, " +
+            "layoutDirection=$layoutDirection, " +
+            "fontFamilyResolver=$fontFamilyResolver, " +
+            "constraints=$constraints, " +
+            "layoutResult=$layoutResult" +
+            ")"
+    }
+    // endregion
+
+    // region Input holders
+    private class NonMeasureInputs(
+        val textFieldState: TransformedTextFieldState,
+        val textStyle: TextStyle,
+        val singleLine: Boolean,
+        val softWrap: Boolean,
+    ) {
+
+        override fun toString(): String = "NonMeasureInputs(" +
+            "textFieldState=$textFieldState, " +
+            "textStyle=$textStyle, " +
+            "singleLine=$singleLine, " +
+            "softWrap=$softWrap" +
+            ")"
+
+        companion object {
+            /**
+             * Implements equivalence by comparing only the parts of [NonMeasureInputs] that may
+             * require re-computing text layout. Notably, it reads the [TextFieldState.text] state
+             * property and compares only the text (not selection). This means that when the text
+             * state changes it will invalidate any snapshot observer that sets this state.
+             */
+            val mutationPolicy = object : SnapshotMutationPolicy<NonMeasureInputs?> {
+                override fun equivalent(a: NonMeasureInputs?, b: NonMeasureInputs?): Boolean =
+                    if (a != null && b != null) {
+                        // We don't need to compare text contents here because the text state is read
+                        // by getOrComputeLayout – if the text state changes, that method will already
+                        // be invalidated. The only reason to compare text here would be to avoid
+                        // invalidating if the TextFieldState is a different instance but with the same
+                        // text, but that is unlikely to happen.
+                        a.textFieldState === b.textFieldState &&
+                            a.textStyle == b.textStyle &&
+                            a.singleLine == b.singleLine &&
+                            a.softWrap == b.softWrap
+                    } else {
+                        !((a == null) xor (b == null))
+                    }
+            }
+        }
+    }
+
+    /**
+     * We store both the [Density] object, as well as its component values, because the same density
+     * object can report different actual densities over time so we need to be able to see when
+     * those values change. We still need the [Density] object to pass to [TextDelegate] though.
+     */
+    private class MeasureInputs(
+        val density: Density,
+        val layoutDirection: LayoutDirection,
+        val fontFamilyResolver: FontFamily.Resolver,
+        val constraints: Constraints,
+    ) {
+        val densityValue: Float = density.density
+        val fontScale: Float = density.fontScale
+
+        override fun toString(): String = "MeasureInputs(" +
+            "density=$density, " +
+            "densityValue=$densityValue, " +
+            "fontScale=$fontScale, " +
+            "layoutDirection=$layoutDirection, " +
+            "fontFamilyResolver=$fontFamilyResolver, " +
+            "constraints=$constraints" +
+            ")"
+
+        companion object {
+            val mutationPolicy = object : SnapshotMutationPolicy<MeasureInputs?> {
+                override fun equivalent(a: MeasureInputs?, b: MeasureInputs?): Boolean =
+                    if (a != null && b != null) {
+                        // Don't compare density – we don't care if the density instance changed,
+                        // only if the actual values used in density calculations did.
+                        a.densityValue == b.densityValue &&
+                            a.fontScale == b.fontScale &&
+                            a.layoutDirection == b.layoutDirection &&
+                            a.fontFamilyResolver == b.fontFamilyResolver &&
+                            a.constraints == b.constraints
+                    } else {
+                        !((a == null) xor (b == null))
+                    }
+            }
+        }
+    }
+    // endregion
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldTextLayoutModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldTextLayoutModifier.kt
new file mode 100644
index 0000000..9050c21
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldTextLayoutModifier.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.text.ceilToIntPx
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.FirstBaseline
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastRoundToInt
+
+/**
+ * This ModifierNodeElement is only responsible for laying out text and reporting its global
+ * position coordinates. Text layout is kept in a separate node than the rest of the Core modifiers
+ * because it will also go through scroll and minSize constraints. We need to know exact size and
+ * coordinates of [TextLayoutResult] to make it relatively easier to calculate the offset between
+ * exact touch coordinates and where they map on the [TextLayoutResult].
+ */
+internal data class TextFieldTextLayoutModifier(
+    private val textLayoutState: TextLayoutState,
+    private val textFieldState: TransformedTextFieldState,
+    private val textStyle: TextStyle,
+    private val singleLine: Boolean,
+    private val onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
+) : ModifierNodeElement<TextFieldTextLayoutModifierNode>() {
+    override fun create(): TextFieldTextLayoutModifierNode = TextFieldTextLayoutModifierNode(
+        textLayoutState = textLayoutState,
+        textFieldState = textFieldState,
+        textStyle = textStyle,
+        singleLine = singleLine,
+        onTextLayout = onTextLayout
+    )
+
+    override fun update(node: TextFieldTextLayoutModifierNode) {
+        node.updateNode(
+            textLayoutState = textLayoutState,
+            textFieldState = textFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            onTextLayout = onTextLayout
+        )
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // no inspector info
+    }
+}
+
+internal class TextFieldTextLayoutModifierNode(
+    private var textLayoutState: TextLayoutState,
+    textFieldState: TransformedTextFieldState,
+    textStyle: TextStyle,
+    private var singleLine: Boolean,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
+) : Modifier.Node(),
+    LayoutModifierNode,
+    GlobalPositionAwareModifierNode,
+    CompositionLocalConsumerModifierNode {
+
+    init {
+        textLayoutState.onTextLayout = onTextLayout
+        textLayoutState.updateNonMeasureInputs(
+            textFieldState = textFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            softWrap = !singleLine
+        )
+    }
+
+    @Suppress("PrimitiveInCollection")
+    private var baselineCache: MutableMap<AlignmentLine, Int>? = null
+
+    /**
+     * Updates all the related properties and invalidates internal state based on the changes.
+     */
+    fun updateNode(
+        textLayoutState: TextLayoutState,
+        textFieldState: TransformedTextFieldState,
+        textStyle: TextStyle,
+        singleLine: Boolean,
+        onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
+    ) {
+        this.textLayoutState = textLayoutState
+        this.textLayoutState.onTextLayout = onTextLayout
+        this.singleLine = singleLine
+        this.textLayoutState.updateNonMeasureInputs(
+            textFieldState = textFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            softWrap = !singleLine
+        )
+    }
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        this.textLayoutState.textLayoutNodeCoordinates = coordinates
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val result = textLayoutState.layoutWithNewMeasureInputs(
+            density = this,
+            layoutDirection = layoutDirection,
+            fontFamilyResolver = currentValueOf(LocalFontFamilyResolver),
+            constraints = constraints,
+        )
+
+        val placeable = measurable.measure(
+            Constraints.fixed(result.size.width, result.size.height)
+        )
+
+        // calculate the min height for single line text to prevent text cuts.
+        // for single line text maxLines puts in max height constraint based on
+        // constant characters therefore if the user enters a character that is
+        // longer (i.e. emoji or a tall script) the text is cut
+        textLayoutState.minHeightForSingleLineField = if (singleLine) {
+            result.getLineBottom(0).ceilToIntPx().toDp()
+        } else {
+            0.dp
+        }
+
+        @Suppress("PrimitiveInCollection")
+        val cache = baselineCache ?: LinkedHashMap(2)
+        cache[FirstBaseline] = result.firstBaseline.fastRoundToInt()
+        cache[LastBaseline] = result.lastBaseline.fastRoundToInt()
+        baselineCache = cache
+
+        return layout(
+            width = result.size.width,
+            height = result.size.height,
+            alignmentLines = baselineCache!!
+        ) {
+            placeable.place(0, 0)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt
new file mode 100644
index 0000000..2ff9d9b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.relocation.BringIntoViewRequester
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+/**
+ * Manages text layout for TextField including layout coordinates of decoration box and inner text
+ * field.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextLayoutState {
+    private var layoutCache = TextFieldLayoutStateCache()
+
+    var onTextLayout: (Density.(() -> TextLayoutResult?) -> Unit)? = null
+
+    val layoutResult: TextLayoutResult? by layoutCache
+
+    /**
+     * Measured layout coordinates of the decoration box, core text field, and text layout node.
+     *
+     * DecoratorNode
+     * -------------------
+     * |  CoreNode       |--> Outer Decoration Box with padding
+     * |  -------------  |
+     * |  |           |  |
+     * |  |           |--|--> Visible inner text field
+     * |  -------------  |    (Below the dashed line is not visible)
+     * |  |           |  |
+     * |  |           |  |
+     * -------------------
+     *    |           |
+     *    |           |---> Scrollable part (TextLayoutNode)
+     *    -------------
+     *
+     * These coordinates are used to calculate the relative positioning between multiple layers
+     * of a BasicTextField. For example, touches are processed by the decoration box but these
+     * should be converted to text layout positions to find out which character is pressed.
+     *
+     * [LayoutCoordinates] object returned from onGloballyPositioned callback is usually the same
+     * instance unless a node is detached and re-attached to the tree. To react to layout and
+     * positional changes even though the object never changes, we employ a neverEqualPolicy.
+     */
+    var textLayoutNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
+    var coreNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
+    var decoratorNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
+
+    /**
+     * Set to a non-zero value for single line TextFields in order to prevent text cuts.
+     */
+    var minHeightForSingleLineField by mutableStateOf(0.dp)
+
+    /**
+     * A [BringIntoViewRequester] that can be used to request a specific region of text be brought
+     * into view (via [TextLayoutState.bringCursorIntoView]).
+     *
+     * This requester should only be applied to the core text field node, _inside_ the internal
+     * scroll container.
+     */
+    val bringIntoViewRequester = BringIntoViewRequester()
+
+    /**
+     * Updates the [TextFieldLayoutStateCache] with inputs that don't come from the measure phase.
+     * This method will initialize the cache the first time it's called.
+     * If the new inputs require re-calculating text layout, any readers of [layoutResult] called
+     * from a snapshot observer will be invalidated.
+     *
+     * @see layoutWithNewMeasureInputs
+     */
+    fun updateNonMeasureInputs(
+        textFieldState: TransformedTextFieldState,
+        textStyle: TextStyle,
+        singleLine: Boolean,
+        softWrap: Boolean,
+    ) {
+        layoutCache.updateNonMeasureInputs(
+            textFieldState = textFieldState,
+            textStyle = textStyle,
+            singleLine = singleLine,
+            softWrap = softWrap,
+        )
+    }
+
+    /**
+     * Updates the [TextFieldLayoutStateCache] with inputs that come from the measure phase and returns the
+     * latest [TextLayoutResult]. If the measure inputs haven't changed significantly since the
+     * last call, this will be the cached result. If the new inputs require re-calculating text
+     * layout, any readers of [layoutResult] called from a snapshot observer will be invalidated.
+     *
+     * [updateNonMeasureInputs] must be called before this method to initialize the cache.
+     */
+    fun layoutWithNewMeasureInputs(
+        density: Density,
+        layoutDirection: LayoutDirection,
+        fontFamilyResolver: FontFamily.Resolver,
+        constraints: Constraints,
+    ): TextLayoutResult {
+        val layoutResult = layoutCache.layoutWithNewMeasureInputs(
+            density = density,
+            layoutDirection = layoutDirection,
+            fontFamilyResolver = fontFamilyResolver,
+            constraints = constraints,
+        )
+
+        onTextLayout?.let { onTextLayout ->
+            val textLayoutProvider = { layoutCache.value }
+            onTextLayout(density, textLayoutProvider)
+        }
+
+        return layoutResult
+    }
+
+    /**
+     * Translates the position of the touch on the screen to the position in text. Because touch
+     * is relative to the decoration box, we need to translate it to the inner text field's
+     * coordinates first before calculating position of the symbol in text.
+     *
+     * @param position original position of the gesture relative to the decoration box
+     * @param coerceInVisibleBounds if true and original [position] is outside visible bounds
+     * of the inner text field, the [position] will be shifted to the closest edge of the inner
+     * text field's visible bounds. This is useful when you have a decoration box
+     * bigger than the inner text field, so when user touches to the decoration box area, the cursor
+     * goes to the beginning or the end of the visible inner text field; otherwise if we put the
+     * cursor under the touch in the invisible part of the inner text field, it would scroll to
+     * make the cursor visible. This behavior is not needed, and therefore
+     * [coerceInVisibleBounds] should be set to false, when the user drags outside visible bounds
+     * to make a selection.
+     * @return The offset that corresponds to the [position]. Returns -1 if text layout has not
+     * been measured yet.
+     */
+    fun getOffsetForPosition(position: Offset, coerceInVisibleBounds: Boolean = true): Int {
+        val layoutResult = layoutResult ?: return -1
+        val coercedPosition = if (coerceInVisibleBounds) {
+            coercedInVisibleBoundsOfInputText(position)
+        } else {
+            position
+        }
+        val relativePosition = fromDecorationToTextLayout(coercedPosition)
+        return layoutResult.getOffsetForPosition(relativePosition)
+    }
+
+    /**
+     * Returns true if the screen coordinates position (x,y) corresponds to a character displayed
+     * in the view. Returns false when the position is in the empty space of left/right of text.
+     * This function may return true even when [offset] is below or above the text layout.
+     */
+    fun isPositionOnText(offset: Offset): Boolean {
+        val layoutResult = layoutResult ?: return false
+        val relativeOffset = fromDecorationToTextLayout(coercedInVisibleBoundsOfInputText(offset))
+        val line = layoutResult.getLineForVerticalPosition(relativeOffset.y)
+        return relativeOffset.x >= layoutResult.getLineLeft(line) &&
+            relativeOffset.x <= layoutResult.getLineRight(line)
+    }
+
+    /**
+     * If click on the decoration box happens outside visible inner text field, coerce the click
+     * position to the visible edges of the inner text field.
+     */
+    internal fun coercedInVisibleBoundsOfInputText(offset: Offset): Offset {
+        // If offset is outside visible bounds of the inner text field, use visible bounds edges
+        val visibleTextLayoutNodeRect =
+            textLayoutNodeCoordinates?.let { textLayoutNodeCoordinates ->
+                if (textLayoutNodeCoordinates.isAttached) {
+                    decoratorNodeCoordinates?.localBoundingBoxOf(textLayoutNodeCoordinates)
+                } else {
+                    Rect.Zero
+                }
+            } ?: Rect.Zero
+        return offset.coerceIn(visibleTextLayoutNodeRect)
+    }
+}
+
+internal fun Offset.coerceIn(rect: Rect): Offset {
+    val xOffset = when {
+        x < rect.left -> rect.left
+        x > rect.right -> rect.right
+        else -> x
+    }
+    val yOffset = when {
+        y < rect.top -> rect.top
+        y > rect.bottom -> rect.bottom
+        else -> y
+    }
+    return Offset(xOffset, yOffset)
+}
+
+/**
+ * Translates a position from text layout node coordinates to core node coordinates.
+ */
+internal fun TextLayoutState.fromTextLayoutToCore(offset: Offset): Offset {
+    return textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { textLayoutNodeCoordinates ->
+        coreNodeCoordinates?.takeIf { it.isAttached }?.let { coreNodeCoordinates ->
+            coreNodeCoordinates.localPositionOf(textLayoutNodeCoordinates, offset)
+        }
+    } ?: offset
+}
+
+/**
+ * Translates the click happened on the decorator node to the position in the text layout node
+ * coordinates. This relative position is then used to determine symbol position in text using
+ * TextLayoutResult object.
+ */
+internal fun TextLayoutState.fromDecorationToTextLayout(offset: Offset): Offset {
+    return textLayoutNodeCoordinates?.let { textLayoutNodeCoordinates ->
+        decoratorNodeCoordinates?.let { decoratorNodeCoordinates ->
+            if (textLayoutNodeCoordinates.isAttached && decoratorNodeCoordinates.isAttached) {
+                textLayoutNodeCoordinates.localPositionOf(decoratorNodeCoordinates, offset)
+            } else {
+                offset
+            }
+        }
+    } ?: offset
+}
+
+internal fun TextLayoutState.fromWindowToDecoration(offset: Offset): Offset {
+    return decoratorNodeCoordinates?.let { decoratorNodeCoordinates ->
+        if (decoratorNodeCoordinates.isAttached) {
+            decoratorNodeCoordinates.windowToLocal(offset)
+        } else {
+            offset
+        }
+    } ?: offset
+}
+
+/**
+ * Asks [TextLayoutState.bringIntoViewRequester] to bring the bounds of the cursor at [cursorIndex]
+ * into view.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal suspend fun TextLayoutState.bringCursorIntoView(cursorIndex: Int) {
+    val layoutResult = layoutResult ?: return
+    val rect = layoutResult.getCursorRect(cursorIndex)
+    bringIntoViewRequester.bringIntoView(rect)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.kt
new file mode 100644
index 0000000..d6cc5d3
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+/**
+ * Copies characters from this [CharSequence] into [destination].
+ *
+ * Platform-specific implementations should use native functions for performing this operation if
+ * they exist, since they will likely be more efficient than copying each character individually.
+ *
+ * @param destination The [CharArray] to copy into.
+ * @param destinationOffset The index in [destination] to start copying to.
+ * @param startIndex The index in `this` of the first character to copy from (inclusive).
+ * @param endIndex The index in `this` of the last character to copy from (exclusive).
+ */
+internal expect fun CharSequence.toCharArray(
+    destination: CharArray,
+    destinationOffset: Int,
+    startIndex: Int,
+    endIndex: Int
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
new file mode 100644
index 0000000..41b0058
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
@@ -0,0 +1,652 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Deletion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Insertion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Replacement
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Untransformed
+import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.TextRange
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * A mutable view of a [TextFieldState] where the text and selection values are transformed by an
+ * [OutputTransformation] and a [CodepointTransformation].
+ *
+ * [outputText] and [visualText] return the transformed text (see the explanation of phases below),
+ * with selection and composition mapped to the corresponding offsets from the untransformed text.
+ * The transformed text is cached in [derived states][derivedStateOf] and only recalculated when the
+ * [TextFieldState] changes or some state read by the transformation functions changes.
+ *
+ * This class defines methods for various operations that can be performed on the underlying
+ * [TextFieldState]. When possible, these methods should be used instead of editing the state
+ * directly, since this class ensures the correct offset mappings are used. If an operation is too
+ * complex to warrant a method here, use [editUntransformedTextAsUser] but be careful to make sure
+ * any offsets are mapped correctly.
+ *
+ * To map offsets from transformed to untransformed text or back, use the [mapFromTransformed] and
+ * [mapToTransformed] methods.
+ *
+ * All operations call [TextFieldState.editAsUser] internally and pass [inputTransformation].
+ *
+ * ## Text transformation phases
+ *
+ * Text is transformed in two phases:
+ * 1. The first phase applies [outputTransformation], and the resulting text, [outputText], is
+ *   published to the semantics system (consumed by a11y services and tests).
+ * 2. The second phase applies [codepointTransformation] and the resulting text, [visualText], is
+ *  laid out and drawn to the screen.
+ *
+ * Any transformations that change the semantics (in the generic sense, not Compose semantics)
+ * should be done in the first phase. Examples include adding prefixes or suffixes to the text or
+ * inserting formatting characters.
+ *
+ * The second phase should only be used for purely visual or layout transformations. Examples
+ * include password masking or inserting spaces for Scribe.
+ *
+ * In most cases, one or both phases will be noops. E.g., password fields will usually only use
+ * the second phase, and non-password fields will usually only use the first phase.
+ *
+ * Here's a diagram explaining the phases:
+ *
+ * ```
+ *  ┌──────────────────┐
+ *  │                  │
+ *  │  TextFieldState  │        "Sam"      - Semantics setSelection: relativeToOriginalText=true
+ *  │                  │
+ *  └──────────────────┘
+ *            │
+ *  OutputTransformation    s/^/Hello, /
+ *            │
+ *            ▼
+ *  ┌──────────────────┐
+ *  │                  │                   - Talkback
+ *  │   Output text    │    "Hello, Sam"   - Tests
+ *  │                  │                   - Semantics setSelection: relativeToOriginalText=false
+ *  └──────────────────┘
+ *            │
+ * CodepointTransformation     s/./•/g
+ *            │
+ *            ▼
+ *  ┌──────────────────┐
+ *  │                  │                   - Measured
+ *  │   Visual text    │    "••••••••••"   - Wrapping, ellipsis, etc.
+ *  │                  │                   - Drawn on screen
+ *  └──────────────────┘
+ * ```
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Stable
+internal class TransformedTextFieldState(
+    private val textFieldState: TextFieldState,
+    private val inputTransformation: InputTransformation? = null,
+    private val codepointTransformation: CodepointTransformation? = null,
+    private val outputTransformation: OutputTransformation? = null,
+) {
+    private val outputTransformedText: State<TransformedText?>? =
+        // Don't allocate a derived state object if we don't need it, they're expensive.
+        outputTransformation?.let { transformation ->
+            derivedStateOf {
+                // text is a state read. transformation may also perform state reads when ran.
+                calculateTransformedText(
+                    untransformedText = textFieldState.text,
+                    outputTransformation = transformation,
+                    wedgeAffinity = selectionWedgeAffinity
+                )
+            }
+        }
+
+    private val codepointTransformedText: State<TransformedText?>? =
+        codepointTransformation?.let { transformation ->
+            derivedStateOf {
+                calculateTransformedText(
+                    // These are state reads. codepointTransformation may also perform state reads
+                    // when ran.
+                    untransformedText = outputTransformedText?.value?.text ?: textFieldState.text,
+                    codepointTransformation = transformation,
+                    wedgeAffinity = selectionWedgeAffinity
+                )
+            }
+        }
+
+    /**
+     * The raw text in the underlying [TextFieldState]. This text does not have any
+     * [CodepointTransformation] applied.
+     */
+    val untransformedText: TextFieldCharSequence
+        get() = textFieldState.text
+
+    /**
+     * The text that should be presented to the user in most cases. If an [OutputTransformation] is
+     * specified, this text has the transformation applied. If there's no transformation, this will
+     * be the same as [untransformedText].
+     *
+     * See the diagram on [TransformedTextFieldState] for a graphical representation of how this
+     * value relates to [untransformedText] and [visualText].
+     */
+    val outputText: TextFieldCharSequence
+        get() = outputTransformedText?.value?.text ?: untransformedText
+
+    /**
+     * The text that should be laid out and drawn to the screen. If a [CodepointTransformation] is
+     * specified, this text has the transformation applied. If there's no transformation, this will
+     * be the same as [outputText].
+     *
+     * See the diagram on [TransformedTextFieldState] for a graphical representation of how this
+     * value relates to [untransformedText] and [outputText].
+     */
+    val visualText: TextFieldCharSequence
+        get() = codepointTransformedText?.value?.text ?: outputText
+
+    /**
+     * Indicates which side of a wedge (text inserted by the [OutputTransformation]) the start and
+     * end of the selection should map to. This allows the user to move the cursor to both sides of
+     * the wedge even though both those indices map to the same index in the untransformed text.
+     */
+    var selectionWedgeAffinity by mutableStateOf(SelectionWedgeAffinity(WedgeAffinity.Start))
+
+    fun placeCursorBeforeCharAt(transformedOffset: Int) {
+        selectCharsIn(TextRange(transformedOffset))
+    }
+
+    fun selectCharsIn(transformedRange: TextRange) {
+        val untransformedRange = mapFromTransformed(transformedRange)
+        selectUntransformedCharsIn(untransformedRange)
+    }
+
+    fun selectUntransformedCharsIn(untransformedRange: TextRange) {
+        textFieldState.editAsUser(inputTransformation) {
+            setSelection(untransformedRange.start, untransformedRange.end)
+        }
+    }
+
+    fun replaceAll(newText: CharSequence) {
+        textFieldState.editAsUser(inputTransformation) {
+            deleteAll()
+            commitText(newText.toString(), 1)
+        }
+    }
+
+    fun selectAll() {
+        textFieldState.editAsUser(inputTransformation) {
+            setSelection(0, length)
+        }
+    }
+
+    fun deleteSelectedText() {
+        textFieldState.editAsUser(
+            inputTransformation,
+            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
+        ) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            delete(selection.min, selection.max)
+            setSelection(selection.min, selection.min)
+        }
+    }
+
+    /**
+     * Replaces the text in given [range] with [newText]. Like all other methods in this class,
+     * [range] is considered to be in transformed space.
+     */
+    fun replaceText(
+        newText: CharSequence,
+        range: TextRange,
+        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible
+    ) {
+        textFieldState.editAsUser(inputTransformation, undoBehavior = undoBehavior) {
+            val selection = mapFromTransformed(range)
+            replace(
+                selection.min,
+                selection.max,
+                newText
+            )
+            val cursor = selection.min + newText.length
+            setSelection(cursor, cursor)
+        }
+    }
+
+    fun replaceSelectedText(
+        newText: CharSequence,
+        clearComposition: Boolean = false,
+        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible
+    ) {
+        textFieldState.editAsUser(inputTransformation, undoBehavior = undoBehavior) {
+            if (clearComposition) {
+                commitComposition()
+            }
+
+            // `selection` is read from the buffer, so we don't need to transform it.
+            val selection = selection
+            replace(
+                selection.min,
+                selection.max,
+                newText
+            )
+            val cursor = selection.min + newText.length
+            setSelection(cursor, cursor)
+        }
+    }
+
+    fun collapseSelectionToMax() {
+        textFieldState.editAsUser(inputTransformation) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            setSelection(selection.max, selection.max)
+        }
+    }
+
+    fun collapseSelectionToEnd() {
+        textFieldState.editAsUser(inputTransformation) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            setSelection(selection.end, selection.end)
+        }
+    }
+
+    fun undo() {
+        textFieldState.undoState.undo()
+    }
+
+    fun redo() {
+        textFieldState.undoState.redo()
+    }
+
+    /**
+     * Runs [block] with a buffer that contains the source untransformed text. This is the text that
+     * will be fed into the [outputTransformation]. Any operations performed on this buffer MUST
+     * take care to explicitly convert between transformed and untransformed offsets and ranges.
+     * When possible, use the other methods on this class to manipulate selection to avoid having
+     * to do these conversions manually.
+     *
+     * @see mapToTransformed
+     * @see mapFromTransformed
+     */
+    inline fun editUntransformedTextAsUser(
+        notifyImeOfChanges: Boolean = true,
+        block: EditingBuffer.() -> Unit
+    ) {
+        textFieldState.editAsUser(
+            inputTransformation = inputTransformation,
+            notifyImeOfChanges = notifyImeOfChanges,
+            block = block
+        )
+    }
+
+    /**
+     * Maps an [offset] in the untransformed text to the corresponding offset or range in [visualText].
+     *
+     * An untransformed offset will map to non-collapsed range if the offset is in the middle of
+     * a surrogate pair in the untransformed text, in which case it will return the range of the
+     * codepoint that the surrogate maps to. Offsets on either side of a surrogate pair will return
+     * collapsed ranges.
+     *
+     * If there is no transformation, or the transformation does not change the text, a collapsed
+     * range of [offset] will be returned.
+     *
+     * @see mapFromTransformed
+     */
+    fun mapToTransformed(offset: Int): TextRange {
+        val presentMapping = outputTransformedText?.value?.offsetMapping
+        val visualMapping = codepointTransformedText?.value?.offsetMapping
+
+        val intermediateRange = presentMapping?.mapFromSource(offset)
+            ?: TextRange(offset)
+        return visualMapping
+            ?.let { mapToTransformed(intermediateRange, it, selectionWedgeAffinity) }
+            ?: intermediateRange
+    }
+
+    /**
+     * Maps a [range] in the untransformed text to the corresponding range in [visualText].
+     *
+     * If there is no transformation, or the transformation does not change the text, [range]
+     * will be returned.
+     *
+     * @see mapFromTransformed
+     */
+    fun mapToTransformed(range: TextRange): TextRange {
+        val presentMapping = outputTransformedText?.value?.offsetMapping
+        val visualMapping = codepointTransformedText?.value?.offsetMapping
+
+        // Only apply the wedge affinity to the final range. If the first mapping returns a range,
+        // the first range should have both edges expanded by the second.
+        val intermediateRange = presentMapping
+            ?.let { mapToTransformed(range, it) }
+            ?: range
+        return visualMapping
+            ?.let { mapToTransformed(intermediateRange, it, selectionWedgeAffinity) }
+            ?: intermediateRange
+    }
+
+    /**
+     * Maps an [offset] in [visualText] to the corresponding offset in the untransformed text.
+     *
+     * Multiple transformed offsets may map to the same untransformed offset. In particular, any
+     * offset in the middle of a surrogate pair will map to offset of the corresponding codepoint
+     * in the untransformed text.
+     *
+     * If there is no transformation, or the transformation does not change the text, [offset]
+     * will be returned.
+     *
+     * @see mapToTransformed
+     */
+    fun mapFromTransformed(offset: Int): TextRange {
+        val presentMapping = outputTransformedText?.value?.offsetMapping
+        val visualMapping = codepointTransformedText?.value?.offsetMapping
+
+        val intermediateOffset = visualMapping?.mapFromDest(offset)
+            ?: TextRange(offset)
+        return presentMapping?.let { mapFromTransformed(intermediateOffset, it) }
+            ?: intermediateOffset
+    }
+
+    /**
+     * Maps a [range] in [visualText] to the corresponding range in the untransformed text.
+     *
+     * If there is no transformation, or the transformation does not change the text, [range]
+     * will be returned.
+     *
+     * @see mapToTransformed
+     */
+    fun mapFromTransformed(range: TextRange): TextRange {
+        val presentMapping = outputTransformedText?.value?.offsetMapping
+        val visualMapping = codepointTransformedText?.value?.offsetMapping
+
+        val intermediateRange = visualMapping?.let { mapFromTransformed(range, it) }
+            ?: range
+        return presentMapping?.let { mapFromTransformed(intermediateRange, it) }
+            ?: intermediateRange
+    }
+
+    // TODO(b/296583846) Get rid of this.
+    /**
+     * Adds [notifyImeListener] to the underlying [TextFieldState] and then suspends until
+     * cancelled, removing the listener before continuing.
+     */
+    suspend fun collectImeNotifications(
+        notifyImeListener: TextFieldState.NotifyImeListener
+    ): Nothing {
+        suspendCancellableCoroutine<Nothing> { continuation ->
+            textFieldState.addNotifyImeListener(notifyImeListener)
+            continuation.invokeOnCancellation {
+                textFieldState.removeNotifyImeListener(notifyImeListener)
+            }
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TransformedTextFieldState) return false
+        if (textFieldState != other.textFieldState) return false
+        if (codepointTransformation != other.codepointTransformation) return false
+        return outputTransformation == other.outputTransformation
+    }
+
+    override fun hashCode(): Int {
+        var result = textFieldState.hashCode()
+        result = 31 * result + (codepointTransformation?.hashCode() ?: 0)
+        result = 31 * result + (outputTransformation?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun toString(): String = "TransformedTextFieldState(" +
+        "textFieldState=$textFieldState, " +
+        "outputTransformation=$outputTransformation, " +
+        "outputTransformedText=$outputTransformedText, " +
+        "codepointTransformation=$codepointTransformation, " +
+        "codepointTransformedText=$codepointTransformedText, " +
+        "outputText=\"$outputText\", " +
+        "visualText=\"$visualText\"" +
+        ")"
+
+    private data class TransformedText(
+        val text: TextFieldCharSequence,
+        val offsetMapping: OffsetMappingCalculator,
+    )
+
+    private companion object {
+
+        /**
+         * Applies an [OutputTransformation] to a [TextFieldCharSequence], returning the
+         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
+         * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
+         * offsets in both directions between the transformed and untransformed text.
+         *
+         * This function is relatively expensive, since it creates a copy of [untransformedText], so
+         * its result should be cached.
+         */
+        @kotlin.jvm.JvmStatic
+        private fun calculateTransformedText(
+            untransformedText: TextFieldCharSequence,
+            outputTransformation: OutputTransformation,
+            wedgeAffinity: SelectionWedgeAffinity
+        ): TransformedText? {
+            val offsetMappingCalculator = OffsetMappingCalculator()
+            val buffer = TextFieldBuffer(
+                initialValue = untransformedText,
+                offsetMappingCalculator = offsetMappingCalculator
+            )
+
+            // This is the call to external code.
+            with(outputTransformation) { buffer.transformOutput() }
+
+            // Avoid allocations + mapping if there weren't actually any transformations.
+            if (buffer.changes.changeCount == 0) {
+                return null
+            }
+
+            val transformedTextWithSelection = buffer.toTextFieldCharSequence(
+                // Pass the calculator explicitly since the one on transformedText won't be updated
+                // yet.
+                selection = mapToTransformed(
+                    range = untransformedText.selectionInChars,
+                    mapping = offsetMappingCalculator,
+                    wedgeAffinity = wedgeAffinity
+                ),
+                composition = untransformedText.compositionInChars?.let {
+                    mapToTransformed(
+                        range = it,
+                        mapping = offsetMappingCalculator,
+                        wedgeAffinity = wedgeAffinity
+                    )
+                }
+            )
+            return TransformedText(transformedTextWithSelection, offsetMappingCalculator)
+        }
+
+        /**
+         * Applies a [CodepointTransformation] to a [TextFieldCharSequence], returning the
+         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
+         * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
+         * offsets in both directions between the transformed and untransformed text.
+         *
+         * This function is relatively expensive, since it creates a copy of [untransformedText], so
+         * its result should be cached.
+         */
+        @kotlin.jvm.JvmStatic
+        private fun calculateTransformedText(
+            untransformedText: TextFieldCharSequence,
+            codepointTransformation: CodepointTransformation,
+            wedgeAffinity: SelectionWedgeAffinity
+        ): TransformedText? {
+            val offsetMappingCalculator = OffsetMappingCalculator()
+
+            // This is the call to external code. Returns same instance if no codepoints change.
+            val transformedText =
+                untransformedText.toVisualText(codepointTransformation, offsetMappingCalculator)
+
+            // Avoid allocations + mapping if there weren't actually any transformations.
+            if (transformedText === untransformedText) {
+                return null
+            }
+
+            val transformedTextWithSelection = TextFieldCharSequence(
+                text = transformedText,
+                // Pass the calculator explicitly since the one on transformedText won't be updated
+                // yet.
+                selection = mapToTransformed(
+                    untransformedText.selectionInChars,
+                    offsetMappingCalculator,
+                    wedgeAffinity
+                ),
+                composition = untransformedText.compositionInChars?.let {
+                    mapToTransformed(it, offsetMappingCalculator, wedgeAffinity)
+                }
+            )
+            return TransformedText(transformedTextWithSelection, offsetMappingCalculator)
+        }
+
+        /**
+         * Maps [range] from untransformed to transformed indices.
+         *
+         * @param wedgeAffinity The [SelectionWedgeAffinity] to use to collapse the transformed
+         * range if necessary. If null, the range will be returned uncollapsed.
+         */
+        @kotlin.jvm.JvmStatic
+        private fun mapToTransformed(
+            range: TextRange,
+            mapping: OffsetMappingCalculator,
+            wedgeAffinity: SelectionWedgeAffinity? = null
+        ): TextRange {
+            val transformedStart = mapping.mapFromSource(range.start)
+            // Avoid calculating mapping again if it's going to be the same value.
+            val transformedEnd = if (range.collapsed) transformedStart else {
+                mapping.mapFromSource(range.end)
+            }
+
+            val transformedMin = minOf(transformedStart.min, transformedEnd.min)
+            val transformedMax = maxOf(transformedStart.max, transformedEnd.max)
+            val transformedRange = if (range.reversed) {
+                TextRange(transformedMax, transformedMin)
+            } else {
+                TextRange(transformedMin, transformedMax)
+            }
+
+            return if (range.collapsed && !transformedRange.collapsed) {
+                // In a wedge.
+                when (wedgeAffinity?.startAffinity) {
+                    WedgeAffinity.Start -> TextRange(transformedRange.start)
+                    WedgeAffinity.End -> TextRange(transformedRange.end)
+                    null -> transformedRange
+                }
+            } else {
+                transformedRange
+            }
+        }
+
+        @kotlin.jvm.JvmStatic
+        private fun mapFromTransformed(
+            range: TextRange,
+            mapping: OffsetMappingCalculator
+        ): TextRange {
+            val untransformedStart = mapping.mapFromDest(range.start)
+            // Avoid calculating mapping again if it's going to be the same value.
+            val untransformedEnd = if (range.collapsed) untransformedStart else {
+                mapping.mapFromDest(range.end)
+            }
+
+            val untransformedMin = minOf(untransformedStart.min, untransformedEnd.min)
+            val untransformedMax = maxOf(untransformedStart.max, untransformedEnd.max)
+            return if (range.reversed) {
+                TextRange(untransformedMax, untransformedMin)
+            } else {
+                TextRange(untransformedMin, untransformedMax)
+            }
+        }
+    }
+}
+
+/**
+ * Represents the [WedgeAffinity] for both sides of a selection.
+ */
+internal data class SelectionWedgeAffinity(
+    val startAffinity: WedgeAffinity,
+    val endAffinity: WedgeAffinity,
+) {
+    constructor(affinity: WedgeAffinity) : this(affinity, affinity)
+}
+
+/**
+ * Determines which side of a wedge a selection marker should be considered to be on when the marker
+ * is in a wedge. A "wedge" is a range of text that the cursor is not allowed inside. A wedge is
+ * created when an [OutputTransformation] either inserts or replaces a non-empty string.
+ */
+internal enum class WedgeAffinity {
+    Start, End
+}
+
+internal enum class IndexTransformationType {
+    Untransformed,
+    Insertion,
+    Replacement,
+    Deletion
+}
+
+/**
+ * Determines if the [transformedQueryIndex] is inside an insertion, replacement, deletion, or none
+ * of the above as specified by the transformations on this [TransformedTextFieldState].
+ *
+ * This function uses continuation-passing style to return multiple values without allocating.
+ *
+ * @param onResult Called with the determined [IndexTransformationType] and the ranges that
+ * [transformedQueryIndex] maps to both in the [TransformedTextFieldState.untransformedText] and
+ * when that range is mapped back into the [TransformedTextFieldState.visualText].
+ */
+internal inline fun <R> TransformedTextFieldState.getIndexTransformationType(
+    transformedQueryIndex: Int,
+    onResult: (
+        IndexTransformationType,
+        untransformed: TextRange,
+        retransformed: TextRange
+    ) -> R
+): R {
+    val untransformed = mapFromTransformed(transformedQueryIndex)
+    val retransformed = mapToTransformed(untransformed)
+    val type = when {
+        untransformed.collapsed && retransformed.collapsed -> {
+            // Simple case: no transformation in effect.
+            Untransformed
+        }
+
+        !untransformed.collapsed && !retransformed.collapsed -> {
+            // Replacement: An non-empty range in the source was replaced with a non-empty string.
+            Replacement
+        }
+
+        untransformed.collapsed && !retransformed.collapsed -> {
+            // Insertion: An empty range in the source was replaced with a non-empty range.
+            Insertion
+        }
+
+        else /* !untransformed.collapsed && retransformed.collapsed */ -> {
+            // Deletion: A non-empty range in the source was replaced with an empty string.
+            Deletion
+        }
+    }
+    return onResult(type, untransformed, retransformed)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownGesture.kt
new file mode 100644
index 0000000..459b62c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/PressDownGesture.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+
+/**
+ * Detects pointer down and up events. This detector does not require events to be unconsumed.
+ */
+internal suspend fun PointerInputScope.detectPressDownGesture(
+    onDown: TapOnPosition,
+    onUp: (() -> Unit)? = null
+) {
+    awaitEachGesture {
+        val down = awaitFirstDown(requireUnconsumed = false)
+        onDown.onEvent(down.position)
+
+        if (onUp != null) {
+            // Wait for that pointer to come up.
+            do {
+                val event = awaitPointerEvent()
+            } while (event.changes.fastAny { it.id == down.id && it.pressed })
+            onUp.invoke()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapGesture.kt
new file mode 100644
index 0000000..b335ca4
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TapAndDoubleTapGesture.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Detect tap and double tap gestures. This is a special gesture detector that's copied from
+ * [PointerInputScope.detectTapGestures] to always call [onTap] even when a double tap is detected.
+ * In that case both [onTap] and [onDoubleTap] are called successively. However, if there is a long
+ * tap, neither of the callbacks are called.
+ */
+internal suspend fun PointerInputScope.detectTapAndDoubleTap(
+    onTap: TapOnPosition? = null,
+    onDoubleTap: TapOnPosition? = null,
+) = coroutineScope {
+    awaitEachGesture {
+        val down = awaitFirstDown()
+        down.consume()
+        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+        var upOrCancel: PointerInputChange? = null
+        try {
+            // wait for first tap up or long press
+            upOrCancel = withTimeout(longPressTimeout) {
+                waitForUpOrCancellation()
+            }
+        } catch (_: PointerEventTimeoutCancellationException) {
+            consumeUntilUp()
+        }
+
+        if (upOrCancel != null) {
+            upOrCancel.consume()
+            // Tap was successful. There was no long click. Now evaluate whether it's gonna be
+            // a double tap.
+            onTap?.onEvent(upOrCancel.position)
+            if (onDoubleTap != null) {
+                // check for second tap
+                val secondDown = awaitSecondDown(upOrCancel)
+
+                if (secondDown != null) {
+                    try {
+                        // Might have a long second press as the second tap
+                        withTimeout(longPressTimeout) {
+                            val secondUp = waitForUpOrCancellation()
+                            if (secondUp != null) {
+                                secondUp.consume()
+                                onDoubleTap.onEvent(secondUp.position)
+                            }
+                        }
+                    } catch (e: PointerEventTimeoutCancellationException) {
+                        consumeUntilUp()
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Waits for [ViewConfiguration.doubleTapTimeoutMillis] for a second press event. If a
+ * second press event is received before the time out, it is returned or `null` is returned
+ * if no second press is received.
+ */
+private suspend fun AwaitPointerEventScope.awaitSecondDown(
+    firstUp: PointerInputChange
+): PointerInputChange? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) {
+    val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
+    var change: PointerInputChange
+    // The second tap doesn't count if it happens before DoubleTapMinTime of the first tap
+    do {
+        change = awaitFirstDown()
+    } while (change.uptimeMillis < minUptime)
+    change
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes
+ * that something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp() {
+    do {
+        val event = awaitPointerEvent()
+        event.changes.fastForEach { it.consume() }
+    } while (event.changes.fastAny { it.pressed })
+}
+
+internal fun interface TapOnPosition {
+    fun onEvent(offset: Offset)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldHandleState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldHandleState.kt
new file mode 100644
index 0000000..8b9a57f
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldHandleState.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.text.style.ResolvedTextDirection
+
+/**
+ * Defines how to render a selection or cursor handle on a TextField.
+ */
+internal data class TextFieldHandleState(
+    val visible: Boolean,
+    val position: Offset,
+    val direction: ResolvedTextDirection,
+    val handlesCrossed: Boolean
+) {
+    companion object {
+        val Hidden = TextFieldHandleState(
+            visible = false,
+            position = Offset.Unspecified,
+            direction = ResolvedTextDirection.Ltr,
+            handlesCrossed = false
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
new file mode 100644
index 0000000..8ac8c84
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.input.internal.TextLayoutState
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+import androidx.compose.foundation.text.input.internal.coerceIn
+import androidx.compose.foundation.text.input.internal.fromTextLayoutToCore
+import androidx.compose.foundation.text.selection.visibleBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.OnGloballyPositionedModifier
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.absoluteValue
+
+internal abstract class TextFieldMagnifierNode : DelegatingNode(),
+    OnGloballyPositionedModifier,
+    DrawModifierNode,
+    SemanticsModifierNode {
+
+    abstract fun update(
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        textLayoutState: TextLayoutState,
+        visible: Boolean
+    )
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {}
+
+    override fun ContentDrawScope.draw() {}
+
+    override fun SemanticsPropertyReceiver.applySemantics() {}
+}
+
+@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
+internal expect fun textFieldMagnifierNode(
+    textFieldState: TransformedTextFieldState,
+    textFieldSelectionState: TextFieldSelectionState,
+    textLayoutState: TextLayoutState,
+    visible: Boolean
+): TextFieldMagnifierNode
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun calculateSelectionMagnifierCenterAndroid(
+    textFieldState: TransformedTextFieldState,
+    selectionState: TextFieldSelectionState,
+    textLayoutState: TextLayoutState,
+    magnifierSize: IntSize
+): Offset {
+    // state read of currentDragPosition so that we always recompose on drag position changes
+    val localDragPosition = selectionState.handleDragPosition
+
+    // Do not show the magnifier if origin position is already Unspecified.
+    // Never show the magnifier in an empty text field.
+    if (localDragPosition.isUnspecified || textFieldState.visualText.isEmpty()) {
+        return Offset.Unspecified
+    }
+
+    val selection = textFieldState.visualText.selectionInChars
+    val textOffset = when (selectionState.draggingHandle) {
+        null -> return Offset.Unspecified
+        Handle.Cursor,
+        Handle.SelectionStart -> selection.start
+        Handle.SelectionEnd -> selection.end
+    }
+
+    // If the text hasn't been laid out yet, don't show the modifier.
+    val layoutResult = textLayoutState.layoutResult ?: return Offset.Unspecified
+
+    val dragX = localDragPosition.x
+    val line = layoutResult.getLineForOffset(textOffset)
+    val lineStart = layoutResult.getLineLeft(line)
+    val lineEnd = layoutResult.getLineRight(line)
+    val lineMin = minOf(lineStart, lineEnd)
+    val lineMax = maxOf(lineStart, lineEnd)
+    val centerX = dragX.coerceIn(lineMin, lineMax)
+
+    // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
+    // magnifier actually is). See
+    // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
+    if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+        return Offset.Unspecified
+    }
+
+    // Center vertically on the current line.
+    val top = layoutResult.getLineTop(line)
+    val bottom = layoutResult.getLineBottom(line)
+    val centerY = ((bottom - top) / 2) + top
+
+    var offset = Offset(centerX, centerY)
+    textLayoutState.textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { innerCoordinates ->
+        offset = offset.coerceIn(innerCoordinates.visibleBounds())
+    }
+    return textLayoutState.fromTextLayoutToCore(offset)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt
new file mode 100644
index 0000000..34f2cc4
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt
@@ -0,0 +1,1364 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.TransferableContent
+import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
+import androidx.compose.foundation.content.readPlainText
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.text.DefaultCursorThickness
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.getSelectedText
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Deletion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Insertion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Replacement
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Untransformed
+import androidx.compose.foundation.text.input.internal.SelectionWedgeAffinity
+import androidx.compose.foundation.text.input.internal.TextFieldDecoratorModifierNode
+import androidx.compose.foundation.text.input.internal.TextLayoutState
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+import androidx.compose.foundation.text.input.internal.WedgeAffinity
+import androidx.compose.foundation.text.input.internal.coerceIn
+import androidx.compose.foundation.text.input.internal.findClosestRect
+import androidx.compose.foundation.text.input.internal.fromDecorationToTextLayout
+import androidx.compose.foundation.text.input.internal.getIndexTransformationType
+import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.Cursor
+import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.None
+import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.Selection
+import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
+import androidx.compose.foundation.text.selection.SelectionAdjustment
+import androidx.compose.foundation.text.selection.SelectionLayout
+import androidx.compose.foundation.text.selection.containsInclusive
+import androidx.compose.foundation.text.selection.getAdjustedCoordinates
+import androidx.compose.foundation.text.selection.getSelectionHandleCoordinates
+import androidx.compose.foundation.text.selection.getTextFieldSelectionLayout
+import androidx.compose.foundation.text.selection.isPrecisePointer
+import androidx.compose.foundation.text.selection.visibleBounds
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextFieldSelectionState(
+    private val textFieldState: TransformedTextFieldState,
+    private val textLayoutState: TextLayoutState,
+    private var density: Density,
+    private var enabled: Boolean,
+    private var readOnly: Boolean,
+    var isFocused: Boolean, /* true iff component is focused and the window is focused */
+) {
+    /**
+     * [HapticFeedback] handle to perform haptic feedback.
+     */
+    private var hapticFeedBack: HapticFeedback? = null
+
+    /**
+     * [TextToolbar] to show floating toolbar(post-M) or primary toolbar(pre-M).
+     */
+    private var textToolbar: TextToolbar? = null
+
+    /**
+     * [ClipboardManager] to perform clipboard features.
+     */
+    private var clipboardManager: ClipboardManager? = null
+
+    /**
+     * Whether user is interacting with the UI in touch mode.
+     */
+    var isInTouchMode: Boolean by mutableStateOf(true)
+        private set
+
+    /**
+     * Reduced [ReceiveContentConfiguration] from the attached modifier node hierarchy. This value
+     * is set by [TextFieldDecoratorModifierNode].
+     */
+    var receiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
+
+    /**
+     * The offset of visible bounds when dragging is started by a cursor or a selection handle.
+     * Total drag value needs to account for any auto scrolling that happens during dragging of a
+     * handle.
+     * This value is an anchor to calculate how much the visible bounds have shifted as the
+     * dragging continues. If a cursor or a selection handle is not dragging, this value needs to be
+     * [Offset.Unspecified]. This includes long press and drag gesture defined on TextField.
+     */
+    private var startContentVisibleOffset by mutableStateOf(Offset.Unspecified)
+
+    /**
+     * Calculates the offset of currently visible bounds.
+     */
+    private val currentContentVisibleOffset: Offset
+        get() = textLayoutCoordinates
+            ?.visibleBounds()
+            ?.topLeft ?: Offset.Unspecified
+
+    /**
+     * Current drag position of a handle for magnifier to read. Only one handle can be dragged
+     * at one time. This uses raw position as in only gesture start position and delta are used to
+     * calculate it. If auto-scroll happens due to selection changes while the gesture is active,
+     * it is not reflected on this value. See [handleDragPosition] for such a behavior.
+     *
+     * This value can reflect the drag position of either a real handle like cursor or selection or
+     * an acting handle when long press dragging happens directly on the text field. However, these
+     * two systems (real and acting handles) use different coordinate systems. When real handles
+     * set this value, they send inner text field coordinates. On the other hand, long press and
+     * drag gesture defined on text field would send coordinates in the decoration coordinates.
+     */
+    private var rawHandleDragPosition by mutableStateOf(Offset.Unspecified)
+
+    /**
+     * Defines where the handle exactly is in text layout node coordinates. This is mainly used by
+     * Magnifier to anchor itself. Also, it provides an updated total drag value to cursor and
+     * selection handles to continue scrolling as they are dragged outside the visible bounds.
+     *
+     * This value is primarily used by Magnifier and any handle dragging gesture detector. Since
+     * these calculations use inner text field coordinates, [handleDragPosition] is also always
+     * represented in the same coordinate system.
+     */
+    val handleDragPosition: Offset
+        get() = when {
+            // nothing is being dragged.
+            rawHandleDragPosition.isUnspecified -> {
+                Offset.Unspecified
+            }
+            // no real handle is being dragged, we need to offset the drag position by current
+            // inner-decorator relative positioning.
+            startContentVisibleOffset.isUnspecified -> {
+                textLayoutState.fromDecorationToTextLayout(rawHandleDragPosition)
+            }
+            // a cursor or a selection handle is being dragged, offset by comparing the current
+            // and starting visible offsets.
+            else -> {
+                rawHandleDragPosition + currentContentVisibleOffset - startContentVisibleOffset
+            }
+        }
+
+    /**
+     * Which selection handle is currently being dragged.
+     */
+    var draggingHandle by mutableStateOf<Handle?>(null)
+
+    /**
+     * Whether to show the cursor handle below cursor indicator when the TextField is focused.
+     */
+    private var showCursorHandle by mutableStateOf(false)
+
+    /**
+     * Whether to show the TextToolbar according to current selection state. This is not the final
+     * decider for showing the toolbar. Please refer to [observeTextToolbarVisibility] docs.
+     */
+    private var textToolbarState by mutableStateOf(TextToolbarState.None)
+
+    /**
+     * Access helper for text layout node coordinates that checks attached state.
+     */
+    private val textLayoutCoordinates: LayoutCoordinates?
+        get() = textLayoutState.textLayoutNodeCoordinates?.takeIf { it.isAttached }
+
+    /**
+     * Whether the contents of this TextField can be changed by the user.
+     */
+    private val editable: Boolean
+        get() = enabled && !readOnly
+
+    /**
+     * The most recent [SelectionLayout] that passed the [SelectionLayout.shouldRecomputeSelection]
+     * check. Provides context to the next selection update such as if the selection is shrinking
+     * or not.
+     */
+    private var previousSelectionLayout: SelectionLayout? = null
+
+    /**
+     * The previous offset of a drag, before selection adjustments.
+     * Only update when a selection layout change has occurred,
+     * or set to -1 if a new drag begins.
+     */
+    private var previousRawDragOffset: Int = -1
+
+    /**
+     * State of the cursor handle that includes its visibility and position.
+     */
+    val cursorHandle by derivedStateOf {
+        // For cursor handle to be visible, [showCursorHandle] must be true and the selection
+        // must be collapsed.
+        // Also, cursor handle should be in visible bounds of the TextField. However, if
+        // cursor is dragging and gets out of bounds, we cannot remove it from composition
+        // because that would stop the drag gesture defined on it. Instead, we allow the handle
+        // to be visible as long as it's being dragged.
+        // Visible bounds calculation lags one frame behind to let auto-scrolling settle.
+        val text = textFieldState.visualText
+        val visible = showCursorHandle &&
+            text.selectionInChars.collapsed &&
+            text.isNotEmpty() &&
+            (draggingHandle == Handle.Cursor || cursorHandleInBounds)
+
+        if (!visible) return@derivedStateOf TextFieldHandleState.Hidden
+
+        // text direction is useless for cursor handle, any value is fine.
+        TextFieldHandleState(
+            visible = true,
+            position = cursorRect.bottomCenter,
+            direction = ResolvedTextDirection.Ltr,
+            handlesCrossed = false
+        )
+    }
+
+    /**
+     * Whether currently cursor handle is in visible bounds. This derived state does not react to
+     * selection changes immediately because every selection change is processed in layout phase
+     * by auto-scroll behavior. Only after giving auto-scroll time to process the cursor movement,
+     * and possibly scroll the cursor back into view, we can say that whether cursor is in visible
+     * bounds or not. This is guaranteed to happen after scroll since new [textLayoutCoordinates]
+     * are reported after the layout phase end.
+     */
+    private val cursorHandleInBounds by derivedStateOf(policy = structuralEqualityPolicy()) {
+        val position = Snapshot.withoutReadObservation { cursorRect.bottomCenter }
+
+        textLayoutCoordinates
+            ?.visibleBounds()
+            ?.containsInclusive(position)
+            ?: false
+    }
+
+    /**
+     * Where the cursor should be at any given time in core node coordinates.
+     *
+     * Returns [Rect.Zero] if text layout has not been calculated yet or the selection is not
+     * collapsed (no cursor to locate).
+     */
+    val cursorRect: Rect by derivedStateOf {
+        val layoutResult = textLayoutState.layoutResult ?: return@derivedStateOf Rect.Zero
+        val value = textFieldState.visualText
+        if (!value.selectionInChars.collapsed) return@derivedStateOf Rect.Zero
+        val cursorRect = layoutResult.getCursorRect(value.selectionInChars.start)
+
+        val cursorWidth = with(density) { DefaultCursorThickness.toPx() }
+        // left and right values in cursorRect should be the same but in any case use the
+        // logically correct anchor.
+        val cursorCenterX = if (layoutResult.layoutInput.layoutDirection == LayoutDirection.Ltr) {
+            (cursorRect.left + cursorWidth / 2)
+        } else {
+            (cursorRect.right - cursorWidth / 2)
+        }
+
+        // don't let cursor go beyond the bounds of text layout node or cursor will be clipped.
+        // but also make sure that empty Text Layout still draws a cursor.
+        val coercedCursorCenterX = cursorCenterX
+            // do not use coerceIn because it is not guaranteed that minimum value is smaller
+            // than the maximum value.
+            .coerceAtMost(layoutResult.size.width - cursorWidth / 2)
+            .coerceAtLeast(cursorWidth / 2)
+
+        Rect(
+            left = coercedCursorCenterX - cursorWidth / 2,
+            right = coercedCursorCenterX + cursorWidth / 2,
+            top = cursorRect.top,
+            bottom = cursorRect.bottom
+        )
+    }
+
+    fun update(
+        hapticFeedBack: HapticFeedback,
+        clipboardManager: ClipboardManager,
+        textToolbar: TextToolbar,
+        density: Density,
+        enabled: Boolean,
+        readOnly: Boolean,
+    ) {
+        if (!enabled) {
+            hideTextToolbar()
+        }
+        this.hapticFeedBack = hapticFeedBack
+        this.clipboardManager = clipboardManager
+        this.textToolbar = textToolbar
+        this.density = density
+        this.enabled = enabled
+        this.readOnly = readOnly
+    }
+
+    /**
+     * Implements the complete set of gestures supported by the cursor handle.
+     */
+    suspend fun PointerInputScope.cursorHandleGestures() {
+        coroutineScope {
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTouchMode()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectCursorHandleDragGestures()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTapGestures(onTap = {
+                    textToolbarState = if (textToolbarState == TextToolbarState.Cursor) {
+                        TextToolbarState.None
+                    } else {
+                        TextToolbarState.Cursor
+                    }
+                })
+            }
+        }
+    }
+
+    /**
+     * Implements the complete set of gestures supported by the TextField area.
+     */
+    suspend fun PointerInputScope.textFieldGestures(
+        requestFocus: () -> Unit,
+        showKeyboard: () -> Unit
+    ) {
+        coroutineScope {
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTouchMode()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTextFieldTapGestures(requestFocus, showKeyboard)
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTextFieldLongPressAndAfterDrag(requestFocus)
+            }
+        }
+    }
+
+    /**
+     * Gesture detector for dragging the selection handles to change the selection in TextField.
+     */
+    suspend fun PointerInputScope.selectionHandleGestures(isStartHandle: Boolean) {
+        coroutineScope {
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTouchMode()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectPressDownGesture(
+                    onDown = {
+                        markStartContentVisibleOffset()
+                        updateHandleDragging(
+                            handle = if (isStartHandle) {
+                                Handle.SelectionStart
+                            } else {
+                                Handle.SelectionEnd
+                            },
+                            position = getAdjustedCoordinates(getHandlePosition(isStartHandle))
+                        )
+                    },
+                    onUp = {
+                        clearHandleDragging()
+                    }
+                )
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectSelectionHandleDragGestures(isStartHandle)
+            }
+        }
+    }
+
+    /**
+     * Starts observing changes in the current state for reactive rules. For example, the cursor
+     * handle or the selection handles should hide whenever the text content changes.
+     */
+    suspend fun observeChanges() {
+        try {
+            coroutineScope {
+                launch { observeTextChanges() }
+                launch { observeTextToolbarVisibility() }
+            }
+        } finally {
+            showCursorHandle = false
+            if (textToolbarState != TextToolbarState.None) {
+                hideTextToolbar()
+            }
+        }
+    }
+
+    fun updateTextToolbarState(textToolbarState: TextToolbarState) {
+        this.textToolbarState = textToolbarState
+    }
+
+    fun dispose() {
+        hideTextToolbar()
+
+        textToolbar = null
+        clipboardManager = null
+        hapticFeedBack = null
+    }
+
+    /**
+     * Detects the current pointer type in this [PointerInputScope] to update the touch mode state.
+     * This helper gesture detector should be added to all TextField pointer input receivers such
+     * as TextFieldDecorator, cursor handle, and selection handles.
+     */
+    private suspend fun PointerInputScope.detectTouchMode() {
+        awaitPointerEventScope {
+            while (true) {
+                val event = awaitPointerEvent(PointerEventPass.Initial)
+                isInTouchMode = !event.isPrecisePointer
+            }
+        }
+    }
+
+    private suspend fun PointerInputScope.detectTextFieldTapGestures(
+        requestFocus: () -> Unit,
+        showKeyboard: () -> Unit
+    ) {
+        detectTapAndDoubleTap(
+            onTap = { offset ->
+                logDebug { "onTapTextField" }
+                requestFocus()
+
+                if (editable && isFocused) {
+                    showKeyboard()
+                    if (textFieldState.visualText.isNotEmpty()) {
+                        showCursorHandle = true
+                    }
+
+                    // do not show any TextToolbar.
+                    updateTextToolbarState(TextToolbarState.None)
+
+                    val coercedOffset = textLayoutState.coercedInVisibleBoundsOfInputText(offset)
+
+                    placeCursorAtNearestOffset(
+                        textLayoutState.fromDecorationToTextLayout(coercedOffset)
+                    )
+                }
+            },
+            onDoubleTap = { offset ->
+                logDebug { "onDoubleTapTextField" }
+                // onTap is already called at this point. Focus is requested.
+
+                showCursorHandle = false
+                // go into selection mode.
+                updateTextToolbarState(TextToolbarState.Selection)
+
+                val index = textLayoutState.getOffsetForPosition(offset)
+                val newSelection = updateSelection(
+                    // reset selection, otherwise a previous selection may be used
+                    // as context for creating the next selection
+                    textFieldCharSequence = TextFieldCharSequence(
+                        textFieldState.visualText,
+                        TextRange.Zero
+                    ),
+                    startOffset = index,
+                    endOffset = index,
+                    isStartHandle = false,
+                    adjustment = SelectionAdjustment.Word,
+                )
+                textFieldState.selectCharsIn(newSelection)
+            }
+        )
+    }
+
+    /**
+     * Calculates the valid cursor position nearest to [offset] and sets the cursor to it.
+     * Takes into account text transformations ([TransformedTextFieldState]) to avoid putting the
+     * cursor in the middle of replacements.
+     *
+     * If the cursor would end up in the middle of an insertion or replacement, it is instead pushed
+     * to the nearest edge of the wedge to the [offset].
+     *
+     * @param offset Where the cursor is in text layout coordinates. If the caller has the offset
+     * in decorator coordinates, [TextLayoutState.fromDecorationToTextLayout] can be used to convert
+     * between the two spaces.
+     * @return true if the cursor moved, false if the cursor position did not need to change.
+     */
+    private fun placeCursorAtNearestOffset(offset: Offset): Boolean {
+        val layoutResult = textLayoutState.layoutResult ?: return false
+
+        // First step: calculate the proposed cursor index.
+        val index = layoutResult.getOffsetForPosition(offset)
+        if (index == -1) return false
+
+        // Second step: if a transformation is applied, determine if the proposed cursor position
+        // would be in a range where the cursor is not allowed to be. If so, push it to the
+        // appropriate edge of that range.
+        var newAffinity: SelectionWedgeAffinity? = null
+        val untransformedCursor =
+            textFieldState.getIndexTransformationType(index) { type, untransformed, retransformed ->
+                when (type) {
+                    Untransformed -> untransformed.start
+
+                    // Deletion. Doesn't matter which end of the deleted range we put the cursor,
+                    // they'll both map to the same transformed offset.
+                    Deletion -> untransformed.start
+
+                    // The untransformed offset will be the same no matter which side we put the
+                    // cursor on, so we need to set the affinity to the closer edge.
+                    Insertion -> {
+                        val wedgeStartCursorRect = layoutResult.getCursorRect(retransformed.start)
+                        val wedgeEndCursorRect = layoutResult.getCursorRect(retransformed.end)
+                        newAffinity = if (offset.findClosestRect(
+                                wedgeStartCursorRect,
+                                wedgeEndCursorRect
+                            ) < 0
+                        ) {
+                            SelectionWedgeAffinity(WedgeAffinity.Start)
+                        } else {
+                            SelectionWedgeAffinity(WedgeAffinity.End)
+                        }
+                        untransformed.start
+                    }
+
+                    // Set the untransformed cursor to the edge that corresponds to the closer edge
+                    // in the transformed text.
+                    Replacement -> {
+                        val wedgeStartCursorRect = layoutResult.getCursorRect(retransformed.start)
+                        val wedgeEndCursorRect = layoutResult.getCursorRect(retransformed.end)
+                        if (offset.findClosestRect(wedgeStartCursorRect, wedgeEndCursorRect) < 0) {
+                            untransformed.start
+                        } else {
+                            untransformed.end
+                        }
+                    }
+                }
+            }
+        val untransformedCursorRange = TextRange(untransformedCursor)
+
+        // Nothing changed, skip onValueChange and hapticFeedback.
+        if (untransformedCursorRange == textFieldState.untransformedText.selectionInChars &&
+            (newAffinity == null || newAffinity == textFieldState.selectionWedgeAffinity)
+        ) {
+            return false
+        }
+
+        textFieldState.selectUntransformedCharsIn(untransformedCursorRange)
+        newAffinity?.let {
+            textFieldState.selectionWedgeAffinity = it
+        }
+        return true
+    }
+
+    private suspend fun PointerInputScope.detectCursorHandleDragGestures() {
+        var cursorDragStart = Offset.Unspecified
+        var cursorDragDelta = Offset.Unspecified
+
+        fun onDragStop() {
+            // Only execute clear-up if drag was actually ongoing.
+            if (cursorDragStart.isSpecified) {
+                cursorDragStart = Offset.Unspecified
+                cursorDragDelta = Offset.Unspecified
+                clearHandleDragging()
+            }
+        }
+
+        // b/288931376: detectDragGestures do not call onDragCancel when composable is disposed.
+        try {
+            detectDragGestures(
+                onDragStart = {
+                    // mark start drag point
+                    cursorDragStart = getAdjustedCoordinates(cursorRect.bottomCenter)
+                    cursorDragDelta = Offset.Zero
+                    isInTouchMode = true
+                    markStartContentVisibleOffset()
+                    updateHandleDragging(Handle.Cursor, cursorDragStart)
+                },
+                onDragEnd = { onDragStop() },
+                onDragCancel = { onDragStop() },
+                onDrag = onDrag@{ change, dragAmount ->
+                    cursorDragDelta += dragAmount
+
+                    updateHandleDragging(Handle.Cursor, cursorDragStart + cursorDragDelta)
+
+                    if (placeCursorAtNearestOffset(handleDragPosition)) {
+                        change.consume()
+                        // TODO: only perform haptic feedback if filter does not override the change
+                        hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                    }
+                }
+            )
+        } finally {
+            onDragStop()
+        }
+    }
+
+    private suspend fun PointerInputScope.detectTextFieldLongPressAndAfterDrag(
+        requestFocus: () -> Unit
+    ) {
+        var dragBeginOffsetInText = -1
+        var dragBeginPosition: Offset = Offset.Unspecified
+        var dragTotalDistance: Offset = Offset.Zero
+        var actingHandle: Handle = Handle.SelectionEnd // start with a placeholder.
+
+        fun onDragStop() {
+            // Only execute clear-up if drag was actually ongoing.
+            if (dragBeginPosition.isSpecified) {
+                clearHandleDragging()
+                dragBeginOffsetInText = -1
+                dragBeginPosition = Offset.Unspecified
+                dragTotalDistance = Offset.Zero
+                previousRawDragOffset = -1
+            }
+        }
+
+        // offsets received by this gesture detector are in decoration box coordinates
+        detectDragGesturesAfterLongPress(
+            onDragStart = onDragStart@{ dragStartOffset ->
+                logDebug { "onDragStart after longPress $dragStartOffset" }
+                requestFocus()
+
+                // this gesture detector is applied on the decoration box. We do not need to
+                // convert the gesture offset, that's going to be calculated by [handleDragPosition]
+                updateHandleDragging(
+                    handle = actingHandle,
+                    position = dragStartOffset
+                )
+
+                dragBeginPosition = dragStartOffset
+                dragTotalDistance = Offset.Zero
+                previousRawDragOffset = -1
+
+                // Long Press at the blank area, the cursor should show up at the end of the line.
+                if (!textLayoutState.isPositionOnText(dragStartOffset)) {
+                    val offset = textLayoutState.getOffsetForPosition(dragStartOffset)
+
+                    hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                    textFieldState.placeCursorBeforeCharAt(offset)
+                    showCursorHandle = true
+                    updateTextToolbarState(TextToolbarState.Cursor)
+                } else {
+                    if (textFieldState.visualText.isEmpty()) return@onDragStart
+                    val offset = textLayoutState.getOffsetForPosition(dragStartOffset)
+                    val newSelection = updateSelection(
+                        // reset selection, otherwise a previous selection may be used
+                        // as context for creating the next selection
+                        textFieldCharSequence = TextFieldCharSequence(
+                            textFieldState.visualText,
+                            TextRange.Zero
+                        ),
+                        startOffset = offset,
+                        endOffset = offset,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                    )
+                    textFieldState.selectCharsIn(newSelection)
+                    updateTextToolbarState(TextToolbarState.Selection)
+                    // For touch, set the begin offset to the adjusted selection.
+                    // When char based selection is used, we want to ensure we snap the
+                    // beginning offset to the start word boundary of the first selected word.
+                    dragBeginOffsetInText = newSelection.start
+                }
+            },
+            onDragEnd = { onDragStop() },
+            onDragCancel = { onDragStop() },
+            onDrag = onDrag@{ _, dragAmount ->
+                // selection never started, did not consume any drag
+                if (textFieldState.visualText.isEmpty()) return@onDrag
+
+                dragTotalDistance += dragAmount
+
+                // "start position + total delta" is not enough to understand the current
+                // pointer position relative to text layout. We need to also account for any
+                // changes to visible offset that's caused by auto-scrolling while dragging.
+                val currentDragPosition = dragBeginPosition + dragTotalDistance
+
+                logDebug { "onDrag after longPress $currentDragPosition" }
+
+                val startOffset: Int
+                val endOffset: Int
+                val adjustment: SelectionAdjustment
+
+                if (
+                    dragBeginOffsetInText < 0 && // drag started in end padding
+                    !textLayoutState.isPositionOnText(currentDragPosition) // still in end padding
+                ) {
+                    startOffset = textLayoutState.getOffsetForPosition(dragBeginPosition)
+                    endOffset = textLayoutState.getOffsetForPosition(currentDragPosition)
+
+                    adjustment = if (startOffset == endOffset) {
+                        // start and end is in the same end padding, keep the collapsed selection
+                        SelectionAdjustment.None
+                    } else {
+                        SelectionAdjustment.Word
+                    }
+                } else {
+                    startOffset = dragBeginOffsetInText.takeIf { it >= 0 }
+                        ?: textLayoutState.getOffsetForPosition(
+                            position = dragBeginPosition,
+                            coerceInVisibleBounds = false
+                        )
+                    endOffset = textLayoutState.getOffsetForPosition(
+                        position = currentDragPosition,
+                        coerceInVisibleBounds = false
+                    )
+
+                    if (dragBeginOffsetInText < 0 && startOffset == endOffset) {
+                        // if we are selecting starting from end padding,
+                        // don't start selection until we have and un-collapsed selection.
+                        return@onDrag
+                    }
+
+                    adjustment = SelectionAdjustment.Word
+                }
+
+                val prevSelection = textFieldState.visualText.selectionInChars
+                var newSelection = updateSelection(
+                    textFieldCharSequence = textFieldState.visualText,
+                    startOffset = startOffset,
+                    endOffset = endOffset,
+                    isStartHandle = false,
+                    adjustment = adjustment,
+                    allowPreviousSelectionCollapsed = false,
+                )
+
+                // Although we support reversed selection, reversing the selection after it's
+                // initiated via long press has a visual glitch that's hard to get rid of. When
+                // handles (start/end) switch places after the selection reverts, draw happens a
+                // bit late, making it obvious that selection handles switched places. We simply do
+                // not allow reversed selection during long press drag.
+                if (newSelection.reversed) {
+                    newSelection = newSelection.reverse()
+                }
+
+                // When drag starts from the end padding, we eventually need to update the start
+                // point once a selection is initiated. Otherwise, startOffset is always calculated
+                // from dragBeginPosition which can refer to different positions on text if
+                // TextField starts scrolling.
+                if (dragBeginOffsetInText == -1 && !newSelection.collapsed) {
+                    dragBeginOffsetInText = newSelection.start
+                }
+
+                // if the new selection is not equal to previous selection, consider updating the
+                // acting handle. Otherwise, acting handle should remain the same.
+                if (newSelection != prevSelection) {
+                    // Find the growing direction of selection
+                    // - Since we do not allow reverse selection,
+                    //   - selection.start == selection.min
+                    //   - selection.end == selection.max
+                    // - If only start or end changes ([A, B] => [A, C]; [C, E] => [D, E])
+                    //   - acting handle is the changing handle.
+                    // - If both change, find the middle point and see how it moves.
+                    //   - If middle point moves right, acting handle is SelectionEnd
+                    //   - Otherwise, acting handle is SelectionStart
+                    actingHandle = when {
+                        newSelection.start != prevSelection.start &&
+                            newSelection.end == prevSelection.end -> Handle.SelectionStart
+                        newSelection.start == prevSelection.start &&
+                            newSelection.end != prevSelection.end -> Handle.SelectionEnd
+                        else -> {
+                            val newMiddle = (newSelection.start + newSelection.end) / 2f
+                            val prevMiddle = (prevSelection.start + prevSelection.end) / 2f
+                            if (newMiddle > prevMiddle) {
+                                Handle.SelectionEnd
+                            } else {
+                                Handle.SelectionStart
+                            }
+                        }
+                    }
+                }
+
+                // Do not allow selection to collapse on itself while dragging. Selection can
+                // reverse but does not collapse.
+                if (prevSelection.collapsed || !newSelection.collapsed) {
+                    textFieldState.selectCharsIn(newSelection)
+                }
+                updateHandleDragging(
+                    handle = actingHandle,
+                    position = currentDragPosition
+                )
+            }
+        )
+    }
+
+    private suspend fun PointerInputScope.detectSelectionHandleDragGestures(
+        isStartHandle: Boolean
+    ) {
+        var dragBeginPosition: Offset = Offset.Unspecified
+        var dragTotalDistance: Offset = Offset.Zero
+        val handle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
+
+        fun onDragStop() {
+            // Only execute clear-up if drag was actually ongoing.
+            if (dragBeginPosition.isSpecified) {
+                clearHandleDragging()
+                dragBeginPosition = Offset.Unspecified
+                dragTotalDistance = Offset.Zero
+                previousRawDragOffset = -1
+            }
+        }
+
+        // b/288931376: detectDragGestures do not call onDragCancel when composable is disposed.
+        try {
+            detectDragGestures(
+                onDragStart = {
+                    // The position of the character where the drag gesture should begin. This is in
+                    // the composable coordinates.
+                    dragBeginPosition = getAdjustedCoordinates(getHandlePosition(isStartHandle))
+
+                    // no need to call markStartContentVisibleOffset, since it was called by the
+                    // initial down event.
+                    updateHandleDragging(handle, dragBeginPosition)
+
+                    // Zero out the total distance that being dragged.
+                    dragTotalDistance = Offset.Zero
+
+                    previousRawDragOffset = -1
+                },
+                onDragEnd = { onDragStop() },
+                onDragCancel = { onDragStop() },
+                onDrag = onDrag@{ _, delta ->
+                    dragTotalDistance += delta
+                    val layoutResult = textLayoutState.layoutResult ?: return@onDrag
+
+                    updateHandleDragging(handle, dragBeginPosition + dragTotalDistance)
+
+                    val startOffset = if (isStartHandle) {
+                        layoutResult.getOffsetForPosition(handleDragPosition)
+                    } else {
+                        textFieldState.visualText.selectionInChars.start
+                    }
+
+                    val endOffset = if (isStartHandle) {
+                        textFieldState.visualText.selectionInChars.end
+                    } else {
+                        layoutResult.getOffsetForPosition(handleDragPosition)
+                    }
+
+                    val prevSelection = textFieldState.visualText.selectionInChars
+                    val newSelection = updateSelection(
+                        textFieldCharSequence = textFieldState.visualText,
+                        startOffset = startOffset,
+                        endOffset = endOffset,
+                        isStartHandle = isStartHandle,
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                    )
+                    // Do not allow selection to collapse on itself while dragging selection
+                    // handles. Selection can reverse but does not collapse.
+                    if (prevSelection.collapsed || !newSelection.collapsed) {
+                        textFieldState.selectCharsIn(newSelection)
+                    }
+                }
+            )
+        } finally {
+            logDebug {
+                "Selection Handle drag cancelled for " +
+                    "draggingHandle: $draggingHandle definedOn: $handle"
+            }
+            if (draggingHandle == handle) {
+                onDragStop()
+            }
+        }
+    }
+
+    private suspend fun observeTextChanges() {
+        snapshotFlow { textFieldState.visualText }
+            .distinctUntilChanged(TextFieldCharSequence::contentEquals)
+            // first value needs to be dropped because it cannot be compared to a prior value
+            .drop(1)
+            .collect {
+                showCursorHandle = false
+                // hide the toolbar any time text content changes.
+                updateTextToolbarState(TextToolbarState.None)
+            }
+    }
+
+    /**
+     * Manages the visibility of text toolbar according to current state and received events from
+     * various sources.
+     *
+     * - Tapping the cursor handle toggles the visibility of the toolbar [TextToolbarState.Cursor].
+     * - Dragging the cursor handle or selection handles temporarily hides the toolbar
+     * [draggingHandle].
+     * - Tapping somewhere on the TextField, whether it causes a cursor position change or not,
+     * fully hides the toolbar [TextToolbarState.None].
+     * - When cursor or selection leaves the visible bounds, text toolbar is temporarily hidden.
+     * [getContentRect]
+     * - When selection is initiated via long press, double click, or semantics, text toolbar shows
+     * [TextToolbarState.Selection]
+     */
+    private suspend fun observeTextToolbarVisibility() {
+        snapshotFlow {
+            val isCollapsed = textFieldState.visualText.selectionInChars.collapsed
+            val textToolbarStateVisible =
+                isCollapsed && textToolbarState == TextToolbarState.Cursor ||
+                    !isCollapsed && textToolbarState == TextToolbarState.Selection
+
+            val textToolbarVisible =
+                // toolbar is requested specifically for the current selection state
+                textToolbarStateVisible &&
+                    draggingHandle == null && // not dragging any selection handles
+                    isInTouchMode // toolbar hidden when not in touch mode
+
+            // final visibility decision is made by contentRect visibility.
+            // if contentRect is not in visible bounds, just pass Rect.Zero to the observer so that
+            // it hides the toolbar. If Rect is successfully passed to the observer, toolbar will
+            // be displayed.
+            if (!textToolbarVisible) {
+                Rect.Zero
+            } else {
+                // contentRect is calculated in root coordinates. VisibleBounds are in parent
+                // coordinates. Convert visibleBounds to root before checking the overlap.
+                val visibleBounds = textLayoutCoordinates?.visibleBounds()
+                if (visibleBounds != null) {
+                    val visibleBoundsTopLeftInRoot =
+                        textLayoutCoordinates?.localToRoot(visibleBounds.topLeft)
+                    val visibleBoundsInRoot =
+                        Rect(visibleBoundsTopLeftInRoot!!, visibleBounds.size)
+
+                    // contentRect can be very wide if a big part of text is selected. Our toolbar
+                    // should be aligned only to visible region.
+                    getContentRect()
+                        .takeIf { visibleBoundsInRoot.overlaps(it) }
+                        ?.intersect(visibleBoundsInRoot)
+                        ?: Rect.Zero
+                } else {
+                    Rect.Zero
+                }
+            }
+        }.collect { rect ->
+            if (rect == Rect.Zero) {
+                hideTextToolbar()
+            } else {
+                showTextToolbar(rect)
+            }
+        }
+    }
+
+    /**
+     * Calculate selected region as [Rect]. The top is the top of the first selected
+     * line, and the bottom is the bottom of the last selected line. The left is the leftmost
+     * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
+     */
+    private fun getContentRect(): Rect {
+        val text = textFieldState.visualText
+        // accept cursor position as content rect when selection is collapsed
+        // contentRect is defined in text layout node coordinates, so it needs to be realigned to
+        // the root container.
+        if (text.selectionInChars.collapsed) {
+            val topLeft = textLayoutCoordinates?.localToRoot(cursorRect.topLeft) ?: Offset.Zero
+            return Rect(topLeft, cursorRect.size)
+        }
+        val startOffset =
+            textLayoutCoordinates?.localToRoot(getHandlePosition(true)) ?: Offset.Zero
+        val endOffset =
+            textLayoutCoordinates?.localToRoot(getHandlePosition(false)) ?: Offset.Zero
+        val startTop =
+            textLayoutCoordinates?.localToRoot(
+                Offset(
+                    0f,
+                    textLayoutState.layoutResult?.getCursorRect(
+                        text.selectionInChars.start
+                    )?.top ?: 0f
+                )
+            )?.y ?: 0f
+        val endTop =
+            textLayoutCoordinates?.localToRoot(
+                Offset(
+                    0f,
+                    textLayoutState.layoutResult?.getCursorRect(
+                        text.selectionInChars.end
+                    )?.top ?: 0f
+                )
+            )?.y ?: 0f
+
+        return Rect(
+            left = min(startOffset.x, endOffset.x),
+            right = max(startOffset.x, endOffset.x),
+            top = min(startTop, endTop),
+            bottom = max(startOffset.y, endOffset.y)
+        )
+    }
+
+    /**
+     * Calculates and returns the [TextFieldHandleState] of the requested selection handle which is
+     * specified by [isStartHandle]. Pass [includePosition] as false to omit the position from the
+     * result. This helps create a derived state which does not invalidate according position
+     * changes.
+     */
+    internal fun getSelectionHandleState(
+        isStartHandle: Boolean,
+        includePosition: Boolean
+    ): TextFieldHandleState {
+        val handle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
+
+        val layoutResult = textLayoutState.layoutResult ?: return TextFieldHandleState.Hidden
+
+        val selection = textFieldState.visualText.selectionInChars
+
+        if (selection.collapsed) return TextFieldHandleState.Hidden
+
+        val position = getHandlePosition(isStartHandle)
+
+        val visible = draggingHandle == handle ||
+            (textLayoutCoordinates
+                ?.visibleBounds()
+                ?.containsInclusive(position)
+                ?: false)
+
+        if (!visible) return TextFieldHandleState.Hidden
+
+        val directionOffset = if (isStartHandle) selection.start else max(selection.end - 1, 0)
+        val direction = layoutResult.getBidiRunDirection(directionOffset)
+        val handlesCrossed = selection.reversed
+
+        // Handle normally is visible when it's out of bounds but when the handle is being dragged,
+        // we let it stay on the screen to maintain gesture continuation. However, we still want
+        // to coerce handle's position to visible bounds to not let it jitter while scrolling the
+        // TextField as the selection is expanding.
+        val coercedPosition = if (includePosition) {
+            textLayoutCoordinates?.visibleBounds()?.let { position.coerceIn(it) }
+                ?: position
+        } else {
+            Offset.Unspecified
+        }
+        return TextFieldHandleState(
+            visible = true,
+            position = coercedPosition,
+            direction = direction,
+            handlesCrossed = handlesCrossed
+        )
+    }
+
+    private fun getHandlePosition(isStartHandle: Boolean): Offset {
+        val layoutResult = textLayoutState.layoutResult ?: return Offset.Zero
+        val selection = textFieldState.visualText.selectionInChars
+        val offset = if (isStartHandle) {
+            selection.start
+        } else {
+            selection.end
+        }
+        return getSelectionHandleCoordinates(
+            textLayoutResult = layoutResult,
+            offset = offset,
+            isStart = isStartHandle,
+            areHandlesCrossed = selection.reversed
+        )
+    }
+
+    /**
+     * Sets currently dragging handle state to [handle] and positions it at [position]. This is
+     * mostly useful for updating the magnifier.
+     *
+     * @param handle A real or acting handle that specifies which one is being dragged.
+     * @param position Where the handle currently is
+     */
+    fun updateHandleDragging(
+        handle: Handle,
+        position: Offset
+    ) {
+        draggingHandle = handle
+        rawHandleDragPosition = position
+    }
+
+    /**
+     * When a Selection or Cursor Handle is started to being dragged, this function should be called
+     * to mark the current visible offset, so that if content gets scrolled during the drag, we
+     * can correctly offset the actual position where drag corresponds to.
+     */
+    private fun markStartContentVisibleOffset() {
+        startContentVisibleOffset = textLayoutCoordinates
+            ?.visibleBounds()
+            ?.topLeft ?: Offset.Unspecified
+    }
+
+    /**
+     * Call this function when a selection or cursor handle is stopped dragging.
+     */
+    fun clearHandleDragging() {
+        draggingHandle = null
+        rawHandleDragPosition = Offset.Unspecified
+        startContentVisibleOffset = Offset.Unspecified
+    }
+
+    /**
+     * The method for cutting text.
+     *
+     * If there is no selection, return.
+     * Put the selected text into the [ClipboardManager].
+     * The new text should be the text before the selection plus the text after the selection.
+     * And the new cursor offset should be between the text before the selection, and the text
+     * after the selection.
+     */
+    fun cut() {
+        val text = textFieldState.visualText
+        if (text.selectionInChars.collapsed) return
+
+        clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
+
+        textFieldState.deleteSelectedText()
+    }
+
+    /**
+     * The method for copying text.
+     *
+     * If there is no selection, return.
+     * Put the selected text into the [ClipboardManager], and cancel the selection, if
+     * [cancelSelection] is true.
+     * The text in the text field should be unchanged.
+     * If [cancelSelection] is true, the new cursor offset should be at the end of the previous
+     * selected text.
+     */
+    fun copy(cancelSelection: Boolean = true) {
+        val text = textFieldState.visualText
+        if (text.selectionInChars.collapsed) return
+
+        clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
+
+        if (!cancelSelection) return
+
+        textFieldState.collapseSelectionToMax()
+    }
+
+    fun paste() {
+        val receiveContentConfiguration = receiveContentConfiguration?.invoke()
+            ?: return pasteAsPlainText()
+
+        val clipEntry = clipboardManager?.getClip() ?: return
+        val clipMetadata = clipboardManager?.getClipMetadata() ?: return pasteAsPlainText()
+
+        val remaining = receiveContentConfiguration.receiveContentListener.onReceive(
+            TransferableContent(
+                clipEntry = clipEntry,
+                source = TransferableContent.Source.Clipboard,
+                clipMetadata = clipMetadata
+            )
+        )
+
+        // TODO(halilibo): this is not 1-to-1 compatible with ClipboardManager.getText() which
+        //  returns an AnnotatedString and supports copy-pasting AnnotatedStrings inside the app.
+        remaining?.clipEntry?.readPlainText()?.let { clipboardText ->
+            textFieldState.replaceSelectedText(
+                clipboardText,
+                undoBehavior = TextFieldEditUndoBehavior.NeverMerge
+            )
+        }
+    }
+
+    /**
+     * The method for pasting text.
+     *
+     * Get the text from [ClipboardManager]. If it's null, return.
+     * The new content should be the text before the selected text, plus the text from the
+     * [ClipboardManager], and plus the text after the selected text.
+     * Then the selection should collapse, and the new cursor offset should be at the end of the
+     * newly added text.
+     */
+    private fun pasteAsPlainText() {
+        val clipboardText = clipboardManager?.getText()?.text ?: return
+
+        textFieldState.replaceSelectedText(
+            clipboardText,
+            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
+        )
+    }
+
+    /**
+     * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
+     * to make the FloatingToolbar show up in the proper place. In addition, this function passes
+     * the copy, paste and cut method as callbacks when "copy", "cut" or "paste" is clicked.
+     *
+     * @param contentRect Rectangle region where the toolbar will be anchored.
+     */
+    private fun showTextToolbar(contentRect: Rect) {
+        val selection = textFieldState.visualText.selectionInChars
+
+        // if receive content is configured, hasClip should be enough to show the paste option
+        val canPasteContent = receiveContentConfiguration?.invoke() != null &&
+            clipboardManager?.hasClip() == true
+        // if receive content is not configured, we expect at least a text item to be present
+        val canPasteText = clipboardManager?.hasText() == true
+        val canPaste = editable && (canPasteContent || canPasteText)
+
+        // TODO(halilibo): Add a new TextToolbar option "paste as plain text".
+        val paste: (() -> Unit)? = if (canPaste) {
+            {
+                paste()
+                updateTextToolbarState(TextToolbarState.None)
+            }
+        } else null
+
+        val copy: (() -> Unit)? = if (!selection.collapsed) {
+            {
+                copy()
+                updateTextToolbarState(TextToolbarState.None)
+            }
+        } else null
+
+        val cut: (() -> Unit)? = if (!selection.collapsed && editable) {
+            {
+                cut()
+                updateTextToolbarState(TextToolbarState.None)
+            }
+        } else null
+
+        val selectAll: (() -> Unit)? = if (selection.length != textFieldState.visualText.length) {
+            {
+                textFieldState.selectAll()
+                updateTextToolbarState(TextToolbarState.Selection)
+            }
+        } else null
+
+        textToolbar?.showMenu(
+            rect = contentRect,
+            onCopyRequested = copy,
+            onPasteRequested = paste,
+            onCutRequested = cut,
+            onSelectAllRequested = selectAll
+        )
+    }
+
+    fun deselect() {
+        if (!textFieldState.visualText.selectionInChars.collapsed) {
+            textFieldState.collapseSelectionToEnd()
+        }
+
+        showCursorHandle = false
+        updateTextToolbarState(TextToolbarState.None)
+    }
+
+    private fun hideTextToolbar() {
+        if (textToolbar?.status == TextToolbarStatus.Shown) {
+            textToolbar?.hide()
+        }
+    }
+
+    /**
+     * Update the text field's selection based on new offsets.
+     *
+     * @param textFieldCharSequence the current text editing state
+     * @param startOffset the start offset to use
+     * @param endOffset the end offset to use
+     * @param isStartHandle whether the start or end handle is being updated
+     * @param adjustment The selection adjustment to use
+     * @param allowPreviousSelectionCollapsed Allow a collapsed selection to be passed to selection
+     * adjustment. In most cases, a collapsed selection should be considered "no previous
+     * selection" for selection adjustment. However, in some cases - like starting a selection in
+     * end padding - a collapsed selection may be necessary context to avoid selection flickering.
+     */
+    private fun updateSelection(
+        textFieldCharSequence: TextFieldCharSequence,
+        startOffset: Int,
+        endOffset: Int,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment,
+        allowPreviousSelectionCollapsed: Boolean = false,
+    ): TextRange {
+        val newSelection = getTextFieldSelection(
+            rawStartOffset = startOffset,
+            rawEndOffset = endOffset,
+            previousSelection = textFieldCharSequence.selectionInChars
+                .takeIf { allowPreviousSelectionCollapsed || !it.collapsed },
+            isStartHandle = isStartHandle,
+            adjustment = adjustment,
+        )
+
+        if (newSelection == textFieldCharSequence.selectionInChars) return newSelection
+
+        val onlyChangeIsReversed =
+            newSelection.reversed != textFieldCharSequence.selectionInChars.reversed &&
+                newSelection.run { TextRange(end, start) } == textFieldCharSequence.selectionInChars
+
+        // don't haptic if we are using a mouse or if we aren't moving the selection bounds
+        if (isInTouchMode && !onlyChangeIsReversed) {
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
+
+        return newSelection
+    }
+
+    private fun getTextFieldSelection(
+        rawStartOffset: Int,
+        rawEndOffset: Int,
+        previousSelection: TextRange?,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment
+    ): TextRange {
+        val layoutResult = textLayoutState.layoutResult ?: return TextRange.Zero
+
+        // When the previous selection is null, it's allowed to have collapsed selection on
+        // TextField. So we can ignore the SelectionAdjustment.Character.
+        if (previousSelection == null && adjustment == SelectionAdjustment.Character) {
+            return TextRange(rawStartOffset, rawEndOffset)
+        }
+
+        val selectionLayout = getTextFieldSelectionLayout(
+            layoutResult = layoutResult,
+            rawStartHandleOffset = rawStartOffset,
+            rawEndHandleOffset = rawEndOffset,
+            rawPreviousHandleOffset = previousRawDragOffset,
+            previousSelectionRange = previousSelection ?: TextRange.Zero,
+            isStartOfSelection = previousSelection == null,
+            isStartHandle = isStartHandle,
+        )
+
+        if (previousSelection != null &&
+            !selectionLayout.shouldRecomputeSelection(previousSelectionLayout)
+        ) {
+            return previousSelection
+        }
+
+        val result = adjustment.adjust(selectionLayout).toTextRange()
+        previousSelectionLayout = selectionLayout
+        previousRawDragOffset = if (isStartHandle) rawStartOffset else rawEndOffset
+
+        return result
+    }
+}
+
+private fun TextRange.reverse() = TextRange(end, start)
+
+/**
+ * A state that indicates when to show TextToolbar.
+ *
+ * - [None] Do not show the TextToolbar at all.
+ * - [Cursor] if selection is collapsed and all the other criteria are met, show the TextToolbar.
+ * - [Selection] if selection is expanded and all the other criteria are met, show the TextToolbar.
+ *
+ * @see [TextFieldSelectionState.observeTextToolbarVisibility]
+ */
+internal enum class TextToolbarState {
+    None,
+    Cursor,
+    Selection,
+}
+
+private const val DEBUG = false
+private const val DEBUG_TAG = "TextFieldSelectionState"
+
+private fun logDebug(text: () -> String) {
+    if (DEBUG) {
+        println("$DEBUG_TAG: ${text()}")
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextPreparedSelection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextPreparedSelection.kt
new file mode 100644
index 0000000..069c2a1
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextPreparedSelection.kt
@@ -0,0 +1,524 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.findFollowingBreak
+import androidx.compose.foundation.text.findParagraphEnd
+import androidx.compose.foundation.text.findParagraphStart
+import androidx.compose.foundation.text.findPrecedingBreak
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Deletion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Insertion
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Replacement
+import androidx.compose.foundation.text.input.internal.IndexTransformationType.Untransformed
+import androidx.compose.foundation.text.input.internal.SelectionWedgeAffinity
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+import androidx.compose.foundation.text.input.internal.WedgeAffinity
+import androidx.compose.foundation.text.input.internal.getIndexTransformationType
+import androidx.compose.foundation.text.input.internal.selection.TextFieldPreparedSelection.Companion.NoCharacterFound
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import kotlin.math.abs
+
+/**
+ * [TextFieldPreparedSelection] provides a scope for many selection-related operations. However,
+ * some vertical cursor operations like moving between lines or page up and down require a cache of
+ * X position in text to remember where to move the cursor in next line.
+ * [TextFieldPreparedSelection] is a disposable scope that cannot hold its own state. This class
+ * helps to pass a cached X value between selection operations in different scopes.
+ */
+internal class TextFieldPreparedSelectionState {
+    /**
+     * it's set at the start of vertical navigation and used as the preferred value to set a new
+     * cursor position.
+     */
+    var cachedX: Float = Float.NaN
+
+    /**
+     * Remove and forget the cached X used for vertical navigation.
+     */
+    fun resetCachedX() {
+        cachedX = Float.NaN
+    }
+}
+
+/**
+ * This utility class implements many selection-related operations on text (including basic
+ * cursor movements and deletions) and combines them, taking into account how the text was
+ * rendered. So, for example, [moveCursorToLineEnd] moves it to the visual line end.
+ *
+ * For many of these operations, it's particularly important to keep the difference between
+ * selection start and selection end. In some systems, they are called "anchor" and "caret"
+ * respectively. For example, for selection from scratch, after [moveCursorLeftByWord]
+ * [moveCursorRight] will move the left side of the selection, but after [moveCursorRightByWord]
+ * the right one.
+ *
+ * @param state Transformed version of TextFieldState that helps to manipulate underlying buffer
+ * through transformed coordinates.
+ * @param textLayoutResult Visual representation of text inside [state]. Used to calculate line
+ * and paragraph metrics.
+ * @param visibleTextLayoutHeight Height of the visible area of text inside TextField to decide
+ * where cursor needs to move when page up/down is requested.
+ * @param textPreparedSelectionState An object that holds any context that needs to be long lived
+ * between successive [TextFieldPreparedSelection]s, e.g. original X position of the cursor while
+ * moving the cursor up/down.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextFieldPreparedSelection(
+    private val state: TransformedTextFieldState,
+    private val textLayoutResult: TextLayoutResult,
+    private val visibleTextLayoutHeight: Float,
+    private val textPreparedSelectionState: TextFieldPreparedSelectionState
+) {
+    /**
+     * Read the value from state without read observation to not accidentally cause recompositions.
+     * Freezing the initial value is necessary to make atomic operations in the scope of this
+     * [TextFieldPreparedSelection]. It is also used to make comparison between the initial state
+     * and the modified state of selection and content.
+     */
+    val initialValue = Snapshot.withoutReadObservation { state.visualText }
+
+    /**
+     * Current active selection in the context of this [TextFieldPreparedSelection]
+     */
+    var selection = initialValue.selectionInChars
+
+    /**
+     * Initial text value.
+     */
+    private val text: String = initialValue.toString()
+
+    /**
+     * Deletes selected region from [state] if [selection] is not collapsed. Otherwise, deletes the
+     * range returned by [block]. If returned TextRange is null, this function does nothing.
+     */
+    inline fun deleteIfSelectedOr(block: () -> TextRange?) {
+        if (!selection.collapsed) {
+            state.replaceText("", selection)
+        } else {
+            block()?.let { state.replaceText("", it) }
+        }
+    }
+
+    /**
+     * Executes PageUp key
+     */
+    fun moveCursorUpByPage() = applyIfNotEmpty(false) {
+        setCursor(jumpByPagesOffset(-1))
+    }
+
+    /**
+     * Executes PageDown key
+     */
+    fun moveCursorDownByPage() = applyIfNotEmpty(false) {
+        setCursor(jumpByPagesOffset(1))
+    }
+
+    /**
+     * Returns a cursor position after jumping back or forth by [pagesAmount] number of pages,
+     * where `page` is the visible amount of space in the text field. Visible rectangle is
+     * calculated by the coordinates of decoration box around the TextField. If text layout has not
+     * been measured yet, this function returns the current offset.
+     */
+    private fun jumpByPagesOffset(pagesAmount: Int): Int {
+        val currentOffset = initialValue.selectionInChars.end
+        val currentPos = textLayoutResult.getCursorRect(currentOffset)
+        val newPos = currentPos.translate(
+            translateX = 0f,
+            translateY = visibleTextLayoutHeight * pagesAmount
+        )
+        // which line does the new cursor position belong?
+        val topLine = textLayoutResult.getLineForVerticalPosition(newPos.top)
+        val lineSeparator = textLayoutResult.getLineBottom(topLine)
+        return if (abs(newPos.top - lineSeparator) > abs(newPos.bottom - lineSeparator)) {
+            // most of new cursor is on top line
+            textLayoutResult.getOffsetForPosition(newPos.topLeft)
+        } else {
+            // most of new cursor is on bottom line
+            textLayoutResult.getOffsetForPosition(newPos.bottomLeft)
+        }
+    }
+
+    /**
+     * Only apply the given [block] if the text is not empty.
+     *
+     * @param resetCachedX Whether to reset the cachedX parameter in [TextFieldPreparedSelectionState].
+     */
+    private inline fun applyIfNotEmpty(
+        resetCachedX: Boolean = true,
+        block: TextFieldPreparedSelection.() -> Unit
+    ): TextFieldPreparedSelection {
+        if (resetCachedX) {
+            textPreparedSelectionState.resetCachedX()
+        }
+        if (text.isNotEmpty()) {
+            this.block()
+        }
+        return this
+    }
+
+    /**
+     * Sets a collapsed selection at given [offset].
+     */
+    private fun setCursor(offset: Int) {
+        selection = TextRange(offset, offset)
+    }
+
+    fun selectAll() = applyIfNotEmpty {
+        selection = TextRange(0, text.length)
+    }
+
+    fun deselect() = applyIfNotEmpty {
+        setCursor(selection.end)
+    }
+
+    fun moveCursorLeft() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorPrev()
+        } else {
+            moveCursorNext()
+        }
+    }
+
+    fun moveCursorRight() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorNext()
+        } else {
+            moveCursorPrev()
+        }
+    }
+
+    /**
+     * If there is already a selection, collapse it to the left side. Otherwise, execute [or]
+     */
+    fun collapseLeftOr(or: TextFieldPreparedSelection.() -> Unit) = applyIfNotEmpty {
+        if (selection.collapsed) {
+            or(this)
+        } else {
+            if (isLtr()) {
+                setCursor(selection.min)
+            } else {
+                setCursor(selection.max)
+            }
+        }
+    }
+
+    /**
+     * If there is already a selection, collapse it to the right side. Otherwise, execute [or]
+     */
+    fun collapseRightOr(or: TextFieldPreparedSelection.() -> Unit) = applyIfNotEmpty {
+        if (selection.collapsed) {
+            or(this)
+        } else {
+            if (isLtr()) {
+                setCursor(selection.max)
+            } else {
+                setCursor(selection.min)
+            }
+        }
+    }
+
+    /**
+     * Returns the index of the character break preceding the end of [selection].
+     */
+    fun getPrecedingCharacterIndex() = text.findPrecedingBreak(selection.end)
+
+    /**
+     * Returns the index of the character break following the end of [selection]. Returns
+     * [NoCharacterFound] if there are no more breaks before the end of the string.
+     */
+    fun getNextCharacterIndex() = text.findFollowingBreak(selection.end)
+
+    private fun moveCursorPrev() = applyIfNotEmpty {
+        val oldCursor = selection.end
+        val newCursor = calculateAdjacentCursorPosition(text, oldCursor, forward = false, state)
+        if (newCursor != oldCursor) {
+            setCursor(newCursor)
+        }
+    }
+
+    private fun moveCursorNext() = applyIfNotEmpty {
+        val oldCursor = selection.end
+        val newCursor = calculateAdjacentCursorPosition(text, oldCursor, forward = true, state)
+        if (newCursor != oldCursor) {
+            setCursor(newCursor)
+        }
+    }
+
+    fun moveCursorToHome() = applyIfNotEmpty {
+        setCursor(0)
+    }
+
+    fun moveCursorToEnd() = applyIfNotEmpty {
+        setCursor(text.length)
+    }
+
+    fun moveCursorLeftByWord() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorPrevByWord()
+        } else {
+            moveCursorNextByWord()
+        }
+    }
+
+    fun moveCursorRightByWord() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorNextByWord()
+        } else {
+            moveCursorPrevByWord()
+        }
+    }
+
+    fun getNextWordOffset(): Int = textLayoutResult.getNextWordOffsetForLayout()
+
+    private fun moveCursorNextByWord() = applyIfNotEmpty {
+        setCursor(getNextWordOffset())
+    }
+
+    fun getPreviousWordOffset(): Int = textLayoutResult.getPrevWordOffsetForLayout()
+
+    private fun moveCursorPrevByWord() = applyIfNotEmpty {
+        setCursor(getPreviousWordOffset())
+    }
+
+    fun moveCursorPrevByParagraph() = applyIfNotEmpty {
+        var paragraphStart = text.findParagraphStart(selection.min)
+        if (paragraphStart == selection.min && paragraphStart != 0) {
+            paragraphStart = text.findParagraphStart(paragraphStart - 1)
+        }
+        setCursor(paragraphStart)
+    }
+
+    fun moveCursorNextByParagraph() = applyIfNotEmpty {
+        var paragraphEnd = text.findParagraphEnd(selection.max)
+        if (paragraphEnd == selection.max && paragraphEnd != text.length) {
+            paragraphEnd = text.findParagraphEnd(paragraphEnd + 1)
+        }
+        setCursor(paragraphEnd)
+    }
+
+    fun moveCursorUpByLine() = applyIfNotEmpty(false) {
+        setCursor(textLayoutResult.jumpByLinesOffset(-1))
+    }
+
+    fun moveCursorDownByLine() = applyIfNotEmpty(false) {
+        setCursor(textLayoutResult.jumpByLinesOffset(1))
+    }
+
+    fun getLineStartByOffset(): Int = textLayoutResult.getLineStartByOffsetForLayout()
+
+    fun moveCursorToLineStart() = applyIfNotEmpty {
+        setCursor(getLineStartByOffset())
+    }
+
+    fun getLineEndByOffset(): Int = textLayoutResult.getLineEndByOffsetForLayout()
+
+    fun moveCursorToLineEnd() = applyIfNotEmpty {
+        setCursor(getLineEndByOffset())
+    }
+
+    fun moveCursorToLineLeftSide() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorToLineStart()
+        } else {
+            moveCursorToLineEnd()
+        }
+    }
+
+    fun moveCursorToLineRightSide() = applyIfNotEmpty {
+        if (isLtr()) {
+            moveCursorToLineEnd()
+        } else {
+            moveCursorToLineStart()
+        }
+    }
+
+    /** Selects a text from the original selection start to a current selection end. */
+    fun selectMovement() = applyIfNotEmpty(resetCachedX = false) {
+        selection = TextRange(initialValue.selectionInChars.start, selection.end)
+    }
+
+    private fun isLtr(): Boolean {
+        val direction = textLayoutResult.getParagraphDirection(selection.end)
+        return direction == ResolvedTextDirection.Ltr
+    }
+
+    private tailrec fun TextLayoutResult.getNextWordOffsetForLayout(
+        currentOffset: Int = selection.end
+    ): Int {
+        if (currentOffset >= initialValue.length) {
+            return initialValue.length
+        }
+        val currentWord = getWordBoundary(charOffset(currentOffset))
+        return if (currentWord.end <= currentOffset) {
+            getNextWordOffsetForLayout(currentOffset + 1)
+        } else {
+            currentWord.end
+        }
+    }
+
+    private tailrec fun TextLayoutResult.getPrevWordOffsetForLayout(
+        currentOffset: Int = selection.end
+    ): Int {
+        if (currentOffset <= 0) {
+            return 0
+        }
+        val currentWord = getWordBoundary(charOffset(currentOffset))
+        return if (currentWord.start >= currentOffset) {
+            getPrevWordOffsetForLayout(currentOffset - 1)
+        } else {
+            currentWord.start
+        }
+    }
+
+    private fun TextLayoutResult.getLineStartByOffsetForLayout(
+        currentOffset: Int = selection.min
+    ): Int {
+        val currentLine = getLineForOffset(currentOffset)
+        return getLineStart(currentLine)
+    }
+
+    private fun TextLayoutResult.getLineEndByOffsetForLayout(
+        currentOffset: Int = selection.max
+    ): Int {
+        val currentLine = getLineForOffset(currentOffset)
+        return getLineEnd(currentLine, true)
+    }
+
+    private fun TextLayoutResult.jumpByLinesOffset(linesAmount: Int): Int {
+        val currentOffset = selection.end
+
+        if (textPreparedSelectionState.cachedX.isNaN()) {
+            textPreparedSelectionState.cachedX = getCursorRect(currentOffset).left
+        }
+
+        val targetLine = getLineForOffset(currentOffset) + linesAmount
+        when {
+            targetLine < 0 -> {
+                return 0
+            }
+
+            targetLine >= lineCount -> {
+                return text.length
+            }
+        }
+
+        val y = getLineBottom(targetLine) - 1
+        val x = textPreparedSelectionState.cachedX.also {
+            if ((isLtr() && it >= getLineRight(targetLine)) ||
+                (!isLtr() && it <= getLineLeft(targetLine))
+            ) {
+                return getLineEnd(targetLine, true)
+            }
+        }
+
+        return getOffsetForPosition(Offset(x, y))
+    }
+
+    private fun charOffset(offset: Int) = offset.coerceAtMost(text.length - 1)
+
+    companion object {
+        /**
+         * Value returned by [getNextCharacterIndex] and [getPrecedingCharacterIndex] when no valid
+         * index could be found, e.g. it would be the end of the string.
+         *
+         * This is equivalent to `BreakIterator.DONE` on JVM/Android.
+         */
+        const val NoCharacterFound = -1
+    }
+}
+
+/**
+ * Given some transformed text and the current cursor offset in that text, calculates the offset of
+ * the nearest next position of the cursor in the transformed text. Takes into account text
+ * transformations ([TransformedTextFieldState]) to avoid putting the cursor in the middle of
+ * replacements.
+ */
+@VisibleForTesting
+internal fun calculateAdjacentCursorPosition(
+    transformedText: String,
+    cursor: Int,
+    forward: Boolean,
+    state: TransformedTextFieldState,
+): Int {
+    // First step: find the index of the next cursor position in the visual text. In most cases this
+    // will be the final result, however if transformations are applied we may need to jump the
+    // cursor forward or backward.
+    val proposedCursor = if (forward) {
+        transformedText.findFollowingBreak(cursor)
+    } else {
+        transformedText.findPrecedingBreak(cursor)
+    }
+    if (proposedCursor == NoCharacterFound) {
+        // At the start or end of the text, no change.
+        return cursor
+    }
+
+    // Second step: if a transformation is applied, determine if the proposed cursor position would
+    // be in a range where the cursor is not allowed to be. If so, push it to the appropriate edge
+    // of that range.
+    return state.getIndexTransformationType(proposedCursor) { type, _, retransformed ->
+        when (type) {
+            Untransformed -> proposedCursor
+
+            // It doesn't matter which end of the deleted range we put the cursor, they'll both map
+            // to the same transformed offset.
+            Deletion -> proposedCursor
+
+            // Moving forward into a replacement means we should jump to the end, moving backwards
+            // into it means jump to the start.
+            Replacement -> if (forward) retransformed.end else retransformed.start
+
+            // Moving into an insertion is like a replacement in that the cursor may only be placed
+            // on either edge of the range. However, since both edges of the range map to the same
+            // untransformed index, we need to set the affinity.
+            Insertion -> {
+                if (forward) {
+                    if (proposedCursor == retransformed.start) {
+                        // Moving to start of wedge, update affinity and set cursor.
+                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.Start)
+                        return proposedCursor
+                    } else {
+                        // Moving to middle or end of wedge, update affinity but don't need to move
+                        // cursor.
+                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.End)
+                        // No offset change.
+                        cursor
+                    }
+                } else {
+                    // We're navigating to or within a wedge. Use affinity (doesn't matter which
+                    // one, selection is a cursor).
+                    if (proposedCursor == retransformed.end) {
+                        // Moving to end of wedge, update affinity and set cursor.
+                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.End)
+                        return proposedCursor
+                    } else {
+                        // Moving to middle or start of wedge, update affinity but don't need to
+                        // move cursor.
+                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.Start)
+                        // No offset change.
+                        return cursor
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoOperation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoOperation.kt
new file mode 100644
index 0000000..5b7b1a6
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoOperation.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.timeNowMillis
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.ui.text.TextRange
+
+/**
+ * An undo identifier designed for text editors. Defines a single atomic change that can be applied
+ * directly or in reverse to modify the contents of a text editor.
+ *
+ * @param index Start point of [preText] and [postText].
+ * @param preText Previously written text that's deleted starting from [index].
+ * @param postText New text that's inserted at [index]
+ * @param preSelection Previous selection before changes are applied
+ * @param postSelection New selection after changes are applied
+ * @param timeInMillis When did this change was first committed
+ * @param canMerge Whether this change can be merged with the next or previous change in an undo
+ * stack. There are many other rules that affect the merging strategy between two
+ * [TextUndoOperation]s but this flag is a sure way to force a non-mergeable property.
+ */
+internal class TextUndoOperation(
+    val index: Int,
+    val preText: String,
+    val postText: String,
+    val preSelection: TextRange,
+    val postSelection: TextRange,
+    val timeInMillis: Long = timeNowMillis(),
+    val canMerge: Boolean = true
+) {
+
+    /**
+     * What kind of edit operation is defined by this change. Edit type is decided by forward the
+     * behavior of this change in forward direction (pre -> post).
+     */
+    val textEditType: TextEditType = when {
+        preText.isEmpty() && postText.isEmpty() ->
+            throw IllegalArgumentException("Either pre or post text must not be empty")
+
+        preText.isEmpty() && postText.isNotEmpty() -> TextEditType.Insert
+        preText.isNotEmpty() && postText.isEmpty() -> TextEditType.Delete
+        else -> TextEditType.Replace
+    }
+
+    /**
+     * Only required while deciding whether to merge two deletion type undo operations.
+     */
+    val deletionType: TextDeleteType
+        get() {
+            if (textEditType != TextEditType.Delete) return TextDeleteType.NotByUser
+            if (!postSelection.collapsed) return TextDeleteType.NotByUser
+            if (preSelection.collapsed) {
+                return if (preSelection.start > postSelection.start) {
+                    TextDeleteType.Start
+                } else {
+                    TextDeleteType.End
+                }
+            } else if (preSelection.start == postSelection.start && preSelection.start == index) {
+                return TextDeleteType.Inner
+            }
+            return TextDeleteType.NotByUser
+        }
+
+    companion object {
+
+        val Saver = object : Saver<TextUndoOperation, Any> {
+            override fun SaverScope.save(value: TextUndoOperation): Any = listOf(
+                value.index,
+                value.preText,
+                value.postText,
+                value.preSelection.start,
+                value.preSelection.end,
+                value.postSelection.start,
+                value.postSelection.end,
+                value.timeInMillis
+            )
+
+            override fun restore(value: Any): TextUndoOperation {
+                return with((value as List<*>)) {
+                    TextUndoOperation(
+                        index = get(0) as Int,
+                        preText = get(1) as String,
+                        postText = get(2) as String,
+                        preSelection = TextRange(get(3) as Int, get(4) as Int),
+                        postSelection = TextRange(get(5) as Int, get(6) as Int),
+                        timeInMillis = get(7) as Long,
+                    )
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Apply a given [TextUndoOperation] in reverse to undo this [TextFieldState].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldState.undo(op: TextUndoOperation) {
+    editWithNoSideEffects {
+        replace(op.index, op.index + op.postText.length, op.preText)
+        setSelection(op.preSelection.start, op.preSelection.end)
+    }
+}
+
+/**
+ * Apply a given [TextUndoOperation] in forward direction to redo this [TextFieldState].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldState.redo(op: TextUndoOperation) {
+    editWithNoSideEffects {
+        replace(op.index, op.index + op.preText.length, op.postText)
+        setSelection(op.postSelection.start, op.postSelection.end)
+    }
+}
+
+/**
+ * Possible types of a text operation.
+ *
+ * 1. Insert; if the edited range has 0 length, and the new text is longer than 0 length
+ * 2. Delete: if the edited range is longer than 0, and the new text has 0 length
+ * 3. Replace: All other changes.
+ */
+internal enum class TextEditType {
+    Insert, Delete, Replace
+}
+
+/**
+ * When a delete occurs during text editing, it can happen in various shapes.
+ *
+ * 1. Start; When a single character is removed to the start (towards 0) of the cursor, backspace
+ * key behavior.
+ *   "abcd|efg" -> "abc|efg"
+ * 2. End; When a single character is removed to the end (towards length) of the cursor, delete
+ * key behavior.
+ *   "abcd|efg" -> "abcd|fg"
+ * 3. Inner; When a selection of characters are removed, directionless. Both backspace and delete
+ * express the same behavior in this case.
+ *   "ab|cde|fg" -> "ab|fg"
+ * 4. NotByUser; A text editing operation that cannot be executed via a hardware or software
+ * keyboard. For example when a portion of text is removed but it's not next to a cursor or
+ * selection, or selection remains after removal.
+ *   "abcd|efg"  -> "bcd|efg"
+ *   "abc|def|g" -> "a|bc|g"
+ */
+internal enum class TextDeleteType {
+    Start, End, Inner, NotByUser
+}
+
+/**
+ * There are multiple strategies while deciding how to add certain edit operations to undo stack.
+ *   - Normally, merge is decided by UndoOperation's own merge logic, comparing itself to the
+ *   latest operation in the Undo stack.
+ *   - Programmatic updates should clear the history completely.
+ *   - Some atomic actions like cut, and paste shouldn't merge to previous or next actions.
+ */
+internal enum class TextFieldEditUndoBehavior {
+    MergeIfPossible,
+    ClearHistory,
+    NeverMerge
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
new file mode 100644
index 0000000..b27c60c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.undo
+
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * A generic purpose undo/redo stack manager.
+ *
+ * @param initialUndoStack Previous undo stack if this manager is being restored from a saved state.
+ * @param initialRedoStack Previous redo stack if this manager is being restored from a saved state.
+ * @param capacity Maximum number of elements that can be hosted by this UndoManager. Total element
+ * count is the sum of undo and redo stack sizes.
+ */
+internal class UndoManager<T>(
+    initialUndoStack: List<T> = emptyList(),
+    initialRedoStack: List<T> = emptyList(),
+    private val capacity: Int = 100
+) {
+
+    private var undoStack = SnapshotStateList<T>().apply {
+        addAll(initialUndoStack)
+    }
+    private var redoStack = SnapshotStateList<T>().apply {
+        addAll(initialRedoStack)
+    }
+
+    internal val canUndo: Boolean
+        get() = undoStack.isNotEmpty()
+
+    internal val canRedo: Boolean
+        get() = redoStack.isNotEmpty()
+
+    val size: Int
+        get() = undoStack.size + redoStack.size
+
+    init {
+        require(capacity >= 0) {
+            "Capacity must be a positive integer"
+        }
+        require(size <= capacity) {
+            "Initial list of undo and redo operations have a size=($size) greater " +
+                "than the given capacity=($capacity)."
+        }
+    }
+
+    fun record(undoableAction: T) {
+        // First clear the redo stack.
+        redoStack.clear()
+
+        while (size > capacity - 1) { // leave room for the immediate `add`
+            undoStack.removeFirst()
+        }
+        undoStack.add(undoableAction)
+    }
+
+    /**
+     * Request undo.
+     *
+     * This method returns the item that was on top of the undo stack. By the time this function
+     * returns, the given item has already been carried to the redo stack.
+     */
+    fun undo(): T {
+        check(canUndo) {
+            "It's an error to call undo while there is nothing to undo. " +
+                "Please first check `canUndo` value before calling the `undo` function."
+        }
+
+        val topOperation = undoStack.removeLast()
+
+        redoStack.add(topOperation)
+        return topOperation
+    }
+
+    /**
+     * Request redo.
+     *
+     * This method returns the item that was on top of the redo stack. By the time this function
+     * returns, the given item has already been carried back to the undo stack.
+     */
+    fun redo(): T {
+        check(canRedo) {
+            "It's an error to call redo while there is nothing to redo. " +
+                "Please first check `canRedo` value before calling the `redo` function."
+        }
+
+        val topOperation = redoStack.removeLast()
+
+        undoStack.add(topOperation)
+        return topOperation
+    }
+
+    fun clearHistory() {
+        undoStack.clear()
+        redoStack.clear()
+    }
+
+    companion object {
+
+        /**
+         * Saver factory for a generic [UndoManager].
+         *
+         * @param itemSaver Since [UndoManager] is defined as a generic class, a specific item saver
+         * is required to _serialize_ each individual item in undo and redo stacks.
+         */
+        inline fun <reified T> createSaver(
+            itemSaver: Saver<T, Any>
+        ) = object : Saver<UndoManager<T>, Any> {
+            /**
+             * Saves the contents of given [value] to a list.
+             *
+             * List's structure is
+             *   - Capacity
+             *   - n; Number of items in undo stack
+             *   - m; Number of items in redo stack
+             *   - n items in order from undo stack
+             *   - m items in order from redo stack
+             */
+            override fun SaverScope.save(value: UndoManager<T>): Any = buildList {
+                add(value.capacity)
+                add(value.undoStack.size)
+                add(value.redoStack.size)
+                value.undoStack.fastForEach {
+                    with(itemSaver) {
+                        add(save(it))
+                    }
+                }
+                value.redoStack.fastForEach {
+                    with(itemSaver) {
+                        add(save(it))
+                    }
+                }
+            }
+
+            @Suppress("UNCHECKED_CAST")
+            override fun restore(value: Any): UndoManager<T> {
+                val list = value as List<Any>
+                val (capacity, undoSize, redoSize) = (list as List<Int>)
+                var i = 3
+                val undoStackItems = buildList {
+                    while (i < undoSize + 3) {
+                        add(itemSaver.restore(list[i])!!)
+                        i++
+                    }
+                }
+                val redoStackItems = buildList {
+                    while (i < undoSize + redoSize + 3) {
+                        add(itemSaver.restore(list[i])!!)
+                        i++
+                    }
+                }
+                return UndoManager(undoStackItems, redoStackItems, capacity)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
index 248d6d7..48f1ff5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
@@ -112,6 +112,16 @@
     }
 
     fun updateTextLayout(textLayoutResult: TextLayoutResult) {
+        val prevTextLayoutResult = params.textLayoutResult
+
+        // Don't notify on null. We don't want every new Text that enters composition to
+        // notify a selectable change. It was already handled when it was created.
+        if (prevTextLayoutResult != null &&
+            prevTextLayoutResult.layoutInput.text != textLayoutResult.layoutInput.text
+        ) {
+            // Text content changed, notify selection to update itself.
+            selectionRegistrar.notifySelectableChange(selectableId)
+        }
         params = params.copy(textLayoutResult = textLayoutResult)
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index ec504b5..5808ef2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -22,8 +22,8 @@
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.TextDragObserver
+import androidx.compose.foundation.text.input.internal.coerceIn
 import androidx.compose.foundation.text.selection.Selection.AnchorInfo
-import androidx.compose.foundation.text2.input.internal.coerceIn
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -304,6 +304,8 @@
             currentDragPosition = null
         }
 
+        // This function is meant to handle changes in the selectable content,
+        // such as the text changing.
         selectionRegistrar.onSelectableChangeCallback = { selectableKey ->
             if (selectableKey in selectionRegistrar.subselections) {
                 // Clear the selection range of each Selectable.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt
deleted file mode 100644
index 64378b5..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.interaction.Interaction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.KeyCommand
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.platformDefaultKeyMapping
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.ImeActionHandler
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldBuffer
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldDecorator
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.TextObfuscationMode
-import androidx.compose.foundation.text2.input.internal.syncTextFieldState
-import androidx.compose.foundation.text2.input.mask
-import androidx.compose.foundation.text2.input.then
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.input.key.onPreviewKeyEvent
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.TextToolbar
-import androidx.compose.ui.semantics.copyText
-import androidx.compose.ui.semantics.cutText
-import androidx.compose.ui.semantics.password
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.unit.Density
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.consumeAsFlow
-import kotlinx.coroutines.launch
-
-/**
- * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
- * alternative to [BasicTextField2]. It only supports a single line of content and comes with
- * default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
- * are appropriate for entering secure content. Additionally, some context menu actions like cut,
- * copy, and drag are disabled for added security.
- *
- * Whenever the user edits the text, [onValueChange] is called with the most up to date state
- * represented by [String] with which developer is expected to update their state.
- *
- * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
- * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
- * the contents of the field will _not_ be updated to reflect the value until editing is done. When
- * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
- * an [inputTransformation] to accept or reject changes during editing. For more direct control of
- * the field contents use the [BasicSecureTextField] overload that accepts a [TextFieldState].
- *
- * @param value The input [String] text to be shown in the text field.
- * @param onValueChange The callback that is triggered when the user or the system updates the
- * text. The updated text is passed as a parameter of the callback. The value passed to the callback
- * will already have had the [inputTransformation] applied.
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param onSubmit Called when the user submits a form either by pressing the action button in the
- * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
- * submits the form by pressing the action button in the IME, the provided IME action is passed to
- * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
- * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
- * action has been handled completely, which will skip the default behavior, such as hiding the
- * keyboard for the [ImeAction.Done] action.
- * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
- * icons on the keyboard.
- * @param textObfuscationMode Determines the method used to obscure the input text.
- * @param keyboardType The keyboard type to be used in this text field. It is set to
- * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
- * fields.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when changing the [value] programmatically, or
- * when the transformation is changed. If the transformation is changed on an existing text field,
- * it will be applied to the next user edit. The transformation will not immediately affect the
- * current [value].
- * @param textStyle Style configuration for text content that's displayed in the editor.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, there will be no cursor drawn.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
- * bounds of the text field. It controls the state of the scroll for the text field.
- */
-@ExperimentalFoundationApi
-@Composable
-fun BasicSecureTextField(
-    value: String,
-    onValueChange: (String) -> Unit,
-    modifier: Modifier = Modifier,
-    // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
-    onSubmit: ImeActionHandler? = null,
-    imeAction: ImeAction = ImeAction.Default,
-    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
-    keyboardType: KeyboardType = KeyboardType.Password,
-    enabled: Boolean = true,
-    inputTransformation: InputTransformation? = null,
-    textStyle: TextStyle = TextStyle.Default,
-    interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
-    decorator: TextFieldDecorator? = null,
-    scrollState: ScrollState = rememberScrollState(),
-) {
-    val state = remember {
-        TextFieldState(
-            initialText = value,
-            // Initialize the cursor to be at the end of the field.
-            initialSelectionInChars = TextRange(value.length)
-        )
-    }
-
-    // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
-    // some state that is preserved across updates (selection).
-    var valueWithSelection by remember {
-        mutableStateOf(
-            TextFieldValue(
-                text = value,
-                selection = TextRange(value.length)
-            )
-        )
-    }
-    valueWithSelection = valueWithSelection.copy(text = value)
-
-    BasicSecureTextField(
-        state = state,
-        modifier = modifier.syncTextFieldState(
-            state = state,
-            value = valueWithSelection,
-            onValueChanged = {
-                // Don't fire the callback if only the selection/cursor changed.
-                if (it.text != valueWithSelection.text) {
-                    onValueChange(it.text)
-                }
-                valueWithSelection = it
-            },
-            writeSelectionFromTextFieldValue = false
-        ),
-        onSubmit = onSubmit,
-        imeAction = imeAction,
-        textObfuscationMode = textObfuscationMode,
-        keyboardType = keyboardType,
-        enabled = enabled,
-        inputTransformation = inputTransformation,
-        textStyle = textStyle,
-        interactionSource = interactionSource,
-        cursorBrush = cursorBrush,
-        scrollState = scrollState,
-        onTextLayout = onTextLayout,
-        decorator = decorator,
-    )
-}
-
-/**
- * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
- * alternative to [BasicTextField2]. It only supports a single line of content and comes with
- * default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
- * are appropriate for entering secure content. Additionally, some context menu actions like cut,
- * copy, and drag are disabled for added security.
- *
- * @param state [TextFieldState] object that holds the internal state of a [BasicTextField2].
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param onSubmit Called when the user submits a form either by pressing the action button in the
- * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
- * submits the form by pressing the action button in the IME, the provided IME action is passed to
- * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
- * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
- * action has been handled completely, which will skip the default behavior, such as hiding the
- * keyboard for the [ImeAction.Done] action.
- * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
- * icons on the keyboard.
- * @param textObfuscationMode Determines the method used to obscure the input text.
- * @param keyboardType The keyboard type to be used in this text field. It is set to
- * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
- * fields.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
- * when the transformation is changed. If the transformation is changed on an existing text field,
- * it will be applied to the next user edit. The transformation will not immediately affect the
- * current [state].
- * @param textStyle Style configuration for text content that's displayed in the editor.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, there will be no cursor drawn.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
- * bounds of the text field. It controls the state of the scroll for the text field.
- */
-@ExperimentalFoundationApi
-// This takes a composable lambda, but it is not primarily a container.
-@Suppress("ComposableLambdaParameterPosition")
-@Composable
-fun BasicSecureTextField(
-    state: TextFieldState,
-    modifier: Modifier = Modifier,
-    // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
-    onSubmit: ImeActionHandler? = null,
-    imeAction: ImeAction = ImeAction.Default,
-    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
-    keyboardType: KeyboardType = KeyboardType.Password,
-    enabled: Boolean = true,
-    inputTransformation: InputTransformation? = null,
-    textStyle: TextStyle = TextStyle.Default,
-    interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
-    decorator: TextFieldDecorator? = null,
-    scrollState: ScrollState = rememberScrollState(),
-    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
-    // lambda.
-) {
-    val coroutineScope = rememberCoroutineScope()
-    val secureTextFieldController = remember(coroutineScope) {
-        SecureTextFieldController(coroutineScope)
-    }
-
-    // revealing last typed character depends on two conditions;
-    // 1 - Requested Obfuscation method
-    // 2 - if the system allows it
-    val revealLastTypedEnabled = textObfuscationMode == TextObfuscationMode.RevealLastTyped
-
-    // while toggling between obfuscation methods if the revealing gets disabled, reset the reveal.
-    if (!revealLastTypedEnabled) {
-        secureTextFieldController.passwordRevealFilter.hide()
-    }
-
-    val codepointTransformation = when {
-        revealLastTypedEnabled -> {
-            secureTextFieldController.codepointTransformation
-        }
-
-        textObfuscationMode == TextObfuscationMode.Hidden -> {
-            CodepointTransformation.mask('\u2022')
-        }
-
-        else -> null
-    }
-
-    val secureTextFieldModifier = modifier
-        .semantics(mergeDescendants = true) {
-            password()
-            copyText { false }
-            cutText { false }
-        }
-        .then(
-            if (revealLastTypedEnabled) {
-                secureTextFieldController.focusChangeModifier
-            } else {
-                Modifier
-            }
-        )
-
-    DisableCutCopy {
-        BasicTextField2(
-            state = state,
-            modifier = secureTextFieldModifier,
-            enabled = enabled,
-            readOnly = false,
-            inputTransformation = if (revealLastTypedEnabled) {
-                inputTransformation.then(secureTextFieldController.passwordRevealFilter)
-            } else inputTransformation,
-            textStyle = textStyle,
-            interactionSource = interactionSource,
-            cursorBrush = cursorBrush,
-            lineLimits = TextFieldLineLimits.SingleLine,
-            scrollState = scrollState,
-            keyboardOptions = KeyboardOptions(
-                autoCorrect = false,
-                keyboardType = keyboardType,
-                imeAction = imeAction
-            ),
-            keyboardActions = onSubmit?.let { KeyboardActions(onSubmit = it::onImeAction) }
-                ?: KeyboardActions.Default,
-            onTextLayout = onTextLayout,
-            codepointTransformation = codepointTransformation,
-            decorator = decorator,
-        )
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-internal class SecureTextFieldController(
-    coroutineScope: CoroutineScope
-) {
-    /**
-     * A special [InputTransformation] that tracks changes to the content to identify the last typed
-     * character to reveal. `scheduleHide` lambda is delegated to a member function to be able to
-     * use [passwordRevealFilter] instance.
-     */
-    val passwordRevealFilter = PasswordRevealFilter(::scheduleHide)
-
-    /**
-     * Pass to [BasicTextField2] for obscuring text input.
-     */
-    val codepointTransformation = CodepointTransformation { codepointIndex, codepoint ->
-        if (codepointIndex == passwordRevealFilter.revealCodepointIndex) {
-            // reveal the last typed character by not obscuring it
-            codepoint
-        } else {
-            0x2022
-        }
-    }
-
-    val focusChangeModifier = Modifier.onFocusChanged {
-        if (!it.isFocused) passwordRevealFilter.hide()
-    }
-
-    private val resetTimerSignal = Channel<Unit>(Channel.UNLIMITED)
-
-    init {
-        // start a coroutine that listens for scheduled hide events.
-        coroutineScope.launch {
-            resetTimerSignal.consumeAsFlow()
-                .collectLatest {
-                    delay(LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS)
-                    passwordRevealFilter.hide()
-                }
-        }
-    }
-
-    private fun scheduleHide() {
-        // signal the listener that a new hide call is scheduled.
-        val result = resetTimerSignal.trySend(Unit)
-        if (!result.isSuccess) {
-            passwordRevealFilter.hide()
-        }
-    }
-}
-
-/**
- * Special filter that tracks the changes in a TextField to identify the last typed character and
- * mark it for reveal in password fields.
- *
- * @param scheduleHide A lambda that schedules a [hide] call into future after a new character is
- * typed.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal class PasswordRevealFilter(
-    val scheduleHide: () -> Unit
-) : InputTransformation {
-    // TODO: Consider setting this as a tracking annotation in AnnotatedString.
-    internal var revealCodepointIndex by mutableIntStateOf(-1)
-        private set
-
-    override fun transformInput(
-        originalValue: TextFieldCharSequence,
-        valueWithChanges: TextFieldBuffer
-    ) {
-        // We only care about a single character insertion changes
-        val singleCharacterInsertion = valueWithChanges.changes.changeCount == 1 &&
-            valueWithChanges.changes.getRange(0).length == 1 &&
-            valueWithChanges.changes.getOriginalRange(0).length == 0
-
-        // if there is an expanded selection, don't reveal anything
-        if (!singleCharacterInsertion || valueWithChanges.hasSelection) {
-            revealCodepointIndex = -1
-            return
-        }
-
-        val insertionPoint = valueWithChanges.changes.getRange(0).min
-        if (revealCodepointIndex != insertionPoint) {
-            // start the timer for auto hide
-            scheduleHide()
-            revealCodepointIndex = insertionPoint
-        }
-    }
-
-    /**
-     * Removes any revealed character index. Everything goes back into hiding.
-     */
-    fun hide() {
-        revealCodepointIndex = -1
-    }
-}
-
-// adopted from PasswordTransformationMethod from Android platform.
-private const val LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS = 1500L
-
-// TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
-@OptIn(ExperimentalFoundationApi::class)
-private fun KeyboardActions(onSubmit: ImeActionHandler) = KeyboardActions(
-    onDone = {
-        if (!onSubmit.onImeAction(ImeAction.Done)) {
-            defaultKeyboardAction(ImeAction.Done)
-        }
-    },
-    onGo = {
-        if (!onSubmit.onImeAction(ImeAction.Go)) {
-            defaultKeyboardAction(ImeAction.Go)
-        }
-    },
-    onNext = {
-        if (!onSubmit.onImeAction(ImeAction.Next)) {
-            defaultKeyboardAction(ImeAction.Next)
-        }
-    },
-    onPrevious = {
-        if (!onSubmit.onImeAction(ImeAction.Previous)) {
-            defaultKeyboardAction(ImeAction.Previous)
-        }
-    },
-    onSearch = {
-        if (!onSubmit.onImeAction(ImeAction.Search)) {
-            defaultKeyboardAction(ImeAction.Search)
-        }
-    },
-    onSend = {
-        if (!onSubmit.onImeAction(ImeAction.Send)) {
-            defaultKeyboardAction(ImeAction.Send)
-        }
-    },
-)
-
-/**
- * Overrides the TextToolbar and keyboard shortcuts to never allow copy or cut options by the
- * composables inside [content].
- */
-@Composable
-private fun DisableCutCopy(
-    content: @Composable () -> Unit
-) {
-    val currentToolbar = LocalTextToolbar.current
-    val copyDisabledToolbar = remember(currentToolbar) {
-        object : TextToolbar by currentToolbar {
-            override fun showMenu(
-                rect: Rect,
-                onCopyRequested: (() -> Unit)?,
-                onPasteRequested: (() -> Unit)?,
-                onCutRequested: (() -> Unit)?,
-                onSelectAllRequested: (() -> Unit)?
-            ) {
-                currentToolbar.showMenu(
-                    rect = rect,
-                    onPasteRequested = onPasteRequested,
-                    onSelectAllRequested = onSelectAllRequested,
-                    onCopyRequested = null,
-                    onCutRequested = null
-                )
-            }
-        }
-    }
-    CompositionLocalProvider(LocalTextToolbar provides copyDisabledToolbar) {
-        Box(modifier = Modifier.onPreviewKeyEvent { keyEvent ->
-            // BasicTextField2 uses this static mapping
-            val command = platformDefaultKeyMapping.map(keyEvent)
-            // do not propagate copy and cut operations
-            command == KeyCommand.COPY || command == KeyCommand.CUT
-        }) {
-            content()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
deleted file mode 100644
index b893c00..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
-import androidx.compose.foundation.gestures.scrollable
-import androidx.compose.foundation.interaction.Interaction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
-import androidx.compose.foundation.interaction.collectIsHoveredAsState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.relocation.bringIntoViewRequester
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.CursorHandle
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.heightInLines
-import androidx.compose.foundation.text.selection.SelectionHandle
-import androidx.compose.foundation.text.selection.SelectionHandleAnchor
-import androidx.compose.foundation.text.selection.SelectionHandleInfo
-import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
-import androidx.compose.foundation.text.textFieldMinSize
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.OutputTransformation
-import androidx.compose.foundation.text2.input.SingleLineCodepointTransformation
-import androidx.compose.foundation.text2.input.TextFieldDecorator
-import androidx.compose.foundation.text2.input.TextFieldLineLimits
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.TextFieldCoreModifier
-import androidx.compose.foundation.text2.input.internal.TextFieldDecoratorModifier
-import androidx.compose.foundation.text2.input.internal.TextFieldTextLayoutModifier
-import androidx.compose.foundation.text2.input.internal.TextLayoutState
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
-import androidx.compose.foundation.text2.input.internal.syncTextFieldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalHapticFeedback
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalTextToolbar
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
-
-/**
- * Basic text composable that provides an interactive box that accepts text input through software
- * or hardware keyboard, but provides no decorations like hint or placeholder.
- *
- * Whenever the user edits the text, [onValueChange] is called with the most up to date state
- * represented by [String] with which developer is expected to update their state.
- *
- * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
- * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
- * the contents of the field will _not_ be updated to reflect the value until editing is done. When
- * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
- * a [inputTransformation] to accept or reject changes during editing. For more direct control of
- * the field contents use the [BasicTextField2] overload that accepts a [TextFieldState].
- *
- * Unlike [TextFieldState] overload, this composable does not let the developer control selection,
- * cursor, and observe text composition information. Please check [TextFieldState] and corresponding
- * [BasicTextField2] overload for more information.
- *
- * If you want to add decorations to your text field, such as icon or similar, and increase the
- * hit target area, use the decorator:
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
- *
- * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
- * convert every character to uppercase) the input received from the user, use an
- * [InputTransformation].
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
- *
- * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
- * direction can be achieved by using [TextFieldLineLimits].
- *
- * Scroll state of the composable is also hoisted to enable observation and manipulation of the
- * scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
- * position without focusing, or changing selection.
- *
- * @param value The input [String] text to be shown in the text field.
- * @param onValueChange The callback that is triggered when the user or the system updates the
- * text. The updated text is passed as a parameter of the callback. The value passed to the callback
- * will already have had the [inputTransformation] applied.
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
- * field can not be modified, however, a user can focus it and copy text from it. Read-only text
- * fields are usually used to display pre-filled forms that user can not edit.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when a new [value] is passed in, or when the
- * transformation is changed. If the transformation is changed on an existing text field, it will be
- * applied to the next user edit, it will not immediately affect the current [value].
- * @param textStyle Typographic and graphic style configuration for text content that's displayed
- * in the editor.
- * @param keyboardOptions Software keyboard options that contain configurations such as
- * [KeyboardType] and [ImeAction].
- * @param keyboardActions When the input service emits an IME action, the corresponding callback
- * is called. Note that this IME action may be different from what you specified in
- * [KeyboardOptions.imeAction].
- * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and
- * ignore newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed without
- * specifying the [outputTransformation] parameter, a [CodepointTransformation] is automatically
- * applied. This transformation replaces any newline characters ('\n') within the text with regular
- * whitespace (' '), ensuring that the contents of the text field are presented in a single line.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, then no cursor will be drawn.
- * @param codepointTransformation Visual transformation interface that provides a 1-to-1 mapping of
- * codepoints.
- * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
- * text field are presented.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
- * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
- * scroll behavior. In other cases the text field becomes vertically scrollable.
- * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
- * text field are presented.
- */
-@ExperimentalFoundationApi
-// This takes a composable lambda, but it is not primarily a container.
-@Suppress("ComposableLambdaParameterPosition")
-@Composable
-fun BasicTextField2(
-    value: String,
-    onValueChange: (String) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    inputTransformation: InputTransformation? = null,
-    textStyle: TextStyle = TextStyle.Default,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
-    interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
-    codepointTransformation: CodepointTransformation? = null,
-    outputTransformation: OutputTransformation? = null,
-    decorator: TextFieldDecorator? = null,
-    scrollState: ScrollState = rememberScrollState(),
-    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
-    // lambda.
-) {
-    val state = remember {
-        TextFieldState(
-            initialText = value,
-            // Initialize the cursor to be at the end of the field.
-            initialSelectionInChars = TextRange(value.length)
-        )
-    }
-
-    // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
-    // some state that is preserved across updates (selection).
-    var valueWithSelection by remember {
-        mutableStateOf(
-            TextFieldValue(
-                text = value,
-                selection = TextRange(value.length)
-            )
-        )
-    }
-    valueWithSelection = valueWithSelection.copy(text = value)
-
-    BasicTextField2(
-        state = state,
-        modifier = modifier.syncTextFieldState(
-            state = state,
-            value = valueWithSelection,
-            onValueChanged = {
-                // Don't fire the callback if only the selection/cursor changed.
-                if (it.text != valueWithSelection.text) {
-                    onValueChange(it.text)
-                }
-                valueWithSelection = it
-            },
-            writeSelectionFromTextFieldValue = false
-        ),
-        enabled = enabled,
-        readOnly = readOnly,
-        inputTransformation = inputTransformation,
-        textStyle = textStyle,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        lineLimits = lineLimits,
-        onTextLayout = onTextLayout,
-        interactionSource = interactionSource,
-        cursorBrush = cursorBrush,
-        scrollState = scrollState,
-        codepointTransformation = codepointTransformation,
-        outputTransformation = outputTransformation,
-        decorator = decorator,
-    )
-}
-
-/**
- * Basic text composable that provides an interactive box that accepts text input through software
- * or hardware keyboard, but provides no decorations like hint or placeholder.
- *
- * All the editing state of this composable is hoisted through [state]. Whenever the contents of
- * this composable change via user input or semantics, [TextFieldState.text] gets updated.
- * Similarly, all the programmatic updates made to [state] also reflect on this composable.
- *
- * If you want to add decorations to your text field, such as icon or similar, and increase the
- * hit target area, use the decorator:
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
- *
- * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
- * convert every character to uppercase) the input received from the user, use an
- * [InputTransformation].
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
- *
- * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
- * direction can be achieved by using [TextFieldLineLimits].
- *
- * Scroll state of the composable is also hoisted to enable observation and manipulation of the
- * scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
- * position without focusing, or changing selection.
- *
- * @param state [TextFieldState] object that holds the internal editing state of [BasicTextField2].
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
- * field can not be modified, however, a user can focus it and copy text from it. Read-only text
- * fields are usually used to display pre-filled forms that user can not edit.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
- * when the transformation is changed. If the transformation is changed on an existing text field,
- * it will be applied to the next user edit. the transformation will not immediately affect the
- * current [state].
- * @param textStyle Typographic and graphic style configuration for text content that's displayed
- * in the editor.
- * @param keyboardOptions Software keyboard options that contain configurations such as
- * [KeyboardType] and [ImeAction].
- * @param keyboardActions When the input service emits an IME action, the corresponding callback
- * is called. Note that this IME action may be different from what you specified in
- * [KeyboardOptions.imeAction].
- * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and
- * ignore newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed without
- * specifying the [codepointTransformation] parameter, a [CodepointTransformation] is automatically
- * applied. This transformation replaces any newline characters ('\n') within the text with regular
- * whitespace (' '), ensuring that the contents of the text field are presented in a single line.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, then no cursor will be drawn.
- * @param codepointTransformation Visual transformation interface that provides a 1-to-1 mapping of
- * codepoints.
- * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
- * text field are presented.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
- * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
- * scroll behavior. In other cases the text field becomes vertically scrollable.
- */
-@ExperimentalFoundationApi
-// This takes a composable lambda, but it is not primarily a container.
-@Suppress("ComposableLambdaParameterPosition")
-@Composable
-fun BasicTextField2(
-    state: TextFieldState,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    inputTransformation: InputTransformation? = null,
-    textStyle: TextStyle = TextStyle.Default,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
-    interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
-    codepointTransformation: CodepointTransformation? = null,
-    outputTransformation: OutputTransformation? = null,
-    decorator: TextFieldDecorator? = null,
-    scrollState: ScrollState = rememberScrollState(),
-    // Last parameter must not be a function unless it's intended to be commonly used as a trailing
-    // lambda.
-) {
-    val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-    val windowInfo = LocalWindowInfo.current
-    val singleLine = lineLimits == SingleLine
-    // We're using this to communicate focus state to cursor for now.
-    @Suppress("NAME_SHADOWING")
-    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-    val orientation = if (singleLine) Orientation.Horizontal else Orientation.Vertical
-    val isFocused = interactionSource.collectIsFocusedAsState().value
-    val isDragHovered = interactionSource.collectIsHoveredAsState().value
-    val isWindowFocused = windowInfo.isWindowFocused
-
-    val transformedState = remember(
-        state,
-        inputTransformation,
-        codepointTransformation,
-        outputTransformation
-    ) {
-        // First prefer provided codepointTransformation if not null, e.g. BasicSecureTextField
-        // would send PasswordTransformation. Second, apply a SingleLineCodepointTransformation if
-        // text field is configured to be single line. Else, don't apply any visual transformation.
-        val appliedCodepointTransformation = codepointTransformation
-            ?: SingleLineCodepointTransformation.takeIf { singleLine }
-        TransformedTextFieldState(
-            textFieldState = state,
-            inputTransformation = inputTransformation,
-            codepointTransformation = appliedCodepointTransformation,
-            outputTransformation = outputTransformation
-        )
-    }
-
-    // Invalidate textLayoutState if TextFieldState itself has changed, since TextLayoutState
-    // would be carrying an invalid TextFieldState in its nonMeasureInputs.
-    val textLayoutState = remember(transformedState) { TextLayoutState() }
-
-    val textFieldSelectionState = remember(transformedState) {
-        TextFieldSelectionState(
-            textFieldState = transformedState,
-            textLayoutState = textLayoutState,
-            density = density,
-            enabled = enabled,
-            readOnly = readOnly,
-            isFocused = isFocused && isWindowFocused
-        )
-    }
-    val currentHapticFeedback = LocalHapticFeedback.current
-    val currentClipboardManager = LocalClipboardManager.current
-    val currentTextToolbar = LocalTextToolbar.current
-    SideEffect {
-        // These properties are not backed by snapshot state, so they can't be updated directly in
-        // composition.
-        textFieldSelectionState.update(
-            hapticFeedBack = currentHapticFeedback,
-            clipboardManager = currentClipboardManager,
-            textToolbar = currentTextToolbar,
-            density = density,
-            enabled = enabled,
-            readOnly = readOnly,
-        )
-    }
-
-    DisposableEffect(textFieldSelectionState) {
-        onDispose {
-            textFieldSelectionState.dispose()
-        }
-    }
-
-    val decorationModifiers = modifier
-        .then(
-            // semantics + some focus + input session + touch to focus
-            TextFieldDecoratorModifier(
-                textFieldState = transformedState,
-                textLayoutState = textLayoutState,
-                textFieldSelectionState = textFieldSelectionState,
-                filter = inputTransformation,
-                enabled = enabled,
-                readOnly = readOnly,
-                keyboardOptions = keyboardOptions,
-                keyboardActions = keyboardActions,
-                singleLine = singleLine,
-                interactionSource = interactionSource
-            )
-        )
-        .focusable(interactionSource = interactionSource, enabled = enabled)
-        .scrollable(
-            state = scrollState,
-            orientation = orientation,
-            // Disable scrolling when textField is disabled, there is no where to scroll, and
-            // another dragging gesture is taking place
-            enabled = enabled &&
-                scrollState.maxValue > 0 &&
-                textFieldSelectionState.draggingHandle == null,
-            reverseDirection = ScrollableDefaults.reverseDirection(
-                layoutDirection = layoutDirection,
-                orientation = orientation,
-                reverseScrolling = false
-            ),
-            interactionSource = interactionSource,
-        )
-
-    Box(decorationModifiers, propagateMinConstraints = true) {
-        val nonNullDecorator = decorator ?: DefaultTextFieldDecorator
-        nonNullDecorator.Decoration {
-            val minLines: Int
-            val maxLines: Int
-            if (lineLimits is MultiLine) {
-                minLines = lineLimits.minHeightInLines
-                maxLines = lineLimits.maxHeightInLines
-            } else {
-                minLines = 1
-                maxLines = 1
-            }
-
-            Box(
-                propagateMinConstraints = true,
-                modifier = Modifier
-                    .heightIn(min = textLayoutState.minHeightForSingleLineField)
-                    .heightInLines(
-                        textStyle = textStyle,
-                        minLines = minLines,
-                        maxLines = maxLines
-                    )
-                    .textFieldMinSize(textStyle)
-                    .clipToBounds()
-                    .then(
-                        TextFieldCoreModifier(
-                            isFocused = isFocused && isWindowFocused,
-                            isDragHovered = isDragHovered,
-                            textLayoutState = textLayoutState,
-                            textFieldState = transformedState,
-                            textFieldSelectionState = textFieldSelectionState,
-                            cursorBrush = cursorBrush,
-                            writeable = enabled && !readOnly,
-                            scrollState = scrollState,
-                            orientation = orientation
-                        )
-                    )
-            ) {
-                Box(
-                    modifier = Modifier
-                        .bringIntoViewRequester(textLayoutState.bringIntoViewRequester)
-                        .then(
-                            TextFieldTextLayoutModifier(
-                                textLayoutState = textLayoutState,
-                                textFieldState = transformedState,
-                                textStyle = textStyle,
-                                singleLine = singleLine,
-                                onTextLayout = onTextLayout
-                            )
-                        )
-                )
-
-                if (enabled && isFocused &&
-                    isWindowFocused && textFieldSelectionState.isInTouchMode
-                ) {
-                    TextFieldSelectionHandles(
-                        selectionState = textFieldSelectionState
-                    )
-                    if (!readOnly) {
-                        TextFieldCursorHandle(
-                            selectionState = textFieldSelectionState
-                        )
-                    }
-                }
-            }
-        }
-    }
-}
-
-@Composable
-internal fun TextFieldCursorHandle(selectionState: TextFieldSelectionState) {
-    val cursorHandleState = selectionState.cursorHandle
-    if (cursorHandleState.visible) {
-        CursorHandle(
-            handlePosition = cursorHandleState.position,
-            modifier = Modifier
-                .semantics {
-                    this[SelectionHandleInfoKey] = SelectionHandleInfo(
-                        handle = Handle.Cursor,
-                        position = cursorHandleState.position,
-                        anchor = SelectionHandleAnchor.Middle,
-                        visible = true,
-                    )
-                }
-                .pointerInput(selectionState) {
-                    with(selectionState) { cursorHandleGestures() }
-                },
-            minTouchTargetSize = MinTouchTargetSizeForHandles,
-        )
-    }
-}
-
-@Composable
-internal fun TextFieldSelectionHandles(
-    selectionState: TextFieldSelectionState
-) {
-    // Does not recompose if only position of the handle changes.
-    val startHandleState by remember {
-        derivedStateOf {
-            selectionState.getSelectionHandleState(isStartHandle = true, includePosition = false)
-        }
-    }
-    if (startHandleState.visible) {
-        SelectionHandle(
-            offsetProvider = {
-                selectionState
-                    .getSelectionHandleState(isStartHandle = true, includePosition = true)
-                    .position
-            },
-            isStartHandle = true,
-            direction = startHandleState.direction,
-            handlesCrossed = startHandleState.handlesCrossed,
-            modifier = Modifier.pointerInput(selectionState) {
-                with(selectionState) { selectionHandleGestures(true) }
-            },
-            minTouchTargetSize = MinTouchTargetSizeForHandles,
-        )
-    }
-
-    // Does not recompose if only position of the handle changes.
-    val endHandleState by remember {
-        derivedStateOf {
-            selectionState.getSelectionHandleState(isStartHandle = false, includePosition = false)
-        }
-    }
-    if (endHandleState.visible) {
-        SelectionHandle(
-            offsetProvider = {
-                selectionState
-                    .getSelectionHandleState(isStartHandle = false, includePosition = true)
-                    .position
-            },
-            isStartHandle = false,
-            direction = endHandleState.direction,
-            handlesCrossed = endHandleState.handlesCrossed,
-            modifier = Modifier.pointerInput(selectionState) {
-                with(selectionState) { selectionHandleGestures(false) }
-            },
-            minTouchTargetSize = MinTouchTargetSizeForHandles,
-        )
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private val DefaultTextFieldDecorator = TextFieldDecorator { it() }
-
-/**
- * Defines a minimum touch target area size for Selection and Cursor handles.
- *
- * Although BasicTextField is not part of Material spec, this accessibility feature is important
- * enough to be included at foundation layer, and also TextField cannot change selection handles
- * provided by BasicTextField to somehow achieve this accessibility requirement.
- *
- * This value is adopted from Android platform's TextView implementation.
- */
-private val MinTouchTargetSizeForHandles = DpSize(40.dp, 40.dp)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt
deleted file mode 100644
index 81efc4c..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.appendCodePointX
-import androidx.compose.foundation.text2.input.internal.OffsetMappingCalculator
-import androidx.compose.foundation.text2.input.internal.charCount
-import androidx.compose.foundation.text2.input.internal.codePointAt
-import androidx.compose.runtime.Stable
-
-/**
- * Visual transformation interface for input fields.
- *
- * This interface is responsible for 1-to-1 mapping of every codepoint in input state to another
- * codepoint before text is rendered. Visual transformation is useful when the underlying source
- * of input needs to remain but rendered content should look different, e.g. password obscuring.
- */
-@ExperimentalFoundationApi
-@Stable
-fun interface CodepointTransformation {
-
-    /**
-     * Transforms a single [codepoint] located at [codepointIndex] to another codepoint.
-     *
-     * A codepoint is an integer that always maps to a single character. Every codepoint in Unicode
-     * is comprised of 16 bits, 2 bytes.
-     */
-    // TODO: add more codepoint explanation or doc referral
-    fun transform(codepointIndex: Int, codepoint: Int): Int
-
-    companion object
-}
-
-/**
- * Creates a masking [CodepointTransformation] that maps all codepoints to a specific [character].
- */
-@ExperimentalFoundationApi
-@Stable
-fun CodepointTransformation.Companion.mask(character: Char): CodepointTransformation =
-    MaskCodepointTransformation(character)
-
-@OptIn(ExperimentalFoundationApi::class)
-private data class MaskCodepointTransformation(val character: Char) : CodepointTransformation {
-    override fun transform(codepointIndex: Int, codepoint: Int): Int {
-        return character.code
-    }
-}
-
-/**
- * [CodepointTransformation] that converts all line breaks (\n) into white space(U+0020) and
- * carriage returns(\r) to zero-width no-break space (U+FEFF). This transformation forces any
- * content to appear as single line.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal object SingleLineCodepointTransformation : CodepointTransformation {
-
-    private const val LINE_FEED = '\n'.code
-    private const val CARRIAGE_RETURN = '\r'.code
-
-    private const val WHITESPACE = ' '.code
-    private const val ZERO_WIDTH_SPACE = '\uFEFF'.code
-
-    override fun transform(codepointIndex: Int, codepoint: Int): Int {
-        if (codepoint == LINE_FEED) return WHITESPACE
-        if (codepoint == CARRIAGE_RETURN) return ZERO_WIDTH_SPACE
-        return codepoint
-    }
-
-    override fun toString(): String {
-        return "SingleLineCodepointTransformation"
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence.toVisualText(
-    codepointTransformation: CodepointTransformation,
-    offsetMappingCalculator: OffsetMappingCalculator
-): CharSequence {
-    val text = this
-    var changed = false
-    val newText = buildString {
-        var charOffset = 0
-        var codePointOffset = 0
-        while (charOffset < text.length) {
-            val codePoint = text.codePointAt(charOffset)
-            val newCodePoint = codepointTransformation.transform(codePointOffset, codePoint)
-            val charCount = charCount(codePoint)
-            if (newCodePoint != codePoint) {
-                changed = true
-                val newCharCount = charCount(newCodePoint)
-                offsetMappingCalculator.recordEditOperation(
-                    sourceStart = length,
-                    sourceEnd = length + charCount,
-                    newLength = newCharCount
-                )
-            }
-            appendCodePointX(newCodePoint)
-
-            charOffset += charCount
-            codePointOffset += 1
-        }
-    }
-
-    // Return the same instance if nothing changed, which signals to the caller that nothing changed
-    // and allows the new string to be GC'd earlier.
-    return if (changed) newText else this
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/ImeActionHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/ImeActionHandler.kt
deleted file mode 100644
index 18eb84c..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/ImeActionHandler.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.text.input.ImeAction
-
-// TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
-@ExperimentalFoundationApi
-@Stable
-fun interface ImeActionHandler {
-    fun onImeAction(action: ImeAction): Boolean
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/InputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/InputTransformation.kt
deleted file mode 100644
index ead2e22..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/InputTransformation.kt
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.substring
-import androidx.compose.ui.text.toUpperCase
-
-/**
- * A function that is ran after every change made to a [TextFieldState] by user input and can change
- * or reject that input.
- *
- * Input transformations are ran after hardware and software keyboard events, when text is pasted or
- * dropped into the field, or when an accessibility service changes the text.
- *
- * To chain filters together, call [then].
- *
- * Prebuilt filters are provided for common filter operations. See:
- *  - [InputTransformation].[maxLengthInChars]`()`
- *  - [InputTransformation].[maxLengthInCodepoints]`()`
- *  - [InputTransformation].[allCaps]`()`
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
- */
-@ExperimentalFoundationApi
-@Stable
-fun interface InputTransformation {
-
-    /**
-     * Optional [KeyboardOptions] that will be used as the default keyboard options for configuring
-     * the IME. The options passed directly to the text field composable will always override this.
-     */
-    val keyboardOptions: KeyboardOptions? get() = null
-
-    /**
-     * The transform operation. For more information see the documentation on [InputTransformation].
-     *
-     * To reject all changes in [valueWithChanges], call
-     * `valueWithChanges.`[revertAllChanges][TextFieldBuffer.revertAllChanges].
-     *
-     * @param originalValue The value of the field before the change was performed.
-     * @param valueWithChanges The value of the field after the change. This value can be changed
-     * in-place to alter or reject the changes or set the selection.
-     */
-    fun transformInput(originalValue: TextFieldCharSequence, valueWithChanges: TextFieldBuffer)
-
-    companion object : InputTransformation {
-        override fun transformInput(
-            originalValue: TextFieldCharSequence,
-            valueWithChanges: TextFieldBuffer
-        ) {
-            // Noop.
-        }
-    }
-}
-
-// region Pre-built transformations
-
-/**
- * Creates a filter chain that will run [next] after this. Filters are applied sequentially, so any
- * changes made by this filter will be visible to [next].
- *
- * The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
- * use the options from this transformation.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
- *
- * @param next The [InputTransformation] that will be ran after this one.
- */
-@ExperimentalFoundationApi
-@Stable
[email protected]("thenOrNull")
-fun InputTransformation?.then(next: InputTransformation?): InputTransformation? = when {
-    this == null -> next
-    next == null -> this
-    else -> this.then(next)
-}
-
-/**
- * Creates a filter chain that will run [next] after this. Filters are applied sequentially, so any
- * changes made by this filter will be visible to [next].
- *
- * The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
- * use the options from this transformation.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
- *
- * @param next The [InputTransformation] that will be ran after this one.
- */
-@ExperimentalFoundationApi
-@Stable
-fun InputTransformation.then(next: InputTransformation): InputTransformation =
-    FilterChain(this, next)
-
-/**
- * Creates an [InputTransformation] from a function that accepts both the old and proposed
- * [TextFieldCharSequence] and returns the [TextFieldCharSequence] to use for the field.
- *
- * [transformation] can return either `old`, `proposed`, or a completely different value.
- *
- * The selection or cursor will be updated automatically. For more control of selection
- * implement [InputTransformation] directly.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueChooseSample
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueReplaceSample
- */
-@ExperimentalFoundationApi
-@Stable
-fun InputTransformation.byValue(
-    transformation: (
-        current: CharSequence,
-        proposed: CharSequence
-    ) -> CharSequence
-): InputTransformation = this.then(InputTransformationByValue(transformation))
-
-/**
- * Returns a [InputTransformation] that forces all text to be uppercase.
- *
- * This transformation automatically configures the keyboard to capitalize all characters.
- *
- * @param locale The [Locale] in which to perform the case conversion.
- */
-@ExperimentalFoundationApi
-@Stable
-fun InputTransformation.allCaps(locale: Locale): InputTransformation =
-    this.then(AllCapsTransformation(locale))
-
-/**
- * Returns [InputTransformation] that rejects input which causes the total length of the text field to be
- * more than [maxLength] characters.
- *
- * @see maxLengthInCodepoints
- */
-@ExperimentalFoundationApi
-@Stable
-fun InputTransformation.maxLengthInChars(maxLength: Int): InputTransformation =
-    this.then(MaxLengthFilter(maxLength, inCodepoints = false))
-
-/**
- * Returns a [InputTransformation] that rejects input which causes the total length of the text field to
- * be more than [maxLength] codepoints.
- *
- * @see maxLengthInChars
- */
-@ExperimentalFoundationApi
-@Stable
-fun InputTransformation.maxLengthInCodepoints(maxLength: Int): InputTransformation =
-    this.then(MaxLengthFilter(maxLength, inCodepoints = true))
-
-// endregion
-// region Transformation implementations
-
-@OptIn(ExperimentalFoundationApi::class)
-private class FilterChain(
-    private val first: InputTransformation,
-    private val second: InputTransformation,
-) : InputTransformation {
-
-    override val keyboardOptions: KeyboardOptions?
-        // TODO(b/295951492) Do proper merging.
-        get() = second.keyboardOptions ?: first.keyboardOptions
-
-    override fun transformInput(
-        originalValue: TextFieldCharSequence,
-        valueWithChanges: TextFieldBuffer
-    ) {
-        first.transformInput(originalValue, valueWithChanges)
-        second.transformInput(originalValue, valueWithChanges)
-    }
-
-    override fun toString(): String = "$first.then($second)"
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other === null) return false
-        if (this::class != other::class) return false
-
-        other as FilterChain
-
-        if (first != other.first) return false
-        if (second != other.second) return false
-        if (keyboardOptions != other.keyboardOptions) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = first.hashCode()
-        result = 31 * result + second.hashCode()
-        result = 32 * result + keyboardOptions.hashCode()
-        return result
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private data class InputTransformationByValue(
-    val transformation: (
-        old: CharSequence,
-        proposed: CharSequence
-    ) -> CharSequence
-) : InputTransformation {
-    override fun transformInput(
-        originalValue: TextFieldCharSequence,
-        valueWithChanges: TextFieldBuffer
-    ) {
-        val proposed = valueWithChanges.toTextFieldCharSequence()
-        val accepted = transformation(originalValue, proposed)
-        when {
-            // These are reference comparisons – text comparison will be done by setTextIfChanged.
-            accepted === proposed -> return
-            accepted === originalValue -> valueWithChanges.revertAllChanges()
-            else -> {
-                valueWithChanges.setTextIfChanged(accepted)
-            }
-        }
-    }
-
-    override fun toString(): String = "InputTransformation.byValue(transformation=$transformation)"
-}
-
-// This is a very naive implementation for now, not intended to be production-ready.
-@OptIn(ExperimentalFoundationApi::class)
-private data class AllCapsTransformation(private val locale: Locale) : InputTransformation {
-    override val keyboardOptions = KeyboardOptions(
-        capitalization = KeyboardCapitalization.Characters
-    )
-
-    override fun transformInput(
-        originalValue: TextFieldCharSequence,
-        valueWithChanges: TextFieldBuffer
-    ) {
-        // only update inserted content
-        valueWithChanges.changes.forEachChange { range, _ ->
-            if (!range.collapsed) {
-                valueWithChanges.replace(
-                    range.min,
-                    range.max,
-                    valueWithChanges.asCharSequence().substring(range).toUpperCase(locale)
-                )
-            }
-        }
-    }
-
-    override fun toString(): String = "InputTransformation.allCaps(locale=$locale)"
-}
-
-// This is a very naive implementation for now, not intended to be production-ready.
-@OptIn(ExperimentalFoundationApi::class)
-private data class MaxLengthFilter(
-    private val maxLength: Int,
-    private val inCodepoints: Boolean
-) : InputTransformation {
-
-    init {
-        require(maxLength >= 0) { "maxLength must be at least zero, was $maxLength" }
-    }
-
-    override fun transformInput(
-        originalValue: TextFieldCharSequence,
-        valueWithChanges: TextFieldBuffer
-    ) {
-        val newLength =
-            if (inCodepoints) valueWithChanges.codepointLength else valueWithChanges.length
-        if (newLength > maxLength) {
-            valueWithChanges.revertAllChanges()
-        }
-    }
-
-    override fun toString(): String {
-        val name = if (inCodepoints) "maxLengthInCodepoints" else "maxLengthInChars"
-        return "InputTransformation.$name(maxLength=$maxLength)"
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/OutputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/OutputTransformation.kt
deleted file mode 100644
index db02f2e..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/OutputTransformation.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.runtime.Stable
-
-/**
- * A function ([transformOutput]) that transforms the text presented to a user by a
- * [BasicTextField2].
- */
-@ExperimentalFoundationApi
-@Stable
-fun interface OutputTransformation {
-
-    /**
-     * Given a [TextFieldBuffer] that contains the contents of a [TextFieldState], modifies the
-     * text. After this function returns, the contents of the buffer will be presented to the user
-     * as the contents of the text field instead of the raw contents of the [TextFieldState].
-     *
-     * Note that the contents of the [TextFieldState] remain completely unchanged. This is a one-way
-     * transformation that only affects what is presented to the user.
-     */
-    fun TextFieldBuffer.transformOutput()
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
deleted file mode 100644
index 7b8e982..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
+++ /dev/null
@@ -1,658 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList
-import androidx.compose.foundation.text2.input.internal.ChangeTracker
-import androidx.compose.foundation.text2.input.internal.OffsetMappingCalculator
-import androidx.compose.foundation.text2.input.internal.PartialGapBuffer
-import androidx.compose.ui.text.TextRange
-
-/**
- * A text buffer that can be edited, similar to [StringBuilder].
- *
- * This class provides methods for changing the text, such as:
- *  - [replace]
- *  - [append]
- *  - [insert]
- *  - [delete]
- *
- * This class also stores and tracks the cursor position or selection range. The cursor position is
- * just a selection range with zero length. The cursor and selection can be changed using methods
- * such as:
- *  - [placeCursorAfterCodepointAt]
- *  - [placeCursorAfterCharAt]
- *  - [placeCursorBeforeCodepointAt]
- *  - [placeCursorBeforeCharAt]
- *  - [placeCursorAtEnd]
- *  - [selectAll]
- *
- * To get one of these, and for usage samples, see [TextFieldState.edit]. Every change to the buffer
- * is tracked in a [ChangeList] which you can access via the [changes] property.
- */
-@ExperimentalFoundationApi
-class TextFieldBuffer internal constructor(
-    initialValue: TextFieldCharSequence,
-    initialChanges: ChangeTracker? = null,
-    /**
-     * The value reverted to when [revertAllChanges] is called. This is not necessarily
-     * [initialValue] since the initial value may have already have had some intermediate changes
-     * applied to it.
-     */
-    private val sourceValue: TextFieldCharSequence = initialValue,
-    private val offsetMappingCalculator: OffsetMappingCalculator? = null,
-) : Appendable {
-
-    private val buffer = PartialGapBuffer(initialValue)
-
-    /**
-     * Lazily-allocated [ChangeTracker], initialized on the first text change.
-     */
-    private var changeTracker: ChangeTracker? =
-        initialChanges?.let { ChangeTracker(initialChanges) }
-
-    /**
-     * The number of characters in the text field. This will be equal to or greater than
-     * [codepointLength].
-     */
-    val length: Int get() = buffer.length
-
-    /**
-     * The number of codepoints in the text field. This will be equal to or less than [length].
-     */
-    val codepointLength: Int get() = Character.codePointCount(buffer, 0, length)
-
-    /**
-     * The [ChangeList] represents the changes made to this value and is inherently mutable. This
-     * means that the returned [ChangeList] always reflects the complete list of changes made to
-     * this value at any given time, even those made after reading this property.
-     *
-     * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
-     * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
-     */
-    val changes: ChangeList get() = changeTracker ?: EmptyChangeList
-
-    /**
-     * True if the selection range has non-zero length. If this is false, then the selection
-     * represents the cursor.
-     *
-     * @see selectionInChars
-     */
-    @get:JvmName("hasSelection")
-    val hasSelection: Boolean
-        get() = !selectionInChars.collapsed
-
-    /**
-     * The selected range of characters.
-     *
-     * @see selectionInCodepoints
-     */
-    var selectionInChars: TextRange = initialValue.selectionInChars
-        private set
-
-    /**
-     * The selected range of codepoints.
-     *
-     * @see selectionInChars
-     */
-    val selectionInCodepoints: TextRange
-        get() = charsToCodepoints(selectionInChars)
-
-    /**
-     * Replaces the text between [start] (inclusive) and [end] (exclusive) in this value with
-     * [text], and records the change in [changes].
-     *
-     * @param start The character offset of the first character to replace.
-     * @param end The character offset of the first character after the text to replace.
-     * @param text The text to replace the range `[start, end)` with.
-     *
-     * @see append
-     * @see insert
-     * @see delete
-     */
-    fun replace(start: Int, end: Int, text: CharSequence) {
-        replace(start, end, text, 0, text.length)
-    }
-
-    /**
-     * Replaces the text between [start] (inclusive) and [end] (exclusive) in this value with
-     * [text], and records the change in [changes].
-     *
-     * @param start The character offset of the first character to replace.
-     * @param end The character offset of the first character after the text to replace.
-     * @param text The text to replace the range `[start, end)` with.
-     * @param textStart The character offset of the first character in [text] to copy.
-     * @param textEnd The character offset after the last character in [text] to copy.
-     *
-     * @see append
-     * @see insert
-     * @see delete
-     */
-    internal fun replace(
-        start: Int,
-        end: Int,
-        text: CharSequence,
-        textStart: Int = 0,
-        textEnd: Int = text.length
-    ) {
-        require(start <= end) { "Expected start=$start <= end=$end" }
-        require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
-        onTextWillChange(start, end, textEnd - textStart)
-        buffer.replace(start, end, text, textStart, textEnd)
-    }
-
-    /**
-     * Similar to `replace(0, length, newText)` but only records a change if [newText] is actually
-     * different from the current buffer value.
-     */
-    internal fun setTextIfChanged(newText: CharSequence) {
-        findCommonPrefixAndSuffix(buffer, newText) { thisStart, thisEnd, newStart, newEnd ->
-            replace(thisStart, thisEnd, newText, newStart, newEnd)
-        }
-    }
-
-    // Doc inherited from Appendable.
-    // This append overload should be first so it ends up being the target of links to this method.
-    override fun append(text: CharSequence?): Appendable = apply {
-        if (text != null) {
-            onTextWillChange(length, length, text.length)
-            buffer.replace(buffer.length, buffer.length, text)
-        }
-    }
-
-    // Doc inherited from Appendable.
-    override fun append(text: CharSequence?, start: Int, end: Int): Appendable = apply {
-        if (text != null) {
-            onTextWillChange(length, length, end - start)
-            buffer.replace(buffer.length, buffer.length, text.subSequence(start, end))
-        }
-    }
-
-    // Doc inherited from Appendable.
-    override fun append(char: Char): Appendable = apply {
-        onTextWillChange(length, length, 1)
-        buffer.replace(buffer.length, buffer.length, char.toString())
-    }
-
-    /**
-     * Called just before the text contents are about to change.
-     *
-     * @param replaceStart The first offset to be replaced (inclusive).
-     * @param replaceEnd The last offset to be replaced (exclusive).
-     * @param newLength The length of the replacement.
-     */
-    private fun onTextWillChange(replaceStart: Int, replaceEnd: Int, newLength: Int) {
-        (changeTracker ?: ChangeTracker().also { changeTracker = it })
-            .trackChange(replaceStart, replaceEnd, newLength)
-        offsetMappingCalculator?.recordEditOperation(replaceStart, replaceEnd, newLength)
-
-        // Adjust selection.
-        val start = minOf(replaceStart, replaceEnd)
-        val end = maxOf(replaceStart, replaceEnd)
-        var selStart = selectionInChars.min
-        var selEnd = selectionInChars.max
-
-        if (selEnd < start) {
-            // The entire selection is before the insertion point – we don't have to adjust the
-            // mark at all, so skip the math.
-            return
-        }
-
-        if (selStart <= start && end <= selEnd) {
-            // The insertion is entirely inside the selection, move the end only.
-            val diff = newLength - (end - start)
-            // Preserve "cursorness".
-            if (selStart == selEnd) {
-                selStart += diff
-            }
-            selEnd += diff
-        } else if (selStart > start && selEnd < end) {
-            // Selection is entirely inside replacement, move it to the end.
-            selStart = start + newLength
-            selEnd = start + newLength
-        } else if (selStart >= end) {
-            // The entire selection is after the insertion, so shift everything forward.
-            val diff = newLength - (end - start)
-            selStart += diff
-            selEnd += diff
-        } else if (start < selStart) {
-            // Insertion is around start of selection, truncate start of selection.
-            selStart = start + newLength
-            selEnd += newLength - (end - start)
-        } else {
-            // Insertion is around end of selection, truncate end of selection.
-            selEnd = start
-        }
-        selectionInChars = TextRange(selStart, selEnd)
-    }
-
-    /**
-     * Returns the [Char] at [index] in this buffer.
-     */
-    fun charAt(index: Int): Char = buffer[index]
-
-    override fun toString(): String = buffer.toString()
-
-    /**
-     * Returns a [CharSequence] backed by this buffer. Any subsequent changes to this buffer will
-     * be visible in the returned sequence as well.
-     */
-    fun asCharSequence(): CharSequence = buffer
-
-    private fun clearChangeList() {
-        changeTracker?.clearChanges()
-    }
-
-    /**
-     * Revert all changes made to this value since it was created.
-     *
-     * After calling this method, this object will be in the same state it was when it was initially
-     * created, and [changes] will be empty.
-     */
-    fun revertAllChanges() {
-        replace(0, length, sourceValue.toString())
-        selectionInChars = sourceValue.selectionInChars
-        clearChangeList()
-    }
-
-    /**
-     * Places the cursor before the codepoint at the given index.
-     *
-     * If [index] is inside an invalid run, the cursor will be placed at the nearest earlier index.
-     *
-     * To place the cursor at the beginning of the field, pass index 0. To place the cursor at the
-     * end of the field, after the last character, pass index
-     * [TextFieldBuffer.codepointLength] or call [placeCursorAtEnd].
-     *
-     * @param index Codepoint index to place cursor before, should be in range 0 to
-     * [TextFieldBuffer.codepointLength], inclusive.
-     *
-     * @see placeCursorBeforeCharAt
-     * @see placeCursorAfterCodepointAt
-     */
-    fun placeCursorBeforeCodepointAt(index: Int) {
-        requireValidIndex(index, startExclusive = true, endExclusive = false, inCodepoints = true)
-        val charIndex = codepointIndexToCharIndex(index)
-        selectionInChars = TextRange(charIndex)
-    }
-
-    /**
-     * Places the cursor before the character at the given index.
-     *
-     * If [index] is inside a surrogate pair or other invalid run, the cursor will be placed at the
-     * nearest earlier index.
-     *
-     * To place the cursor at the beginning of the field, pass index 0. To place the cursor at the
-     * end of the field, after the last character, pass index [TextFieldBuffer.length] or call
-     * [placeCursorAtEnd].
-     *
-     * @param index Character index to place cursor before, should be in range 0 to
-     * [TextFieldBuffer.length], inclusive.
-     *
-     * @see placeCursorBeforeCodepointAt
-     * @see placeCursorAfterCharAt
-     */
-    fun placeCursorBeforeCharAt(index: Int) {
-        requireValidIndex(index, startExclusive = true, endExclusive = false, inCodepoints = false)
-        selectionInChars = TextRange(index)
-    }
-
-    /**
-     * Places the cursor after the codepoint at the given index.
-     *
-     * If [index] is inside an invalid run, the cursor will be placed at the nearest later index.
-     *
-     * To place the cursor at the end of the field, after the last character, pass index
-     * [TextFieldBuffer.codepointLength] or call [placeCursorAtEnd].
-     *
-     * @param index Codepoint index to place cursor after, should be in range 0 (inclusive) to
-     * [TextFieldBuffer.codepointLength] (exclusive).
-     *
-     * @see placeCursorAfterCharAt
-     * @see placeCursorBeforeCodepointAt
-     */
-    fun placeCursorAfterCodepointAt(index: Int) {
-        requireValidIndex(index, startExclusive = false, endExclusive = true, inCodepoints = true)
-        val charIndex = codepointIndexToCharIndex((index + 1).coerceAtMost(codepointLength))
-        selectionInChars = TextRange(charIndex)
-    }
-
-    /**
-     * Places the cursor after the character at the given index.
-     *
-     * If [index] is inside a surrogate pair or other invalid run, the cursor will be placed at the
-     * nearest later index.
-     *
-     * To place the cursor at the end of the field, after the last character, pass index
-     * [TextFieldBuffer.length] or call [placeCursorAtEnd].
-     *
-     * @param index Character index to place cursor after, should be in range 0 (inclusive) to
-     * [TextFieldBuffer.length] (exclusive).
-     *
-     * @see placeCursorAfterCodepointAt
-     * @see placeCursorBeforeCharAt
-     */
-    fun placeCursorAfterCharAt(index: Int) {
-        requireValidIndex(index, startExclusive = false, endExclusive = true, inCodepoints = false)
-        selectionInChars = TextRange((index + 1).coerceAtMost(length))
-    }
-
-    /**
-     * Places the selection around the given [range] in codepoints.
-     *
-     * If the start or end of [range] fall inside invalid runs, the values will be adjusted to the
-     * nearest earlier and later codepoints, respectively.
-     *
-     * To place the start of the selection at the beginning of the field, pass index 0. To place the
-     * end of the selection at the end of the field, after the last codepoint, pass index
-     * [TextFieldBuffer.codepointLength]. Passing a zero-length range is the same as calling
-     * [placeCursorBeforeCodepointAt].
-     *
-     * @param range Codepoint range of the selection, should be in range 0 to
-     * [TextFieldBuffer.codepointLength], inclusive.
-     *
-     * @see selectCharsIn
-     */
-    fun selectCodepointsIn(range: TextRange) {
-        requireValidRange(range, inCodepoints = true)
-        selectionInChars = codepointsToChars(range)
-    }
-
-    /**
-     * Places the selection around the given [range] in characters.
-     *
-     * If the start or end of [range] fall inside surrogate pairs or other invalid runs, the values will
-     * be adjusted to the nearest earlier and later characters, respectively.
-     *
-     * To place the start of the selection at the beginning of the field, pass index 0. To place the end
-     * of the selection at the end of the field, after the last character, pass index
-     * [TextFieldBuffer.length]. Passing a zero-length range is the same as calling
-     * [placeCursorBeforeCharAt].
-     *
-     * @param range Codepoint range of the selection, should be in range 0 to
-     * [TextFieldBuffer.length], inclusive.
-     *
-     * @see selectCodepointsIn
-     */
-    fun selectCharsIn(range: TextRange) {
-        requireValidRange(range, inCodepoints = false)
-        selectionInChars = range
-    }
-
-    /**
-     * Returns an immutable [TextFieldCharSequence] that has the same contents of this buffer.
-     *
-     * @param selection The selection for the returned [TextFieldCharSequence]. Default value is
-     * this buffer's selection. Passing a different value in here _only_ affects the return value,
-     * it does not change the current selection in the buffer.
-     * @param composition The composition range for the returned [TextFieldCharSequence]. Default
-     * value is no composition (null).
-     */
-    internal fun toTextFieldCharSequence(
-        selection: TextRange = selectionInChars,
-        composition: TextRange? = null
-    ): TextFieldCharSequence = TextFieldCharSequence(
-        buffer.toString(),
-        selection = selection,
-        composition = composition
-    )
-
-    private fun requireValidIndex(
-        index: Int,
-        startExclusive: Boolean,
-        endExclusive: Boolean,
-        inCodepoints: Boolean
-    ) {
-        var start = if (startExclusive) 0 else -1
-        var end = if (endExclusive) length else length + 1
-
-        // The "units" of the range in the error message should match the units passed in.
-        // If the input was in codepoint indices, the output should be in codepoint indices.
-        if (inCodepoints) {
-            start = charIndexToCodepointIndex(start)
-            end = charIndexToCodepointIndex(end)
-        }
-
-        require(index in start until end) {
-            val unit = if (inCodepoints) "codepoints" else "chars"
-            "Expected $index to be in [$start, $end) $unit"
-        }
-    }
-
-    private fun requireValidRange(range: TextRange, inCodepoints: Boolean) {
-        // The "units" of the range in the error message should match the units passed in.
-        // If the input was in codepoint indices, the output should be in codepoint indices.
-        val validRange = TextRange(0, length)
-            .let { if (inCodepoints) charsToCodepoints(it) else it }
-        require(range in validRange) {
-            val unit = if (inCodepoints) "codepoints" else "chars"
-            "Expected $range to be in $validRange ($unit)"
-        }
-    }
-
-    private fun codepointsToChars(range: TextRange): TextRange = TextRange(
-        codepointIndexToCharIndex(range.start),
-        codepointIndexToCharIndex(range.end)
-    )
-
-    private fun charsToCodepoints(range: TextRange): TextRange = TextRange(
-        charIndexToCodepointIndex(range.start),
-        charIndexToCodepointIndex(range.end),
-    )
-
-    // TODO Support actual codepoints.
-    private fun codepointIndexToCharIndex(index: Int): Int = index
-    private fun charIndexToCodepointIndex(index: Int): Int = index
-
-    /**
-     * The ordered list of non-overlapping and discontinuous changes performed on a
-     * [TextFieldBuffer] during the current [edit][TextFieldState.edit] or
-     * [filter][InputTransformation.transformInput] operation. Changes are listed in the order they appear in the
-     * text, not the order in which they were made. Overlapping changes are represented as a single
-     * change.
-     */
-    @ExperimentalFoundationApi
-    interface ChangeList {
-        /**
-         * The number of changes that have been performed.
-         */
-        val changeCount: Int
-
-        /**
-         * Returns the range in the [TextFieldBuffer] that was changed.
-         *
-         * @throws IndexOutOfBoundsException If [changeIndex] is not in [0, [changeCount]).
-         */
-        fun getRange(changeIndex: Int): TextRange
-
-        /**
-         * Returns the range in the original text that was replaced.
-         *
-         * @throws IndexOutOfBoundsException If [changeIndex] is not in [0, [changeCount]).
-         */
-        fun getOriginalRange(changeIndex: Int): TextRange
-    }
-}
-
-/**
- * Insert [text] at the given [index] in this value. Pass 0 to insert [text] at the beginning of
- * this buffer, and pass [TextFieldBuffer.length] to insert [text] at the end of this buffer.
- *
- * This is equivalent to calling `replace(index, index, text)`.
- *
- * @param index The character offset at which to insert [text].
- * @param text The text to insert.
- *
- * @see TextFieldBuffer.replace
- * @see TextFieldBuffer.append
- * @see TextFieldBuffer.delete
- */
-@ExperimentalFoundationApi
-fun TextFieldBuffer.insert(index: Int, text: String) {
-    replace(index, index, text)
-}
-
-/**
- * Delete the text between [start] (inclusive) and [end] (exclusive). Pass 0 as [start] and
- * [TextFieldBuffer.length] as [end] to delete everything in this buffer.
- *
- * @param start The character offset of the first character to delete.
- * @param end The character offset of the first character after the deleted range.
- *
- * @see TextFieldBuffer.replace
- * @see TextFieldBuffer.append
- * @see TextFieldBuffer.insert
- */
-@ExperimentalFoundationApi
-fun TextFieldBuffer.delete(start: Int, end: Int) {
-    replace(start, end, "")
-}
-
-/**
- * Places the cursor at the end of the text.
- */
-@ExperimentalFoundationApi
-fun TextFieldBuffer.placeCursorAtEnd() {
-    placeCursorBeforeCharAt(length)
-}
-
-/**
- * Places the selection around all the text.
- */
-@ExperimentalFoundationApi
-fun TextFieldBuffer.selectAll() {
-    selectCharsIn(TextRange(0, length))
-}
-
-/**
- * Iterates over all the changes in this [ChangeList].
- *
- * Changes are iterated by index, so any changes made by [block] after the current one will be
- * visited by [block]. [block] should not make any new changes _before_ the current one or changes
- * will be visited more than once. If you need to make changes, consider using
- * [forEachChangeReversed].
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
- *
- * @see forEachChangeReversed
- */
-@ExperimentalFoundationApi
-inline fun ChangeList.forEachChange(
-    block: (range: TextRange, originalRange: TextRange) -> Unit
-) {
-    var i = 0
-    // Check the size every iteration in case more changes were performed.
-    while (i < changeCount) {
-        block(getRange(i), getOriginalRange(i))
-        i++
-    }
-}
-
-/**
- * Iterates over all the changes in this [ChangeList] in reverse order.
- *
- * Changes are iterated by index, so [block] should not perform any new changes before the current
- * one or changes may be skipped. [block] may make non-overlapping changes after the current one
- * safely, such changes will not be visited.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
- *
- * @see forEachChange
- */
-@ExperimentalFoundationApi
-inline fun ChangeList.forEachChangeReversed(
-    block: (range: TextRange, originalRange: TextRange) -> Unit
-) {
-    var i = changeCount - 1
-    while (i >= 0) {
-        block(getRange(i), getOriginalRange(i))
-        i--
-    }
-}
-
-/**
- * Finds the common prefix and suffix between [a] and [b] and then reports the ranges of each that
- * excludes those. The values are reported via an (inline) callback instead of a return value to
- * avoid having to allocate something to hold them. If the [CharSequence]s are identical, the
- * callback is not invoked.
- *
- * E.g. given `a="abcde"` and `b="abbbdefe"`, the middle diff for `a` is `"ab[cd]e"` and for `b` is
- * `ab[bbdef]e`, so reports `aMiddle=TextRange(2, 4)` and `bMiddle=TextRange(2, 7)`.
- */
-internal inline fun findCommonPrefixAndSuffix(
-    a: CharSequence,
-    b: CharSequence,
-    onFound: (aPrefixStart: Int, aSuffixStart: Int, bPrefixStart: Int, bSuffixStart: Int) -> Unit
-) {
-    var aStart = 0
-    var aEnd = a.length
-    var bStart = 0
-    var bEnd = b.length
-
-    // If either one is empty, the diff range is the entire non-empty one.
-    if (a.isNotEmpty() && b.isNotEmpty()) {
-        var prefixFound = false
-        var suffixFound = false
-
-        do {
-            if (!prefixFound) {
-                if (a[aStart] == b[bStart]) {
-                    aStart += 1
-                    bStart += 1
-                } else {
-                    prefixFound = true
-                }
-            }
-            if (!suffixFound) {
-                if (a[aEnd - 1] == b[bEnd - 1]) {
-                    aEnd -= 1
-                    bEnd -= 1
-                } else {
-                    suffixFound = true
-                }
-            }
-        } while (
-        // As soon as we've completely traversed one of the strings, if the other hasn't also
-        // finished being traversed then we've found the diff region.
-            aStart < aEnd && bStart < bEnd &&
-            // If we've found the end of the common prefix and the start of the common suffix we're
-            // done.
-            !(prefixFound && suffixFound)
-        )
-    }
-
-    if (aStart >= aEnd && bStart >= bEnd) {
-        return
-    }
-
-    onFound(aStart, aEnd, bStart, bEnd)
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private object EmptyChangeList : ChangeList {
-    override val changeCount: Int
-        get() = 0
-
-    override fun getRange(changeIndex: Int): TextRange {
-        throw IndexOutOfBoundsException()
-    }
-
-    override fun getOriginalRange(changeIndex: Int): TextRange {
-        throw IndexOutOfBoundsException()
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
deleted file mode 100644
index d04e4fb..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.internal.toCharArray
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.coerceIn
-
-/**
- * An immutable snapshot of the contents of a [TextFieldState].
- *
- * This class is a [CharSequence] and directly represents the text being edited. It also stores
- * the current [selectionInChars] of the field, which may either represent the cursor (if the
- * selection is [collapsed][TextRange.collapsed]) or the selection range.
- *
- * This class also may contain the range being composed by the IME, if any, although this is not
- * exposed.
- *
- * @see TextFieldBuffer
- */
-@ExperimentalFoundationApi
-sealed interface TextFieldCharSequence : CharSequence {
-    /**
-     * The selection range. If the selection is collapsed, it represents cursor
-     * location. When selection range is out of bounds, it is constrained with the text length.
-     */
-    val selectionInChars: TextRange
-
-    /**
-     * Composition range created by IME. If null, there is no composition range.
-     *
-     * Input service composition is an instance of text produced by IME. An example visual for the
-     * composition is that the currently composed word is visually separated from others with
-     * underline, or text background. For description of composition please check
-     * [W3C IME Composition](https://www.w3.org/TR/ime-api/#ime-composition)
-     *
-     * Composition can only be set by the system.
-     */
-    val compositionInChars: TextRange?
-
-    /**
-     * Returns true if the text in this object is equal to the text in [other], disregarding any
-     * other properties of this (such as selection) or [other].
-     */
-    fun contentEquals(other: CharSequence): Boolean
-
-    abstract override fun toString(): String
-    abstract override fun equals(other: Any?): Boolean
-    abstract override fun hashCode(): Int
-}
-
-@ExperimentalFoundationApi
-fun TextFieldCharSequence(
-    text: String = "",
-    selection: TextRange = TextRange.Zero
-): TextFieldCharSequence = TextFieldCharSequenceWrapper(text, selection, composition = null)
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence(
-    text: CharSequence,
-    selection: TextRange,
-    composition: TextRange? = null
-): TextFieldCharSequence = TextFieldCharSequenceWrapper(text, selection, composition)
-
-/**
- * Copies the contents of this sequence from [[sourceStartIndex], [sourceEndIndex]) into
- * [destination] starting at [destinationOffset].
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence.toCharArray(
-    destination: CharArray,
-    destinationOffset: Int,
-    sourceStartIndex: Int,
-    sourceEndIndex: Int
-) = (this as TextFieldCharSequenceWrapper).toCharArray(
-    destination,
-    destinationOffset,
-    sourceStartIndex,
-    sourceEndIndex
-)
-
-@OptIn(ExperimentalFoundationApi::class)
-private class TextFieldCharSequenceWrapper(
-    private val text: CharSequence,
-    selection: TextRange,
-    composition: TextRange?
-) : TextFieldCharSequence {
-
-    override val length: Int
-        get() = text.length
-
-    override val selectionInChars: TextRange = selection.coerceIn(0, text.length)
-
-    override val compositionInChars: TextRange? = composition?.coerceIn(0, text.length)
-
-    override operator fun get(index: Int): Char = text[index]
-
-    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
-        text.subSequence(startIndex, endIndex)
-
-    override fun toString(): String = text.toString()
-
-    override fun contentEquals(other: CharSequence): Boolean = text.contentEquals(other)
-
-    fun toCharArray(
-        destination: CharArray,
-        destinationOffset: Int,
-        sourceStartIndex: Int,
-        sourceEndIndex: Int
-    ) {
-        text.toCharArray(destination, destinationOffset, sourceStartIndex, sourceEndIndex)
-    }
-
-    /**
-     * Returns true if [other] is a [TextFieldCharSequence] with the same contents, text, and composition.
-     * To compare just the text, call [contentEquals].
-     */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other === null) return false
-        if (this::class != other::class) return false
-
-        other as TextFieldCharSequenceWrapper
-
-        if (selectionInChars != other.selectionInChars) return false
-        if (compositionInChars != other.compositionInChars) return false
-        if (!contentEquals(other.text)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = text.hashCode()
-        result = 31 * result + selectionInChars.hashCode()
-        result = 31 * result + (compositionInChars?.hashCode() ?: 0)
-        return result
-    }
-}
-
-/**
- * Returns the text before the selection.
- *
- * @param maxChars maximum number of characters (inclusive) before the minimum value in
- * [TextFieldCharSequence.selectionInChars].
- *
- * @see TextRange.min
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence.getTextBeforeSelection(maxChars: Int): CharSequence =
-    subSequence(kotlin.math.max(0, selectionInChars.min - maxChars), selectionInChars.min)
-
-/**
- * Returns the text after the selection.
- *
- * @param maxChars maximum number of characters (exclusive) after the maximum value in
- * [TextFieldCharSequence.selectionInChars].
- *
- * @see TextRange.max
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence.getTextAfterSelection(maxChars: Int): CharSequence =
-    subSequence(selectionInChars.max, kotlin.math.min(selectionInChars.max + maxChars, length))
-
-/**
- * Returns the currently selected text.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldCharSequence.getSelectedText(): CharSequence =
-    subSequence(selectionInChars.min, selectionInChars.max)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldDecorator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldDecorator.kt
deleted file mode 100644
index ffdd4c6..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldDecorator.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.Composable
-
-/**
- * Composable interface that allows to add decorations around text field, such as icon,
- * placeholder, helper messages or similar, and automatically increase the hit target area
- * of the text field.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
- */
-@ExperimentalFoundationApi
-fun interface TextFieldDecorator {
-
-    /**
-     * To allow you to control the placement of the inner text field relative to your decorations,
-     * the text field implementation will pass in a framework-controlled composable parameter
-     * [innerTextField] to this method. You must not call [innerTextField] more than once.
-     */
-    // Composable parameters of Composable functions are normally slots for callers to inject
-    // their content but this one is a special inverted-slot API. It's better to be explicit
-    // with the naming.
-    @Suppress("ComposableLambdaParameterNaming")
-    @Composable
-    fun Decoration(innerTextField: @Composable () -> Unit)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt
deleted file mode 100644
index 9e85752..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
-import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.Stable
-
-/**
- * Values that specify the text wrapping, scrolling, and height measurement behavior for
- * text fields.
- *
- * @see SingleLine
- * @see MultiLine
- */
-@ExperimentalFoundationApi
-@Stable
-sealed interface TextFieldLineLimits {
-
-    /**
-     * The text field is always a single line tall, ignores newlines in the text, and scrolls
-     * horizontally when the text overflows.
-     */
-    object SingleLine : TextFieldLineLimits
-
-    /**
-     * The text field will be at least [minHeightInLines] tall, if the text overflows it will wrap,
-     * and if the text ends up being more than one line the field will grow until it is
-     * [maxHeightInLines] tall and then start scrolling vertically.
-     *
-     * It is required that 1 ≤ [minHeightInLines] ≤ [maxHeightInLines].
-     *
-     * To specify the minimum and/or maximum height of the field in non-text units, such as dps, use
-     * the [heightIn] modifier.
-     */
-    @Immutable
-    class MultiLine(
-        val minHeightInLines: Int = 1,
-        val maxHeightInLines: Int = Int.MAX_VALUE
-    ) : TextFieldLineLimits {
-        init {
-            require(minHeightInLines in 1..maxHeightInLines) {
-                "Expected 1 ≤ minHeightInLines ≤ maxHeightInLines, were " +
-                    "$minHeightInLines, $maxHeightInLines"
-            }
-        }
-
-        override fun toString(): String =
-            "MultiLine(minHeightInLines=$minHeightInLines, maxHeightInLines=$maxHeightInLines)"
-
-        override fun equals(other: Any?): Boolean {
-            if (this === other) return true
-            if (other === null) return false
-            if (this::class != other::class) return false
-            other as MultiLine
-            if (minHeightInLines != other.minHeightInLines) return false
-            if (maxHeightInLines != other.maxHeightInLines) return false
-            return true
-        }
-
-        override fun hashCode(): Int {
-            var result = minHeightInLines
-            result = 31 * result + maxHeightInLines
-            return result
-        }
-    }
-
-    companion object {
-        val Default: TextFieldLineLimits = MultiLine()
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
deleted file mode 100644
index 131a13f..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
+++ /dev/null
@@ -1,572 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package androidx.compose.foundation.text2.input
-
-import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.internal.EditingBuffer
-import androidx.compose.foundation.text2.input.internal.undo.TextFieldEditUndoBehavior
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.coerceIn
-import androidx.compose.ui.text.input.TextFieldValue
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-
-internal fun TextFieldState(initialValue: TextFieldValue): TextFieldState {
-    return TextFieldState(
-        initialText = initialValue.text,
-        initialSelectionInChars = initialValue.selection
-    )
-}
-
-/**
- * The editable text state of a text field, including both the [text] itself and position of the
- * cursor or selection.
- *
- * To change the text field contents programmatically, call [edit], [setTextAndSelectAll],
- * [setTextAndPlaceCursorAtEnd], or [clearText]. To observe the value of the field over time, call
- * [forEachTextValue] or [textAsFlow].
- *
- * When instantiating this class from a composable, use [rememberTextFieldState] to automatically
- * save and restore the field state. For more advanced use cases, pass [TextFieldState.Saver] to
- * [rememberSaveable].
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2StateCompleteSample
- */
-@ExperimentalFoundationApi
-@Stable
-class TextFieldState internal constructor(
-    initialText: String,
-    initialSelectionInChars: TextRange,
-    initialTextUndoManager: TextUndoManager
-) {
-
-    constructor(
-        initialText: String = "",
-        initialSelectionInChars: TextRange = TextRange(initialText.length)
-    ) : this(initialText, initialSelectionInChars, TextUndoManager())
-
-    /**
-     * Manages the history of edit operations that happen in this [TextFieldState].
-     */
-    internal val textUndoManager: TextUndoManager = initialTextUndoManager
-
-    /**
-     * The editing buffer used for applying editor commands from IME. All edits coming from gestures
-     * or IME commands, must be reflected on this buffer eventually.
-     */
-    @VisibleForTesting
-    internal var mainBuffer: EditingBuffer = EditingBuffer(
-        text = initialText,
-        selection = initialSelectionInChars.coerceIn(0, initialText.length)
-    )
-
-    /**
-     * The current text and selection. This value will automatically update when the user enters
-     * text or otherwise changes the text field contents. To change it programmatically, call
-     * [edit].
-     *
-     * This is backed by snapshot state, so reading this property in a restartable function (e.g.
-     * a composable function) will cause the function to restart when the text field's value
-     * changes.
-     *
-     * To observe changes to this property outside a restartable function, see [forEachTextValue]
-     * and [textAsFlow].
-     *
-     * @sample androidx.compose.foundation.samples.BasicTextField2TextDerivedStateSample
-     *
-     * @see edit
-     * @see forEachTextValue
-     * @see textAsFlow
-     */
-    var text: TextFieldCharSequence by mutableStateOf(
-        TextFieldCharSequence(initialText, initialSelectionInChars)
-    )
-        private set
-
-    /**
-     * Runs [block] with a mutable version of the current state. The block can make changes to the
-     * text and cursor/selection. See the documentation on [TextFieldBuffer] for a more detailed
-     * description of the available operations.
-     *
-     * @sample androidx.compose.foundation.samples.BasicTextField2StateEditSample
-     *
-     * @see setTextAndPlaceCursorAtEnd
-     * @see setTextAndSelectAll
-     */
-    inline fun edit(block: TextFieldBuffer.() -> Unit) {
-        val mutableValue = startEdit(text)
-        mutableValue.block()
-        commitEdit(mutableValue)
-    }
-
-    override fun toString(): String =
-        "TextFieldState(selectionInChars=${text.selectionInChars}, text=\"$text\")"
-
-    /**
-     * Undo history controller for this TextFieldState.
-     *
-     * @sample androidx.compose.foundation.samples.BasicTextField2UndoSample
-     */
-    // TextField does not implement UndoState because Undo related APIs should be able to remain
-    // separately experimental than TextFieldState
-    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    @ExperimentalFoundationApi
-    @get:ExperimentalFoundationApi
-    val undoState: UndoState = UndoState(this)
-
-    @Suppress("ShowingMemberInHiddenClass")
-    @PublishedApi
-    internal fun startEdit(value: TextFieldCharSequence): TextFieldBuffer =
-        TextFieldBuffer(value)
-
-    /**
-     * If the text or selection in [newValue] was actually modified, updates this state's internal
-     * values. If [newValue] was not modified at all, the state is not updated, and this will not
-     * invalidate anyone who is observing this state.
-     *
-     * @param newValue [TextFieldBuffer] that contains the latest updates
-     */
-    @Suppress("ShowingMemberInHiddenClass")
-    @PublishedApi
-    internal fun commitEdit(newValue: TextFieldBuffer) {
-        val textChanged = newValue.changes.changeCount > 0
-        val selectionChanged = newValue.selectionInChars != mainBuffer.selection
-        if (textChanged || selectionChanged) {
-            val finalValue = newValue.toTextFieldCharSequence()
-            resetStateAndNotifyIme(finalValue)
-        }
-        textUndoManager.clearHistory()
-    }
-
-    /**
-     * An edit block that updates [TextFieldState] on behalf of user actions such as gestures,
-     * IME commands, hardware keyboard events, clipboard actions, and more. These modifications
-     * must also run through the given [filter] since they are user actions.
-     *
-     * Be careful that this method is not snapshot aware. It is only safe to call this from main
-     * thread, or global snapshot. Also, this function is defined as inline for performance gains,
-     * and it's not actually safe to early return from [block].
-     *
-     * Also all user edits should be recorded by [textUndoManager] since reverting to a previous
-     * state requires all edit operations to be executed in reverse. However, some commands like
-     * cut, and paste should be atomic operations that do not merge with previous or next operations
-     * in the Undo stack. This can be controlled by [undoBehavior].
-     *
-     * @param inputTransformation [InputTransformation] to run after [block] is applied
-     * @param notifyImeOfChanges Whether IME should be notified of these changes. Only pass false to
-     * this argument if the source of the changes is IME itself.
-     * @param block The function that updates the current editing buffer.
-     */
-    internal inline fun editAsUser(
-        inputTransformation: InputTransformation?,
-        notifyImeOfChanges: Boolean = true,
-        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
-        block: EditingBuffer.() -> Unit
-    ) {
-        val previousValue = text
-
-        mainBuffer.changeTracker.clearChanges()
-        mainBuffer.block()
-
-        if (mainBuffer.changeTracker.changeCount == 0 &&
-            previousValue.selectionInChars == mainBuffer.selection &&
-            previousValue.compositionInChars == mainBuffer.composition) {
-            // nothing has changed after applying block.
-            return
-        }
-
-        commitEditAsUser(previousValue, inputTransformation, notifyImeOfChanges, undoBehavior)
-    }
-
-    /**
-     * Edits the contents of this [TextFieldState] without going through an [InputTransformation],
-     * or recording the changes to the [textUndoManager]. IME would still be notified of any changes
-     * committed by [block].
-     *
-     * This method of editing is not recommended for majority of use cases. It is originally added
-     * to support applying of undo/redo actions without clearing the history. Also, it doesn't
-     * allocate an additional buffer like [edit] method because changes are ignored and it's not
-     * a public API.
-     */
-    internal inline fun editWithNoSideEffects(block: EditingBuffer.() -> Unit) {
-        val previousValue = text
-
-        mainBuffer.changeTracker.clearChanges()
-        mainBuffer.block()
-
-        val afterEditValue = TextFieldCharSequence(
-            text = mainBuffer.toString(),
-            selection = mainBuffer.selection,
-            composition = mainBuffer.composition
-        )
-
-        text = afterEditValue
-        notifyIme(previousValue, afterEditValue)
-    }
-
-    private fun commitEditAsUser(
-        previousValue: TextFieldCharSequence,
-        inputTransformation: InputTransformation?,
-        notifyImeOfChanges: Boolean,
-        undoBehavior: TextFieldEditUndoBehavior
-    ) {
-        val afterEditValue = TextFieldCharSequence(
-            text = mainBuffer.toString(),
-            selection = mainBuffer.selection,
-            composition = mainBuffer.composition
-        )
-
-        if (inputTransformation == null) {
-            val oldValue = text
-            text = afterEditValue
-            if (notifyImeOfChanges) {
-                notifyIme(oldValue, afterEditValue)
-            }
-            recordEditForUndo(previousValue, text, mainBuffer.changeTracker, undoBehavior)
-            return
-        }
-
-        val oldValue = text
-
-        // if only difference is composition, don't run filter, don't send it to undo manager
-        if (afterEditValue.contentEquals(oldValue) &&
-            afterEditValue.selectionInChars == oldValue.selectionInChars
-        ) {
-            text = afterEditValue
-            if (notifyImeOfChanges) {
-                notifyIme(oldValue, afterEditValue)
-            }
-            return
-        }
-
-        val mutableValue = TextFieldBuffer(
-            initialValue = afterEditValue,
-            sourceValue = oldValue,
-            initialChanges = mainBuffer.changeTracker
-        )
-        inputTransformation.transformInput(
-            originalValue = oldValue,
-            valueWithChanges = mutableValue
-        )
-        // If neither the text nor the selection changed, we want to preserve the composition.
-        // Otherwise, the IME will reset it anyway.
-        val afterFilterValue = mutableValue.toTextFieldCharSequence(
-            composition = afterEditValue.compositionInChars
-        )
-        if (afterFilterValue == afterEditValue) {
-            text = afterFilterValue
-            if (notifyImeOfChanges) {
-                notifyIme(oldValue, afterEditValue)
-            }
-        } else {
-            resetStateAndNotifyIme(afterFilterValue)
-        }
-        // mutableValue contains all the changes from user and the filter.
-        recordEditForUndo(previousValue, text, mutableValue.changes, undoBehavior)
-    }
-
-    /**
-     * Records the difference between [previousValue] and [postValue], defined by [changes],
-     * into [textUndoManager] according to the strategy defined by [undoBehavior].
-     */
-    private fun recordEditForUndo(
-        previousValue: TextFieldCharSequence,
-        postValue: TextFieldCharSequence,
-        changes: TextFieldBuffer.ChangeList,
-        undoBehavior: TextFieldEditUndoBehavior
-    ) {
-        when (undoBehavior) {
-            TextFieldEditUndoBehavior.ClearHistory -> {
-                textUndoManager.clearHistory()
-            }
-            TextFieldEditUndoBehavior.MergeIfPossible -> {
-                textUndoManager.recordChanges(
-                    pre = previousValue,
-                    post = postValue,
-                    changes = changes,
-                    allowMerge = true
-                )
-            }
-            TextFieldEditUndoBehavior.NeverMerge -> {
-                textUndoManager.recordChanges(
-                    pre = previousValue,
-                    post = postValue,
-                    changes = changes,
-                    allowMerge = false
-                )
-            }
-        }
-    }
-
-    internal fun addNotifyImeListener(notifyImeListener: NotifyImeListener) {
-        notifyImeListeners.add(notifyImeListener)
-    }
-
-    internal fun removeNotifyImeListener(notifyImeListener: NotifyImeListener) {
-        notifyImeListeners.remove(notifyImeListener)
-    }
-
-    /**
-     * A listener that can be attached to a [TextFieldState] to listen for change events that may
-     * interest IME.
-     *
-     * State in [TextFieldState] can change through various means but categorically there are two
-     * sources; Developer([TextFieldState.edit]) and User([TextFieldState.editAsUser]). Only
-     * non-filtered IME sourced changes can skip updating the IME. Otherwise, all changes must be
-     * contacted to IME to let it synchronize its state with the [TextFieldState]. Such
-     * communication is built by IME registering a [NotifyImeListener] on a [TextFieldState].
-     */
-    internal fun interface NotifyImeListener {
-
-        fun onChange(oldValue: TextFieldCharSequence, newValue: TextFieldCharSequence)
-    }
-
-    /**
-     * Must be called whenever [text] needs to change but the content of the changes are not yet
-     * replicated on [mainBuffer].
-     *
-     * This method updates the internal editing buffer with the given [TextFieldCharSequence], it
-     * also notifies the IME about the selection or composition changes.
-     */
-    @VisibleForTesting
-    internal fun resetStateAndNotifyIme(newValue: TextFieldCharSequence) {
-        val bufferState = TextFieldCharSequence(
-            mainBuffer.toString(),
-            mainBuffer.selection,
-            mainBuffer.composition
-        )
-
-        var textChanged = false
-        var selectionChanged = false
-        val compositionChanged = newValue.compositionInChars != mainBuffer.composition
-
-        if (!bufferState.contentEquals(newValue)) {
-            // reset the buffer in its entirety
-            mainBuffer = EditingBuffer(
-                text = newValue.toString(),
-                selection = newValue.selectionInChars
-            )
-            textChanged = true
-        } else if (bufferState.selectionInChars != newValue.selectionInChars) {
-            mainBuffer.setSelection(newValue.selectionInChars.start, newValue.selectionInChars.end)
-            selectionChanged = true
-        }
-
-        val composition = newValue.compositionInChars
-        if (composition == null || composition.collapsed) {
-            mainBuffer.commitComposition()
-        } else {
-            mainBuffer.setComposition(composition.min, composition.max)
-        }
-
-        if (textChanged || (!selectionChanged && compositionChanged)) {
-            mainBuffer.commitComposition()
-        }
-
-        val finalValue = TextFieldCharSequence(
-            if (textChanged) newValue else bufferState,
-            mainBuffer.selection,
-            mainBuffer.composition
-        )
-
-        // value must be set before notifyImeListeners are called. Even though we are sending the
-        // previous and current values, a system callback may request the latest state e.g. IME
-        // restartInput call is handled before notifyImeListeners return.
-        text = finalValue
-
-        notifyIme(bufferState, finalValue)
-    }
-
-    private val notifyImeListeners = mutableVectorOf<NotifyImeListener>()
-
-    private fun notifyIme(
-        oldValue: TextFieldCharSequence,
-        newValue: TextFieldCharSequence
-    ) {
-        notifyImeListeners.forEach { it.onChange(oldValue, newValue) }
-    }
-
-    /**
-     * Saves and restores a [TextFieldState] for [rememberSaveable].
-     *
-     * @see rememberTextFieldState
-     */
-    // Preserve nullability since this is public API.
-    @Suppress("RedundantNullableReturnType")
-    object Saver : androidx.compose.runtime.saveable.Saver<TextFieldState, Any> {
-
-        override fun SaverScope.save(value: TextFieldState): Any? {
-            return listOf(
-                value.text.toString(),
-                value.text.selectionInChars.start,
-                value.text.selectionInChars.end,
-                with(TextUndoManager.Companion.Saver) {
-                    save(value.textUndoManager)
-                }
-            )
-        }
-
-        override fun restore(value: Any): TextFieldState? {
-            val (text, selectionStart, selectionEnd, savedTextUndoManager) = value as List<*>
-            return TextFieldState(
-                initialText = text as String,
-                initialSelectionInChars = TextRange(
-                    start = selectionStart as Int,
-                    end = selectionEnd as Int
-                ),
-                initialTextUndoManager = with(TextUndoManager.Companion.Saver) {
-                    restore(savedTextUndoManager!!)
-                }!!
-            )
-        }
-    }
-}
-
-/**
- * Returns a [Flow] of the values of [TextFieldState.text] as seen from the global snapshot.
- * The initial value is emitted immediately when the flow is collected.
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2TextValuesSample
- */
-@ExperimentalFoundationApi
-fun TextFieldState.textAsFlow(): Flow<TextFieldCharSequence> = snapshotFlow { text }
-
-/**
- * Create and remember a [TextFieldState]. The state is remembered using [rememberSaveable] and so
- * will be saved and restored with the composition.
- *
- * If you need to store a [TextFieldState] in another object, use the [TextFieldState.Saver] object
- * to manually save and restore the state.
- */
-@ExperimentalFoundationApi
-@Composable
-fun rememberTextFieldState(
-    initialText: String = "",
-    initialSelectionInChars: TextRange = TextRange(initialText.length)
-): TextFieldState = rememberSaveable(saver = TextFieldState.Saver) {
-    TextFieldState(initialText, initialSelectionInChars)
-}
-
-/**
- * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
- * and places the cursor at the end of the new text.
- *
- * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
- * equivalent to calling:
- * ```
- * edit {
- *   replace(0, length, text)
- *   placeCursorAtEnd()
- * }
- * ```
- *
- * @see setTextAndSelectAll
- * @see clearText
- * @see TextFieldBuffer.placeCursorAtEnd
- */
-@ExperimentalFoundationApi
-fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String) {
-    edit {
-        replace(0, length, text)
-        placeCursorAtEnd()
-    }
-}
-
-/**
- * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
- * and selects all the text.
- *
- * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
- * equivalent to calling:
- * ```
- * edit {
- *   replace(0, length, text)
- *   selectAll()
- * }
- * ```
- *
- * @see setTextAndPlaceCursorAtEnd
- * @see clearText
- * @see TextFieldBuffer.selectAll
- */
-@ExperimentalFoundationApi
-fun TextFieldState.setTextAndSelectAll(text: String) {
-    edit {
-        replace(0, length, text)
-        selectAll()
-    }
-}
-
-/**
- * Deletes all the text in the state.
- *
- * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
- * equivalent to calling:
- * ```
- * edit {
- *   delete(0, length)
- *   placeCursorAtEnd()
- * }
- * ```
- *
- * @see setTextAndPlaceCursorAtEnd
- * @see setTextAndSelectAll
- */
-@ExperimentalFoundationApi
-fun TextFieldState.clearText() {
-    edit {
-        delete(0, length)
-        placeCursorAtEnd()
-    }
-}
-
-/**
- * Invokes [block] with the value of [TextFieldState.text], and every time the value is changed.
- *
- * The caller will be suspended until its coroutine is cancelled. If the text is changed while
- * [block] is suspended, [block] will be cancelled and re-executed with the new value immediately.
- * [block] will never be executed concurrently with itself.
- *
- * To get access to a [Flow] of [TextFieldState.text] over time, use [textAsFlow].
- *
- * Warning: Do not update the value of the [TextFieldState] from [block]. If you want to perform
- * either a side effect when text is changed, or filter it in some way, use an
- * [InputTransformation].
- *
- * @sample androidx.compose.foundation.samples.BasicTextField2ForEachTextValueSample
- *
- * @see textAsFlow
- */
-@ExperimentalFoundationApi
-suspend fun TextFieldState.forEachTextValue(
-    block: suspend (TextFieldCharSequence) -> Unit
-): Nothing {
-    textAsFlow().collectLatest(block)
-    error("textAsFlow expected not to complete without exception")
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextObfuscationMode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextObfuscationMode.kt
deleted file mode 100644
index 89ed6bc..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextObfuscationMode.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-
-/**
- * Defines how the text will be obscured in secure text fields.
- *
- * Text obscuring refers to replacing the original text content with a mask via various methods.
- * It is most common in password fields.
- *
- * The default behavior for typing input on Desktop has always been to keep it completely hidden.
- * However, on mobile devices, the default behavior is to briefly reveal the last typed character
- * for a short period or until another character is typed. This helps the user to follow the text
- * input while also protecting their privacy by not revealing too much information to others.
- */
-@ExperimentalFoundationApi
-@JvmInline
-value class TextObfuscationMode internal constructor(val value: Int) {
-    companion object {
-        /**
-         * Do not obscure any content, making all the content visible.
-         *
-         * It can be useful when you want to briefly reveal the content by clicking a reveal button.
-         */
-        val Visible = TextObfuscationMode(0)
-
-        /**
-         * Default behavior on mobile devices. Reveals the last typed character for a short amount
-         * of time.
-         *
-         * Note; on Android this feature also depends on a system setting called
-         * `Settings.System.TEXT_SHOW_PASSWORD`. If the system setting is disabled, this option
-         * behaves exactly as [Hidden].
-         */
-        val RevealLastTyped = TextObfuscationMode(1)
-
-        /**
-         * Default behavior on desktop platforms. All characters are hidden.
-         */
-        val Hidden = TextObfuscationMode(2)
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt
deleted file mode 100644
index a080737..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.SNAPSHOTS_INTERVAL_MILLIS
-import androidx.compose.foundation.text2.input.internal.undo.TextDeleteType
-import androidx.compose.foundation.text2.input.internal.undo.TextEditType
-import androidx.compose.foundation.text2.input.internal.undo.TextUndoOperation
-import androidx.compose.foundation.text2.input.internal.undo.UndoManager
-import androidx.compose.foundation.text2.input.internal.undo.redo
-import androidx.compose.foundation.text2.input.internal.undo.undo
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.text.substring
-
-/**
- * An undo manager designed specifically for text editing. This class is mostly responsible for
- * delegating the incoming undo/redo/record/clear requests to its own [undoManager]. Its most
- * important task is to keep a separate staging area for incoming text edit operations to possibly
- * merge them before committing as a single undoable action.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextUndoManager(
-    initialStagingUndo: TextUndoOperation? = null,
-    private val undoManager: UndoManager<TextUndoOperation> = UndoManager(
-        capacity = TEXT_UNDO_CAPACITY
-    )
-) {
-    private var stagingUndo by mutableStateOf<TextUndoOperation?>(initialStagingUndo)
-
-    val canUndo: Boolean
-        get() = undoManager.canUndo || stagingUndo != null
-
-    val canRedo: Boolean
-        get() = undoManager.canRedo && stagingUndo == null
-
-    fun undo(state: TextFieldState) {
-        if (!canUndo) {
-            return
-        }
-
-        flush()
-        state.undo(undoManager.undo())
-    }
-
-    fun redo(state: TextFieldState) {
-        if (!canRedo) {
-            return
-        }
-
-        state.redo(undoManager.redo())
-    }
-
-    fun record(op: TextUndoOperation) {
-        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
-        if (unobservedStagingUndo == null) {
-            stagingUndo = op
-            return
-        }
-
-        val mergedOp = unobservedStagingUndo.merge(op)
-
-        if (mergedOp != null) {
-            // merged operation should replace the top op
-            stagingUndo = mergedOp
-            return
-        }
-
-        // no merge, flush the staging area and put the new operation in
-        flush()
-        stagingUndo = op
-    }
-
-    fun clearHistory() {
-        stagingUndo = null
-        undoManager.clearHistory()
-    }
-
-    private fun flush() {
-        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
-        unobservedStagingUndo?.let { undoManager.record(it) }
-        stagingUndo = null
-    }
-
-    companion object {
-        object Saver : androidx.compose.runtime.saveable.Saver<TextUndoManager, Any> {
-            private val undoManagerSaver = UndoManager.createSaver(TextUndoOperation.Saver)
-
-            override fun SaverScope.save(value: TextUndoManager): Any {
-                return listOf(
-                    value.stagingUndo?.let {
-                        with(TextUndoOperation.Saver) {
-                            save(it)
-                        }
-                    },
-                    with(undoManagerSaver) {
-                        save(value.undoManager)
-                    }
-                )
-            }
-
-            override fun restore(value: Any): TextUndoManager? {
-                val (savedStagingUndo, savedUndoManager) = value as List<*>
-                return TextUndoManager(
-                    savedStagingUndo?.let {
-                        with(TextUndoOperation.Saver) {
-                            restore(it)
-                        }
-                    },
-                    with(undoManagerSaver) {
-                        restore(savedUndoManager!!)
-                    }!!
-                )
-            }
-        }
-    }
-}
-
-/**
- * Try to merge this [TextUndoOperation] with the [next]. Chronologically the [next] op must
- * come after this one. If merge is not possible, this function returns null.
- *
- * There are many rules that govern the grouping logic of successive undo operations. Here we try
- * to cover the most basic requirements but this is certainly not an exhaustive list.
- *
- * 1. Each action defines whether they can be merged at all. For example, text edits that are
- * caused by cut or paste define themselves as unmergeable no matter what comes before or next.
- * 2. If certain amount of time has passed since the latest grouping has begun.
- * 3. Enter key (hard line break) is unmergeable.
- * 4. Only same type of text edits can be merged. An insertion must be grouped by other insertions,
- * a deletion by other deletions. Replace type of edit is never mergeable.
- *   4.a. Two insertions can only be merged if the chronologically next one is a suffix of the
- *   previous insertion. In other words, cursor should always be moving forwards.
- *   4.b. Deletions have directionality. Cursor can only insert in place and move forwards but
- *   deletion can be requested either forwards (delete) or backwards (backspace). Only deletions
- *   that have the same direction can be merged. They also have to share a boundary.
- */
-internal fun TextUndoOperation.merge(next: TextUndoOperation): TextUndoOperation? {
-    if (!canMerge || !next.canMerge) return null
-    // Do not merge if [other] came before this op, or if certain amount of time has passed
-    // between these ops
-    if (
-        next.timeInMillis < timeInMillis ||
-        next.timeInMillis - timeInMillis >= SNAPSHOTS_INTERVAL_MILLIS
-    ) return null
-    // Do not merge undo history when one of the ops is a new line insertion
-    if (isNewLineInsert || next.isNewLineInsert) return null
-    // Only same type of ops can be merged together
-    if (textEditType != next.textEditType) return null
-
-    // only merge insertions if the chronologically next one continuous from the end of
-    // this previous insertion
-    if (textEditType == TextEditType.Insert && index + postText.length == next.index) {
-        return TextUndoOperation(
-            index = index,
-            preText = "",
-            postText = postText + next.postText,
-            preSelection = this.preSelection,
-            postSelection = next.postSelection,
-            timeInMillis = timeInMillis
-        )
-    } else if (textEditType == TextEditType.Delete) {
-        // only merge consecutive deletions if both have the same directionality
-        if (
-            deletionType == next.deletionType &&
-            (deletionType == TextDeleteType.Start || deletionType == TextDeleteType.End)
-        ) {
-            // This op deletes
-            if (index == next.index + next.preText.length) {
-                return TextUndoOperation(
-                    index = next.index,
-                    preText = next.preText + preText,
-                    postText = "",
-                    preSelection = preSelection,
-                    postSelection = next.postSelection,
-                    timeInMillis = timeInMillis
-                )
-            } else if (index == next.index) {
-                return TextUndoOperation(
-                    index = index,
-                    preText = preText + next.preText,
-                    postText = "",
-                    preSelection = preSelection,
-                    postSelection = next.postSelection,
-                    timeInMillis = timeInMillis
-                )
-            }
-        }
-    }
-    return null
-}
-
-/**
- * Adds the [changes] to this [UndoManager] by converting from [TextFieldBuffer.ChangeList] space
- * to [TextUndoOperation] space.
- *
- * @param pre State of the [TextFieldBuffer] before any changes are applied
- * @param post State of the [TextFieldBuffer] after all the changes are applied
- * @param changes List of changes that are applied on [pre] that transforms it to [post].
- * @param allowMerge Whether to allow merging the calculated operation with the last operation
- * in the stack.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextUndoManager.recordChanges(
-    pre: TextFieldCharSequence,
-    post: TextFieldCharSequence,
-    changes: TextFieldBuffer.ChangeList,
-    allowMerge: Boolean = true
-) {
-    // if there are unmerged changes coming from a single edit, force merge all of them to
-    // create a single replace undo operation
-    if (changes.changeCount > 1) {
-        record(
-            TextUndoOperation(
-                index = 0,
-                preText = pre.toString(),
-                postText = post.toString(),
-                preSelection = pre.selectionInChars,
-                postSelection = post.selectionInChars,
-                canMerge = false
-            )
-        )
-    } else if (changes.changeCount == 1) {
-        val preRange = changes.getOriginalRange(0)
-        val postRange = changes.getRange(0)
-        if (!preRange.collapsed || !postRange.collapsed) {
-            record(
-                TextUndoOperation(
-                    index = preRange.min,
-                    preText = pre.substring(preRange),
-                    postText = post.substring(postRange),
-                    preSelection = pre.selectionInChars,
-                    postSelection = post.selectionInChars,
-                    canMerge = allowMerge
-                )
-            )
-        }
-    }
-}
-
-/**
- * Determines whether this operation is adding a new hard line break. This type of change produces
- * unmergable [TextUndoOperation].
- */
-private val TextUndoOperation.isNewLineInsert
-    get() = this.postText == "\n" || this.postText == "\r\n"
-
-private const val TEXT_UNDO_CAPACITY = 100
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt
deleted file mode 100644
index 855413b..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-
-/**
- * Defines an interactable undo history.
- */
-@ExperimentalFoundationApi
-class UndoState internal constructor(private val state: TextFieldState) {
-
-    /**
-     * Whether it is possible to execute a meaningful undo action right now. If this value is false,
-     * calling `undo` would be a no-op.
-     */
-    @Suppress("GetterSetterNames")
-    @get:Suppress("GetterSetterNames")
-    val canUndo: Boolean
-        get() = state.textUndoManager.canUndo
-
-    /**
-     * Whether it is possible to execute a meaningful redo action right now. If this value is false,
-     * calling `redo` would be a no-op.
-     */
-    @Suppress("GetterSetterNames")
-    @get:Suppress("GetterSetterNames")
-    val canRedo: Boolean
-        get() = state.textUndoManager.canRedo
-
-    /**
-     * Reverts the latest edit action or a group of actions that are merged together. Calling it
-     * repeatedly can continue undoing the previous actions.
-     */
-    fun undo() {
-        state.textUndoManager.undo(state)
-    }
-
-    /**
-     * Re-applies a change that was previously reverted via [undo].
-     */
-    fun redo() {
-        state.textUndoManager.redo(state)
-    }
-
-    /**
-     * Clears all undo and redo history up to this point.
-     */
-    fun clearHistory() {
-        state.textUndoManager.clearHistory()
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
deleted file mode 100644
index 8273ced..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package androidx.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldBuffer.ChangeList
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.text.TextRange
-
-/**
- * Keeps track of changes made to a text buffer via [trackChange], and reports changes as a
- * [ChangeList].
- *
- * @param initialChanges If non-null, used to initialize this tracker by copying changes.
- */
-internal class ChangeTracker(initialChanges: ChangeTracker? = null) : ChangeList {
-
-    private var _changes = mutableVectorOf<Change>()
-    private var _changesTemp = mutableVectorOf<Change>()
-
-    init {
-        initialChanges?._changes?.forEach {
-            _changes += Change(it.preStart, it.preEnd, it.originalStart, it.originalEnd)
-        }
-    }
-
-    override val changeCount: Int
-        get() = _changes.size
-
-    /**
-     * This function deals with three "coordinate spaces":
-     *  - `original`: The text before any changes were made.
-     *  - `pre`: The text before this change is applied, but with all previous changes applied.
-     *  - `post`: The text after this change is applied, including all the previous changes.
-     *
-     * When this function is called, the existing changes map ranges in `original` to ranges in
-     * `pre`. The new change is a mapping from `pre` to `post`. This function must determine the
-     * corresponding range in `original` for this change, and convert all other changes' `pre`
-     * ranges to `post`. It must also ensure that any adjacent or overlapping ranges are merged to
-     * ensure the [ChangeList] invariant that all changes are non-overlapping.
-     *
-     * The algorithm works as follows:
-     *  1. Find all the changes that are adjacent to or overlap with this one. This search is
-     *     performed in the `pre` space since that's the space the new change shares with the
-     *     existing changes.
-     *  2. Merge all the changes from (1) into a single range in the `original` and `pre` spaces.
-     *  3. Merge the new change with the change from (2), updating the end of the range to account
-     *     for the new text.
-     *  3. Offset all remaining changes are to account for the new text.
-     */
-    fun trackChange(preStart: Int, preEnd: Int, postLength: Int) {
-        if (preStart == preEnd && postLength == 0) {
-            // Ignore noop changes.
-            return
-        }
-        val preMin = minOf(preStart, preEnd)
-        val preMax = maxOf(preStart, preEnd)
-
-        var i = 0
-        var recordedNewChange = false
-        val postDelta = postLength - (preMax - preMin)
-
-        var mergedOverlappingChange: Change? = null
-        while (i < _changes.size) {
-            val change = _changes[i]
-
-            // Merge adjacent and overlapping changes as we go.
-            if (
-                change.preStart in preMin..preMax ||
-                change.preEnd in preMin..preMax
-            ) {
-                if (mergedOverlappingChange == null) {
-                    mergedOverlappingChange = change
-                } else {
-                    mergedOverlappingChange.preEnd = change.preEnd
-                    mergedOverlappingChange.originalEnd = change.originalEnd
-                }
-                // Don't append overlapping changes to the temp list until we're finished merging.
-                i++
-                continue
-            }
-
-            if (change.preStart > preMax && !recordedNewChange) {
-                // First non-overlapping change after the new one – record the change before
-                // proceeding.
-                appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
-                recordedNewChange = true
-            }
-
-            if (recordedNewChange) {
-                change.preStart += postDelta
-                change.preEnd += postDelta
-            }
-            _changesTemp += change
-            i++
-        }
-
-        if (!recordedNewChange) {
-            // The new change is after or overlapping all previous changes so it hasn't been
-            // appended yet.
-            appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
-        }
-
-        // Swap the lists.
-        val oldChanges = _changes
-        _changes = _changesTemp
-        _changesTemp = oldChanges
-        _changesTemp.clear()
-    }
-
-    fun clearChanges() {
-        _changes.clear()
-    }
-
-    override fun getRange(changeIndex: Int): TextRange =
-        _changes[changeIndex].let { TextRange(it.preStart, it.preEnd) }
-
-    override fun getOriginalRange(changeIndex: Int): TextRange =
-        _changes[changeIndex].let { TextRange(it.originalStart, it.originalEnd) }
-
-    override fun toString(): String = buildString {
-        append("ChangeList(changes=[")
-        _changes.forEachIndexed { i, change ->
-            append(
-                "(${change.originalStart},${change.originalEnd})->" +
-                    "(${change.preStart},${change.preEnd})"
-            )
-            if (i < changeCount - 1) append(", ")
-        }
-        append("])")
-    }
-
-    private fun appendNewChange(
-        mergedOverlappingChange: Change?,
-        preMin: Int,
-        preMax: Int,
-        postDelta: Int
-    ) {
-        var originalDelta = if (_changesTemp.isEmpty()) 0 else {
-            _changesTemp.last().let { it.preEnd - it.originalEnd }
-        }
-        val newChange: Change
-        if (mergedOverlappingChange == null) {
-            // There were no overlapping changes, so allocate a new one.
-            val originalStart = preMin - originalDelta
-            val originalEnd = originalStart + (preMax - preMin)
-            newChange = Change(
-                preStart = preMin,
-                preEnd = preMax + postDelta,
-                originalStart = originalStart,
-                originalEnd = originalEnd
-            )
-        } else {
-            newChange = mergedOverlappingChange
-            // Convert the merged overlapping changes to the `post` space.
-            // Merge the new changed with the merged overlapping changes.
-            if (newChange.preStart > preMin) {
-                // The new change starts before the merged overlapping set.
-                newChange.preStart = preMin
-                newChange.originalStart = preMin
-            }
-            if (preMax > newChange.preEnd) {
-                // The new change ends after the merged overlapping set.
-                originalDelta = newChange.preEnd - newChange.originalEnd
-                newChange.preEnd = preMax
-                newChange.originalEnd = preMax - originalDelta
-            }
-            newChange.preEnd += postDelta
-        }
-        _changesTemp += newChange
-    }
-
-    private data class Change(
-        var preStart: Int,
-        var preEnd: Int,
-        var originalStart: Int,
-        var originalEnd: Int
-    )
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt
deleted file mode 100644
index 7fd39aa..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-internal expect fun CharSequence.codePointAt(index: Int): Int
-internal expect fun CharSequence.codePointCount(): Int
-internal expect fun charCount(codePoint: Int): Int
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditCommand.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditCommand.kt
deleted file mode 100644
index 9983ff8..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditCommand.kt
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.text.findFollowingBreak
-import androidx.compose.foundation.text.findPrecedingBreak
-
-/**
- * Commit final [text] to the text box and set the new cursor position.
- *
- * See [`commitText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#commitText(java.lang.CharSequence,%20int)).
- *
- * @param text The text to commit.
- * @param newCursorPosition The cursor position after inserted text.
- */
-internal fun EditingBuffer.commitText(
-    text: String,
-    newCursorPosition: Int
-) {
-    // API description says replace ongoing composition text if there. Then, if there is no
-    // composition text, insert text into cursor position or replace selection.
-    if (hasComposition()) {
-        replace(compositionStart, compositionEnd, text)
-    } else {
-        // In this editing buffer, insert into cursor or replace selection are equivalent.
-        replace(selectionStart, selectionEnd, text)
-    }
-
-    // After replace function is called, the editing buffer places the cursor at the end of the
-    // modified range.
-    val newCursor = cursor
-
-    // See above API description for the meaning of newCursorPosition.
-    val newCursorInBuffer = if (newCursorPosition > 0) {
-        newCursor + newCursorPosition - 1
-    } else {
-        newCursor + newCursorPosition - text.length
-    }
-
-    cursor = newCursorInBuffer.coerceIn(0, length)
-}
-
-/**
- * Mark a certain region of text as composing text.
- *
- * See [`setComposingRegion`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setComposingRegion(int,%2520int)).
- *
- * @param start The inclusive start offset of the composing region.
- * @param end The exclusive end offset of the composing region
- */
-internal fun EditingBuffer.setComposingRegion(
-    start: Int,
-    end: Int
-) {
-    // The API description says, different from SetComposingText, SetComposingRegion must
-    // preserve the ongoing composition text and set new composition.
-    if (hasComposition()) {
-        commitComposition()
-    }
-
-    // Sanitize the input: reverse if reversed, clamped into valid range, ignore empty range.
-    val clampedStart = start.coerceIn(0, length)
-    val clampedEnd = end.coerceIn(0, length)
-    if (clampedStart == clampedEnd) {
-        // do nothing. empty composition range is not allowed.
-    } else if (clampedStart < clampedEnd) {
-        setComposition(clampedStart, clampedEnd)
-    } else {
-        setComposition(clampedEnd, clampedStart)
-    }
-}
-
-/**
- * Replace the currently composing text with the given text, and set the new cursor position. Any
- * composing text set previously will be removed automatically.
- *
- * See [`setComposingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setComposingText(java.lang.CharSequence,%2520int)).
- *
- * @param text The composing text.
- * @param newCursorPosition The cursor position after setting composing text.
- */
-internal fun EditingBuffer.setComposingText(
-    text: String,
-    newCursorPosition: Int
-) {
-    if (hasComposition()) {
-        // API doc says, if there is ongoing composing text, replace it with new text.
-        val compositionStart = compositionStart
-        replace(compositionStart, compositionEnd, text)
-        if (text.isNotEmpty()) {
-            setComposition(compositionStart, compositionStart + text.length)
-        }
-    } else {
-        // If there is no composing text, insert composing text into cursor position with
-        // removing selected text if any.
-        val selectionStart = selectionStart
-        replace(selectionStart, selectionEnd, text)
-        if (text.isNotEmpty()) {
-            setComposition(selectionStart, selectionStart + text.length)
-        }
-    }
-
-    // After replace function is called, the editing buffer places the cursor at the end of the
-    // modified range.
-    val newCursor = cursor
-
-    // See above API description for the meaning of newCursorPosition.
-    val newCursorInBuffer = if (newCursorPosition > 0) {
-        newCursor + newCursorPosition - 1
-    } else {
-        newCursor + newCursorPosition - text.length
-    }
-
-    cursor = newCursorInBuffer.coerceIn(0, length)
-}
-
-/**
- * Delete [lengthBeforeCursor] characters of text before the current cursor position, and delete
- * [lengthAfterCursor] characters of text after the current cursor position, excluding the selection.
- *
- * Before and after refer to the order of the characters in the string, not to their visual
- * representation.
- *
- * See [`deleteSurroundingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingText(int,%2520int)).
- *
- * @param lengthBeforeCursor The number of characters in UTF-16 before the cursor to be deleted.
- * Must be non-negative.
- * @param lengthAfterCursor The number of characters in UTF-16 after the cursor to be deleted.
- * Must be non-negative.
- */
-internal fun EditingBuffer.deleteSurroundingText(
-    lengthBeforeCursor: Int,
-    lengthAfterCursor: Int
-) {
-    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
-        "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
-            "$lengthBeforeCursor and $lengthAfterCursor respectively."
-    }
-
-    // calculate the end with safe addition since lengthAfterCursor can be set to e.g. Int.MAX
-    // by the input
-    val end = selectionEnd.addExactOrElse(lengthAfterCursor) { length }
-    delete(selectionEnd, minOf(end, length))
-
-    // calculate the start with safe subtraction since lengthBeforeCursor can be set to e.g.
-    // Int.MAX by the input
-    val start = selectionStart.subtractExactOrElse(lengthBeforeCursor) { 0 }
-    delete(maxOf(0, start), selectionStart)
-}
-
-/**
- * A variant of [deleteSurroundingText]. The difference is that
- * * The lengths are supplied in code points, not in chars.
- * * This command does nothing if there are one or more invalid surrogate pairs
- * in the requested range.
- *
- * See [`deleteSurroundingTextInCodePoints`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingTextInCodePoints(int,%2520int)).
- *
- * @param lengthBeforeCursor The number of characters in Unicode code points before the cursor to be
- * deleted. Must be non-negative.
- * @param lengthAfterCursor The number of characters in Unicode code points after the cursor to be
- * deleted. Must be non-negative.
- */
-internal fun EditingBuffer.deleteSurroundingTextInCodePoints(
-    lengthBeforeCursor: Int,
-    lengthAfterCursor: Int
-) {
-    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
-        "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
-            "$lengthBeforeCursor and $lengthAfterCursor respectively."
-    }
-
-    // Convert code point length into character length. Then call the common logic of the
-    // DeleteSurroundingTextEditOp
-    var beforeLenInChars = 0
-    for (i in 0 until lengthBeforeCursor) {
-        beforeLenInChars++
-        if (selectionStart > beforeLenInChars) {
-            val lead = this[selectionStart - beforeLenInChars - 1]
-            val trail = this[selectionStart - beforeLenInChars]
-
-            if (isSurrogatePair(lead, trail)) {
-                beforeLenInChars++
-            }
-        }
-        if (beforeLenInChars == selectionStart) break
-    }
-
-    var afterLenInChars = 0
-    for (i in 0 until lengthAfterCursor) {
-        afterLenInChars++
-        if (selectionEnd + afterLenInChars < length) {
-            val lead = this[selectionEnd + afterLenInChars - 1]
-            val trail = this[selectionEnd + afterLenInChars]
-
-            if (isSurrogatePair(lead, trail)) {
-                afterLenInChars++
-            }
-        }
-        if (selectionEnd + afterLenInChars == length) break
-    }
-
-    delete(selectionEnd, selectionEnd + afterLenInChars)
-    delete(selectionStart - beforeLenInChars, selectionStart)
-}
-
-/**
- * Finishes the composing text that is currently active. This simply leaves the text as-is,
- * removing any special composing styling or other state that was around it. The cursor position
- * remains unchanged.
- *
- * See [`finishComposingText`](https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#finishComposingText()).
- */
-internal fun EditingBuffer.finishComposingText() {
-    commitComposition()
-}
-
-/**
- * Represents a backspace operation at the cursor position.
- *
- * If there is composition, delete the text in the composition range.
- * If there is no composition but there is selection, delete whole selected range.
- * If there is no composition and selection, perform backspace key event at the cursor position.
- */
-internal fun EditingBuffer.backspace() {
-    if (hasComposition()) {
-        delete(compositionStart, compositionEnd)
-    } else if (cursor == -1) {
-        val delStart = selectionStart
-        val delEnd = selectionEnd
-        cursor = selectionStart
-        delete(delStart, delEnd)
-    } else if (cursor != 0) {
-        val prevCursorPos = toString().findPrecedingBreak(cursor)
-        delete(prevCursorPos, cursor)
-    }
-}
-
-/**
- * Moves the cursor with [amount] characters.
- *
- * If there is selection, cancel the selection first and move the cursor to the selection start
- * position. Then perform the cursor movement.
- *
- * @param amount The amount of cursor movement. If you want to move backward, pass negative value.
- */
-internal fun EditingBuffer.moveCursor(amount: Int) {
-    if (cursor == -1) {
-        cursor = selectionStart
-    }
-
-    var newCursor = selectionStart
-    val bufferText = toString()
-    if (amount > 0) {
-        for (i in 0 until amount) {
-            val next = bufferText.findFollowingBreak(newCursor)
-            if (next == -1) break
-            newCursor = next
-        }
-    } else {
-        for (i in 0 until -amount) {
-            val prev = bufferText.findPrecedingBreak(newCursor)
-            if (prev == -1) break
-            newCursor = prev
-        }
-    }
-
-    cursor = newCursor
-}
-
-/**
- * Deletes all the text in the buffer.
- */
-internal fun EditingBuffer.deleteAll() {
-    replace(0, length, "")
-}
-
-/**
- * Helper function that returns true when [high] is a Unicode high-surrogate code unit and [low]
- * is a Unicode low-surrogate code unit.
- */
-private fun isSurrogatePair(high: Char, low: Char): Boolean =
-    high.isHighSurrogate() && low.isLowSurrogate()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
deleted file mode 100644
index 56c34fa..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-
-/**
- * The editing buffer
- *
- * This class manages the all editing relate states, editing buffers, selection, styles, etc.
- */
-internal class EditingBuffer(
-    /**
-     * The initial text of this editing buffer
-     */
-    text: AnnotatedString,
-    /**
-     * The initial selection range of this buffer.
-     * If you provide collapsed selection, it is treated as the cursor position. The cursor and
-     * selection cannot exists at the same time.
-     * The selection must points the valid index of the initialText, otherwise
-     * IndexOutOfBoundsException will be thrown.
-     */
-    selection: TextRange
-) {
-    internal companion object {
-        const val NOWHERE = -1
-    }
-
-    private val gapBuffer = PartialGapBuffer(text.text)
-
-    val changeTracker = ChangeTracker()
-
-    /**
-     * The inclusive selection start offset
-     */
-    var selectionStart = selection.start
-        private set(value) {
-            require(value >= 0) { "Cannot set selectionStart to a negative value: $value" }
-            field = value
-        }
-
-    /**
-     * The exclusive selection end offset
-     */
-    var selectionEnd = selection.end
-        private set(value) {
-            require(value >= 0) { "Cannot set selectionEnd to a negative value: $value" }
-            field = value
-        }
-
-    /**
-     * The inclusive composition start offset
-     *
-     * If there is no composing text, returns -1
-     */
-    var compositionStart = NOWHERE
-        private set
-
-    /**
-     * The exclusive composition end offset
-     *
-     * If there is no composing text, returns -1
-     */
-    var compositionEnd = NOWHERE
-        private set
-
-    /**
-     * Helper function that returns true if the editing buffer has composition text
-     */
-    fun hasComposition(): Boolean = compositionStart != NOWHERE
-
-    /**
-     * Returns the composition information as TextRange. Returns null if no
-     * composition is set.
-     */
-    val composition: TextRange?
-        get() = if (hasComposition()) {
-            TextRange(compositionStart, compositionEnd)
-        } else null
-
-    /**
-     * Returns the selection information as TextRange
-     */
-    val selection: TextRange
-        get() = TextRange(selectionStart, selectionEnd)
-
-    /**
-     * Helper accessor for cursor offset
-     */
-    /*VisibleForTesting*/
-    var cursor: Int
-        /**
-         * Return the cursor offset.
-         *
-         * Since selection and cursor cannot exist at the same time, return -1 if there is a
-         * selection.
-         */
-        get() = if (selectionStart == selectionEnd) selectionEnd else -1
-        /**
-         * Set the cursor offset.
-         *
-         * Since selection and cursor cannot exist at the same time, cancel selection if there is.
-         */
-        set(cursor) = setSelection(cursor, cursor)
-
-    /**
-     * [] operator for the character at the index.
-     */
-    operator fun get(index: Int): Char = gapBuffer[index]
-
-    /**
-     * Returns the length of the buffer.
-     */
-    val length: Int get() = gapBuffer.length
-
-    constructor(
-        text: String,
-        selection: TextRange
-    ) : this(AnnotatedString(text), selection)
-
-    init {
-        checkRange(selection.start, selection.end)
-    }
-
-    /**
-     * Replace the text and move the cursor to the end of inserted text.
-     *
-     * This function cancels selection if there is any.
-     *
-     * @throws IndexOutOfBoundsException if start or end offset is outside of current buffer
-     */
-    fun replace(start: Int, end: Int, text: CharSequence) {
-        checkRange(start, end)
-        val min = minOf(start, end)
-        val max = maxOf(start, end)
-
-        // coerce the replacement bounds before tracking change. This is mostly necessary for
-        // composition based typing when each keystroke may trigger a replace function that looks
-        // like "abcd" => "abcde".
-
-        // coerce min
-        var i = 0
-        var cMin = min
-        while (cMin < max && i < text.length && text[i] == gapBuffer[cMin]) {
-            i++
-            cMin++
-        }
-        // coerce max
-        var j = text.length
-        var cMax = max
-        while (cMax > min && j > i && text[j - 1] == gapBuffer[cMax - 1]) {
-            j--
-            cMax--
-        }
-
-        changeTracker.trackChange(cMin, cMax, j - i)
-
-        gapBuffer.replace(min, max, text)
-
-        // On Android, all text modification APIs also provides explicit cursor location. On the
-        // other hand, desktop applications usually don't. So, here tentatively move the cursor to
-        // the end offset of the editing area for desktop like application. In case of Android,
-        // implementation will call setSelection immediately after replace function to update this
-        // tentative cursor location.
-        selectionStart = min + text.length
-        selectionEnd = min + text.length
-
-        // Similarly, if text modification happens, cancel ongoing composition. If caller wants to
-        // change the composition text, it is caller's responsibility to call setComposition again
-        // to set composition range after replace function.
-        compositionStart = NOWHERE
-        compositionEnd = NOWHERE
-    }
-
-    /**
-     * Remove the given range of text.
-     *
-     * Different from replace method, this doesn't move cursor location to the end of modified text.
-     * Instead, preserve the selection with adjusting the deleted text.
-     */
-    fun delete(start: Int, end: Int) {
-        checkRange(start, end)
-        val deleteRange = TextRange(start, end)
-
-        changeTracker.trackChange(start, end, 0)
-
-        gapBuffer.replace(deleteRange.min, deleteRange.max, "")
-
-        val newSelection = updateRangeAfterDelete(
-            TextRange(selectionStart, selectionEnd),
-            deleteRange
-        )
-        selectionStart = newSelection.start
-        selectionEnd = newSelection.end
-
-        if (hasComposition()) {
-            val compositionRange = TextRange(compositionStart, compositionEnd)
-            val newComposition = updateRangeAfterDelete(compositionRange, deleteRange)
-            if (newComposition.collapsed) {
-                commitComposition()
-            } else {
-                compositionStart = newComposition.min
-                compositionEnd = newComposition.max
-            }
-        }
-    }
-
-    /**
-     * Mark the specified area of the text as selected text.
-     *
-     * You can set cursor by specifying the same value to `start` and `end`.
-     * The reversed range is not allowed.
-     * @param start the inclusive start offset of the selection
-     * @param end the exclusive end offset of the selection
-     */
-    fun setSelection(start: Int, end: Int) {
-        val clampedStart = start.coerceIn(0, length)
-        val clampedEnd = end.coerceIn(0, length)
-
-        selectionStart = clampedStart
-        selectionEnd = clampedEnd
-    }
-
-    /**
-     * Mark the specified area of the text as composition text.
-     *
-     * The empty range or reversed range is not allowed.
-     * Use clearComposition in case of clearing composition.
-     *
-     * @param start the inclusive start offset of the composition
-     * @param end the exclusive end offset of the composition
-     *
-     * @throws IndexOutOfBoundsException if start or end offset is ouside of current buffer
-     * @throws IllegalArgumentException if start is larger than or equal to end. (reversed or
-     *                                  collapsed range)
-     */
-    fun setComposition(start: Int, end: Int) {
-        if (start < 0 || start > gapBuffer.length) {
-            throw IndexOutOfBoundsException(
-                "start ($start) offset is outside of text region ${gapBuffer.length}"
-            )
-        }
-        if (end < 0 || end > gapBuffer.length) {
-            throw IndexOutOfBoundsException(
-                "end ($end) offset is outside of text region ${gapBuffer.length}"
-            )
-        }
-        if (start >= end) {
-            throw IllegalArgumentException("Do not set reversed or empty range: $start > $end")
-        }
-
-        compositionStart = start
-        compositionEnd = end
-    }
-
-    /**
-     * Commits the ongoing composition text and reset the composition range.
-     */
-    fun commitComposition() {
-        compositionStart = NOWHERE
-        compositionEnd = NOWHERE
-    }
-
-    override fun toString(): String = gapBuffer.toString()
-
-    fun toAnnotatedString(): AnnotatedString = AnnotatedString(toString())
-
-    private fun checkRange(start: Int, end: Int) {
-        if (start < 0 || start > gapBuffer.length) {
-            throw IndexOutOfBoundsException(
-                "start ($start) offset is outside of text region ${gapBuffer.length}"
-            )
-        }
-
-        if (end < 0 || end > gapBuffer.length) {
-            throw IndexOutOfBoundsException(
-                "end ($end) offset is outside of text region ${gapBuffer.length}"
-            )
-        }
-    }
-}
-
-/**
- * Returns the updated TextRange for [target] after the [deleted] TextRange is deleted as a Pair.
- *
- * If the [deleted] Range covers the whole target, Pair(-1,-1) is returned.
- */
-/*@VisibleForTesting*/
-internal fun updateRangeAfterDelete(target: TextRange, deleted: TextRange): TextRange {
-    var targetMin = target.min
-    var targetMax = target.max
-
-    // Following figure shows the deletion range and composition range.
-    // |---| represents deleted range.
-    // |===| represents target range.
-    if (deleted.intersects(target)) {
-        if (deleted.contains(target)) {
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :      |-------------|
-            //   Target     :          |======|
-            //
-            // Result:
-            //   Buffer     : ABCDETUVWXYZ
-            //   Target     :
-            targetMin = deleted.min
-            targetMax = targetMin
-        } else if (target.contains(deleted)) {
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :          |------|
-            //   Target     :        |==========|
-            //
-            // Result:
-            //   Buffer     : ABCDEFGHIQRSTUVWXYZ
-            //   Target     :        |===|
-            targetMax -= deleted.length
-        } else if (deleted.contains(targetMin)) {
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :      |---------|
-            //   Target     :            |========|
-            //
-            // Result:
-            //   Buffer     : ABCDEFPQRSTUVWXYZ
-            //   Target     :       |=====|
-            targetMin = deleted.min
-            targetMax -= deleted.length
-        } else { // deleteRange contains myMax
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :         |---------|
-            //   Target     :    |=======|
-            //
-            // Result:
-            //   Buffer     : ABCDEFGHSTUVWXYZ
-            //   Target     :    |====|
-            targetMax = deleted.min
-        }
-    } else {
-        if (targetMax <= deleted.min) {
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :            |-------|
-            //   Target     :  |=======|
-            //
-            // Result:
-            //   Buffer     : ABCDEFGHIJKLTUVWXYZ
-            //   Target     :  |=======|
-            // do nothing
-        } else {
-            // Input:
-            //   Buffer     : ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            //   Deleted    :  |-------|
-            //   Target     :            |=======|
-            //
-            // Result:
-            //   Buffer     : AJKLMNOPQRSTUVWXYZ
-            //   Target     :    |=======|
-            targetMin -= deleted.length
-            targetMax -= deleted.length
-        }
-    }
-
-    return TextRange(targetMin, targetMax)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt
deleted file mode 100644
index 40d3970..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-/**
- * The gap buffer implementation
- *
- * @param initBuffer An initial buffer. This class takes ownership of this object, so do not modify
- *                   array after passing to this constructor
- * @param initGapStart An initial inclusive gap start offset of the buffer
- * @param initGapEnd An initial exclusive gap end offset of the buffer
- */
-private class GapBuffer(initBuffer: CharArray, initGapStart: Int, initGapEnd: Int) {
-
-    /**
-     * The current capacity of the buffer
-     */
-    private var capacity = initBuffer.size
-
-    /**
-     * The buffer
-     */
-    private var buffer = initBuffer
-
-    /**
-     * The inclusive start offset of the gap
-     */
-    private var gapStart = initGapStart
-
-    /**
-     * The exclusive end offset of the gap
-     */
-    private var gapEnd = initGapEnd
-
-    /**
-     * The length of the gap.
-     */
-    private fun gapLength(): Int = gapEnd - gapStart
-
-    /**
-     * [] operator for the character at the index.
-     */
-    operator fun get(index: Int): Char {
-        if (index < gapStart) {
-            return buffer[index]
-        } else {
-            return buffer[index - gapStart + gapEnd]
-        }
-    }
-
-    /**
-     * Check if the gap has a requested size, and allocate new buffer if there is enough space.
-     */
-    private fun makeSureAvailableSpace(requestSize: Int) {
-        if (requestSize <= gapLength()) {
-            return
-        }
-
-        // Allocating necessary memory space by doubling the array size.
-        val necessarySpace = requestSize - gapLength()
-        var newCapacity = capacity * 2
-        while ((newCapacity - capacity) < necessarySpace) {
-            newCapacity *= 2
-        }
-
-        val newBuffer = CharArray(newCapacity)
-        buffer.copyInto(newBuffer, 0, 0, gapStart)
-        val tailLength = capacity - gapEnd
-        val newEnd = newCapacity - tailLength
-        buffer.copyInto(newBuffer, newEnd, gapEnd, gapEnd + tailLength)
-
-        buffer = newBuffer
-        capacity = newCapacity
-        gapEnd = newEnd
-    }
-
-    /**
-     * Delete the given range of the text.
-     */
-    private fun delete(start: Int, end: Int) {
-        if (start < gapStart && end <= gapStart) {
-            // The remove happens in the head buffer. Copy the tail part of the head buffer to the
-            // tail buffer.
-            //
-            // Example:
-            // Input:
-            //   buffer:     ABCDEFGHIJKLMNOPQ*************RSTUVWXYZ
-            //   del region:     |-----|
-            //
-            // First, move the remaining part of the head buffer to the tail buffer.
-            //   buffer:     ABCDEFGHIJKLMNOPQ*****KLKMNOPQRSTUVWXYZ
-            //   move data:            ^^^^^^^ =>  ^^^^^^^^
-            //
-            // Then, delete the given range. (just updating gap positions)
-            //   buffer:     ABCD******************KLKMNOPQRSTUVWXYZ
-            //   del region:     |-----|
-            //
-            // Output:       ABCD******************KLKMNOPQRSTUVWXYZ
-            val copyLen = gapStart - end
-            buffer.copyInto(buffer, gapEnd - copyLen, end, gapStart)
-            gapStart = start
-            gapEnd -= copyLen
-        } else if (start < gapStart && end >= gapStart) {
-            // The remove happens with accrossing the gap region. Just update the gap position
-            //
-            // Example:
-            // Input:
-            //   buffer:     ABCDEFGHIJKLMNOPQ************RSTUVWXYZ
-            //   del region:             |-------------------|
-            //
-            // Output:       ABCDEFGHIJKL********************UVWXYZ
-            gapEnd = end + gapLength()
-            gapStart = start
-        } else { // start > gapStart && end > gapStart
-            // The remove happens in the tail buffer. Copy the head part of the tail buffer to the
-            // head buffer.
-            //
-            // Example:
-            // Input:
-            //   buffer:     ABCDEFGHIJKL************MNOPQRSTUVWXYZ
-            //   del region:                            |-----|
-            //
-            // First, move the remaining part in the tail buffer to the head buffer.
-            //   buffer:     ABCDEFGHIJKLMNO*********MNOPQRSTUVWXYZ
-            //   move dat:               ^^^    <=   ^^^
-            //
-            // Then, delete the given range. (just updating gap positions)
-            //   buffer:     ABCDEFGHIJKLMNO******************VWXYZ
-            //   del region:                            |-----|
-            //
-            // Output:       ABCDEFGHIJKLMNO******************VWXYZ
-            val startInBuffer = start + gapLength()
-            val endInBuffer = end + gapLength()
-            val copyLen = startInBuffer - gapEnd
-            buffer.copyInto(buffer, gapStart, gapEnd, startInBuffer)
-            gapStart += copyLen
-            gapEnd = endInBuffer
-        }
-    }
-
-    /**
-     * Replace a region of this buffer with given text.
-     *
-     * @param start The index of the first character in this buffer to replace.
-     * @param end The index after the last character in this buffer to replace.
-     * @param text The new text to insert into the buffer.
-     * @param textStart The index of the first character in [text] to copy.
-     * @param textEnd The index after the last character in [text] to copy.
-     */
-    fun replace(
-        start: Int,
-        end: Int,
-        text: CharSequence,
-        textStart: Int = 0,
-        textEnd: Int = text.length
-    ) {
-        val textLength = textEnd - textStart
-        makeSureAvailableSpace(textLength - (end - start))
-
-        delete(start, end)
-
-        text.toCharArray(buffer, gapStart, textStart, textEnd)
-        gapStart += textLength
-    }
-
-    /**
-     * Write the current text into outBuf.
-     * @param builder The output string builder
-     */
-    fun append(builder: StringBuilder) {
-        builder.append(buffer, 0, gapStart)
-        builder.append(buffer, gapEnd, capacity - gapEnd)
-    }
-
-    /**
-     * The lengh of this gap buffer.
-     *
-     * This doesn't include internal hidden gap length.
-     */
-    fun length() = capacity - gapLength()
-
-    override fun toString(): String = StringBuilder().apply { append(this) }.toString()
-}
-
-/**
- * An editing buffer that uses Gap Buffer only around the cursor location.
- *
- * Different from the original gap buffer, this gap buffer doesn't convert all given text into
- * mutable buffer. Instead, this gap buffer converts cursor around text into mutable gap buffer
- * for saving construction time and memory space. If text modification outside of the gap buffer
- * is requested, this class flush the buffer and create new String, then start new gap buffer.
- *
- * @param text The initial text
- */
-internal class PartialGapBuffer(text: CharSequence) : CharSequence {
-    internal companion object {
-        const val BUF_SIZE = 255
-        const val SURROUNDING_SIZE = 64
-        const val NOWHERE = -1
-    }
-
-    private var text: CharSequence = text
-    private var buffer: GapBuffer? = null
-    private var bufStart = NOWHERE
-    private var bufEnd = NOWHERE
-
-    /**
-     * The text length
-     */
-    override val length: Int
-        get() {
-            val buffer = buffer ?: return text.length
-            return text.length - (bufEnd - bufStart) + buffer.length()
-        }
-
-    /**
-     * Replace a region of this buffer with given text.
-     *
-     * @param start The index of the first character in this buffer to replace.
-     * @param end The index after the last character in this buffer to replace.
-     * @param text The new text to insert into the buffer.
-     * @param textStart The index of the first character in [text] to copy.
-     * @param textEnd The index after the last character in [text] to copy.
-     */
-    fun replace(
-        start: Int,
-        end: Int,
-        text: CharSequence,
-        textStart: Int = 0,
-        textEnd: Int = text.length
-    ) {
-        require(start <= end) { "start=$start > end=$end" }
-        require(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
-        require(start >= 0) { "start must be non-negative, but was $start" }
-        require(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
-
-        val buffer = buffer
-        val textLength = textEnd - textStart
-        if (buffer == null) { // First time to create gap buffer
-            val charArray = CharArray(maxOf(BUF_SIZE, textLength + 2 * SURROUNDING_SIZE))
-
-            // Convert surrounding text into buffer.
-            val leftCopyCount = minOf(start, SURROUNDING_SIZE)
-            val rightCopyCount = minOf(this.text.length - end, SURROUNDING_SIZE)
-
-            // Copy left surrounding
-            this.text.toCharArray(charArray, 0, start - leftCopyCount, start)
-
-            // Copy right surrounding
-            this.text.toCharArray(
-                charArray,
-                charArray.size - rightCopyCount,
-                end,
-                end + rightCopyCount
-            )
-
-            // Copy given text into buffer
-            text.toCharArray(charArray, leftCopyCount, textStart, textEnd)
-
-            this.buffer = GapBuffer(
-                charArray,
-                initGapStart = leftCopyCount + textLength,
-                initGapEnd = charArray.size - rightCopyCount
-            )
-            bufStart = start - leftCopyCount
-            bufEnd = end + rightCopyCount
-            return
-        }
-
-        // Convert user space offset into buffer space offset
-        val bufferStart = start - bufStart
-        val bufferEnd = end - bufStart
-        if (bufferStart < 0 || bufferEnd > buffer.length()) {
-            // Text modification outside of gap buffer has requested. Flush the buffer and try it
-            // again.
-            this.text = toString()
-            this.buffer = null
-            bufStart = NOWHERE
-            bufEnd = NOWHERE
-            return replace(start, end, text, textStart, textEnd)
-        }
-
-        buffer.replace(bufferStart, bufferEnd, text, textStart, textEnd)
-    }
-
-    /**
-     * [] operator for the character at the index.
-     */
-    override operator fun get(index: Int): Char {
-        val buffer = buffer ?: return text[index]
-        if (index < bufStart) {
-            return text[index]
-        }
-        val gapBufLength = buffer.length()
-        if (index < gapBufLength + bufStart) {
-            return buffer[index - bufStart]
-        }
-        return text[index - (gapBufLength - bufEnd + bufStart)]
-    }
-
-    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
-        toString().subSequence(startIndex, endIndex)
-
-    override fun toString(): String {
-        val b = buffer ?: return text.toString()
-        val sb = StringBuilder()
-        sb.append(text, 0, bufStart)
-        b.append(sb)
-        sb.append(text, bufEnd, text.length)
-        return sb.toString()
-    }
-
-    /**
-     * Compares the contents of this buffer with the contents of [other].
-     */
-    fun contentEquals(other: CharSequence): Boolean {
-        return toString() == other.toString()
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/MathUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/MathUtils.kt
deleted file mode 100644
index f9dfcb4e..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/MathUtils.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.text.selection.containsInclusive
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-
-/**
- * Adds [this] and [right], and if an overflow occurs returns result of [defaultValue].
- */
-internal inline fun Int.addExactOrElse(right: Int, defaultValue: () -> Int): Int {
-    val result = this + right
-    // HD 2-12 Overflow iff both arguments have the opposite sign of the result
-    return if (this xor result and (right xor result) < 0) defaultValue() else result
-}
-
-/**
- * Subtracts [right] from [this], and if an overflow occurs returns result of [defaultValue].
- */
-internal inline fun Int.subtractExactOrElse(right: Int, defaultValue: () -> Int): Int {
-    val result = this - right
-    // HD 2-12 Overflow iff the arguments have different signs and
-    // the sign of the result is different from the sign of x
-    return if (this xor right and (this xor result) < 0) defaultValue() else result
-}
-
-/**
- * Returns -1 if this [Offset] is closer to [rect1], 1 if it's closer to [rect2], or 0 if it's
- * equidistant to both. If the point is inside either rectangle, the distance is calculated as zero.
- */
-internal fun Offset.findClosestRect(rect1: Rect, rect2: Rect): Int {
-    val comparativeDistTo1 = distanceSquaredToClosestCornerFromOutside(rect1)
-    val comparativeDistTo2 = distanceSquaredToClosestCornerFromOutside(rect2)
-    if (comparativeDistTo1 == comparativeDistTo2) return 0
-    return if (comparativeDistTo1 < comparativeDistTo2) -1 else 1
-}
-
-/**
- * Calculates the distance from this [Offset] to the nearest point on [rect].
- * Returns 0 if the offset is within [rect].
- */
-private fun Offset.distanceSquaredToClosestCornerFromOutside(rect: Rect): Float {
-    if (rect.containsInclusive(this)) return 0f
-    var distance = Float.MAX_VALUE
-    (rect.topLeft - this).getDistanceSquared().let { if (it < distance) distance = it }
-    (rect.topRight - this).getDistanceSquared().let { if (it < distance) distance = it }
-    (rect.bottomLeft - this).getDistanceSquared().let { if (it < distance) distance = it }
-    (rect.bottomRight - this).getDistanceSquared().let { if (it < distance) distance = it }
-    return distance
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt
deleted file mode 100644
index 6c9ebb7..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt
+++ /dev/null
@@ -1,415 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.ui.text.TextRange
-
-/**
- * Builds up bidirectional mapping functions to map offsets from an original string to corresponding
- * in a string that has some edit operations applied.
- *
- * Edit operations must be reported via [recordEditOperation]. Offsets can be mapped
- * [from the source string][mapFromSource] and [back to the source][mapFromDest]. Mapping between
- * source and transformed offsets is a symmetric operation – given a sequence of edit operations,
- * the result of mapping each offset from the source to transformed strings will be the same as
- * mapping backwards on the inverse sequence of edit operations.
- *
- * By default, offsets map one-to-one. However, around an edit operation, some alternative rules
- * apply. In general, offsets are mapped one-to-one where they can be unambiguously. When there
- * isn't enough information, the mapping is ambiguous, and the mapping result will be a [TextRange]
- * instead of a single value, where the range represents all the possible offsets that it could map
- * to.
- *
- * _Note: In the following discussion, `I` refers to the start offset in the source text, `N` refers
- * to the length of the range in the source text, and `N'` refers to the length of the replacement
- * text. So given `"abc"` and replacing `"bc"` with `"xyz"`, `I=1`, `N=2`, and `N'=3`._
- *
- * ### Insertions
- * When text is inserted (i.e. zero-length range is replaced with non-zero-length range), the
- * mapping is ambiguous because all of the offsets in the inserted text map back to the same offset
- * in the source text - the offset where the text was inserted. That means the insertion point can
- * map to any of the offsets in the inserted text. I.e. I -> I..N'
- *
- * - This is slightly different than the replacement case, because the offset of the start of
- *   the operation and the offset of the end of the operation (which are the same) map to a
- *   range instead of a scalar. This is because there is not enough information to map the start
- *   and end offsets 1-to-1 to offsets in the transformed text.
- * - This is symmetric with deletion: Mapping backward from an insertion is the same as mapping
- *   forward from a deletion.
- *
- * ### Deletions
- * In the inverse case, when text is deleted, mapping is unambiguous. All the offsets in the
- * deleted range map to the start of the deleted range. I.e. I..N -> I
- *
- * - This is symmetric with insertion: Mapping backward from a deletion is the same as mapping
- *   forward from an insertion.
- *
- * ### Replacements
- * When text is replaced, there are both ambiguous and unambiguous mappings:
- * - The offsets at the _ends_ of the replaced range map unambiguously to the offsets at the
- *   corresponding edges of the replaced text. I -> I and I+1 -> I+N
- * - The offsets _inside_ the replaced range (exclusive) map ambiguously to the entire replaced
- *   range. I+1..I+N-1 -> I+1..I+N'-1
- * - Note that this means that when a string with length >1 is replaced by a single character,
- *   all the offsets inside that string will map not to the index of the replacement character
- *   but to a single-char _range_ containing that character.
- *
- * ### Examples
- *
- * #### Inserting text
- * ```
- *     012
- * A: "ab"
- *     | \
- *     |  \
- * B: "azzzb"
- *     012345
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 |
- * |--------:|:-:|:-:|:-:|
- * |   to B: | 0 |1-4| 5 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 | 3 | 4 | 5 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
- * |   to A: | 0 | 1 | 1 | 1 | 1 | 2 |
- *
- * #### Deleting text
- * ```
- *     012345
- * A: "azzzb"
- *     |  /
- *     | /
- * B: "ab"
- *     012
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
- * |   to B: | 0 | 1 | 1 | 1 | 1 | 2 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 |
- * |--------:|:-:|:-:|:-:|
- * |   to A: | 0 |1-4| 5 |
- *
- * #### Replacing text: single char with char
- * ```
- *     0123
- * A: "abc"
- *      |
- *      |
- * B: "azc"
- *     0123
- * ```
- *
- * Forward/reverse mapping: identity
- *
- * | from: | 0 | 1 | 2 | 3 |
- * |------:|:-:|:-:|:-:|:-:|
- * |   to: | 0 | 1 | 2 | 3 |
- *
- * #### Replacing text: char with chars
- * ```
- *     0123
- * A: "abc"
- *      |
- *      |\
- * B: "azzc"
- *     01234
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 | 3 |
- * |--------:|:-:|:-:|:-:|:-:|
- * |   to B: | 0 | 1 | 3 | 4 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 | 3 | 4 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|
- * |   to A: | 0 | 1 | 1 | 2 | 3 |
- *
- * #### Replacing text: chars with chars
- * ```
- *     012345
- * A: "abcde"
- *      | |
- *      | /
- * B: "azze"
- *     01234
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
- * |   to B: | 0 | 1 |1-3|1-3| 3 | 4 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 | 3 | 4 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|
- * |   to A: | 0 | 1 |1-4| 4 | 5 |
- *
- * ### Multiple operations
- *
- * While the above apply to single edit operations, when multiple edit operations are recorded the
- * same rules apply. The rules are applied to the first operation, then the result of that is
- * effectively used as the input text for the next operation, etc. Because an offset can map to a
- * range at each step, we track both a start and end offset (which start as the same value), and
- * at each step combine the start and end _ranges_ by taking their union.
- *
- * #### Multiple char-to-char replacements (codepoint transformation):
- * ```
- *     0123
- * A: "abc"
- *     |
- *    "•bc"
- *      |
- *    "••c"
- *       |
- * B: "•••"
- *     0123
- * ```
- *
- * Forward/reverse mapping: identity
- *
- * | from: | 0 | 1 | 2 | 3 |
- * |------:|:-:|:-:|:-:|:-:|
- * |   to: | 0 | 1 | 2 | 3 |
- *
- * #### Multiple inserts:
- * ```
- *     01234
- * A: "abcd"
- *      |
- *    "a(bcd"
- *         |
- * B: "a(bc)d"
- *     0123456
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 | 3 | 4 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|
- * |   to B: | 0 |1-2| 3 |4-5| 6 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
- * |   to A: | 0 | 1 | 1 | 2 | 3 | 3 | 4 |
- *
- * #### Multiple replacements of the same range:
- * ```
- *     01234
- * A: "abcd"
- *      ||
- *    "awxd"
- *      ||
- * B: "ayzd"
- *     01234
- * ```
- *
- * Forward mapping:
- *
- * | from A: | 0 | 1 | 2 | 3 | 4 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|
- * |   to B: | 0 | 1 |1-3| 3 | 4 |
- *
- * Reverse mapping:
- *
- * | from B: | 0 | 1 | 2 | 3 | 4 |
- * |--------:|:-:|:-:|:-:|:-:|:-:|
- * |   to A: | 0 | 1 |1-3| 3 | 4 |
- *
- * For other edge cases, including overlapping replacements, see `OffsetMappingCalculatorTest`.
- */
-internal class OffsetMappingCalculator {
-    /** Resizable array of edit operations, size is defined by [opsSize]. */
-    private var ops = OpArray(size = 10)
-    private var opsSize = 0
-
-    /**
-     * Records an edit operation that replaces the range from [sourceStart] (inclusive) to
-     * [sourceEnd] (exclusive) in the original text with some text with length [newLength].
-     */
-    fun recordEditOperation(sourceStart: Int, sourceEnd: Int, newLength: Int) {
-        require(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
-        val sourceMin = minOf(sourceStart, sourceEnd)
-        val sourceMax = maxOf(sourceMin, sourceEnd)
-        val sourceLength = sourceMax - sourceMin
-
-        // 0,0 is a noop, and 1,1 is always a 1-to-1 mapping so we don't need to record it.
-        if (sourceLength < 2 && sourceLength == newLength) return
-
-        // Append the operation to the array, growing it if necessary.
-        val newSize = opsSize + 1
-        if (newSize > ops.size) {
-            val newCapacity = maxOf(newSize * 2, ops.size * 2)
-            ops = ops.copyOf(newCapacity)
-        }
-        ops.set(opsSize, sourceMin, sourceLength, newLength)
-        opsSize = newSize
-    }
-
-    /**
-     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
-     * in the transformed string.
-     */
-    fun mapFromSource(offset: Int): TextRange = map(offset, fromSource = true)
-
-    /**
-     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
-     * in the transformed string.
-     */
-    fun mapFromDest(offset: Int): TextRange = map(offset, fromSource = false)
-
-    private fun map(offset: Int, fromSource: Boolean): TextRange {
-        var start = offset
-        var end = offset
-
-        // This algorithm works for both forward and reverse mapping, we just need to iterate
-        // backwards to do reverse mapping.
-        ops.forEach(max = opsSize, reversed = !fromSource) { opOffset, opSrcLen, opDestLen ->
-            val newStart = mapStep(
-                offset = start,
-                opOffset = opOffset,
-                untransformedLen = opSrcLen,
-                transformedLen = opDestLen,
-                fromSource = fromSource
-            )
-            val newEnd = mapStep(
-                offset = end,
-                opOffset = opOffset,
-                untransformedLen = opSrcLen,
-                transformedLen = opDestLen,
-                fromSource = fromSource
-            )
-            // range = newStart ∪ newEnd
-            // Note we don't read TextRange.min/max here because the above code always returns
-            // normalized ranges. It's no less correct, but there's no need to do the additional
-            // min/max calls inside the min/max properties.
-            start = minOf(newStart.start, newEnd.start)
-            end = maxOf(newStart.end, newEnd.end)
-        }
-
-        return TextRange(start, end)
-    }
-
-    private fun mapStep(
-        offset: Int,
-        opOffset: Int,
-        untransformedLen: Int,
-        transformedLen: Int,
-        fromSource: Boolean
-    ): TextRange {
-        val srcLen = if (fromSource) untransformedLen else transformedLen
-        val destLen = if (fromSource) transformedLen else untransformedLen
-        return when {
-            // Before the operation, no change.
-            offset < opOffset -> TextRange(offset)
-
-            offset == opOffset -> {
-                if (srcLen == 0) {
-                    // On insertion point, map to inserted range.
-                    TextRange(opOffset, opOffset + destLen)
-                } else {
-                    // On start of replacement, map to start of replaced range.
-                    TextRange(opOffset)
-                }
-            }
-
-            offset < opOffset + srcLen -> {
-                if (destLen == 0) {
-                    // In deleted range, map to start of deleted range.
-                    TextRange(opOffset)
-                } else {
-                    // In replaced range, map to transformed range.
-                    TextRange(opOffset, opOffset + destLen)
-                }
-            }
-
-            // On end of or after replaced range, offset the offset.
-            else -> TextRange(offset - srcLen + destLen)
-        }
-    }
-}
-
-/**
- * An array of 3-tuples of ints. Each element's values are stored as individual values in the
- * underlying array.
- */
[email protected]
-private value class OpArray private constructor(private val values: IntArray) {
-    constructor(size: Int) : this(IntArray(size * ElementSize))
-
-    val size: Int get() = values.size / ElementSize
-
-    fun set(index: Int, offset: Int, srcLen: Int, destLen: Int) {
-        values[index * ElementSize] = offset
-        values[index * ElementSize + 1] = srcLen
-        values[index * ElementSize + 2] = destLen
-    }
-
-    fun copyOf(newSize: Int) = OpArray(values.copyOf(newSize * ElementSize))
-
-    /**
-     * Loops through the array between 0 and [max] (exclusive). If [reversed] is false (the
-     * default), iterates forward from 0. When it's true, iterates backwards from `max-1`.
-     */
-    inline fun forEach(
-        max: Int,
-        reversed: Boolean = false,
-        block: (offset: Int, srcLen: Int, destLen: Int) -> Unit
-    ) {
-        if (max < 0) return
-        // Note: This stamps out block twice at the callsite, which is normally bad for an inline
-        // function to do. However, this is a file-private function which is only called in one
-        // place that would need to have two copies of mostly-identical code anyway. Doing the
-        // duplication here keeps the more complicated logic at the callsite more readable.
-        if (reversed) {
-            for (i in max - 1 downTo 0) {
-                val offset = values[i * ElementSize]
-                val srcLen = values[i * ElementSize + 1]
-                val destLen = values[i * ElementSize + 2]
-                block(offset, srcLen, destLen)
-            }
-        } else {
-            for (i in 0 until max) {
-                val offset = values[i * ElementSize]
-                val srcLen = values[i * ElementSize + 1]
-                val destLen = values[i * ElementSize + 2]
-                block(offset, srcLen, destLen)
-            }
-        }
-    }
-
-    private companion object {
-        const val ElementSize = 3
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/StateSyncingModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/StateSyncingModifier.kt
deleted file mode 100644
index 4e5a2e3..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/StateSyncingModifier.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusEventModifierNode
-import androidx.compose.ui.focus.FocusState
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverModifierNode
-import androidx.compose.ui.node.observeReads
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.text.input.TextFieldValue
-
-/**
- * Synchronizes between [TextFieldState], immutable values, and value change callbacks for
- * [BasicTextField2] overloads that take a value+callback for state instead of taking a
- * [TextFieldState] directly. Effectively a fancy `rememberUpdatedState`.
- *
- * Only intended for use from [BasicTextField2].
- *
- * @param writeSelectionFromTextFieldValue If true, [update] will synchronize the selection from the
- * [TextFieldValue] to the [TextFieldState]. The text will be synchronized regardless.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun Modifier.syncTextFieldState(
-    state: TextFieldState,
-    value: TextFieldValue,
-    onValueChanged: (TextFieldValue) -> Unit,
-    writeSelectionFromTextFieldValue: Boolean,
-): Modifier = this.then(
-    StateSyncingModifier(
-        state = state,
-        value = value,
-        onValueChanged = onValueChanged,
-        writeSelectionFromTextFieldValue = writeSelectionFromTextFieldValue
-    )
-)
-
-@OptIn(ExperimentalFoundationApi::class)
-private class StateSyncingModifier(
-    private val state: TextFieldState,
-    private val value: TextFieldValue,
-    private val onValueChanged: (TextFieldValue) -> Unit,
-    private val writeSelectionFromTextFieldValue: Boolean,
-) : ModifierNodeElement<StateSyncingModifierNode>() {
-
-    override fun create(): StateSyncingModifierNode =
-        StateSyncingModifierNode(state, onValueChanged, writeSelectionFromTextFieldValue)
-
-    override fun update(node: StateSyncingModifierNode) {
-        node.update(value, onValueChanged)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        // Always call update, without comparing the text. Update can compare more efficiently.
-        return false
-    }
-
-    override fun hashCode(): Int {
-        // Avoid calculating hash from values that can change on every recomposition.
-        return state.hashCode()
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        // no inspector properties
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private class StateSyncingModifierNode(
-    private val state: TextFieldState,
-    private var onValueChanged: (TextFieldValue) -> Unit,
-    private val writeSelectionFromTextFieldValue: Boolean,
-) : Modifier.Node(), ObserverModifierNode, FocusEventModifierNode {
-
-    private var isFocused = false
-    private var lastValueWhileFocused: TextFieldValue? = null
-
-    override val shouldAutoInvalidate: Boolean
-        get() = false
-
-    /**
-     * Synchronizes the latest [value] to the [TextFieldState] and updates our [onValueChanged]
-     * callback. Should be called from [ModifierNodeElement.update].
-     */
-    fun update(value: TextFieldValue, onValueChanged: (TextFieldValue) -> Unit) {
-        this.onValueChanged = onValueChanged
-
-        // Don't modify the text programmatically while an edit session is in progress.
-        // WARNING: While editing, the code that holds the external state is temporarily not the
-        // actual source of truth. This "stealing" of control is generally an anti-pattern. We do it
-        // intentionally here because text field state is very sensitive to timing, and if a state
-        // update is delivered a frame late, it breaks text input. It is very easy to accidentally
-        // introduce small bits of asynchrony in real-world scenarios, e.g. with Flow-based reactive
-        // architectures. The benefit of avoiding that easy pitfall outweighs the weirdness in this
-        // case.
-        if (!isFocused) {
-            updateState(value)
-        } else {
-            this.lastValueWhileFocused = value
-        }
-    }
-
-    override fun onAttach() {
-        // Don't fire the callback on first frame.
-        observeTextState(fireOnValueChanged = false)
-    }
-
-    override fun onFocusEvent(focusState: FocusState) {
-        if (this.isFocused && !focusState.isFocused) {
-            // Lost focus, perform deferred synchronization.
-            lastValueWhileFocused?.let(::updateState)
-            lastValueWhileFocused = null
-        }
-        this.isFocused = focusState.isFocused
-    }
-
-    /** Called by the modifier system when the [TextFieldState] has changed. */
-    override fun onObservedReadsChanged() {
-        observeTextState()
-    }
-
-    private fun updateState(value: TextFieldValue) {
-        state.edit {
-            // Avoid registering a state change if the text isn't actually different.
-            setTextIfChanged(value.text)
-
-            // The BasicTextField2(String) variant can't push a selection value, so ignore it.
-            if (writeSelectionFromTextFieldValue) {
-                selectCharsIn(value.selection)
-            }
-        }
-    }
-
-    private fun observeTextState(fireOnValueChanged: Boolean = true) {
-        lateinit var text: TextFieldCharSequence
-        observeReads {
-            text = state.text
-        }
-
-        // This code is outside of the observeReads lambda so we don't observe any state reads the
-        // callback happens to do.
-        if (fireOnValueChanged) {
-            val newValue = TextFieldValue(
-                text = text.toString(),
-                selection = text.selectionInChars,
-                composition = text.compositionInChars
-            )
-            onValueChanged(newValue)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt
deleted file mode 100644
index be1237c..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.infiniteRepeatable
-import androidx.compose.animation.core.keyframes
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.scrollBy
-import androidx.compose.foundation.text.selection.LocalTextSelectionColors
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
-import androidx.compose.foundation.text2.input.internal.selection.textFieldMagnifierNode
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.MotionDurationScale
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.isUnspecified
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.SemanticsModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.node.invalidateMeasurement
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextPainter
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastCoerceIn
-import kotlin.math.ceil
-import kotlin.math.floor
-import kotlin.math.min
-import kotlin.math.truncate
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * Modifier element for the core functionality of [BasicTextField2] that is passed as inner
- * TextField to the decoration box. This is only half the actual modifiers for the field, the other
- * half are only attached to the decorated text field.
- *
- * This modifier mostly handles layout and draw.
- */
-internal data class TextFieldCoreModifier(
-    private val isFocused: Boolean, /* true iff component is focused and the window in focus */
-    private val isDragHovered: Boolean,
-    private val textLayoutState: TextLayoutState,
-    private val textFieldState: TransformedTextFieldState,
-    private val textFieldSelectionState: TextFieldSelectionState,
-    private val cursorBrush: Brush,
-    private val writeable: Boolean,
-    private val scrollState: ScrollState,
-    private val orientation: Orientation,
-) : ModifierNodeElement<TextFieldCoreModifierNode>() {
-
-    override fun create(): TextFieldCoreModifierNode = TextFieldCoreModifierNode(
-        isFocused = isFocused,
-        isDragHovered = isDragHovered,
-        textLayoutState = textLayoutState,
-        textFieldState = textFieldState,
-        textFieldSelectionState = textFieldSelectionState,
-        cursorBrush = cursorBrush,
-        writeable = writeable,
-        scrollState = scrollState,
-        orientation = orientation,
-    )
-
-    override fun update(node: TextFieldCoreModifierNode) {
-        node.updateNode(
-            isFocused = isFocused,
-            isDragHovered = isDragHovered,
-            textLayoutState = textLayoutState,
-            textFieldState = textFieldState,
-            textFieldSelectionState = textFieldSelectionState,
-            cursorBrush = cursorBrush,
-            writeable = writeable,
-            scrollState = scrollState,
-            orientation = orientation,
-        )
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        // no inspector info
-    }
-}
-
-/** Modifier node for [TextFieldCoreModifier]. */
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextFieldCoreModifierNode(
-    // true iff this component is focused and the window is focused
-    private var isFocused: Boolean,
-    private var isDragHovered: Boolean,
-    private var textLayoutState: TextLayoutState,
-    private var textFieldState: TransformedTextFieldState,
-    private var textFieldSelectionState: TextFieldSelectionState,
-    private var cursorBrush: Brush,
-    private var writeable: Boolean,
-    private var scrollState: ScrollState,
-    private var orientation: Orientation,
-) : DelegatingNode(),
-    LayoutModifierNode,
-    DrawModifierNode,
-    CompositionLocalConsumerModifierNode,
-    GlobalPositionAwareModifierNode,
-    SemanticsModifierNode {
-
-    /**
-     * Animatable object for cursor's alpha value. It becomes 1f for half a second and 0f for
-     * another half a second when TextField is focused and editable. Initial value should be 0f
-     * so that when cursor needs to be drawn for the first time, change to 1f invalidates draw.
-     */
-    private val cursorAlpha = Animatable(0f)
-
-    /**
-     * Whether to show cursor at all when TextField has focus. This depends on enabled, read only,
-     * and brush at a given time.
-     */
-    private val showCursor: Boolean
-        get() = writeable && (isFocused || isDragHovered) && cursorBrush.isSpecified
-
-    /**
-     * Observes the [textFieldState] for any changes to content or selection. If a change happens,
-     * cursor blink animation gets reset.
-     */
-    private var changeObserverJob: Job? = null
-
-    /**
-     * When selection/cursor changes its position, it may go out of the visible area. When that
-     * happens, ideally we would want to scroll the TextField to keep the changing handle in the
-     * visible area. The following member variables keep track of the latest selection and cursor
-     * positions that we have adjusted for. When we detect a change to both of them during the
-     * layout phase, ScrollState gets adjusted.
-     */
-    private var previousSelection: TextRange? = null
-    private var previousCursorRect: Rect = Rect(-1f, -1f, -1f, -1f)
-
-    private val textFieldMagnifierNode = delegate(
-        textFieldMagnifierNode(
-            textFieldState = textFieldState,
-            textFieldSelectionState = textFieldSelectionState,
-            textLayoutState = textLayoutState,
-            visible = isFocused || isDragHovered
-        )
-    )
-
-    /**
-     * Updates all the related properties and invalidates internal state based on the changes.
-     */
-    fun updateNode(
-        isFocused: Boolean,
-        isDragHovered: Boolean,
-        textLayoutState: TextLayoutState,
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        cursorBrush: Brush,
-        writeable: Boolean,
-        scrollState: ScrollState,
-        orientation: Orientation,
-    ) {
-        val previousShowCursor = this.showCursor
-        val wasFocused = this.isFocused
-        val previousTextFieldState = this.textFieldState
-        val previousTextLayoutState = this.textLayoutState
-        val previousTextFieldSelectionState = this.textFieldSelectionState
-        val previousScrollState = this.scrollState
-
-        this.isFocused = isFocused
-        this.isDragHovered = isDragHovered
-        this.textLayoutState = textLayoutState
-        this.textFieldState = textFieldState
-        this.textFieldSelectionState = textFieldSelectionState
-        this.cursorBrush = cursorBrush
-        this.writeable = writeable
-        this.scrollState = scrollState
-        this.orientation = orientation
-
-        textFieldMagnifierNode.update(
-            textFieldState = textFieldState,
-            textFieldSelectionState = textFieldSelectionState,
-            textLayoutState = textLayoutState,
-            visible = isFocused || isDragHovered
-        )
-
-        if (!showCursor) {
-            changeObserverJob?.cancel()
-            changeObserverJob = null
-            coroutineScope.launch { cursorAlpha.snapTo(0f) }
-        } else if (!wasFocused ||
-            previousTextFieldState != textFieldState ||
-            !previousShowCursor
-        ) {
-            // this node is writeable, focused and gained that focus just now.
-            // start the state value observation
-            changeObserverJob = coroutineScope.launch {
-                // Animate the cursor even when animations are disabled by the system.
-                withContext(FixedMotionDurationScale) {
-                    snapshotFlow { textFieldState.visualText }
-                        .collectLatest {
-                            // ensure that the value is always 1f _this_ frame by calling snapTo
-                            cursorAlpha.snapTo(1f)
-                            // then start the cursor blinking on animation clock (500ms on to start)
-                            cursorAlpha.animateTo(0f, cursorAnimationSpec)
-                        }
-                }
-            }
-        }
-
-        if (previousTextFieldState != textFieldState ||
-            previousTextLayoutState != textLayoutState ||
-            previousTextFieldSelectionState != textFieldSelectionState ||
-            previousScrollState != scrollState) {
-            invalidateMeasurement()
-        }
-    }
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ) = if (orientation == Orientation.Vertical) {
-        measureVerticalScroll(measurable, constraints)
-    } else {
-        measureHorizontalScroll(measurable, constraints)
-    }
-
-    override fun ContentDrawScope.draw() {
-        drawContent()
-        val value = textFieldState.visualText
-        val textLayoutResult = textLayoutState.layoutResult ?: return
-
-        if (value.selectionInChars.collapsed) {
-            drawText(textLayoutResult)
-            drawCursor()
-        } else {
-            drawSelection(value.selectionInChars, textLayoutResult)
-            drawText(textLayoutResult)
-        }
-
-        with(textFieldMagnifierNode) { draw() }
-    }
-
-    private fun MeasureScope.measureVerticalScroll(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        // remove any height constraints for TextField since it'll be able to scroll vertically.
-        val childConstraints = constraints.copy(maxHeight = Constraints.Infinity)
-        val placeable = measurable.measure(childConstraints)
-        // final height is the minimum of calculated or constrained maximum height.
-        val height = min(placeable.height, constraints.maxHeight)
-
-        return layout(placeable.width, height) {
-            // we may need to update the scroll state to bring the cursor back into view after
-            // layout is completed.
-            val currSelection = textFieldState.visualText.selectionInChars
-            val offsetToFollow = calculateOffsetToFollow(currSelection)
-
-            updateScrollState(
-                offsetToFollow = offsetToFollow,
-                containerSize = height,
-                textFieldSize = placeable.height,
-                layoutDirection = layoutDirection
-            )
-
-            // only update the previous selection if this node is focused.
-            if (isFocused) {
-                previousSelection = currSelection
-            }
-
-            placeable.placeRelative(0, -scrollState.value)
-        }
-    }
-
-    private fun MeasureScope.measureHorizontalScroll(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        // If the maxIntrinsicWidth of the children is already smaller than the constraint, pass
-        // the original constraints so that the children has more information to determine its
-        // size.
-        val maxIntrinsicWidth = measurable.maxIntrinsicWidth(constraints.maxHeight)
-
-        // remove any width constraints for TextField since it'll be able to scroll horizontally.
-        val childConstraints = if (maxIntrinsicWidth < constraints.maxWidth) {
-            constraints
-        } else {
-            constraints.copy(maxWidth = Constraints.Infinity)
-        }
-        val placeable = measurable.measure(childConstraints)
-        val width = min(placeable.width, constraints.maxWidth)
-
-        return layout(width, placeable.height) {
-            // we may need to update the scroll state to bring the cursor back into view before
-            // layout is updated.
-            val currSelection = textFieldState.visualText.selectionInChars
-            val offsetToFollow = calculateOffsetToFollow(currSelection)
-
-            updateScrollState(
-                offsetToFollow = offsetToFollow,
-                containerSize = width,
-                textFieldSize = placeable.width,
-                layoutDirection = layoutDirection
-            )
-
-            // only update the previous selection if this node is focused.
-            if (isFocused) {
-                previousSelection = currSelection
-            }
-
-            placeable.placeRelative(-scrollState.value, 0)
-        }
-    }
-
-    private fun calculateOffsetToFollow(currSelection: TextRange): Int {
-        return when {
-            currSelection.end != previousSelection?.end -> currSelection.end
-            currSelection.start != previousSelection?.start -> currSelection.start
-            else -> -1
-        }
-    }
-
-    /**
-     * Updates the scroll state to make sure cursor is visible after text content, selection, or
-     * layout changes. Only scroll changes won't trigger this.
-     *
-     * @param offsetToFollow The index of the character that needs to be followed and scrolled into
-     * view.
-     * @param containerSize Either height or width of scrollable host, depending on scroll
-     * orientation.
-     * @param textFieldSize Either height or width of scrollable text field content, depending on
-     * scroll orientation.
-     */
-    private fun Density.updateScrollState(
-        offsetToFollow: Int,
-        containerSize: Int,
-        textFieldSize: Int,
-        layoutDirection: LayoutDirection
-    ) {
-        val layoutResult = textLayoutState.layoutResult ?: return
-        val rawCursorRect = layoutResult.getCursorRect(
-            offsetToFollow.coerceIn(0..layoutResult.layoutInput.text.length)
-        )
-
-        val cursorRect = if (offsetToFollow >= 0) {
-            getCursorRectInScroller(
-                cursorRect = rawCursorRect,
-                rtl = layoutDirection == LayoutDirection.Rtl,
-                textFieldWidth = textFieldSize
-            )
-        } else {
-            null
-        }
-
-        // update the maximum scroll value
-        val difference = textFieldSize - containerSize
-        scrollState.maxValue = difference
-
-        // if the cursor is not showing, we don't have to update the scroll state for the cursor
-        // if there is no rect area to bring into view, we can early return.
-        if (!showCursor || cursorRect == null) return
-
-        // Check if cursor has actually changed its location
-        if (cursorRect.left != previousCursorRect.left ||
-            cursorRect.top != previousCursorRect.top) {
-            val vertical = orientation == Orientation.Vertical
-            val cursorStart = if (vertical) cursorRect.top else cursorRect.left
-            val cursorEnd = if (vertical) cursorRect.bottom else cursorRect.right
-
-            val startVisibleBound = scrollState.value
-            val endVisibleBound = startVisibleBound + containerSize
-            val offsetDifference = when {
-                // make bottom/end of the cursor visible
-                //
-                // text box
-                // +----------------------+
-                // |                      |
-                // |                      |
-                // |          cursor      |
-                // |             |        |
-                // +-------------|--------+
-                //               |
-                //
-                cursorEnd > endVisibleBound -> cursorEnd - endVisibleBound
-
-                // in rare cases when there's not enough space to fit the whole cursor, prioritise
-                // the bottom/end of the cursor
-                //
-                //             cursor
-                // text box      |
-                // +-------------|--------+
-                // |             |        |
-                // +-------------|--------+
-                //               |
-                //
-                cursorStart < startVisibleBound && cursorEnd - cursorStart > containerSize ->
-                    cursorEnd - endVisibleBound
-
-                // make top/start of the cursor visible if there's enough space to fit the whole
-                // cursor
-                //
-                //               cursor
-                // text box       |
-                // +--------------|-------+
-                // |              |       |
-                // |                      |
-                // |                      |
-                // |                      |
-                // +----------------------+
-                //
-                cursorStart < startVisibleBound && cursorEnd - cursorStart <= containerSize ->
-                    cursorStart - startVisibleBound
-
-                // otherwise keep current offset
-                else -> 0f
-            }
-            previousCursorRect = cursorRect
-            // this call will respect the earlier set maxValue
-            // no need to coerce again.
-            // prefer to use immediate dispatch instead of suspending scroll calls
-            coroutineScope.launch(
-                DisabledMotionDurationScale,
-                start = CoroutineStart.UNDISPATCHED
-            ) {
-                scrollState.scrollBy(offsetDifference.roundToNext())
-                // make sure to use the cursor rect from text layout since bringIntoView does its
-                // own checks for RTL layouts.
-                textLayoutState.bringIntoViewRequester.bringIntoView(rawCursorRect)
-            }
-        }
-    }
-
-    /**
-     * Draws the selection highlight.
-     */
-    private fun DrawScope.drawSelection(
-        selection: TextRange,
-        textLayoutResult: TextLayoutResult
-    ) {
-        val start = selection.min
-        val end = selection.max
-        if (start != end) {
-            val selectionBackgroundColor = currentValueOf(LocalTextSelectionColors)
-                .backgroundColor
-            val selectionPath = textLayoutResult.getPathForRange(start, end)
-            drawPath(selectionPath, color = selectionBackgroundColor)
-        }
-    }
-
-    /**
-     * Draws the text content.
-     */
-    private fun DrawScope.drawText(textLayoutResult: TextLayoutResult) {
-        drawIntoCanvas { canvas ->
-            TextPainter.paint(canvas, textLayoutResult)
-        }
-    }
-
-    /**
-     * Draws the cursor indicator. Do not confuse it with cursor handle which is a popup that
-     * carries the cursor movement gestures.
-     */
-    private fun DrawScope.drawCursor() {
-        // Only draw cursor if it can be shown and its alpha is higher than 0f
-        // Alpha is checked before showCursor purposefully to make sure that we read
-        // cursorAlpha.value in draw phase. So, when the alpha value changes, draw phase
-        // invalidates.
-        if (cursorAlpha.value <= 0f || !showCursor) return
-
-        val cursorAlphaValue = cursorAlpha.value.fastCoerceIn(0f, 1f)
-        if (cursorAlphaValue == 0f) return
-
-        val cursorRect = textFieldSelectionState.cursorRect
-
-        drawLine(
-            cursorBrush,
-            cursorRect.topCenter,
-            cursorRect.bottomCenter,
-            alpha = cursorAlphaValue,
-            strokeWidth = cursorRect.width
-        )
-    }
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        this.textLayoutState.coreNodeCoordinates = coordinates
-        textFieldMagnifierNode.onGloballyPositioned(coordinates)
-    }
-
-    override fun SemanticsPropertyReceiver.applySemantics() {
-        with(textFieldMagnifierNode) { applySemantics() }
-    }
-}
-
-private val cursorAnimationSpec: AnimationSpec<Float> = infiniteRepeatable(
-    animation = keyframes {
-        durationMillis = 1000
-        1f at 0
-        1f at 499
-        0f at 500
-        0f at 999
-    }
-)
-
-private val DefaultCursorThickness = 2.dp
-
-/**
- * If brush has a specified color. It's possible that [SolidColor] contains [Color.Unspecified].
- */
-private val Brush.isSpecified: Boolean
-    get() = !(this is SolidColor && this.value.isUnspecified)
-
-private object FixedMotionDurationScale : MotionDurationScale {
-    override val scaleFactor: Float
-        get() = 1f
-}
-
-private object DisabledMotionDurationScale : MotionDurationScale {
-    override val scaleFactor: Float
-        get() = 0f
-}
-
-/**
- * Converts cursorRect in text layout coordinates to scroller coordinates by adding the default
- * cursor thickness and calculating the relative positioning caused by the layout direction.
- *
- * @param cursorRect Reported cursor rect by the text layout.
- * @param rtl True if layout direction is RightToLeft
- * @param textFieldWidth Total width of TextField composable
- */
-private fun Density.getCursorRectInScroller(
-    cursorRect: Rect,
-    rtl: Boolean,
-    textFieldWidth: Int
-): Rect {
-    val thickness = DefaultCursorThickness.roundToPx()
-
-    val cursorLeft = if (rtl) {
-        textFieldWidth - cursorRect.right
-    } else {
-        cursorRect.left
-    }
-
-    val cursorRight = if (rtl) {
-        textFieldWidth - cursorRect.right + thickness
-    } else {
-        cursorRect.left + thickness
-    }
-    return cursorRect.copy(left = cursorLeft, right = cursorRight)
-}
-
-/**
- * Rounds a negative number to floor, and a positive number to ceil. This is essentially the
- * opposite of [truncate].
- */
-private fun Float.roundToNext(): Float = when {
-    this.isNaN() || this.isInfinite() -> this
-    this > 0 -> ceil(this)
-    else -> floor(this)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
deleted file mode 100644
index 7727ce4..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
+++ /dev/null
@@ -1,709 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
-import androidx.compose.foundation.content.internal.dragAndDropRequestPermission
-import androidx.compose.foundation.content.internal.getReceiveContentConfiguration
-import androidx.compose.foundation.content.readPlainText
-import androidx.compose.foundation.interaction.HoverInteraction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.KeyboardActionScope
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
-import androidx.compose.foundation.text2.input.internal.selection.TextToolbarState
-import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusEventModifierNode
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.focus.FocusRequesterModifierNode
-import androidx.compose.ui.focus.FocusState
-import androidx.compose.ui.focus.requestFocus
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.KeyInputModifierNode
-import androidx.compose.ui.input.pointer.PointerEvent
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutAwareModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverModifierNode
-import androidx.compose.ui.node.PointerInputModifierNode
-import androidx.compose.ui.node.SemanticsModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.node.invalidateSemantics
-import androidx.compose.ui.node.observeReads
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.platform.PlatformTextInputModifierNode
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.platform.SoftwareKeyboardController
-import androidx.compose.ui.platform.WindowInfo
-import androidx.compose.ui.platform.establishTextInputSession
-import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.semantics.copyText
-import androidx.compose.ui.semantics.cutText
-import androidx.compose.ui.semantics.disabled
-import androidx.compose.ui.semantics.editable
-import androidx.compose.ui.semantics.editableText
-import androidx.compose.ui.semantics.getTextLayoutResult
-import androidx.compose.ui.semantics.insertTextAtCursor
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.onImeAction
-import androidx.compose.ui.semantics.onLongClick
-import androidx.compose.ui.semantics.pasteText
-import androidx.compose.ui.semantics.setSelection
-import androidx.compose.ui.semantics.setText
-import androidx.compose.ui.semantics.textSelectionRange
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.ImeOptions
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.IntSize
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalFoundationApi::class)
-private val MediaTypesText = setOf(MediaType.Text)
-
-@OptIn(ExperimentalFoundationApi::class)
-private val MediaTypesAll = setOf(MediaType.All)
-
-/**
- * Modifier element for most of the functionality of [BasicTextField2] that is attached to the
- * decoration box. This is only half the actual modifiers for the field, the other half are only
- * attached to the internal text field.
- *
- * This modifier handles input events (both key and pointer), semantics, and focus.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal data class TextFieldDecoratorModifier(
-    private val textFieldState: TransformedTextFieldState,
-    private val textLayoutState: TextLayoutState,
-    private val textFieldSelectionState: TextFieldSelectionState,
-    private val filter: InputTransformation?,
-    private val enabled: Boolean,
-    private val readOnly: Boolean,
-    private val keyboardOptions: KeyboardOptions,
-    private val keyboardActions: KeyboardActions,
-    private val singleLine: Boolean,
-    private val interactionSource: MutableInteractionSource
-) : ModifierNodeElement<TextFieldDecoratorModifierNode>() {
-    override fun create(): TextFieldDecoratorModifierNode = TextFieldDecoratorModifierNode(
-        textFieldState = textFieldState,
-        textLayoutState = textLayoutState,
-        textFieldSelectionState = textFieldSelectionState,
-        filter = filter,
-        enabled = enabled,
-        readOnly = readOnly,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        singleLine = singleLine,
-        interactionSource = interactionSource,
-    )
-
-    override fun update(node: TextFieldDecoratorModifierNode) {
-        node.updateNode(
-            textFieldState = textFieldState,
-            textLayoutState = textLayoutState,
-            textFieldSelectionState = textFieldSelectionState,
-            filter = filter,
-            enabled = enabled,
-            readOnly = readOnly,
-            keyboardOptions = keyboardOptions,
-            keyboardActions = keyboardActions,
-            singleLine = singleLine,
-            interactionSource = interactionSource,
-        )
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        // Show nothing in the inspector.
-    }
-}
-
-/** Modifier node for [TextFieldDecoratorModifier]. */
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextFieldDecoratorModifierNode(
-    var textFieldState: TransformedTextFieldState,
-    var textLayoutState: TextLayoutState,
-    var textFieldSelectionState: TextFieldSelectionState,
-    var filter: InputTransformation?,
-    var enabled: Boolean,
-    var readOnly: Boolean,
-    keyboardOptions: KeyboardOptions,
-    var keyboardActions: KeyboardActions,
-    var singleLine: Boolean,
-    var interactionSource: MutableInteractionSource
-) : DelegatingNode(),
-    PlatformTextInputModifierNode,
-    SemanticsModifierNode,
-    FocusRequesterModifierNode,
-    FocusEventModifierNode,
-    GlobalPositionAwareModifierNode,
-    PointerInputModifierNode,
-    KeyInputModifierNode,
-    CompositionLocalConsumerModifierNode,
-    ModifierLocalModifierNode,
-    ObserverModifierNode,
-    LayoutAwareModifierNode {
-
-    private val editable get() = enabled && !readOnly
-
-    private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
-        with(textFieldSelectionState) {
-            textFieldGestures(
-                requestFocus = {
-                    if (!isFocused) requestFocus()
-                },
-                showKeyboard = {
-                    if (inputSessionJob != null) {
-                        // just reshow the keyboard in existing session
-                        requireKeyboardController().show()
-                    } else {
-                        startInputSession(fromTap = true)
-                    }
-                }
-            )
-        }
-    })
-
-    /**
-     * The last enter event that was submitted to [interactionSource] from [dragAndDropNode]. We
-     * need to keep a reference to this event to send a follow-up exit event.
-     *
-     * We are using interaction source hover state as a hacky capsule to carry dragging events to
-     * core modifier node which draws the cursor and shows the magnifier. TextFields are not
-     * really focused when a dragging text hovers over them. Focused TextFields should have active
-     * input connections that is not required in a drag and drop scenario.
-     *
-     * When proper hover events are implemented for [interactionSource], the below code in
-     * [dragAndDropNode] should be revised.
-     */
-    private var dragEnterEvent: HoverInteraction.Enter? = null
-
-    /**
-     * Special Drag and Drop node for BasicTextField2 that is also aware of `receiveContent` API.
-     */
-    private val dragAndDropNode = delegate(
-        textFieldDragAndDropNode(
-            hintMediaTypes = {
-                val receiveContentConfiguration = getReceiveContentConfiguration()
-                // if receiveContent configuration is set, all drag operations should be
-                // accepted. ReceiveContent handler should evaluate the incoming content.
-                if (receiveContentConfiguration != null) {
-                    MediaTypesAll
-                } else {
-                    MediaTypesText
-                }
-            },
-            dragAndDropRequestPermission = {
-                if (getReceiveContentConfiguration() != null) {
-                    dragAndDropRequestPermission(it)
-                }
-            },
-            onEntered = {
-                dragEnterEvent = HoverInteraction.Enter().also {
-                    interactionSource.tryEmit(it)
-                }
-                // Although BasicTextField2 itself is not a `receiveContent` node, it should
-                // behave like one. Delegate the enter event to the ancestor nodes just like
-                // `receiveContent` itself would.
-                getReceiveContentConfiguration()?.receiveContentListener?.onDragEnter()
-            },
-            onMoved = { position ->
-                val positionOnTextField = textLayoutState.fromWindowToDecoration(position)
-                val cursorPosition = textLayoutState.getOffsetForPosition(positionOnTextField)
-                textFieldState.selectCharsIn(TextRange(cursorPosition))
-                textFieldSelectionState.updateHandleDragging(Handle.Cursor, positionOnTextField)
-            },
-            onDrop = { clipEntry, clipMetadata ->
-                emitDragExitEvent()
-                textFieldSelectionState.clearHandleDragging()
-                var plainText = clipEntry.readPlainText()
-
-                val receiveContentConfiguration = getReceiveContentConfiguration()
-                // if receiveContent configuration is set, all drag operations should be
-                // accepted. ReceiveContent handler should evaluate the incoming content.
-                if (receiveContentConfiguration != null) {
-                    val transferableContent = TransferableContent(
-                        clipEntry,
-                        clipMetadata,
-                        TransferableContent.Source.DragAndDrop
-                    )
-
-                    val remaining = receiveContentConfiguration
-                        .receiveContentListener
-                        .onReceive(transferableContent)
-                    plainText = remaining?.clipEntry?.readPlainText()
-                }
-                plainText?.let(textFieldState::replaceSelectedText)
-                true
-            },
-            onExited = {
-                emitDragExitEvent()
-                textFieldSelectionState.clearHandleDragging()
-                // Although BasicTextField2 itself is not a `receiveContent` node, it should
-                // behave like one. Delegate the exit event to the ancestor nodes just like
-                // `receiveContent` itself would.
-                getReceiveContentConfiguration()?.receiveContentListener?.onDragExit()
-            },
-            onEnded = {
-                emitDragExitEvent()
-            })
-    )
-
-    var keyboardOptions: KeyboardOptions = keyboardOptions.withDefaultsFrom(filter?.keyboardOptions)
-        private set
-
-    /**
-     * Needs to be kept separate from a window focus so we can restart an input session when the
-     * window receives the focus back. Element can stay focused even if the window loses its focus.
-     */
-    private var isElementFocused: Boolean = false
-
-    /**
-     * Keeps focus state of the window
-     */
-    private var windowInfo: WindowInfo? = null
-
-    private val isFocused: Boolean get() = isElementFocused && windowInfo?.isWindowFocused == true
-
-    /**
-     * Manages key events. These events often are sourced by a hardware keyboard but it's also
-     * possible that IME or some other platform system simulates a KeyEvent.
-     */
-    private val textFieldKeyEventHandler = createTextFieldKeyEventHandler()
-
-    private val keyboardActionScope = object : KeyboardActionScope {
-        private val focusManager: FocusManager
-            get() = currentValueOf(LocalFocusManager)
-
-        override fun defaultKeyboardAction(imeAction: ImeAction) {
-            when (imeAction) {
-                ImeAction.Next -> {
-                    focusManager.moveFocus(FocusDirection.Next)
-                }
-                ImeAction.Previous -> {
-                    focusManager.moveFocus(FocusDirection.Previous)
-                }
-                ImeAction.Done -> {
-                    requireKeyboardController().hide()
-                }
-                ImeAction.Go, ImeAction.Search, ImeAction.Send,
-                ImeAction.Default, ImeAction.None -> Unit
-            }
-        }
-    }
-
-    private val onImeActionPerformed: (ImeAction) -> Unit = { imeAction ->
-        val keyboardAction = when (imeAction) {
-            ImeAction.Done -> keyboardActions.onDone
-            ImeAction.Go -> keyboardActions.onGo
-            ImeAction.Next -> keyboardActions.onNext
-            ImeAction.Previous -> keyboardActions.onPrevious
-            ImeAction.Search -> keyboardActions.onSearch
-            ImeAction.Send -> keyboardActions.onSend
-            ImeAction.Default, ImeAction.None -> null
-            else -> error("invalid ImeAction")
-        }
-        keyboardAction?.invoke(keyboardActionScope)
-            ?: keyboardActionScope.defaultKeyboardAction(imeAction)
-    }
-
-    /**
-     * A coroutine job that observes text and layout changes in selection state to react to those
-     * changes.
-     */
-    private var inputSessionJob: Job? = null
-
-    private val receiveContentConfigurationProvider: () -> ReceiveContentConfiguration? = {
-        getReceiveContentConfiguration()
-    }
-
-    /**
-     * Updates all the related properties and invalidates internal state based on the changes.
-     */
-    fun updateNode(
-        textFieldState: TransformedTextFieldState,
-        textLayoutState: TextLayoutState,
-        textFieldSelectionState: TextFieldSelectionState,
-        filter: InputTransformation?,
-        enabled: Boolean,
-        readOnly: Boolean,
-        keyboardOptions: KeyboardOptions,
-        keyboardActions: KeyboardActions,
-        singleLine: Boolean,
-        interactionSource: MutableInteractionSource
-    ) {
-        // Find the diff: current previous and new values before updating current.
-        val previousWriteable = this.enabled && !this.readOnly
-        val writeable = enabled && !readOnly
-
-        val previousEnabled = this.enabled
-        val previousTextFieldState = this.textFieldState
-        val previousKeyboardOptions = this.keyboardOptions
-        val previousTextFieldSelectionState = this.textFieldSelectionState
-        val previousFilter = this.filter
-
-        // Apply the diff.
-        this.textFieldState = textFieldState
-        this.textLayoutState = textLayoutState
-        this.textFieldSelectionState = textFieldSelectionState
-        this.filter = filter
-        this.enabled = enabled
-        this.readOnly = readOnly
-        this.keyboardOptions = keyboardOptions.withDefaultsFrom(filter?.keyboardOptions)
-        this.keyboardActions = keyboardActions
-        this.singleLine = singleLine
-        this.interactionSource = interactionSource
-
-        // React to diff.
-        // Something about the session changed, restart the session.
-        if (writeable != previousWriteable ||
-            textFieldState != previousTextFieldState ||
-            keyboardOptions != previousKeyboardOptions ||
-            filter != previousFilter
-        ) {
-            if (writeable && isFocused) {
-                // The old session will be implicitly disposed.
-                startInputSession(fromTap = false)
-            } else if (!writeable) {
-                // We were made read-only or disabled, hide the keyboard.
-                disposeInputSession()
-            }
-        }
-
-        if (previousEnabled != enabled) {
-            invalidateSemantics()
-        }
-
-        if (textFieldSelectionState != previousTextFieldSelectionState) {
-            pointerInputNode.resetPointerInputHandler()
-            if (isAttached) {
-                textFieldSelectionState.receiveContentConfiguration =
-                    receiveContentConfigurationProvider
-            }
-        }
-    }
-
-    override val shouldMergeDescendantSemantics: Boolean
-        get() = true
-
-    // This function is called inside a snapshot observer.
-    override fun SemanticsPropertyReceiver.applySemantics() {
-        val text = textFieldState.outputText
-        val selection = text.selectionInChars
-        editableText = AnnotatedString(text.toString())
-        textSelectionRange = selection
-
-        if (!enabled) disabled()
-        if (editable) editable()
-
-        getTextLayoutResult {
-            textLayoutState.layoutResult?.let { result -> it.add(result) } ?: false
-        }
-        setText { newText ->
-            if (!editable) return@setText false
-
-            textFieldState.replaceAll(newText)
-            true
-        }
-        @Suppress("NAME_SHADOWING")
-        setSelection { start, end, relativeToOriginal ->
-            // in traversal mode (relativeToOriginal=true) we get selection from the
-            // `textSelectionRange` semantics which is selection in original text. In non-traversal
-            // mode selection comes from the Talkback and indices are relative to the transformed
-            // text
-            val text = if (relativeToOriginal) {
-                textFieldState.untransformedText
-            } else {
-                textFieldState.visualText
-            }
-            val selection = text.selectionInChars
-
-            if (!enabled ||
-                minOf(start, end) < 0 ||
-                maxOf(start, end) > text.length
-            ) {
-                return@setSelection false
-            }
-
-            // Selection is already selected, don't need to do any work.
-            if (start == selection.start && end == selection.end) {
-                return@setSelection true
-            }
-
-            val selectionRange = TextRange(start, end)
-            // Do not show toolbar if it's a traversal mode (with the volume keys), or if the
-            // selection is collapsed.
-            if (relativeToOriginal || start == end) {
-                textFieldSelectionState.updateTextToolbarState(TextToolbarState.None)
-            } else {
-                textFieldSelectionState.updateTextToolbarState(TextToolbarState.Selection)
-            }
-            if (relativeToOriginal) {
-                textFieldState.selectUntransformedCharsIn(selectionRange)
-            } else {
-                textFieldState.selectCharsIn(selectionRange)
-            }
-            return@setSelection true
-        }
-        insertTextAtCursor { newText ->
-            if (!editable) return@insertTextAtCursor false
-
-            // Finish composing text first because when the field is focused the IME
-            // might set composition.
-            textFieldState.replaceSelectedText(newText, clearComposition = true)
-            true
-        }
-        onImeAction(keyboardOptions.imeAction) {
-            onImeActionPerformed(keyboardOptions.imeAction)
-            true
-        }
-        onClick {
-            // according to the documentation, we still need to provide proper semantics actions
-            // even if the state is 'disabled'
-            if (!isFocused) {
-                requestFocus()
-            } else if (!readOnly) {
-                requireKeyboardController().show()
-            }
-            true
-        }
-        onLongClick {
-            if (!isFocused) {
-                requestFocus()
-            }
-            textFieldSelectionState.updateTextToolbarState(TextToolbarState.Selection)
-            true
-        }
-        if (!selection.collapsed) {
-            copyText {
-                textFieldSelectionState.copy()
-                true
-            }
-            if (enabled && !readOnly) {
-                cutText {
-                    textFieldSelectionState.cut()
-                    true
-                }
-            }
-        }
-        if (editable) {
-            pasteText {
-                textFieldSelectionState.paste()
-                true
-            }
-        }
-    }
-
-    override fun onFocusEvent(focusState: FocusState) {
-        if (isElementFocused == focusState.isFocused) {
-            return
-        }
-        isElementFocused = focusState.isFocused
-        textFieldSelectionState.isFocused = this.isFocused
-
-        if (focusState.isFocused) {
-            // Deselect when losing focus even if readonly.
-            if (editable) {
-                startInputSession(fromTap = false)
-            }
-        } else {
-            disposeInputSession()
-            textFieldState.collapseSelectionToMax()
-        }
-    }
-
-    override fun onAttach() {
-        onObservedReadsChanged()
-        textFieldSelectionState.receiveContentConfiguration = receiveContentConfigurationProvider
-    }
-
-    override fun onDetach() {
-        disposeInputSession()
-        textFieldSelectionState.receiveContentConfiguration = null
-    }
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        textLayoutState.decoratorNodeCoordinates = coordinates
-    }
-
-    override fun onRemeasured(size: IntSize) {
-        if (!isFocused) return
-
-        // Ensure that the cursor is kept in view if the decoration box is resized while focused.
-        // This handles the case where a multi-line text field sitting right above the keyboard
-        // grows due to a newline entered while typing, which isn't handled by the cursor moving yet
-        // because the resize happens after the text state change, and the resize moves the cursor
-        // under the keyboard. This also covers the case where the field shrinks while focused.
-        val selection = textFieldState.visualText.selectionInChars
-        if (selection.collapsed) {
-            coroutineScope.launch {
-                textLayoutState.bringCursorIntoView(cursorIndex = selection.start)
-            }
-        }
-    }
-
-    override fun onPointerEvent(
-        pointerEvent: PointerEvent,
-        pass: PointerEventPass,
-        bounds: IntSize
-    ) {
-        pointerInputNode.onPointerEvent(pointerEvent, pass, bounds)
-    }
-
-    override fun onCancelPointerInput() {
-        pointerInputNode.onCancelPointerInput()
-    }
-
-    override fun onPreKeyEvent(event: KeyEvent): Boolean {
-        return textFieldKeyEventHandler.onPreKeyEvent(
-            event = event,
-            textFieldState = textFieldState,
-            textFieldSelectionState = textFieldSelectionState,
-            focusManager = currentValueOf(LocalFocusManager),
-            keyboardController = requireKeyboardController()
-        )
-    }
-
-    override fun onKeyEvent(event: KeyEvent): Boolean {
-        return textFieldKeyEventHandler.onKeyEvent(
-            event = event,
-            textFieldState = textFieldState,
-            textLayoutState = textLayoutState,
-            textFieldSelectionState = textFieldSelectionState,
-            editable = enabled && !readOnly,
-            singleLine = singleLine,
-            onSubmit = { onImeActionPerformed(keyboardOptions.imeAction) }
-        )
-    }
-
-    override fun onObservedReadsChanged() {
-        observeReads {
-            windowInfo = currentValueOf(LocalWindowInfo)
-            startOrDisposeInputSessionOnWindowFocusChange()
-        }
-    }
-
-    private fun startInputSession(fromTap: Boolean) {
-        if (!fromTap && !keyboardOptions.shouldShowKeyboardOnFocus) return
-
-        val receiveContentConfiguration = getReceiveContentConfiguration()
-
-        inputSessionJob = coroutineScope.launch {
-            // This will automatically cancel the previous session, if any, so we don't need to
-            // cancel the inputSessionJob ourselves.
-            establishTextInputSession {
-                // Re-start observing changes in case our TextFieldState instance changed.
-                launch(start = CoroutineStart.UNDISPATCHED) {
-                    textFieldSelectionState.observeChanges()
-                }
-
-                platformSpecificTextInputSession(
-                    state = textFieldState,
-                    layoutState = textLayoutState,
-                    imeOptions = keyboardOptions.toImeOptions(singleLine),
-                    receiveContentConfiguration = receiveContentConfiguration,
-                    onImeAction = onImeActionPerformed
-                )
-            }
-        }
-    }
-
-    private fun disposeInputSession() {
-        inputSessionJob?.cancel()
-        inputSessionJob = null
-    }
-
-    private fun startOrDisposeInputSessionOnWindowFocusChange() {
-        if (windowInfo == null) return
-        if (windowInfo?.isWindowFocused == true && isElementFocused) {
-            startInputSession(fromTap = false)
-        } else {
-            disposeInputSession()
-        }
-    }
-
-    private fun requireKeyboardController(): SoftwareKeyboardController =
-        currentValueOf(LocalSoftwareKeyboardController)
-            ?: error("No software keyboard controller")
-
-    private fun emitDragExitEvent() {
-        dragEnterEvent?.let {
-            interactionSource.tryEmit(HoverInteraction.Exit(it))
-            dragEnterEvent = null
-        }
-    }
-}
-
-/**
- * Runs platform-specific text input logic.
- */
-internal expect suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TransformedTextFieldState,
-    layoutState: TextLayoutState,
-    imeOptions: ImeOptions,
-    receiveContentConfiguration: ReceiveContentConfiguration?,
-    onImeAction: ((ImeAction) -> Unit)?
-): Nothing
-
-/**
- * Returns a [KeyboardOptions] that is merged with [defaults], with this object's values taking
- * precedence.
- */
-// TODO(b/295951492) KeyboardOptions can't actually be merged correctly in all cases, because its
-//  properties don't all have proper "unspecified" values. I think we can fix that in a
-//  backwards-compatible way, but it will require adding new API outside of the text2 package so we
-//  should hold off on making them until after the study.
-internal fun KeyboardOptions.withDefaultsFrom(defaults: KeyboardOptions?): KeyboardOptions {
-    if (defaults == null) return this
-    return KeyboardOptions(
-        capitalization = if (this.capitalization != KeyboardCapitalization.None) {
-            this.capitalization
-        } else {
-            defaults.capitalization
-        },
-        autoCorrect = this.autoCorrect && defaults.autoCorrect,
-        keyboardType = if (this.keyboardType != KeyboardType.Text) {
-            this.keyboardType
-        } else {
-            defaults.keyboardType
-        },
-        imeAction = if (this.imeAction != ImeAction.Default) {
-            this.imeAction
-        } else {
-            defaults.imeAction
-        }
-    )
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.kt
deleted file mode 100644
index 26ce2d6..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.draganddrop.DragAndDropModifierNode
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.ClipEntry
-import androidx.compose.ui.platform.ClipMetadata
-
-@OptIn(ExperimentalFoundationApi::class)
-internal expect fun textFieldDragAndDropNode(
-    hintMediaTypes: () -> Set<MediaType>,
-    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
-    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
-    onStarted: ((event: DragAndDropEvent) -> Unit)? = null,
-    onEntered: ((event: DragAndDropEvent) -> Unit)? = null,
-    onMoved: ((position: Offset) -> Unit)? = null,
-    onChanged: ((event: DragAndDropEvent) -> Unit)? = null,
-    onExited: ((event: DragAndDropEvent) -> Unit)? = null,
-    onEnded: ((event: DragAndDropEvent) -> Unit)? = null,
-): DragAndDropModifierNode
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt
deleted file mode 100644
index 1f6e494..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.DeadKeyCombiner
-import androidx.compose.foundation.text.KeyCommand
-import androidx.compose.foundation.text.appendCodePointX
-import androidx.compose.foundation.text.cancelsTextSelection
-import androidx.compose.foundation.text.isTypedEvent
-import androidx.compose.foundation.text.platformDefaultKeyMapping
-import androidx.compose.foundation.text.showCharacterPalette
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldPreparedSelection
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldPreparedSelection.Companion.NoCharacterFound
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldPreparedSelectionState
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.type
-import androidx.compose.ui.platform.SoftwareKeyboardController
-import androidx.compose.ui.text.TextRange
-
-/**
- * Factory function to create a platform specific [TextFieldKeyEventHandler].
- */
-internal expect fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler
-
-/**
- * Handles KeyEvents coming to a BasicTextField2. This is mostly to support hardware keyboard but
- * any KeyEvent can also be sent by the IME or other platform systems.
- *
- * This class is left abstract to make sure that each platform extends from it. Platforms can
- * decide to extend or completely override KeyEvent actions defined here.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal abstract class TextFieldKeyEventHandler {
-    private val preparedSelectionState = TextFieldPreparedSelectionState()
-    private val deadKeyCombiner = DeadKeyCombiner()
-    private val keyMapping = platformDefaultKeyMapping
-
-    open fun onPreKeyEvent(
-        event: KeyEvent,
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        focusManager: FocusManager,
-        keyboardController: SoftwareKeyboardController
-    ): Boolean {
-        val selection = textFieldState.visualText.selectionInChars
-        return if (!selection.collapsed && event.cancelsTextSelection()) {
-            textFieldSelectionState.deselect()
-            true
-        } else {
-            false
-        }
-    }
-
-    open fun onKeyEvent(
-        event: KeyEvent,
-        textFieldState: TransformedTextFieldState,
-        textLayoutState: TextLayoutState,
-        textFieldSelectionState: TextFieldSelectionState,
-        editable: Boolean,
-        singleLine: Boolean,
-        onSubmit: () -> Unit
-    ): Boolean {
-        if (event.type != KeyEventType.KeyDown) {
-            return false
-        }
-
-        if (event.isTypedEvent) {
-            val codePoint = deadKeyCombiner.consume(event)
-            if (codePoint != null) {
-                val text = StringBuilder(2).appendCodePointX(codePoint).toString()
-                return if (editable) {
-                    textFieldState.editUntransformedTextAsUser {
-                        commitComposition()
-                        commitText(text, 1)
-                    }
-                    preparedSelectionState.resetCachedX()
-                    true
-                } else {
-                    false
-                }
-            }
-        }
-
-        val command = keyMapping.map(event)
-        if (command == null || (command.editsText && !editable)) {
-            return false
-        }
-        var consumed = true
-        preparedSelectionContext(textFieldState, textLayoutState) {
-            when (command) {
-                KeyCommand.COPY -> textFieldSelectionState.copy(false)
-                KeyCommand.PASTE -> textFieldSelectionState.paste()
-                KeyCommand.CUT -> textFieldSelectionState.cut()
-                KeyCommand.LEFT_CHAR -> collapseLeftOr { moveCursorLeft() }
-                KeyCommand.RIGHT_CHAR -> collapseRightOr { moveCursorRight() }
-                KeyCommand.LEFT_WORD -> moveCursorLeftByWord()
-                KeyCommand.RIGHT_WORD -> moveCursorRightByWord()
-                KeyCommand.PREV_PARAGRAPH -> moveCursorPrevByParagraph()
-                KeyCommand.NEXT_PARAGRAPH -> moveCursorNextByParagraph()
-                KeyCommand.UP -> moveCursorUpByLine()
-                KeyCommand.DOWN -> moveCursorDownByLine()
-                KeyCommand.PAGE_UP -> moveCursorUpByPage()
-                KeyCommand.PAGE_DOWN -> moveCursorDownByPage()
-                KeyCommand.LINE_START -> moveCursorToLineStart()
-                KeyCommand.LINE_END -> moveCursorToLineEnd()
-                KeyCommand.LINE_LEFT -> moveCursorToLineLeftSide()
-                KeyCommand.LINE_RIGHT -> moveCursorToLineRightSide()
-                KeyCommand.HOME -> moveCursorToHome()
-                KeyCommand.END -> moveCursorToEnd()
-                KeyCommand.DELETE_PREV_CHAR -> {
-                    deleteIfSelectedOr {
-                        getPrecedingCharacterIndex().takeIf { it != NoCharacterFound }?.let {
-                            TextRange(it, selection.end)
-                        }
-                    }
-                }
-
-                KeyCommand.DELETE_NEXT_CHAR -> {
-                    // Note that some software keyboards, such as Samsung, go through this code
-                    // path instead of making calls on the InputConnection directly.
-                    deleteIfSelectedOr {
-                        getNextCharacterIndex().takeIf { it != NoCharacterFound }?.let {
-                            TextRange(selection.start, it)
-                        }
-                    }
-                }
-
-                KeyCommand.DELETE_PREV_WORD -> {
-                    deleteIfSelectedOr {
-                        TextRange(getPreviousWordOffset(), selection.end)
-                    }
-                }
-
-                KeyCommand.DELETE_NEXT_WORD -> {
-                    deleteIfSelectedOr {
-                        TextRange(selection.start, getNextWordOffset())
-                    }
-                }
-
-                KeyCommand.DELETE_FROM_LINE_START -> {
-                    deleteIfSelectedOr {
-                        TextRange(getLineStartByOffset(), selection.end)
-                    }
-                }
-
-                KeyCommand.DELETE_TO_LINE_END -> {
-                    deleteIfSelectedOr {
-                        TextRange(selection.start, getLineEndByOffset())
-                    }
-                }
-
-                KeyCommand.NEW_LINE -> {
-                    if (!singleLine) {
-                        textFieldState.editUntransformedTextAsUser {
-                            commitComposition()
-                            commitText("\n", 1)
-                        }
-                    } else {
-                        onSubmit()
-                    }
-                }
-
-                KeyCommand.TAB -> {
-                    if (!singleLine) {
-                        textFieldState.editUntransformedTextAsUser {
-                            commitComposition()
-                            commitText("\t", 1)
-                        }
-                    } else {
-                        consumed = false // let propagate to focus system
-                    }
-                }
-
-                KeyCommand.SELECT_ALL -> selectAll()
-                KeyCommand.SELECT_LEFT_CHAR -> moveCursorLeft().selectMovement()
-                KeyCommand.SELECT_RIGHT_CHAR -> moveCursorRight().selectMovement()
-                KeyCommand.SELECT_LEFT_WORD -> moveCursorLeftByWord().selectMovement()
-                KeyCommand.SELECT_RIGHT_WORD -> moveCursorRightByWord().selectMovement()
-                KeyCommand.SELECT_PREV_PARAGRAPH -> moveCursorPrevByParagraph().selectMovement()
-                KeyCommand.SELECT_NEXT_PARAGRAPH -> moveCursorNextByParagraph().selectMovement()
-                KeyCommand.SELECT_LINE_START -> moveCursorToLineStart().selectMovement()
-                KeyCommand.SELECT_LINE_END -> moveCursorToLineEnd().selectMovement()
-                KeyCommand.SELECT_LINE_LEFT -> moveCursorToLineLeftSide().selectMovement()
-                KeyCommand.SELECT_LINE_RIGHT -> moveCursorToLineRightSide().selectMovement()
-                KeyCommand.SELECT_UP -> moveCursorUpByLine().selectMovement()
-                KeyCommand.SELECT_DOWN -> moveCursorDownByLine().selectMovement()
-                KeyCommand.SELECT_PAGE_UP -> moveCursorUpByPage().selectMovement()
-                KeyCommand.SELECT_PAGE_DOWN -> moveCursorDownByPage().selectMovement()
-                KeyCommand.SELECT_HOME -> moveCursorToHome().selectMovement()
-                KeyCommand.SELECT_END -> moveCursorToEnd().selectMovement()
-                KeyCommand.DESELECT -> deselect()
-                KeyCommand.UNDO -> {
-                    textFieldState.undo()
-                }
-
-                KeyCommand.REDO -> {
-                    textFieldState.redo()
-                }
-
-                KeyCommand.CHARACTER_PALETTE -> {
-                    showCharacterPalette()
-                }
-            }
-        }
-        return consumed
-    }
-
-    private inline fun preparedSelectionContext(
-        state: TransformedTextFieldState,
-        textLayoutState: TextLayoutState,
-        block: TextFieldPreparedSelection.() -> Unit
-    ) {
-        val layoutResult = textLayoutState.layoutResult ?: return
-        val visibleTextLayoutHeight = textLayoutState.getVisibleTextLayoutHeight() ?: return
-        val preparedSelection = TextFieldPreparedSelection(
-            state = state,
-            textLayoutResult = layoutResult,
-            visibleTextLayoutHeight = visibleTextLayoutHeight,
-            textPreparedSelectionState = preparedSelectionState
-        )
-        preparedSelection.block()
-        if (preparedSelection.selection != preparedSelection.initialValue.selectionInChars) {
-            // selection changes are applied atomically at the end of context evaluation
-            state.selectCharsIn(preparedSelection.selection)
-        }
-    }
-
-    /**
-     * Returns the current viewport height of TextField to help calculate where cursor should travel
-     * when page down and up events are received.
-     */
-    private fun TextLayoutState.getVisibleTextLayoutHeight(): Float? {
-        return textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { textLayoutCoordinates ->
-            decoratorNodeCoordinates?.takeIf { it.isAttached }?.let { decoratorCoordinates ->
-                decoratorCoordinates.localBoundingBoxOf(textLayoutCoordinates)
-            }
-        }?.size?.height
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt
deleted file mode 100644
index bf7172c..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.InternalFoundationTextApi
-import androidx.compose.foundation.text.TextDelegate
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.TextFieldLayoutStateCache.MeasureInputs
-import androidx.compose.foundation.text2.input.internal.TextFieldLayoutStateCache.NonMeasureInputs
-import androidx.compose.runtime.SnapshotMutationPolicy
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.StateObject
-import androidx.compose.runtime.snapshots.StateRecord
-import androidx.compose.runtime.snapshots.withCurrent
-import androidx.compose.runtime.snapshots.writable
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextLayoutInput
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-
-/**
- * Performs text layout lazily, on-demand for text fields with snapshot-aware caching.
- *
- * You can basically think of this as a `derivedStateOf` that combines all the inputs to text layout
- * — the text itself, configuration parameters, and layout inputs — and spits out a
- * [TextLayoutResult]. The [value] property will register snapshot reads for all the inputs and
- * either return a cached result or re-compute the result and cache it in the current snapshot.
- * The cache is snapshot aware: when a new layout is computed, it will only be cached in the current
- * snapshot. When the snapshot with the new result is applied, its cache will also be visible to the
- * parent snapshot.
- *
- * All the possible inputs to text layout are grouped into two groups: those that come from the
- * layout system ([MeasureInputs]) and those that are passed explicitly to the text field composable
- * ([NonMeasureInputs]). Each of these groups can only be updated in bulk, and each group is stored
- * in an instance of a dedicated class. This means for each type of update, only one state object
- * is needed.
- */
-@OptIn(ExperimentalFoundationApi::class, InternalFoundationTextApi::class)
-internal class TextFieldLayoutStateCache : State<TextLayoutResult?>, StateObject {
-    private var nonMeasureInputs: NonMeasureInputs? by mutableStateOf(
-        value = null,
-        policy = NonMeasureInputs.mutationPolicy
-    )
-    private var measureInputs: MeasureInputs? by mutableStateOf(
-        value = null,
-        policy = MeasureInputs.mutationPolicy
-    )
-
-    /**
-     * Returns the [TextLayoutResult] for the current text field state and layout inputs, or null
-     * if the layout cannot be computed at this time.
-     *
-     * This method will re-calculate the text layout if the text or any of the other layout inputs
-     * have changed, otherwise it will return a cached value.
-     *
-     * [updateNonMeasureInputs] and [layoutWithNewMeasureInputs] must both be called before this
-     * to initialize all the inputs, or it will return null.
-     */
-    override val value: TextLayoutResult?
-        get() {
-            // If this is called from the global snapshot, there is technically a race between
-            // reading each of our input state objects. That's fine because worst case we'll just
-            // re-compute the layout on the next read anyway.
-            val nonMeasureInputs = nonMeasureInputs ?: return null
-            val measureInputs = measureInputs ?: return null
-            return getOrComputeLayout(nonMeasureInputs, measureInputs)
-        }
-
-    /**
-     * Updates the inputs that aren't from the measure phase.
-     *
-     * If any of the inputs changed, this method will invalidate any callers of [value]. If the
-     * inputs did not change it will not invalidate callers of [value].
-     *
-     * Note: This will register a snapshot read of [TextFieldState.text] if called from a snapshot
-     * observer.
-     *
-     * @see layoutWithNewMeasureInputs
-     */
-    fun updateNonMeasureInputs(
-        textFieldState: TransformedTextFieldState,
-        textStyle: TextStyle,
-        singleLine: Boolean,
-        softWrap: Boolean,
-    ) {
-        nonMeasureInputs = NonMeasureInputs(
-            textFieldState = textFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            softWrap = softWrap,
-        )
-    }
-
-    /**
-     * Updates the inputs from the measure phase and returns the most up-to-date [TextLayoutResult].
-     *
-     * If any of the inputs changed, this method will invalidate any callers of [value], re-compute
-     * the text layout, and return the new layout result. If the inputs did not change, it will
-     * return a cached value without invalidating callers of [value].
-     *
-     * @see updateNonMeasureInputs
-     */
-    fun layoutWithNewMeasureInputs(
-        density: Density,
-        layoutDirection: LayoutDirection,
-        fontFamilyResolver: FontFamily.Resolver,
-        constraints: Constraints,
-    ): TextLayoutResult {
-        val measureInputs = MeasureInputs(
-            density = density,
-            layoutDirection = layoutDirection,
-            fontFamilyResolver = fontFamilyResolver,
-            constraints = constraints,
-        )
-        this.measureInputs = measureInputs
-        val nonMeasureInputs = checkNotNull(nonMeasureInputs) {
-            "Called layoutWithNewMeasureInputs before updateNonMeasureInputs"
-        }
-        return getOrComputeLayout(nonMeasureInputs, measureInputs)
-    }
-
-    private fun getOrComputeLayout(
-        nonMeasureInputs: NonMeasureInputs,
-        measureInputs: MeasureInputs
-    ): TextLayoutResult {
-        val visualText = nonMeasureInputs.textFieldState.visualText
-
-        // Use withCurrent here so the cache itself is never reported as a read state object. It
-        // doesn't need to be, because it's always guaranteed to return the same value for the same
-        // inputs, so it's good enough to read the input states and those will invalidate the
-        // caller when they change.
-        record.withCurrent { cachedRecord ->
-            val cachedResult = cachedRecord.layoutResult
-
-            if (cachedResult != null &&
-                cachedRecord.visualText?.contentEquals(visualText) == true &&
-                cachedRecord.singleLine == nonMeasureInputs.singleLine &&
-                cachedRecord.softWrap == nonMeasureInputs.softWrap &&
-                cachedRecord.layoutDirection == measureInputs.layoutDirection &&
-                cachedRecord.densityValue == measureInputs.density.density &&
-                cachedRecord.fontScale == measureInputs.density.fontScale &&
-                cachedRecord.constraints == measureInputs.constraints &&
-                cachedRecord.fontFamilyResolver == measureInputs.fontFamilyResolver
-            ) {
-                // Fast path: None of the inputs changed.
-                if (cachedRecord.textStyle == nonMeasureInputs.textStyle) return cachedResult
-                // Slightly slower than fast path: Layout did not change but TextLayoutInput did
-                if (cachedRecord.textStyle
-                        ?.hasSameDrawAffectingAttributes(nonMeasureInputs.textStyle) == true
-                ) {
-                    return cachedResult.copy(
-                        layoutInput = TextLayoutInput(
-                            cachedResult.layoutInput.text,
-                            nonMeasureInputs.textStyle,
-                            cachedResult.layoutInput.placeholders,
-                            cachedResult.layoutInput.maxLines,
-                            cachedResult.layoutInput.softWrap,
-                            cachedResult.layoutInput.overflow,
-                            cachedResult.layoutInput.density,
-                            cachedResult.layoutInput.layoutDirection,
-                            cachedResult.layoutInput.fontFamilyResolver,
-                            cachedResult.layoutInput.constraints
-                        )
-                    )
-                }
-            }
-
-            // Slow path: Some input changed, need to re-layout.
-            return computeLayout(visualText, nonMeasureInputs, measureInputs, cachedResult)
-                .also { newResult ->
-                    // TODO(b/294403840) TextDelegate does its own caching and may return the same
-                    //  TextLayoutResult object. We should inline that so we don't check twice.
-                    if (newResult != cachedResult) {
-                        updateCacheIfWritable {
-                            this.visualText = visualText
-                            this.singleLine = nonMeasureInputs.singleLine
-                            this.softWrap = nonMeasureInputs.softWrap
-                            this.textStyle = nonMeasureInputs.textStyle
-                            this.layoutDirection = measureInputs.layoutDirection
-                            this.densityValue = measureInputs.densityValue
-                            this.fontScale = measureInputs.fontScale
-                            this.constraints = measureInputs.constraints
-                            this.fontFamilyResolver = measureInputs.fontFamilyResolver
-                            this.layoutResult = newResult
-                        }
-                    }
-                }
-        }
-    }
-
-    private inline fun updateCacheIfWritable(block: CacheRecord.() -> Unit) {
-        val snapshot = Snapshot.current
-        // We can't write to the cache when called from a read-only snapshot.
-        if (!snapshot.readOnly) {
-            record.writable(this, snapshot, block)
-        }
-    }
-
-    private fun computeLayout(
-        visualText: CharSequence,
-        nonMeasureInputs: NonMeasureInputs,
-        measureInputs: MeasureInputs,
-        prevResult: TextLayoutResult?
-    ): TextLayoutResult {
-        // TODO(b/294403840) Don't use TextDelegate – it is not designed for this use case,
-        //  optimized for re-use which we don't take advantage of here, and does its own caching
-        //  checks. Maybe we can use MultiParagraphLayoutCache like BasicText?
-
-        // We have to always create a new TextDelegate since it contains internal state that is
-        // not snapshot-aware.
-        val textDelegate = TextDelegate(
-            text = AnnotatedString(visualText.toString()),
-            style = nonMeasureInputs.textStyle,
-            density = measureInputs.density,
-            fontFamilyResolver = measureInputs.fontFamilyResolver,
-            softWrap = nonMeasureInputs.softWrap,
-            placeholders = emptyList()
-        )
-
-        return textDelegate.layout(
-            layoutDirection = measureInputs.layoutDirection,
-            constraints = measureInputs.constraints,
-            prevResult = prevResult
-        )
-    }
-
-    // region StateObject
-    private var record = CacheRecord()
-    override val firstStateRecord: StateRecord
-        get() = record
-
-    override fun prependStateRecord(value: StateRecord) {
-        this.record = value as CacheRecord
-    }
-
-    override fun mergeRecords(
-        previous: StateRecord,
-        current: StateRecord,
-        applied: StateRecord
-    ): StateRecord {
-        // This is just a cache, so it's safe to always take the most recent record – worst case
-        // we'll just re-compute the layout.
-        // However, if we needed to, we could increase the chance of a cache hit by comparing
-        // property-by-property and taking the latest version of each property.
-        return applied
-    }
-
-    /**
-     * State record that stores the cached [TextLayoutResult], as well as all the inputs used to
-     * generate that result.
-     */
-    private class CacheRecord : StateRecord() {
-        // Inputs. These are slightly different from the values in (Non)MeasuredInputs because they
-        // represent the values read from objects in the inputs that are relevant to layout, whereas
-        // the Inputs classes contain objects where we don't always care about the entire object.
-        // E.g. text layout doesn't care about TextFieldState instances, it only cares about the
-        // actual text. If the TFS instance changes but has the same text, we don't need to
-        // re-layout. Also if the TFS object _doesn't_ change but its text _does_, we do need to
-        // re-layout. That state read happens in getOrComputeLayout to invalidate correctly.
-        var visualText: CharSequence? = null
-        var textStyle: TextStyle? = null
-        var singleLine: Boolean = false
-        var softWrap: Boolean = false
-        var densityValue: Float = Float.NaN
-        var fontScale: Float = Float.NaN
-        var layoutDirection: LayoutDirection? = null
-        var fontFamilyResolver: FontFamily.Resolver? = null
-
-        /** Not nullable to avoid boxing. */
-        var constraints: Constraints = Constraints()
-
-        // Outputs.
-        var layoutResult: TextLayoutResult? = null
-
-        override fun create(): StateRecord = CacheRecord()
-
-        override fun assign(value: StateRecord) {
-            value as CacheRecord
-            visualText = value.visualText
-            textStyle = value.textStyle
-            singleLine = value.singleLine
-            softWrap = value.softWrap
-            densityValue = value.densityValue
-            fontScale = value.fontScale
-            layoutDirection = value.layoutDirection
-            fontFamilyResolver = value.fontFamilyResolver
-            constraints = value.constraints
-            layoutResult = value.layoutResult
-        }
-
-        override fun toString(): String = "CacheRecord(" +
-            "visualText=$visualText, " +
-            "textStyle=$textStyle, " +
-            "singleLine=$singleLine, " +
-            "softWrap=$softWrap, " +
-            "densityValue=$densityValue, " +
-            "fontScale=$fontScale, " +
-            "layoutDirection=$layoutDirection, " +
-            "fontFamilyResolver=$fontFamilyResolver, " +
-            "constraints=$constraints, " +
-            "layoutResult=$layoutResult" +
-            ")"
-    }
-    // endregion
-
-    // region Input holders
-    private class NonMeasureInputs(
-        val textFieldState: TransformedTextFieldState,
-        val textStyle: TextStyle,
-        val singleLine: Boolean,
-        val softWrap: Boolean,
-    ) {
-
-        override fun toString(): String = "NonMeasureInputs(" +
-            "textFieldState=$textFieldState, " +
-            "textStyle=$textStyle, " +
-            "singleLine=$singleLine, " +
-            "softWrap=$softWrap" +
-            ")"
-
-        companion object {
-            /**
-             * Implements equivalence by comparing only the parts of [NonMeasureInputs] that may
-             * require re-computing text layout. Notably, it reads the [TextFieldState.text] state
-             * property and compares only the text (not selection). This means that when the text
-             * state changes it will invalidate any snapshot observer that sets this state.
-             */
-            val mutationPolicy = object : SnapshotMutationPolicy<NonMeasureInputs?> {
-                override fun equivalent(a: NonMeasureInputs?, b: NonMeasureInputs?): Boolean =
-                    if (a != null && b != null) {
-                        // We don't need to compare text contents here because the text state is read
-                        // by getOrComputeLayout – if the text state changes, that method will already
-                        // be invalidated. The only reason to compare text here would be to avoid
-                        // invalidating if the TextFieldState is a different instance but with the same
-                        // text, but that is unlikely to happen.
-                        a.textFieldState === b.textFieldState &&
-                            a.textStyle == b.textStyle &&
-                            a.singleLine == b.singleLine &&
-                            a.softWrap == b.softWrap
-                    } else {
-                        !((a == null) xor (b == null))
-                    }
-            }
-        }
-    }
-
-    /**
-     * We store both the [Density] object, as well as its component values, because the same density
-     * object can report different actual densities over time so we need to be able to see when
-     * those values change. We still need the [Density] object to pass to [TextDelegate] though.
-     */
-    private class MeasureInputs(
-        val density: Density,
-        val layoutDirection: LayoutDirection,
-        val fontFamilyResolver: FontFamily.Resolver,
-        val constraints: Constraints,
-    ) {
-        val densityValue: Float = density.density
-        val fontScale: Float = density.fontScale
-
-        override fun toString(): String = "MeasureInputs(" +
-            "density=$density, " +
-            "densityValue=$densityValue, " +
-            "fontScale=$fontScale, " +
-            "layoutDirection=$layoutDirection, " +
-            "fontFamilyResolver=$fontFamilyResolver, " +
-            "constraints=$constraints" +
-            ")"
-
-        companion object {
-            val mutationPolicy = object : SnapshotMutationPolicy<MeasureInputs?> {
-                override fun equivalent(a: MeasureInputs?, b: MeasureInputs?): Boolean =
-                    if (a != null && b != null) {
-                        // Don't compare density – we don't care if the density instance changed,
-                        // only if the actual values used in density calculations did.
-                        a.densityValue == b.densityValue &&
-                            a.fontScale == b.fontScale &&
-                            a.layoutDirection == b.layoutDirection &&
-                            a.fontFamilyResolver == b.fontFamilyResolver &&
-                            a.constraints == b.constraints
-                    } else {
-                        !((a == null) xor (b == null))
-                    }
-            }
-        }
-    }
-    // endregion
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt
deleted file mode 100644
index 9c8052f..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.text.ceilToIntPx
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.FirstBaseline
-import androidx.compose.ui.layout.LastBaseline
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.LocalFontFamilyResolver
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastRoundToInt
-
-/**
- * This ModifierNodeElement is only responsible for laying out text and reporting its global
- * position coordinates. Text layout is kept in a separate node than the rest of the Core modifiers
- * because it will also go through scroll and minSize constraints. We need to know exact size and
- * coordinates of [TextLayoutResult] to make it relatively easier to calculate the offset between
- * exact touch coordinates and where they map on the [TextLayoutResult].
- */
-internal data class TextFieldTextLayoutModifier(
-    private val textLayoutState: TextLayoutState,
-    private val textFieldState: TransformedTextFieldState,
-    private val textStyle: TextStyle,
-    private val singleLine: Boolean,
-    private val onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
-) : ModifierNodeElement<TextFieldTextLayoutModifierNode>() {
-    override fun create(): TextFieldTextLayoutModifierNode = TextFieldTextLayoutModifierNode(
-        textLayoutState = textLayoutState,
-        textFieldState = textFieldState,
-        textStyle = textStyle,
-        singleLine = singleLine,
-        onTextLayout = onTextLayout
-    )
-
-    override fun update(node: TextFieldTextLayoutModifierNode) {
-        node.updateNode(
-            textLayoutState = textLayoutState,
-            textFieldState = textFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            onTextLayout = onTextLayout
-        )
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        // no inspector info
-    }
-}
-
-internal class TextFieldTextLayoutModifierNode(
-    private var textLayoutState: TextLayoutState,
-    textFieldState: TransformedTextFieldState,
-    textStyle: TextStyle,
-    private var singleLine: Boolean,
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
-) : Modifier.Node(),
-    LayoutModifierNode,
-    GlobalPositionAwareModifierNode,
-    CompositionLocalConsumerModifierNode {
-
-    init {
-        textLayoutState.onTextLayout = onTextLayout
-        textLayoutState.updateNonMeasureInputs(
-            textFieldState = textFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            softWrap = !singleLine
-        )
-    }
-
-    @Suppress("PrimitiveInCollection")
-    private var baselineCache: MutableMap<AlignmentLine, Int>? = null
-
-    /**
-     * Updates all the related properties and invalidates internal state based on the changes.
-     */
-    fun updateNode(
-        textLayoutState: TextLayoutState,
-        textFieldState: TransformedTextFieldState,
-        textStyle: TextStyle,
-        singleLine: Boolean,
-        onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?
-    ) {
-        this.textLayoutState = textLayoutState
-        this.textLayoutState.onTextLayout = onTextLayout
-        this.singleLine = singleLine
-        this.textLayoutState.updateNonMeasureInputs(
-            textFieldState = textFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            softWrap = !singleLine
-        )
-    }
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        this.textLayoutState.textLayoutNodeCoordinates = coordinates
-    }
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        val result = textLayoutState.layoutWithNewMeasureInputs(
-            density = this,
-            layoutDirection = layoutDirection,
-            fontFamilyResolver = currentValueOf(LocalFontFamilyResolver),
-            constraints = constraints,
-        )
-
-        val placeable = measurable.measure(
-            Constraints.fixed(result.size.width, result.size.height)
-        )
-
-        // calculate the min height for single line text to prevent text cuts.
-        // for single line text maxLines puts in max height constraint based on
-        // constant characters therefore if the user enters a character that is
-        // longer (i.e. emoji or a tall script) the text is cut
-        textLayoutState.minHeightForSingleLineField = if (singleLine) {
-            result.getLineBottom(0).ceilToIntPx().toDp()
-        } else {
-            0.dp
-        }
-
-        @Suppress("PrimitiveInCollection")
-        val cache = baselineCache ?: LinkedHashMap(2)
-        cache[FirstBaseline] = result.firstBaseline.fastRoundToInt()
-        cache[LastBaseline] = result.lastBaseline.fastRoundToInt()
-        baselineCache = cache
-
-        return layout(
-            width = result.size.width,
-            height = result.size.height,
-            alignmentLines = baselineCache!!
-        ) {
-            placeable.place(0, 0)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt
deleted file mode 100644
index cd9e23e..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.relocation.BringIntoViewRequester
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.neverEqualPolicy
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-
-/**
- * Manages text layout for TextField including layout coordinates of decoration box and inner text
- * field.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextLayoutState {
-    private var layoutCache = TextFieldLayoutStateCache()
-
-    var onTextLayout: (Density.(() -> TextLayoutResult?) -> Unit)? = null
-
-    val layoutResult: TextLayoutResult? by layoutCache
-
-    /**
-     * Measured layout coordinates of the decoration box, core text field, and text layout node.
-     *
-     * DecoratorNode
-     * -------------------
-     * |  CoreNode       |--> Outer Decoration Box with padding
-     * |  -------------  |
-     * |  |           |  |
-     * |  |           |--|--> Visible inner text field
-     * |  -------------  |    (Below the dashed line is not visible)
-     * |  |           |  |
-     * |  |           |  |
-     * -------------------
-     *    |           |
-     *    |           |---> Scrollable part (TextLayoutNode)
-     *    -------------
-     *
-     * These coordinates are used to calculate the relative positioning between multiple layers
-     * of a BasicTextField. For example, touches are processed by the decoration box but these
-     * should be converted to text layout positions to find out which character is pressed.
-     *
-     * [LayoutCoordinates] object returned from onGloballyPositioned callback is usually the same
-     * instance unless a node is detached and re-attached to the tree. To react to layout and
-     * positional changes even though the object never changes, we employ a neverEqualPolicy.
-     */
-    var textLayoutNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
-    var coreNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
-    var decoratorNodeCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
-
-    /**
-     * Set to a non-zero value for single line TextFields in order to prevent text cuts.
-     */
-    var minHeightForSingleLineField by mutableStateOf(0.dp)
-
-    /**
-     * A [BringIntoViewRequester] that can be used to request a specific region of text be brought
-     * into view (via [TextLayoutState.bringCursorIntoView]).
-     *
-     * This requester should only be applied to the core text field node, _inside_ the internal
-     * scroll container.
-     */
-    val bringIntoViewRequester = BringIntoViewRequester()
-
-    /**
-     * Updates the [TextFieldLayoutStateCache] with inputs that don't come from the measure phase.
-     * This method will initialize the cache the first time it's called.
-     * If the new inputs require re-calculating text layout, any readers of [layoutResult] called
-     * from a snapshot observer will be invalidated.
-     *
-     * @see layoutWithNewMeasureInputs
-     */
-    fun updateNonMeasureInputs(
-        textFieldState: TransformedTextFieldState,
-        textStyle: TextStyle,
-        singleLine: Boolean,
-        softWrap: Boolean,
-    ) {
-        layoutCache.updateNonMeasureInputs(
-            textFieldState = textFieldState,
-            textStyle = textStyle,
-            singleLine = singleLine,
-            softWrap = softWrap,
-        )
-    }
-
-    /**
-     * Updates the [TextFieldLayoutStateCache] with inputs that come from the measure phase and returns the
-     * latest [TextLayoutResult]. If the measure inputs haven't changed significantly since the
-     * last call, this will be the cached result. If the new inputs require re-calculating text
-     * layout, any readers of [layoutResult] called from a snapshot observer will be invalidated.
-     *
-     * [updateNonMeasureInputs] must be called before this method to initialize the cache.
-     */
-    fun layoutWithNewMeasureInputs(
-        density: Density,
-        layoutDirection: LayoutDirection,
-        fontFamilyResolver: FontFamily.Resolver,
-        constraints: Constraints,
-    ): TextLayoutResult {
-        val layoutResult = layoutCache.layoutWithNewMeasureInputs(
-            density = density,
-            layoutDirection = layoutDirection,
-            fontFamilyResolver = fontFamilyResolver,
-            constraints = constraints,
-        )
-
-        onTextLayout?.let { onTextLayout ->
-            val textLayoutProvider = { layoutCache.value }
-            onTextLayout(density, textLayoutProvider)
-        }
-
-        return layoutResult
-    }
-
-    /**
-     * Translates the position of the touch on the screen to the position in text. Because touch
-     * is relative to the decoration box, we need to translate it to the inner text field's
-     * coordinates first before calculating position of the symbol in text.
-     *
-     * @param position original position of the gesture relative to the decoration box
-     * @param coerceInVisibleBounds if true and original [position] is outside visible bounds
-     * of the inner text field, the [position] will be shifted to the closest edge of the inner
-     * text field's visible bounds. This is useful when you have a decoration box
-     * bigger than the inner text field, so when user touches to the decoration box area, the cursor
-     * goes to the beginning or the end of the visible inner text field; otherwise if we put the
-     * cursor under the touch in the invisible part of the inner text field, it would scroll to
-     * make the cursor visible. This behavior is not needed, and therefore
-     * [coerceInVisibleBounds] should be set to false, when the user drags outside visible bounds
-     * to make a selection.
-     * @return The offset that corresponds to the [position]. Returns -1 if text layout has not
-     * been measured yet.
-     */
-    fun getOffsetForPosition(position: Offset, coerceInVisibleBounds: Boolean = true): Int {
-        val layoutResult = layoutResult ?: return -1
-        val coercedPosition = if (coerceInVisibleBounds) {
-            coercedInVisibleBoundsOfInputText(position)
-        } else {
-            position
-        }
-        val relativePosition = fromDecorationToTextLayout(coercedPosition)
-        return layoutResult.getOffsetForPosition(relativePosition)
-    }
-
-    /**
-     * Returns true if the screen coordinates position (x,y) corresponds to a character displayed
-     * in the view. Returns false when the position is in the empty space of left/right of text.
-     * This function may return true even when [offset] is below or above the text layout.
-     */
-    fun isPositionOnText(offset: Offset): Boolean {
-        val layoutResult = layoutResult ?: return false
-        val relativeOffset = fromDecorationToTextLayout(coercedInVisibleBoundsOfInputText(offset))
-        val line = layoutResult.getLineForVerticalPosition(relativeOffset.y)
-        return relativeOffset.x >= layoutResult.getLineLeft(line) &&
-            relativeOffset.x <= layoutResult.getLineRight(line)
-    }
-
-    /**
-     * If click on the decoration box happens outside visible inner text field, coerce the click
-     * position to the visible edges of the inner text field.
-     */
-    internal fun coercedInVisibleBoundsOfInputText(offset: Offset): Offset {
-        // If offset is outside visible bounds of the inner text field, use visible bounds edges
-        val visibleTextLayoutNodeRect =
-            textLayoutNodeCoordinates?.let { textLayoutNodeCoordinates ->
-                if (textLayoutNodeCoordinates.isAttached) {
-                    decoratorNodeCoordinates?.localBoundingBoxOf(textLayoutNodeCoordinates)
-                } else {
-                    Rect.Zero
-                }
-            } ?: Rect.Zero
-        return offset.coerceIn(visibleTextLayoutNodeRect)
-    }
-}
-
-internal fun Offset.coerceIn(rect: Rect): Offset {
-    val xOffset = when {
-        x < rect.left -> rect.left
-        x > rect.right -> rect.right
-        else -> x
-    }
-    val yOffset = when {
-        y < rect.top -> rect.top
-        y > rect.bottom -> rect.bottom
-        else -> y
-    }
-    return Offset(xOffset, yOffset)
-}
-
-/**
- * Translates a position from text layout node coordinates to core node coordinates.
- */
-internal fun TextLayoutState.fromTextLayoutToCore(offset: Offset): Offset {
-    return textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { textLayoutNodeCoordinates ->
-        coreNodeCoordinates?.takeIf { it.isAttached }?.let { coreNodeCoordinates ->
-            coreNodeCoordinates.localPositionOf(textLayoutNodeCoordinates, offset)
-        }
-    } ?: offset
-}
-
-/**
- * Translates the click happened on the decorator node to the position in the text layout node
- * coordinates. This relative position is then used to determine symbol position in text using
- * TextLayoutResult object.
- */
-internal fun TextLayoutState.fromDecorationToTextLayout(offset: Offset): Offset {
-    return textLayoutNodeCoordinates?.let { textLayoutNodeCoordinates ->
-        decoratorNodeCoordinates?.let { decoratorNodeCoordinates ->
-            if (textLayoutNodeCoordinates.isAttached && decoratorNodeCoordinates.isAttached) {
-                textLayoutNodeCoordinates.localPositionOf(decoratorNodeCoordinates, offset)
-            } else {
-                offset
-            }
-        }
-    } ?: offset
-}
-
-internal fun TextLayoutState.fromWindowToDecoration(offset: Offset): Offset {
-    return decoratorNodeCoordinates?.let { decoratorNodeCoordinates ->
-        if (decoratorNodeCoordinates.isAttached) {
-            decoratorNodeCoordinates.windowToLocal(offset)
-        } else {
-            offset
-        }
-    } ?: offset
-}
-
-/**
- * Asks [TextLayoutState.bringIntoViewRequester] to bring the bounds of the cursor at [cursorIndex]
- * into view.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal suspend fun TextLayoutState.bringCursorIntoView(cursorIndex: Int) {
-    val layoutResult = layoutResult ?: return
-    val rect = layoutResult.getCursorRect(cursorIndex)
-    bringIntoViewRequester.bringIntoView(rect)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt
deleted file mode 100644
index e332b1b..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-/**
- * Copies characters from this [CharSequence] into [destination].
- *
- * Platform-specific implementations should use native functions for performing this operation if
- * they exist, since they will likely be more efficient than copying each character individually.
- *
- * @param destination The [CharArray] to copy into.
- * @param destinationOffset The index in [destination] to start copying to.
- * @param startIndex The index in `this` of the first character to copy from (inclusive).
- * @param endIndex The index in `this` of the last character to copy from (exclusive).
- */
-internal expect fun CharSequence.toCharArray(
-    destination: CharArray,
-    destinationOffset: Int,
-    startIndex: Int,
-    endIndex: Int
-)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt
deleted file mode 100644
index b11ae88..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt
+++ /dev/null
@@ -1,654 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.OutputTransformation
-import androidx.compose.foundation.text2.input.TextFieldBuffer
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Deletion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Insertion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Replacement
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Untransformed
-import androidx.compose.foundation.text2.input.internal.undo.TextFieldEditUndoBehavior
-import androidx.compose.foundation.text2.input.toVisualText
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.text.TextRange
-import kotlinx.coroutines.suspendCancellableCoroutine
-
-/**
- * A mutable view of a [TextFieldState] where the text and selection values are transformed by an
- * [OutputTransformation] and a [CodepointTransformation].
- *
- * [outputText] and [visualText] return the transformed text (see the explanation of phases below),
- * with selection and composition mapped to the corresponding offsets from the untransformed text.
- * The transformed text is cached in [derived states][derivedStateOf] and only recalculated when the
- * [TextFieldState] changes or some state read by the transformation functions changes.
- *
- * This class defines methods for various operations that can be performed on the underlying
- * [TextFieldState]. When possible, these methods should be used instead of editing the state
- * directly, since this class ensures the correct offset mappings are used. If an operation is too
- * complex to warrant a method here, use [editUntransformedTextAsUser] but be careful to make sure
- * any offsets are mapped correctly.
- *
- * To map offsets from transformed to untransformed text or back, use the [mapFromTransformed] and
- * [mapToTransformed] methods.
- *
- * All operations call [TextFieldState.editAsUser] internally and pass [inputTransformation].
- *
- * ## Text transformation phases
- *
- * Text is transformed in two phases:
- * 1. The first phase applies [outputTransformation], and the resulting text, [outputText], is
- *   published to the semantics system (consumed by a11y services and tests).
- * 2. The second phase applies [codepointTransformation] and the resulting text, [visualText], is
- *  laid out and drawn to the screen.
- *
- * Any transformations that change the semantics (in the generic sense, not Compose semantics)
- * should be done in the first phase. Examples include adding prefixes or suffixes to the text or
- * inserting formatting characters.
- *
- * The second phase should only be used for purely visual or layout transformations. Examples
- * include password masking or inserting spaces for Scribe.
- *
- * In most cases, one or both phases will be noops. E.g., password fields will usually only use
- * the second phase, and non-password fields will usually only use the first phase.
- *
- * Here's a diagram explaining the phases:
- *
- * ```
- *  ┌──────────────────┐
- *  │                  │
- *  │  TextFieldState  │        "Sam"      - Semantics setSelection: relativeToOriginalText=true
- *  │                  │
- *  └──────────────────┘
- *            │
- *  OutputTransformation    s/^/Hello, /
- *            │
- *            ▼
- *  ┌──────────────────┐
- *  │                  │                   - Talkback
- *  │   Output text    │    "Hello, Sam"   - Tests
- *  │                  │                   - Semantics setSelection: relativeToOriginalText=false
- *  └──────────────────┘
- *            │
- * CodepointTransformation     s/./•/g
- *            │
- *            ▼
- *  ┌──────────────────┐
- *  │                  │                   - Measured
- *  │   Visual text    │    "••••••••••"   - Wrapping, ellipsis, etc.
- *  │                  │                   - Drawn on screen
- *  └──────────────────┘
- * ```
- */
-@OptIn(ExperimentalFoundationApi::class)
-@Stable
-internal class TransformedTextFieldState(
-    private val textFieldState: TextFieldState,
-    private val inputTransformation: InputTransformation? = null,
-    private val codepointTransformation: CodepointTransformation? = null,
-    private val outputTransformation: OutputTransformation? = null,
-) {
-    private val outputTransformedText: State<TransformedText?>? =
-        // Don't allocate a derived state object if we don't need it, they're expensive.
-        outputTransformation?.let { transformation ->
-            derivedStateOf {
-                // text is a state read. transformation may also perform state reads when ran.
-                calculateTransformedText(
-                    untransformedText = textFieldState.text,
-                    outputTransformation = transformation,
-                    wedgeAffinity = selectionWedgeAffinity
-                )
-            }
-        }
-
-    private val codepointTransformedText: State<TransformedText?>? =
-        codepointTransformation?.let { transformation ->
-            derivedStateOf {
-                calculateTransformedText(
-                    // These are state reads. codepointTransformation may also perform state reads
-                    // when ran.
-                    untransformedText = outputTransformedText?.value?.text ?: textFieldState.text,
-                    codepointTransformation = transformation,
-                    wedgeAffinity = selectionWedgeAffinity
-                )
-            }
-        }
-
-    /**
-     * The raw text in the underlying [TextFieldState]. This text does not have any
-     * [CodepointTransformation] applied.
-     */
-    val untransformedText: TextFieldCharSequence
-        get() = textFieldState.text
-
-    /**
-     * The text that should be presented to the user in most cases. If an [OutputTransformation] is
-     * specified, this text has the transformation applied. If there's no transformation, this will
-     * be the same as [untransformedText].
-     *
-     * See the diagram on [TransformedTextFieldState] for a graphical representation of how this
-     * value relates to [untransformedText] and [visualText].
-     */
-    val outputText: TextFieldCharSequence
-        get() = outputTransformedText?.value?.text ?: untransformedText
-
-    /**
-     * The text that should be laid out and drawn to the screen. If a [CodepointTransformation] is
-     * specified, this text has the transformation applied. If there's no transformation, this will
-     * be the same as [outputText].
-     *
-     * See the diagram on [TransformedTextFieldState] for a graphical representation of how this
-     * value relates to [untransformedText] and [outputText].
-     */
-    val visualText: TextFieldCharSequence
-        get() = codepointTransformedText?.value?.text ?: outputText
-
-    /**
-     * Indicates which side of a wedge (text inserted by the [OutputTransformation]) the start and
-     * end of the selection should map to. This allows the user to move the cursor to both sides of
-     * the wedge even though both those indices map to the same index in the untransformed text.
-     */
-    var selectionWedgeAffinity by mutableStateOf(SelectionWedgeAffinity(WedgeAffinity.Start))
-
-    fun placeCursorBeforeCharAt(transformedOffset: Int) {
-        selectCharsIn(TextRange(transformedOffset))
-    }
-
-    fun selectCharsIn(transformedRange: TextRange) {
-        val untransformedRange = mapFromTransformed(transformedRange)
-        selectUntransformedCharsIn(untransformedRange)
-    }
-
-    fun selectUntransformedCharsIn(untransformedRange: TextRange) {
-        textFieldState.editAsUser(inputTransformation) {
-            setSelection(untransformedRange.start, untransformedRange.end)
-        }
-    }
-
-    fun replaceAll(newText: CharSequence) {
-        textFieldState.editAsUser(inputTransformation) {
-            deleteAll()
-            commitText(newText.toString(), 1)
-        }
-    }
-
-    fun selectAll() {
-        textFieldState.editAsUser(inputTransformation) {
-            setSelection(0, length)
-        }
-    }
-
-    fun deleteSelectedText() {
-        textFieldState.editAsUser(
-            inputTransformation,
-            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
-        ) {
-            // `selection` is read from the buffer, so we don't need to transform it.
-            delete(selection.min, selection.max)
-            setSelection(selection.min, selection.min)
-        }
-    }
-
-    /**
-     * Replaces the text in given [range] with [newText]. Like all other methods in this class,
-     * [range] is considered to be in transformed space.
-     */
-    fun replaceText(
-        newText: CharSequence,
-        range: TextRange,
-        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible
-    ) {
-        textFieldState.editAsUser(inputTransformation, undoBehavior = undoBehavior) {
-            val selection = mapFromTransformed(range)
-            replace(
-                selection.min,
-                selection.max,
-                newText
-            )
-            val cursor = selection.min + newText.length
-            setSelection(cursor, cursor)
-        }
-    }
-
-    fun replaceSelectedText(
-        newText: CharSequence,
-        clearComposition: Boolean = false,
-        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible
-    ) {
-        textFieldState.editAsUser(inputTransformation, undoBehavior = undoBehavior) {
-            if (clearComposition) {
-                commitComposition()
-            }
-
-            // `selection` is read from the buffer, so we don't need to transform it.
-            val selection = selection
-            replace(
-                selection.min,
-                selection.max,
-                newText
-            )
-            val cursor = selection.min + newText.length
-            setSelection(cursor, cursor)
-        }
-    }
-
-    fun collapseSelectionToMax() {
-        textFieldState.editAsUser(inputTransformation) {
-            // `selection` is read from the buffer, so we don't need to transform it.
-            setSelection(selection.max, selection.max)
-        }
-    }
-
-    fun collapseSelectionToEnd() {
-        textFieldState.editAsUser(inputTransformation) {
-            // `selection` is read from the buffer, so we don't need to transform it.
-            setSelection(selection.end, selection.end)
-        }
-    }
-
-    fun undo() {
-        textFieldState.undoState.undo()
-    }
-
-    fun redo() {
-        textFieldState.undoState.redo()
-    }
-
-    /**
-     * Runs [block] with a buffer that contains the source untransformed text. This is the text that
-     * will be fed into the [outputTransformation]. Any operations performed on this buffer MUST
-     * take care to explicitly convert between transformed and untransformed offsets and ranges.
-     * When possible, use the other methods on this class to manipulate selection to avoid having
-     * to do these conversions manually.
-     *
-     * @see mapToTransformed
-     * @see mapFromTransformed
-     */
-    inline fun editUntransformedTextAsUser(
-        notifyImeOfChanges: Boolean = true,
-        block: EditingBuffer.() -> Unit
-    ) {
-        textFieldState.editAsUser(
-            inputTransformation = inputTransformation,
-            notifyImeOfChanges = notifyImeOfChanges,
-            block = block
-        )
-    }
-
-    /**
-     * Maps an [offset] in the untransformed text to the corresponding offset or range in [visualText].
-     *
-     * An untransformed offset will map to non-collapsed range if the offset is in the middle of
-     * a surrogate pair in the untransformed text, in which case it will return the range of the
-     * codepoint that the surrogate maps to. Offsets on either side of a surrogate pair will return
-     * collapsed ranges.
-     *
-     * If there is no transformation, or the transformation does not change the text, a collapsed
-     * range of [offset] will be returned.
-     *
-     * @see mapFromTransformed
-     */
-    fun mapToTransformed(offset: Int): TextRange {
-        val presentMapping = outputTransformedText?.value?.offsetMapping
-        val visualMapping = codepointTransformedText?.value?.offsetMapping
-
-        val intermediateRange = presentMapping?.mapFromSource(offset)
-            ?: TextRange(offset)
-        return visualMapping
-            ?.let { mapToTransformed(intermediateRange, it, selectionWedgeAffinity) }
-            ?: intermediateRange
-    }
-
-    /**
-     * Maps a [range] in the untransformed text to the corresponding range in [visualText].
-     *
-     * If there is no transformation, or the transformation does not change the text, [range]
-     * will be returned.
-     *
-     * @see mapFromTransformed
-     */
-    fun mapToTransformed(range: TextRange): TextRange {
-        val presentMapping = outputTransformedText?.value?.offsetMapping
-        val visualMapping = codepointTransformedText?.value?.offsetMapping
-
-        // Only apply the wedge affinity to the final range. If the first mapping returns a range,
-        // the first range should have both edges expanded by the second.
-        val intermediateRange = presentMapping
-            ?.let { mapToTransformed(range, it) }
-            ?: range
-        return visualMapping
-            ?.let { mapToTransformed(intermediateRange, it, selectionWedgeAffinity) }
-            ?: intermediateRange
-    }
-
-    /**
-     * Maps an [offset] in [visualText] to the corresponding offset in the untransformed text.
-     *
-     * Multiple transformed offsets may map to the same untransformed offset. In particular, any
-     * offset in the middle of a surrogate pair will map to offset of the corresponding codepoint
-     * in the untransformed text.
-     *
-     * If there is no transformation, or the transformation does not change the text, [offset]
-     * will be returned.
-     *
-     * @see mapToTransformed
-     */
-    fun mapFromTransformed(offset: Int): TextRange {
-        val presentMapping = outputTransformedText?.value?.offsetMapping
-        val visualMapping = codepointTransformedText?.value?.offsetMapping
-
-        val intermediateOffset = visualMapping?.mapFromDest(offset)
-            ?: TextRange(offset)
-        return presentMapping?.let { mapFromTransformed(intermediateOffset, it) }
-            ?: intermediateOffset
-    }
-
-    /**
-     * Maps a [range] in [visualText] to the corresponding range in the untransformed text.
-     *
-     * If there is no transformation, or the transformation does not change the text, [range]
-     * will be returned.
-     *
-     * @see mapToTransformed
-     */
-    fun mapFromTransformed(range: TextRange): TextRange {
-        val presentMapping = outputTransformedText?.value?.offsetMapping
-        val visualMapping = codepointTransformedText?.value?.offsetMapping
-
-        val intermediateRange = visualMapping?.let { mapFromTransformed(range, it) }
-            ?: range
-        return presentMapping?.let { mapFromTransformed(intermediateRange, it) }
-            ?: intermediateRange
-    }
-
-    // TODO(b/296583846) Get rid of this.
-    /**
-     * Adds [notifyImeListener] to the underlying [TextFieldState] and then suspends until
-     * cancelled, removing the listener before continuing.
-     */
-    suspend fun collectImeNotifications(
-        notifyImeListener: TextFieldState.NotifyImeListener
-    ): Nothing {
-        suspendCancellableCoroutine<Nothing> { continuation ->
-            textFieldState.addNotifyImeListener(notifyImeListener)
-            continuation.invokeOnCancellation {
-                textFieldState.removeNotifyImeListener(notifyImeListener)
-            }
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is TransformedTextFieldState) return false
-        if (textFieldState != other.textFieldState) return false
-        if (codepointTransformation != other.codepointTransformation) return false
-        return outputTransformation == other.outputTransformation
-    }
-
-    override fun hashCode(): Int {
-        var result = textFieldState.hashCode()
-        result = 31 * result + (codepointTransformation?.hashCode() ?: 0)
-        result = 31 * result + (outputTransformation?.hashCode() ?: 0)
-        return result
-    }
-
-    override fun toString(): String = "TransformedTextFieldState(" +
-        "textFieldState=$textFieldState, " +
-        "outputTransformation=$outputTransformation, " +
-        "outputTransformedText=$outputTransformedText, " +
-        "codepointTransformation=$codepointTransformation, " +
-        "codepointTransformedText=$codepointTransformedText, " +
-        "outputText=\"$outputText\", " +
-        "visualText=\"$visualText\"" +
-        ")"
-
-    private data class TransformedText(
-        val text: TextFieldCharSequence,
-        val offsetMapping: OffsetMappingCalculator,
-    )
-
-    private companion object {
-
-        /**
-         * Applies an [OutputTransformation] to a [TextFieldCharSequence], returning the
-         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
-         * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
-         * offsets in both directions between the transformed and untransformed text.
-         *
-         * This function is relatively expensive, since it creates a copy of [untransformedText], so
-         * its result should be cached.
-         */
-        @kotlin.jvm.JvmStatic
-        private fun calculateTransformedText(
-            untransformedText: TextFieldCharSequence,
-            outputTransformation: OutputTransformation,
-            wedgeAffinity: SelectionWedgeAffinity
-        ): TransformedText? {
-            val offsetMappingCalculator = OffsetMappingCalculator()
-            val buffer = TextFieldBuffer(
-                initialValue = untransformedText,
-                offsetMappingCalculator = offsetMappingCalculator
-            )
-
-            // This is the call to external code.
-            with(outputTransformation) { buffer.transformOutput() }
-
-            // Avoid allocations + mapping if there weren't actually any transformations.
-            if (buffer.changes.changeCount == 0) {
-                return null
-            }
-
-            val transformedTextWithSelection = buffer.toTextFieldCharSequence(
-                // Pass the calculator explicitly since the one on transformedText won't be updated
-                // yet.
-                selection = mapToTransformed(
-                    range = untransformedText.selectionInChars,
-                    mapping = offsetMappingCalculator,
-                    wedgeAffinity = wedgeAffinity
-                ),
-                composition = untransformedText.compositionInChars?.let {
-                    mapToTransformed(
-                        range = it,
-                        mapping = offsetMappingCalculator,
-                        wedgeAffinity = wedgeAffinity
-                    )
-                }
-            )
-            return TransformedText(transformedTextWithSelection, offsetMappingCalculator)
-        }
-
-        /**
-         * Applies a [CodepointTransformation] to a [TextFieldCharSequence], returning the
-         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
-         * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
-         * offsets in both directions between the transformed and untransformed text.
-         *
-         * This function is relatively expensive, since it creates a copy of [untransformedText], so
-         * its result should be cached.
-         */
-        @kotlin.jvm.JvmStatic
-        private fun calculateTransformedText(
-            untransformedText: TextFieldCharSequence,
-            codepointTransformation: CodepointTransformation,
-            wedgeAffinity: SelectionWedgeAffinity
-        ): TransformedText? {
-            val offsetMappingCalculator = OffsetMappingCalculator()
-
-            // This is the call to external code. Returns same instance if no codepoints change.
-            val transformedText =
-                untransformedText.toVisualText(codepointTransformation, offsetMappingCalculator)
-
-            // Avoid allocations + mapping if there weren't actually any transformations.
-            if (transformedText === untransformedText) {
-                return null
-            }
-
-            val transformedTextWithSelection = TextFieldCharSequence(
-                text = transformedText,
-                // Pass the calculator explicitly since the one on transformedText won't be updated
-                // yet.
-                selection = mapToTransformed(
-                    untransformedText.selectionInChars,
-                    offsetMappingCalculator,
-                    wedgeAffinity
-                ),
-                composition = untransformedText.compositionInChars?.let {
-                    mapToTransformed(it, offsetMappingCalculator, wedgeAffinity)
-                }
-            )
-            return TransformedText(transformedTextWithSelection, offsetMappingCalculator)
-        }
-
-        /**
-         * Maps [range] from untransformed to transformed indices.
-         *
-         * @param wedgeAffinity The [SelectionWedgeAffinity] to use to collapse the transformed
-         * range if necessary. If null, the range will be returned uncollapsed.
-         */
-        @kotlin.jvm.JvmStatic
-        private fun mapToTransformed(
-            range: TextRange,
-            mapping: OffsetMappingCalculator,
-            wedgeAffinity: SelectionWedgeAffinity? = null
-        ): TextRange {
-            val transformedStart = mapping.mapFromSource(range.start)
-            // Avoid calculating mapping again if it's going to be the same value.
-            val transformedEnd = if (range.collapsed) transformedStart else {
-                mapping.mapFromSource(range.end)
-            }
-
-            val transformedMin = minOf(transformedStart.min, transformedEnd.min)
-            val transformedMax = maxOf(transformedStart.max, transformedEnd.max)
-            val transformedRange = if (range.reversed) {
-                TextRange(transformedMax, transformedMin)
-            } else {
-                TextRange(transformedMin, transformedMax)
-            }
-
-            return if (range.collapsed && !transformedRange.collapsed) {
-                // In a wedge.
-                when (wedgeAffinity?.startAffinity) {
-                    WedgeAffinity.Start -> TextRange(transformedRange.start)
-                    WedgeAffinity.End -> TextRange(transformedRange.end)
-                    null -> transformedRange
-                }
-            } else {
-                transformedRange
-            }
-        }
-
-        @kotlin.jvm.JvmStatic
-        private fun mapFromTransformed(
-            range: TextRange,
-            mapping: OffsetMappingCalculator
-        ): TextRange {
-            val untransformedStart = mapping.mapFromDest(range.start)
-            // Avoid calculating mapping again if it's going to be the same value.
-            val untransformedEnd = if (range.collapsed) untransformedStart else {
-                mapping.mapFromDest(range.end)
-            }
-
-            val untransformedMin = minOf(untransformedStart.min, untransformedEnd.min)
-            val untransformedMax = maxOf(untransformedStart.max, untransformedEnd.max)
-            return if (range.reversed) {
-                TextRange(untransformedMax, untransformedMin)
-            } else {
-                TextRange(untransformedMin, untransformedMax)
-            }
-        }
-    }
-}
-
-/**
- * Represents the [WedgeAffinity] for both sides of a selection.
- */
-internal data class SelectionWedgeAffinity(
-    val startAffinity: WedgeAffinity,
-    val endAffinity: WedgeAffinity,
-) {
-    constructor(affinity: WedgeAffinity) : this(affinity, affinity)
-}
-
-/**
- * Determines which side of a wedge a selection marker should be considered to be on when the marker
- * is in a wedge. A "wedge" is a range of text that the cursor is not allowed inside. A wedge is
- * created when an [OutputTransformation] either inserts or replaces a non-empty string.
- */
-internal enum class WedgeAffinity {
-    Start, End
-}
-
-internal enum class IndexTransformationType {
-    Untransformed,
-    Insertion,
-    Replacement,
-    Deletion
-}
-
-/**
- * Determines if the [transformedQueryIndex] is inside an insertion, replacement, deletion, or none
- * of the above as specified by the transformations on this [TransformedTextFieldState].
- *
- * This function uses continuation-passing style to return multiple values without allocating.
- *
- * @param onResult Called with the determined [IndexTransformationType] and the ranges that
- * [transformedQueryIndex] maps to both in the [TransformedTextFieldState.untransformedText] and
- * when that range is mapped back into the [TransformedTextFieldState.visualText].
- */
-internal inline fun <R> TransformedTextFieldState.getIndexTransformationType(
-    transformedQueryIndex: Int,
-    onResult: (
-        IndexTransformationType,
-        untransformed: TextRange,
-        retransformed: TextRange
-    ) -> R
-): R {
-    val untransformed = mapFromTransformed(transformedQueryIndex)
-    val retransformed = mapToTransformed(untransformed)
-    val type = when {
-        untransformed.collapsed && retransformed.collapsed -> {
-            // Simple case: no transformation in effect.
-            Untransformed
-        }
-
-        !untransformed.collapsed && !retransformed.collapsed -> {
-            // Replacement: An non-empty range in the source was replaced with a non-empty string.
-            Replacement
-        }
-
-        untransformed.collapsed && !retransformed.collapsed -> {
-            // Insertion: An empty range in the source was replaced with a non-empty range.
-            Insertion
-        }
-
-        else /* !untransformed.collapsed && retransformed.collapsed */ -> {
-            // Deletion: A non-empty range in the source was replaced with an empty string.
-            Deletion
-        }
-    }
-    return onResult(type, untransformed, retransformed)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownGesture.kt
deleted file mode 100644
index 91d2f2e..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/PressDownGesture.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.util.fastAny
-
-/**
- * Detects pointer down and up events. This detector does not require events to be unconsumed.
- */
-internal suspend fun PointerInputScope.detectPressDownGesture(
-    onDown: TapOnPosition,
-    onUp: (() -> Unit)? = null
-) {
-    awaitEachGesture {
-        val down = awaitFirstDown(requireUnconsumed = false)
-        onDown.onEvent(down.position)
-
-        if (onUp != null) {
-            // Wait for that pointer to come up.
-            do {
-                val event = awaitPointerEvent()
-            } while (event.changes.fastAny { it.id == down.id && it.pressed })
-            onUp.invoke()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapGesture.kt
deleted file mode 100644
index 0cbe8e5..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TapAndDoubleTapGesture.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.gestures.waitForUpOrCancellation
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.AwaitPointerEventScope
-import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
-import androidx.compose.ui.input.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.platform.ViewConfiguration
-import androidx.compose.ui.util.fastAny
-import androidx.compose.ui.util.fastForEach
-import kotlinx.coroutines.coroutineScope
-
-/**
- * Detect tap and double tap gestures. This is a special gesture detector that's copied from
- * [PointerInputScope.detectTapGestures] to always call [onTap] even when a double tap is detected.
- * In that case both [onTap] and [onDoubleTap] are called successively. However, if there is a long
- * tap, neither of the callbacks are called.
- */
-internal suspend fun PointerInputScope.detectTapAndDoubleTap(
-    onTap: TapOnPosition? = null,
-    onDoubleTap: TapOnPosition? = null,
-) = coroutineScope {
-    awaitEachGesture {
-        val down = awaitFirstDown()
-        down.consume()
-        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
-        var upOrCancel: PointerInputChange? = null
-        try {
-            // wait for first tap up or long press
-            upOrCancel = withTimeout(longPressTimeout) {
-                waitForUpOrCancellation()
-            }
-        } catch (_: PointerEventTimeoutCancellationException) {
-            consumeUntilUp()
-        }
-
-        if (upOrCancel != null) {
-            upOrCancel.consume()
-            // Tap was successful. There was no long click. Now evaluate whether it's gonna be
-            // a double tap.
-            onTap?.onEvent(upOrCancel.position)
-            if (onDoubleTap != null) {
-                // check for second tap
-                val secondDown = awaitSecondDown(upOrCancel)
-
-                if (secondDown != null) {
-                    try {
-                        // Might have a long second press as the second tap
-                        withTimeout(longPressTimeout) {
-                            val secondUp = waitForUpOrCancellation()
-                            if (secondUp != null) {
-                                secondUp.consume()
-                                onDoubleTap.onEvent(secondUp.position)
-                            }
-                        }
-                    } catch (e: PointerEventTimeoutCancellationException) {
-                        consumeUntilUp()
-                    }
-                }
-            }
-        }
-    }
-}
-
-/**
- * Waits for [ViewConfiguration.doubleTapTimeoutMillis] for a second press event. If a
- * second press event is received before the time out, it is returned or `null` is returned
- * if no second press is received.
- */
-private suspend fun AwaitPointerEventScope.awaitSecondDown(
-    firstUp: PointerInputChange
-): PointerInputChange? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) {
-    val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
-    var change: PointerInputChange
-    // The second tap doesn't count if it happens before DoubleTapMinTime of the first tap
-    do {
-        change = awaitFirstDown()
-    } while (change.uptimeMillis < minUptime)
-    change
-}
-
-/**
- * Consumes all pointer events until nothing is pressed and then returns. This method assumes
- * that something is currently pressed.
- */
-private suspend fun AwaitPointerEventScope.consumeUntilUp() {
-    do {
-        val event = awaitPointerEvent()
-        event.changes.fastForEach { it.consume() }
-    } while (event.changes.fastAny { it.pressed })
-}
-
-internal fun interface TapOnPosition {
-    fun onEvent(offset: Offset)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldHandleState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldHandleState.kt
deleted file mode 100644
index e51ee00..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldHandleState.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.text.style.ResolvedTextDirection
-
-/**
- * Defines how to render a selection or cursor handle on a TextField.
- */
-internal data class TextFieldHandleState(
-    val visible: Boolean,
-    val position: Offset,
-    val direction: ResolvedTextDirection,
-    val handlesCrossed: Boolean
-) {
-    companion object {
-        val Hidden = TextFieldHandleState(
-            visible = false,
-            position = Offset.Unspecified,
-            direction = ResolvedTextDirection.Ltr,
-            handlesCrossed = false
-        )
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt
deleted file mode 100644
index 7112a8b..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.selection.visibleBounds
-import androidx.compose.foundation.text2.input.internal.TextLayoutState
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.foundation.text2.input.internal.coerceIn
-import androidx.compose.foundation.text2.input.internal.fromTextLayoutToCore
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.isUnspecified
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.OnGloballyPositionedModifier
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.SemanticsModifierNode
-import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.unit.IntSize
-import kotlin.math.absoluteValue
-
-internal abstract class TextFieldMagnifierNode : DelegatingNode(),
-    OnGloballyPositionedModifier,
-    DrawModifierNode,
-    SemanticsModifierNode {
-
-    abstract fun update(
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        textLayoutState: TextLayoutState,
-        visible: Boolean
-    )
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {}
-
-    override fun ContentDrawScope.draw() {}
-
-    override fun SemanticsPropertyReceiver.applySemantics() {}
-}
-
-@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-internal expect fun textFieldMagnifierNode(
-    textFieldState: TransformedTextFieldState,
-    textFieldSelectionState: TextFieldSelectionState,
-    textLayoutState: TextLayoutState,
-    visible: Boolean
-): TextFieldMagnifierNode
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun calculateSelectionMagnifierCenterAndroid(
-    textFieldState: TransformedTextFieldState,
-    selectionState: TextFieldSelectionState,
-    textLayoutState: TextLayoutState,
-    magnifierSize: IntSize
-): Offset {
-    // state read of currentDragPosition so that we always recompose on drag position changes
-    val localDragPosition = selectionState.handleDragPosition
-
-    // Do not show the magnifier if origin position is already Unspecified.
-    // Never show the magnifier in an empty text field.
-    if (localDragPosition.isUnspecified || textFieldState.visualText.isEmpty()) {
-        return Offset.Unspecified
-    }
-
-    val selection = textFieldState.visualText.selectionInChars
-    val textOffset = when (selectionState.draggingHandle) {
-        null -> return Offset.Unspecified
-        Handle.Cursor,
-        Handle.SelectionStart -> selection.start
-        Handle.SelectionEnd -> selection.end
-    }
-
-    // If the text hasn't been laid out yet, don't show the modifier.
-    val layoutResult = textLayoutState.layoutResult ?: return Offset.Unspecified
-
-    val dragX = localDragPosition.x
-    val line = layoutResult.getLineForOffset(textOffset)
-    val lineStart = layoutResult.getLineLeft(line)
-    val lineEnd = layoutResult.getLineRight(line)
-    val lineMin = minOf(lineStart, lineEnd)
-    val lineMax = maxOf(lineStart, lineEnd)
-    val centerX = dragX.coerceIn(lineMin, lineMax)
-
-    // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
-    // magnifier actually is). See
-    // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
-    if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
-        return Offset.Unspecified
-    }
-
-    // Center vertically on the current line.
-    val top = layoutResult.getLineTop(line)
-    val bottom = layoutResult.getLineBottom(line)
-    val centerY = ((bottom - top) / 2) + top
-
-    var offset = Offset(centerX, centerY)
-    textLayoutState.textLayoutNodeCoordinates?.takeIf { it.isAttached }?.let { innerCoordinates ->
-        offset = offset.coerceIn(innerCoordinates.visibleBounds())
-    }
-    return textLayoutState.fromTextLayoutToCore(offset)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt
deleted file mode 100644
index aff84e6..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt
+++ /dev/null
@@ -1,1361 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
-import androidx.compose.foundation.content.readPlainText
-import androidx.compose.foundation.gestures.detectDragGestures
-import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.text.DefaultCursorThickness
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.selection.SelectionAdjustment
-import androidx.compose.foundation.text.selection.SelectionLayout
-import androidx.compose.foundation.text.selection.containsInclusive
-import androidx.compose.foundation.text.selection.getAdjustedCoordinates
-import androidx.compose.foundation.text.selection.getSelectionHandleCoordinates
-import androidx.compose.foundation.text.selection.getTextFieldSelectionLayout
-import androidx.compose.foundation.text.selection.isPrecisePointer
-import androidx.compose.foundation.text.selection.visibleBounds
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.getSelectedText
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Deletion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Insertion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Replacement
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Untransformed
-import androidx.compose.foundation.text2.input.internal.SelectionWedgeAffinity
-import androidx.compose.foundation.text2.input.internal.TextFieldDecoratorModifierNode
-import androidx.compose.foundation.text2.input.internal.TextLayoutState
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.foundation.text2.input.internal.WedgeAffinity
-import androidx.compose.foundation.text2.input.internal.coerceIn
-import androidx.compose.foundation.text2.input.internal.findClosestRect
-import androidx.compose.foundation.text2.input.internal.fromDecorationToTextLayout
-import androidx.compose.foundation.text2.input.internal.getIndexTransformationType
-import androidx.compose.foundation.text2.input.internal.undo.TextFieldEditUndoBehavior
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.structuralEqualityPolicy
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.isSpecified
-import androidx.compose.ui.geometry.isUnspecified
-import androidx.compose.ui.hapticfeedback.HapticFeedback
-import androidx.compose.ui.hapticfeedback.HapticFeedbackType
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.platform.ClipboardManager
-import androidx.compose.ui.platform.TextToolbar
-import androidx.compose.ui.platform.TextToolbarStatus
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.style.ResolvedTextDirection
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import kotlin.math.max
-import kotlin.math.min
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextFieldSelectionState(
-    private val textFieldState: TransformedTextFieldState,
-    private val textLayoutState: TextLayoutState,
-    private var density: Density,
-    private var enabled: Boolean,
-    private var readOnly: Boolean,
-    var isFocused: Boolean, /* true iff component is focused and the window is focused */
-) {
-    /**
-     * [HapticFeedback] handle to perform haptic feedback.
-     */
-    private var hapticFeedBack: HapticFeedback? = null
-
-    /**
-     * [TextToolbar] to show floating toolbar(post-M) or primary toolbar(pre-M).
-     */
-    private var textToolbar: TextToolbar? = null
-
-    /**
-     * [ClipboardManager] to perform clipboard features.
-     */
-    private var clipboardManager: ClipboardManager? = null
-
-    /**
-     * Whether user is interacting with the UI in touch mode.
-     */
-    var isInTouchMode: Boolean by mutableStateOf(true)
-        private set
-
-    /**
-     * Reduced [ReceiveContentConfiguration] from the attached modifier node hierarchy. This value
-     * is set by [TextFieldDecoratorModifierNode].
-     */
-    var receiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
-
-    /**
-     * The offset of visible bounds when dragging is started by a cursor or a selection handle.
-     * Total drag value needs to account for any auto scrolling that happens during dragging of a
-     * handle.
-     * This value is an anchor to calculate how much the visible bounds have shifted as the
-     * dragging continues. If a cursor or a selection handle is not dragging, this value needs to be
-     * [Offset.Unspecified]. This includes long press and drag gesture defined on TextField.
-     */
-    private var startContentVisibleOffset by mutableStateOf(Offset.Unspecified)
-
-    /**
-     * Calculates the offset of currently visible bounds.
-     */
-    private val currentContentVisibleOffset: Offset
-        get() = textLayoutCoordinates
-            ?.visibleBounds()
-            ?.topLeft ?: Offset.Unspecified
-
-    /**
-     * Current drag position of a handle for magnifier to read. Only one handle can be dragged
-     * at one time. This uses raw position as in only gesture start position and delta are used to
-     * calculate it. If auto-scroll happens due to selection changes while the gesture is active,
-     * it is not reflected on this value. See [handleDragPosition] for such a behavior.
-     *
-     * This value can reflect the drag position of either a real handle like cursor or selection or
-     * an acting handle when long press dragging happens directly on the text field. However, these
-     * two systems (real and acting handles) use different coordinate systems. When real handles
-     * set this value, they send inner text field coordinates. On the other hand, long press and
-     * drag gesture defined on text field would send coordinates in the decoration coordinates.
-     */
-    private var rawHandleDragPosition by mutableStateOf(Offset.Unspecified)
-
-    /**
-     * Defines where the handle exactly is in text layout node coordinates. This is mainly used by
-     * Magnifier to anchor itself. Also, it provides an updated total drag value to cursor and
-     * selection handles to continue scrolling as they are dragged outside the visible bounds.
-     *
-     * This value is primarily used by Magnifier and any handle dragging gesture detector. Since
-     * these calculations use inner text field coordinates, [handleDragPosition] is also always
-     * represented in the same coordinate system.
-     */
-    val handleDragPosition: Offset
-        get() = when {
-            // nothing is being dragged.
-            rawHandleDragPosition.isUnspecified -> {
-                Offset.Unspecified
-            }
-            // no real handle is being dragged, we need to offset the drag position by current
-            // inner-decorator relative positioning.
-            startContentVisibleOffset.isUnspecified -> {
-                textLayoutState.fromDecorationToTextLayout(rawHandleDragPosition)
-            }
-            // a cursor or a selection handle is being dragged, offset by comparing the current
-            // and starting visible offsets.
-            else -> {
-                rawHandleDragPosition + currentContentVisibleOffset - startContentVisibleOffset
-            }
-        }
-
-    /**
-     * Which selection handle is currently being dragged.
-     */
-    var draggingHandle by mutableStateOf<Handle?>(null)
-
-    /**
-     * Whether to show the cursor handle below cursor indicator when the TextField is focused.
-     */
-    private var showCursorHandle by mutableStateOf(false)
-
-    /**
-     * Whether to show the TextToolbar according to current selection state. This is not the final
-     * decider for showing the toolbar. Please refer to [observeTextToolbarVisibility] docs.
-     */
-    private var textToolbarState by mutableStateOf(TextToolbarState.None)
-
-    /**
-     * Access helper for text layout node coordinates that checks attached state.
-     */
-    private val textLayoutCoordinates: LayoutCoordinates?
-        get() = textLayoutState.textLayoutNodeCoordinates?.takeIf { it.isAttached }
-
-    /**
-     * Whether the contents of this TextField can be changed by the user.
-     */
-    private val editable: Boolean
-        get() = enabled && !readOnly
-
-    /**
-     * The most recent [SelectionLayout] that passed the [SelectionLayout.shouldRecomputeSelection]
-     * check. Provides context to the next selection update such as if the selection is shrinking
-     * or not.
-     */
-    private var previousSelectionLayout: SelectionLayout? = null
-
-    /**
-     * The previous offset of a drag, before selection adjustments.
-     * Only update when a selection layout change has occurred,
-     * or set to -1 if a new drag begins.
-     */
-    private var previousRawDragOffset: Int = -1
-
-    /**
-     * State of the cursor handle that includes its visibility and position.
-     */
-    val cursorHandle by derivedStateOf {
-        // For cursor handle to be visible, [showCursorHandle] must be true and the selection
-        // must be collapsed.
-        // Also, cursor handle should be in visible bounds of the TextField. However, if
-        // cursor is dragging and gets out of bounds, we cannot remove it from composition
-        // because that would stop the drag gesture defined on it. Instead, we allow the handle
-        // to be visible as long as it's being dragged.
-        // Visible bounds calculation lags one frame behind to let auto-scrolling settle.
-        val text = textFieldState.visualText
-        val visible = showCursorHandle &&
-            text.selectionInChars.collapsed &&
-            text.isNotEmpty() &&
-            (draggingHandle == Handle.Cursor || cursorHandleInBounds)
-
-        if (!visible) return@derivedStateOf TextFieldHandleState.Hidden
-
-        // text direction is useless for cursor handle, any value is fine.
-        TextFieldHandleState(
-            visible = true,
-            position = cursorRect.bottomCenter,
-            direction = ResolvedTextDirection.Ltr,
-            handlesCrossed = false
-        )
-    }
-
-    /**
-     * Whether currently cursor handle is in visible bounds. This derived state does not react to
-     * selection changes immediately because every selection change is processed in layout phase
-     * by auto-scroll behavior. Only after giving auto-scroll time to process the cursor movement,
-     * and possibly scroll the cursor back into view, we can say that whether cursor is in visible
-     * bounds or not. This is guaranteed to happen after scroll since new [textLayoutCoordinates]
-     * are reported after the layout phase end.
-     */
-    private val cursorHandleInBounds by derivedStateOf(policy = structuralEqualityPolicy()) {
-        val position = Snapshot.withoutReadObservation { cursorRect.bottomCenter }
-
-        textLayoutCoordinates
-            ?.visibleBounds()
-            ?.containsInclusive(position)
-            ?: false
-    }
-
-    /**
-     * Where the cursor should be at any given time in core node coordinates.
-     *
-     * Returns [Rect.Zero] if text layout has not been calculated yet or the selection is not
-     * collapsed (no cursor to locate).
-     */
-    val cursorRect: Rect by derivedStateOf {
-        val layoutResult = textLayoutState.layoutResult ?: return@derivedStateOf Rect.Zero
-        val value = textFieldState.visualText
-        if (!value.selectionInChars.collapsed) return@derivedStateOf Rect.Zero
-        val cursorRect = layoutResult.getCursorRect(value.selectionInChars.start)
-
-        val cursorWidth = with(density) { DefaultCursorThickness.toPx() }
-        // left and right values in cursorRect should be the same but in any case use the
-        // logically correct anchor.
-        val cursorCenterX = if (layoutResult.layoutInput.layoutDirection == LayoutDirection.Ltr) {
-            (cursorRect.left + cursorWidth / 2)
-        } else {
-            (cursorRect.right - cursorWidth / 2)
-        }
-
-        // don't let cursor go beyond the bounds of text layout node or cursor will be clipped.
-        // but also make sure that empty Text Layout still draws a cursor.
-        val coercedCursorCenterX = cursorCenterX
-            // do not use coerceIn because it is not guaranteed that minimum value is smaller
-            // than the maximum value.
-            .coerceAtMost(layoutResult.size.width - cursorWidth / 2)
-            .coerceAtLeast(cursorWidth / 2)
-
-        Rect(
-            left = coercedCursorCenterX - cursorWidth / 2,
-            right = coercedCursorCenterX + cursorWidth / 2,
-            top = cursorRect.top,
-            bottom = cursorRect.bottom
-        )
-    }
-
-    fun update(
-        hapticFeedBack: HapticFeedback,
-        clipboardManager: ClipboardManager,
-        textToolbar: TextToolbar,
-        density: Density,
-        enabled: Boolean,
-        readOnly: Boolean,
-    ) {
-        if (!enabled) {
-            hideTextToolbar()
-        }
-        this.hapticFeedBack = hapticFeedBack
-        this.clipboardManager = clipboardManager
-        this.textToolbar = textToolbar
-        this.density = density
-        this.enabled = enabled
-        this.readOnly = readOnly
-    }
-
-    /**
-     * Implements the complete set of gestures supported by the cursor handle.
-     */
-    suspend fun PointerInputScope.cursorHandleGestures() {
-        coroutineScope {
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTouchMode()
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectCursorHandleDragGestures()
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTapGestures(onTap = {
-                    textToolbarState = if (textToolbarState == TextToolbarState.Cursor) {
-                        TextToolbarState.None
-                    } else {
-                        TextToolbarState.Cursor
-                    }
-                })
-            }
-        }
-    }
-
-    /**
-     * Implements the complete set of gestures supported by the TextField area.
-     */
-    suspend fun PointerInputScope.textFieldGestures(
-        requestFocus: () -> Unit,
-        showKeyboard: () -> Unit
-    ) {
-        coroutineScope {
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTouchMode()
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTextFieldTapGestures(requestFocus, showKeyboard)
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTextFieldLongPressAndAfterDrag(requestFocus)
-            }
-        }
-    }
-
-    /**
-     * Gesture detector for dragging the selection handles to change the selection in TextField.
-     */
-    suspend fun PointerInputScope.selectionHandleGestures(isStartHandle: Boolean) {
-        coroutineScope {
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectTouchMode()
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectPressDownGesture(
-                    onDown = {
-                        markStartContentVisibleOffset()
-                        updateHandleDragging(
-                            handle = if (isStartHandle) {
-                                Handle.SelectionStart
-                            } else {
-                                Handle.SelectionEnd
-                            },
-                            position = getAdjustedCoordinates(getHandlePosition(isStartHandle))
-                        )
-                    },
-                    onUp = {
-                        clearHandleDragging()
-                    }
-                )
-            }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectSelectionHandleDragGestures(isStartHandle)
-            }
-        }
-    }
-
-    /**
-     * Starts observing changes in the current state for reactive rules. For example, the cursor
-     * handle or the selection handles should hide whenever the text content changes.
-     */
-    suspend fun observeChanges() {
-        try {
-            coroutineScope {
-                launch { observeTextChanges() }
-                launch { observeTextToolbarVisibility() }
-            }
-        } finally {
-            showCursorHandle = false
-            if (textToolbarState != TextToolbarState.None) {
-                hideTextToolbar()
-            }
-        }
-    }
-
-    fun updateTextToolbarState(textToolbarState: TextToolbarState) {
-        this.textToolbarState = textToolbarState
-    }
-
-    fun dispose() {
-        hideTextToolbar()
-
-        textToolbar = null
-        clipboardManager = null
-        hapticFeedBack = null
-    }
-
-    /**
-     * Detects the current pointer type in this [PointerInputScope] to update the touch mode state.
-     * This helper gesture detector should be added to all TextField pointer input receivers such
-     * as TextFieldDecorator, cursor handle, and selection handles.
-     */
-    private suspend fun PointerInputScope.detectTouchMode() {
-        awaitPointerEventScope {
-            while (true) {
-                val event = awaitPointerEvent(PointerEventPass.Initial)
-                isInTouchMode = !event.isPrecisePointer
-            }
-        }
-    }
-
-    private suspend fun PointerInputScope.detectTextFieldTapGestures(
-        requestFocus: () -> Unit,
-        showKeyboard: () -> Unit
-    ) {
-        detectTapAndDoubleTap(
-            onTap = { offset ->
-                logDebug { "onTapTextField" }
-                requestFocus()
-
-                if (editable && isFocused) {
-                    showKeyboard()
-                    if (textFieldState.visualText.isNotEmpty()) {
-                        showCursorHandle = true
-                    }
-
-                    // do not show any TextToolbar.
-                    updateTextToolbarState(TextToolbarState.None)
-
-                    val coercedOffset = textLayoutState.coercedInVisibleBoundsOfInputText(offset)
-
-                    placeCursorAtNearestOffset(
-                        textLayoutState.fromDecorationToTextLayout(coercedOffset)
-                    )
-                }
-            },
-            onDoubleTap = { offset ->
-                logDebug { "onDoubleTapTextField" }
-                // onTap is already called at this point. Focus is requested.
-
-                showCursorHandle = false
-                // go into selection mode.
-                updateTextToolbarState(TextToolbarState.Selection)
-
-                val index = textLayoutState.getOffsetForPosition(offset)
-                val newSelection = updateSelection(
-                    // reset selection, otherwise a previous selection may be used
-                    // as context for creating the next selection
-                    textFieldCharSequence = TextFieldCharSequence(
-                        textFieldState.visualText,
-                        TextRange.Zero
-                    ),
-                    startOffset = index,
-                    endOffset = index,
-                    isStartHandle = false,
-                    adjustment = SelectionAdjustment.Word,
-                )
-                textFieldState.selectCharsIn(newSelection)
-            }
-        )
-    }
-
-    /**
-     * Calculates the valid cursor position nearest to [offset] and sets the cursor to it.
-     * Takes into account text transformations ([TransformedTextFieldState]) to avoid putting the
-     * cursor in the middle of replacements.
-     *
-     * If the cursor would end up in the middle of an insertion or replacement, it is instead pushed
-     * to the nearest edge of the wedge to the [offset].
-     *
-     * @param offset Where the cursor is in text layout coordinates. If the caller has the offset
-     * in decorator coordinates, [TextLayoutState.fromDecorationToTextLayout] can be used to convert
-     * between the two spaces.
-     * @return true if the cursor moved, false if the cursor position did not need to change.
-     */
-    private fun placeCursorAtNearestOffset(offset: Offset): Boolean {
-        val layoutResult = textLayoutState.layoutResult ?: return false
-
-        // First step: calculate the proposed cursor index.
-        val index = layoutResult.getOffsetForPosition(offset)
-        if (index == -1) return false
-
-        // Second step: if a transformation is applied, determine if the proposed cursor position
-        // would be in a range where the cursor is not allowed to be. If so, push it to the
-        // appropriate edge of that range.
-        var newAffinity: SelectionWedgeAffinity? = null
-        val untransformedCursor =
-            textFieldState.getIndexTransformationType(index) { type, untransformed, retransformed ->
-                when (type) {
-                    Untransformed -> untransformed.start
-
-                    // Deletion. Doesn't matter which end of the deleted range we put the cursor,
-                    // they'll both map to the same transformed offset.
-                    Deletion -> untransformed.start
-
-                    // The untransformed offset will be the same no matter which side we put the
-                    // cursor on, so we need to set the affinity to the closer edge.
-                    Insertion -> {
-                        val wedgeStartCursorRect = layoutResult.getCursorRect(retransformed.start)
-                        val wedgeEndCursorRect = layoutResult.getCursorRect(retransformed.end)
-                        newAffinity = if (offset.findClosestRect(
-                                wedgeStartCursorRect,
-                                wedgeEndCursorRect
-                            ) < 0
-                        ) {
-                            SelectionWedgeAffinity(WedgeAffinity.Start)
-                        } else {
-                            SelectionWedgeAffinity(WedgeAffinity.End)
-                        }
-                        untransformed.start
-                    }
-
-                    // Set the untransformed cursor to the edge that corresponds to the closer edge
-                    // in the transformed text.
-                    Replacement -> {
-                        val wedgeStartCursorRect = layoutResult.getCursorRect(retransformed.start)
-                        val wedgeEndCursorRect = layoutResult.getCursorRect(retransformed.end)
-                        if (offset.findClosestRect(wedgeStartCursorRect, wedgeEndCursorRect) < 0) {
-                            untransformed.start
-                        } else {
-                            untransformed.end
-                        }
-                    }
-                }
-            }
-        val untransformedCursorRange = TextRange(untransformedCursor)
-
-        // Nothing changed, skip onValueChange and hapticFeedback.
-        if (untransformedCursorRange == textFieldState.untransformedText.selectionInChars &&
-            (newAffinity == null || newAffinity == textFieldState.selectionWedgeAffinity)
-        ) {
-            return false
-        }
-
-        textFieldState.selectUntransformedCharsIn(untransformedCursorRange)
-        newAffinity?.let {
-            textFieldState.selectionWedgeAffinity = it
-        }
-        return true
-    }
-
-    private suspend fun PointerInputScope.detectCursorHandleDragGestures() {
-        var cursorDragStart = Offset.Unspecified
-        var cursorDragDelta = Offset.Unspecified
-
-        fun onDragStop() {
-            // Only execute clear-up if drag was actually ongoing.
-            if (cursorDragStart.isSpecified) {
-                cursorDragStart = Offset.Unspecified
-                cursorDragDelta = Offset.Unspecified
-                clearHandleDragging()
-            }
-        }
-
-        // b/288931376: detectDragGestures do not call onDragCancel when composable is disposed.
-        try {
-            detectDragGestures(
-                onDragStart = {
-                    // mark start drag point
-                    cursorDragStart = getAdjustedCoordinates(cursorRect.bottomCenter)
-                    cursorDragDelta = Offset.Zero
-                    isInTouchMode = true
-                    markStartContentVisibleOffset()
-                    updateHandleDragging(Handle.Cursor, cursorDragStart)
-                },
-                onDragEnd = { onDragStop() },
-                onDragCancel = { onDragStop() },
-                onDrag = onDrag@{ change, dragAmount ->
-                    cursorDragDelta += dragAmount
-
-                    updateHandleDragging(Handle.Cursor, cursorDragStart + cursorDragDelta)
-
-                    if (placeCursorAtNearestOffset(handleDragPosition)) {
-                        change.consume()
-                        // TODO: only perform haptic feedback if filter does not override the change
-                        hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-                    }
-                }
-            )
-        } finally {
-            onDragStop()
-        }
-    }
-
-    private suspend fun PointerInputScope.detectTextFieldLongPressAndAfterDrag(
-        requestFocus: () -> Unit
-    ) {
-        var dragBeginOffsetInText = -1
-        var dragBeginPosition: Offset = Offset.Unspecified
-        var dragTotalDistance: Offset = Offset.Zero
-        var actingHandle: Handle = Handle.SelectionEnd // start with a placeholder.
-
-        fun onDragStop() {
-            // Only execute clear-up if drag was actually ongoing.
-            if (dragBeginPosition.isSpecified) {
-                clearHandleDragging()
-                dragBeginOffsetInText = -1
-                dragBeginPosition = Offset.Unspecified
-                dragTotalDistance = Offset.Zero
-                previousRawDragOffset = -1
-            }
-        }
-
-        // offsets received by this gesture detector are in decoration box coordinates
-        detectDragGesturesAfterLongPress(
-            onDragStart = onDragStart@{ dragStartOffset ->
-                logDebug { "onDragStart after longPress $dragStartOffset" }
-                requestFocus()
-
-                // this gesture detector is applied on the decoration box. We do not need to
-                // convert the gesture offset, that's going to be calculated by [handleDragPosition]
-                updateHandleDragging(
-                    handle = actingHandle,
-                    position = dragStartOffset
-                )
-
-                dragBeginPosition = dragStartOffset
-                dragTotalDistance = Offset.Zero
-                previousRawDragOffset = -1
-
-                // Long Press at the blank area, the cursor should show up at the end of the line.
-                if (!textLayoutState.isPositionOnText(dragStartOffset)) {
-                    val offset = textLayoutState.getOffsetForPosition(dragStartOffset)
-
-                    hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-                    textFieldState.placeCursorBeforeCharAt(offset)
-                    showCursorHandle = true
-                    updateTextToolbarState(TextToolbarState.Cursor)
-                } else {
-                    if (textFieldState.visualText.isEmpty()) return@onDragStart
-                    val offset = textLayoutState.getOffsetForPosition(dragStartOffset)
-                    val newSelection = updateSelection(
-                        // reset selection, otherwise a previous selection may be used
-                        // as context for creating the next selection
-                        textFieldCharSequence = TextFieldCharSequence(
-                            textFieldState.visualText,
-                            TextRange.Zero
-                        ),
-                        startOffset = offset,
-                        endOffset = offset,
-                        isStartHandle = false,
-                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
-                    )
-                    textFieldState.selectCharsIn(newSelection)
-                    updateTextToolbarState(TextToolbarState.Selection)
-                    // For touch, set the begin offset to the adjusted selection.
-                    // When char based selection is used, we want to ensure we snap the
-                    // beginning offset to the start word boundary of the first selected word.
-                    dragBeginOffsetInText = newSelection.start
-                }
-            },
-            onDragEnd = { onDragStop() },
-            onDragCancel = { onDragStop() },
-            onDrag = onDrag@{ _, dragAmount ->
-                // selection never started, did not consume any drag
-                if (textFieldState.visualText.isEmpty()) return@onDrag
-
-                dragTotalDistance += dragAmount
-
-                // "start position + total delta" is not enough to understand the current
-                // pointer position relative to text layout. We need to also account for any
-                // changes to visible offset that's caused by auto-scrolling while dragging.
-                val currentDragPosition = dragBeginPosition + dragTotalDistance
-
-                logDebug { "onDrag after longPress $currentDragPosition" }
-
-                val startOffset: Int
-                val endOffset: Int
-                val adjustment: SelectionAdjustment
-
-                if (
-                    dragBeginOffsetInText < 0 && // drag started in end padding
-                    !textLayoutState.isPositionOnText(currentDragPosition) // still in end padding
-                ) {
-                    startOffset = textLayoutState.getOffsetForPosition(dragBeginPosition)
-                    endOffset = textLayoutState.getOffsetForPosition(currentDragPosition)
-
-                    adjustment = if (startOffset == endOffset) {
-                        // start and end is in the same end padding, keep the collapsed selection
-                        SelectionAdjustment.None
-                    } else {
-                        SelectionAdjustment.Word
-                    }
-                } else {
-                    startOffset = dragBeginOffsetInText.takeIf { it >= 0 }
-                        ?: textLayoutState.getOffsetForPosition(
-                            position = dragBeginPosition,
-                            coerceInVisibleBounds = false
-                        )
-                    endOffset = textLayoutState.getOffsetForPosition(
-                        position = currentDragPosition,
-                        coerceInVisibleBounds = false
-                    )
-
-                    if (dragBeginOffsetInText < 0 && startOffset == endOffset) {
-                        // if we are selecting starting from end padding,
-                        // don't start selection until we have and un-collapsed selection.
-                        return@onDrag
-                    }
-
-                    adjustment = SelectionAdjustment.Word
-                }
-
-                val prevSelection = textFieldState.visualText.selectionInChars
-                var newSelection = updateSelection(
-                    textFieldCharSequence = textFieldState.visualText,
-                    startOffset = startOffset,
-                    endOffset = endOffset,
-                    isStartHandle = false,
-                    adjustment = adjustment,
-                    allowPreviousSelectionCollapsed = false,
-                )
-
-                // Although we support reversed selection, reversing the selection after it's
-                // initiated via long press has a visual glitch that's hard to get rid of. When
-                // handles (start/end) switch places after the selection reverts, draw happens a
-                // bit late, making it obvious that selection handles switched places. We simply do
-                // not allow reversed selection during long press drag.
-                if (newSelection.reversed) {
-                    newSelection = newSelection.reverse()
-                }
-
-                // When drag starts from the end padding, we eventually need to update the start
-                // point once a selection is initiated. Otherwise, startOffset is always calculated
-                // from dragBeginPosition which can refer to different positions on text if
-                // TextField starts scrolling.
-                if (dragBeginOffsetInText == -1 && !newSelection.collapsed) {
-                    dragBeginOffsetInText = newSelection.start
-                }
-
-                // if the new selection is not equal to previous selection, consider updating the
-                // acting handle. Otherwise, acting handle should remain the same.
-                if (newSelection != prevSelection) {
-                    // Find the growing direction of selection
-                    // - Since we do not allow reverse selection,
-                    //   - selection.start == selection.min
-                    //   - selection.end == selection.max
-                    // - If only start or end changes ([A, B] => [A, C]; [C, E] => [D, E])
-                    //   - acting handle is the changing handle.
-                    // - If both change, find the middle point and see how it moves.
-                    //   - If middle point moves right, acting handle is SelectionEnd
-                    //   - Otherwise, acting handle is SelectionStart
-                    actingHandle = when {
-                        newSelection.start != prevSelection.start &&
-                            newSelection.end == prevSelection.end -> Handle.SelectionStart
-                        newSelection.start == prevSelection.start &&
-                            newSelection.end != prevSelection.end -> Handle.SelectionEnd
-                        else -> {
-                            val newMiddle = (newSelection.start + newSelection.end) / 2f
-                            val prevMiddle = (prevSelection.start + prevSelection.end) / 2f
-                            if (newMiddle > prevMiddle) {
-                                Handle.SelectionEnd
-                            } else {
-                                Handle.SelectionStart
-                            }
-                        }
-                    }
-                }
-
-                // Do not allow selection to collapse on itself while dragging. Selection can
-                // reverse but does not collapse.
-                if (prevSelection.collapsed || !newSelection.collapsed) {
-                    textFieldState.selectCharsIn(newSelection)
-                }
-                updateHandleDragging(
-                    handle = actingHandle,
-                    position = currentDragPosition
-                )
-            }
-        )
-    }
-
-    private suspend fun PointerInputScope.detectSelectionHandleDragGestures(
-        isStartHandle: Boolean
-    ) {
-        var dragBeginPosition: Offset = Offset.Unspecified
-        var dragTotalDistance: Offset = Offset.Zero
-        val handle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
-
-        fun onDragStop() {
-            // Only execute clear-up if drag was actually ongoing.
-            if (dragBeginPosition.isSpecified) {
-                clearHandleDragging()
-                dragBeginPosition = Offset.Unspecified
-                dragTotalDistance = Offset.Zero
-                previousRawDragOffset = -1
-            }
-        }
-
-        // b/288931376: detectDragGestures do not call onDragCancel when composable is disposed.
-        try {
-            detectDragGestures(
-                onDragStart = {
-                    // The position of the character where the drag gesture should begin. This is in
-                    // the composable coordinates.
-                    dragBeginPosition = getAdjustedCoordinates(getHandlePosition(isStartHandle))
-
-                    // no need to call markStartContentVisibleOffset, since it was called by the
-                    // initial down event.
-                    updateHandleDragging(handle, dragBeginPosition)
-
-                    // Zero out the total distance that being dragged.
-                    dragTotalDistance = Offset.Zero
-
-                    previousRawDragOffset = -1
-                },
-                onDragEnd = { onDragStop() },
-                onDragCancel = { onDragStop() },
-                onDrag = onDrag@{ _, delta ->
-                    dragTotalDistance += delta
-                    val layoutResult = textLayoutState.layoutResult ?: return@onDrag
-
-                    updateHandleDragging(handle, dragBeginPosition + dragTotalDistance)
-
-                    val startOffset = if (isStartHandle) {
-                        layoutResult.getOffsetForPosition(handleDragPosition)
-                    } else {
-                        textFieldState.visualText.selectionInChars.start
-                    }
-
-                    val endOffset = if (isStartHandle) {
-                        textFieldState.visualText.selectionInChars.end
-                    } else {
-                        layoutResult.getOffsetForPosition(handleDragPosition)
-                    }
-
-                    val prevSelection = textFieldState.visualText.selectionInChars
-                    val newSelection = updateSelection(
-                        textFieldCharSequence = textFieldState.visualText,
-                        startOffset = startOffset,
-                        endOffset = endOffset,
-                        isStartHandle = isStartHandle,
-                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
-                    )
-                    // Do not allow selection to collapse on itself while dragging selection
-                    // handles. Selection can reverse but does not collapse.
-                    if (prevSelection.collapsed || !newSelection.collapsed) {
-                        textFieldState.selectCharsIn(newSelection)
-                    }
-                }
-            )
-        } finally {
-            logDebug {
-                "Selection Handle drag cancelled for " +
-                    "draggingHandle: $draggingHandle definedOn: $handle"
-            }
-            if (draggingHandle == handle) {
-                onDragStop()
-            }
-        }
-    }
-
-    private suspend fun observeTextChanges() {
-        snapshotFlow { textFieldState.visualText }
-            .distinctUntilChanged(TextFieldCharSequence::contentEquals)
-            // first value needs to be dropped because it cannot be compared to a prior value
-            .drop(1)
-            .collect {
-                showCursorHandle = false
-                // hide the toolbar any time text content changes.
-                updateTextToolbarState(TextToolbarState.None)
-            }
-    }
-
-    /**
-     * Manages the visibility of text toolbar according to current state and received events from
-     * various sources.
-     *
-     * - Tapping the cursor handle toggles the visibility of the toolbar [TextToolbarState.Cursor].
-     * - Dragging the cursor handle or selection handles temporarily hides the toolbar
-     * [draggingHandle].
-     * - Tapping somewhere on the TextField, whether it causes a cursor position change or not,
-     * fully hides the toolbar [TextToolbarState.None].
-     * - When cursor or selection leaves the visible bounds, text toolbar is temporarily hidden.
-     * [getContentRect]
-     * - When selection is initiated via long press, double click, or semantics, text toolbar shows
-     * [TextToolbarState.Selection]
-     */
-    private suspend fun observeTextToolbarVisibility() {
-        snapshotFlow {
-            val isCollapsed = textFieldState.visualText.selectionInChars.collapsed
-            val textToolbarStateVisible =
-                isCollapsed && textToolbarState == TextToolbarState.Cursor ||
-                    !isCollapsed && textToolbarState == TextToolbarState.Selection
-
-            val textToolbarVisible =
-                // toolbar is requested specifically for the current selection state
-                textToolbarStateVisible &&
-                    draggingHandle == null && // not dragging any selection handles
-                    isInTouchMode // toolbar hidden when not in touch mode
-
-            // final visibility decision is made by contentRect visibility.
-            // if contentRect is not in visible bounds, just pass Rect.Zero to the observer so that
-            // it hides the toolbar. If Rect is successfully passed to the observer, toolbar will
-            // be displayed.
-            if (!textToolbarVisible) {
-                Rect.Zero
-            } else {
-                // contentRect is calculated in root coordinates. VisibleBounds are in parent
-                // coordinates. Convert visibleBounds to root before checking the overlap.
-                val visibleBounds = textLayoutCoordinates?.visibleBounds()
-                if (visibleBounds != null) {
-                    val visibleBoundsTopLeftInRoot =
-                        textLayoutCoordinates?.localToRoot(visibleBounds.topLeft)
-                    val visibleBoundsInRoot =
-                        Rect(visibleBoundsTopLeftInRoot!!, visibleBounds.size)
-
-                    // contentRect can be very wide if a big part of text is selected. Our toolbar
-                    // should be aligned only to visible region.
-                    getContentRect()
-                        .takeIf { visibleBoundsInRoot.overlaps(it) }
-                        ?.intersect(visibleBoundsInRoot)
-                        ?: Rect.Zero
-                } else {
-                    Rect.Zero
-                }
-            }
-        }.collect { rect ->
-            if (rect == Rect.Zero) {
-                hideTextToolbar()
-            } else {
-                showTextToolbar(rect)
-            }
-        }
-    }
-
-    /**
-     * Calculate selected region as [Rect]. The top is the top of the first selected
-     * line, and the bottom is the bottom of the last selected line. The left is the leftmost
-     * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
-     */
-    private fun getContentRect(): Rect {
-        val text = textFieldState.visualText
-        // accept cursor position as content rect when selection is collapsed
-        // contentRect is defined in text layout node coordinates, so it needs to be realigned to
-        // the root container.
-        if (text.selectionInChars.collapsed) {
-            val topLeft = textLayoutCoordinates?.localToRoot(cursorRect.topLeft) ?: Offset.Zero
-            return Rect(topLeft, cursorRect.size)
-        }
-        val startOffset =
-            textLayoutCoordinates?.localToRoot(getHandlePosition(true)) ?: Offset.Zero
-        val endOffset =
-            textLayoutCoordinates?.localToRoot(getHandlePosition(false)) ?: Offset.Zero
-        val startTop =
-            textLayoutCoordinates?.localToRoot(
-                Offset(
-                    0f,
-                    textLayoutState.layoutResult?.getCursorRect(
-                        text.selectionInChars.start
-                    )?.top ?: 0f
-                )
-            )?.y ?: 0f
-        val endTop =
-            textLayoutCoordinates?.localToRoot(
-                Offset(
-                    0f,
-                    textLayoutState.layoutResult?.getCursorRect(
-                        text.selectionInChars.end
-                    )?.top ?: 0f
-                )
-            )?.y ?: 0f
-
-        return Rect(
-            left = min(startOffset.x, endOffset.x),
-            right = max(startOffset.x, endOffset.x),
-            top = min(startTop, endTop),
-            bottom = max(startOffset.y, endOffset.y)
-        )
-    }
-
-    /**
-     * Calculates and returns the [TextFieldHandleState] of the requested selection handle which is
-     * specified by [isStartHandle]. Pass [includePosition] as false to omit the position from the
-     * result. This helps create a derived state which does not invalidate according position
-     * changes.
-     */
-    internal fun getSelectionHandleState(
-        isStartHandle: Boolean,
-        includePosition: Boolean
-    ): TextFieldHandleState {
-        val handle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
-
-        val layoutResult = textLayoutState.layoutResult ?: return TextFieldHandleState.Hidden
-
-        val selection = textFieldState.visualText.selectionInChars
-
-        if (selection.collapsed) return TextFieldHandleState.Hidden
-
-        val position = getHandlePosition(isStartHandle)
-
-        val visible = draggingHandle == handle ||
-            (textLayoutCoordinates
-                ?.visibleBounds()
-                ?.containsInclusive(position)
-                ?: false)
-
-        if (!visible) return TextFieldHandleState.Hidden
-
-        val directionOffset = if (isStartHandle) selection.start else max(selection.end - 1, 0)
-        val direction = layoutResult.getBidiRunDirection(directionOffset)
-        val handlesCrossed = selection.reversed
-
-        // Handle normally is visible when it's out of bounds but when the handle is being dragged,
-        // we let it stay on the screen to maintain gesture continuation. However, we still want
-        // to coerce handle's position to visible bounds to not let it jitter while scrolling the
-        // TextField as the selection is expanding.
-        val coercedPosition = if (includePosition) {
-            textLayoutCoordinates?.visibleBounds()?.let { position.coerceIn(it) }
-                ?: position
-        } else {
-            Offset.Unspecified
-        }
-        return TextFieldHandleState(
-            visible = true,
-            position = coercedPosition,
-            direction = direction,
-            handlesCrossed = handlesCrossed
-        )
-    }
-
-    private fun getHandlePosition(isStartHandle: Boolean): Offset {
-        val layoutResult = textLayoutState.layoutResult ?: return Offset.Zero
-        val selection = textFieldState.visualText.selectionInChars
-        val offset = if (isStartHandle) {
-            selection.start
-        } else {
-            selection.end
-        }
-        return getSelectionHandleCoordinates(
-            textLayoutResult = layoutResult,
-            offset = offset,
-            isStart = isStartHandle,
-            areHandlesCrossed = selection.reversed
-        )
-    }
-
-    /**
-     * Sets currently dragging handle state to [handle] and positions it at [position]. This is
-     * mostly useful for updating the magnifier.
-     *
-     * @param handle A real or acting handle that specifies which one is being dragged.
-     * @param position Where the handle currently is
-     */
-    fun updateHandleDragging(
-        handle: Handle,
-        position: Offset
-    ) {
-        draggingHandle = handle
-        rawHandleDragPosition = position
-    }
-
-    /**
-     * When a Selection or Cursor Handle is started to being dragged, this function should be called
-     * to mark the current visible offset, so that if content gets scrolled during the drag, we
-     * can correctly offset the actual position where drag corresponds to.
-     */
-    private fun markStartContentVisibleOffset() {
-        startContentVisibleOffset = textLayoutCoordinates
-            ?.visibleBounds()
-            ?.topLeft ?: Offset.Unspecified
-    }
-
-    /**
-     * Call this function when a selection or cursor handle is stopped dragging.
-     */
-    fun clearHandleDragging() {
-        draggingHandle = null
-        rawHandleDragPosition = Offset.Unspecified
-        startContentVisibleOffset = Offset.Unspecified
-    }
-
-    /**
-     * The method for cutting text.
-     *
-     * If there is no selection, return.
-     * Put the selected text into the [ClipboardManager].
-     * The new text should be the text before the selection plus the text after the selection.
-     * And the new cursor offset should be between the text before the selection, and the text
-     * after the selection.
-     */
-    fun cut() {
-        val text = textFieldState.visualText
-        if (text.selectionInChars.collapsed) return
-
-        clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
-
-        textFieldState.deleteSelectedText()
-    }
-
-    /**
-     * The method for copying text.
-     *
-     * If there is no selection, return.
-     * Put the selected text into the [ClipboardManager], and cancel the selection, if
-     * [cancelSelection] is true.
-     * The text in the text field should be unchanged.
-     * If [cancelSelection] is true, the new cursor offset should be at the end of the previous
-     * selected text.
-     */
-    fun copy(cancelSelection: Boolean = true) {
-        val text = textFieldState.visualText
-        if (text.selectionInChars.collapsed) return
-
-        clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
-
-        if (!cancelSelection) return
-
-        textFieldState.collapseSelectionToMax()
-    }
-
-    fun paste() {
-        val receiveContentConfiguration = receiveContentConfiguration?.invoke()
-            ?: return pasteAsPlainText()
-
-        val clipEntry = clipboardManager?.getClip() ?: return
-        val clipMetadata = clipboardManager?.getClipMetadata() ?: return pasteAsPlainText()
-
-        val remaining = receiveContentConfiguration.receiveContentListener.onReceive(
-            TransferableContent(
-                clipEntry = clipEntry,
-                source = TransferableContent.Source.Clipboard,
-                clipMetadata = clipMetadata
-            )
-        )
-
-        // TODO(halilibo): this is not 1-to-1 compatible with ClipboardManager.getText() which
-        //  returns an AnnotatedString and supports copy-pasting AnnotatedStrings inside the app.
-        remaining?.clipEntry?.readPlainText()?.let { clipboardText ->
-            textFieldState.replaceSelectedText(
-                clipboardText,
-                undoBehavior = TextFieldEditUndoBehavior.NeverMerge
-            )
-        }
-    }
-
-    /**
-     * The method for pasting text.
-     *
-     * Get the text from [ClipboardManager]. If it's null, return.
-     * The new content should be the text before the selected text, plus the text from the
-     * [ClipboardManager], and plus the text after the selected text.
-     * Then the selection should collapse, and the new cursor offset should be at the end of the
-     * newly added text.
-     */
-    private fun pasteAsPlainText() {
-        val clipboardText = clipboardManager?.getText()?.text ?: return
-
-        textFieldState.replaceSelectedText(
-            clipboardText,
-            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
-        )
-    }
-
-    /**
-     * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
-     * to make the FloatingToolbar show up in the proper place. In addition, this function passes
-     * the copy, paste and cut method as callbacks when "copy", "cut" or "paste" is clicked.
-     *
-     * @param contentRect Rectangle region where the toolbar will be anchored.
-     */
-    private fun showTextToolbar(contentRect: Rect) {
-        val selection = textFieldState.visualText.selectionInChars
-
-        // if receive content is configured, hasClip should be enough to show the paste option
-        val canPasteContent = receiveContentConfiguration?.invoke() != null &&
-            clipboardManager?.hasClip() == true
-        // if receive content is not configured, we expect at least a text item to be present
-        val canPasteText = clipboardManager?.hasText() == true
-        val canPaste = editable && (canPasteContent || canPasteText)
-
-        // TODO(halilibo): Add a new TextToolbar option "paste as plain text".
-        val paste: (() -> Unit)? = if (canPaste) {
-            {
-                paste()
-                updateTextToolbarState(TextToolbarState.None)
-            }
-        } else null
-
-        val copy: (() -> Unit)? = if (!selection.collapsed) {
-            {
-                copy()
-                updateTextToolbarState(TextToolbarState.None)
-            }
-        } else null
-
-        val cut: (() -> Unit)? = if (!selection.collapsed && editable) {
-            {
-                cut()
-                updateTextToolbarState(TextToolbarState.None)
-            }
-        } else null
-
-        val selectAll: (() -> Unit)? = if (selection.length != textFieldState.visualText.length) {
-            {
-                textFieldState.selectAll()
-                updateTextToolbarState(TextToolbarState.Selection)
-            }
-        } else null
-
-        textToolbar?.showMenu(
-            rect = contentRect,
-            onCopyRequested = copy,
-            onPasteRequested = paste,
-            onCutRequested = cut,
-            onSelectAllRequested = selectAll
-        )
-    }
-
-    fun deselect() {
-        if (!textFieldState.visualText.selectionInChars.collapsed) {
-            textFieldState.collapseSelectionToEnd()
-        }
-
-        showCursorHandle = false
-        updateTextToolbarState(TextToolbarState.None)
-    }
-
-    private fun hideTextToolbar() {
-        if (textToolbar?.status == TextToolbarStatus.Shown) {
-            textToolbar?.hide()
-        }
-    }
-
-    /**
-     * Update the text field's selection based on new offsets.
-     *
-     * @param textFieldCharSequence the current text editing state
-     * @param startOffset the start offset to use
-     * @param endOffset the end offset to use
-     * @param isStartHandle whether the start or end handle is being updated
-     * @param adjustment The selection adjustment to use
-     * @param allowPreviousSelectionCollapsed Allow a collapsed selection to be passed to selection
-     * adjustment. In most cases, a collapsed selection should be considered "no previous
-     * selection" for selection adjustment. However, in some cases - like starting a selection in
-     * end padding - a collapsed selection may be necessary context to avoid selection flickering.
-     */
-    private fun updateSelection(
-        textFieldCharSequence: TextFieldCharSequence,
-        startOffset: Int,
-        endOffset: Int,
-        isStartHandle: Boolean,
-        adjustment: SelectionAdjustment,
-        allowPreviousSelectionCollapsed: Boolean = false,
-    ): TextRange {
-        val newSelection = getTextFieldSelection(
-            rawStartOffset = startOffset,
-            rawEndOffset = endOffset,
-            previousSelection = textFieldCharSequence.selectionInChars
-                .takeIf { allowPreviousSelectionCollapsed || !it.collapsed },
-            isStartHandle = isStartHandle,
-            adjustment = adjustment,
-        )
-
-        if (newSelection == textFieldCharSequence.selectionInChars) return newSelection
-
-        val onlyChangeIsReversed =
-            newSelection.reversed != textFieldCharSequence.selectionInChars.reversed &&
-                newSelection.run { TextRange(end, start) } == textFieldCharSequence.selectionInChars
-
-        // don't haptic if we are using a mouse or if we aren't moving the selection bounds
-        if (isInTouchMode && !onlyChangeIsReversed) {
-            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-        }
-
-        return newSelection
-    }
-
-    private fun getTextFieldSelection(
-        rawStartOffset: Int,
-        rawEndOffset: Int,
-        previousSelection: TextRange?,
-        isStartHandle: Boolean,
-        adjustment: SelectionAdjustment
-    ): TextRange {
-        val layoutResult = textLayoutState.layoutResult ?: return TextRange.Zero
-
-        // When the previous selection is null, it's allowed to have collapsed selection on
-        // TextField. So we can ignore the SelectionAdjustment.Character.
-        if (previousSelection == null && adjustment == SelectionAdjustment.Character) {
-            return TextRange(rawStartOffset, rawEndOffset)
-        }
-
-        val selectionLayout = getTextFieldSelectionLayout(
-            layoutResult = layoutResult,
-            rawStartHandleOffset = rawStartOffset,
-            rawEndHandleOffset = rawEndOffset,
-            rawPreviousHandleOffset = previousRawDragOffset,
-            previousSelectionRange = previousSelection ?: TextRange.Zero,
-            isStartOfSelection = previousSelection == null,
-            isStartHandle = isStartHandle,
-        )
-
-        if (previousSelection != null &&
-            !selectionLayout.shouldRecomputeSelection(previousSelectionLayout)
-        ) {
-            return previousSelection
-        }
-
-        val result = adjustment.adjust(selectionLayout).toTextRange()
-        previousSelectionLayout = selectionLayout
-        previousRawDragOffset = if (isStartHandle) rawStartOffset else rawEndOffset
-
-        return result
-    }
-}
-
-private fun TextRange.reverse() = TextRange(end, start)
-
-/**
- * A state that indicates when to show TextToolbar.
- *
- * - [None] Do not show the TextToolbar at all.
- * - [Cursor] if selection is collapsed and all the other criteria are met, show the TextToolbar.
- * - [Selection] if selection is expanded and all the other criteria are met, show the TextToolbar.
- *
- * @see [TextFieldSelectionState.observeTextToolbarVisibility]
- */
-internal enum class TextToolbarState {
-    None,
-    Cursor,
-    Selection,
-}
-
-private const val DEBUG = false
-private const val DEBUG_TAG = "TextFieldSelectionState"
-
-private fun logDebug(text: () -> String) {
-    if (DEBUG) {
-        println("$DEBUG_TAG: ${text()}")
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextPreparedSelection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextPreparedSelection.kt
deleted file mode 100644
index f15b717..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextPreparedSelection.kt
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.findFollowingBreak
-import androidx.compose.foundation.text.findParagraphEnd
-import androidx.compose.foundation.text.findParagraphStart
-import androidx.compose.foundation.text.findPrecedingBreak
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Deletion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Insertion
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Replacement
-import androidx.compose.foundation.text2.input.internal.IndexTransformationType.Untransformed
-import androidx.compose.foundation.text2.input.internal.SelectionWedgeAffinity
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.foundation.text2.input.internal.WedgeAffinity
-import androidx.compose.foundation.text2.input.internal.getIndexTransformationType
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldPreparedSelection.Companion.NoCharacterFound
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.style.ResolvedTextDirection
-import kotlin.math.abs
-
-/**
- * [TextFieldPreparedSelection] provides a scope for many selection-related operations. However,
- * some vertical cursor operations like moving between lines or page up and down require a cache of
- * X position in text to remember where to move the cursor in next line.
- * [TextFieldPreparedSelection] is a disposable scope that cannot hold its own state. This class
- * helps to pass a cached X value between selection operations in different scopes.
- */
-internal class TextFieldPreparedSelectionState {
-    /**
-     * it's set at the start of vertical navigation and used as the preferred value to set a new
-     * cursor position.
-     */
-    var cachedX: Float = Float.NaN
-
-    /**
-     * Remove and forget the cached X used for vertical navigation.
-     */
-    fun resetCachedX() {
-        cachedX = Float.NaN
-    }
-}
-
-/**
- * This utility class implements many selection-related operations on text (including basic
- * cursor movements and deletions) and combines them, taking into account how the text was
- * rendered. So, for example, [moveCursorToLineEnd] moves it to the visual line end.
- *
- * For many of these operations, it's particularly important to keep the difference between
- * selection start and selection end. In some systems, they are called "anchor" and "caret"
- * respectively. For example, for selection from scratch, after [moveCursorLeftByWord]
- * [moveCursorRight] will move the left side of the selection, but after [moveCursorRightByWord]
- * the right one.
- *
- * @param state Transformed version of TextFieldState that helps to manipulate underlying buffer
- * through transformed coordinates.
- * @param textLayoutResult Visual representation of text inside [state]. Used to calculate line
- * and paragraph metrics.
- * @param visibleTextLayoutHeight Height of the visible area of text inside TextField to decide
- * where cursor needs to move when page up/down is requested.
- * @param textPreparedSelectionState An object that holds any context that needs to be long lived
- * between successive [TextFieldPreparedSelection]s, e.g. original X position of the cursor while
- * moving the cursor up/down.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal class TextFieldPreparedSelection(
-    private val state: TransformedTextFieldState,
-    private val textLayoutResult: TextLayoutResult,
-    private val visibleTextLayoutHeight: Float,
-    private val textPreparedSelectionState: TextFieldPreparedSelectionState
-) {
-    /**
-     * Read the value from state without read observation to not accidentally cause recompositions.
-     * Freezing the initial value is necessary to make atomic operations in the scope of this
-     * [TextFieldPreparedSelection]. It is also used to make comparison between the initial state
-     * and the modified state of selection and content.
-     */
-    val initialValue = Snapshot.withoutReadObservation { state.visualText }
-
-    /**
-     * Current active selection in the context of this [TextFieldPreparedSelection]
-     */
-    var selection = initialValue.selectionInChars
-
-    /**
-     * Initial text value.
-     */
-    private val text: String = initialValue.toString()
-
-    /**
-     * Deletes selected region from [state] if [selection] is not collapsed. Otherwise, deletes the
-     * range returned by [block]. If returned TextRange is null, this function does nothing.
-     */
-    inline fun deleteIfSelectedOr(block: () -> TextRange?) {
-        if (!selection.collapsed) {
-            state.replaceText("", selection)
-        } else {
-            block()?.let { state.replaceText("", it) }
-        }
-    }
-
-    /**
-     * Executes PageUp key
-     */
-    fun moveCursorUpByPage() = applyIfNotEmpty(false) {
-        setCursor(jumpByPagesOffset(-1))
-    }
-
-    /**
-     * Executes PageDown key
-     */
-    fun moveCursorDownByPage() = applyIfNotEmpty(false) {
-        setCursor(jumpByPagesOffset(1))
-    }
-
-    /**
-     * Returns a cursor position after jumping back or forth by [pagesAmount] number of pages,
-     * where `page` is the visible amount of space in the text field. Visible rectangle is
-     * calculated by the coordinates of decoration box around the TextField. If text layout has not
-     * been measured yet, this function returns the current offset.
-     */
-    private fun jumpByPagesOffset(pagesAmount: Int): Int {
-        val currentOffset = initialValue.selectionInChars.end
-        val currentPos = textLayoutResult.getCursorRect(currentOffset)
-        val newPos = currentPos.translate(
-            translateX = 0f,
-            translateY = visibleTextLayoutHeight * pagesAmount
-        )
-        // which line does the new cursor position belong?
-        val topLine = textLayoutResult.getLineForVerticalPosition(newPos.top)
-        val lineSeparator = textLayoutResult.getLineBottom(topLine)
-        return if (abs(newPos.top - lineSeparator) > abs(newPos.bottom - lineSeparator)) {
-            // most of new cursor is on top line
-            textLayoutResult.getOffsetForPosition(newPos.topLeft)
-        } else {
-            // most of new cursor is on bottom line
-            textLayoutResult.getOffsetForPosition(newPos.bottomLeft)
-        }
-    }
-
-    /**
-     * Only apply the given [block] if the text is not empty.
-     *
-     * @param resetCachedX Whether to reset the cachedX parameter in [TextFieldPreparedSelectionState].
-     */
-    private inline fun applyIfNotEmpty(
-        resetCachedX: Boolean = true,
-        block: TextFieldPreparedSelection.() -> Unit
-    ): TextFieldPreparedSelection {
-        if (resetCachedX) {
-            textPreparedSelectionState.resetCachedX()
-        }
-        if (text.isNotEmpty()) {
-            this.block()
-        }
-        return this
-    }
-
-    /**
-     * Sets a collapsed selection at given [offset].
-     */
-    private fun setCursor(offset: Int) {
-        selection = TextRange(offset, offset)
-    }
-
-    fun selectAll() = applyIfNotEmpty {
-        selection = TextRange(0, text.length)
-    }
-
-    fun deselect() = applyIfNotEmpty {
-        setCursor(selection.end)
-    }
-
-    fun moveCursorLeft() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorPrev()
-        } else {
-            moveCursorNext()
-        }
-    }
-
-    fun moveCursorRight() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorNext()
-        } else {
-            moveCursorPrev()
-        }
-    }
-
-    /**
-     * If there is already a selection, collapse it to the left side. Otherwise, execute [or]
-     */
-    fun collapseLeftOr(or: TextFieldPreparedSelection.() -> Unit) = applyIfNotEmpty {
-        if (selection.collapsed) {
-            or(this)
-        } else {
-            if (isLtr()) {
-                setCursor(selection.min)
-            } else {
-                setCursor(selection.max)
-            }
-        }
-    }
-
-    /**
-     * If there is already a selection, collapse it to the right side. Otherwise, execute [or]
-     */
-    fun collapseRightOr(or: TextFieldPreparedSelection.() -> Unit) = applyIfNotEmpty {
-        if (selection.collapsed) {
-            or(this)
-        } else {
-            if (isLtr()) {
-                setCursor(selection.max)
-            } else {
-                setCursor(selection.min)
-            }
-        }
-    }
-
-    /**
-     * Returns the index of the character break preceding the end of [selection].
-     */
-    fun getPrecedingCharacterIndex() = text.findPrecedingBreak(selection.end)
-
-    /**
-     * Returns the index of the character break following the end of [selection]. Returns
-     * [NoCharacterFound] if there are no more breaks before the end of the string.
-     */
-    fun getNextCharacterIndex() = text.findFollowingBreak(selection.end)
-
-    private fun moveCursorPrev() = applyIfNotEmpty {
-        val oldCursor = selection.end
-        val newCursor = calculateAdjacentCursorPosition(text, oldCursor, forward = false, state)
-        if (newCursor != oldCursor) {
-            setCursor(newCursor)
-        }
-    }
-
-    private fun moveCursorNext() = applyIfNotEmpty {
-        val oldCursor = selection.end
-        val newCursor = calculateAdjacentCursorPosition(text, oldCursor, forward = true, state)
-        if (newCursor != oldCursor) {
-            setCursor(newCursor)
-        }
-    }
-
-    fun moveCursorToHome() = applyIfNotEmpty {
-        setCursor(0)
-    }
-
-    fun moveCursorToEnd() = applyIfNotEmpty {
-        setCursor(text.length)
-    }
-
-    fun moveCursorLeftByWord() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorPrevByWord()
-        } else {
-            moveCursorNextByWord()
-        }
-    }
-
-    fun moveCursorRightByWord() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorNextByWord()
-        } else {
-            moveCursorPrevByWord()
-        }
-    }
-
-    fun getNextWordOffset(): Int = textLayoutResult.getNextWordOffsetForLayout()
-
-    private fun moveCursorNextByWord() = applyIfNotEmpty {
-        setCursor(getNextWordOffset())
-    }
-
-    fun getPreviousWordOffset(): Int = textLayoutResult.getPrevWordOffsetForLayout()
-
-    private fun moveCursorPrevByWord() = applyIfNotEmpty {
-        setCursor(getPreviousWordOffset())
-    }
-
-    fun moveCursorPrevByParagraph() = applyIfNotEmpty {
-        var paragraphStart = text.findParagraphStart(selection.min)
-        if (paragraphStart == selection.min && paragraphStart != 0) {
-            paragraphStart = text.findParagraphStart(paragraphStart - 1)
-        }
-        setCursor(paragraphStart)
-    }
-
-    fun moveCursorNextByParagraph() = applyIfNotEmpty {
-        var paragraphEnd = text.findParagraphEnd(selection.max)
-        if (paragraphEnd == selection.max && paragraphEnd != text.length) {
-            paragraphEnd = text.findParagraphEnd(paragraphEnd + 1)
-        }
-        setCursor(paragraphEnd)
-    }
-
-    fun moveCursorUpByLine() = applyIfNotEmpty(false) {
-        setCursor(textLayoutResult.jumpByLinesOffset(-1))
-    }
-
-    fun moveCursorDownByLine() = applyIfNotEmpty(false) {
-        setCursor(textLayoutResult.jumpByLinesOffset(1))
-    }
-
-    fun getLineStartByOffset(): Int = textLayoutResult.getLineStartByOffsetForLayout()
-
-    fun moveCursorToLineStart() = applyIfNotEmpty {
-        setCursor(getLineStartByOffset())
-    }
-
-    fun getLineEndByOffset(): Int = textLayoutResult.getLineEndByOffsetForLayout()
-
-    fun moveCursorToLineEnd() = applyIfNotEmpty {
-        setCursor(getLineEndByOffset())
-    }
-
-    fun moveCursorToLineLeftSide() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorToLineStart()
-        } else {
-            moveCursorToLineEnd()
-        }
-    }
-
-    fun moveCursorToLineRightSide() = applyIfNotEmpty {
-        if (isLtr()) {
-            moveCursorToLineEnd()
-        } else {
-            moveCursorToLineStart()
-        }
-    }
-
-    /** Selects a text from the original selection start to a current selection end. */
-    fun selectMovement() = applyIfNotEmpty(resetCachedX = false) {
-        selection = TextRange(initialValue.selectionInChars.start, selection.end)
-    }
-
-    private fun isLtr(): Boolean {
-        val direction = textLayoutResult.getParagraphDirection(selection.end)
-        return direction == ResolvedTextDirection.Ltr
-    }
-
-    private tailrec fun TextLayoutResult.getNextWordOffsetForLayout(
-        currentOffset: Int = selection.end
-    ): Int {
-        if (currentOffset >= initialValue.length) {
-            return initialValue.length
-        }
-        val currentWord = getWordBoundary(charOffset(currentOffset))
-        return if (currentWord.end <= currentOffset) {
-            getNextWordOffsetForLayout(currentOffset + 1)
-        } else {
-            currentWord.end
-        }
-    }
-
-    private tailrec fun TextLayoutResult.getPrevWordOffsetForLayout(
-        currentOffset: Int = selection.end
-    ): Int {
-        if (currentOffset <= 0) {
-            return 0
-        }
-        val currentWord = getWordBoundary(charOffset(currentOffset))
-        return if (currentWord.start >= currentOffset) {
-            getPrevWordOffsetForLayout(currentOffset - 1)
-        } else {
-            currentWord.start
-        }
-    }
-
-    private fun TextLayoutResult.getLineStartByOffsetForLayout(
-        currentOffset: Int = selection.min
-    ): Int {
-        val currentLine = getLineForOffset(currentOffset)
-        return getLineStart(currentLine)
-    }
-
-    private fun TextLayoutResult.getLineEndByOffsetForLayout(
-        currentOffset: Int = selection.max
-    ): Int {
-        val currentLine = getLineForOffset(currentOffset)
-        return getLineEnd(currentLine, true)
-    }
-
-    private fun TextLayoutResult.jumpByLinesOffset(linesAmount: Int): Int {
-        val currentOffset = selection.end
-
-        if (textPreparedSelectionState.cachedX.isNaN()) {
-            textPreparedSelectionState.cachedX = getCursorRect(currentOffset).left
-        }
-
-        val targetLine = getLineForOffset(currentOffset) + linesAmount
-        when {
-            targetLine < 0 -> {
-                return 0
-            }
-
-            targetLine >= lineCount -> {
-                return text.length
-            }
-        }
-
-        val y = getLineBottom(targetLine) - 1
-        val x = textPreparedSelectionState.cachedX.also {
-            if ((isLtr() && it >= getLineRight(targetLine)) ||
-                (!isLtr() && it <= getLineLeft(targetLine))
-            ) {
-                return getLineEnd(targetLine, true)
-            }
-        }
-
-        return getOffsetForPosition(Offset(x, y))
-    }
-
-    private fun charOffset(offset: Int) = offset.coerceAtMost(text.length - 1)
-
-    companion object {
-        /**
-         * Value returned by [getNextCharacterIndex] and [getPrecedingCharacterIndex] when no valid
-         * index could be found, e.g. it would be the end of the string.
-         *
-         * This is equivalent to `BreakIterator.DONE` on JVM/Android.
-         */
-        const val NoCharacterFound = -1
-    }
-}
-
-/**
- * Given some transformed text and the current cursor offset in that text, calculates the offset of
- * the nearest next position of the cursor in the transformed text. Takes into account text
- * transformations ([TransformedTextFieldState]) to avoid putting the cursor in the middle of
- * replacements.
- */
-@VisibleForTesting
-internal fun calculateAdjacentCursorPosition(
-    transformedText: String,
-    cursor: Int,
-    forward: Boolean,
-    state: TransformedTextFieldState,
-): Int {
-    // First step: find the index of the next cursor position in the visual text. In most cases this
-    // will be the final result, however if transformations are applied we may need to jump the
-    // cursor forward or backward.
-    val proposedCursor = if (forward) {
-        transformedText.findFollowingBreak(cursor)
-    } else {
-        transformedText.findPrecedingBreak(cursor)
-    }
-    if (proposedCursor == NoCharacterFound) {
-        // At the start or end of the text, no change.
-        return cursor
-    }
-
-    // Second step: if a transformation is applied, determine if the proposed cursor position would
-    // be in a range where the cursor is not allowed to be. If so, push it to the appropriate edge
-    // of that range.
-    return state.getIndexTransformationType(proposedCursor) { type, _, retransformed ->
-        when (type) {
-            Untransformed -> proposedCursor
-
-            // It doesn't matter which end of the deleted range we put the cursor, they'll both map
-            // to the same transformed offset.
-            Deletion -> proposedCursor
-
-            // Moving forward into a replacement means we should jump to the end, moving backwards
-            // into it means jump to the start.
-            Replacement -> if (forward) retransformed.end else retransformed.start
-
-            // Moving into an insertion is like a replacement in that the cursor may only be placed
-            // on either edge of the range. However, since both edges of the range map to the same
-            // untransformed index, we need to set the affinity.
-            Insertion -> {
-                if (forward) {
-                    if (proposedCursor == retransformed.start) {
-                        // Moving to start of wedge, update affinity and set cursor.
-                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.Start)
-                        return proposedCursor
-                    } else {
-                        // Moving to middle or end of wedge, update affinity but don't need to move
-                        // cursor.
-                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.End)
-                        // No offset change.
-                        cursor
-                    }
-                } else {
-                    // We're navigating to or within a wedge. Use affinity (doesn't matter which
-                    // one, selection is a cursor).
-                    if (proposedCursor == retransformed.end) {
-                        // Moving to end of wedge, update affinity and set cursor.
-                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.End)
-                        return proposedCursor
-                    } else {
-                        // Moving to middle or start of wedge, update affinity but don't need to
-                        // move cursor.
-                        state.selectionWedgeAffinity = SelectionWedgeAffinity(WedgeAffinity.Start)
-                        // No offset change.
-                        return cursor
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt
deleted file mode 100644
index ce572cb4..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.timeNowMillis
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.ui.text.TextRange
-
-/**
- * An undo identifier designed for text editors. Defines a single atomic change that can be applied
- * directly or in reverse to modify the contents of a text editor.
- *
- * @param index Start point of [preText] and [postText].
- * @param preText Previously written text that's deleted starting from [index].
- * @param postText New text that's inserted at [index]
- * @param preSelection Previous selection before changes are applied
- * @param postSelection New selection after changes are applied
- * @param timeInMillis When did this change was first committed
- * @param canMerge Whether this change can be merged with the next or previous change in an undo
- * stack. There are many other rules that affect the merging strategy between two
- * [TextUndoOperation]s but this flag is a sure way to force a non-mergeable property.
- */
-internal class TextUndoOperation(
-    val index: Int,
-    val preText: String,
-    val postText: String,
-    val preSelection: TextRange,
-    val postSelection: TextRange,
-    val timeInMillis: Long = timeNowMillis(),
-    val canMerge: Boolean = true
-) {
-
-    /**
-     * What kind of edit operation is defined by this change. Edit type is decided by forward the
-     * behavior of this change in forward direction (pre -> post).
-     */
-    val textEditType: TextEditType = when {
-        preText.isEmpty() && postText.isEmpty() ->
-            throw IllegalArgumentException("Either pre or post text must not be empty")
-
-        preText.isEmpty() && postText.isNotEmpty() -> TextEditType.Insert
-        preText.isNotEmpty() && postText.isEmpty() -> TextEditType.Delete
-        else -> TextEditType.Replace
-    }
-
-    /**
-     * Only required while deciding whether to merge two deletion type undo operations.
-     */
-    val deletionType: TextDeleteType
-        get() {
-            if (textEditType != TextEditType.Delete) return TextDeleteType.NotByUser
-            if (!postSelection.collapsed) return TextDeleteType.NotByUser
-            if (preSelection.collapsed) {
-                return if (preSelection.start > postSelection.start) {
-                    TextDeleteType.Start
-                } else {
-                    TextDeleteType.End
-                }
-            } else if (preSelection.start == postSelection.start && preSelection.start == index) {
-                return TextDeleteType.Inner
-            }
-            return TextDeleteType.NotByUser
-        }
-
-    companion object {
-
-        val Saver = object : Saver<TextUndoOperation, Any> {
-            override fun SaverScope.save(value: TextUndoOperation): Any = listOf(
-                value.index,
-                value.preText,
-                value.postText,
-                value.preSelection.start,
-                value.preSelection.end,
-                value.postSelection.start,
-                value.postSelection.end,
-                value.timeInMillis
-            )
-
-            override fun restore(value: Any): TextUndoOperation {
-                return with((value as List<*>)) {
-                    TextUndoOperation(
-                        index = get(0) as Int,
-                        preText = get(1) as String,
-                        postText = get(2) as String,
-                        preSelection = TextRange(get(3) as Int, get(4) as Int),
-                        postSelection = TextRange(get(5) as Int, get(6) as Int),
-                        timeInMillis = get(7) as Long,
-                    )
-                }
-            }
-        }
-    }
-}
-
-/**
- * Apply a given [TextUndoOperation] in reverse to undo this [TextFieldState].
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldState.undo(op: TextUndoOperation) {
-    editWithNoSideEffects {
-        replace(op.index, op.index + op.postText.length, op.preText)
-        setSelection(op.preSelection.start, op.preSelection.end)
-    }
-}
-
-/**
- * Apply a given [TextUndoOperation] in forward direction to redo this [TextFieldState].
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldState.redo(op: TextUndoOperation) {
-    editWithNoSideEffects {
-        replace(op.index, op.index + op.preText.length, op.postText)
-        setSelection(op.postSelection.start, op.postSelection.end)
-    }
-}
-
-/**
- * Possible types of a text operation.
- *
- * 1. Insert; if the edited range has 0 length, and the new text is longer than 0 length
- * 2. Delete: if the edited range is longer than 0, and the new text has 0 length
- * 3. Replace: All other changes.
- */
-internal enum class TextEditType {
-    Insert, Delete, Replace
-}
-
-/**
- * When a delete occurs during text editing, it can happen in various shapes.
- *
- * 1. Start; When a single character is removed to the start (towards 0) of the cursor, backspace
- * key behavior.
- *   "abcd|efg" -> "abc|efg"
- * 2. End; When a single character is removed to the end (towards length) of the cursor, delete
- * key behavior.
- *   "abcd|efg" -> "abcd|fg"
- * 3. Inner; When a selection of characters are removed, directionless. Both backspace and delete
- * express the same behavior in this case.
- *   "ab|cde|fg" -> "ab|fg"
- * 4. NotByUser; A text editing operation that cannot be executed via a hardware or software
- * keyboard. For example when a portion of text is removed but it's not next to a cursor or
- * selection, or selection remains after removal.
- *   "abcd|efg"  -> "bcd|efg"
- *   "abc|def|g" -> "a|bc|g"
- */
-internal enum class TextDeleteType {
-    Start, End, Inner, NotByUser
-}
-
-/**
- * There are multiple strategies while deciding how to add certain edit operations to undo stack.
- *   - Normally, merge is decided by UndoOperation's own merge logic, comparing itself to the
- *   latest operation in the Undo stack.
- *   - Programmatic updates should clear the history completely.
- *   - Some atomic actions like cut, and paste shouldn't merge to previous or next actions.
- */
-internal enum class TextFieldEditUndoBehavior {
-    MergeIfPossible,
-    ClearHistory,
-    NeverMerge
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt
deleted file mode 100644
index f127be51..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.undo
-
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.runtime.snapshots.SnapshotStateList
-import androidx.compose.ui.util.fastForEach
-
-/**
- * A generic purpose undo/redo stack manager.
- *
- * @param initialUndoStack Previous undo stack if this manager is being restored from a saved state.
- * @param initialRedoStack Previous redo stack if this manager is being restored from a saved state.
- * @param capacity Maximum number of elements that can be hosted by this UndoManager. Total element
- * count is the sum of undo and redo stack sizes.
- */
-internal class UndoManager<T>(
-    initialUndoStack: List<T> = emptyList(),
-    initialRedoStack: List<T> = emptyList(),
-    private val capacity: Int = 100
-) {
-
-    private var undoStack = SnapshotStateList<T>().apply {
-        addAll(initialUndoStack)
-    }
-    private var redoStack = SnapshotStateList<T>().apply {
-        addAll(initialRedoStack)
-    }
-
-    internal val canUndo: Boolean
-        get() = undoStack.isNotEmpty()
-
-    internal val canRedo: Boolean
-        get() = redoStack.isNotEmpty()
-
-    val size: Int
-        get() = undoStack.size + redoStack.size
-
-    init {
-        require(capacity >= 0) {
-            "Capacity must be a positive integer"
-        }
-        require(size <= capacity) {
-            "Initial list of undo and redo operations have a size=($size) greater " +
-                "than the given capacity=($capacity)."
-        }
-    }
-
-    fun record(undoableAction: T) {
-        // First clear the redo stack.
-        redoStack.clear()
-
-        while (size > capacity - 1) { // leave room for the immediate `add`
-            undoStack.removeFirst()
-        }
-        undoStack.add(undoableAction)
-    }
-
-    /**
-     * Request undo.
-     *
-     * This method returns the item that was on top of the undo stack. By the time this function
-     * returns, the given item has already been carried to the redo stack.
-     */
-    fun undo(): T {
-        check(canUndo) {
-            "It's an error to call undo while there is nothing to undo. " +
-                "Please first check `canUndo` value before calling the `undo` function."
-        }
-
-        val topOperation = undoStack.removeLast()
-
-        redoStack.add(topOperation)
-        return topOperation
-    }
-
-    /**
-     * Request redo.
-     *
-     * This method returns the item that was on top of the redo stack. By the time this function
-     * returns, the given item has already been carried back to the undo stack.
-     */
-    fun redo(): T {
-        check(canRedo) {
-            "It's an error to call redo while there is nothing to redo. " +
-                "Please first check `canRedo` value before calling the `redo` function."
-        }
-
-        val topOperation = redoStack.removeLast()
-
-        undoStack.add(topOperation)
-        return topOperation
-    }
-
-    fun clearHistory() {
-        undoStack.clear()
-        redoStack.clear()
-    }
-
-    companion object {
-
-        /**
-         * Saver factory for a generic [UndoManager].
-         *
-         * @param itemSaver Since [UndoManager] is defined as a generic class, a specific item saver
-         * is required to _serialize_ each individual item in undo and redo stacks.
-         */
-        inline fun <reified T> createSaver(
-            itemSaver: Saver<T, Any>
-        ) = object : Saver<UndoManager<T>, Any> {
-            /**
-             * Saves the contents of given [value] to a list.
-             *
-             * List's structure is
-             *   - Capacity
-             *   - n; Number of items in undo stack
-             *   - m; Number of items in redo stack
-             *   - n items in order from undo stack
-             *   - m items in order from redo stack
-             */
-            override fun SaverScope.save(value: UndoManager<T>): Any = buildList {
-                add(value.capacity)
-                add(value.undoStack.size)
-                add(value.redoStack.size)
-                value.undoStack.fastForEach {
-                    with(itemSaver) {
-                        add(save(it))
-                    }
-                }
-                value.redoStack.fastForEach {
-                    with(itemSaver) {
-                        add(save(it))
-                    }
-                }
-            }
-
-            @Suppress("UNCHECKED_CAST")
-            override fun restore(value: Any): UndoManager<T> {
-                val list = value as List<Any>
-                val (capacity, undoSize, redoSize) = (list as List<Int>)
-                var i = 3
-                val undoStackItems = buildList {
-                    while (i < undoSize + 3) {
-                        add(itemSaver.restore(list[i])!!)
-                        i++
-                    }
-                }
-                val redoStackItems = buildList {
-                    while (i < undoSize + redoSize + 3) {
-                        add(itemSaver.restore(list[i])!!)
-                        i++
-                    }
-                }
-                return UndoManager(undoStackItems, redoStackItems, capacity)
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/DesktopTextInputSession.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/DesktopTextInputSession.desktop.kt
new file mode 100644
index 0000000..1c55c8a
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/DesktopTextInputSession.desktop.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
+import androidx.compose.ui.platform.PlatformTextInputSession
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import kotlinx.coroutines.awaitCancellation
+
+/**
+ * Runs desktop-specific text input session logic.
+ */
+internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
+    state: TransformedTextFieldState,
+    layoutState: TextLayoutState,
+    imeOptions: ImeOptions,
+    receiveContentConfiguration: ReceiveContentConfiguration?,
+    onImeAction: ((ImeAction) -> Unit)?
+): Nothing {
+    // TODO(b/267235947) Wire up desktop.
+    awaitCancellation()
+}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.desktop.kt
new file mode 100644
index 0000000..1a42b60
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.desktop.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.content.MediaType
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropModifierNode
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.ClipMetadata
+
+/**
+ * System DragAndDrop is not yet supported on Desktop flavor of BTF2.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal actual fun textFieldDragAndDropNode(
+    hintMediaTypes: () -> Set<MediaType>,
+    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
+    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
+    onStarted: ((event: DragAndDropEvent) -> Unit)?,
+    onEntered: ((event: DragAndDropEvent) -> Unit)?,
+    onMoved: ((position: Offset) -> Unit)?,
+    onChanged: ((event: DragAndDropEvent) -> Unit)?,
+    onExited: ((event: DragAndDropEvent) -> Unit)?,
+    onEnded: ((event: DragAndDropEvent) -> Unit)?,
+): DragAndDropModifierNode {
+    return DragAndDropModifierNode()
+}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.desktop.kt
new file mode 100644
index 0000000..b9da0aa9
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.desktop.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.foundation.text.input.internal
+
+/**
+ * Factory function to create a platform specific [TextFieldKeyEventHandler].
+ */
+internal actual fun createTextFieldKeyEventHandler() = object : TextFieldKeyEventHandler() {}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.desktop.kt
new file mode 100644
index 0000000..543f44b
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.desktop.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.TextFieldCharSequence
+import androidx.compose.foundation.text.input.toCharArray
+
+@OptIn(ExperimentalFoundationApi::class)
+internal actual fun CharSequence.toCharArray(
+    destination: CharArray,
+    destinationOffset: Int,
+    startIndex: Int,
+    endIndex: Int
+) {
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    when (this) {
+        is TextFieldCharSequence -> toCharArray(
+            destination,
+            destinationOffset,
+            startIndex,
+            endIndex
+        )
+
+        is java.lang.String -> getChars(startIndex, endIndex, destination, destinationOffset)
+        is StringBuilder -> getChars(startIndex, endIndex, destination, destinationOffset)
+        else -> {
+            require(startIndex in indices && endIndex in 0..length) {
+                "Expected source [$startIndex, $endIndex) to be in [0, $length)"
+            }
+            val copyLength = endIndex - startIndex
+            require(
+                destinationOffset in destination.indices &&
+                    destinationOffset + copyLength in 0..destination.size
+            ) {
+                "Expected destination [$destinationOffset, ${destinationOffset + copyLength}) " +
+                    "to be in [0, ${destination.size})"
+            }
+
+            for (i in 0 until copyLength) {
+                destination[destinationOffset + i] = get(startIndex + i)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
new file mode 100644
index 0000000..76d9d25
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal.selection
+
+import androidx.compose.foundation.text.input.internal.TextLayoutState
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
+
+/**
+ * There is no magnifier on Desktop. Return a noop [TextFieldMagnifierNode] implementation.
+ */
+internal actual fun textFieldMagnifierNode(
+    textFieldState: TransformedTextFieldState,
+    textFieldSelectionState: TextFieldSelectionState,
+    textLayoutState: TextLayoutState,
+    visible: Boolean
+) = object : TextFieldMagnifierNode() {
+    override fun update(
+        textFieldState: TransformedTextFieldState,
+        textFieldSelectionState: TextFieldSelectionState,
+        textLayoutState: TextLayoutState,
+        visible: Boolean
+    ) {}
+}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt
deleted file mode 100644
index 028fe3e..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
-import androidx.compose.ui.platform.PlatformTextInputSession
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.ImeOptions
-import kotlinx.coroutines.awaitCancellation
-
-/**
- * Runs desktop-specific text input session logic.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TransformedTextFieldState,
-    layoutState: TextLayoutState,
-    imeOptions: ImeOptions,
-    receiveContentConfiguration: ReceiveContentConfiguration?,
-    onImeAction: ((ImeAction) -> Unit)?
-): Nothing {
-    // TODO(b/267235947) Wire up desktop.
-    awaitCancellation()
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.desktop.kt
deleted file mode 100644
index da21b3b..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDragAndDropNode.desktop.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
-import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.draganddrop.DragAndDropModifierNode
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.ClipEntry
-import androidx.compose.ui.platform.ClipMetadata
-
-/**
- * System DragAndDrop is not yet supported on Desktop flavor of BTF2.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal actual fun textFieldDragAndDropNode(
-    hintMediaTypes: () -> Set<MediaType>,
-    onDrop: (clipEntry: ClipEntry, clipMetadata: ClipMetadata) -> Boolean,
-    dragAndDropRequestPermission: (DragAndDropEvent) -> Unit,
-    onStarted: ((event: DragAndDropEvent) -> Unit)?,
-    onEntered: ((event: DragAndDropEvent) -> Unit)?,
-    onMoved: ((position: Offset) -> Unit)?,
-    onChanged: ((event: DragAndDropEvent) -> Unit)?,
-    onExited: ((event: DragAndDropEvent) -> Unit)?,
-    onEnded: ((event: DragAndDropEvent) -> Unit)?,
-): DragAndDropModifierNode {
-    return DragAndDropModifierNode()
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.desktop.kt
deleted file mode 100644
index 78a357e..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.desktop.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-/**
- * Factory function to create a platform specific [TextFieldKeyEventHandler].
- */
-internal actual fun createTextFieldKeyEventHandler() = object : TextFieldKeyEventHandler() {}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.desktop.kt
deleted file mode 100644
index 2e2dd1c..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.desktop.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.toCharArray
-
-@OptIn(ExperimentalFoundationApi::class)
-internal actual fun CharSequence.toCharArray(
-    destination: CharArray,
-    destinationOffset: Int,
-    startIndex: Int,
-    endIndex: Int
-) {
-    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-    when (this) {
-        is TextFieldCharSequence -> toCharArray(
-            destination,
-            destinationOffset,
-            startIndex,
-            endIndex
-        )
-
-        is java.lang.String -> getChars(startIndex, endIndex, destination, destinationOffset)
-        is StringBuilder -> getChars(startIndex, endIndex, destination, destinationOffset)
-        else -> {
-            require(startIndex in indices && endIndex in 0..length) {
-                "Expected source [$startIndex, $endIndex) to be in [0, $length)"
-            }
-            val copyLength = endIndex - startIndex
-            require(
-                destinationOffset in destination.indices &&
-                    destinationOffset + copyLength in 0..destination.size
-            ) {
-                "Expected destination [$destinationOffset, ${destinationOffset + copyLength}) " +
-                    "to be in [0, ${destination.size})"
-            }
-
-            for (i in 0 until copyLength) {
-                destination[destinationOffset + i] = get(startIndex + i)
-            }
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
deleted file mode 100644
index 269bce7..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.text2.input.internal.TextLayoutState
-import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-
-/**
- * There is no magnifier on Desktop. Return a noop [TextFieldMagnifierNode] implementation.
- */
-internal actual fun textFieldMagnifierNode(
-    textFieldState: TransformedTextFieldState,
-    textFieldSelectionState: TextFieldSelectionState,
-    textLayoutState: TextLayoutState,
-    visible: Boolean
-) = object : TextFieldMagnifierNode() {
-    override fun update(
-        textFieldState: TransformedTextFieldState,
-        textFieldSelectionState: TextFieldSelectionState,
-        textLayoutState: TextLayoutState,
-        visible: Boolean
-    ) {}
-}
diff --git a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.jvm.kt b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.jvm.kt
new file mode 100644
index 0000000..c08bce1
--- /dev/null
+++ b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text/input/internal/CodepointHelpers.jvm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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.compose.foundation.text.input.internal
+
+internal actual fun CharSequence.codePointAt(index: Int): Int =
+    java.lang.Character.codePointAt(this, index)
+
+internal actual fun CharSequence.codePointCount(): Int =
+    java.lang.Character.codePointCount(this, 0, length)
+
+internal actual fun charCount(codePoint: Int): Int =
+    java.lang.Character.charCount(codePoint)
diff --git a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt
deleted file mode 100644
index 95a0458..0000000
--- a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2023 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.compose.foundation.text2.input.internal
-
-internal actual fun CharSequence.codePointAt(index: Int): Int =
-    java.lang.Character.codePointAt(this, index)
-
-internal actual fun CharSequence.codePointCount(): Int =
-    java.lang.Character.codePointCount(this, 0, length)
-
-internal actual fun charCount(codePoint: Int): Int =
-    java.lang.Character.charCount(codePoint)
diff --git a/compose/lint/common/build.gradle b/compose/lint/common/build.gradle
index 844c29b..9fdd9c8 100644
--- a/compose/lint/common/build.gradle
+++ b/compose/lint/common/build.gradle
@@ -32,7 +32,7 @@
     compileOnly(libs.androidLintMinComposeApi)
     compileOnly(libs.kotlinStdlib)
 
-    api(libs.kotlinMetadataJvm)
+    api(libs.kotlinMetadataJvm) { exclude group: "org.jetbrains.kotlin" }
 
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.androidLint)
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
index cc8ef54..0cdf01c 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -263,7 +263,7 @@
 /**
  * Returns whether this type reference is @Composable or not
  */
-private val UTypeReferenceExpression.isComposable: Boolean
+val UTypeReferenceExpression.isComposable: Boolean
     get() {
         if (type.hasAnnotation(Names.Runtime.Composable.javaFqn)) return true
 
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
index 2d470e0..f34baf9 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
@@ -55,7 +55,7 @@
             val methodName = node.methodName ?: return
             if (methodName in MethodNames) {
                 val receiverType = node.receiverType as? PsiClassReferenceType ?: return
-                val qualifiedName = receiverType.reference.qualifiedName ?: return
+                val qualifiedName = receiverType.canonicalText
                 val indexOfAngleBracket = qualifiedName.indexOf('<')
                 if (indexOfAngleBracket > 0 &&
                     qualifiedName.substring(0, indexOfAngleBracket) in CollectionClasses
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
index b84882b..41c356b 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
@@ -27,13 +27,20 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.intellij.psi.impl.source.PsiClassReferenceType
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.calls.KtSimpleFunctionCall
+import org.jetbrains.kotlin.analysis.api.calls.singleFunctionCallOrNull
+import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType
 import org.jetbrains.kotlin.psi.KtCallElement
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.util.OperatorNameConventions
 import org.jetbrains.uast.UBlockExpression
 import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.ULambdaExpression
+import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UReturnExpression
-import org.jetbrains.uast.UUnknownExpression
 import org.jetbrains.uast.UVariable
 import org.jetbrains.uast.skipParenthesizedExprDown
 import org.jetbrains.uast.toUElement
@@ -66,18 +73,13 @@
      * This handler visits every lambda expression and reports an issue if the following criteria
      * (in order) hold true:
      *
-     * 1. There is only one expression inside the lambda.
-     * 2. The expression is a function call
-     * 3. The lambda is being invoked as part of a function call, and not as a property assignment
+     * 1. There is only one expression inside the lambda
+     * 2. The lambda literal is created as part of a function call, and not as a property assignment
      *    such as val foo = @Composable {}
-     * 4. The receiver type of the function call is `Function0` (i.e, we are invoking something
-     *    that matches `() -> Unit` - this both avoids non-lambda invocations but also makes sure
-     *    that we don't warn for lambdas that have parameters, such as @Composable (Int) -> Unit
-     *    - this cannot be inlined.)
-     * 5. The outer function call that contains this lambda is not a call to a `LayoutNode`
-     *    (because these are technically constructor invocations that we just intercept calls to
-     *    there is no way to avoid using a trailing lambda for this)
-     * 6. The lambda is not being passed as a parameter, for example `Foo { lambda -> lambda() }`
+     * 3. The expression is an invoke() call
+     * 4. The receiver type of the invoke call is a functional type, and it is a subtype of (i.e
+     * compatible to cast to) the lambda parameter functional type
+     * 5. The lambda parameter and literal have matching composability
      */
     class UnnecessaryLambdaCreationHandler(private val context: JavaContext) : UElementHandler() {
 
@@ -90,7 +92,7 @@
                 is UCallExpression -> expr
                 is UReturnExpression -> {
                     if (expr.sourcePsi == null) { // implicit return
-                        expr.returnExpression as? UCallExpression
+                        expr.returnExpression?.skipParenthesizedExprDown() as? UCallExpression
                     } else null
                 }
                 else -> null
@@ -108,18 +110,19 @@
             // below will be Function0 even if in the actual source it has a scope. Return early to
             // avoid false positives.
             parentExpression.resolve() ?: return
-
-            // If the expression has no receiver, it is not a lambda invocation
-            val functionType = expression.receiverType as? PsiClassReferenceType ?: return
-
-            // Find the functional type of the parent argument, for example () -> Unit (Function0)
-            val argumentType = node.getExpressionType() as? PsiClassReferenceType ?: return
+            val resolved = expression.resolve() ?: return
+            if (resolved.name != OperatorNameConventions.INVOKE.identifier) return
 
             // Return if the receiver of the lambda argument and the lambda itself don't match. This
             // happens if the functional types are different, for example a lambda with 0 parameters
             // (Function0) and a lambda with 1 parameter (Function1). Similarly for two lambdas
             // with 0 parameters, but one that has a receiver scope (SomeScope.() -> Unit).
-            if (functionType != argumentType) return
+            val expressionSourcePsi = expression.sourcePsi as? KtCallElement ?: return
+            analyze(expressionSourcePsi) {
+                val functionType = dispatchReceiverType(expressionSourcePsi) ?: return
+                val argumentType = toLambdaFunctionalType(node) ?: return
+                if (!(functionType isSubTypeOf argumentType)) return
+            }
 
             val expectedComposable = node.isComposable
 
@@ -127,17 +130,24 @@
             val sourcePsi = expression.sourcePsi as? KtCallElement ?: return
             val resolvedLambdaSource = sourcePsi.calleeExpression?.toUElement()
                 ?.tryResolve()?.toUElement()
-                // Sometimes the above will give us a method (representing the getter for a
+                // Sometimes the above will give us a method (representing the getter for a`
                 // property), when the actual backing element is a property. Going to the source
                 // and back should give us the actual UVariable we are looking for.
                 ?.sourcePsi.toUElement()
 
             val isComposable = when (resolvedLambdaSource) {
                 is UVariable -> resolvedLambdaSource.isComposable
-                // TODO: if the resolved source is a parameter in a local function, it
-                //  incorrectly returns an UnknownKotlinExpression instead of a UParameter
-                //  https://youtrack.jetbrains.com/issue/KTIJ-19125
-                is UUnknownExpression -> return
+                // If the source is a method, then the lambda is the return type of the method, so
+                // check the return type
+                is UMethod -> resolvedLambdaSource.returnTypeReference?.isComposable == true
+                // Safe return if we failed to resolve. This can happen for implicit `it` parameters
+                // that are lambdas, but this should only happen exceptionally for lambdas with
+                // an `Any` parameter, such as { any: Any -> }.let { it(Any()) }, since this passes
+                // the isSubTypeOf check above. In this case it isn't possible to inline this call,
+                // so no need to handle these implicit parameters.
+                null -> return
+                // Throw since this is an internal check, and we want to fix this for unknown types.
+                // If making this check public, it's safer to return instead without throwing.
                 else -> error(parentExpression.asSourceString())
             }
 
@@ -146,7 +156,7 @@
             context.report(
                 ISSUE,
                 node,
-                context.getNameLocation(expression),
+                context.getNameLocation(expression as UElement),
                 "Creating an unnecessary lambda to emit a captured lambda"
             )
         }
@@ -171,3 +181,18 @@
         )
     }
 }
+
+private fun KtAnalysisSession.dispatchReceiverType(callElement: KtCallElement): KtFunctionalType? =
+    callElement.resolveCall()
+        ?.singleFunctionCallOrNull()
+        ?.takeIf { it is KtSimpleFunctionCall && it.isImplicitInvoke }
+        ?.partiallyAppliedSymbol
+        ?.dispatchReceiver
+        ?.type as? KtFunctionalType
+
+private fun KtAnalysisSession.toLambdaFunctionalType(
+    lambdaExpression: ULambdaExpression
+): KtFunctionalType? {
+    val sourcePsi = lambdaExpression.sourcePsi as? KtLambdaExpression ?: return null
+    return sourcePsi.getKtType() as? KtFunctionalType
+}
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
index c512441..1c026c7 100644
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
@@ -363,5 +363,59 @@
         """
         ).expectClean()
     }
+
+    @Test
+    fun warnsForFunctionsReturningALambda() {
+        check(
+            """
+            package test
+
+            import androidx.compose.runtime.Composable
+
+            fun returnsLambda(): () -> Unit = {}
+            fun returnsComposableLambda(): @Composable () -> Unit = {}
+
+            @Composable
+            fun Test() {
+                ComposableFunction {
+                    returnsLambda()()
+                }
+
+                InlineComposableFunction {
+                    returnsLambda()()
+                }
+
+                ReifiedComposableFunction<Any> {
+                    returnsLambda()()
+                }
+
+                ComposableFunction {
+                    returnsComposableLambda()()
+                }
+
+                InlineComposableFunction {
+                    returnsComposableLambda()()
+                }
+
+                ReifiedComposableFunction<Any> {
+                    returnsComposableLambda()()
+                }
+            }
+        """
+        ).expect(
+            """
+src/test/test.kt:23: Error: Creating an unnecessary lambda to emit a captured lambda [UnnecessaryLambdaCreation]
+        returnsComposableLambda()()
+                                 ~
+src/test/test.kt:27: Error: Creating an unnecessary lambda to emit a captured lambda [UnnecessaryLambdaCreation]
+        returnsComposableLambda()()
+                                 ~
+src/test/test.kt:31: Error: Creating an unnecessary lambda to emit a captured lambda [UnnecessaryLambdaCreation]
+        returnsComposableLambda()()
+                                 ~
+3 errors, 0 warnings
+        """
+        )
+    }
 }
 /* ktlint-enable max-line-length */
diff --git a/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleContainerTest.kt b/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleContainerTest.kt
index fcb8d67..6eb6718 100644
--- a/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleContainerTest.kt
+++ b/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleContainerTest.kt
@@ -16,11 +16,36 @@
 
 package androidx.compose.material.ripple
 
+import android.content.Context
+import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.activity.ComponentActivity
+import androidx.compose.foundation.IndicationNodeFactory
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.core.view.children
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -143,9 +168,121 @@
             Truth.assertThat(hostView6).isNotEqualTo(hostView5)
         }
     }
+
+    private object TestRipple : IndicationNodeFactory {
+        override fun create(interactionSource: InteractionSource): DelegatableNode {
+            return createRippleModifierNode(
+                interactionSource = interactionSource,
+                bounded = true,
+                radius = Dp.Unspecified,
+                color = { Color.Red },
+                rippleAlpha = { RippleAlpha(0.2f, 0.2f, 0.2f, 0.2f) }
+            )
+        }
+
+        override fun equals(other: Any?) = other === this
+
+        override fun hashCode(): Int = -1
+    }
+
+    @Test
+    fun addingRippleContainerDoesNotCauseRelayout() {
+        val requestLayoutTrackingFrameLayout = RequestLayoutTrackingFrameLayout(rule.activity)
+        val composeView = ComposeView(rule.activity)
+        val interactionSource1 = MutableInteractionSource()
+        val interactionSource2 = MutableInteractionSource()
+
+        var androidComposeView: ViewGroup? = null
+        var scope: CoroutineScope? = null
+
+        rule.runOnUiThread {
+            requestLayoutTrackingFrameLayout.addView(composeView)
+            rule.activity.setContentView(requestLayoutTrackingFrameLayout)
+            composeView.setContent {
+                scope = rememberCoroutineScope()
+                androidComposeView = LocalView.current as ViewGroup
+                Column {
+                    Box(
+                        Modifier
+                            .wrapContentSize(align = Alignment.Center)
+                            .size(40.dp)
+                            .indication(
+                                interactionSource = interactionSource1,
+                                indication = TestRipple
+                            )
+                    )
+                    Box(
+                        Modifier
+                            .wrapContentSize(align = Alignment.Center)
+                            .size(40.dp)
+                            .indication(
+                                interactionSource = interactionSource2,
+                                indication = TestRipple
+                            )
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // RippleContainer should be lazily added
+            val children = androidComposeView!!.children
+            val hasRippleContainer = children.filterIsInstance<RippleContainer>().any()
+            Truth.assertThat(hasRippleContainer).isFalse()
+            Truth.assertThat(requestLayoutTrackingFrameLayout.requestLayoutCalled).isTrue()
+            // reset tracking
+            requestLayoutTrackingFrameLayout.requestLayoutCalled = false
+
+            // Emit press on first ripple
+            scope!!.launch {
+                interactionSource1.emit(PressInteraction.Press(Offset.Zero))
+            }
+        }
+
+        rule.runOnIdle {
+            // Emitting the interaction should cause us to create the ripple container and initial
+            // host view without triggering a relayout
+            val children = androidComposeView!!.children
+            val rippleContainer = children.filterIsInstance<RippleContainer>().singleOrNull()
+            Truth.assertThat(rippleContainer).isNotNull()
+            val rippleHostView = rippleContainer!!
+                .children
+                .filterIsInstance<RippleHostView>()
+                .singleOrNull()
+            Truth.assertThat(rippleHostView).isNotNull()
+            Truth.assertThat(requestLayoutTrackingFrameLayout.requestLayoutCalled).isFalse()
+
+            // Emit press on second ripple
+            scope!!.launch {
+                interactionSource2.emit(PressInteraction.Press(Offset.Zero))
+            }
+        }
+
+        rule.runOnIdle {
+            // Emitting another interaction should cause us to create an addition host view
+            // without triggering a relayout
+            val children = androidComposeView!!.children
+            val rippleContainer = children.filterIsInstance<RippleContainer>().singleOrNull()
+            Truth.assertThat(rippleContainer).isNotNull()
+            val rippleHostViews = rippleContainer!!
+                .children
+                .filterIsInstance<RippleHostView>().toList()
+            Truth.assertThat(rippleHostViews.size).isEqualTo(2)
+            Truth.assertThat(requestLayoutTrackingFrameLayout.requestLayoutCalled).isFalse()
+        }
+    }
 }
 
 private class TestRippleHostKey : RippleHostKey {
     override fun onResetRippleHostView() {
     }
 }
+
+private class RequestLayoutTrackingFrameLayout(context: Context) : FrameLayout(context) {
+    var requestLayoutCalled = false
+
+    override fun requestLayout() {
+        super.requestLayout()
+        requestLayoutCalled = true
+    }
+}
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
index b69c886..30c28cb 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
@@ -87,6 +87,11 @@
         setMeasuredDimension(0, 0)
     }
 
+    @Suppress("MissingSuperCall")
+    override fun requestLayout() {
+        // RippleHostViews don't partake in layout, and shouldn't invalidate layout
+    }
+
     /**
      * @return a [RippleHostView] for [this] [RippleHostKey]. This result will
      * be cached if possible, to allow re-using the same [RippleHostView].
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 7234f90..21f35fb2 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 androidXMultiplatform {
@@ -49,6 +48,7 @@
                 api("androidx.compose.ui:ui:1.6.0")
                 api("androidx.compose.ui:ui-text:1.6.0")
 
+                implementation(project(":compose:animation:animation-core"))
                 implementation("androidx.compose.animation:animation:1.6.0")
                 implementation("androidx.compose.foundation:foundation-layout:1.6.0")
                 implementation("androidx.compose.ui:ui-util:1.6.0")
diff --git a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt b/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt
deleted file mode 100644
index 1aec7be..0000000
--- a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2023 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.compose.material
-
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ButtonPaparazziScreenshotTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun default_button() {
-        paparazzi.snapshot {
-            MaterialTheme {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
index 4751d172..1ef9312 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
@@ -23,8 +23,6 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
 import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.unit.Velocity
 
 /**
@@ -44,13 +42,7 @@
 fun Modifier.pullRefresh(
     state: PullRefreshState,
     enabled: Boolean = true
-) = inspectable(inspectorInfo = debugInspectorInfo {
-    name = "pullRefresh"
-    properties["state"] = state
-    properties["enabled"] = enabled
-}) {
-    Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
-}
+) = pullRefresh(state::onPull, state::onRelease, enabled)
 
 /**
  * A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom
@@ -79,14 +71,7 @@
     onPull: (pullDelta: Float) -> Float,
     onRelease: suspend (flingVelocity: Float) -> Float,
     enabled: Boolean = true
-) = inspectable(inspectorInfo = debugInspectorInfo {
-    name = "pullRefresh"
-    properties["onPull"] = onPull
-    properties["onRelease"] = onRelease
-    properties["enabled"] = enabled
-}) {
-    Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
-}
+) = nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
 
 private class PullRefreshNestedScrollConnection(
     private val onPull: (pullDelta: Float) -> Float,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
index d780486..2e3f000 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
@@ -22,8 +22,6 @@
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.graphics.drawscope.clipRect
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.util.fastCoerceIn
 
 /**
@@ -40,37 +38,31 @@
 fun Modifier.pullRefreshIndicatorTransform(
     state: PullRefreshState,
     scale: Boolean = false,
-) = inspectable(inspectorInfo = debugInspectorInfo {
-    name = "pullRefreshIndicatorTransform"
-    properties["state"] = state
-    properties["scale"] = scale
-}) {
-    Modifier
-        // Essentially we only want to clip the at the top, so the indicator will not appear when
-        // the position is 0. It is preferable to clip the indicator as opposed to the layout that
-        // contains the indicator, as this would also end up clipping shadows drawn by items in a
-        // list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE
-        // for the other dimensions to allow for more room for elevation / arbitrary indicators - we
-        // only ever really want to clip at the top edge.
-        .drawWithContent {
-            clipRect(
-                top = 0f,
-                left = -Float.MAX_VALUE,
-                right = Float.MAX_VALUE,
-                bottom = Float.MAX_VALUE
-            ) {
-                [email protected]()
-            }
+) =
+    // Essentially we only want to clip the at the top, so the indicator will not appear when
+    // the position is 0. It is preferable to clip the indicator as opposed to the layout that
+    // contains the indicator, as this would also end up clipping shadows drawn by items in a
+    // list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE
+    // for the other dimensions to allow for more room for elevation / arbitrary indicators - we
+    // only ever really want to clip at the top edge.
+    drawWithContent {
+        clipRect(
+            top = 0f,
+            left = -Float.MAX_VALUE,
+            right = Float.MAX_VALUE,
+            bottom = Float.MAX_VALUE
+        ) {
+            [email protected]()
         }
-        .graphicsLayer {
-            translationY = state.position - size.height
+    }
+    .graphicsLayer {
+        translationY = state.position - size.height
 
-            if (scale && !state.refreshing) {
-                val scaleFraction = LinearOutSlowInEasing
-                    .transform(state.position / state.threshold)
-                    .fastCoerceIn(0f, 1f)
-                scaleX = scaleFraction
-                scaleY = scaleFraction
-            }
+        if (scale && !state.refreshing) {
+            val scaleFraction = LinearOutSlowInEasing
+                .transform(state.position / state.threshold)
+                .fastCoerceIn(0f, 1f)
+            scaleX = scaleFraction
+            scaleY = scaleFraction
         }
-}
+    }
diff --git a/compose/material3/material3-adaptive/OWNERS b/compose/material3/adaptive/OWNERS
similarity index 100%
rename from compose/material3/material3-adaptive/OWNERS
rename to compose/material3/adaptive/OWNERS
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
new file mode 100644
index 0000000..34e7c93
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -0,0 +1,173 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive.layout {
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface AdaptStrategy {
+    method public String adapt();
+    field public static final androidx.compose.material3.adaptive.layout.AdaptStrategy.Companion Companion;
+  }
+
+  public static final class AdaptStrategy.Companion {
+    method public androidx.compose.material3.adaptive.layout.AdaptStrategy getHide();
+    property public final androidx.compose.material3.adaptive.layout.AdaptStrategy Hide;
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
+    field public static final androidx.compose.material3.adaptive.layout.HingePolicy.Companion Companion;
+  }
+
+  public static final class HingePolicy.Companion {
+    method public int getAlwaysAvoid();
+    method public int getAvoidOccluding();
+    method public int getAvoidSeparating();
+    method public int getNeverAvoid();
+    property public final int AlwaysAvoid;
+    property public final int AvoidOccluding;
+    property public final int AvoidSeparating;
+    property public final int NeverAvoid;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
+    field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults INSTANCE;
+  }
+
+  public final class ListDetailPaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getDetail();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getList();
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Detail;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole List;
+    field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
+    field public static final androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion Companion;
+  }
+
+  public static final class PaneAdaptedValue.Companion {
+    method public String getExpanded();
+    method public String getHidden();
+    property public final String Expanded;
+    property public final String Hidden;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
+    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
+    method public float getHorizontalPartitionSpacerSize();
+    method public int getMaxHorizontalPartitions();
+    method public int getMaxVerticalPartitions();
+    method public float getVerticalPartitionSpacerSize();
+    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
+    property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
+    property public final float horizontalPartitionSpacerSize;
+    property public final int maxHorizontalPartitions;
+    property public final int maxVerticalPartitions;
+    property public final float verticalPartitionSpacerSize;
+  }
+
+  public final class PaneScaffoldDirectiveKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
+    method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
+    field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
+  }
+
+  public final class SupportingPaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getMain();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getSupporting();
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Main;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Supporting;
+    field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
+    ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.layout.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy tertiaryPaneAdaptStrategy);
+    method public operator androidx.compose.material3.adaptive.layout.AdaptStrategy get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
+    ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public T? getContent();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getPane();
+    property public final T? content;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
+  }
+
+  public final class ThreePaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
+    method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole[] values();
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
+  }
+
+  public interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope {
+    method public String getAnimationToolingLabel();
+    method public androidx.compose.animation.EnterTransition getEnterTransition();
+    method public androidx.compose.animation.ExitTransition getExitTransition();
+    method public String getPaneAdaptedValue();
+    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? getPositionAnimationSpec();
+    property public abstract String animationToolingLabel;
+    property public abstract androidx.compose.animation.EnterTransition enterTransition;
+    property public abstract androidx.compose.animation.ExitTransition exitTransition;
+    property public abstract String paneAdaptedValue;
+    property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? positionAnimationSpec;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldState {
+    method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
+    property public abstract androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue scaffoldValue;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue {
+    ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
+    method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+    method public String getPrimary();
+    method public String getSecondary();
+    method public String getTertiary();
+    property public final String primary;
+    property public final String secondary;
+    property public final String tertiary;
+  }
+
+  public final class ThreePaneScaffoldValueKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>? currentDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory);
+  }
+
+}
+
diff --git a/compose/material3/material3-adaptive/api/res-current.txt b/compose/material3/adaptive/adaptive-layout/api/res-current.txt
similarity index 100%
copy from compose/material3/material3-adaptive/api/res-current.txt
copy to compose/material3/adaptive/adaptive-layout/api/res-current.txt
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
new file mode 100644
index 0000000..34e7c93
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -0,0 +1,173 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive.layout {
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface AdaptStrategy {
+    method public String adapt();
+    field public static final androidx.compose.material3.adaptive.layout.AdaptStrategy.Companion Companion;
+  }
+
+  public static final class AdaptStrategy.Companion {
+    method public androidx.compose.material3.adaptive.layout.AdaptStrategy getHide();
+    property public final androidx.compose.material3.adaptive.layout.AdaptStrategy Hide;
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
+    field public static final androidx.compose.material3.adaptive.layout.HingePolicy.Companion Companion;
+  }
+
+  public static final class HingePolicy.Companion {
+    method public int getAlwaysAvoid();
+    method public int getAvoidOccluding();
+    method public int getAvoidSeparating();
+    method public int getNeverAvoid();
+    property public final int AlwaysAvoid;
+    property public final int AvoidOccluding;
+    property public final int AvoidSeparating;
+    property public final int NeverAvoid;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
+    field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults INSTANCE;
+  }
+
+  public final class ListDetailPaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getDetail();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getList();
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Detail;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole List;
+    field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
+    field public static final androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion Companion;
+  }
+
+  public static final class PaneAdaptedValue.Companion {
+    method public String getExpanded();
+    method public String getHidden();
+    property public final String Expanded;
+    property public final String Hidden;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
+    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
+    method public float getHorizontalPartitionSpacerSize();
+    method public int getMaxHorizontalPartitions();
+    method public int getMaxVerticalPartitions();
+    method public float getVerticalPartitionSpacerSize();
+    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
+    property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
+    property public final float horizontalPartitionSpacerSize;
+    property public final int maxHorizontalPartitions;
+    property public final int maxVerticalPartitions;
+    property public final float verticalPartitionSpacerSize;
+  }
+
+  public final class PaneScaffoldDirectiveKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
+    method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
+    field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
+  }
+
+  public final class SupportingPaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getMain();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getSupporting();
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Main;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Supporting;
+    field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
+    ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.layout.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy tertiaryPaneAdaptStrategy);
+    method public operator androidx.compose.material3.adaptive.layout.AdaptStrategy get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
+    ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public T? getContent();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getPane();
+    property public final T? content;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
+  }
+
+  public final class ThreePaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
+    method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole[] values();
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
+    enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
+  }
+
+  public interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope {
+    method public String getAnimationToolingLabel();
+    method public androidx.compose.animation.EnterTransition getEnterTransition();
+    method public androidx.compose.animation.ExitTransition getExitTransition();
+    method public String getPaneAdaptedValue();
+    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? getPositionAnimationSpec();
+    property public abstract String animationToolingLabel;
+    property public abstract androidx.compose.animation.EnterTransition enterTransition;
+    property public abstract androidx.compose.animation.ExitTransition exitTransition;
+    property public abstract String paneAdaptedValue;
+    property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? positionAnimationSpec;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldState {
+    method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
+    property public abstract androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue scaffoldValue;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue {
+    ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
+    method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+    method public String getPrimary();
+    method public String getSecondary();
+    method public String getTertiary();
+    property public final String primary;
+    property public final String secondary;
+    property public final String tertiary;
+  }
+
+  public final class ThreePaneScaffoldValueKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>? currentDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<?>> destinationHistory);
+  }
+
+}
+
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
new file mode 100644
index 0000000..ec87135
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -0,0 +1,131 @@
+/*
+ * 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.tasks.KotlinCompile
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+}
+
+androidXMultiplatform {
+    android()
+    desktop()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(libs.kotlinStdlibCommon)
+                api("androidx.compose.foundation:foundation:1.6.0-rc01")
+                api(project(":compose:material3:adaptive:adaptive"))
+                implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
+                implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
+                // TODO(conradchen): pin the depe when the change required is released to public
+                implementation(project(":window:window-core"))
+            }
+        }
+
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+                api("androidx.annotation:annotation-experimental:1.4.0")
+            }
+        }
+
+        desktopMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        desktopTest {
+            dependsOn(jvmTest)
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(project(":compose:material3:material3"))
+                implementation(project(":compose:test-utils"))
+                implementation(project(":window:window-testing"))
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+
+        androidUnitTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+    }
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive.layout"
+}
+
+androidx {
+    name = "Material Adaptive"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Compose Material Design Adaptive Library"
+}
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
+}
+
+// Screenshot tests related setup
+android {
+    sourceSets.androidTest.assets.srcDirs +=
+            project.rootDir.absolutePath + "/../../golden/compose/material3/adaptive"
+    namespace "androidx.compose.material3.adaptive.layout"
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/GoldenCommon.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/GoldenCommon.kt
new file mode 100644
index 0000000..f16ff5e
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/GoldenCommon.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+internal const val GOLDEN_MATERIAL3_ADAPTIVE = "compose/material3/adaptive"
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt
new file mode 100644
index 0000000..5dfa0175
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.toSize
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal fun ComposeContentTestRule.setContentWithSimulatedSize(
+    simulatedWidth: Dp,
+    simulatedHeight: Dp,
+    content: @Composable () -> Unit
+) {
+    setContent {
+        val currentDensity = LocalDensity.current
+        val windowSize = with(currentDensity) {
+            currentWindowSize().toSize().toDpSize();
+        }
+        val simulatedDensity = Density(
+            currentDensity.density * (windowSize.width / simulatedWidth)
+        )
+        CompositionLocalProvider(LocalDensity provides simulatedDensity) {
+            Box(
+                Modifier.fillMaxWidth().height(simulatedHeight),
+            ) {
+                content()
+            }
+        }
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffoldStateTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffoldStateTest.kt
new file mode 100644
index 0000000..77df51f
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffoldStateTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListDetailPaneScaffoldStateTest {
+    @get:Rule
+    val composeRule = createComposeRule()
+
+    @Test
+    fun singlePaneLayout_listPaneExpandedByDefault() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_listAndDetailPaneExpandedByDefault() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = MockDualPaneScaffoldDirective
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_paneDestinationExpanded() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = MockSinglePaneScaffoldDirective,
+                currentDestination = ThreePaneScaffoldDestinationItem(
+                    ListDetailPaneScaffoldRole.Detail,
+                    null
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_paneDestinationExpanded() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                currentDestination = ThreePaneScaffoldDestinationItem(
+                    ListDetailPaneScaffoldRole.Extra,
+                    null
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldState.scaffoldValue.tertiary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(0.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 16.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 16.dp,
+    excludedBounds = emptyList()
+)
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffoldStateTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffoldStateTest.kt
new file mode 100644
index 0000000..fc8bb82
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffoldStateTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SupportingPaneScaffoldStateTest {
+    @get:Rule
+    val composeRule = createComposeRule()
+
+    @Test
+    fun singlePaneLayout_mainPaneExpandedByDefault() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_mainAndSupportingPaneExpandedByDefault() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = MockDualPaneScaffoldDirective
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_paneDestinationExpanded() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = MockSinglePaneScaffoldDirective,
+                currentDestination = ThreePaneScaffoldDestinationItem(
+                    SupportingPaneScaffoldRole.Supporting,
+                    null
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_paneDestinationExpanded() {
+        lateinit var scaffoldState: ThreePaneScaffoldState
+
+        composeRule.setContent {
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                currentDestination = ThreePaneScaffoldDestinationItem(
+                    SupportingPaneScaffoldRole.Extra,
+                    null
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldState.scaffoldValue.tertiary).isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(0.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 16.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 16.dp,
+    excludedBounds = emptyList()
+)
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
new file mode 100644
index 0000000..c707ab5
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import android.os.Build
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class ThreePaneScaffoldScreenshotTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3_ADAPTIVE)
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_standard() {
+        rule.setContent {
+            SampleThreePaneScaffoldStandardMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard")
+    }
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_dense() {
+        rule.setContent {
+            SampleThreePaneScaffoldDenseMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense")
+    }
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_standard_medium_size_window() {
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 700.dp,
+            simulatedHeight = 500.dp
+        ) {
+            SampleThreePaneScaffoldStandardMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard_medium")
+    }
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_dense_medium_size_window() {
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 700.dp,
+            simulatedHeight = 500.dp
+        ) {
+            SampleThreePaneScaffoldDenseMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense_medium")
+    }
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_standard_expanded_size_window() {
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 1024.dp,
+            simulatedHeight = 800.dp
+        ) {
+            SampleThreePaneScaffoldStandardMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard_expanded")
+    }
+
+    @Test
+    fun threePaneScaffold_listDetailPaneOrder_dense_expanded_size_window() {
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 1024.dp,
+            simulatedHeight = 800.dp
+        ) {
+            SampleThreePaneScaffoldDenseMode()
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense_expanded")
+    }
+
+    @Test
+    fun threePaneScaffold_insets_compact_size_window() {
+        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
+        rule.setContent {
+            SampleThreePaneScaffoldWithInsets(mockInsets)
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_compact")
+    }
+
+    @Test
+    fun threePaneScaffold_insets_medium_size_window() {
+        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 700.dp,
+            simulatedHeight = 500.dp
+        ) {
+            SampleThreePaneScaffoldWithInsets(mockInsets)
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_medium")
+    }
+
+    @Test
+    fun threePaneScaffold_insets_expanded_size_window() {
+        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
+        rule.setContentWithSimulatedSize(
+            simulatedWidth = 1024.dp,
+            simulatedHeight = 800.dp
+        ) {
+            SampleThreePaneScaffoldWithInsets(mockInsets)
+        }
+
+        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_expanded")
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+private fun SampleThreePaneScaffoldStandardMode() {
+    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        currentWindowAdaptiveInfo()
+    )
+    val scaffoldValue = calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        ThreePaneScaffoldDefaults.adaptStrategies(),
+        null
+    )
+    SampleThreePaneScaffold(
+        scaffoldDirective,
+        scaffoldValue,
+        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
+    )
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+private fun SampleThreePaneScaffoldDenseMode() {
+    val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        currentWindowAdaptiveInfo()
+    )
+    val scaffoldValue = calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        ThreePaneScaffoldDefaults.adaptStrategies(),
+        null
+    )
+    SampleThreePaneScaffold(
+        scaffoldDirective,
+        scaffoldValue,
+        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
+    )
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+private fun SampleThreePaneScaffoldWithInsets(
+    windowInsets: WindowInsets
+) {
+    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        currentWindowAdaptiveInfo()
+    )
+    val scaffoldValue = calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        ThreePaneScaffoldDefaults.adaptStrategies(),
+        null
+    )
+    SampleThreePaneScaffold(
+        scaffoldDirective,
+        scaffoldValue,
+        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
+        windowInsets
+    )
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
new file mode 100644
index 0000000..57db5ac
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ThreePaneScaffoldTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun threePaneScaffold_allPanesHidden_noVisiblePanes() {
+         val testScaffoldValue = ThreePaneScaffoldValue(
+             PaneAdaptedValue.Hidden,
+             PaneAdaptedValue.Hidden,
+             PaneAdaptedValue.Hidden
+         )
+         rule.setContent {
+             SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+         }
+
+         rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
+         rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
+         rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
+    }
+
+    @Test
+    fun threePaneScaffold_oneExpandedPane_onlyExpandedPanesAreVisible() {
+        val testScaffoldValue = ThreePaneScaffoldValue(
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Hidden,
+            PaneAdaptedValue.Hidden
+        )
+        rule.setContent {
+            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+        }
+
+        rule.onNodeWithTag("PrimaryPane").assertExists()
+        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
+        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
+    }
+
+    @Test
+    fun threePaneScaffold_twoExpandedPanes_onlyExpandedPanesAreVisible() {
+        val testScaffoldValue = ThreePaneScaffoldValue(
+            PaneAdaptedValue.Hidden,
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Expanded
+        )
+        rule.setContent {
+            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+        }
+
+        rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
+        rule.onNodeWithTag("SecondaryPane").assertExists()
+        rule.onNodeWithTag("TertiaryPane").assertExists()
+    }
+
+    @Test
+    fun threePaneScaffold_threeExpandedPanes_onlyExpandedPanesAreVisible() {
+        val testScaffoldValue = ThreePaneScaffoldValue(
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Expanded
+        )
+        rule.setContent {
+            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+        }
+
+        rule.onNodeWithTag("PrimaryPane").assertExists()
+        rule.onNodeWithTag("SecondaryPane").assertExists()
+        rule.onNodeWithTag("TertiaryPane").assertExists()
+    }
+
+    @Test
+    fun threePaneScaffold_scaffoldValueChangeWithSinglePane_expandedPanesAreChanged() {
+        var testScaffoldValue by mutableStateOf(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            )
+        )
+        rule.setContent {
+            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+        }
+
+        rule.onNodeWithTag("PrimaryPane").assertExists()
+        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
+        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
+
+        testScaffoldValue = ThreePaneScaffoldValue(
+            PaneAdaptedValue.Hidden,
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Hidden
+        )
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
+        rule.onNodeWithTag("SecondaryPane").assertExists()
+        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
+    }
+
+    @Test
+    fun threePaneScaffold_scaffoldValueChangeWithDualPane_expandedPanesAreChanged() {
+        var testScaffoldValue by mutableStateOf(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            )
+        )
+        rule.setContent {
+            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
+        }
+
+        rule.onNodeWithTag("PrimaryPane").assertExists()
+        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
+        rule.onNodeWithTag("TertiaryPane").assertExists()
+
+        testScaffoldValue = ThreePaneScaffoldValue(
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Expanded,
+            PaneAdaptedValue.Hidden
+        )
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("PrimaryPane").assertExists()
+        rule.onNodeWithTag("SecondaryPane").assertExists()
+        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(0.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+internal const val ThreePaneScaffoldTestTag = "SampleThreePaneScaffold"
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+private fun SampleThreePaneScaffold(scaffoldValue: ThreePaneScaffoldValue) {
+    SampleThreePaneScaffold(
+        MockScaffoldDirective,
+        scaffoldValue,
+        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
+    )
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+internal fun SampleThreePaneScaffold(
+    scaffoldDirective: PaneScaffoldDirective,
+    scaffoldValue: ThreePaneScaffoldValue,
+    paneOrder: ThreePaneScaffoldHorizontalOrder,
+    windowInsets: WindowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout)
+) {
+    ThreePaneScaffold(
+        modifier = Modifier.fillMaxSize().testTag(ThreePaneScaffoldTestTag),
+        scaffoldDirective = scaffoldDirective,
+        scaffoldValue = scaffoldValue,
+        paneOrder = paneOrder,
+        windowInsets = windowInsets,
+        secondaryPane = {
+            AnimatedPane(
+                modifier = Modifier.testTag(tag = "SecondaryPane")
+            ) {
+                Surface(
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.secondary
+                ) {}
+            }
+        },
+        tertiaryPane = {
+            AnimatedPane(
+                modifier = Modifier.testTag(tag = "TertiaryPane")
+            ) {
+                Surface(
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.tertiary
+                ) {}
+            }
+        }
+    ) {
+        AnimatedPane(
+            modifier = Modifier.testTag(tag = "PrimaryPane")
+        ) {
+            Surface(
+                modifier = Modifier.fillMaxSize(),
+                color = MaterialTheme.colorScheme.primary
+            ) {}
+        }
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
new file mode 100644
index 0000000..2813adb
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.HingeInfo
+import androidx.compose.material3.adaptive.Posture
+import androidx.compose.material3.adaptive.WindowAdaptiveInfo
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.window.core.layout.WindowSizeClass
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@RunWith(JUnit4::class)
+class PaneScaffoldDirectiveTest {
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_compactWidth() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(400, 800),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(16.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_mediumWidth() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(750, 900),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_expandedWidth() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(1200, 800),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_tabletop() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(isTabletop = true)
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_compactWidth() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(400, 800),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(16.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_mediumWidth() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(750, 900),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_expandedWidth() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(1200, 800),
+                Posture()
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_tabletop() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(isTabletop = true)
+            )
+        )
+
+        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
+        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(
+            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
+        ).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
+        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_alwaysAvoidHinge() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AlwaysAvoid
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.getBounds())
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_avoidOccludingHinge() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AvoidOccluding
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(0, 2).getBounds())
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_avoidSeparatingHinge() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AvoidSeparating
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(2, 3).getBounds())
+    }
+
+    @Test
+    fun test_calculateStandardPaneScaffoldDirective_neverAvoidHinge() {
+        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.NeverAvoid
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEmpty()
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_alwaysAvoidHinge() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AlwaysAvoid
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.getBounds())
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_avoidOccludingHinge() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AvoidOccluding
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(0, 2).getBounds())
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_avoidSeparatingHinge() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.AvoidSeparating
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(2, 3).getBounds())
+    }
+
+    @Test
+    fun test_calculateDensePaneScaffoldDirective_neverAvoidHinge() {
+        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+            WindowAdaptiveInfo(
+                WindowSizeClass(700, 800),
+                Posture(hingeList = hingeList)
+            ),
+            HingePolicy.NeverAvoid
+        )
+
+        assertThat(scaffoldDirective.excludedBounds).isEmpty()
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val hingeList = listOf(
+    HingeInfo(
+        bounds = Rect(0F, 0F, 1F, 1F),
+        isVertical = true,
+        isSeparating = false,
+        isOccluding = true
+    ),
+    HingeInfo(
+        bounds = Rect(1F, 1F, 2F, 2F),
+        isVertical = true,
+        isSeparating = false,
+        isOccluding = true
+    ),
+    HingeInfo(
+        bounds = Rect(2F, 2F, 3F, 3F),
+        isVertical = true,
+        isSeparating = true,
+        isOccluding = false
+    ),
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun List<HingeInfo>.getBounds(): List<Rect> {
+    return map { it.bounds }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt
new file mode 100644
index 0000000..4710f5b
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@RunWith(JUnit4::class)
+class ThreePaneMotionTest {
+    @Test
+    fun noPane_noMotion() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotion.NoMotion)
+    }
+
+    @Test
+    fun singlePane_firstToSecond_movesLeft() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToLeftMotion)
+    }
+
+    @Test
+    fun singlePane_firstToThird_movesLeft() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToLeftMotion)
+    }
+
+    @Test
+    fun singlePane_secondToThird_movesLeft() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToLeftMotion)
+    }
+
+    @Test
+    fun singlePane_secondToFirst_movesRight() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToRightMotion)
+    }
+
+    @Test
+    fun singlePane_thirdToFirst_movesRight() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToRightMotion)
+    }
+
+    @Test
+    fun singlePane_thirdToSecond_movesRight() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToRightMotion)
+    }
+
+    @Test
+    fun dualPane_hidesFirstShowsThird_movesLeft() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToLeftMotion)
+    }
+
+    @Test
+    fun dualPane_hidesThirdShowsFirst_movesRight() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.movePanesToRightMotion)
+    }
+
+    @Test
+    fun dualPane_hidesSecondShowsThird_switchRightTwoPanes() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.switchRightTwoPanesMotion)
+    }
+
+    @Test
+    fun dualPane_hidesThirdShowsSecond_switchRightTwoPanes() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.switchRightTwoPanesMotion)
+    }
+
+    @Test
+    fun dualPane_hidesFirstShowsSecond_switchLeftTwoPanes() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.switchLeftTwoPanesMotion)
+    }
+
+    @Test
+    fun dualPane_hidesSecondShowsFirst_switchLeftTwoPanes() {
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotionDefaults.switchLeftTwoPanesMotion)
+    }
+
+    @Test
+    fun changeNumberOfPanes_noMotion() {
+        // TODO(conradchen): Update this when we support motions in this case
+        val motions = calculateThreePaneMotion(
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            ),
+            ThreePaneScaffoldValue(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Hidden
+            ),
+            PaneOrder
+        )
+        assertThat(motions).isEqualTo(ThreePaneMotion.NoMotion)
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal val PaneOrder = ThreePaneScaffoldDefaults.SupportingPaneLayoutPaneOrder
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValueTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValueTest.kt
new file mode 100644
index 0000000..48dbd86
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValueTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@RunWith(JUnit4::class)
+class ThreePaneScaffoldValueTest {
+    @Test
+    fun calculateWithoutHistory_onePaneLayout_noDestination() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 1,
+            adaptStrategies = MockAdaptStrategies,
+            currentDestination = null
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithHistory_onePaneLayout_noDestination() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 1,
+            adaptStrategies = MockAdaptStrategies,
+            destinationHistory = emptyList()
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithoutHistory_onePaneLayout() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 1,
+            adaptStrategies = MockAdaptStrategies,
+            currentDestination =
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithHistory_onePaneLayout() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 1,
+            adaptStrategies = MockAdaptStrategies,
+            destinationHistory = listOf(
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
+            )
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithoutHistory_twoPaneLayout_noDestination() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 2,
+            adaptStrategies = MockAdaptStrategies,
+            currentDestination = null
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithHistory_twoPaneLayout_noDestination() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 2,
+            adaptStrategies = MockAdaptStrategies,
+            destinationHistory = emptyList()
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
+    }
+
+    @Test
+    fun calculateWithoutHistory_twoPaneLayout() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 2,
+            adaptStrategies = MockAdaptStrategies,
+            currentDestination =
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null)
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
+    }
+
+    @Test
+    fun calculateWithHistory_twoPaneLayout() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 2,
+            adaptStrategies = MockAdaptStrategies,
+            destinationHistory = listOf(
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
+            )
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
+    }
+
+    @Test
+    fun calculateWithHistory_twoPaneLayout_longHistory() {
+        val scaffoldState = calculateThreePaneScaffoldValue(
+            maxHorizontalPartitions = 2,
+            adaptStrategies = MockAdaptStrategies,
+            destinationHistory = listOf(
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Primary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Primary, null),
+                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null)
+            )
+        )
+        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
+    }
+
+    private fun ThreePaneScaffoldValue.assertState(
+        role: ThreePaneScaffoldRole,
+        state: PaneAdaptedValue
+    ) {
+        assertThat(this[role]).isEqualTo(state)
+    }
+
+    companion object {
+        private val PrimaryPaneAdaptStrategy = AdaptStrategy.Hide
+        private val SecondaryPaneAdaptStrategy = AdaptStrategy.Hide
+        private val TertiaryPaneAdaptStrategy = AdaptStrategy.Hide
+        private val PrimaryPaneAdaptedState = PaneAdaptedValue.Hidden
+        private val SecondaryPaneAdaptedState = PaneAdaptedValue.Hidden
+        private val TertiaryPaneAdaptedState = PaneAdaptedValue.Hidden
+        private val MockAdaptStrategies = ThreePaneScaffoldAdaptStrategies(
+            PrimaryPaneAdaptStrategy,
+            SecondaryPaneAdaptStrategy,
+            TertiaryPaneAdaptStrategy
+        )
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt
new file mode 100644
index 0000000..6f73836
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+
+/**
+ * Provides the information about how the associated pane should be adapted if it cannot be
+ * displayed in its [PaneAdaptedValue.Expanded] state.
+ */
+@ExperimentalMaterial3AdaptiveApi
+interface AdaptStrategy {
+    /**
+     * Override this function to provide the resulted adapted state.
+     */
+    fun adapt(): PaneAdaptedValue
+
+    private class BaseAdaptStrategy(
+        private val description: String,
+        private val adaptedState: PaneAdaptedValue
+    ) : AdaptStrategy {
+        override fun adapt() = adaptedState
+
+        override fun toString() = "AdaptStrategy[$description]"
+    }
+
+    companion object {
+        /**
+         * The default [AdaptStrategy] that suggests the layout to hide the associated pane when
+         * it has to be adapted, i.e., cannot be displayed in its [PaneAdaptedValue.Expanded] state.
+         */
+        val Hide: AdaptStrategy = BaseAdaptStrategy("Hide", PaneAdaptedValue.Hidden)
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
new file mode 100644
index 0000000..0e97207
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.TwoWayConverter
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.animateBounds(
+    modifier: Modifier = Modifier,
+    sizeAnimationSpec: FiniteAnimationSpec<IntSize> = spring(
+        Spring.DampingRatioNoBouncy,
+        Spring.StiffnessMediumLow
+    ),
+    positionAnimationSpec: FiniteAnimationSpec<IntOffset> = spring(
+        Spring.DampingRatioNoBouncy,
+        Spring.StiffnessMediumLow
+    ),
+    debug: Boolean = false,
+    lookaheadScope: (closestLookaheadScope: LookaheadScope) -> LookaheadScope = { it }
+) = composed {
+
+    val outerOffsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
+    val outerSizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
+
+    val offsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
+    val sizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
+
+    // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as
+    // intermediateLayout is expected to produce intermediate stages of a layout transform.
+    // When the measure block is invoked after lookahead pass, the lookahead size of the
+    // child will be accessible as a parameter to the measure block.
+    @Suppress("DEPRECATION") // TODO: Update intermediateLayout usage to approachLayout
+    this
+        .drawWithContent {
+            drawContent()
+            if (debug) {
+                val offset = outerOffsetAnimation.target!! - outerOffsetAnimation.value!!
+                translate(
+                    offset.x.toFloat(), offset.y.toFloat()
+                ) {
+                    drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f))
+                }
+            }
+        }
+        .intermediateLayout { measurable, constraints ->
+            val (w, h) = outerSizeAnimation.updateTarget(
+                lookaheadSize,
+                sizeAnimationSpec,
+            )
+            measurable
+                .measure(constraints)
+                .run {
+                    layout(w, h) {
+                        val (x, y) = outerOffsetAnimation.updateTargetBasedOnCoordinates(
+                            positionAnimationSpec
+                        )
+                        place(x, y)
+                    }
+                }
+        }
+        .then(modifier)
+        .drawWithContent {
+            drawContent()
+            if (debug) {
+                val offset = offsetAnimation.target!! - offsetAnimation.value!!
+                translate(
+                    offset.x.toFloat(), offset.y.toFloat()
+                ) {
+                    drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f))
+                }
+            }
+        }
+        .intermediateLayout { measurable, _ ->
+            // When layout changes, the lookahead pass will calculate a new final size for the
+            // child modifier. This lookahead size can be used to animate the size
+            // change, such that the animation starts from the current size and gradually
+            // change towards `lookaheadSize`.
+            val (width, height) = sizeAnimation.updateTarget(
+                lookaheadSize,
+                sizeAnimationSpec,
+            )
+            // Creates a fixed set of constraints using the animated size
+            val animatedConstraints = Constraints.fixed(width, height)
+            // Measure child/children with animated constraints.
+            val placeable = measurable.measure(animatedConstraints)
+            layout(placeable.width, placeable.height) {
+                val (x, y) = with(lookaheadScope(this@intermediateLayout)) {
+                    offsetAnimation.updateTargetBasedOnCoordinates(
+                        positionAnimationSpec,
+                    )
+                }
+                placeable.place(x, y)
+            }
+        }
+}
+
+context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
+@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun DeferredAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
+    animationSpec: FiniteAnimationSpec<IntOffset>,
+): IntOffset {
+    coordinates?.let { coordinates ->
+        with(this@PlacementScope) {
+            val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
+            val animOffset = updateTarget(
+                targetOffset.round(),
+                animationSpec,
+            )
+            val current = lookaheadScopeCoordinates.localPositionOf(
+                coordinates,
+                Offset.Zero
+            ).round()
+            return (animOffset - current)
+        }
+    }
+
+    return IntOffset.Zero
+}
+
+// Experimenting with a way to initialize animation during measurement && only take the last target
+// change in a frame (if the target was changed multiple times in the same frame) as the
+// animation target.
+internal class DeferredAnimation<T, V : AnimationVector>(
+    private val vectorConverter: TwoWayConverter<T, V>
+) {
+    val value: T?
+        get() = animatable?.value ?: target
+    var target: T? by mutableStateOf(null)
+        private set
+    private var animatable: Animatable<T, V>? = null
+
+    internal val isActive: Boolean
+        get() = target != animatable?.targetValue || animatable?.isRunning == true
+
+    context (CoroutineScope)
+    fun updateTarget(
+        targetValue: T,
+        animationSpec: FiniteAnimationSpec<T>,
+    ): T {
+        target = targetValue
+        if (target != null && target != animatable?.targetValue) {
+            animatable?.run {
+                launch {
+                    animateTo(
+                        targetValue,
+                        animationSpec
+                    )
+                }
+            } ?: Animatable(targetValue, vectorConverter).let {
+                animatable = it
+            }
+        }
+        return animatable?.value ?: targetValue
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
new file mode 100644
index 0000000..e6d9904
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
+ * panes in a canonical list-detail layout.
+ *
+ * @param listPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.List].
+ * @param modifier [Modifier] of the scaffold layout.
+ * @param scaffoldState the state of the scaffold, which provides the current scaffold directive
+ *        and scaffold value.
+ * @param windowInsets window insets that the scaffold will respect.
+ * @param extraPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Extra].
+ * @param detailPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Detail].
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun ListDetailPaneScaffold(
+    listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    scaffoldState: ThreePaneScaffoldState = calculateListDetailPaneScaffoldState(),
+    windowInsets: WindowInsets = ListDetailPaneScaffoldDefaults.windowInsets,
+    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    detailPane: @Composable ThreePaneScaffoldScope.() -> Unit
+) {
+    ThreePaneScaffold(
+        modifier = modifier.fillMaxSize(),
+        scaffoldDirective = scaffoldState.scaffoldDirective,
+        scaffoldValue = scaffoldState.scaffoldValue,
+        paneOrder = ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
+        windowInsets = windowInsets,
+        secondaryPane = listPane,
+        tertiaryPane = extraPane,
+        primaryPane = detailPane
+    )
+}
+
+/**
+ * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
+ * [ThreePaneScaffoldAdaptStrategies], and the current pane destination of a
+ * [ListDetailPaneScaffold].
+ *
+ * @param currentDestination the current destination item, which will be guaranteed to have the
+ *        highest priority when deciding pane visibilities.
+ * @param scaffoldDirective the layout directives that the associated [ListDetailPaneScaffold]
+ *        needs to follow. The default value will be the calculation result from
+ *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
+ *        will be automatically updated when the window configuration changes.
+ * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateListDetailPaneScaffoldState(
+    currentDestination: ThreePaneScaffoldDestinationItem<*> =
+        ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        ListDetailPaneScaffoldDefaults.adaptStrategies()
+): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
+    scaffoldDirective,
+    calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        adaptStrategies,
+        currentDestination
+    )
+)
+
+/**
+ * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
+ * [ThreePaneScaffoldAdaptStrategies], and the pane destination history of a
+ * [ListDetailPaneScaffold].
+ *
+ * @param destinationHistory The history of past destinations items. The last destination will
+ *        have the highest priority, and the second last destination will have the second highest
+ *        priority, and so forth until all panes have a priority assigned. Note that the last
+ *        destination is supposed to be the last item of the provided list. When the history is
+ *        empty or there are panes left unassigned, default priorities will be assigned to those
+ *        panes in the order of Detail > List > Extra.
+ * @param scaffoldDirective the layout directives that the associated [ListDetailPaneScaffold]
+ *        needs to follow. The default value will be the calculation result from
+ *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
+ *        will be automatically updated when the window configuration changes.
+ * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateListDetailPaneScaffoldState(
+    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        ListDetailPaneScaffoldDefaults.adaptStrategies()
+): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
+    scaffoldDirective,
+    calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        adaptStrategies,
+        destinationHistory
+    )
+)
+
+/**
+ * Provides default values of [ListDetailPaneScaffold].
+ */
+@ExperimentalMaterial3AdaptiveApi
+object ListDetailPaneScaffoldDefaults {
+    /**
+     * Default insets that will be used and consumed by [ListDetailPaneScaffold]. By default it will
+     * be the union of [WindowInsets.Companion.systemBars] and
+     * [WindowInsets.Companion.displayCutout].
+     */
+    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
+
+    /**
+     * Creates a default [ThreePaneScaffoldAdaptStrategies] for [ListDetailPaneScaffold].
+     *
+     * @param detailPaneAdaptStrategy the adapt strategy of the primary pane
+     * @param listPaneAdaptStrategy the adapt strategy of the secondary pane
+     * @param extraPaneAdaptStrategy the adapt strategy of the tertiary pane
+     */
+    fun adaptStrategies(
+        detailPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        listPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+    ): ThreePaneScaffoldAdaptStrategies =
+        ThreePaneScaffoldAdaptStrategies(
+            detailPaneAdaptStrategy,
+            listPaneAdaptStrategy,
+            extraPaneAdaptStrategy
+        )
+}
+
+/**
+ * The set of the available pane roles of [ListDetailPaneScaffold]. Basically those values are
+ * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
+ * the raw [ThreePaneScaffoldRole] under the context of [ListDetailPaneScaffold] for better
+ * code clarity.
+ */
+@ExperimentalMaterial3AdaptiveApi
+object ListDetailPaneScaffoldRole {
+    /**
+     * The list pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Secondary].
+     */
+    val List = ThreePaneScaffoldRole.Secondary
+
+    /**
+     * The detail pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Primary].
+     */
+    val Detail = ThreePaneScaffoldRole.Primary
+
+    /**
+     * The extra pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Tertiary].
+     */
+    val Extra = ThreePaneScaffoldRole.Tertiary
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt
new file mode 100644
index 0000000..54501b1
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+
+/**
+ * The adapted state of a pane. It gives clues to pane scaffolds about if a certain pane should be
+ * composed and how.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@JvmInline
+value class PaneAdaptedValue private constructor(private val description: String) {
+    companion object {
+        /**
+         * Denotes that the associated pane should be displayed in its full width and height.
+         */
+        val Expanded = PaneAdaptedValue("Expanded")
+        /**
+         * Denotes that the associated pane should be hidden.
+         */
+        val Hidden = PaneAdaptedValue("Hidden")
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
new file mode 100644
index 0000000..57f1ad7
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Scope for the panes of pane scaffolds.
+ */
+@ExperimentalMaterial3AdaptiveApi
+interface PaneScaffoldScope {
+    /**
+     * This modifier specifies the preferred width for a pane, and the pane scaffold implementation
+     * will respect this width whenever possible. In case the modifier is not set or set to
+     * [Dp.Unspecified], the default preferred widths of the respective scaffold implementation will
+     * be used.
+     */
+    fun Modifier.preferredWidth(width: Dp): Modifier
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal abstract class PaneScaffoldScopeImpl : PaneScaffoldScope {
+    override fun Modifier.preferredWidth(width: Dp): Modifier {
+        require(width == Dp.Unspecified || width > 0.dp) { "invalid width" }
+        return this.then(PreferredWidthElement(width))
+    }
+}
+
+private class PreferredWidthElement(
+    private val width: Dp,
+) : ModifierNodeElement<PreferredWidthNode>() {
+    private val inspectorInfo = debugInspectorInfo {
+        name = "preferredWidth"
+        value = width
+    }
+
+    override fun create(): PreferredWidthNode {
+        return PreferredWidthNode(width)
+    }
+
+    override fun update(node: PreferredWidthNode) {
+        node.width = width
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        inspectorInfo()
+    }
+
+    override fun hashCode(): Int {
+        return width.hashCode()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        val otherModifier = other as? PreferredWidthElement ?: return false
+        return width == otherModifier.width
+    }
+}
+
+private class PreferredWidthNode(var width: Dp) : ParentDataModifierNode, Modifier.Node() {
+    override fun Density.modifyParentData(parentData: Any?) =
+        ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+            it.preferredWidth = with(this) { width.toPx() }
+        }
+}
+
+internal fun Modifier.animatedPane(): Modifier {
+    return this.then(AnimatedPaneElement)
+}
+
+private object AnimatedPaneElement : ModifierNodeElement<AnimatedPaneNode>() {
+    private val inspectorInfo = debugInspectorInfo {
+        name = "isPaneComposable"
+        value = true
+    }
+
+    override fun create(): AnimatedPaneNode {
+        return AnimatedPaneNode()
+    }
+
+    override fun update(node: AnimatedPaneNode) {
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        inspectorInfo()
+    }
+
+    override fun hashCode(): Int {
+        return 0
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return (other is AnimatedPaneElement)
+    }
+}
+
+private class AnimatedPaneNode : ParentDataModifierNode, Modifier.Node() {
+    override fun Density.modifyParentData(parentData: Any?) =
+        ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+            it.isAnimatedPane = true
+        }
+}
+
+internal data class PaneScaffoldParentData(
+    var preferredWidth: Float? = null,
+    var isAnimatedPane: Boolean = false
+)
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
new file mode 100644
index 0000000..18bc602
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.Posture
+import androidx.compose.material3.adaptive.WindowAdaptiveInfo
+import androidx.compose.material3.adaptive.allVerticalHingeBounds
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.material3.adaptive.occludingVerticalHingeBounds
+import androidx.compose.material3.adaptive.separatingVerticalHingeBounds
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.window.core.layout.WindowWidthSizeClass
+
+/**
+ * Calculates the standard [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
+ * method with [currentWindowAdaptiveInfo] to acquire Material-recommended adaptive layout
+ * settings of the current activity window.
+ *
+ * See more details on the [Material design guideline site]
+ * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes).
+ *
+ * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
+ *        layout adaptation decisions like [WindowSizeClass].
+ * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address
+ *        vertical hinges.
+ * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
+ */
+// TODO(b/285144647): Add more details regarding the use scenarios of this function.
+@ExperimentalMaterial3AdaptiveApi
+fun calculateStandardPaneScaffoldDirective(
+    windowAdaptiveInfo: WindowAdaptiveInfo,
+    verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
+): PaneScaffoldDirective {
+    val maxHorizontalPartitions: Int
+    val contentPadding: PaddingValues
+    val verticalSpacerSize: Dp
+    when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
+        WindowWidthSizeClass.COMPACT -> {
+            maxHorizontalPartitions = 1
+            contentPadding = PaddingValues(16.dp)
+            verticalSpacerSize = 0.dp
+        }
+        WindowWidthSizeClass.MEDIUM -> {
+            maxHorizontalPartitions = 1
+            contentPadding = PaddingValues(24.dp)
+            verticalSpacerSize = 0.dp
+        }
+        else -> {
+            maxHorizontalPartitions = 2
+            contentPadding = PaddingValues(24.dp)
+            verticalSpacerSize = 24.dp
+        }
+    }
+    val maxVerticalPartitions: Int
+    val horizontalSpacerSize: Dp
+
+    // TODO(conradchen): Confirm the table top mode settings
+    if (windowAdaptiveInfo.windowPosture.isTabletop) {
+        maxVerticalPartitions = 2
+        horizontalSpacerSize = 24.dp
+    } else {
+        maxVerticalPartitions = 1
+        horizontalSpacerSize = 0.dp
+    }
+
+    return PaneScaffoldDirective(
+        contentPadding,
+        maxHorizontalPartitions,
+        verticalSpacerSize,
+        maxVerticalPartitions,
+        horizontalSpacerSize,
+        getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy)
+    )
+}
+
+/**
+ * Calculates the dense-mode [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
+ * method with [currentWindowAdaptiveInfo] to acquire Material-recommended dense-mode adaptive
+ * layout settings of the current activity window.
+ *
+ * See more details on the [Material design guideline site]
+ * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes).
+ *
+ * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
+ *        layout adaptation decisions like [WindowSizeClass].
+ * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address
+ *        vertical hinges.
+ * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
+ */
+// TODO(b/285144647): Add more details regarding the use scenarios of this function.
+@ExperimentalMaterial3AdaptiveApi
+fun calculateDensePaneScaffoldDirective(
+    windowAdaptiveInfo: WindowAdaptiveInfo,
+    verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
+): PaneScaffoldDirective {
+    val maxHorizontalPartitions: Int
+    val contentPadding: PaddingValues
+    val verticalSpacerSize: Dp
+    when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
+        WindowWidthSizeClass.COMPACT -> {
+            maxHorizontalPartitions = 1
+            contentPadding = PaddingValues(16.dp)
+            verticalSpacerSize = 0.dp
+        }
+        WindowWidthSizeClass.MEDIUM -> {
+            maxHorizontalPartitions = 2
+            contentPadding = PaddingValues(24.dp)
+            verticalSpacerSize = 24.dp
+        }
+        else -> {
+            maxHorizontalPartitions = 2
+            contentPadding = PaddingValues(24.dp)
+            verticalSpacerSize = 24.dp
+        }
+    }
+    val maxVerticalPartitions: Int
+    val horizontalSpacerSize: Dp
+
+    if (windowAdaptiveInfo.windowPosture.isTabletop) {
+        maxVerticalPartitions = 2
+        horizontalSpacerSize = 24.dp
+    } else {
+        maxVerticalPartitions = 1
+        horizontalSpacerSize = 0.dp
+    }
+
+    return PaneScaffoldDirective(
+        contentPadding,
+        maxHorizontalPartitions,
+        verticalSpacerSize,
+        maxVerticalPartitions,
+        horizontalSpacerSize,
+        getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy)
+    )
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun getExcludedVerticalBounds(posture: Posture, hingePolicy: HingePolicy): List<Rect> {
+    return when (hingePolicy) {
+        HingePolicy.AvoidSeparating -> posture.separatingVerticalHingeBounds
+        HingePolicy.AvoidOccluding -> posture.occludingVerticalHingeBounds
+        HingePolicy.AlwaysAvoid -> posture.allVerticalHingeBounds
+        else -> emptyList()
+    }
+}
+
+/**
+ * Top-level directives about how a pane scaffold should be arranged and spaced, like how many
+ * partitions the layout can be split into and what should be the gutter size.
+ *
+ * @constructor create an instance of [PaneScaffoldDirective]
+ * @param contentPadding Size of the paddings between the panes and the outer bounds of the layout.
+ * @param maxHorizontalPartitions the max number of partitions along the horizontal axis the layout
+ *        can be split into.
+ * @param horizontalPartitionSpacerSize Size of the spacers between horizontal partitions.
+ *        It's equivalent to the left/right margins the horizontal partitions.
+ * @param maxVerticalPartitions the max number of partitions along the vertical axis the layout can
+ *        be split into.
+ * @param verticalPartitionSpacerSize Size of the spacers between vertical partitions.
+ *        It's equivalent to the top/bottom margins of the vertical partitions.
+ * @param excludedBounds the bounds of all areas in the window that the layout needs to avoid
+ *        displaying anything upon it. Usually these bounds represent where physical hinges are.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Immutable
+class PaneScaffoldDirective(
+    val contentPadding: PaddingValues,
+    val maxHorizontalPartitions: Int,
+    val horizontalPartitionSpacerSize: Dp,
+    val maxVerticalPartitions: Int,
+    val verticalPartitionSpacerSize: Dp,
+    val excludedBounds: List<Rect>
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is PaneScaffoldDirective) return false
+        if (contentPadding != other.contentPadding) return false
+        if (maxHorizontalPartitions != other.maxHorizontalPartitions) return false
+        if (horizontalPartitionSpacerSize != other.horizontalPartitionSpacerSize) return false
+        if (maxVerticalPartitions != other.maxVerticalPartitions) return false
+        if (verticalPartitionSpacerSize != other.verticalPartitionSpacerSize) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = contentPadding.hashCode()
+        result = 31 * result + maxHorizontalPartitions
+        result = 31 * result + horizontalPartitionSpacerSize.hashCode()
+        result = 31 * result + maxVerticalPartitions
+        result = 31 * result + verticalPartitionSpacerSize.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "PaneScaffoldDirective(contentPadding=$contentPadding, " +
+            "maxHorizontalPartitions=$maxHorizontalPartitions, " +
+            "horizontalPartitionSpacerSize=$horizontalPartitionSpacerSize, " +
+            "maxVerticalPartitions=$maxVerticalPartitions, " +
+            "verticalPartitionSpacerSize=$verticalPartitionSpacerSize, " +
+            "number of excluded bounds=${excludedBounds.size})"
+    }
+}
+
+/** Policies that indicate how hinges are supposed to be addressed in an adaptive layout. */
+@Immutable
[email protected]
+value class HingePolicy private constructor(private val value: Int) {
+    override fun toString(): String {
+        return "HingePolicy." + when (this) {
+            AlwaysAvoid -> "AlwaysAvoid"
+            AvoidSeparating -> "AvoidOccludingAndSeparating"
+            AvoidOccluding -> "AvoidOccludingOnly"
+            NeverAvoid -> "NeverAvoid"
+            else -> ""
+        }
+    }
+
+    companion object {
+        /** When rendering content in a layout, always avoid where hinges are. */
+        val AlwaysAvoid = HingePolicy(0)
+        /**
+         * When rendering content in a layout, avoid hinges that are separating. Note that an
+         * occluding hinge is supposed to be separating as well but not vice versa.
+         */
+        val AvoidSeparating = HingePolicy(1)
+        /**
+         * When rendering content in a layout, avoid hinges that are occluding. Note that an
+         * occluding hinge is supposed to be separating as well but not vice versa.
+         */
+        val AvoidOccluding = HingePolicy(2)
+        /** When rendering content in a layout, never avoid any hinges, separating or not. */
+        val NeverAvoid = HingePolicy(3)
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
new file mode 100644
index 0000000..7764d64
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
+ * panes in a canonical supporting-pane layout.
+ *
+ * @param supportingPane the supporting pane of the scaffold.
+ *        See [SupportingPaneScaffoldRole.Supporting].
+ * @param modifier [Modifier] of the scaffold layout.
+ * @param scaffoldState the state of the scaffold, which provides the current scaffold directive
+ *        and scaffold value.
+ * @param windowInsets window insets that the scaffold will respect.
+ * @param extraPane the extra pane of the scaffold. See [SupportingPaneScaffoldRole.Extra].
+ * @param mainPane the main pane of the scaffold. See [SupportingPaneScaffoldRole.Main].
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun SupportingPaneScaffold(
+    supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    scaffoldState: ThreePaneScaffoldState = calculateSupportingPaneScaffoldState(),
+    windowInsets: WindowInsets = SupportingPaneScaffoldDefaults.windowInsets,
+    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    mainPane: @Composable ThreePaneScaffoldScope.() -> Unit
+) {
+    ThreePaneScaffold(
+        modifier = modifier.fillMaxSize(),
+        scaffoldDirective = scaffoldState.scaffoldDirective,
+        scaffoldValue = scaffoldState.scaffoldValue,
+        paneOrder = ThreePaneScaffoldDefaults.SupportingPaneLayoutPaneOrder,
+        windowInsets = windowInsets,
+        secondaryPane = supportingPane,
+        tertiaryPane = extraPane,
+        primaryPane = mainPane
+    )
+}
+
+/**
+ * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
+ * [ThreePaneScaffoldAdaptStrategies], and the current pane destination of a
+ * [SupportingPaneScaffold].
+ *
+ * @param currentDestination the current destination item, which will be guaranteed to have the
+ *        highest priority when deciding pane visibilities.
+ * @param scaffoldDirective the layout directives that the associated [SupportingPaneScaffold]
+ *        needs to follow. The default value will be the calculation result from
+ *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
+ *        will be automatically updated when the window configuration changes.
+ * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateSupportingPaneScaffoldState(
+    currentDestination: ThreePaneScaffoldDestinationItem<*> =
+        ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        SupportingPaneScaffoldDefaults.adaptStrategies()
+): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
+    scaffoldDirective,
+    calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        adaptStrategies,
+        currentDestination
+    )
+)
+
+/**
+ * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
+ * [ThreePaneScaffoldAdaptStrategies], and the pane destination history of a
+ * [SupportingPaneScaffold].
+ *
+ * @param destinationHistory The history of past destination items. The last destination will
+ *        have the highest priority, and the second last destination will have the second highest
+ *        priority, and so forth until all panes have a priority assigned. Note that the last
+ *        destination is supposed to be the last item of the provided list. When the history is
+ *        empty or there are panes left unassigned, default priorities will be assigned to those
+ *        panes in the order of Main > Supporting > Extra.
+ * @param scaffoldDirective the layout directives that the associated [SupportingPaneScaffold]
+ *        needs to follow. The default value will be the calculation result from
+ *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
+ *        will be automatically updated when the window configuration changes.
+ * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateSupportingPaneScaffoldState(
+    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        SupportingPaneScaffoldDefaults.adaptStrategies()
+): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
+    scaffoldDirective,
+    calculateThreePaneScaffoldValue(
+        scaffoldDirective.maxHorizontalPartitions,
+        adaptStrategies,
+        destinationHistory
+    )
+)
+
+/**
+ * Provides default values of [SupportingPaneScaffold].
+ */
+@ExperimentalMaterial3AdaptiveApi
+object SupportingPaneScaffoldDefaults {
+    /**
+     * Default insets that will be used and consumed by [SupportingPaneScaffold]. By default it will
+     * be the union of [WindowInsets.Companion.systemBars] and
+     * [WindowInsets.Companion.displayCutout].
+     */
+    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
+
+    /**
+     * Creates a default [ThreePaneScaffoldAdaptStrategies] for [SupportingPaneScaffold].
+     *
+     * @param mainPaneAdaptStrategy the adapt strategy of the main pane
+     * @param supportingPaneAdaptStrategy the adapt strategy of the supporting pane
+     * @param extraPaneAdaptStrategy the adapt strategy of the extra pane
+     */
+    fun adaptStrategies(
+        mainPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        supportingPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+    ): ThreePaneScaffoldAdaptStrategies =
+        ThreePaneScaffoldAdaptStrategies(
+            mainPaneAdaptStrategy,
+            supportingPaneAdaptStrategy,
+            extraPaneAdaptStrategy
+        )
+}
+
+/**
+ * The set of the available pane roles of [SupportingPaneScaffold]. Basically those values are
+ * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
+ * the raw [ThreePaneScaffoldRole] under the context of [SupportingPaneScaffold] for better
+ * code clarity.
+ */
+@ExperimentalMaterial3AdaptiveApi
+object SupportingPaneScaffoldRole {
+    /**
+     * The main pane of [SupportingPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Primary].
+     */
+    val Main = ThreePaneScaffoldRole.Primary
+
+    /**
+     * The supporting pane of [SupportingPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Secondary].
+     */
+    val Supporting = ThreePaneScaffoldRole.Secondary
+
+    /**
+     * The extra pane of [SupportingPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Tertiary].
+     */
+    val Extra = ThreePaneScaffoldRole.Tertiary
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
new file mode 100644
index 0000000..ba986e4
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.adaptive.layout
+
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.unit.IntOffset
+
+/**
+ * Holds the transitions that can be applied to the different panes.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Immutable
+internal class ThreePaneMotion internal constructor(
+    internal val animationSpec: FiniteAnimationSpec<IntOffset> = snap(),
+    private val firstPaneEnterTransition: EnterTransition = EnterTransition.None,
+    private val firstPaneExitTransition: ExitTransition = ExitTransition.None,
+    private val secondPaneEnterTransition: EnterTransition = EnterTransition.None,
+    private val secondPaneExitTransition: ExitTransition = ExitTransition.None,
+    private val thirdPaneEnterTransition: EnterTransition = EnterTransition.None,
+    private val thirdPaneExitTransition: ExitTransition = ExitTransition.None
+) {
+
+    /**
+     * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole]
+     * at the given [ThreePaneScaffoldHorizontalOrder].
+     */
+    fun enterTransition(
+        role: ThreePaneScaffoldRole,
+        paneOrder: ThreePaneScaffoldHorizontalOrder
+    ): EnterTransition {
+        // Quick return in case this instance is the NoMotion one.
+        if (this === NoMotion) return EnterTransition.None
+
+        return when (paneOrder.indexOf(role)) {
+            0 -> firstPaneEnterTransition
+            1 -> secondPaneEnterTransition
+            else -> thirdPaneEnterTransition
+        }
+    }
+
+    /**
+     * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole]
+     * at the given [ThreePaneScaffoldHorizontalOrder].
+     */
+    fun exitTransition(
+        role: ThreePaneScaffoldRole,
+        paneOrder: ThreePaneScaffoldHorizontalOrder
+    ): ExitTransition {
+        // Quick return in case this instance is the NoMotion one.
+        if (this === NoMotion) return ExitTransition.None
+
+        return when (paneOrder.indexOf(role)) {
+            0 -> firstPaneExitTransition
+            1 -> secondPaneExitTransition
+            else -> thirdPaneExitTransition
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ThreePaneMotion) return false
+        if (this.animationSpec != other.animationSpec) return false
+        if (this.firstPaneEnterTransition != other.firstPaneEnterTransition) return false
+        if (this.firstPaneExitTransition != other.firstPaneExitTransition) return false
+        if (this.secondPaneEnterTransition != other.secondPaneEnterTransition) return false
+        if (this.secondPaneExitTransition != other.secondPaneExitTransition) return false
+        if (this.thirdPaneEnterTransition != other.thirdPaneEnterTransition) return false
+        if (this.thirdPaneExitTransition != other.thirdPaneExitTransition) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = animationSpec.hashCode()
+        result = 31 * result + firstPaneEnterTransition.hashCode()
+        result = 31 * result + firstPaneExitTransition.hashCode()
+        result = 31 * result + secondPaneEnterTransition.hashCode()
+        result = 31 * result + secondPaneExitTransition.hashCode()
+        result = 31 * result + thirdPaneEnterTransition.hashCode()
+        result = 31 * result + thirdPaneExitTransition.hashCode()
+        return result
+    }
+
+    companion object {
+        /**
+         * A ThreePaneMotion with all transitions set to [EnterTransition.None] and
+         * [ExitTransition.None].
+         */
+        val NoMotion = ThreePaneMotion()
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal fun calculateThreePaneMotion(
+    previousScaffoldValue: ThreePaneScaffoldValue,
+    currentScaffoldValue: ThreePaneScaffoldValue,
+    paneOrder: ThreePaneScaffoldHorizontalOrder
+): ThreePaneMotion {
+    if (previousScaffoldValue.equals(currentScaffoldValue)) {
+        return ThreePaneMotion.NoMotion
+    }
+    val previousExpandedCount = previousScaffoldValue.expandedCount
+    val currentExpandedCount = currentScaffoldValue.expandedCount
+    if (previousExpandedCount != currentExpandedCount) {
+        // TODO(conradchen): Address this case
+        return ThreePaneMotion.NoMotion
+    }
+    return when (previousExpandedCount) {
+        1 -> when (PaneAdaptedValue.Expanded) {
+            previousScaffoldValue[paneOrder.firstPane] -> {
+                ThreePaneMotionDefaults.movePanesToLeftMotion
+            }
+
+            previousScaffoldValue[paneOrder.thirdPane] -> {
+                ThreePaneMotionDefaults.movePanesToRightMotion
+            }
+
+            currentScaffoldValue[paneOrder.thirdPane] -> {
+                ThreePaneMotionDefaults.movePanesToLeftMotion
+            }
+
+            else -> {
+                ThreePaneMotionDefaults.movePanesToRightMotion
+            }
+        }
+
+        2 -> when {
+            previousScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded &&
+                currentScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded -> {
+                // The first pane stays, the right two panes switch
+                ThreePaneMotionDefaults.switchRightTwoPanesMotion
+            }
+
+            previousScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded &&
+                currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> {
+                // The third pane stays, the left two panes switch
+                ThreePaneMotionDefaults.switchLeftTwoPanesMotion
+            }
+
+            // Implies the second pane stays hereafter
+            currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> {
+                // The third pane shows, all panes move left
+                ThreePaneMotionDefaults.movePanesToLeftMotion
+            }
+
+            else -> {
+                // The first pane shows, all panes move right
+                ThreePaneMotionDefaults.movePanesToRightMotion
+            }
+        }
+
+        else -> {
+            // Should not happen
+            ThreePaneMotion.NoMotion
+        }
+    }
+}
+
+@ExperimentalMaterial3AdaptiveApi
+internal object ThreePaneMotionDefaults {
+    /**
+     * A default [SpringSpec] for the panes motion.
+     */
+    // TODO(conradchen): open this to public when we support motion customization
+    val PaneSpringSpec: SpringSpec<IntOffset> =
+        spring(
+            dampingRatio = 0.8f,
+            stiffness = 600f,
+            visibilityThreshold = IntOffset.VisibilityThreshold
+        )
+
+    private val slideInFromLeft = slideInHorizontally(PaneSpringSpec) { -it }
+    private val slideInFromRight = slideInHorizontally(PaneSpringSpec) { it }
+    private val slideOutToLeft = slideOutHorizontally(PaneSpringSpec) { -it }
+    private val slideOutToRight = slideOutHorizontally(PaneSpringSpec) { it }
+
+    val movePanesToRightMotion = ThreePaneMotion(
+        PaneSpringSpec,
+        slideInFromLeft,
+        slideOutToRight,
+        slideInFromLeft,
+        slideOutToRight,
+        slideInFromLeft,
+        slideOutToRight
+    )
+
+    val movePanesToLeftMotion = ThreePaneMotion(
+        PaneSpringSpec,
+        slideInFromRight,
+        slideOutToLeft,
+        slideInFromRight,
+        slideOutToLeft,
+        slideInFromRight,
+        slideOutToLeft
+    )
+
+    val switchLeftTwoPanesMotion = ThreePaneMotion(
+        PaneSpringSpec,
+        slideInFromLeft,
+        slideOutToLeft,
+        slideInFromLeft,
+        slideOutToLeft,
+        EnterTransition.None,
+        ExitTransition.None
+    )
+
+    val switchRightTwoPanesMotion = ThreePaneMotion(
+        PaneSpringSpec,
+        EnterTransition.None,
+        ExitTransition.None,
+        slideInFromRight,
+        slideOutToRight,
+        slideInFromRight,
+        slideOutToRight
+    )
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
new file mode 100644
index 0000000..0471d58
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -0,0 +1,713 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.MultiContentMeasurePolicy
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.roundToIntRect
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A pane scaffold composable that can display up to three panes according to the instructions
+ * provided by [ThreePaneScaffoldValue] in the order that [ThreePaneScaffoldHorizontalOrder]
+ * specifies, and allocate margins and spacers according to [PaneScaffoldDirective].
+ *
+ * [ThreePaneScaffold] is the base composable functions of adaptive programming. Developers can
+ * freely pipeline the relevant adaptive signals and use them as input of the scaffold function
+ * to render the final adaptive layout.
+ *
+ * It's recommended to use [ThreePaneScaffold] with [calculateStandardPaneScaffoldDirective],
+ * [calculateThreePaneScaffoldValue] to follow the Material design guidelines on adaptive
+ * programming.
+ *
+ * @param modifier The modifier to be applied to the layout.
+ * @param scaffoldDirective The top-level directives about how the scaffold should arrange its panes.
+ * @param scaffoldValue The current adapted value of the scaffold.
+ * @param paneOrder The horizontal order of the panes from start to end in the scaffold.
+ * @param secondaryPane The content of the secondary pane that has a priority lower then the primary
+ *                      pane but higher than the tertiary pane.
+ * @param tertiaryPane The content of the tertiary pane that has the lowest priority.
+ * @param primaryPane The content of the primary pane that has the highest priority.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+internal fun ThreePaneScaffold(
+    modifier: Modifier,
+    scaffoldDirective: PaneScaffoldDirective,
+    scaffoldValue: ThreePaneScaffoldValue,
+    paneOrder: ThreePaneScaffoldHorizontalOrder,
+    windowInsets: WindowInsets,
+    secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val ltrPaneOrder = remember(paneOrder, layoutDirection) {
+        paneOrder.toLtrOrder(layoutDirection)
+    }
+    val previousScaffoldValue = remember { ThreePaneScaffoldValueHolder(scaffoldValue) }
+    val paneMotion = calculateThreePaneMotion(
+        previousScaffoldValue = previousScaffoldValue.value,
+        currentScaffoldValue = scaffoldValue,
+        paneOrder = ltrPaneOrder
+    )
+    previousScaffoldValue.value = scaffoldValue
+
+    // Create PaneWrappers for each of the panes and map the transitions according to each pane
+    // role and order.
+    val contents = listOf<@Composable () -> Unit>(
+        {
+            remember { ThreePaneScaffoldScopeImpl() }.apply {
+                paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Primary]
+                positionAnimationSpec = paneMotion.animationSpec
+                enterTransition = paneMotion.enterTransition(
+                    ThreePaneScaffoldRole.Primary,
+                    ltrPaneOrder
+                )
+                exitTransition = paneMotion.exitTransition(
+                    ThreePaneScaffoldRole.Primary,
+                    ltrPaneOrder
+                )
+                animationToolingLabel = "Primary"
+            }.primaryPane()
+        },
+        {
+            remember { ThreePaneScaffoldScopeImpl() }.apply {
+                paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Secondary]
+                positionAnimationSpec = paneMotion.animationSpec
+                enterTransition = paneMotion.enterTransition(
+                    ThreePaneScaffoldRole.Secondary,
+                    ltrPaneOrder
+                )
+                exitTransition = paneMotion.exitTransition(
+                    ThreePaneScaffoldRole.Secondary,
+                    ltrPaneOrder
+                )
+                animationToolingLabel = "Secondary"
+            }.secondaryPane()
+        },
+        {
+            if (tertiaryPane != null) {
+                remember { ThreePaneScaffoldScopeImpl() }.apply {
+                    paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Tertiary]
+                    positionAnimationSpec = paneMotion.animationSpec
+                    enterTransition = paneMotion.enterTransition(
+                        ThreePaneScaffoldRole.Tertiary,
+                        ltrPaneOrder
+                    )
+                    exitTransition = paneMotion.exitTransition(
+                        ThreePaneScaffoldRole.Tertiary,
+                        ltrPaneOrder
+                    )
+                    animationToolingLabel = "Tertiary"
+                }.tertiaryPane()
+            }
+        },
+    )
+
+    val measurePolicy = remember {
+        ThreePaneContentMeasurePolicy(scaffoldDirective, scaffoldValue, ltrPaneOrder, windowInsets)
+    }.apply {
+        this.scaffoldDirective = scaffoldDirective
+        this.scaffoldValue = scaffoldValue
+        this.paneOrder = ltrPaneOrder
+        this.windowInsets = windowInsets
+    }
+
+    LookaheadScope {
+        Layout(
+            contents = contents,
+            modifier = modifier,
+            measurePolicy = measurePolicy
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private class ThreePaneScaffoldValueHolder(var value: ThreePaneScaffoldValue)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private class ThreePaneContentMeasurePolicy(
+    scaffoldDirective: PaneScaffoldDirective,
+    scaffoldValue: ThreePaneScaffoldValue,
+    paneOrder: ThreePaneScaffoldHorizontalOrder,
+    windowInsets: WindowInsets
+) : MultiContentMeasurePolicy {
+    var scaffoldDirective by mutableStateOf(scaffoldDirective)
+    var scaffoldValue by mutableStateOf(scaffoldValue)
+    var paneOrder by mutableStateOf(paneOrder)
+    var windowInsets by mutableStateOf(windowInsets)
+
+    /**
+     * Data class that is used to store the position and width of an expanded pane to be reused when
+     * the pane is being hidden.
+     */
+    private data class PanePlacement(var positionX: Int = 0, var measuredWidth: Int = 0)
+
+    private val placementsCache = mapOf(
+        ThreePaneScaffoldRole.Primary to PanePlacement(),
+        ThreePaneScaffoldRole.Secondary to PanePlacement(),
+        ThreePaneScaffoldRole.Tertiary to PanePlacement()
+    )
+
+    override fun MeasureScope.measure(
+        measurables: List<List<Measurable>>,
+        constraints: Constraints
+    ): MeasureResult {
+        val primaryMeasurables = measurables[0]
+        val secondaryMeasurables = measurables[1]
+        val tertiaryMeasurables = measurables[2]
+        return layout(constraints.maxWidth, constraints.maxHeight) {
+            if (coordinates == null) {
+                return@layout
+            }
+            val visiblePanes = getPanesMeasurables(
+                paneOrder = paneOrder,
+                primaryMeasurables = primaryMeasurables,
+                scaffoldValue = scaffoldValue,
+                secondaryMeasurables = secondaryMeasurables,
+                tertiaryMeasurables = tertiaryMeasurables
+            ) {
+                it != PaneAdaptedValue.Hidden
+            }
+
+            val hiddenPanes = getPanesMeasurables(
+                paneOrder = paneOrder,
+                primaryMeasurables = primaryMeasurables,
+                scaffoldValue = scaffoldValue,
+                secondaryMeasurables = secondaryMeasurables,
+                tertiaryMeasurables = tertiaryMeasurables
+            ) {
+                it == PaneAdaptedValue.Hidden
+            }
+
+            val verticalSpacerSize = scaffoldDirective.horizontalPartitionSpacerSize.roundToPx()
+            val leftContentPadding = max(
+                scaffoldDirective.contentPadding.calculateLeftPadding(layoutDirection).roundToPx(),
+                windowInsets.getLeft(this@measure, layoutDirection)
+            )
+            val rightContentPadding = max(
+                scaffoldDirective.contentPadding.calculateRightPadding(layoutDirection).roundToPx(),
+                windowInsets.getRight(this@measure, layoutDirection)
+            )
+            val topContentPadding = max(
+                scaffoldDirective.contentPadding.calculateTopPadding().roundToPx(),
+                windowInsets.getTop(this@measure)
+            )
+            val bottomContentPadding = max(
+                scaffoldDirective.contentPadding.calculateBottomPadding().roundToPx(),
+                windowInsets.getBottom(this@measure)
+            )
+            val outerBounds = IntRect(
+                leftContentPadding,
+                topContentPadding,
+                constraints.maxWidth - rightContentPadding,
+                constraints.maxHeight - bottomContentPadding
+            )
+
+            if (scaffoldDirective.excludedBounds.isNotEmpty()) {
+                val layoutBounds = coordinates!!.boundsInWindow()
+                val layoutPhysicalPartitions = mutableListOf<Rect>()
+                var actualLeft = layoutBounds.left + leftContentPadding
+                var actualRight = layoutBounds.right - rightContentPadding
+                val actualTop = layoutBounds.top + topContentPadding
+                val actualBottom = layoutBounds.bottom - bottomContentPadding
+                // Assume hinge bounds are sorted from left to right, non-overlapped.
+                @Suppress("ListIterator")
+                scaffoldDirective.excludedBounds.forEach { hingeBound ->
+                    if (hingeBound.left <= actualLeft) {
+                        // The hinge is at the left of the layout, adjust the left edge of
+                        // the current partition to the actual displayable bounds.
+                        actualLeft = max(actualLeft, hingeBound.right)
+                    } else if (hingeBound.right >= actualRight) {
+                        // The hinge is right at the right of the layout and there's no more
+                        // room for more partitions, adjust the right edge of the current
+                        // partition to the actual displayable bounds.
+                        actualRight = min(hingeBound.left, actualRight)
+                        return@forEach
+                    } else {
+                        // The hinge is inside the layout, add the current partition to the list
+                        // and move the left edge of the next partition to the right of the
+                        // hinge.
+                        layoutPhysicalPartitions.add(
+                            Rect(actualLeft, actualTop, hingeBound.left, actualBottom)
+                        )
+                        actualLeft +=
+                            max(hingeBound.right, hingeBound.left + verticalSpacerSize)
+                    }
+                }
+                if (actualLeft < actualRight) {
+                    // The last partition
+                    layoutPhysicalPartitions.add(
+                        Rect(actualLeft, actualTop, actualRight, actualBottom)
+                    )
+                }
+                if (layoutPhysicalPartitions.size == 0) {
+                    // Display nothing
+                } else if (layoutPhysicalPartitions.size == 1) {
+                    measureAndPlacePanes(
+                        layoutPhysicalPartitions[0],
+                        verticalSpacerSize,
+                        visiblePanes,
+                        isLookingAhead
+                    )
+                } else if (layoutPhysicalPartitions.size < visiblePanes.size) {
+                    // Note that the only possible situation is we have only two physical partitions
+                    // but three expanded panes to show. In this case fit two panes in the larger
+                    // partition.
+                    if (layoutPhysicalPartitions[0].width > layoutPhysicalPartitions[1].width) {
+                        measureAndPlacePanes(
+                            layoutPhysicalPartitions[0],
+                            verticalSpacerSize,
+                            visiblePanes.subList(0, 2),
+                            isLookingAhead
+                        )
+                        measureAndPlacePane(
+                            layoutPhysicalPartitions[1],
+                            visiblePanes[2],
+                            isLookingAhead
+                        )
+                    } else {
+                        measureAndPlacePane(
+                            layoutPhysicalPartitions[0],
+                            visiblePanes[0],
+                            isLookingAhead
+                        )
+                        measureAndPlacePanes(
+                            layoutPhysicalPartitions[1],
+                            verticalSpacerSize,
+                            visiblePanes.subList(1, 3),
+                            isLookingAhead
+                        )
+                    }
+                } else {
+                    // Layout each visible pane in a physical partition
+                    visiblePanes.fastForEachIndexed { index, paneMeasurable ->
+                        measureAndPlacePane(
+                            layoutPhysicalPartitions[index],
+                            paneMeasurable,
+                            isLookingAhead
+                        )
+                    }
+                }
+            } else {
+                measureAndPlacePanesWithLocalBounds(
+                    outerBounds,
+                    verticalSpacerSize,
+                    visiblePanes,
+                    isLookingAhead
+                )
+            }
+
+            // Place the hidden panes. Those should only exist when isLookingAhead = true.
+            // Placing these type of pane during the lookahead phase ensures a proper motion
+            // at the AnimatedVisibility.
+            // The placement is done using the outerBounds, as the placementsCache holds
+            // absolute position values.
+            placeHiddenPanes(
+                outerBounds.top,
+                outerBounds.height,
+                hiddenPanes
+            )
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun MeasureScope.getPanesMeasurables(
+        paneOrder: ThreePaneScaffoldHorizontalOrder,
+        primaryMeasurables: List<Measurable>,
+        scaffoldValue: ThreePaneScaffoldValue,
+        secondaryMeasurables: List<Measurable>,
+        tertiaryMeasurables: List<Measurable>,
+        predicate: (PaneAdaptedValue) -> Boolean
+    ): List<PaneMeasurable> {
+        return buildList {
+            paneOrder.forEach { role ->
+                if (predicate(scaffoldValue[role])) {
+                    when (role) {
+                        ThreePaneScaffoldRole.Primary -> {
+                            createPaneMeasurableIfNeeded(
+                                primaryMeasurables,
+                                ThreePaneScaffoldDefaults.PrimaryPanePriority,
+                                role,
+                                ThreePaneScaffoldDefaults.PrimaryPanePreferredWidth
+                                    .roundToPx()
+                            )
+                        }
+
+                        ThreePaneScaffoldRole.Secondary -> {
+                            createPaneMeasurableIfNeeded(
+                                secondaryMeasurables,
+                                ThreePaneScaffoldDefaults.SecondaryPanePriority,
+                                role,
+                                ThreePaneScaffoldDefaults.SecondaryPanePreferredWidth
+                                    .roundToPx()
+                            )
+                        }
+
+                        ThreePaneScaffoldRole.Tertiary -> {
+                            createPaneMeasurableIfNeeded(
+                                tertiaryMeasurables,
+                                ThreePaneScaffoldDefaults.TertiaryPanePriority,
+                                role,
+                                ThreePaneScaffoldDefaults.TertiaryPanePreferredWidth
+                                    .roundToPx()
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun MutableList<PaneMeasurable>.createPaneMeasurableIfNeeded(
+        measurables: List<Measurable>,
+        priority: Int,
+        role: ThreePaneScaffoldRole,
+        defaultPreferredWidth: Int
+    ) {
+        if (measurables.isNotEmpty()) {
+            add(PaneMeasurable(measurables[0], priority, role, defaultPreferredWidth))
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun Placeable.PlacementScope.measureAndPlacePane(
+        partitionBounds: Rect,
+        measurable: PaneMeasurable,
+        isLookingAhead: Boolean
+    ) {
+        val localBounds = getLocalBounds(partitionBounds)
+        measurable.measuredWidth = localBounds.width
+        measurable.apply {
+            measure(Constraints.fixed(measuredWidth, localBounds.height))
+                .place(localBounds.left, localBounds.top)
+        }
+        if (isLookingAhead) {
+            // Cache the values to be used when this measurable role is being hidden.
+            // See placeHiddenPanes.
+            val cachedPanePlacement = placementsCache[measurable.role]!!
+            cachedPanePlacement.measuredWidth = measurable.measuredWidth
+            cachedPanePlacement.positionX = localBounds.left
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun Placeable.PlacementScope.measureAndPlacePanes(
+        partitionBounds: Rect,
+        spacerSize: Int,
+        measurables: List<PaneMeasurable>,
+        isLookingAhead: Boolean
+    ) {
+        measureAndPlacePanesWithLocalBounds(
+            getLocalBounds(partitionBounds),
+            spacerSize,
+            measurables,
+            isLookingAhead
+        )
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun Placeable.PlacementScope.measureAndPlacePanesWithLocalBounds(
+        partitionBounds: IntRect,
+        spacerSize: Int,
+        measurables: List<PaneMeasurable>,
+        isLookingAhead: Boolean
+    ) {
+        if (measurables.isEmpty()) {
+            return
+        }
+        val allocatableWidth = partitionBounds.width - (measurables.size - 1) * spacerSize
+        val totalPreferredWidth = measurables.sumOf { it.measuredWidth }
+        if (allocatableWidth > totalPreferredWidth) {
+            // Allocate the remaining space to the pane with the highest priority.
+            measurables.maxBy {
+                it.priority
+            }.measuredWidth += allocatableWidth - totalPreferredWidth
+        } else if (allocatableWidth < totalPreferredWidth) {
+            // Scale down all panes to fit in the available space.
+            val scale = allocatableWidth.toFloat() / totalPreferredWidth
+            measurables.fastForEach {
+                it.measuredWidth = (it.measuredWidth * scale).toInt()
+            }
+        }
+        var positionX = partitionBounds.left
+        measurables.fastForEach {
+            it.measure(Constraints.fixed(it.measuredWidth, partitionBounds.height))
+                .place(positionX, partitionBounds.top)
+            if (isLookingAhead) {
+                // Cache the values to be used when this measurable's role is being hidden.
+                // See placeHiddenPanes.
+                val cachedPanePlacement = placementsCache[it.role]!!
+                cachedPanePlacement.measuredWidth = it.measuredWidth
+                cachedPanePlacement.positionX = positionX
+            }
+            positionX += it.measuredWidth + spacerSize
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+    private fun Placeable.PlacementScope.placeHiddenPanes(
+        partitionTop: Int,
+        partitionHeight: Int,
+        measurables: List<PaneMeasurable>
+    ) {
+        // When panes are being hidden, apply each pane's width and position from the cache to
+        // maintain the those before it's hidden by the AnimatedVisibility.
+        measurables.fastForEach {
+            if (!it.isAnimatedPane) {
+                // When panes are not animated, we don't need to measure and place them.
+                return
+            }
+            val cachedPanePlacement = placementsCache[it.role]!!
+            it.measure(
+                Constraints.fixed(
+                    width = cachedPanePlacement.measuredWidth,
+                    height = partitionHeight
+                )
+            ).place(
+                cachedPanePlacement.positionX,
+                partitionTop,
+                ThreePaneScaffoldDefaults.HiddenPaneZIndex
+            )
+        }
+    }
+
+    private fun Placeable.PlacementScope.getLocalBounds(bounds: Rect): IntRect {
+        return bounds.translate(coordinates!!.windowToLocal(Offset.Zero)).roundToIntRect()
+    }
+}
+
+/**
+ * A conditional [Modifier.clipToBounds] that will only clip when the given [adaptedValue] is
+ * [PaneAdaptedValue.Hidden].
+ */
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun Modifier.clipToBounds(adaptedValue: PaneAdaptedValue): Modifier =
+    if (adaptedValue == PaneAdaptedValue.Hidden) this.clipToBounds() else this
+
+/**
+ * The root composable of pane contents in a [ThreePaneScaffold] that supports default motions
+ * during pane switching. It's recommended to use this composable to wrap your own contents when
+ * passing them into pane parameters of the scaffold functions, therefore your panes can have a
+ * nice default animation for free.
+ *
+ * See usage samples at:
+ * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSample
+ * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSampleWithExtraPane
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun ThreePaneScaffoldScope.AnimatedPane(
+    modifier: Modifier,
+    content: (@Composable ThreePaneScaffoldScope.() -> Unit),
+) {
+    AnimatedVisibility(
+        visible = paneAdaptedValue == PaneAdaptedValue.Expanded,
+        modifier = modifier
+            .animatedPane()
+            .clipToBounds(paneAdaptedValue)
+            .then(
+                if (paneAdaptedValue == PaneAdaptedValue.Expanded) {
+                    Modifier.animateBounds(
+                        // TODO Figure out why we need to pass a non-null here to get the bounds
+                        //  animation going on the first navigation event that pass in the spec
+                        //  later on. To resolve this, we default to the paneSpringSpec().
+                        //  Otherwise, the first motion shows a snap instead of a smooth
+                        //  transition.
+                        positionAnimationSpec = positionAnimationSpec
+                            ?: ThreePaneMotionDefaults.PaneSpringSpec
+                    )
+                } else {
+                    Modifier
+                }
+            ),
+        enter = enterTransition,
+        exit = exitTransition,
+        label = "AnimatedVisibility: $animationToolingLabel"
+    ) {
+        content()
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private class PaneMeasurable(
+    val measurable: Measurable,
+    val priority: Int,
+    val role: ThreePaneScaffoldRole,
+    defaultPreferredWidth: Int
+) : Measurable by measurable {
+    private val data = ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData())
+
+    var measuredWidth = if (data.preferredWidth == null || data.preferredWidth!!.isNaN()) {
+        defaultPreferredWidth
+    } else {
+        data.preferredWidth!!.toInt()
+    }
+
+    val isAnimatedPane = data.isAnimatedPane
+}
+
+/**
+ * Scope for the panes of [ThreePaneScaffold].
+ */
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+interface ThreePaneScaffoldScope : PaneScaffoldScope {
+    /**
+     * The adapted value of the associated pane to the scope.
+     */
+    val paneAdaptedValue: PaneAdaptedValue
+
+    /**
+     * The position animation spec of the associated pane to the scope. [AnimatedPane] will use this
+     * value to perform pane animations during scaffold state changes.
+     */
+    val positionAnimationSpec: FiniteAnimationSpec<IntOffset>?
+
+    /**
+     * The [EnterTransition] of the associated pane. [AnimatedPane] will use this value to perform
+     * pane entering animations when it's showing during scaffold state changes.
+     */
+    val enterTransition: EnterTransition
+
+    /**
+     * The [ExitTransition] of the associated pane. [AnimatedPane] will use this value to perform
+     * pane exiting animations when it's hiding during scaffold state changes.
+     */
+    val exitTransition: ExitTransition
+
+    /**
+     * The label will be used by [AnimatedPane] to provide tooling labels to the foundation
+     * animation APIs like [AnimatedVisibility].
+     */
+    val animationToolingLabel: String
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private class ThreePaneScaffoldScopeImpl : ThreePaneScaffoldScope, PaneScaffoldScopeImpl() {
+    override var paneAdaptedValue by mutableStateOf(PaneAdaptedValue.Hidden)
+    override var positionAnimationSpec: FiniteAnimationSpec<IntOffset>? by mutableStateOf(null)
+    override var enterTransition by mutableStateOf(EnterTransition.None)
+    override var exitTransition by mutableStateOf(ExitTransition.None)
+    override var animationToolingLabel by mutableStateOf("")
+}
+
+/**
+ * Provides default values of [ThreePaneScaffold] and the calculation functions of
+ * [ThreePaneScaffoldValue].
+ */
+@ExperimentalMaterial3AdaptiveApi
+internal object ThreePaneScaffoldDefaults {
+    /**
+     * Denotes [ThreePaneScaffold] to use the list-detail pane-order to arrange its panes
+     * horizontally, which allocates panes in the order of secondary, primary, and tertiary from
+     * start to end.
+     */
+    // TODO(conradchen/sgibly): Consider moving this to the ListDetailPaneScaffoldDefaults
+    val ListDetailLayoutPaneOrder = ThreePaneScaffoldHorizontalOrder(
+        ThreePaneScaffoldRole.Secondary,
+        ThreePaneScaffoldRole.Primary,
+        ThreePaneScaffoldRole.Tertiary
+    )
+
+    /**
+     * Denotes [ThreePaneScaffold] to use the supporting-pane pane-order to arrange its panes
+     * horizontally, which allocates panes in the order of primary, secondary, and tertiary from
+     * start to end.
+     */
+    // TODO(conradchen/sgibly): Consider moving this to the SupportingPaneScaffoldDefaults
+    val SupportingPaneLayoutPaneOrder = ThreePaneScaffoldHorizontalOrder(
+        ThreePaneScaffoldRole.Primary,
+        ThreePaneScaffoldRole.Secondary,
+        ThreePaneScaffoldRole.Tertiary
+    )
+
+    /**
+     * The default preferred width of [ThreePaneScaffoldRole.Secondary]. See more details in
+     * [ThreePaneScaffoldScope.preferredWidth].
+     */
+    val SecondaryPanePreferredWidth = 412.dp
+
+    /**
+     * The default preferred width of [ThreePaneScaffoldRole.Tertiary]. See more details in
+     * [ThreePaneScaffoldScope.preferredWidth].
+     */
+    val TertiaryPanePreferredWidth = 412.dp
+
+    // Make it the same as the secondary and tertiary panes, so we can have a semi-50-50-split on
+    // narrower windows by default.
+    val PrimaryPanePreferredWidth = 412.dp
+
+    // TODO(conradchen): consider declaring a value class for priority
+    const val PrimaryPanePriority = 10
+    const val SecondaryPanePriority = 5
+    const val TertiaryPanePriority = 1
+
+    /**
+     * Creates a default [ThreePaneScaffoldAdaptStrategies].
+     *
+     * @param primaryPaneAdaptStrategy the adapt strategy of the primary pane
+     * @param secondaryPaneAdaptStrategy the adapt strategy of the secondary pane
+     * @param tertiaryPaneAdaptStrategy the adapt strategy of the tertiary pane
+     */
+    fun adaptStrategies(
+        primaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        secondaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+        tertiaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
+    ): ThreePaneScaffoldAdaptStrategies =
+        ThreePaneScaffoldAdaptStrategies(
+            primaryPaneAdaptStrategy,
+            secondaryPaneAdaptStrategy,
+            tertiaryPaneAdaptStrategy
+        )
+
+    /**
+     * The negative z-index of hidden panes to make visible panes always show upon hidden panes
+     * during pane animations.
+     */
+    const val HiddenPaneZIndex = -0.1f
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt
new file mode 100644
index 0000000..62fd21c
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+
+/**
+ * The adaptation specs of [ThreePaneScaffold]. This class denotes how each pane of
+ * [ThreePaneScaffold] should be adapted. It should be used as an input parameter of
+ * [calculateThreePaneScaffoldValue] to decide the [ThreePaneScaffoldValue].
+ *
+ * @constructor create an instance of [ThreePaneScaffoldAdaptStrategies]
+ * @param primaryPaneAdaptStrategy [AdaptStrategy] of the primary pane of [ThreePaneScaffold]
+ * @param secondaryPaneAdaptStrategy [AdaptStrategy] of the secondary pane of [ThreePaneScaffold]
+ * @param tertiaryPaneAdaptStrategy [AdaptStrategy] of the tertiary pane of [ThreePaneScaffold]
+ */
+@ExperimentalMaterial3AdaptiveApi
+class ThreePaneScaffoldAdaptStrategies(
+    private val primaryPaneAdaptStrategy: AdaptStrategy,
+    private val secondaryPaneAdaptStrategy: AdaptStrategy,
+    private val tertiaryPaneAdaptStrategy: AdaptStrategy
+) {
+    operator fun get(role: ThreePaneScaffoldRole): AdaptStrategy {
+        return when (role) {
+            ThreePaneScaffoldRole.Primary -> primaryPaneAdaptStrategy
+            ThreePaneScaffoldRole.Secondary -> secondaryPaneAdaptStrategy
+            ThreePaneScaffoldRole.Tertiary -> tertiaryPaneAdaptStrategy
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ThreePaneScaffoldAdaptStrategies) return false
+        if (primaryPaneAdaptStrategy != other.primaryPaneAdaptStrategy) return false
+        if (secondaryPaneAdaptStrategy != other.secondaryPaneAdaptStrategy) return false
+        if (tertiaryPaneAdaptStrategy != other.tertiaryPaneAdaptStrategy) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = primaryPaneAdaptStrategy.hashCode()
+        result = 31 * result + secondaryPaneAdaptStrategy.hashCode()
+        result = 31 * result + tertiaryPaneAdaptStrategy.hashCode()
+        return result
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt
new file mode 100644
index 0000000..797a08f
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+
+/**
+ * An item representing a navigation destination in a [ThreePaneScaffold].
+ *
+ * @param pane the pane destination of the navigation.
+ * @param content the optional content, or an id representing the content of the destination.
+ * The type [T] must be storable in a Bundle.
+ */
+@ExperimentalMaterial3AdaptiveApi
+class ThreePaneScaffoldDestinationItem<out T>(
+    val pane: ThreePaneScaffoldRole,
+    val content: T? = null,
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ThreePaneScaffoldDestinationItem<*>) return false
+
+        if (pane != other.pane) return false
+        if (content != other.content) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = pane.hashCode()
+        result = 31 * result + (content?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun toString(): String {
+        return "ThreePaneScaffoldDestinationItem(pane=$pane, content=$content)"
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
new file mode 100644
index 0000000..589c663
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Represents the horizontal order of panes in a [ThreePaneScaffold] from start to end. Note that
+ * the values of [firstPane], [secondPane] and [thirdPane] have to be different, otherwise
+ * [IllegalArgumentException] will be thrown.
+ *
+ * @constructor create an instance of [ThreePaneScaffoldHorizontalOrder]
+ * @param firstPane The first pane from the start of the [ThreePaneScaffold]
+ * @param secondPane The second pane from the start of the [ThreePaneScaffold]
+ * @param thirdPane The third pane from the start of the [ThreePaneScaffold]
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Immutable
+internal class ThreePaneScaffoldHorizontalOrder(
+    val firstPane: ThreePaneScaffoldRole,
+    val secondPane: ThreePaneScaffoldRole,
+    val thirdPane: ThreePaneScaffoldRole
+) {
+    init {
+        require(firstPane != secondPane && secondPane != thirdPane && firstPane != thirdPane) {
+            "invalid ThreePaneScaffoldHorizontalOrder($firstPane, $secondPane, $thirdPane)" +
+                " - panes must be unique"
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ThreePaneScaffoldHorizontalOrder) return false
+        if (firstPane != other.firstPane) return false
+        if (secondPane != other.secondPane) return false
+        if (thirdPane != other.thirdPane) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = firstPane.hashCode()
+        result = 31 * result + secondPane.hashCode()
+        result = 31 * result + thirdPane.hashCode()
+        return result
+    }
+}
+
+/**
+ * Converts a bidirectional order to a left-to-right order.
+ */
+@ExperimentalMaterial3AdaptiveApi
+internal fun ThreePaneScaffoldHorizontalOrder.toLtrOrder(
+    layoutDirection: LayoutDirection
+): ThreePaneScaffoldHorizontalOrder {
+    return if (layoutDirection == LayoutDirection.Rtl) {
+        ThreePaneScaffoldHorizontalOrder(
+            thirdPane,
+            secondPane,
+            firstPane
+        )
+    } else {
+        this
+    }
+}
+
+@ExperimentalMaterial3AdaptiveApi
+internal inline fun ThreePaneScaffoldHorizontalOrder.forEach(
+    action: (ThreePaneScaffoldRole) -> Unit
+) {
+    action(firstPane)
+    action(secondPane)
+    action(thirdPane)
+}
+
+@ExperimentalMaterial3AdaptiveApi
+internal inline fun ThreePaneScaffoldHorizontalOrder.forEachIndexed(
+    action: (Int, ThreePaneScaffoldRole) -> Unit
+) {
+    action(0, firstPane)
+    action(1, secondPane)
+    action(2, thirdPane)
+}
+
+@ExperimentalMaterial3AdaptiveApi
+internal fun ThreePaneScaffoldHorizontalOrder.indexOf(role: ThreePaneScaffoldRole): Int {
+    forEachIndexed { i, r ->
+        if (r == role) {
+            return i
+        }
+    }
+    // should never reach this far
+    return 0
+}
+
+/**
+ * The set of the available pane roles of [ThreePaneScaffold].
+ */
+@ExperimentalMaterial3AdaptiveApi
+enum class ThreePaneScaffoldRole {
+    /**
+     * The primary pane of [ThreePaneScaffold]. It is supposed to have the highest priority during
+     * layout adaptation and usually contains the most important content of the screen, like content
+     * details in a list-detail settings.
+     */
+    Primary,
+
+    /**
+     * The secondary pane of [ThreePaneScaffold]. It is supposed to have the second highest priority
+     * during layout adaptation and usually contains the supplement content of the screen, like
+     * content list in a list-detail settings.
+     */
+    Secondary,
+
+    /**
+     * The tertiary pane of [ThreePaneScaffold]. It is supposed to have the lowest priority during
+     * layout adaptation and usually contains the additional info which will only be shown under
+     * user interaction.
+     */
+    Tertiary
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
new file mode 100644
index 0000000..8d4bc0f
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Stable
+
+/**
+ * The state of [ThreePaneScaffold]. It provides the layout directive and value state that will
+ * be updated directly. It also provides functions to perform navigation.
+ *
+ * @property scaffoldDirective the current layout directives that the associated
+ *           [ThreePaneScaffold] needs to follow. It's supposed to be automatically updated
+ *           when the window configuration changes.
+ * @property scaffoldValue the current layout value of the associated [ThreePaneScaffold],
+ *           which represents unique layout states of the scaffold.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Stable
+interface ThreePaneScaffoldState {
+    val scaffoldDirective: PaneScaffoldDirective
+    val scaffoldValue: ThreePaneScaffoldValue
+}
+
+@ExperimentalMaterial3AdaptiveApi
+internal class ThreePaneScaffoldStateImpl(
+    override val scaffoldDirective: PaneScaffoldDirective,
+    override val scaffoldValue: ThreePaneScaffoldValue
+) : ThreePaneScaffoldState
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt
new file mode 100644
index 0000000..5440514
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.layout
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.util.fastForEachReversed
+
+@ExperimentalMaterial3AdaptiveApi
+private inline fun buildThreePaneScaffoldValue(
+    buildAction: (ThreePaneScaffoldRole) -> PaneAdaptedValue
+): ThreePaneScaffoldValue {
+    return ThreePaneScaffoldValue(
+        buildAction(ThreePaneScaffoldRole.Primary),
+        buildAction(ThreePaneScaffoldRole.Secondary),
+        buildAction(ThreePaneScaffoldRole.Tertiary)
+    )
+}
+
+/**
+ * Calculates the current adapted value of [ThreePaneScaffold] according to the given
+ * [maxHorizontalPartitions], [adaptStrategies] and [currentDestination]. The returned value can be
+ * used as a unique representation of the current layout structure.
+ *
+ * The function will treat the current destination as the highest priority and then adapt the rest
+ * panes according to the order of [ThreePaneScaffoldRole.Primary],
+ * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there
+ * are still remaining partitions to put the pane, the pane will be set as
+ * [PaneAdaptedValue.Expanded], otherwise it will be adapted according to its associated
+ * [AdaptStrategy].
+ *
+ * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
+ *        how many expanded panes can be shown at the same time.
+ * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
+ *        the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
+ * @param currentDestination The current destination item, which will be treated as having
+ *        the highest priority, can be `null`.
+ */
+@ExperimentalMaterial3AdaptiveApi
+fun calculateThreePaneScaffoldValue(
+    maxHorizontalPartitions: Int,
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
+    currentDestination: ThreePaneScaffoldDestinationItem<*>?,
+): ThreePaneScaffoldValue {
+    var expandedCount = if (currentDestination != null) 1 else 0
+    return buildThreePaneScaffoldValue { role ->
+        when {
+            role == currentDestination?.pane -> PaneAdaptedValue.Expanded
+            expandedCount < maxHorizontalPartitions -> {
+                expandedCount++
+                PaneAdaptedValue.Expanded
+            }
+
+            else -> adaptStrategies[role].adapt()
+        }
+    }
+}
+
+/**
+ * Calculates the current adapted value of [ThreePaneScaffold] according to the given
+ * [maxHorizontalPartitions], [adaptStrategies] and [destinationHistory]. The returned value can be
+ * used as a unique representation of the current layout structure.
+ *
+ * The function will treat the current focus as the highest priority and then adapt the rest
+ * panes according to the order of [ThreePaneScaffoldRole.Primary],
+ * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there are still
+ * remaining partitions to put the pane, the pane will be set as [PaneAdaptedValue.Expanded],
+ * otherwise it will be adapted according to its associated [AdaptStrategy].
+ *
+ * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
+ *        how many expanded panes can be shown at the same time.
+ * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
+ *        the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
+ * @param destinationHistory The history of past destination items. The last destination will have
+ *        the highest priority, and the second last destination will have the second highest
+ *        priority, and so forth until all panes have a priority assigned. Note that the last
+ *        destination is supposed to be the last item of the provided list.
+ */
+@ExperimentalMaterial3AdaptiveApi
+fun calculateThreePaneScaffoldValue(
+    maxHorizontalPartitions: Int,
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
+    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
+): ThreePaneScaffoldValue {
+    var expandedCount = 0
+    var primaryPaneAdaptedValue: PaneAdaptedValue? = null
+    var secondaryPaneAdaptedValue: PaneAdaptedValue? = null
+    var tertiaryPaneAdaptedValue: PaneAdaptedValue? = null
+    destinationHistory.fastForEachReversed {
+        if (expandedCount >= maxHorizontalPartitions) {
+            return@fastForEachReversed
+        }
+        when (it.pane) {
+            ThreePaneScaffoldRole.Primary -> {
+                if (primaryPaneAdaptedValue == null) {
+                    primaryPaneAdaptedValue = PaneAdaptedValue.Expanded
+                    expandedCount++
+                }
+            }
+            ThreePaneScaffoldRole.Secondary -> {
+                if (secondaryPaneAdaptedValue == null) {
+                    secondaryPaneAdaptedValue = PaneAdaptedValue.Expanded
+                    expandedCount++
+                }
+            }
+            ThreePaneScaffoldRole.Tertiary -> {
+                if (tertiaryPaneAdaptedValue == null) {
+                    tertiaryPaneAdaptedValue = PaneAdaptedValue.Expanded
+                    expandedCount++
+                }
+            }
+        }
+    }
+    return ThreePaneScaffoldValue(
+        primary = primaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
+            expandedCount++
+            PaneAdaptedValue.Expanded
+        } else {
+            adaptStrategies[ThreePaneScaffoldRole.Primary].adapt()
+        },
+        secondary = secondaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
+            expandedCount++
+            PaneAdaptedValue.Expanded
+        } else {
+            adaptStrategies[ThreePaneScaffoldRole.Secondary].adapt()
+        },
+        tertiary = tertiaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
+            expandedCount++
+            PaneAdaptedValue.Expanded
+        } else {
+            adaptStrategies[ThreePaneScaffoldRole.Tertiary].adapt()
+        }
+    )
+}
+
+/**
+ * The adapted value of [ThreePaneScaffold]. It contains each pane's adapted value.
+ * [ThreePaneScaffold] will use the adapted values to decide which panes should be displayed
+ * and how they should be displayed. With other input parameters of [ThreePaneScaffold] fixed,
+ * each possible instance of this class should represent a unique state of [ThreePaneScaffold]
+ * and developers can compare two [ThreePaneScaffoldValue] to decide if there is a layout structure
+ * change.
+ *
+ * For a Material-opinionated layout, it's suggested to use [calculateThreePaneScaffoldValue] to
+ * calculate the current scaffold value.
+ *
+ * @constructor create an instance of [ThreePaneScaffoldValue]
+ * @param primary [PaneAdaptedValue] of the primary pane of [ThreePaneScaffold]
+ * @param secondary [PaneAdaptedValue] of the secondary pane of [ThreePaneScaffold]
+ * @param tertiary [PaneAdaptedValue] of the tertiary pane of [ThreePaneScaffold]
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Immutable
+class ThreePaneScaffoldValue(
+    val primary: PaneAdaptedValue,
+    val secondary: PaneAdaptedValue,
+    val tertiary: PaneAdaptedValue
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ThreePaneScaffoldValue) return false
+        if (primary != other.primary) return false
+        if (secondary != other.secondary) return false
+        if (tertiary != other.tertiary) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = primary.hashCode()
+        result = 31 * result + secondary.hashCode()
+        result = 31 * result + tertiary.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ThreePaneScaffoldValue(primary=$primary, " +
+            "secondary=$secondary, " +
+            "tertiary=$tertiary)"
+    }
+
+    operator fun get(role: ThreePaneScaffoldRole): PaneAdaptedValue =
+        when (role) {
+            ThreePaneScaffoldRole.Primary -> primary
+            ThreePaneScaffoldRole.Secondary -> secondary
+            ThreePaneScaffoldRole.Tertiary -> tertiary
+        }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal val ThreePaneScaffoldValue.expandedCount: Int get() {
+    var count = 0
+    if (primary == PaneAdaptedValue.Expanded) {
+        count++
+    }
+    if (secondary == PaneAdaptedValue.Expanded) {
+        count++
+    }
+    if (tertiary == PaneAdaptedValue.Expanded) {
+        count++
+    }
+    return count
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
new file mode 100644
index 0000000..f3c507c
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -0,0 +1,32 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive.navigation {
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
+    method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior[] values();
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopLatest;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilContentChange;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilCurrentDestinationChange;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilScaffoldValueChange;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
+    method public boolean canNavigateBack(optional androidx.compose.material3.adaptive.navigation.BackNavigationBehavior backNavigationBehavior);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
+    method public boolean isDestinationHistoryAware();
+    method public boolean navigateBack(optional androidx.compose.material3.adaptive.navigation.BackNavigationBehavior backNavigationBehavior);
+    method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public void setDestinationHistoryAware(boolean);
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
+    property public abstract boolean isDestinationHistoryAware;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
+  }
+
+  public final class ThreePaneScaffoldNavigatorKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+  }
+
+}
+
diff --git a/compose/material3/material3-adaptive/api/res-current.txt b/compose/material3/adaptive/adaptive-navigation/api/res-current.txt
similarity index 100%
copy from compose/material3/material3-adaptive/api/res-current.txt
copy to compose/material3/adaptive/adaptive-navigation/api/res-current.txt
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
new file mode 100644
index 0000000..f3c507c
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -0,0 +1,32 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive.navigation {
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
+    method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior[] values();
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopLatest;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilContentChange;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilCurrentDestinationChange;
+    enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilScaffoldValueChange;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
+    method public boolean canNavigateBack(optional androidx.compose.material3.adaptive.navigation.BackNavigationBehavior backNavigationBehavior);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
+    method public boolean isDestinationHistoryAware();
+    method public boolean navigateBack(optional androidx.compose.material3.adaptive.navigation.BackNavigationBehavior backNavigationBehavior);
+    method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public void setDestinationHistoryAware(boolean);
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
+    property public abstract boolean isDestinationHistoryAware;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
+  }
+
+  public final class ThreePaneScaffoldNavigatorKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+  }
+
+}
+
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
new file mode 100644
index 0000000..3736767
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -0,0 +1,131 @@
+/*
+ * 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.tasks.KotlinCompile
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+}
+
+androidXMultiplatform {
+    android()
+    desktop()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(libs.kotlinStdlibCommon)
+                api("androidx.compose.foundation:foundation:1.6.0-rc01")
+                implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
+                implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
+                implementation(project(":compose:material3:adaptive:adaptive-layout"))
+                // TODO(conradchen): pin the depe when the change required is released to public
+                implementation(project(":window:window-core"))
+            }
+        }
+
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+                api("androidx.annotation:annotation-experimental:1.4.0")
+            }
+        }
+
+        desktopMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        desktopTest {
+            dependsOn(jvmTest)
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(project(":compose:material3:material3"))
+                implementation(project(":compose:test-utils"))
+                implementation(project(":window:window-testing"))
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+
+        androidUnitTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+    }
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive.navigation"
+}
+
+androidx {
+    name = "Material Adaptive"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Compose Material Design Adaptive Library"
+}
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
+}
+
+// Screenshot tests related setup
+android {
+    sourceSets.androidTest.assets.srcDirs +=
+            project.rootDir.absolutePath + "/../../golden/compose/material3/adaptive"
+    namespace "androidx.compose.material3.adaptive.navigation"
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
new file mode 100644
index 0000000..603fd04d
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.navigation
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.properties.Delegates
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListDetailPaneScaffoldNavigatorTest {
+    @get:Rule
+    val composeRule = createComposeRule()
+
+    @Test
+    fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateToExtra_hideListWhenNotHistoryAware() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                isDestinationHistoryAware = false
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateToExtra_keepListExpandedWhenHistoryAware() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                isDestinationHistoryAware = true
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            assertThat(canNavigateBack).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_withSimplePop_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(
+                    BackNavigationBehavior.PopUntilCurrentDestinationChange
+                )
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilCurrentDestinationChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceCurrentDestinationChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(
+                    BackNavigationBehavior.PopUntilCurrentDestinationChange
+                )
+            ).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                ),
+                isDestinationHistoryAware = true
+            )
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                ),
+                isDestinationHistoryAware = false
+            )
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.List)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+                scaffoldDirective = mockCurrentScaffoldDirective.value,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                )
+            )
+        }
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            // Switches to dual pane
+            mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(0.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 16.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun ThreePaneScaffoldValue.assert(
+    expectedDetailPaneAdaptedValue: PaneAdaptedValue,
+    expectedListPaneAdaptedValue: PaneAdaptedValue,
+    expectedExtraPaneAdaptedValue: PaneAdaptedValue
+) {
+    assertThat(this[ListDetailPaneScaffoldRole.Detail]).isEqualTo(expectedDetailPaneAdaptedValue)
+    assertThat(this[ListDetailPaneScaffoldRole.List]).isEqualTo(expectedListPaneAdaptedValue)
+    assertThat(this[ListDetailPaneScaffoldRole.Extra]).isEqualTo(expectedExtraPaneAdaptedValue)
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
new file mode 100644
index 0000000..8ef7193
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -0,0 +1,615 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.navigation
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.properties.Delegates
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SupportingPaneScaffoldNavigatorTest {
+    @get:Rule
+    val composeRule = createComposeRule()
+
+    @Test
+    fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            assertThat(canNavigateBack).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateToExtra_hideSupportingWhenNotHistoryAware() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0)
+                ),
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                isDestinationHistoryAware = false
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_navigateToExtra_keepSupportingExpandedWhenHistoryAware() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0)
+                ),
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                isDestinationHistoryAware = true
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(canNavigateBack).isTrue()
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        var canNavigateBack by Delegates.notNull<Boolean>()
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockSinglePaneScaffoldDirective
+            )
+            canNavigateBack = scaffoldNavigator.canNavigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(canNavigateBack).isTrue()
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
+            ).isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            assertThat(canNavigateBack).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_withSimplePop_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(
+                    BackNavigationBehavior.PopUntilCurrentDestinationChange
+                )
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilCurrentDestinationChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceCurrentDestinationChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(
+                    BackNavigationBehavior.PopUntilCurrentDestinationChange
+                )
+            ).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_canNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 1),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isTrue()
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceContentChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                )
+            )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            assertThat(
+                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
+            ).isFalse()
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 1),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                ),
+                isDestinationHistoryAware = true
+            )
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = MockDualPaneScaffoldDirective,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 1),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                ),
+                isDestinationHistoryAware = false
+            )
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            scaffoldNavigator.navigateBack()
+        }
+
+        composeRule.runOnIdle {
+            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
+                PaneAdaptedValue.Expanded,
+                PaneAdaptedValue.Hidden,
+                PaneAdaptedValue.Expanded
+            )
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
+            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+        val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
+
+        composeRule.setContent {
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+                scaffoldDirective = mockCurrentScaffoldDirective.value,
+                initialDestinationHistory = listOf(
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
+                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
+                )
+            )
+        }
+        composeRule.runOnIdle {
+            assertThat(
+                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
+            ).isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+            // Switches to dual pane
+            mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
+            assertThat(
+                scaffoldNavigator.currentDestination?.pane
+            ).isEqualTo(SupportingPaneScaffoldRole.Main)
+            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(0.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 16.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun ThreePaneScaffoldValue.assert(
+    expectedMainPaneAdaptedValue: PaneAdaptedValue,
+    expectedSupportingPaneAdaptedValue: PaneAdaptedValue,
+    expectedExtraPaneAdaptedValue: PaneAdaptedValue
+) {
+    assertThat(this[SupportingPaneScaffoldRole.Main]).isEqualTo(expectedMainPaneAdaptedValue)
+    assertThat(this[SupportingPaneScaffoldRole.Supporting]).isEqualTo(
+        expectedSupportingPaneAdaptedValue
+    )
+    assertThat(this[SupportingPaneScaffoldRole.Extra]).isEqualTo(expectedExtraPaneAdaptedValue)
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt
new file mode 100644
index 0000000..3ae80e2
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.adaptive.navigation
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
+
+/**
+ * A class to control how back navigation should behave in a [ThreePaneScaffoldNavigator].
+ */
+@ExperimentalMaterial3AdaptiveApi
+enum class BackNavigationBehavior {
+    /** Pop the latest destination from the backstack. */
+    PopLatest,
+
+    /**
+     * Pop destinations from the backstack until there is a change in the scaffold value.
+     *
+     * For example, in a single-pane layout, this will skip entries until the current destination
+     * is a different [ThreePaneScaffoldRole]. In a multi-pane layout, this will skip entries until
+     * the [PaneAdaptedValue] of any pane changes.
+     */
+    PopUntilScaffoldValueChange,
+
+    /**
+     * Pop destinations from the backstack until there is a change in the current destination pane.
+     *
+     * In a single-pane layout, this should behave similarly to [PopUntilScaffoldValueChange]. In a
+     * multi-pane layout, it is possible for both the current destination and previous destination
+     * to be showing at the same time, so this may not result in a visual change in the scaffold.
+     */
+    PopUntilCurrentDestinationChange,
+
+    /**
+     * Pop destinations from the backstack until there is a content change.
+     *
+     * A "content change" is defined as either a change in the content of the current
+     * [ThreePaneScaffoldDestinationItem], or a change in the scaffold value (similar to
+     * [PopUntilScaffoldValueChange]).
+     */
+    PopUntilContentChange,
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
new file mode 100644
index 0000000..e4c9e79
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.navigation
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
+import androidx.compose.material3.adaptive.layout.calculateStandardPaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.calculateThreePaneScaffoldValue
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastMap
+
+/**
+ * The common interface of the default navigation implementations for different three-pane
+ * scaffolds.
+ *
+ * In general, we suggest you to use [rememberListDetailPaneScaffoldNavigator] or
+ * [rememberSupportingPaneScaffoldNavigator] to get remembered default instances of this interface
+ * for [ListDetailPaneScaffold] and [SupportingPaneScaffold], respectively. Those default
+ * implementations work independently from any navigation frameworks.
+ *
+ * If you need to integrate with existing navigation frameworks or implement your own custom
+ * navigation logic, usually creating whole new APIs that's tailored for your own solution will be
+ * recommended, instead of implementing this interface. But we recommend you refer to the API design
+ * and the default implementation to get better understanding and address the intricacies of
+ * navigation in an adaptive scenario.
+ *
+ * @param T the type representing the content (or id of the content) for a navigation destination.
+ * This type must be storable in a Bundle.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Stable
+interface ThreePaneScaffoldNavigator<T> {
+    /**
+     * The current scaffold state provided by the navigator.
+     *
+     * Implementors of this interface should ensure this value is updated whenever a navigation
+     * operation is performed.
+     */
+    val scaffoldState: ThreePaneScaffoldState
+
+    /**
+     * The current destination as tracked by the navigator.
+     *
+     * Implementors of this interface should ensure this value is updated whenever a navigation
+     * operation is performed.
+     */
+    val currentDestination: ThreePaneScaffoldDestinationItem<T>?
+
+    /**
+     * Indicates if the navigator should be aware of pane destination history when deciding the
+     * result [ThreePaneScaffoldValue] by a navigation operation. If the value is `false`, only
+     * the current destination will be considered in the scaffold value calculation.
+     *
+     * @see calculateThreePaneScaffoldValue for more detailed explanation about history awareness.
+     */
+    var isDestinationHistoryAware: Boolean
+
+    /**
+     * Navigates to a new destination. The new destination is supposed to have the highest
+     * priority when calculating the new [scaffoldState]. When implementing this method, please
+     * ensure the new destination pane will be expanded or adapted in a reasonable way so it
+     * provides users the sense that the new destination is the pane under current usage.
+     *
+     * @param pane the new destination pane.
+     * @param content the optional content, or an id representing the content of the new
+     * destination.
+     */
+    fun navigateTo(pane: ThreePaneScaffoldRole, content: T? = null)
+
+    /**
+     * Returns `true` if there is a previous destination to navigate back to.
+     *
+     * Implementors of this interface should ensure the logic of this function is consistent with
+     * [navigateBack].
+     *
+     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
+     * during the back navigation. See [BackNavigationBehavior].
+     */
+    fun canNavigateBack(
+        backNavigationBehavior: BackNavigationBehavior =
+            BackNavigationBehavior.PopUntilScaffoldValueChange
+    ): Boolean
+
+    /**
+     * Navigates to the previous destination. Returns `true` if there is a previous destination to
+     * navigate back to. When implementing this function, please make sure the logic is consistent
+     * with [canNavigateBack].
+     *
+     * Implementors of this interface should ensure the logic of this function is consistent with
+     * [canNavigateBack].
+     *
+     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
+     * during the back navigation. See [BackNavigationBehavior].
+     */
+    fun navigateBack(
+        backNavigationBehavior: BackNavigationBehavior =
+            BackNavigationBehavior.PopUntilScaffoldValueChange
+    ): Boolean
+}
+
+/**
+ * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
+ * [ListDetailPaneScaffold], which will be updated automatically when the input values change.
+ * The default navigator is supposed to be used independently from any navigation frameworks and
+ * it will address the navigation purely inside the [ListDetailPaneScaffold].
+ *
+ * @param scaffoldDirective the current layout directives to follow. The default value will be
+ *        calculated with [calculateStandardPaneScaffoldDirective] using [WindowAdaptiveInfo]
+ *        retrieved from the current context.
+ * @param adaptStrategies adaptation strategies of each pane.
+ * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
+ *        full destination history, instead of just the current destination. See
+ *        [calculateThreePaneScaffoldValue] for more relevant details.
+ * @param initialDestinationHistory the initial pane destination history of the scaffold, by default
+ *        it will be just the list pane.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun <T> rememberListDetailPaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        ListDetailPaneScaffoldDefaults.adaptStrategies(),
+    isDestinationHistoryAware: Boolean = true,
+    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>> =
+        DefaultListDetailPaneHistory,
+): ThreePaneScaffoldNavigator<T> =
+    rememberThreePaneScaffoldNavigator(
+        scaffoldDirective,
+        adaptStrategies,
+        isDestinationHistoryAware,
+        initialDestinationHistory
+    )
+
+/**
+ * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
+ * [SupportingPaneScaffold], which will be updated automatically when the input values change.
+ * The default navigator is supposed to be used independently from any navigation frameworks and
+ * it will address the navigation purely inside the [SupportingPaneScaffold].
+ *
+ * @param scaffoldDirective the current layout directives to follow. The default value will be
+ *        calculated with [calculateStandardPaneScaffoldDirective] using [WindowAdaptiveInfo]
+ *        retrieved from the current context.
+ * @param adaptStrategies adaptation strategies of each pane.
+ * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
+ *        full destination history, instead of just the current destination. See
+ *        [calculateThreePaneScaffoldValue] for more relevant details.
+ * @param initialDestinationHistory the initial destination history of the scaffold, by default it
+ *        will be just the main pane.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun <T> rememberSupportingPaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective =
+        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        SupportingPaneScaffoldDefaults.adaptStrategies(),
+    isDestinationHistoryAware: Boolean = true,
+    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>> =
+        DefaultSupportingPaneHistory,
+): ThreePaneScaffoldNavigator<T> =
+    rememberThreePaneScaffoldNavigator(
+        scaffoldDirective,
+        adaptStrategies,
+        isDestinationHistoryAware,
+        initialDestinationHistory
+    )
+
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+internal fun <T> rememberThreePaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective,
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
+    isDestinationHistoryAware: Boolean,
+    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>>
+): ThreePaneScaffoldNavigator<T> =
+    rememberSaveable(
+        saver = DefaultThreePaneScaffoldNavigator.saver(
+            scaffoldDirective,
+            adaptStrategies,
+            isDestinationHistoryAware
+        )
+    ) {
+        DefaultThreePaneScaffoldNavigator(
+            initialDestinationHistory = initialDestinationHistory,
+            initialScaffoldDirective = scaffoldDirective,
+            initialAdaptStrategies = adaptStrategies,
+            initialIsDestinationHistoryAware = isDestinationHistoryAware
+        )
+    }.apply {
+        this.scaffoldDirective = scaffoldDirective
+        this.adaptStrategies = adaptStrategies
+        this.isDestinationHistoryAware = isDestinationHistoryAware
+    }
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal class DefaultThreePaneScaffoldNavigator<T>(
+    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>>,
+    initialScaffoldDirective: PaneScaffoldDirective,
+    initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies,
+    initialIsDestinationHistoryAware: Boolean
+) : ThreePaneScaffoldNavigator<T>, ThreePaneScaffoldState {
+
+    private val destinationHistory =
+        mutableStateListOf<ThreePaneScaffoldDestinationItem<T>>().apply {
+            addAll(initialDestinationHistory)
+        }
+
+    override val scaffoldState = this
+
+    override var scaffoldDirective by mutableStateOf(initialScaffoldDirective)
+
+    override var isDestinationHistoryAware by mutableStateOf(initialIsDestinationHistoryAware)
+
+    var adaptStrategies by mutableStateOf(initialAdaptStrategies)
+
+    override val currentDestination get() = destinationHistory.lastOrNull()
+
+    override val scaffoldValue by derivedStateOf {
+        calculateScaffoldValue(destinationHistory.lastIndex)
+    }
+
+    override fun navigateTo(pane: ThreePaneScaffoldRole, content: T?) {
+        destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, content))
+    }
+
+    override fun canNavigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean =
+        getPreviousDestinationIndex(backNavigationBehavior) >= 0
+
+    override fun navigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean {
+        val previousDestinationIndex = getPreviousDestinationIndex(backNavigationBehavior)
+        if (previousDestinationIndex < 0) {
+            destinationHistory.clear()
+            return false
+        }
+        val targetSize = previousDestinationIndex + 1
+        while (destinationHistory.size > targetSize) {
+            destinationHistory.removeLast()
+        }
+        return true
+    }
+
+    private fun getPreviousDestinationIndex(backNavBehavior: BackNavigationBehavior): Int {
+        if (destinationHistory.size <= 1) {
+            // No previous destination
+            return -1
+        }
+        when (backNavBehavior) {
+            BackNavigationBehavior.PopLatest -> return destinationHistory.lastIndex - 1
+
+            BackNavigationBehavior.PopUntilScaffoldValueChange ->
+                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
+                    val previousValue = calculateScaffoldValue(previousDestinationIndex)
+                    if (previousValue != scaffoldValue) {
+                        return previousDestinationIndex
+                    }
+                }
+
+            BackNavigationBehavior.PopUntilCurrentDestinationChange ->
+                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
+                    val destination = destinationHistory[previousDestinationIndex].pane
+                    if (destination != currentDestination?.pane) {
+                        return previousDestinationIndex
+                    }
+                }
+
+            BackNavigationBehavior.PopUntilContentChange ->
+                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
+                    val content = destinationHistory[previousDestinationIndex].content
+                    if (content != currentDestination?.content) {
+                        return previousDestinationIndex
+                    }
+                    // A scaffold value change also counts as a content change.
+                    val previousValue = calculateScaffoldValue(previousDestinationIndex)
+                    if (previousValue != scaffoldValue) {
+                        return previousDestinationIndex
+                    }
+                }
+        }
+
+        return -1
+    }
+
+    private fun calculateScaffoldValue(destinationIndex: Int) =
+        if (destinationIndex == -1) {
+            calculateThreePaneScaffoldValue(
+                scaffoldDirective.maxHorizontalPartitions,
+                adaptStrategies,
+                null
+            )
+        } else if (isDestinationHistoryAware) {
+            calculateThreePaneScaffoldValue(
+                scaffoldDirective.maxHorizontalPartitions,
+                adaptStrategies,
+                destinationHistory.subList(0, destinationIndex + 1)
+            )
+        } else {
+            calculateThreePaneScaffoldValue(
+                scaffoldDirective.maxHorizontalPartitions,
+                adaptStrategies,
+                destinationHistory[destinationIndex]
+            )
+        }
+
+    companion object {
+        /**
+         * To keep destination history saved
+         */
+        fun <T> saver(
+            initialScaffoldDirective: PaneScaffoldDirective,
+            initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies,
+            initialDestinationHistoryAware: Boolean
+        ): Saver<DefaultThreePaneScaffoldNavigator<T>, *> {
+            val destinationItemSaver = destinationItemSaver<T>()
+            return listSaver(
+                save = {
+                    it.destinationHistory.fastMap { destination ->
+                        with(destinationItemSaver) { save(destination) }
+                    }
+                },
+                restore = {
+                    DefaultThreePaneScaffoldNavigator(
+                        initialDestinationHistory = it.fastMap { savedDestination ->
+                            destinationItemSaver.restore(savedDestination!!)!!
+                        },
+                        initialScaffoldDirective = initialScaffoldDirective,
+                        initialAdaptStrategies = initialAdaptStrategies,
+                        initialIsDestinationHistoryAware = initialDestinationHistoryAware
+                    )
+                }
+            )
+        }
+    }
+}
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal fun <T> destinationItemSaver(): Saver<ThreePaneScaffoldDestinationItem<T>, Any> =
+    listSaver(
+        save = { listOf(it.pane, it.content) },
+        restore = {
+            @Suppress("UNCHECKED_CAST")
+            (ThreePaneScaffoldDestinationItem(
+                pane = it[0] as ThreePaneScaffoldRole,
+                content = it[1] as T?
+            ))
+        }
+    )
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val DefaultListDetailPaneHistory: List<ThreePaneScaffoldDestinationItem<Nothing>> =
+    listOf(ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List))
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val DefaultSupportingPaneHistory: List<ThreePaneScaffoldDestinationItem<Nothing>> =
+    listOf(ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main))
diff --git a/compose/material3/adaptive/adaptive/api/current.txt b/compose/material3/adaptive/adaptive/api/current.txt
new file mode 100644
index 0000000..b7dcb35
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/api/current.txt
@@ -0,0 +1,55 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive {
+
+  public final class AndroidPosture_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.Posture calculatePosture(java.util.List<? extends androidx.window.layout.FoldingFeature> foldingFeatures);
+  }
+
+  public final class AndroidWindowAdaptiveInfo_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3 adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class HingeInfo {
+    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
+    method public androidx.compose.ui.geometry.Rect getBounds();
+    method public boolean isOccluding();
+    method public boolean isSeparating();
+    method public boolean isVertical();
+    property public final androidx.compose.ui.geometry.Rect bounds;
+    property public final boolean isOccluding;
+    property public final boolean isSeparating;
+    property public final boolean isVertical;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
+    ctor public Posture(optional boolean isTabletop, optional java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList);
+    method public java.util.List<androidx.compose.material3.adaptive.HingeInfo> getHingeList();
+    method public boolean isTabletop();
+    property public final java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList;
+    property public final boolean isTabletop;
+  }
+
+  public final class PostureKt {
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
+    ctor public WindowAdaptiveInfo(androidx.window.core.layout.WindowSizeClass windowSizeClass, androidx.compose.material3.adaptive.Posture windowPosture);
+    method public androidx.compose.material3.adaptive.Posture getWindowPosture();
+    method public androidx.window.core.layout.WindowSizeClass getWindowSizeClass();
+    property public final androidx.compose.material3.adaptive.Posture windowPosture;
+    property public final androidx.window.core.layout.WindowSizeClass windowSizeClass;
+  }
+
+}
+
diff --git a/compose/material3/material3-adaptive/api/res-current.txt b/compose/material3/adaptive/adaptive/api/res-current.txt
similarity index 100%
rename from compose/material3/material3-adaptive/api/res-current.txt
rename to compose/material3/adaptive/adaptive/api/res-current.txt
diff --git a/compose/material3/adaptive/adaptive/api/restricted_current.txt b/compose/material3/adaptive/adaptive/api/restricted_current.txt
new file mode 100644
index 0000000..b7dcb35
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/api/restricted_current.txt
@@ -0,0 +1,55 @@
+// Signature format: 4.0
+package androidx.compose.material3.adaptive {
+
+  public final class AndroidPosture_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.Posture calculatePosture(java.util.List<? extends androidx.window.layout.FoldingFeature> foldingFeatures);
+  }
+
+  public final class AndroidWindowAdaptiveInfo_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3 adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class HingeInfo {
+    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
+    method public androidx.compose.ui.geometry.Rect getBounds();
+    method public boolean isOccluding();
+    method public boolean isSeparating();
+    method public boolean isVertical();
+    property public final androidx.compose.ui.geometry.Rect bounds;
+    property public final boolean isOccluding;
+    property public final boolean isSeparating;
+    property public final boolean isVertical;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
+    ctor public Posture(optional boolean isTabletop, optional java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList);
+    method public java.util.List<androidx.compose.material3.adaptive.HingeInfo> getHingeList();
+    method public boolean isTabletop();
+    property public final java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList;
+    property public final boolean isTabletop;
+  }
+
+  public final class PostureKt {
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
+    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
+    ctor public WindowAdaptiveInfo(androidx.window.core.layout.WindowSizeClass windowSizeClass, androidx.compose.material3.adaptive.Posture windowPosture);
+    method public androidx.compose.material3.adaptive.Posture getWindowPosture();
+    method public androidx.window.core.layout.WindowSizeClass getWindowSizeClass();
+    property public final androidx.compose.material3.adaptive.Posture windowPosture;
+    property public final androidx.window.core.layout.WindowSizeClass windowSizeClass;
+  }
+
+}
+
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
new file mode 100644
index 0000000..17f440f
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 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.tasks.KotlinCompile
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+}
+
+androidXMultiplatform {
+    android()
+    desktop()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(libs.kotlinStdlibCommon)
+                api("androidx.compose.foundation:foundation:1.6.0")
+                implementation("androidx.compose.ui:ui-util:1.6.0")
+                // TODO(conradchen): pin the depe when the change required is released to public
+                implementation(project(":window:window-core"))
+            }
+        }
+
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+                api("androidx.annotation:annotation-experimental:1.4.0")
+                implementation("androidx.window:window:1.2.0")
+            }
+        }
+
+        desktopMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        desktopTest {
+            dependsOn(jvmTest)
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(project(":compose:material3:material3"))
+                implementation(project(":compose:test-utils"))
+                implementation(project(":window:window-testing"))
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+
+        androidUnitTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.junit)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+            }
+        }
+    }
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive"
+}
+
+androidx {
+    name = "Material Adaptive"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Compose Material Design Adaptive Library"
+}
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
+}
+
+// Screenshot tests related setup
+android {
+    sourceSets.androidTest.assets.srcDirs +=
+            project.rootDir.absolutePath + "/../../golden/compose/material3/adaptive"
+    namespace "androidx.compose.material3.adaptive"
+}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
rename to compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
rename to compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
rename to compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt
rename to compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt
diff --git a/compose/material3/material3-adaptive/src/androidMain/AndroidManifest.xml b/compose/material3/adaptive/adaptive/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidMain/AndroidManifest.xml
rename to compose/material3/adaptive/adaptive/src/androidMain/AndroidManifest.xml
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
rename to compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
diff --git a/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt
new file mode 100644
index 0000000..afd78f5
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive
+
+import android.app.Activity
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.toSize
+import androidx.window.core.layout.WindowSizeClass
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoTracker
+import androidx.window.layout.WindowMetricsCalculator
+import kotlinx.coroutines.flow.map
+
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+actual fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo {
+    val windowSize = with(LocalDensity.current) {
+        currentWindowSize().toSize().toDpSize()
+    }
+    return WindowAdaptiveInfo(
+        WindowSizeClass(windowSize.width.value.toInt(), windowSize.height.value.toInt()),
+        calculatePosture(collectFoldingFeaturesAsState().value)
+    )
+}
+
+/**
+ * Returns and automatically update the current window size from [WindowMetricsCalculator].
+ *
+ * @return an [IntSize] that represents the current window size.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun currentWindowSize(): IntSize {
+    // Observe view configuration changes and recalculate the size class on each change. We can't
+    // use Activity#onConfigurationChanged as this will sometimes fail to be called on different
+    // API levels, hence why this function needs to be @Composable so we can observe the
+    // ComposeView's configuration changes.
+    LocalConfiguration.current
+    val windowBounds =
+        WindowMetricsCalculator
+            .getOrCreate()
+            .computeCurrentWindowMetrics(LocalContext.current)
+            .bounds
+   return IntSize(windowBounds.width(), windowBounds.height())
+}
+
+/**
+ * Collects the current window folding features from [WindowInfoTracker] in to a [State].
+ *
+ * @return a [State] of a [FoldingFeature] list.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun collectFoldingFeaturesAsState(): State<List<FoldingFeature>> {
+    val context = LocalContext.current
+    return remember(context) {
+        if (context is Activity) {
+            // TODO(b/284347941) remove the instance check after the test bug is fixed.
+            WindowInfoTracker
+                .getOrCreate(context)
+                .windowLayoutInfo(context)
+        } else {
+            WindowInfoTracker
+                .getOrCreate(context)
+                .windowLayoutInfo(context)
+        }.map { it.displayFeatures.filterIsInstance<FoldingFeature>() }
+    }.collectAsState(emptyList())
+}
diff --git a/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt
new file mode 100644
index 0000000..3ad72c5
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive
+
+@RequiresOptIn(
+    "This material3 adaptive API is experimental and is likely to change or to be" +
+        "removed in the future."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalMaterial3AdaptiveApi
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
similarity index 100%
rename from compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
rename to compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
diff --git a/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt
new file mode 100644
index 0000000..57b2015
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.window.core.layout.WindowSizeClass
+
+/**
+ * Calculates and returns [WindowAdaptiveInfo] of the provided context. It's a convenient function
+ * that uses the default [WindowSizeClass] constructor and the default [Posture] calculation
+ * functions to retrieve [WindowSizeClass] and [Posture].
+ *
+ * @return [WindowAdaptiveInfo] of the provided context
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+expect fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo
+
+/**
+ * This class collects window info that affects adaptation decisions. An adaptive layout is supposed
+ * to use the info from this class to decide how the layout is supposed to be adapted.
+ *
+ * @constructor create an instance of [WindowAdaptiveInfo]
+ * @param windowSizeClass [WindowSizeClass] of the current window.
+ * @param windowPosture [Posture] of the current window.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Immutable
+class WindowAdaptiveInfo(
+    val windowSizeClass: WindowSizeClass,
+    val windowPosture: Posture
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowAdaptiveInfo) return false
+        if (windowSizeClass != other.windowSizeClass) return false
+        if (windowPosture != other.windowPosture) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = windowSizeClass.hashCode()
+        result = 31 * result + windowPosture.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "WindowAdaptiveInfo(windowSizeClass=$windowSizeClass, windowPosture=$windowPosture)"
+    }
+}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-documentation.md b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-documentation.md
similarity index 100%
rename from compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-documentation.md
rename to compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-documentation.md
diff --git a/compose/material3/adaptive/adaptive/src/desktopMain/kotlin/androidx/compose/material3/adaptive/DesktopWindowInfo.desktop.kt b/compose/material3/adaptive/adaptive/src/desktopMain/kotlin/androidx/compose/material3/adaptive/DesktopWindowInfo.desktop.kt
new file mode 100644
index 0000000..4e76db0
--- /dev/null
+++ b/compose/material3/adaptive/adaptive/src/desktopMain/kotlin/androidx/compose/material3/adaptive/DesktopWindowInfo.desktop.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.adaptive
+
+import androidx.compose.runtime.Composable
+
+@Composable
+@ExperimentalMaterial3AdaptiveApi
+actual fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo {
+    TODO("Not yet implemented")
+}
diff --git a/compose/material3/adaptive/benchmark/build.gradle b/compose/material3/adaptive/benchmark/build.gradle
new file mode 100644
index 0000000..e63b29e
--- /dev/null
+++ b/compose/material3/adaptive/benchmark/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+    id("org.jetbrains.kotlin.android")
+    id("androidx.benchmark")
+}
+
+dependencies {
+    androidTestImplementation(project(":compose:material3:adaptive:adaptive-layout"))
+    androidTestImplementation(project(":benchmark:benchmark-junit4"))
+    androidTestImplementation(project(":compose:runtime:runtime"))
+    androidTestImplementation(project(":compose:benchmark-utils"))
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(libs.kotlinTestCommon)
+    androidTestImplementation(libs.junit)
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive.benchmark"
+}
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..2a48785
--- /dev/null
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.benchmark
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.calculateListDetailPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.graphics.Color
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class ListDetailPaneScaffoldBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun singlePane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_navigateToDetail() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
+                                ListDetailPaneScaffoldRole.Detail
+                            } else {
+                                ListDetailPaneScaffoldRole.List
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            }
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
+                                ListDetailPaneScaffoldRole.Extra
+                            } else {
+                                ListDetailPaneScaffoldRole.List
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            }
+        )
+    }
+
+    @Test
+    fun singlePane_navigateToDetail_animated() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase(animated = true) {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
+                                ListDetailPaneScaffoldRole.Detail
+                            } else {
+                                ListDetailPaneScaffoldRole.List
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            },
+            // For skipping animations
+            assertOneRecomposition = false
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra_animated() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase(animated = true) {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
+                                ListDetailPaneScaffoldRole.Extra
+                            } else {
+                                ListDetailPaneScaffoldRole.List
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            },
+            // For skipping animations
+            assertOneRecomposition = false
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class ListDetailPaneScaffoldTestCase(
+    animated: Boolean = false
+) : ThreePaneScaffoldTestCase(animated) {
+    override var currentDestination by mutableStateOf(
+        ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0)
+    )
+
+    @Composable
+    override fun MeasuredContent() {
+        ListDetailPaneScaffold(
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = currentScaffoldDirective,
+                currentDestination = currentDestination
+            ),
+            listPane = { TestPane(Color.Red) },
+            extraPane = { TestPane(Color.Blue) }
+        ) {
+            TestPane(Color.Yellow)
+        }
+    }
+}
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..dcfaec7
--- /dev/null
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.benchmark
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.calculateSupportingPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.graphics.Color
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class SupportingPaneScaffoldBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun singlePane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_navigateBetweenMainAndSupporting() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
+                                SupportingPaneScaffoldRole.Supporting
+                            } else {
+                                SupportingPaneScaffoldRole.Main
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            }
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
+                                SupportingPaneScaffoldRole.Extra
+                            } else {
+                                SupportingPaneScaffoldRole.Main
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            }
+        )
+    }
+
+    @Test
+    fun singlePane_navigateToSupporting_animated() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase(animated = true) {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
+                                SupportingPaneScaffoldRole.Supporting
+                            } else {
+                                SupportingPaneScaffoldRole.Main
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            },
+            // For skipping animations
+            assertOneRecomposition = false
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra_animated() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase(animated = true) {
+                    override fun toggleState() {
+                        val newPane =
+                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
+                                SupportingPaneScaffoldRole.Extra
+                            } else {
+                                SupportingPaneScaffoldRole.Main
+                            }
+                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            },
+            // For skipping animations
+            assertOneRecomposition = false
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class SupportingPaneScaffoldTestCase(
+    animated: Boolean = false
+) : ThreePaneScaffoldTestCase(animated) {
+    override var currentDestination by mutableStateOf(
+        ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0)
+    )
+
+    @Composable
+    override fun MeasuredContent() {
+        SupportingPaneScaffold(
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = currentScaffoldDirective,
+                currentDestination = currentDestination
+            ),
+            supportingPane = { TestPane(Color.Red) },
+            extraPane = { TestPane(Color.Blue) }
+        ) {
+            TestPane(Color.Yellow)
+        }
+    }
+}
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
new file mode 100644
index 0000000..acfcf48
--- /dev/null
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.AnimatedPane
+import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val singlePaneDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val dualPaneDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(24.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 24.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal abstract class ThreePaneScaffoldTestCase(
+    private val animated: Boolean
+) : LayeredComposeTestCase(), ToggleableTestCase {
+    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
+    abstract var currentDestination: ThreePaneScaffoldDestinationItem<Int>
+
+    override fun toggleState() {}
+
+    @Composable
+    fun ThreePaneScaffoldScope.TestPane(color: Color) {
+        val content = @Composable {
+            Box(modifier = Modifier.fillMaxSize().background(color))
+        }
+        if (animated) {
+            AnimatedPane(Modifier) {
+                content()
+            }
+        } else {
+            content()
+        }
+    }
+}
diff --git a/compose/material3/adaptive/samples/build.gradle b/compose/material3/adaptive/samples/build.gradle
new file mode 100644
index 0000000..45dec1d
--- /dev/null
+++ b/compose/material3/adaptive/samples/build.gradle
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 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
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+
+    implementation(libs.kotlinStdlib)
+
+    compileOnly(project(":annotation:annotation-sampled"))
+
+    implementation("androidx.compose.foundation:foundation:1.6.0-rc01")
+    implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
+    implementation(project(":compose:material3:adaptive:adaptive-layout"))
+    implementation(project(":compose:material3:adaptive:adaptive-navigation"))
+    implementation(project(":compose:material3:material3"))
+    implementation(project(":compose:material3:material3-window-size-class"))
+    implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
+    implementation("androidx.compose.ui:ui-tooling-preview:1.4.1")
+
+    debugImplementation("androidx.compose.ui:ui-tooling:1.4.1")
+}
+
+androidx {
+    name = "Compose Material3 Adaptive Samples"
+    type = LibraryType.SAMPLES
+    inceptionYear = "2023"
+    description = "Contains the sample code for the AndroidX Compose Material Adaptive."
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive.samples"
+}
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
new file mode 100644
index 0000000..29d75b0
--- /dev/null
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2023 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.compose.material3.adaptive.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.AnimatedPane
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun ListDetailPaneScaffoldSample() {
+    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
+    ListDetailPaneScaffold(
+        scaffoldState = scaffoldNavigator.scaffoldState,
+        listPane = {
+            AnimatedPane(
+                modifier = Modifier.preferredWidth(200.dp),
+            ) {
+                Surface(
+                    color = MaterialTheme.colorScheme.secondary,
+                    onClick = {
+                        scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
+                    }
+                ) {
+                    Text("List")
+                }
+            }
+        },
+    ) {
+        AnimatedPane(modifier = Modifier) {
+            Surface(
+                color = MaterialTheme.colorScheme.primary,
+                onClick = {
+                    scaffoldNavigator.navigateBack()
+                }
+            ) {
+                Text("Details")
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun ListDetailPaneScaffoldSampleWithExtraPane() {
+    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
+    ListDetailPaneScaffold(
+        scaffoldState = scaffoldNavigator.scaffoldState,
+        listPane = {
+            AnimatedPane(
+                modifier = Modifier.preferredWidth(200.dp),
+            ) {
+                Surface(
+                    color = MaterialTheme.colorScheme.secondary,
+                    onClick = {
+                        scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
+                    }
+                ) {
+                    Text("List")
+                }
+            }
+        },
+        extraPane = {
+            AnimatedPane(
+                modifier = Modifier.fillMaxSize()
+            ) {
+                Surface(
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.tertiary,
+                    onClick = {
+                        scaffoldNavigator.navigateBack()
+                    }
+                ) {
+                    Text("Extra")
+                }
+            }
+        }
+    ) {
+        AnimatedPane(
+            modifier = Modifier
+        ) {
+            Surface(
+                color = MaterialTheme.colorScheme.primary,
+            ) {
+                Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+                    Text("Detail")
+                    Row(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(horizontal = 4.dp),
+                        horizontalArrangement = Arrangement.spacedBy(8.dp)
+                    ) {
+                        Surface(
+                            onClick = {
+                                scaffoldNavigator.navigateBack()
+                            },
+                            modifier = Modifier
+                                .weight(0.5f)
+                                .fillMaxHeight(),
+                            color = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f)
+                        ) {
+                            Box(
+                                modifier = Modifier.fillMaxSize(),
+                                contentAlignment = Alignment.Center
+                            ) {
+                                Text("Previous")
+                            }
+                        }
+                        VerticalDivider()
+                        Surface(
+                            onClick = {
+                                scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra)
+                            },
+                            modifier = Modifier
+                                .weight(0.5f)
+                                .fillMaxHeight(),
+                            color = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
+                        ) {
+                            Box(
+                                modifier = Modifier.fillMaxSize(),
+                                contentAlignment = Alignment.Center
+                            ) {
+                                Text("Next")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 47289bf..769a941 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -43,9 +43,9 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation("androidx.compose.material3:material3:1.2.0-rc01")
-                implementation(project(":compose:material3:material3-adaptive"))
-                implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
+                implementation("androidx.compose.material3:material3:1.2.0")
+                implementation(project(":compose:material3:adaptive:adaptive"))
+                implementation("androidx.compose.ui:ui-util:1.6.0")
                 // TODO(conradchen): pin the depe when the change required is released to public
                 implementation(project(":window:window-core"))
             }
@@ -110,6 +110,9 @@
 
 android {
     namespace "androidx.compose.material3.adaptive.navigationsuite"
+    lintOptions {
+        disable 'IllegalExperimentalApiUsage' // TODO (conradchen): Address before moving to beta
+    }
 }
 
 androidx {
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
index 2e1bb69..135d478 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
@@ -38,8 +38,8 @@
 
     implementation("androidx.compose.foundation:foundation:1.6.0-rc01")
     implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
+    implementation(project(":compose:material3:adaptive:adaptive"))
     implementation(project(":compose:material3:material3"))
-    implementation(project(":compose:material3:material3-adaptive"))
     implementation(project(":compose:material3:material3-adaptive-navigation-suite"))
     implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
     implementation("androidx.compose.ui:ui-tooling-preview:1.4.1")
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidMain/AndroidManifest.xml b/compose/material3/material3-adaptive-navigation-suite/src/androidMain/AndroidManifest.xml
deleted file mode 100644
index 3150d7d..0000000
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidMain/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2023 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.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-</manifest>
\ No newline at end of file
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.android.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.android.kt
deleted file mode 100644
index 4af953a..0000000
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.android.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.navigationsuite
-
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.WindowAdaptiveInfo
-import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
-import androidx.compose.runtime.Composable
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal actual val WindowAdaptiveInfoDefault: WindowAdaptiveInfo
-    @Composable
-    get() = currentWindowAdaptiveInfo()
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
index d4a2106..815e138 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
@@ -42,6 +42,7 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.WindowAdaptiveInfo
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
 import androidx.compose.material3.contentColorFor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
@@ -491,9 +492,9 @@
 )
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal expect val WindowAdaptiveInfoDefault: WindowAdaptiveInfo
+internal val WindowAdaptiveInfoDefault
     @Composable
-    get
+    get() = currentWindowAdaptiveInfo()
 
 private interface NavigationSuiteItemProvider {
     val itemsCount: Int
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/desktopMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.desktop.kt b/compose/material3/material3-adaptive-navigation-suite/src/desktopMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.desktop.kt
deleted file mode 100644
index b4910cf..0000000
--- a/compose/material3/material3-adaptive-navigation-suite/src/desktopMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.desktop.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.navigationsuite
-
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.Posture
-import androidx.compose.material3.adaptive.WindowAdaptiveInfo
-import androidx.window.core.layout.WindowSizeClass
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal actual val WindowAdaptiveInfoDefault: WindowAdaptiveInfo = WindowAdaptiveInfo(
-    windowSizeClass = WindowSizeClass(1000, 1000),
-    windowPosture = Posture()
-)
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
deleted file mode 100644
index 6e44527..0000000
--- a/compose/material3/material3-adaptive/api/current.txt
+++ /dev/null
@@ -1,254 +0,0 @@
-// Signature format: 4.0
-package androidx.compose.material3.adaptive {
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface AdaptStrategy {
-    method public String adapt();
-    field public static final androidx.compose.material3.adaptive.AdaptStrategy.Companion Companion;
-  }
-
-  public static final class AdaptStrategy.Companion {
-    method public androidx.compose.material3.adaptive.AdaptStrategy getHide();
-    property public final androidx.compose.material3.adaptive.AdaptStrategy Hide;
-  }
-
-  public final class AndroidPosture_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.Posture calculatePosture(java.util.List<? extends androidx.window.layout.FoldingFeature> foldingFeatures);
-  }
-
-  public final class AndroidWindowInfo_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
-    method public static androidx.compose.material3.adaptive.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.BackNavigationBehavior[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopLatest;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilContentChange;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilCurrentDestinationChange;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilScaffoldValueChange;
-  }
-
-  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class HingeInfo {
-    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
-    method public androidx.compose.ui.geometry.Rect getBounds();
-    method public boolean isOccluding();
-    method public boolean isSeparating();
-    method public boolean isVertical();
-    property public final androidx.compose.ui.geometry.Rect bounds;
-    property public final boolean isOccluding;
-    property public final boolean isSeparating;
-    property public final boolean isVertical;
-  }
-
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
-    field public static final androidx.compose.material3.adaptive.HingePolicy.Companion Companion;
-  }
-
-  public static final class HingePolicy.Companion {
-    method public int getAlwaysAvoid();
-    method public int getAvoidOccluding();
-    method public int getAvoidSeparating();
-    method public int getNeverAvoid();
-    property public final int AlwaysAvoid;
-    property public final int AvoidOccluding;
-    property public final int AvoidSeparating;
-    property public final int NeverAvoid;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getDetail();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getList();
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Detail;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole List;
-    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole INSTANCE;
-  }
-
-  public final class ListDetailPaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
-    field public static final androidx.compose.material3.adaptive.PaneAdaptedValue.Companion Companion;
-  }
-
-  public static final class PaneAdaptedValue.Companion {
-    method public String getExpanded();
-    method public String getHidden();
-    property public final String Expanded;
-    property public final String Hidden;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
-    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
-    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
-    method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
-    method public float getHorizontalPartitionSpacerSize();
-    method public int getMaxHorizontalPartitions();
-    method public int getMaxVerticalPartitions();
-    method public float getVerticalPartitionSpacerSize();
-    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
-    property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
-    property public final float horizontalPartitionSpacerSize;
-    property public final int maxHorizontalPartitions;
-    property public final int maxVerticalPartitions;
-    property public final float verticalPartitionSpacerSize;
-  }
-
-  public final class PaneScaffoldDirectiveKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
-    method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
-    ctor public Posture(optional boolean isTabletop, optional java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList);
-    method public java.util.List<androidx.compose.material3.adaptive.HingeInfo> getHingeList();
-    method public boolean isTabletop();
-    property public final java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList;
-    property public final boolean isTabletop;
-  }
-
-  public final class PostureKt {
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldDefaults INSTANCE;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getMain();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getSupporting();
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Main;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Supporting;
-    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole INSTANCE;
-  }
-
-  public final class SupportingPaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
-    ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.AdaptStrategy tertiaryPaneAdaptStrategy);
-    method public operator androidx.compose.material3.adaptive.AdaptStrategy get(androidx.compose.material3.adaptive.ThreePaneScaffoldRole role);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
-    ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane, optional T? content);
-    method public T? getContent();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getPane();
-    property public final T? content;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane;
-    field public static final androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem.Companion Companion;
-  }
-
-  public static final class ThreePaneScaffoldDestinationItem.Companion {
-  }
-
-  public final class ThreePaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> content);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
-    method public boolean canNavigateBack(optional androidx.compose.material3.adaptive.BackNavigationBehavior backNavigationBehavior);
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldState getScaffoldState();
-    method public boolean isDestinationHistoryAware();
-    method public boolean navigateBack(optional androidx.compose.material3.adaptive.BackNavigationBehavior backNavigationBehavior);
-    method public void navigateTo(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane, optional T? content);
-    method public void setDestinationHistoryAware(boolean);
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<T>? currentDestination;
-    property public abstract boolean isDestinationHistoryAware;
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState;
-  }
-
-  public final class ThreePaneScaffoldNavigator_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.ThreePaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Primary;
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Secondary;
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Tertiary;
-  }
-
-  public interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.PaneScaffoldScope {
-    method public String getAnimationToolingLabel();
-    method public androidx.compose.animation.EnterTransition getEnterTransition();
-    method public androidx.compose.animation.ExitTransition getExitTransition();
-    method public String getPaneAdaptedValue();
-    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? getPositionAnimationSpec();
-    property public abstract String animationToolingLabel;
-    property public abstract androidx.compose.animation.EnterTransition enterTransition;
-    property public abstract androidx.compose.animation.ExitTransition exitTransition;
-    property public abstract String paneAdaptedValue;
-    property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? positionAnimationSpec;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldState {
-    method public androidx.compose.material3.adaptive.PaneScaffoldDirective getScaffoldDirective();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldValue getScaffoldValue();
-    property public abstract androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective;
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue scaffoldValue;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue {
-    ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
-    method public operator String get(androidx.compose.material3.adaptive.ThreePaneScaffoldRole role);
-    method public String getPrimary();
-    method public String getSecondary();
-    method public String getTertiary();
-    property public final String primary;
-    property public final String secondary;
-    property public final String tertiary;
-  }
-
-  public final class ThreePaneScaffoldValueKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>? currentDestination);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
-    ctor public WindowAdaptiveInfo(androidx.window.core.layout.WindowSizeClass windowSizeClass, androidx.compose.material3.adaptive.Posture windowPosture);
-    method public androidx.compose.material3.adaptive.Posture getWindowPosture();
-    method public androidx.window.core.layout.WindowSizeClass getWindowSizeClass();
-    property public final androidx.compose.material3.adaptive.Posture windowPosture;
-    property public final androidx.window.core.layout.WindowSizeClass windowSizeClass;
-  }
-
-}
-
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
deleted file mode 100644
index 6e44527..0000000
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ /dev/null
@@ -1,254 +0,0 @@
-// Signature format: 4.0
-package androidx.compose.material3.adaptive {
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface AdaptStrategy {
-    method public String adapt();
-    field public static final androidx.compose.material3.adaptive.AdaptStrategy.Companion Companion;
-  }
-
-  public static final class AdaptStrategy.Companion {
-    method public androidx.compose.material3.adaptive.AdaptStrategy getHide();
-    property public final androidx.compose.material3.adaptive.AdaptStrategy Hide;
-  }
-
-  public final class AndroidPosture_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.Posture calculatePosture(java.util.List<? extends androidx.window.layout.FoldingFeature> foldingFeatures);
-  }
-
-  public final class AndroidWindowInfo_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
-    method public static androidx.compose.material3.adaptive.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.BackNavigationBehavior[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopLatest;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilContentChange;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilCurrentDestinationChange;
-    enum_constant public static final androidx.compose.material3.adaptive.BackNavigationBehavior PopUntilScaffoldValueChange;
-  }
-
-  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class HingeInfo {
-    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
-    method public androidx.compose.ui.geometry.Rect getBounds();
-    method public boolean isOccluding();
-    method public boolean isSeparating();
-    method public boolean isVertical();
-    property public final androidx.compose.ui.geometry.Rect bounds;
-    property public final boolean isOccluding;
-    property public final boolean isSeparating;
-    property public final boolean isVertical;
-  }
-
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
-    field public static final androidx.compose.material3.adaptive.HingePolicy.Companion Companion;
-  }
-
-  public static final class HingePolicy.Companion {
-    method public int getAlwaysAvoid();
-    method public int getAvoidOccluding();
-    method public int getAvoidSeparating();
-    method public int getNeverAvoid();
-    property public final int AlwaysAvoid;
-    property public final int AvoidOccluding;
-    property public final int AvoidSeparating;
-    property public final int NeverAvoid;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getDetail();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getList();
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Detail;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole List;
-    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole INSTANCE;
-  }
-
-  public final class ListDetailPaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
-    field public static final androidx.compose.material3.adaptive.PaneAdaptedValue.Companion Companion;
-  }
-
-  public static final class PaneAdaptedValue.Companion {
-    method public String getExpanded();
-    method public String getHidden();
-    property public final String Expanded;
-    property public final String Hidden;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
-    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
-    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
-    method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
-    method public float getHorizontalPartitionSpacerSize();
-    method public int getMaxHorizontalPartitions();
-    method public int getMaxVerticalPartitions();
-    method public float getVerticalPartitionSpacerSize();
-    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
-    property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
-    property public final float horizontalPartitionSpacerSize;
-    property public final int maxHorizontalPartitions;
-    property public final int maxVerticalPartitions;
-    property public final float verticalPartitionSpacerSize;
-  }
-
-  public final class PaneScaffoldDirectiveKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
-    method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
-    ctor public Posture(optional boolean isTabletop, optional java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList);
-    method public java.util.List<androidx.compose.material3.adaptive.HingeInfo> getHingeList();
-    method public boolean isTabletop();
-    property public final java.util.List<androidx.compose.material3.adaptive.HingeInfo> hingeList;
-    property public final boolean isTabletop;
-  }
-
-  public final class PostureKt {
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getAllVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getOccludingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHorizontalHingeBounds(androidx.compose.material3.adaptive.Posture);
-    method public static java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingVerticalHingeBounds(androidx.compose.material3.adaptive.Posture);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldDefaults INSTANCE;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getMain();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getSupporting();
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Main;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Supporting;
-    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole INSTANCE;
-  }
-
-  public final class SupportingPaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?> currentDestination, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory, optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
-    ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.AdaptStrategy tertiaryPaneAdaptStrategy);
-    method public operator androidx.compose.material3.adaptive.AdaptStrategy get(androidx.compose.material3.adaptive.ThreePaneScaffoldRole role);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
-    ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane, optional T? content);
-    method public T? getContent();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getPane();
-    property public final T? content;
-    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane;
-    field public static final androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem.Companion Companion;
-  }
-
-  public static final class ThreePaneScaffoldDestinationItem.Companion {
-  }
-
-  public final class ThreePaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> content);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
-    method public boolean canNavigateBack(optional androidx.compose.material3.adaptive.BackNavigationBehavior backNavigationBehavior);
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldState getScaffoldState();
-    method public boolean isDestinationHistoryAware();
-    method public boolean navigateBack(optional androidx.compose.material3.adaptive.BackNavigationBehavior backNavigationBehavior);
-    method public void navigateTo(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane, optional T? content);
-    method public void setDestinationHistoryAware(boolean);
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<T>? currentDestination;
-    property public abstract boolean isDestinationHistoryAware;
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState;
-  }
-
-  public final class ThreePaneScaffoldNavigator_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.ThreePaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Primary;
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Secondary;
-    enum_constant public static final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Tertiary;
-  }
-
-  public interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.PaneScaffoldScope {
-    method public String getAnimationToolingLabel();
-    method public androidx.compose.animation.EnterTransition getEnterTransition();
-    method public androidx.compose.animation.ExitTransition getExitTransition();
-    method public String getPaneAdaptedValue();
-    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? getPositionAnimationSpec();
-    property public abstract String animationToolingLabel;
-    property public abstract androidx.compose.animation.EnterTransition enterTransition;
-    property public abstract androidx.compose.animation.ExitTransition exitTransition;
-    property public abstract String paneAdaptedValue;
-    property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? positionAnimationSpec;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldState {
-    method public androidx.compose.material3.adaptive.PaneScaffoldDirective getScaffoldDirective();
-    method public androidx.compose.material3.adaptive.ThreePaneScaffoldValue getScaffoldValue();
-    property public abstract androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective;
-    property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue scaffoldValue;
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue {
-    ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
-    method public operator String get(androidx.compose.material3.adaptive.ThreePaneScaffoldRole role);
-    method public String getPrimary();
-    method public String getSecondary();
-    method public String getTertiary();
-    property public final String primary;
-    property public final String secondary;
-    property public final String tertiary;
-  }
-
-  public final class ThreePaneScaffoldValueKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>? currentDestination);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem<?>> destinationHistory);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
-    ctor public WindowAdaptiveInfo(androidx.window.core.layout.WindowSizeClass windowSizeClass, androidx.compose.material3.adaptive.Posture windowPosture);
-    method public androidx.compose.material3.adaptive.Posture getWindowPosture();
-    method public androidx.window.core.layout.WindowSizeClass getWindowSizeClass();
-    property public final androidx.compose.material3.adaptive.Posture windowPosture;
-    property public final androidx.window.core.layout.WindowSizeClass windowSizeClass;
-  }
-
-}
-
diff --git a/compose/material3/material3-adaptive/benchmark/build.gradle b/compose/material3/material3-adaptive/benchmark/build.gradle
deleted file mode 100644
index 48f6444..0000000
--- a/compose/material3/material3-adaptive/benchmark/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("AndroidXComposePlugin")
-    id("org.jetbrains.kotlin.android")
-    id("androidx.benchmark")
-}
-
-dependencies {
-    androidTestImplementation(project(":compose:material3:material3-adaptive"))
-    androidTestImplementation(project(":benchmark:benchmark-junit4"))
-    androidTestImplementation(project(":compose:runtime:runtime"))
-    androidTestImplementation(project(":compose:benchmark-utils"))
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.kotlinStdlib)
-    androidTestImplementation(libs.kotlinTestCommon)
-    androidTestImplementation(libs.junit)
-}
-
-android {
-    namespace "androidx.compose.material3.adaptive.benchmark"
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
deleted file mode 100644
index b6974a1..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.benchmark
-
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.ListDetailPaneScaffold
-import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
-import androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem
-import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.benchmark.benchmarkFirstCompose
-import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
-import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
-import androidx.compose.ui.graphics.Color
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-class ListDetailPaneScaffoldBenchmark {
-    @get:Rule
-    val benchmarkRule = ComposeBenchmarkRule()
-
-    @Test
-    fun singlePane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_navigateToDetail() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
-                                ListDetailPaneScaffoldRole.Detail
-                            } else {
-                                ListDetailPaneScaffoldRole.List
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            }
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
-                                ListDetailPaneScaffoldRole.Extra
-                            } else {
-                                ListDetailPaneScaffoldRole.List
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            }
-        )
-    }
-
-    @Test
-    fun singlePane_navigateToDetail_animated() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase(animated = true) {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
-                                ListDetailPaneScaffoldRole.Detail
-                            } else {
-                                ListDetailPaneScaffoldRole.List
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            },
-            // For skipping animations
-            assertOneRecomposition = false
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra_animated() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase(animated = true) {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == ListDetailPaneScaffoldRole.List) {
-                                ListDetailPaneScaffoldRole.Extra
-                            } else {
-                                ListDetailPaneScaffoldRole.List
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            },
-            // For skipping animations
-            assertOneRecomposition = false
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal open class ListDetailPaneScaffoldTestCase(
-    animated: Boolean = false
-) : ThreePaneScaffoldTestCase(animated) {
-    override var currentDestination by mutableStateOf(
-        ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0)
-    )
-
-    @Composable
-    override fun MeasuredContent() {
-        ListDetailPaneScaffold(
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = currentScaffoldDirective,
-                currentDestination = currentDestination
-            ),
-            listPane = { TestPane(Color.Red) },
-            extraPane = { TestPane(Color.Blue) }
-        ) {
-            TestPane(Color.Yellow)
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
deleted file mode 100644
index a96d4d9..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.benchmark
-
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.SupportingPaneScaffold
-import androidx.compose.material3.adaptive.SupportingPaneScaffoldRole
-import androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem
-import androidx.compose.material3.adaptive.calculateSupportingPaneScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.benchmark.benchmarkFirstCompose
-import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
-import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
-import androidx.compose.ui.graphics.Color
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-class SupportingPaneScaffoldBenchmark {
-    @get:Rule
-    val benchmarkRule = ComposeBenchmarkRule()
-
-    @Test
-    fun singlePane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_navigateBetweenMainAndSupporting() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
-                                SupportingPaneScaffoldRole.Supporting
-                            } else {
-                                SupportingPaneScaffoldRole.Main
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            }
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
-                                SupportingPaneScaffoldRole.Extra
-                            } else {
-                                SupportingPaneScaffoldRole.Main
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            }
-        )
-    }
-
-    @Test
-    fun singlePane_navigateToSupporting_animated() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase(animated = true) {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
-                                SupportingPaneScaffoldRole.Supporting
-                            } else {
-                                SupportingPaneScaffoldRole.Main
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            },
-            // For skipping animations
-            assertOneRecomposition = false
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra_animated() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase(animated = true) {
-                    override fun toggleState() {
-                        val newPane =
-                            if (currentDestination.pane == SupportingPaneScaffoldRole.Main) {
-                                SupportingPaneScaffoldRole.Extra
-                            } else {
-                                SupportingPaneScaffoldRole.Main
-                            }
-                        currentDestination = ThreePaneScaffoldDestinationItem(newPane, 0)
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            },
-            // For skipping animations
-            assertOneRecomposition = false
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal open class SupportingPaneScaffoldTestCase(
-    animated: Boolean = false
-) : ThreePaneScaffoldTestCase(animated) {
-    override var currentDestination by mutableStateOf(
-        ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0)
-    )
-
-    @Composable
-    override fun MeasuredContent() {
-        SupportingPaneScaffold(
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = currentScaffoldDirective,
-                currentDestination = currentDestination
-            ),
-            supportingPane = { TestPane(Color.Red) },
-            extraPane = { TestPane(Color.Blue) }
-        ) {
-            TestPane(Color.Yellow)
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
deleted file mode 100644
index c71021b..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.benchmark
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.adaptive.AnimatedPane
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.PaneScaffoldDirective
-import androidx.compose.material3.adaptive.ThreePaneScaffoldDestinationItem
-import androidx.compose.material3.adaptive.ThreePaneScaffoldScope
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.LayeredComposeTestCase
-import androidx.compose.testutils.ToggleableTestCase
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-val singlePaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-val dualPaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(24.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 24.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal abstract class ThreePaneScaffoldTestCase(
-    private val animated: Boolean
-) : LayeredComposeTestCase(), ToggleableTestCase {
-    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
-    abstract var currentDestination: ThreePaneScaffoldDestinationItem<Int>
-
-    override fun toggleState() {}
-
-    @Composable
-    fun ThreePaneScaffoldScope.TestPane(color: Color) {
-        val content = @Composable {
-            Box(modifier = Modifier.fillMaxSize().background(color))
-        }
-        if (animated) {
-            AnimatedPane(Modifier) {
-                content()
-            }
-        } else {
-            content()
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/build.gradle b/compose/material3/material3-adaptive/build.gradle
deleted file mode 100644
index cbd842e..0000000
--- a/compose/material3/material3-adaptive/build.gradle
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2023 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 androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("AndroidXComposePlugin")
-}
-
-androidXMultiplatform {
-    android()
-    desktop()
-
-    defaultPlatform(PlatformIdentifier.ANDROID)
-
-    sourceSets {
-        commonMain {
-            dependencies {
-                implementation(libs.kotlinStdlibCommon)
-                api("androidx.compose.foundation:foundation:1.6.0-rc01")
-                implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
-                implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
-                // TODO(conradchen): pin the depe when the change required is released to public
-                implementation(project(":window:window-core"))
-            }
-        }
-
-        commonTest {
-            dependencies {
-            }
-        }
-
-        jvmMain {
-            dependsOn(commonMain)
-            dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-        }
-
-        androidMain {
-            dependsOn(jvmMain)
-            dependencies {
-                api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
-                implementation("androidx.window:window:1.2.0")
-            }
-        }
-
-        desktopMain {
-            dependsOn(jvmMain)
-            dependencies {
-            }
-        }
-
-        jvmTest {
-            dependencies {
-            }
-        }
-
-        desktopTest {
-            dependsOn(jvmTest)
-        }
-
-        androidInstrumentedTest {
-            dependsOn(jvmTest)
-            dependencies {
-                implementation(project(":compose:material3:material3"))
-                implementation(project(":compose:test-utils"))
-                implementation(project(":window:window-testing"))
-                implementation(libs.junit)
-                implementation(libs.testRunner)
-                implementation(libs.truth)
-            }
-        }
-
-        androidUnitTest {
-            dependsOn(jvmTest)
-            dependencies {
-                implementation(libs.junit)
-                implementation(libs.testRunner)
-                implementation(libs.truth)
-            }
-        }
-    }
-}
-
-android {
-    namespace "androidx.compose.material3.adaptive"
-}
-
-androidx {
-    name = "Material Adaptive"
-    mavenVersion = LibraryVersions.COMPOSE_MATERIAL3_ADAPTIVE
-    type = LibraryType.PUBLISHED_LIBRARY
-    inceptionYear = "2023"
-    description = "Compose Material Design Adaptive Library"
-}
-
-tasks.withType(KotlinCompile).configureEach {
-    kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
-}
-
-// Screenshot tests related setup
-android {
-    sourceSets.androidTest.assets.srcDirs +=
-            project.rootDir.absolutePath + "/../../golden/compose/material3/material3-adaptive"
-    namespace "androidx.compose.material3.adaptive"
-}
diff --git a/compose/material3/material3-adaptive/samples/build.gradle b/compose/material3/material3-adaptive/samples/build.gradle
deleted file mode 100644
index 1c8ac3c..0000000
--- a/compose/material3/material3-adaptive/samples/build.gradle
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2023 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
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("AndroidXComposePlugin")
-    id("org.jetbrains.kotlin.android")
-}
-
-dependencies {
-
-    implementation(libs.kotlinStdlib)
-
-    compileOnly(project(":annotation:annotation-sampled"))
-
-    implementation("androidx.compose.foundation:foundation:1.6.0-rc01")
-    implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
-    implementation(project(":compose:material3:material3"))
-    implementation(project(":compose:material3:material3-adaptive"))
-    implementation(project(":compose:material3:material3-window-size-class"))
-    implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
-    implementation("androidx.compose.ui:ui-tooling-preview:1.4.1")
-
-    debugImplementation("androidx.compose.ui:ui-tooling:1.4.1")
-}
-
-androidx {
-    name = "Compose Material3 Adaptive Samples"
-    type = LibraryType.SAMPLES
-    inceptionYear = "2023"
-    description = "Contains the sample code for the AndroidX Compose Material Adaptive."
-}
-
-android {
-    namespace "androidx.compose.material3.adaptive.samples"
-}
diff --git a/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
deleted file mode 100644
index 5ddc87f..0000000
--- a/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.VerticalDivider
-import androidx.compose.material3.adaptive.AnimatedPane
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.ListDetailPaneScaffold
-import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
-import androidx.compose.material3.adaptive.rememberListDetailPaneScaffoldNavigator
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Preview
-@Sampled
-@Composable
-fun ListDetailPaneScaffoldSample() {
-    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
-    ListDetailPaneScaffold(
-        scaffoldState = scaffoldNavigator.scaffoldState,
-        listPane = {
-            AnimatedPane(
-                modifier = Modifier.preferredWidth(200.dp),
-            ) {
-                Surface(
-                    color = MaterialTheme.colorScheme.secondary,
-                    onClick = {
-                        scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
-                    }
-                ) {
-                    Text("List")
-                }
-            }
-        },
-    ) {
-        AnimatedPane(modifier = Modifier) {
-            Surface(
-                color = MaterialTheme.colorScheme.primary,
-                onClick = {
-                    scaffoldNavigator.navigateBack()
-                }
-            ) {
-                Text("Details")
-            }
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Preview
-@Sampled
-@Composable
-fun ListDetailPaneScaffoldSampleWithExtraPane() {
-    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
-    ListDetailPaneScaffold(
-        scaffoldState = scaffoldNavigator.scaffoldState,
-        listPane = {
-            AnimatedPane(
-                modifier = Modifier.preferredWidth(200.dp),
-            ) {
-                Surface(
-                    color = MaterialTheme.colorScheme.secondary,
-                    onClick = {
-                        scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
-                    }
-                ) {
-                    Text("List")
-                }
-            }
-        },
-        extraPane = {
-            AnimatedPane(
-                modifier = Modifier.fillMaxSize()
-            ) {
-                Surface(
-                    modifier = Modifier.fillMaxSize(),
-                    color = MaterialTheme.colorScheme.tertiary,
-                    onClick = {
-                        scaffoldNavigator.navigateBack()
-                    }
-                ) {
-                    Text("Extra")
-                }
-            }
-        }
-    ) {
-        AnimatedPane(
-            modifier = Modifier
-        ) {
-            Surface(
-                color = MaterialTheme.colorScheme.primary,
-            ) {
-                Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
-                    Text("Detail")
-                    Row(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(horizontal = 4.dp),
-                        horizontalArrangement = Arrangement.spacedBy(8.dp)
-                    ) {
-                        Surface(
-                            onClick = {
-                                scaffoldNavigator.navigateBack()
-                            },
-                            modifier = Modifier
-                                .weight(0.5f)
-                                .fillMaxHeight(),
-                            color = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f)
-                        ) {
-                            Box(
-                                modifier = Modifier.fillMaxSize(),
-                                contentAlignment = Alignment.Center
-                            ) {
-                                Text("Previous")
-                            }
-                        }
-                        VerticalDivider()
-                        Surface(
-                            onClick = {
-                                scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra)
-                            },
-                            modifier = Modifier
-                                .weight(0.5f)
-                                .fillMaxHeight(),
-                            color = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
-                        ) {
-                            Box(
-                                modifier = Modifier.fillMaxSize(),
-                                contentAlignment = Alignment.Center
-                            ) {
-                                Text("Next")
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/GoldenCommon.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/GoldenCommon.kt
deleted file mode 100644
index a693e3a..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/GoldenCommon.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-internal const val GOLDEN_MATERIAL3_ADAPTIVE = "compose/material3/material3-adaptive"
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
deleted file mode 100644
index 8ed7c70..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.toSize
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal fun ComposeContentTestRule.setContentWithSimulatedSize(
-    simulatedWidth: Dp,
-    simulatedHeight: Dp,
-    content: @Composable () -> Unit
-) {
-    setContent {
-        val currentDensity = LocalDensity.current
-        val windowSize = with(currentDensity) {
-            currentWindowSize().toSize().toDpSize();
-        }
-        val simulatedDensity = Density(
-            currentDensity.density * (windowSize.width / simulatedWidth)
-        )
-        CompositionLocalProvider(LocalDensity provides simulatedDensity) {
-            Box(
-                Modifier.fillMaxWidth().height(simulatedHeight),
-            ) {
-                content()
-            }
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt
deleted file mode 100644
index efdd348..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlin.properties.Delegates
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ListDetailPaneScaffoldNavigatorTest {
-    @get:Rule
-    val composeRule = createComposeRule()
-
-    @Test
-    fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateToExtra_hideListWhenNotHistoryAware() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                isDestinationHistoryAware = false
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateToExtra_keepListExpandedWhenHistoryAware() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                isDestinationHistoryAware = true
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            assertThat(canNavigateBack).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_withSimplePop_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(
-                    BackNavigationBehavior.PopUntilCurrentDestinationChange
-                )
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilCurrentDestinationChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceCurrentDestinationChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(
-                    BackNavigationBehavior.PopUntilCurrentDestinationChange
-                )
-            ).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
-                ),
-                isDestinationHistoryAware = true
-            )
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, 0),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
-                ),
-                isDestinationHistoryAware = false
-            )
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.List)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
-                scaffoldDirective = mockCurrentScaffoldDirective.value,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
-                )
-            )
-        }
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            // Switches to dual pane
-            mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(ListDetailPaneScaffoldRole.Detail)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 16.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun ThreePaneScaffoldValue.assert(
-    expectedDetailPaneAdaptedValue: PaneAdaptedValue,
-    expectedListPaneAdaptedValue: PaneAdaptedValue,
-    expectedExtraPaneAdaptedValue: PaneAdaptedValue
-) {
-    assertThat(this[ListDetailPaneScaffoldRole.Detail]).isEqualTo(expectedDetailPaneAdaptedValue)
-    assertThat(this[ListDetailPaneScaffoldRole.List]).isEqualTo(expectedListPaneAdaptedValue)
-    assertThat(this[ListDetailPaneScaffoldRole.Extra]).isEqualTo(expectedExtraPaneAdaptedValue)
-}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt
deleted file mode 100644
index 01bd1f7..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ListDetailPaneScaffoldStateTest {
-    @get:Rule
-    val composeRule = createComposeRule()
-
-    @Test
-    fun singlePaneLayout_listPaneExpandedByDefault() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_listAndDetailPaneExpandedByDefault() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = MockDualPaneScaffoldDirective
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-
-    @Test
-    fun singlePaneLayout_paneDestinationExpanded() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = MockSinglePaneScaffoldDirective,
-                currentDestination =
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, null)
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_paneDestinationExpanded() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                currentDestination =
-                    ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Extra, null)
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(scaffoldState.scaffoldValue.tertiary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 16.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 16.dp,
-    excludedBounds = emptyList()
-)
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt
deleted file mode 100644
index bd224dc..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt
+++ /dev/null
@@ -1,609 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlin.properties.Delegates
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SupportingPaneScaffoldNavigatorTest {
-    @get:Rule
-    val composeRule = createComposeRule()
-
-    @Test
-    fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            assertThat(canNavigateBack).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateToExtra_hideSupportingWhenNotHistoryAware() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0)
-                ),
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                isDestinationHistoryAware = false
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_navigateToExtra_keepSupportingExpandedWhenHistoryAware() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0)
-                ),
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                isDestinationHistoryAware = true
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(canNavigateBack).isTrue()
-        }
-    }
-
-    @Test
-    fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        var canNavigateBack by Delegates.notNull<Boolean>()
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-            canNavigateBack = scaffoldNavigator.canNavigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(canNavigateBack).isTrue()
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Supporting]
-            ).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            assertThat(canNavigateBack).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_withSimplePop_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(
-                    BackNavigationBehavior.PopUntilCurrentDestinationChange
-                )
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilCurrentDestinationChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceCurrentDestinationChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(
-                    BackNavigationBehavior.PopUntilCurrentDestinationChange
-                )
-            ).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 1),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isTrue()
-            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilContentChange)
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceContentChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                )
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            assertThat(
-                scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
-            ).isFalse()
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 1),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                ),
-                isDestinationHistoryAware = true
-            )
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, 1),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                ),
-                isDestinationHistoryAware = false
-            )
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Supporting)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
-            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            scaffoldNavigator.navigateBack()
-        }
-
-        composeRule.runOnIdle {
-            scaffoldNavigator.scaffoldState.scaffoldValue.assert(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded
-            )
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Extra)
-            assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
-        }
-    }
-
-    @Test
-    fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
-        val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
-
-        composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
-                scaffoldDirective = mockCurrentScaffoldDirective.value,
-                initialDestinationHistory = listOf(
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, 0),
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
-                )
-            )
-        }
-        composeRule.runOnIdle {
-            assertThat(
-                scaffoldNavigator.scaffoldState.scaffoldValue[SupportingPaneScaffoldRole.Main]
-            ).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-            // Switches to dual pane
-            mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
-            assertThat(
-                scaffoldNavigator.currentDestination?.pane
-            ).isEqualTo(SupportingPaneScaffoldRole.Main)
-            assertThat(scaffoldNavigator.currentDestination?.content).isNull()
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 16.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun ThreePaneScaffoldValue.assert(
-    expectedMainPaneAdaptedValue: PaneAdaptedValue,
-    expectedSupportingPaneAdaptedValue: PaneAdaptedValue,
-    expectedExtraPaneAdaptedValue: PaneAdaptedValue
-) {
-    assertThat(this[SupportingPaneScaffoldRole.Main]).isEqualTo(expectedMainPaneAdaptedValue)
-    assertThat(this[SupportingPaneScaffoldRole.Supporting]).isEqualTo(
-        expectedSupportingPaneAdaptedValue
-    )
-    assertThat(this[SupportingPaneScaffoldRole.Extra]).isEqualTo(expectedExtraPaneAdaptedValue)
-}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldStateTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldStateTest.kt
deleted file mode 100644
index 169ccb9..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldStateTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SupportingPaneScaffoldStateTest {
-    @get:Rule
-    val composeRule = createComposeRule()
-
-    @Test
-    fun singlePaneLayout_mainPaneExpandedByDefault() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = MockSinglePaneScaffoldDirective
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_mainAndSupportingPaneExpandedByDefault() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = MockDualPaneScaffoldDirective
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-
-    @Test
-    fun singlePaneLayout_paneDestinationExpanded() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = MockSinglePaneScaffoldDirective,
-                currentDestination =
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Supporting, null)
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-
-    @Test
-    fun dualPaneLayout_paneDestinationExpanded() {
-        lateinit var scaffoldState: ThreePaneScaffoldState
-
-        composeRule.setContent {
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = MockDualPaneScaffoldDirective,
-                currentDestination =
-                    ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Extra, null)
-            )
-        }
-
-        composeRule.runOnIdle {
-            assertThat(scaffoldState.scaffoldValue.primary).isEqualTo(PaneAdaptedValue.Expanded)
-            assertThat(scaffoldState.scaffoldValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
-            assertThat(scaffoldState.scaffoldValue.tertiary).isEqualTo(PaneAdaptedValue.Expanded)
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 16.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 16.dp,
-    excludedBounds = emptyList()
-)
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldScreenshotTest.kt
deleted file mode 100644
index 1659ae5..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldScreenshotTest.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import android.os.Build
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.runtime.Composable
-import androidx.compose.testutils.assertAgainstGolden
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import androidx.test.screenshot.AndroidXScreenshotTestRule
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-class ThreePaneScaffoldScreenshotTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @get:Rule
-    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3_ADAPTIVE)
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_standard() {
-        rule.setContent {
-            SampleThreePaneScaffoldStandardMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard")
-    }
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_dense() {
-        rule.setContent {
-            SampleThreePaneScaffoldDenseMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense")
-    }
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_standard_medium_size_window() {
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 700.dp,
-            simulatedHeight = 500.dp
-        ) {
-            SampleThreePaneScaffoldStandardMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard_medium")
-    }
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_dense_medium_size_window() {
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 700.dp,
-            simulatedHeight = 500.dp
-        ) {
-            SampleThreePaneScaffoldDenseMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense_medium")
-    }
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_standard_expanded_size_window() {
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 1024.dp,
-            simulatedHeight = 800.dp
-        ) {
-            SampleThreePaneScaffoldStandardMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_standard_expanded")
-    }
-
-    @Test
-    fun threePaneScaffold_listDetailPaneOrder_dense_expanded_size_window() {
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 1024.dp,
-            simulatedHeight = 800.dp
-        ) {
-            SampleThreePaneScaffoldDenseMode()
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_listDetail_dense_expanded")
-    }
-
-    @Test
-    fun threePaneScaffold_insets_compact_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContent {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_compact")
-    }
-
-    @Test
-    fun threePaneScaffold_insets_medium_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 700.dp,
-            simulatedHeight = 500.dp
-        ) {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_medium")
-    }
-
-    @Test
-    fun threePaneScaffold_insets_expanded_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 1024.dp,
-            simulatedHeight = 800.dp
-        ) {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_expanded")
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
-private fun SampleThreePaneScaffoldStandardMode() {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-        currentWindowAdaptiveInfo()
-    )
-    val scaffoldValue = calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        ThreePaneScaffoldDefaults.adaptStrategies(),
-        null
-    )
-    SampleThreePaneScaffold(
-        scaffoldDirective,
-        scaffoldValue,
-        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
-    )
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
-private fun SampleThreePaneScaffoldDenseMode() {
-    val scaffoldDirective = calculateDensePaneScaffoldDirective(
-        currentWindowAdaptiveInfo()
-    )
-    val scaffoldValue = calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        ThreePaneScaffoldDefaults.adaptStrategies(),
-        null
-    )
-    SampleThreePaneScaffold(
-        scaffoldDirective,
-        scaffoldValue,
-        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
-    )
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
-private fun SampleThreePaneScaffoldWithInsets(
-    windowInsets: WindowInsets
-) {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-        currentWindowAdaptiveInfo()
-    )
-    val scaffoldValue = calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        ThreePaneScaffoldDefaults.adaptStrategies(),
-        null
-    )
-    SampleThreePaneScaffold(
-        scaffoldDirective,
-        scaffoldValue,
-        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
-        windowInsets
-    )
-}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldTest.kt
deleted file mode 100644
index f142449..0000000
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldTest.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class ThreePaneScaffoldTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun threePaneScaffold_allPanesHidden_noVisiblePanes() {
-         val testScaffoldValue = ThreePaneScaffoldValue(
-             PaneAdaptedValue.Hidden,
-             PaneAdaptedValue.Hidden,
-             PaneAdaptedValue.Hidden
-         )
-         rule.setContent {
-             SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-         }
-
-         rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
-         rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
-         rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
-    }
-
-    @Test
-    fun threePaneScaffold_oneExpandedPane_onlyExpandedPanesAreVisible() {
-        val testScaffoldValue = ThreePaneScaffoldValue(
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Hidden,
-            PaneAdaptedValue.Hidden
-        )
-        rule.setContent {
-            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-        }
-
-        rule.onNodeWithTag("PrimaryPane").assertExists()
-        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
-        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
-    }
-
-    @Test
-    fun threePaneScaffold_twoExpandedPanes_onlyExpandedPanesAreVisible() {
-        val testScaffoldValue = ThreePaneScaffoldValue(
-            PaneAdaptedValue.Hidden,
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Expanded
-        )
-        rule.setContent {
-            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-        }
-
-        rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
-        rule.onNodeWithTag("SecondaryPane").assertExists()
-        rule.onNodeWithTag("TertiaryPane").assertExists()
-    }
-
-    @Test
-    fun threePaneScaffold_threeExpandedPanes_onlyExpandedPanesAreVisible() {
-        val testScaffoldValue = ThreePaneScaffoldValue(
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Expanded
-        )
-        rule.setContent {
-            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-        }
-
-        rule.onNodeWithTag("PrimaryPane").assertExists()
-        rule.onNodeWithTag("SecondaryPane").assertExists()
-        rule.onNodeWithTag("TertiaryPane").assertExists()
-    }
-
-    @Test
-    fun threePaneScaffold_scaffoldValueChangeWithSinglePane_expandedPanesAreChanged() {
-        var testScaffoldValue by mutableStateOf(
-            ThreePaneScaffoldValue(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Hidden
-            )
-        )
-        rule.setContent {
-            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-        }
-
-        rule.onNodeWithTag("PrimaryPane").assertExists()
-        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
-        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
-
-        testScaffoldValue = ThreePaneScaffoldValue(
-            PaneAdaptedValue.Hidden,
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Hidden
-        )
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag("PrimaryPane").assertDoesNotExist()
-        rule.onNodeWithTag("SecondaryPane").assertExists()
-        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
-    }
-
-    @Test
-    fun threePaneScaffold_scaffoldValueChangeWithDualPane_expandedPanesAreChanged() {
-        var testScaffoldValue by mutableStateOf(
-            ThreePaneScaffoldValue(
-                PaneAdaptedValue.Expanded,
-                PaneAdaptedValue.Hidden,
-                PaneAdaptedValue.Expanded
-            )
-        )
-        rule.setContent {
-            SampleThreePaneScaffold(scaffoldValue = testScaffoldValue)
-        }
-
-        rule.onNodeWithTag("PrimaryPane").assertExists()
-        rule.onNodeWithTag("SecondaryPane").assertDoesNotExist()
-        rule.onNodeWithTag("TertiaryPane").assertExists()
-
-        testScaffoldValue = ThreePaneScaffoldValue(
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Expanded,
-            PaneAdaptedValue.Hidden
-        )
-
-        rule.waitForIdle()
-
-        rule.onNodeWithTag("PrimaryPane").assertExists()
-        rule.onNodeWithTag("SecondaryPane").assertExists()
-        rule.onNodeWithTag("TertiaryPane").assertDoesNotExist()
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val MockScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-internal const val ThreePaneScaffoldTestTag = "SampleThreePaneScaffold"
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
-private fun SampleThreePaneScaffold(scaffoldValue: ThreePaneScaffoldValue) {
-    SampleThreePaneScaffold(
-        MockScaffoldDirective,
-        scaffoldValue,
-        ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder
-    )
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
-internal fun SampleThreePaneScaffold(
-    scaffoldDirective: PaneScaffoldDirective,
-    scaffoldValue: ThreePaneScaffoldValue,
-    paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout)
-) {
-    ThreePaneScaffold(
-        modifier = Modifier.fillMaxSize().testTag(ThreePaneScaffoldTestTag),
-        scaffoldDirective = scaffoldDirective,
-        scaffoldValue = scaffoldValue,
-        paneOrder = paneOrder,
-        windowInsets = windowInsets,
-        secondaryPane = {
-            AnimatedPane(
-                modifier = Modifier.testTag(tag = "SecondaryPane")
-            ) {
-                Surface(
-                    modifier = Modifier.fillMaxSize(),
-                    color = MaterialTheme.colorScheme.secondary
-                ) {}
-            }
-        },
-        tertiaryPane = {
-            AnimatedPane(
-                modifier = Modifier.testTag(tag = "TertiaryPane")
-            ) {
-                Surface(
-                    modifier = Modifier.fillMaxSize(),
-                    color = MaterialTheme.colorScheme.tertiary
-                ) {}
-            }
-        }
-    ) {
-        AnimatedPane(
-            modifier = Modifier.testTag(tag = "PrimaryPane")
-        ) {
-            Surface(
-                modifier = Modifier.fillMaxSize(),
-                color = MaterialTheme.colorScheme.primary
-            ) {}
-        }
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
deleted file mode 100644
index f4ad6e8..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import android.app.Activity
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.toSize
-import androidx.window.core.layout.WindowSizeClass
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoTracker
-import androidx.window.layout.WindowMetricsCalculator
-import kotlinx.coroutines.flow.map
-
-/**
- * Calculates and returns [WindowAdaptiveInfo] of the provided context. It's a convenient function
- * that uses the default [WindowSizeClass] constructor and the default [calculatePosture]
- * functions to retrieve [WindowSizeClass] and [Posture].
- *
- * @return [WindowAdaptiveInfo] of the provided context
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo {
-    val windowSize = with(LocalDensity.current) {
-        currentWindowSize().toSize().toDpSize()
-    }
-    return WindowAdaptiveInfo(
-        WindowSizeClass(windowSize.width.value.toInt(), windowSize.height.value.toInt()),
-        calculatePosture(collectFoldingFeaturesAsState().value)
-    )
-}
-
-/**
- * Returns and automatically update the current window size from [WindowMetricsCalculator].
- *
- * @return an [IntSize] that represents the current window size.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun currentWindowSize(): IntSize {
-    // Observe view configuration changes and recalculate the size class on each change. We can't
-    // use Activity#onConfigurationChanged as this will sometimes fail to be called on different
-    // API levels, hence why this function needs to be @Composable so we can observe the
-    // ComposeView's configuration changes.
-    LocalConfiguration.current
-    val windowBounds =
-        WindowMetricsCalculator
-            .getOrCreate()
-            .computeCurrentWindowMetrics(LocalContext.current)
-            .bounds
-   return IntSize(windowBounds.width(), windowBounds.height())
-}
-
-/**
- * Collects the current window folding features from [WindowInfoTracker] in to a [State].
- *
- * @return a [State] of a [FoldingFeature] list.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun collectFoldingFeaturesAsState(): State<List<FoldingFeature>> {
-    val context = LocalContext.current
-    return remember(context) {
-        if (context is Activity) {
-            // TODO(b/284347941) remove the instance check after the test bug is fixed.
-            WindowInfoTracker
-                .getOrCreate(context)
-                .windowLayoutInfo(context)
-        } else {
-            WindowInfoTracker
-                .getOrCreate(context)
-                .windowLayoutInfo(context)
-        }.map { it.displayFeatures.filterIsInstance<FoldingFeature>() }
-    }.collectAsState(emptyList())
-}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/BackNavigationBehavior.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/BackNavigationBehavior.android.kt
deleted file mode 100644
index 6332068..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/BackNavigationBehavior.android.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.material3.adaptive
-
-/**
- * A class to control how back navigation should behave in a [ThreePaneScaffoldNavigator].
- */
-@ExperimentalMaterial3AdaptiveApi
-enum class BackNavigationBehavior {
-    /** Pop the latest destination from the backstack. */
-    PopLatest,
-
-    /**
-     * Pop destinations from the backstack until there is a change in the scaffold value.
-     *
-     * For example, in a single-pane layout, this will skip entries until the current destination
-     * is a different [ThreePaneScaffoldRole]. In a multi-pane layout, this will skip entries until
-     * the [PaneAdaptedValue] of any pane changes.
-     */
-    PopUntilScaffoldValueChange,
-
-    /**
-     * Pop destinations from the backstack until there is a change in the current destination pane.
-     *
-     * In a single-pane layout, this should behave similarly to [PopUntilScaffoldValueChange]. In a
-     * multi-pane layout, it is possible for both the current destination and previous destination
-     * to be showing at the same time, so this may not result in a visual change in the scaffold.
-     */
-    PopUntilCurrentDestinationChange,
-
-    /**
-     * Pop destinations from the backstack until there is a content change.
-     *
-     * A "content change" is defined as either a change in the content of the current
-     * [ThreePaneScaffoldDestinationItem], or a change in the scaffold value (similar to
-     * [PopUntilScaffoldValueChange]).
-     */
-    PopUntilContentChange,
-}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt
deleted file mode 100644
index 690c11b..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/**
- * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
- * panes in a canonical list-detail layout.
- *
- * @param listPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.List].
- * @param modifier [Modifier] of the scaffold layout.
- * @param scaffoldState the state of the scaffold, which provides the current scaffold directive
- *        and scaffold value.
- * @param windowInsets window insets that the scaffold will respect.
- * @param extraPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Extra].
- * @param detailPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Detail].
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun ListDetailPaneScaffold(
-    listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
-    modifier: Modifier = Modifier,
-    scaffoldState: ThreePaneScaffoldState = calculateListDetailPaneScaffoldState(),
-    windowInsets: WindowInsets = ListDetailPaneScaffoldDefaults.windowInsets,
-    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    detailPane: @Composable ThreePaneScaffoldScope.() -> Unit
-) {
-    ThreePaneScaffold(
-        modifier = modifier.fillMaxSize(),
-        scaffoldDirective = scaffoldState.scaffoldDirective,
-        scaffoldValue = scaffoldState.scaffoldValue,
-        paneOrder = ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
-        windowInsets = windowInsets,
-        secondaryPane = listPane,
-        tertiaryPane = extraPane,
-        primaryPane = detailPane
-    )
-}
-
-/**
- * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
- * [ThreePaneScaffoldAdaptStrategies], and the current pane destination of a
- * [ListDetailPaneScaffold].
- *
- * @param currentDestination the current destination item, which will be guaranteed to have the
- *        highest priority when deciding pane visibilities.
- * @param scaffoldDirective the layout directives that the associated [ListDetailPaneScaffold]
- *        needs to follow. The default value will be the calculation result from
- *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
- *        will be automatically updated when the window configuration changes.
- * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun calculateListDetailPaneScaffoldState(
-    currentDestination: ThreePaneScaffoldDestinationItem<*> =
-        ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List, null),
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        ListDetailPaneScaffoldDefaults.adaptStrategies()
-): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
-    scaffoldDirective,
-    calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        adaptStrategies,
-        currentDestination
-    )
-)
-
-/**
- * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
- * [ThreePaneScaffoldAdaptStrategies], and the pane destination history of a
- * [ListDetailPaneScaffold].
- *
- * @param destinationHistory The history of past destinations items. The last destination will
- *        have the highest priority, and the second last destination will have the second highest
- *        priority, and so forth until all panes have a priority assigned. Note that the last
- *        destination is supposed to be the last item of the provided list. When the history is
- *        empty or there are panes left unassigned, default priorities will be assigned to those
- *        panes in the order of Detail > List > Extra.
- * @param scaffoldDirective the layout directives that the associated [ListDetailPaneScaffold]
- *        needs to follow. The default value will be the calculation result from
- *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
- *        will be automatically updated when the window configuration changes.
- * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun calculateListDetailPaneScaffoldState(
-    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        ListDetailPaneScaffoldDefaults.adaptStrategies()
-): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
-    scaffoldDirective,
-    calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        adaptStrategies,
-        destinationHistory
-    )
-)
-
-/**
- * Provides default values of [ListDetailPaneScaffold].
- */
-@ExperimentalMaterial3AdaptiveApi
-object ListDetailPaneScaffoldDefaults {
-    /**
-     * Default insets that will be used and consumed by [ListDetailPaneScaffold]. By default it will
-     * be the union of [WindowInsets.Companion.systemBars] and
-     * [WindowInsets.Companion.displayCutout].
-     */
-    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
-
-    /**
-     * Creates a default [ThreePaneScaffoldAdaptStrategies] for [ListDetailPaneScaffold].
-     *
-     * @param detailPaneAdaptStrategy the adapt strategy of the primary pane
-     * @param listPaneAdaptStrategy the adapt strategy of the secondary pane
-     * @param extraPaneAdaptStrategy the adapt strategy of the tertiary pane
-     */
-    fun adaptStrategies(
-        detailPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        listPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-    ): ThreePaneScaffoldAdaptStrategies =
-        ThreePaneScaffoldAdaptStrategies(
-            detailPaneAdaptStrategy,
-            listPaneAdaptStrategy,
-            extraPaneAdaptStrategy
-        )
-}
-
-/**
- * The set of the available pane roles of [ListDetailPaneScaffold]. Basically those values are
- * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
- * the raw [ThreePaneScaffoldRole] under the context of [ListDetailPaneScaffold] for better
- * code clarity.
- */
-@ExperimentalMaterial3AdaptiveApi
-object ListDetailPaneScaffoldRole {
-    /**
-     * The list pane of [ListDetailPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Secondary].
-     */
-    val List = ThreePaneScaffoldRole.Secondary
-
-    /**
-     * The detail pane of [ListDetailPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Primary].
-     */
-    val Detail = ThreePaneScaffoldRole.Primary
-
-    /**
-     * The extra pane of [ListDetailPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Tertiary].
-     */
-    val Extra = ThreePaneScaffoldRole.Tertiary
-}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt
deleted file mode 100644
index 6cc19e6..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/**
- * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
- * panes in a canonical supporting-pane layout.
- *
- * @param supportingPane the supporting pane of the scaffold.
- *        See [SupportingPaneScaffoldRole.Supporting].
- * @param modifier [Modifier] of the scaffold layout.
- * @param scaffoldState the state of the scaffold, which provides the current scaffold directive
- *        and scaffold value.
- * @param windowInsets window insets that the scaffold will respect.
- * @param extraPane the extra pane of the scaffold. See [SupportingPaneScaffoldRole.Extra].
- * @param mainPane the main pane of the scaffold. See [SupportingPaneScaffoldRole.Main].
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun SupportingPaneScaffold(
-    supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
-    modifier: Modifier = Modifier,
-    scaffoldState: ThreePaneScaffoldState = calculateSupportingPaneScaffoldState(),
-    windowInsets: WindowInsets = SupportingPaneScaffoldDefaults.windowInsets,
-    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    mainPane: @Composable ThreePaneScaffoldScope.() -> Unit
-) {
-    ThreePaneScaffold(
-        modifier = modifier.fillMaxSize(),
-        scaffoldDirective = scaffoldState.scaffoldDirective,
-        scaffoldValue = scaffoldState.scaffoldValue,
-        paneOrder = ThreePaneScaffoldDefaults.SupportingPaneLayoutPaneOrder,
-        windowInsets = windowInsets,
-        secondaryPane = supportingPane,
-        tertiaryPane = extraPane,
-        primaryPane = mainPane
-    )
-}
-
-/**
- * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
- * [ThreePaneScaffoldAdaptStrategies], and the current pane destination of a
- * [SupportingPaneScaffold].
- *
- * @param currentDestination the current destination item, which will be guaranteed to have the
- *        highest priority when deciding pane visibilities.
- * @param scaffoldDirective the layout directives that the associated [SupportingPaneScaffold]
- *        needs to follow. The default value will be the calculation result from
- *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
- *        will be automatically updated when the window configuration changes.
- * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun calculateSupportingPaneScaffoldState(
-    currentDestination: ThreePaneScaffoldDestinationItem<*> =
-        ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main, null),
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        SupportingPaneScaffoldDefaults.adaptStrategies()
-): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
-    scaffoldDirective,
-    calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        adaptStrategies,
-        currentDestination
-    )
-)
-
-/**
- * This function calculates [ThreePaneScaffoldValue] based on the given [PaneScaffoldDirective],
- * [ThreePaneScaffoldAdaptStrategies], and the pane destination history of a
- * [SupportingPaneScaffold].
- *
- * @param destinationHistory The history of past destination items. The last destination will
- *        have the highest priority, and the second last destination will have the second highest
- *        priority, and so forth until all panes have a priority assigned. Note that the last
- *        destination is supposed to be the last item of the provided list. When the history is
- *        empty or there are panes left unassigned, default priorities will be assigned to those
- *        panes in the order of Main > Supporting > Extra.
- * @param scaffoldDirective the layout directives that the associated [SupportingPaneScaffold]
- *        needs to follow. The default value will be the calculation result from
- *        [calculateStandardPaneScaffoldDirective] with the current window configuration, and
- *        will be automatically updated when the window configuration changes.
- * @param adaptStrategies the [ThreePaneScaffoldAdaptStrategies] should be used by scaffold panes.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun calculateSupportingPaneScaffoldState(
-    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        SupportingPaneScaffoldDefaults.adaptStrategies()
-): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
-    scaffoldDirective,
-    calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        adaptStrategies,
-        destinationHistory
-    )
-)
-
-/**
- * Provides default values of [SupportingPaneScaffold].
- */
-@ExperimentalMaterial3AdaptiveApi
-object SupportingPaneScaffoldDefaults {
-    /**
-     * Default insets that will be used and consumed by [SupportingPaneScaffold]. By default it will
-     * be the union of [WindowInsets.Companion.systemBars] and
-     * [WindowInsets.Companion.displayCutout].
-     */
-    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
-
-    /**
-     * Creates a default [ThreePaneScaffoldAdaptStrategies] for [SupportingPaneScaffold].
-     *
-     * @param mainPaneAdaptStrategy the adapt strategy of the main pane
-     * @param supportingPaneAdaptStrategy the adapt strategy of the supporting pane
-     * @param extraPaneAdaptStrategy the adapt strategy of the extra pane
-     */
-    fun adaptStrategies(
-        mainPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        supportingPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-    ): ThreePaneScaffoldAdaptStrategies =
-        ThreePaneScaffoldAdaptStrategies(
-            mainPaneAdaptStrategy,
-            supportingPaneAdaptStrategy,
-            extraPaneAdaptStrategy
-        )
-}
-
-/**
- * The set of the available pane roles of [SupportingPaneScaffold]. Basically those values are
- * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
- * the raw [ThreePaneScaffoldRole] under the context of [SupportingPaneScaffold] for better
- * code clarity.
- */
-@ExperimentalMaterial3AdaptiveApi
-object SupportingPaneScaffoldRole {
-    /**
-     * The main pane of [SupportingPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Primary].
-     */
-    val Main = ThreePaneScaffoldRole.Primary
-
-    /**
-     * The supporting pane of [SupportingPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Secondary].
-     */
-    val Supporting = ThreePaneScaffoldRole.Secondary
-
-    /**
-     * The extra pane of [SupportingPaneScaffold]. It is an alias of
-     * [ThreePaneScaffoldRole.Tertiary].
-     */
-    val Extra = ThreePaneScaffoldRole.Tertiary
-}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.android.kt
deleted file mode 100644
index 4e02b85..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.android.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.runtime.Stable
-
-/**
- * The state of [ThreePaneScaffold]. It provides the layout directive and value state that will
- * be updated directly. It also provides functions to perform navigation.
- *
- * @property scaffoldDirective the current layout directives that the associated
- *           [ThreePaneScaffold] needs to follow. It's supposed to be automatically updated
- *           when the window configuration changes.
- * @property scaffoldValue the current layout value of the associated [ThreePaneScaffold],
- *           which represents unique layout states of the scaffold.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Stable
-interface ThreePaneScaffoldState {
-    val scaffoldDirective: PaneScaffoldDirective
-    val scaffoldValue: ThreePaneScaffoldValue
-}
-
-@ExperimentalMaterial3AdaptiveApi
-internal class ThreePaneScaffoldStateImpl(
-    override val scaffoldDirective: PaneScaffoldDirective,
-    override val scaffoldValue: ThreePaneScaffoldValue
-) : ThreePaneScaffoldState
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt
deleted file mode 100644
index a8a181f..0000000
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.listSaver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.util.fastMap
-
-/**
- * The common interface of the default navigation implementations for different [ThreePaneScaffold].
- *
- * In general, we suggest you to use [rememberListDetailPaneScaffoldNavigator] or
- * [rememberSupportingPaneScaffoldNavigator] to get remembered default instances of this interface
- * for [ListDetailPaneScaffold] and [SupportingPaneScaffold], respectively. Those default
- * implementations work independently from any navigation frameworks.
- *
- * If you need to integrate with existing navigation frameworks or implement your own custom
- * navigation logic, usually creating whole new APIs that's tailored for your own solution will be
- * recommended, instead of implementing this interface. But we recommend you refer to the API design
- * and the default implementation to get better understanding and address the intricacies of
- * navigation in an adaptive scenario.
- *
- * @param T the type representing the content (or id of the content) for a navigation destination.
- * This type must be storable in a Bundle.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Stable
-interface ThreePaneScaffoldNavigator<T> {
-    /**
-     * The current scaffold state provided by the navigator.
-     *
-     * Implementors of this interface should ensure this value is updated whenever a navigation
-     * operation is performed.
-     */
-    val scaffoldState: ThreePaneScaffoldState
-
-    /**
-     * The current destination as tracked by the navigator.
-     *
-     * Implementors of this interface should ensure this value is updated whenever a navigation
-     * operation is performed.
-     */
-    val currentDestination: ThreePaneScaffoldDestinationItem<T>?
-
-    /**
-     * Indicates if the navigator should be aware of pane destination history when deciding the
-     * result [ThreePaneScaffoldValue] by a navigation operation. If the value is `false`, only
-     * the current destination will be considered in the scaffold value calculation.
-     *
-     * @see calculateThreePaneScaffoldValue for more detailed explanation about history awareness.
-     */
-    var isDestinationHistoryAware: Boolean
-
-    /**
-     * Navigates to a new destination. The new destination is supposed to have the highest
-     * priority when calculating the new [scaffoldState]. When implementing this method, please
-     * ensure the new destination pane will be expanded or adapted in a reasonable way so it
-     * provides users the sense that the new destination is the pane under current usage.
-     *
-     * @param pane the new destination pane.
-     * @param content the optional content, or an id representing the content of the new
-     * destination.
-     */
-    fun navigateTo(pane: ThreePaneScaffoldRole, content: T? = null)
-
-    /**
-     * Returns `true` if there is a previous destination to navigate back to.
-     *
-     * Implementors of this interface should ensure the logic of this function is consistent with
-     * [navigateBack].
-     *
-     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
-     * during the back navigation. See [BackNavigationBehavior].
-     */
-    fun canNavigateBack(
-        backNavigationBehavior: BackNavigationBehavior =
-            BackNavigationBehavior.PopUntilScaffoldValueChange
-    ): Boolean
-
-    /**
-     * Navigates to the previous destination. Returns `true` if there is a previous destination to
-     * navigate back to. When implementing this function, please make sure the logic is consistent
-     * with [canNavigateBack].
-     *
-     * Implementors of this interface should ensure the logic of this function is consistent with
-     * [canNavigateBack].
-     *
-     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
-     * during the back navigation. See [BackNavigationBehavior].
-     */
-    fun navigateBack(
-        backNavigationBehavior: BackNavigationBehavior =
-            BackNavigationBehavior.PopUntilScaffoldValueChange
-    ): Boolean
-}
-
-/**
- * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
- * [ListDetailPaneScaffold], which will be updated automatically when the input values change.
- * The default navigator is supposed to be used independently from any navigation frameworks and
- * it will address the navigation purely inside the [ListDetailPaneScaffold].
- *
- * @param scaffoldDirective the current layout directives to follow. The default value will be
- *        calculated with [calculateStandardPaneScaffoldDirective] using [WindowAdaptiveInfo]
- *        retrieved from the current context.
- * @param adaptStrategies adaptation strategies of each pane.
- * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
- *        full destination history, instead of just the current destination. See
- *        [calculateThreePaneScaffoldValue] for more relevant details.
- * @param initialDestinationHistory the initial pane destination history of the scaffold, by default
- *        it will be just the list pane.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun <T> rememberListDetailPaneScaffoldNavigator(
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        ListDetailPaneScaffoldDefaults.adaptStrategies(),
-    isDestinationHistoryAware: Boolean = true,
-    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>> =
-        DefaultListDetailPaneHistory,
-): ThreePaneScaffoldNavigator<T> =
-    rememberThreePaneScaffoldNavigator(
-        scaffoldDirective,
-        adaptStrategies,
-        isDestinationHistoryAware,
-        initialDestinationHistory
-    )
-
-/**
- * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
- * [SupportingPaneScaffold], which will be updated automatically when the input values change.
- * The default navigator is supposed to be used independently from any navigation frameworks and
- * it will address the navigation purely inside the [SupportingPaneScaffold].
- *
- * @param scaffoldDirective the current layout directives to follow. The default value will be
- *        calculated with [calculateStandardPaneScaffoldDirective] using [WindowAdaptiveInfo]
- *        retrieved from the current context.
- * @param adaptStrategies adaptation strategies of each pane.
- * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
- *        full destination history, instead of just the current destination. See
- *        [calculateThreePaneScaffoldValue] for more relevant details.
- * @param initialDestinationHistory the initial destination history of the scaffold, by default it
- *        will be just the main pane.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun <T> rememberSupportingPaneScaffoldNavigator(
-    scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
-        SupportingPaneScaffoldDefaults.adaptStrategies(),
-    isDestinationHistoryAware: Boolean = true,
-    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>> =
-        DefaultSupportingPaneHistory,
-): ThreePaneScaffoldNavigator<T> =
-    rememberThreePaneScaffoldNavigator(
-        scaffoldDirective,
-        adaptStrategies,
-        isDestinationHistoryAware,
-        initialDestinationHistory
-    )
-
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-internal fun <T> rememberThreePaneScaffoldNavigator(
-    scaffoldDirective: PaneScaffoldDirective,
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
-    isDestinationHistoryAware: Boolean,
-    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>>
-): ThreePaneScaffoldNavigator<T> =
-    rememberSaveable(
-        saver = DefaultThreePaneScaffoldNavigator.saver(
-            scaffoldDirective,
-            adaptStrategies,
-            isDestinationHistoryAware
-        )
-    ) {
-        DefaultThreePaneScaffoldNavigator(
-            initialDestinationHistory = initialDestinationHistory,
-            initialScaffoldDirective = scaffoldDirective,
-            initialAdaptStrategies = adaptStrategies,
-            initialIsDestinationHistoryAware = isDestinationHistoryAware
-        )
-    }.apply {
-        this.scaffoldDirective = scaffoldDirective
-        this.adaptStrategies = adaptStrategies
-        this.isDestinationHistoryAware = isDestinationHistoryAware
-    }
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal class DefaultThreePaneScaffoldNavigator<T>(
-    initialDestinationHistory: List<ThreePaneScaffoldDestinationItem<T>>,
-    initialScaffoldDirective: PaneScaffoldDirective,
-    initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies,
-    initialIsDestinationHistoryAware: Boolean
-) : ThreePaneScaffoldNavigator<T>, ThreePaneScaffoldState {
-
-    private val destinationHistory =
-        mutableStateListOf<ThreePaneScaffoldDestinationItem<T>>().apply {
-            addAll(initialDestinationHistory)
-        }
-
-    override val scaffoldState = this
-
-    override var scaffoldDirective by mutableStateOf(initialScaffoldDirective)
-
-    override var isDestinationHistoryAware by mutableStateOf(initialIsDestinationHistoryAware)
-
-    var adaptStrategies by mutableStateOf(initialAdaptStrategies)
-
-    override val currentDestination get() = destinationHistory.lastOrNull()
-
-    override val scaffoldValue by derivedStateOf {
-        calculateScaffoldValue(destinationHistory.lastIndex)
-    }
-
-    override fun navigateTo(pane: ThreePaneScaffoldRole, content: T?) {
-        destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, content))
-    }
-
-    override fun canNavigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean =
-        getPreviousDestinationIndex(backNavigationBehavior) >= 0
-
-    override fun navigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean {
-        val previousDestinationIndex = getPreviousDestinationIndex(backNavigationBehavior)
-        if (previousDestinationIndex < 0) {
-            destinationHistory.clear()
-            return false
-        }
-        val targetSize = previousDestinationIndex + 1
-        while (destinationHistory.size > targetSize) {
-            destinationHistory.removeLast()
-        }
-        return true
-    }
-
-    private fun getPreviousDestinationIndex(backNavBehavior: BackNavigationBehavior): Int {
-        if (destinationHistory.size <= 1) {
-            // No previous destination
-            return -1
-        }
-        when (backNavBehavior) {
-            BackNavigationBehavior.PopLatest -> return destinationHistory.lastIndex - 1
-
-            BackNavigationBehavior.PopUntilScaffoldValueChange ->
-                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
-                    val previousValue = calculateScaffoldValue(previousDestinationIndex)
-                    if (previousValue != scaffoldValue) {
-                        return previousDestinationIndex
-                    }
-                }
-
-            BackNavigationBehavior.PopUntilCurrentDestinationChange ->
-                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
-                    val destination = destinationHistory[previousDestinationIndex].pane
-                    if (destination != currentDestination?.pane) {
-                        return previousDestinationIndex
-                    }
-                }
-
-            BackNavigationBehavior.PopUntilContentChange ->
-                for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
-                    val content = destinationHistory[previousDestinationIndex].content
-                    if (content != currentDestination?.content) {
-                        return previousDestinationIndex
-                    }
-                    // A scaffold value change also counts as a content change.
-                    val previousValue = calculateScaffoldValue(previousDestinationIndex)
-                    if (previousValue != scaffoldValue) {
-                        return previousDestinationIndex
-                    }
-                }
-        }
-
-        return -1
-    }
-
-    private fun calculateScaffoldValue(destinationIndex: Int) =
-        if (destinationIndex == -1) {
-            calculateThreePaneScaffoldValue(
-                scaffoldDirective.maxHorizontalPartitions,
-                adaptStrategies,
-                null
-            )
-        } else if (isDestinationHistoryAware) {
-            calculateThreePaneScaffoldValue(
-                scaffoldDirective.maxHorizontalPartitions,
-                adaptStrategies,
-                destinationHistory.subList(0, destinationIndex + 1)
-            )
-        } else {
-            calculateThreePaneScaffoldValue(
-                scaffoldDirective.maxHorizontalPartitions,
-                adaptStrategies,
-                destinationHistory[destinationIndex]
-            )
-        }
-
-    companion object {
-        /**
-         * To keep destination history saved
-         */
-        fun <T> saver(
-            initialScaffoldDirective: PaneScaffoldDirective,
-            initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies,
-            initialDestinationHistoryAware: Boolean
-        ): Saver<DefaultThreePaneScaffoldNavigator<T>, *> {
-            val destinationItemSaver = ThreePaneScaffoldDestinationItem.saver<T>()
-            return listSaver(
-                save = {
-                    it.destinationHistory.fastMap { destination ->
-                        with(destinationItemSaver) { save(destination) }
-                    }
-                },
-                restore = {
-                    DefaultThreePaneScaffoldNavigator(
-                        initialDestinationHistory = it.fastMap { savedDestination ->
-                            destinationItemSaver.restore(savedDestination!!)!!
-                        },
-                        initialScaffoldDirective = initialScaffoldDirective,
-                        initialAdaptStrategies = initialAdaptStrategies,
-                        initialIsDestinationHistoryAware = initialDestinationHistoryAware
-                    )
-                }
-            )
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val DefaultListDetailPaneHistory: List<ThreePaneScaffoldDestinationItem<Nothing>> =
-    listOf(ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List))
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val DefaultSupportingPaneHistory: List<ThreePaneScaffoldDestinationItem<Nothing>> =
-    listOf(ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main))
diff --git a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirectiveTest.kt b/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirectiveTest.kt
deleted file mode 100644
index f40a549..0000000
--- a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirectiveTest.kt
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.window.core.layout.WindowSizeClass
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@RunWith(JUnit4::class)
-class PaneScaffoldDirectiveTest {
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(400, 800),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(750, 900),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(1200, 800),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(isTabletop = true)
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(400, 800),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(750, 900),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(1200, 800),
-                Posture()
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(isTabletop = true)
-            )
-        )
-
-        assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
-        assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AlwaysAvoid
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.getBounds())
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AvoidOccluding
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(0, 2).getBounds())
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AvoidSeparating
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(2, 3).getBounds())
-    }
-
-    @Test
-    fun test_calculateStandardPaneScaffoldDirective_neverAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.NeverAvoid
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEmpty()
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AlwaysAvoid
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.getBounds())
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AvoidOccluding
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(0, 2).getBounds())
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.AvoidSeparating
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEqualTo(hingeList.subList(2, 3).getBounds())
-    }
-
-    @Test
-    fun test_calculateDensePaneScaffoldDirective_neverAvoidHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
-            WindowAdaptiveInfo(
-                WindowSizeClass(700, 800),
-                Posture(hingeList = hingeList)
-            ),
-            HingePolicy.NeverAvoid
-        )
-
-        assertThat(scaffoldDirective.excludedBounds).isEmpty()
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private val hingeList = listOf(
-    HingeInfo(
-        bounds = Rect(0F, 0F, 1F, 1F),
-        isVertical = true,
-        isSeparating = false,
-        isOccluding = true
-    ),
-    HingeInfo(
-        bounds = Rect(1F, 1F, 2F, 2F),
-        isVertical = true,
-        isSeparating = false,
-        isOccluding = true
-    ),
-    HingeInfo(
-        bounds = Rect(2F, 2F, 3F, 3F),
-        isVertical = true,
-        isSeparating = true,
-        isOccluding = false
-    ),
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun List<HingeInfo>.getBounds(): List<Rect> {
-    return map { it.bounds }
-}
diff --git a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt b/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt
deleted file mode 100644
index b2b2fba..0000000
--- a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@RunWith(JUnit4::class)
-class ThreePaneScaffoldValueTest {
-    @Test
-    fun calculateWithoutHistory_onePaneLayout_noDestination() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 1,
-            adaptStrategies = MockAdaptStrategies,
-            currentDestination = null
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithHistory_onePaneLayout_noDestination() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 1,
-            adaptStrategies = MockAdaptStrategies,
-            destinationHistory = emptyList()
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithoutHistory_onePaneLayout() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 1,
-            adaptStrategies = MockAdaptStrategies,
-            currentDestination =
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithHistory_onePaneLayout() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 1,
-            adaptStrategies = MockAdaptStrategies,
-            destinationHistory = listOf(
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
-            )
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithoutHistory_twoPaneLayout_noDestination() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 2,
-            adaptStrategies = MockAdaptStrategies,
-            currentDestination = null
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithHistory_twoPaneLayout_noDestination() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 2,
-            adaptStrategies = MockAdaptStrategies,
-            destinationHistory = emptyList()
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
-    }
-
-    @Test
-    fun calculateWithoutHistory_twoPaneLayout() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 2,
-            adaptStrategies = MockAdaptStrategies,
-            currentDestination =
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null)
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
-    }
-
-    @Test
-    fun calculateWithHistory_twoPaneLayout() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 2,
-            adaptStrategies = MockAdaptStrategies,
-            destinationHistory = listOf(
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null)
-            )
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
-    }
-
-    @Test
-    fun calculateWithHistory_twoPaneLayout_longHistory() {
-        val scaffoldState = calculateThreePaneScaffoldValue(
-            maxHorizontalPartitions = 2,
-            adaptStrategies = MockAdaptStrategies,
-            destinationHistory = listOf(
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Primary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Secondary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Primary, null),
-                ThreePaneScaffoldDestinationItem(ThreePaneScaffoldRole.Tertiary, null)
-            )
-        )
-        scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
-    }
-
-    private fun ThreePaneScaffoldValue.assertState(
-        role: ThreePaneScaffoldRole,
-        state: PaneAdaptedValue
-    ) {
-        assertThat(this[role]).isEqualTo(state)
-    }
-
-    companion object {
-        private val PrimaryPaneAdaptStrategy = AdaptStrategy.Hide
-        private val SecondaryPaneAdaptStrategy = AdaptStrategy.Hide
-        private val TertiaryPaneAdaptStrategy = AdaptStrategy.Hide
-        private val PrimaryPaneAdaptedState = PaneAdaptedValue.Hidden
-        private val SecondaryPaneAdaptedState = PaneAdaptedValue.Hidden
-        private val TertiaryPaneAdaptedState = PaneAdaptedValue.Hidden
-        private val MockAdaptStrategies = ThreePaneScaffoldAdaptStrategies(
-            PrimaryPaneAdaptStrategy,
-            SecondaryPaneAdaptStrategy,
-            TertiaryPaneAdaptStrategy
-        )
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptStrategy.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptStrategy.kt
deleted file mode 100644
index 550ffda..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptStrategy.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-/**
- * Provides the information about how the associated pane should be adapted if it cannot be
- * displayed in its [PaneAdaptedValue.Expanded] state.
- */
-@ExperimentalMaterial3AdaptiveApi
-interface AdaptStrategy {
-    /**
-     * Override this function to provide the resulted adapted state.
-     */
-    fun adapt(): PaneAdaptedValue
-
-    private class BaseAdaptStrategy(
-        private val description: String,
-        private val adaptedState: PaneAdaptedValue
-    ) : AdaptStrategy {
-        override fun adapt() = adaptedState
-
-        override fun toString() = "AdaptStrategy[$description]"
-    }
-
-    companion object {
-        /**
-         * The default [AdaptStrategy] that suggests the layout to hide the associated pane when
-         * it has to be adapted, i.e., cannot be displayed in its [PaneAdaptedValue.Expanded] state.
-         */
-        val Hide: AdaptStrategy = BaseAdaptStrategy("Hide", PaneAdaptedValue.Hidden)
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AnimateBoundsModifier.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AnimateBoundsModifier.kt
deleted file mode 100644
index a72d4a4..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AnimateBoundsModifier.kt
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector
-import androidx.compose.animation.core.AnimationVector2D
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.TwoWayConverter
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.intermediateLayout
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.round
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
-@OptIn(ExperimentalComposeUiApi::class)
-internal fun Modifier.animateBounds(
-    modifier: Modifier = Modifier,
-    sizeAnimationSpec: FiniteAnimationSpec<IntSize> = spring(
-        Spring.DampingRatioNoBouncy,
-        Spring.StiffnessMediumLow
-    ),
-    positionAnimationSpec: FiniteAnimationSpec<IntOffset> = spring(
-        Spring.DampingRatioNoBouncy,
-        Spring.StiffnessMediumLow
-    ),
-    debug: Boolean = false,
-    lookaheadScope: (closestLookaheadScope: LookaheadScope) -> LookaheadScope = { it }
-) = composed {
-
-    val outerOffsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
-    val outerSizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
-
-    val offsetAnimation = remember { DeferredAnimation(IntOffset.VectorConverter) }
-    val sizeAnimation = remember { DeferredAnimation(IntSize.VectorConverter) }
-
-    // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as
-    // intermediateLayout is expected to produce intermediate stages of a layout transform.
-    // When the measure block is invoked after lookahead pass, the lookahead size of the
-    // child will be accessible as a parameter to the measure block.
-    this
-        .drawWithContent {
-            drawContent()
-            if (debug) {
-                val offset = outerOffsetAnimation.target!! - outerOffsetAnimation.value!!
-                translate(
-                    offset.x.toFloat(), offset.y.toFloat()
-                ) {
-                    drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f))
-                }
-            }
-        }
-        .intermediateLayout { measurable, constraints ->
-            val (w, h) = outerSizeAnimation.updateTarget(
-                lookaheadSize,
-                sizeAnimationSpec,
-            )
-            measurable
-                .measure(constraints)
-                .run {
-                    layout(w, h) {
-                        val (x, y) = outerOffsetAnimation.updateTargetBasedOnCoordinates(
-                            positionAnimationSpec
-                        )
-                        place(x, y)
-                    }
-                }
-        }
-        .then(modifier)
-        .drawWithContent {
-            drawContent()
-            if (debug) {
-                val offset = offsetAnimation.target!! - offsetAnimation.value!!
-                translate(
-                    offset.x.toFloat(), offset.y.toFloat()
-                ) {
-                    drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f))
-                }
-            }
-        }
-        .intermediateLayout { measurable, _ ->
-            // When layout changes, the lookahead pass will calculate a new final size for the
-            // child modifier. This lookahead size can be used to animate the size
-            // change, such that the animation starts from the current size and gradually
-            // change towards `lookaheadSize`.
-            val (width, height) = sizeAnimation.updateTarget(
-                lookaheadSize,
-                sizeAnimationSpec,
-            )
-            // Creates a fixed set of constraints using the animated size
-            val animatedConstraints = Constraints.fixed(width, height)
-            // Measure child/children with animated constraints.
-            val placeable = measurable.measure(animatedConstraints)
-            layout(placeable.width, placeable.height) {
-                val (x, y) = with(lookaheadScope(this@intermediateLayout)) {
-                    offsetAnimation.updateTargetBasedOnCoordinates(
-                        positionAnimationSpec,
-                    )
-                }
-                placeable.place(x, y)
-            }
-        }
-}
-
-context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
-@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
-@OptIn(ExperimentalComposeUiApi::class)
-internal fun DeferredAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
-    animationSpec: FiniteAnimationSpec<IntOffset>,
-): IntOffset {
-    coordinates?.let { coordinates ->
-        with(this@PlacementScope) {
-            val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
-            val animOffset = updateTarget(
-                targetOffset.round(),
-                animationSpec,
-            )
-            val current = lookaheadScopeCoordinates.localPositionOf(
-                coordinates,
-                Offset.Zero
-            ).round()
-            return (animOffset - current)
-        }
-    }
-
-    return IntOffset.Zero
-}
-
-// Experimenting with a way to initialize animation during measurement && only take the last target
-// change in a frame (if the target was changed multiple times in the same frame) as the
-// animation target.
-internal class DeferredAnimation<T, V : AnimationVector>(
-    private val vectorConverter: TwoWayConverter<T, V>
-) {
-    val value: T?
-        get() = animatable?.value ?: target
-    var target: T? by mutableStateOf(null)
-        private set
-    private var animatable: Animatable<T, V>? = null
-
-    internal val isActive: Boolean
-        get() = target != animatable?.targetValue || animatable?.isRunning == true
-
-    context (CoroutineScope)
-    fun updateTarget(
-        targetValue: T,
-        animationSpec: FiniteAnimationSpec<T>,
-    ): T {
-        target = targetValue
-        if (target != null && target != animatable?.targetValue) {
-            animatable?.run {
-                launch {
-                    animateTo(
-                        targetValue,
-                        animationSpec
-                    )
-                }
-            } ?: Animatable(targetValue, vectorConverter).let {
-                animatable = it
-            }
-        }
-        return animatable?.value ?: targetValue
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt
deleted file mode 100644
index 4636316..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-@RequiresOptIn(
-    "This material3-adaptive API is experimental and is likely to change or to be" +
-        "removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalMaterial3AdaptiveApi
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneAdaptedValue.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneAdaptedValue.kt
deleted file mode 100644
index 5d21a80..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneAdaptedValue.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-/**
- * The adapted state of a pane. It gives clues to pane scaffolds about if a certain pane should be
- * composed and how.
- */
-@ExperimentalMaterial3AdaptiveApi
-@JvmInline
-value class PaneAdaptedValue private constructor(private val description: String) {
-    companion object {
-        /**
-         * Denotes that the associated pane should be displayed in its full width and height.
-         */
-        val Expanded = PaneAdaptedValue("Expanded")
-        /**
-         * Denotes that the associated pane should be hidden.
-         */
-        val Hidden = PaneAdaptedValue("Hidden")
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffold.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffold.kt
deleted file mode 100644
index a25f6b9..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffold.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ParentDataModifierNode
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-
-/**
- * Scope for the panes of pane scaffolds.
- */
-@ExperimentalMaterial3AdaptiveApi
-interface PaneScaffoldScope {
-    /**
-     * This modifier specifies the preferred width for a pane, and the pane scaffold implementation
-     * will respect this width whenever possible. In case the modifier is not set or set to
-     * [Dp.Unspecified], the default preferred widths of the respective scaffold implementation will
-     * be used.
-     */
-    fun Modifier.preferredWidth(width: Dp): Modifier
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal abstract class PaneScaffoldScopeImpl : PaneScaffoldScope {
-    override fun Modifier.preferredWidth(width: Dp): Modifier {
-        require(width == Dp.Unspecified || width > 0.dp) { "invalid width" }
-        return this.then(PreferredWidthElement(width))
-    }
-}
-
-private class PreferredWidthElement(
-    private val width: Dp,
-) : ModifierNodeElement<PreferredWidthNode>() {
-    private val inspectorInfo = debugInspectorInfo {
-        name = "preferredWidth"
-        value = width
-    }
-
-    override fun create(): PreferredWidthNode {
-        return PreferredWidthNode(width)
-    }
-
-    override fun update(node: PreferredWidthNode) {
-        node.width = width
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        inspectorInfo()
-    }
-
-    override fun hashCode(): Int {
-        return width.hashCode()
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        val otherModifier = other as? PreferredWidthElement ?: return false
-        return width == otherModifier.width
-    }
-}
-
-private class PreferredWidthNode(var width: Dp) : ParentDataModifierNode, Modifier.Node() {
-    override fun Density.modifyParentData(parentData: Any?) =
-        ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
-            it.preferredWidth = with(this) { width.toPx() }
-        }
-}
-
-internal fun Modifier.animatedPane(): Modifier {
-    return this.then(AnimatedPaneElement)
-}
-
-private object AnimatedPaneElement : ModifierNodeElement<AnimatedPaneNode>() {
-    private val inspectorInfo = debugInspectorInfo {
-        name = "isPaneComposable"
-        value = true
-    }
-
-    override fun create(): AnimatedPaneNode {
-        return AnimatedPaneNode()
-    }
-
-    override fun update(node: AnimatedPaneNode) {
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        inspectorInfo()
-    }
-
-    override fun hashCode(): Int {
-        return 0
-    }
-
-    override fun equals(other: Any?): Boolean {
-        return (other is AnimatedPaneElement)
-    }
-}
-
-private class AnimatedPaneNode : ParentDataModifierNode, Modifier.Node() {
-    override fun Density.modifyParentData(parentData: Any?) =
-        ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
-            it.isAnimatedPane = true
-        }
-}
-
-internal data class PaneScaffoldParentData(
-    var preferredWidth: Float? = null,
-    var isAnimatedPane: Boolean = false
-)
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirective.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirective.kt
deleted file mode 100644
index 272dffd..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/PaneScaffoldDirective.kt
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.window.core.layout.WindowWidthSizeClass
-
-/**
- * Calculates the standard [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
- * method with [calculateWindowAdaptiveInfo] to acquire Material-recommended adaptive layout
- * settings of the current activity window.
- *
- * See more details on the [Material design guideline site]
- * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes).
- *
- * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
- *        layout adaptation decisions like [WindowSizeClass].
- * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address
- *        vertical hinges.
- * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
- */
-// TODO(b/285144647): Add more details regarding the use scenarios of this function.
-@ExperimentalMaterial3AdaptiveApi
-fun calculateStandardPaneScaffoldDirective(
-    windowAdaptiveInfo: WindowAdaptiveInfo,
-    verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
-): PaneScaffoldDirective {
-    val maxHorizontalPartitions: Int
-    val contentPadding: PaddingValues
-    val verticalSpacerSize: Dp
-    when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
-        WindowWidthSizeClass.COMPACT -> {
-            maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(16.dp)
-            verticalSpacerSize = 0.dp
-        }
-        WindowWidthSizeClass.MEDIUM -> {
-            maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(24.dp)
-            verticalSpacerSize = 0.dp
-        }
-        else -> {
-            maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
-            verticalSpacerSize = 24.dp
-        }
-    }
-    val maxVerticalPartitions: Int
-    val horizontalSpacerSize: Dp
-
-    // TODO(conradchen): Confirm the table top mode settings
-    if (windowAdaptiveInfo.windowPosture.isTabletop) {
-        maxVerticalPartitions = 2
-        horizontalSpacerSize = 24.dp
-    } else {
-        maxVerticalPartitions = 1
-        horizontalSpacerSize = 0.dp
-    }
-
-    return PaneScaffoldDirective(
-        contentPadding,
-        maxHorizontalPartitions,
-        verticalSpacerSize,
-        maxVerticalPartitions,
-        horizontalSpacerSize,
-        getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy)
-    )
-}
-
-/**
- * Calculates the dense-mode [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
- * method with [calculateWindowAdaptiveInfo] to acquire Material-recommended dense-mode adaptive
- * layout settings of the current activity window.
- *
- * See more details on the [Material design guideline site]
- * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes).
- *
- * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
- *        layout adaptation decisions like [WindowSizeClass].
- * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address
- *        vertical hinges.
- * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
- */
-// TODO(b/285144647): Add more details regarding the use scenarios of this function.
-@ExperimentalMaterial3AdaptiveApi
-fun calculateDensePaneScaffoldDirective(
-    windowAdaptiveInfo: WindowAdaptiveInfo,
-    verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
-): PaneScaffoldDirective {
-    val maxHorizontalPartitions: Int
-    val contentPadding: PaddingValues
-    val verticalSpacerSize: Dp
-    when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
-        WindowWidthSizeClass.COMPACT -> {
-            maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(16.dp)
-            verticalSpacerSize = 0.dp
-        }
-        WindowWidthSizeClass.MEDIUM -> {
-            maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
-            verticalSpacerSize = 24.dp
-        }
-        else -> {
-            maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
-            verticalSpacerSize = 24.dp
-        }
-    }
-    val maxVerticalPartitions: Int
-    val horizontalSpacerSize: Dp
-
-    if (windowAdaptiveInfo.windowPosture.isTabletop) {
-        maxVerticalPartitions = 2
-        horizontalSpacerSize = 24.dp
-    } else {
-        maxVerticalPartitions = 1
-        horizontalSpacerSize = 0.dp
-    }
-
-    return PaneScaffoldDirective(
-        contentPadding,
-        maxHorizontalPartitions,
-        verticalSpacerSize,
-        maxVerticalPartitions,
-        horizontalSpacerSize,
-        getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy)
-    )
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun getExcludedVerticalBounds(posture: Posture, hingePolicy: HingePolicy): List<Rect> {
-    return when (hingePolicy) {
-        HingePolicy.AvoidSeparating -> posture.separatingVerticalHingeBounds
-        HingePolicy.AvoidOccluding -> posture.occludingVerticalHingeBounds
-        HingePolicy.AlwaysAvoid -> posture.allVerticalHingeBounds
-        else -> emptyList()
-    }
-}
-
-/**
- * Top-level directives about how a pane scaffold should be arranged and spaced, like how many
- * partitions the layout can be split into and what should be the gutter size.
- *
- * @constructor create an instance of [PaneScaffoldDirective]
- * @param contentPadding Size of the paddings between the panes and the outer bounds of the layout.
- * @param maxHorizontalPartitions the max number of partitions along the horizontal axis the layout
- *        can be split into.
- * @param horizontalPartitionSpacerSize Size of the spacers between horizontal partitions.
- *        It's equivalent to the left/right margins the horizontal partitions.
- * @param maxVerticalPartitions the max number of partitions along the vertical axis the layout can
- *        be split into.
- * @param verticalPartitionSpacerSize Size of the spacers between vertical partitions.
- *        It's equivalent to the top/bottom margins of the vertical partitions.
- * @param excludedBounds the bounds of all areas in the window that the layout needs to avoid
- *        displaying anything upon it. Usually these bounds represent where physical hinges are.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-class PaneScaffoldDirective(
-    val contentPadding: PaddingValues,
-    val maxHorizontalPartitions: Int,
-    val horizontalPartitionSpacerSize: Dp,
-    val maxVerticalPartitions: Int,
-    val verticalPartitionSpacerSize: Dp,
-    val excludedBounds: List<Rect>
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is PaneScaffoldDirective) return false
-        if (contentPadding != other.contentPadding) return false
-        if (maxHorizontalPartitions != other.maxHorizontalPartitions) return false
-        if (horizontalPartitionSpacerSize != other.horizontalPartitionSpacerSize) return false
-        if (maxVerticalPartitions != other.maxVerticalPartitions) return false
-        if (verticalPartitionSpacerSize != other.verticalPartitionSpacerSize) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = contentPadding.hashCode()
-        result = 31 * result + maxHorizontalPartitions
-        result = 31 * result + horizontalPartitionSpacerSize.hashCode()
-        result = 31 * result + maxVerticalPartitions
-        result = 31 * result + verticalPartitionSpacerSize.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "PaneScaffoldDirective(contentPadding=$contentPadding, " +
-            "maxHorizontalPartitions=$maxHorizontalPartitions, " +
-            "horizontalPartitionSpacerSize=$horizontalPartitionSpacerSize, " +
-            "maxVerticalPartitions=$maxVerticalPartitions, " +
-            "verticalPartitionSpacerSize=$verticalPartitionSpacerSize, " +
-            "number of excluded bounds=${excludedBounds.size})"
-    }
-}
-
-/** Policies that indicate how hinges are supposed to be addressed in an adaptive layout. */
-@Immutable
[email protected]
-value class HingePolicy private constructor(private val value: Int) {
-    override fun toString(): String {
-        return "HingePolicy." + when (this) {
-            AlwaysAvoid -> "AlwaysAvoid"
-            AvoidSeparating -> "AvoidOccludingAndSeparating"
-            AvoidOccluding -> "AvoidOccludingOnly"
-            NeverAvoid -> "NeverAvoid"
-            else -> ""
-        }
-    }
-
-    companion object {
-        /** When rendering content in a layout, always avoid where hinges are. */
-        val AlwaysAvoid = HingePolicy(0)
-        /**
-         * When rendering content in a layout, avoid hinges that are separating. Note that an
-         * occluding hinge is supposed to be separating as well but not vice versa.
-         */
-        val AvoidSeparating = HingePolicy(1)
-        /**
-         * When rendering content in a layout, avoid hinges that are occluding. Note that an
-         * occluding hinge is supposed to be separating as well but not vice versa.
-         */
-        val AvoidOccluding = HingePolicy(2)
-        /** When rendering content in a layout, never avoid any hinges, separating or not. */
-        val NeverAvoid = HingePolicy(3)
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
deleted file mode 100644
index cf40e81..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
+++ /dev/null
@@ -1,926 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.SpringSpec
-import androidx.compose.animation.core.VisibilityThreshold
-import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideOutHorizontally
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.MultiContentMeasurePolicy
-import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntRect
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.roundToIntRect
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastForEachIndexed
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * A pane scaffold composable that can display up to three panes according to the instructions
- * provided by [ThreePaneScaffoldValue] in the order that [ThreePaneScaffoldHorizontalOrder]
- * specifies, and allocate margins and spacers according to [PaneScaffoldDirective].
- *
- * [ThreePaneScaffold] is the base composable functions of adaptive programming. Developers can
- * freely pipeline the relevant adaptive signals and use them as input of the scaffold function
- * to render the final adaptive layout.
- *
- * It's recommended to use [ThreePaneScaffold] with [calculateStandardPaneScaffoldDirective],
- * [calculateThreePaneScaffoldValue] to follow the Material design guidelines on adaptive
- * programming.
- *
- * @param modifier The modifier to be applied to the layout.
- * @param scaffoldDirective The top-level directives about how the scaffold should arrange its panes.
- * @param scaffoldValue The current adapted value of the scaffold.
- * @param paneOrder The horizontal order of the panes from start to end in the scaffold.
- * @param secondaryPane The content of the secondary pane that has a priority lower then the primary
- *                      pane but higher than the tertiary pane.
- * @param tertiaryPane The content of the tertiary pane that has the lowest priority.
- * @param primaryPane The content of the primary pane that has the highest priority.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-internal fun ThreePaneScaffold(
-    modifier: Modifier,
-    scaffoldDirective: PaneScaffoldDirective,
-    scaffoldValue: ThreePaneScaffoldValue,
-    paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets,
-    secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
-    tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
-) {
-    val layoutDirection = LocalLayoutDirection.current
-    val ltrPaneOrder = remember(paneOrder, layoutDirection) {
-        paneOrder.toLtrOrder(layoutDirection)
-    }
-    val previousScaffoldValue = remember { ThreePaneScaffoldValueHolder(scaffoldValue) }
-    val paneMotion = calculateThreePaneMotion(
-        previousScaffoldValue = previousScaffoldValue.value,
-        currentScaffoldValue = scaffoldValue,
-        paneOrder = ltrPaneOrder
-    )
-    previousScaffoldValue.value = scaffoldValue
-
-    // Create PaneWrappers for each of the panes and map the transitions according to each pane
-    // role and order.
-    val contents = listOf<@Composable () -> Unit>(
-        {
-            remember { ThreePaneScaffoldScopeImpl() }.apply {
-                paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Primary]
-                positionAnimationSpec = paneMotion.animationSpec
-                enterTransition = paneMotion.enterTransition(
-                    ThreePaneScaffoldRole.Primary,
-                    ltrPaneOrder
-                )
-                exitTransition = paneMotion.exitTransition(
-                    ThreePaneScaffoldRole.Primary,
-                    ltrPaneOrder
-                )
-                animationToolingLabel = "Primary"
-            }.primaryPane()
-        },
-        {
-            remember { ThreePaneScaffoldScopeImpl() }.apply {
-                paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Secondary]
-                positionAnimationSpec = paneMotion.animationSpec
-                enterTransition = paneMotion.enterTransition(
-                    ThreePaneScaffoldRole.Secondary,
-                    ltrPaneOrder
-                )
-                exitTransition = paneMotion.exitTransition(
-                    ThreePaneScaffoldRole.Secondary,
-                    ltrPaneOrder
-                )
-                animationToolingLabel = "Secondary"
-            }.secondaryPane()
-        },
-        {
-            if (tertiaryPane != null) {
-                remember { ThreePaneScaffoldScopeImpl() }.apply {
-                    paneAdaptedValue = scaffoldValue[ThreePaneScaffoldRole.Tertiary]
-                    positionAnimationSpec = paneMotion.animationSpec
-                    enterTransition = paneMotion.enterTransition(
-                        ThreePaneScaffoldRole.Tertiary,
-                        ltrPaneOrder
-                    )
-                    exitTransition = paneMotion.exitTransition(
-                        ThreePaneScaffoldRole.Tertiary,
-                        ltrPaneOrder
-                    )
-                    animationToolingLabel = "Tertiary"
-                }.tertiaryPane()
-            }
-        },
-    )
-
-    val measurePolicy = remember {
-        ThreePaneContentMeasurePolicy(scaffoldDirective, scaffoldValue, ltrPaneOrder, windowInsets)
-    }.apply {
-        this.scaffoldDirective = scaffoldDirective
-        this.scaffoldValue = scaffoldValue
-        this.paneOrder = ltrPaneOrder
-        this.windowInsets = windowInsets
-    }
-
-    LookaheadScope {
-        Layout(
-            contents = contents,
-            modifier = modifier,
-            measurePolicy = measurePolicy
-        )
-    }
-}
-
-/**
- * Holds the transitions that can be applied to the different panes.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class ThreePaneMotion internal constructor(
-    internal val animationSpec: FiniteAnimationSpec<IntOffset> = snap(),
-    private val firstPaneEnterTransition: EnterTransition = EnterTransition.None,
-    private val firstPaneExitTransition: ExitTransition = ExitTransition.None,
-    private val secondPaneEnterTransition: EnterTransition = EnterTransition.None,
-    private val secondPaneExitTransition: ExitTransition = ExitTransition.None,
-    private val thirdPaneEnterTransition: EnterTransition = EnterTransition.None,
-    private val thirdPaneExitTransition: ExitTransition = ExitTransition.None
-) {
-
-    /**
-     * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole]
-     * at the given [ThreePaneScaffoldHorizontalOrder].
-     */
-    fun enterTransition(
-        role: ThreePaneScaffoldRole,
-        paneOrder: ThreePaneScaffoldHorizontalOrder
-    ): EnterTransition {
-        // Quick return in case this instance is the NoMotion one.
-        if (this === NoMotion) return EnterTransition.None
-
-        return when (paneOrder.indexOf(role)) {
-            0 -> firstPaneEnterTransition
-            1 -> secondPaneEnterTransition
-            else -> thirdPaneEnterTransition
-        }
-    }
-
-    /**
-     * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole]
-     * at the given [ThreePaneScaffoldHorizontalOrder].
-     */
-    fun exitTransition(
-        role: ThreePaneScaffoldRole,
-        paneOrder: ThreePaneScaffoldHorizontalOrder
-    ): ExitTransition {
-        // Quick return in case this instance is the NoMotion one.
-        if (this === NoMotion) return ExitTransition.None
-
-        return when (paneOrder.indexOf(role)) {
-            0 -> firstPaneExitTransition
-            1 -> secondPaneExitTransition
-            else -> thirdPaneExitTransition
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ThreePaneMotion) return false
-        if (this.animationSpec != other.animationSpec) return false
-        if (this.firstPaneEnterTransition != other.firstPaneEnterTransition) return false
-        if (this.firstPaneExitTransition != other.firstPaneExitTransition) return false
-        if (this.secondPaneEnterTransition != other.secondPaneEnterTransition) return false
-        if (this.secondPaneExitTransition != other.secondPaneExitTransition) return false
-        if (this.thirdPaneEnterTransition != other.thirdPaneEnterTransition) return false
-        if (this.thirdPaneExitTransition != other.thirdPaneExitTransition) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = animationSpec.hashCode()
-        result = 31 * result + firstPaneEnterTransition.hashCode()
-        result = 31 * result + firstPaneExitTransition.hashCode()
-        result = 31 * result + secondPaneEnterTransition.hashCode()
-        result = 31 * result + secondPaneExitTransition.hashCode()
-        result = 31 * result + thirdPaneEnterTransition.hashCode()
-        result = 31 * result + thirdPaneExitTransition.hashCode()
-        return result
-    }
-
-    companion object {
-        /**
-         * A ThreePaneMotion with all transitions set to [EnterTransition.None] and
-         * [ExitTransition.None].
-         */
-        val NoMotion = ThreePaneMotion()
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class ThreePaneScaffoldValueHolder(var value: ThreePaneScaffoldValue)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun calculateThreePaneMotion(
-    previousScaffoldValue: ThreePaneScaffoldValue,
-    currentScaffoldValue: ThreePaneScaffoldValue,
-    paneOrder: ThreePaneScaffoldHorizontalOrder
-): ThreePaneMotion {
-    if (previousScaffoldValue.equals(currentScaffoldValue)) {
-        return ThreePaneMotion.NoMotion
-    }
-    val previousExpandedCount = getExpandedCount(previousScaffoldValue)
-    val currentExpandedCount = getExpandedCount(currentScaffoldValue)
-    if (previousExpandedCount != currentExpandedCount) {
-        // TODO(conradchen): Address this case
-        return ThreePaneMotion.NoMotion
-    }
-    return when (previousExpandedCount) {
-        1 -> when (PaneAdaptedValue.Expanded) {
-            previousScaffoldValue[paneOrder.firstPane] -> {
-                ThreePaneScaffoldDefaults.panesRightMotion
-            }
-
-            previousScaffoldValue[paneOrder.thirdPane] -> {
-                ThreePaneScaffoldDefaults.panesLeftMotion
-            }
-
-            currentScaffoldValue[paneOrder.thirdPane] -> {
-                ThreePaneScaffoldDefaults.panesRightMotion
-            }
-
-            else -> {
-                ThreePaneScaffoldDefaults.panesLeftMotion
-            }
-        }
-
-        2 -> when {
-            previousScaffoldValue[paneOrder.firstPane] != PaneAdaptedValue.Expanded -> {
-                ThreePaneScaffoldDefaults.panesLeftMotion
-            }
-
-            previousScaffoldValue[paneOrder.thirdPane] != PaneAdaptedValue.Expanded -> {
-                ThreePaneScaffoldDefaults.panesRightMotion
-            }
-
-            previousScaffoldValue[paneOrder.secondPane] != PaneAdaptedValue.Expanded &&
-                currentScaffoldValue[paneOrder.firstPane] != PaneAdaptedValue.Expanded -> {
-                ThreePaneScaffoldDefaults.replaceLeftPaneMotion
-            }
-
-            else -> {
-                ThreePaneScaffoldDefaults.replaceRightPaneMotion
-            }
-        }
-
-        else -> {
-            // Should not happen
-            ThreePaneMotion.NoMotion
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun getExpandedCount(scaffoldValue: ThreePaneScaffoldValue): Int {
-    var count = 0
-    if (scaffoldValue.primary == PaneAdaptedValue.Expanded) {
-        count++
-    }
-    if (scaffoldValue.secondary == PaneAdaptedValue.Expanded) {
-        count++
-    }
-    if (scaffoldValue.tertiary == PaneAdaptedValue.Expanded) {
-        count++
-    }
-    return count
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class ThreePaneContentMeasurePolicy(
-    scaffoldDirective: PaneScaffoldDirective,
-    scaffoldValue: ThreePaneScaffoldValue,
-    paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets
-) : MultiContentMeasurePolicy {
-    var scaffoldDirective by mutableStateOf(scaffoldDirective)
-    var scaffoldValue by mutableStateOf(scaffoldValue)
-    var paneOrder by mutableStateOf(paneOrder)
-    var windowInsets by mutableStateOf(windowInsets)
-
-    /**
-     * Data class that is used to store the position and width of an expanded pane to be reused when
-     * the pane is being hidden.
-     */
-    private data class PanePlacement(var positionX: Int = 0, var measuredWidth: Int = 0)
-
-    private val placementsCache = mapOf(
-        ThreePaneScaffoldRole.Primary to PanePlacement(),
-        ThreePaneScaffoldRole.Secondary to PanePlacement(),
-        ThreePaneScaffoldRole.Tertiary to PanePlacement()
-    )
-
-    override fun MeasureScope.measure(
-        measurables: List<List<Measurable>>,
-        constraints: Constraints
-    ): MeasureResult {
-        val primaryMeasurables = measurables[0]
-        val secondaryMeasurables = measurables[1]
-        val tertiaryMeasurables = measurables[2]
-        return layout(constraints.maxWidth, constraints.maxHeight) {
-            if (coordinates == null) {
-                return@layout
-            }
-            val visiblePanes = getPanesMeasurables(
-                paneOrder = paneOrder,
-                primaryMeasurables = primaryMeasurables,
-                scaffoldValue = scaffoldValue,
-                secondaryMeasurables = secondaryMeasurables,
-                tertiaryMeasurables = tertiaryMeasurables
-            ) {
-                it != PaneAdaptedValue.Hidden
-            }
-
-            val hiddenPanes = getPanesMeasurables(
-                paneOrder = paneOrder,
-                primaryMeasurables = primaryMeasurables,
-                scaffoldValue = scaffoldValue,
-                secondaryMeasurables = secondaryMeasurables,
-                tertiaryMeasurables = tertiaryMeasurables
-            ) {
-                it == PaneAdaptedValue.Hidden
-            }
-
-            val verticalSpacerSize = scaffoldDirective.horizontalPartitionSpacerSize.roundToPx()
-            val leftContentPadding = max(
-                scaffoldDirective.contentPadding.calculateLeftPadding(layoutDirection).roundToPx(),
-                windowInsets.getLeft(this@measure, layoutDirection)
-            )
-            val rightContentPadding = max(
-                scaffoldDirective.contentPadding.calculateRightPadding(layoutDirection).roundToPx(),
-                windowInsets.getRight(this@measure, layoutDirection)
-            )
-            val topContentPadding = max(
-                scaffoldDirective.contentPadding.calculateTopPadding().roundToPx(),
-                windowInsets.getTop(this@measure)
-            )
-            val bottomContentPadding = max(
-                scaffoldDirective.contentPadding.calculateBottomPadding().roundToPx(),
-                windowInsets.getBottom(this@measure)
-            )
-            val outerBounds = IntRect(
-                leftContentPadding,
-                topContentPadding,
-                constraints.maxWidth - rightContentPadding,
-                constraints.maxHeight - bottomContentPadding
-            )
-
-            if (scaffoldDirective.excludedBounds.isNotEmpty()) {
-                val layoutBounds = coordinates!!.boundsInWindow()
-                val layoutPhysicalPartitions = mutableListOf<Rect>()
-                var actualLeft = layoutBounds.left + leftContentPadding
-                var actualRight = layoutBounds.right - rightContentPadding
-                val actualTop = layoutBounds.top + topContentPadding
-                val actualBottom = layoutBounds.bottom - bottomContentPadding
-                // Assume hinge bounds are sorted from left to right, non-overlapped.
-                @Suppress("ListIterator")
-                scaffoldDirective.excludedBounds.forEach { hingeBound ->
-                    if (hingeBound.left <= actualLeft) {
-                        // The hinge is at the left of the layout, adjust the left edge of
-                        // the current partition to the actual displayable bounds.
-                        actualLeft = max(actualLeft, hingeBound.right)
-                    } else if (hingeBound.right >= actualRight) {
-                        // The hinge is right at the right of the layout and there's no more
-                        // room for more partitions, adjust the right edge of the current
-                        // partition to the actual displayable bounds.
-                        actualRight = min(hingeBound.left, actualRight)
-                        return@forEach
-                    } else {
-                        // The hinge is inside the layout, add the current partition to the list
-                        // and move the left edge of the next partition to the right of the
-                        // hinge.
-                        layoutPhysicalPartitions.add(
-                            Rect(actualLeft, actualTop, hingeBound.left, actualBottom)
-                        )
-                        actualLeft +=
-                            max(hingeBound.right, hingeBound.left + verticalSpacerSize)
-                    }
-                }
-                if (actualLeft < actualRight) {
-                    // The last partition
-                    layoutPhysicalPartitions.add(
-                        Rect(actualLeft, actualTop, actualRight, actualBottom)
-                    )
-                }
-                if (layoutPhysicalPartitions.size == 0) {
-                    // Display nothing
-                } else if (layoutPhysicalPartitions.size == 1) {
-                    measureAndPlacePanes(
-                        layoutPhysicalPartitions[0],
-                        verticalSpacerSize,
-                        visiblePanes,
-                        isLookingAhead
-                    )
-                } else if (layoutPhysicalPartitions.size < visiblePanes.size) {
-                    // Note that the only possible situation is we have only two physical partitions
-                    // but three expanded panes to show. In this case fit two panes in the larger
-                    // partition.
-                    if (layoutPhysicalPartitions[0].width > layoutPhysicalPartitions[1].width) {
-                        measureAndPlacePanes(
-                            layoutPhysicalPartitions[0],
-                            verticalSpacerSize,
-                            visiblePanes.subList(0, 2),
-                            isLookingAhead
-                        )
-                        measureAndPlacePane(
-                            layoutPhysicalPartitions[1],
-                            visiblePanes[2],
-                            isLookingAhead
-                        )
-                    } else {
-                        measureAndPlacePane(
-                            layoutPhysicalPartitions[0],
-                            visiblePanes[0],
-                            isLookingAhead
-                        )
-                        measureAndPlacePanes(
-                            layoutPhysicalPartitions[1],
-                            verticalSpacerSize,
-                            visiblePanes.subList(1, 3),
-                            isLookingAhead
-                        )
-                    }
-                } else {
-                    // Layout each visible pane in a physical partition
-                    visiblePanes.fastForEachIndexed { index, paneMeasurable ->
-                        measureAndPlacePane(
-                            layoutPhysicalPartitions[index],
-                            paneMeasurable,
-                            isLookingAhead
-                        )
-                    }
-                }
-            } else {
-                measureAndPlacePanesWithLocalBounds(
-                    outerBounds,
-                    verticalSpacerSize,
-                    visiblePanes,
-                    isLookingAhead
-                )
-            }
-
-            // Place the hidden panes. Those should only exist when isLookingAhead = true.
-            // Placing these type of pane during the lookahead phase ensures a proper motion
-            // at the AnimatedVisibility.
-            // The placement is done using the outerBounds, as the placementsCache holds
-            // absolute position values.
-            placeHiddenPanes(
-                outerBounds.top,
-                outerBounds.height,
-                hiddenPanes
-            )
-        }
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun MeasureScope.getPanesMeasurables(
-        paneOrder: ThreePaneScaffoldHorizontalOrder,
-        primaryMeasurables: List<Measurable>,
-        scaffoldValue: ThreePaneScaffoldValue,
-        secondaryMeasurables: List<Measurable>,
-        tertiaryMeasurables: List<Measurable>,
-        predicate: (PaneAdaptedValue) -> Boolean
-    ): List<PaneMeasurable> {
-        return buildList {
-            paneOrder.forEach { role ->
-                if (predicate(scaffoldValue[role])) {
-                    when (role) {
-                        ThreePaneScaffoldRole.Primary -> {
-                            createPaneMeasurableIfNeeded(
-                                primaryMeasurables,
-                                ThreePaneScaffoldDefaults.PrimaryPanePriority,
-                                role,
-                                ThreePaneScaffoldDefaults.PrimaryPanePreferredWidth
-                                    .roundToPx()
-                            )
-                        }
-
-                        ThreePaneScaffoldRole.Secondary -> {
-                            createPaneMeasurableIfNeeded(
-                                secondaryMeasurables,
-                                ThreePaneScaffoldDefaults.SecondaryPanePriority,
-                                role,
-                                ThreePaneScaffoldDefaults.SecondaryPanePreferredWidth
-                                    .roundToPx()
-                            )
-                        }
-
-                        ThreePaneScaffoldRole.Tertiary -> {
-                            createPaneMeasurableIfNeeded(
-                                tertiaryMeasurables,
-                                ThreePaneScaffoldDefaults.TertiaryPanePriority,
-                                role,
-                                ThreePaneScaffoldDefaults.TertiaryPanePreferredWidth
-                                    .roundToPx()
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun MutableList<PaneMeasurable>.createPaneMeasurableIfNeeded(
-        measurables: List<Measurable>,
-        priority: Int,
-        role: ThreePaneScaffoldRole,
-        defaultPreferredWidth: Int
-    ) {
-        if (measurables.isNotEmpty()) {
-            add(PaneMeasurable(measurables[0], priority, role, defaultPreferredWidth))
-        }
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun Placeable.PlacementScope.measureAndPlacePane(
-        partitionBounds: Rect,
-        measurable: PaneMeasurable,
-        isLookingAhead: Boolean
-    ) {
-        val localBounds = getLocalBounds(partitionBounds)
-        measurable.measuredWidth = localBounds.width
-        measurable.apply {
-            measure(Constraints.fixed(measuredWidth, localBounds.height))
-                .place(localBounds.left, localBounds.top)
-        }
-        if (isLookingAhead) {
-            // Cache the values to be used when this measurable role is being hidden.
-            // See placeHiddenPanes.
-            val cachedPanePlacement = placementsCache[measurable.role]!!
-            cachedPanePlacement.measuredWidth = measurable.measuredWidth
-            cachedPanePlacement.positionX = localBounds.left
-        }
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun Placeable.PlacementScope.measureAndPlacePanes(
-        partitionBounds: Rect,
-        spacerSize: Int,
-        measurables: List<PaneMeasurable>,
-        isLookingAhead: Boolean
-    ) {
-        measureAndPlacePanesWithLocalBounds(
-            getLocalBounds(partitionBounds),
-            spacerSize,
-            measurables,
-            isLookingAhead
-        )
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun Placeable.PlacementScope.measureAndPlacePanesWithLocalBounds(
-        partitionBounds: IntRect,
-        spacerSize: Int,
-        measurables: List<PaneMeasurable>,
-        isLookingAhead: Boolean
-    ) {
-        if (measurables.isEmpty()) {
-            return
-        }
-        val allocatableWidth = partitionBounds.width - (measurables.size - 1) * spacerSize
-        val totalPreferredWidth = measurables.sumOf { it.measuredWidth }
-        if (allocatableWidth > totalPreferredWidth) {
-            // Allocate the remaining space to the pane with the highest priority.
-            measurables.maxBy {
-                it.priority
-            }.measuredWidth += allocatableWidth - totalPreferredWidth
-        } else if (allocatableWidth < totalPreferredWidth) {
-            // Scale down all panes to fit in the available space.
-            val scale = allocatableWidth.toFloat() / totalPreferredWidth
-            measurables.fastForEach {
-                it.measuredWidth = (it.measuredWidth * scale).toInt()
-            }
-        }
-        var positionX = partitionBounds.left
-        measurables.fastForEach {
-            it.measure(Constraints.fixed(it.measuredWidth, partitionBounds.height))
-                .place(positionX, partitionBounds.top)
-            if (isLookingAhead) {
-                // Cache the values to be used when this measurable's role is being hidden.
-                // See placeHiddenPanes.
-                val cachedPanePlacement = placementsCache[it.role]!!
-                cachedPanePlacement.measuredWidth = it.measuredWidth
-                cachedPanePlacement.positionX = positionX
-            }
-            positionX += it.measuredWidth + spacerSize
-        }
-    }
-
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
-    private fun Placeable.PlacementScope.placeHiddenPanes(
-        partitionTop: Int,
-        partitionHeight: Int,
-        measurables: List<PaneMeasurable>
-    ) {
-        // When panes are being hidden, apply each pane's width and position from the cache to
-        // maintain the those before it's hidden by the AnimatedVisibility.
-        measurables.fastForEach {
-            if (!it.isAnimatedPane) {
-                // When panes are not animated, we don't need to measure and place them.
-                return
-            }
-            val cachedPanePlacement = placementsCache[it.role]!!
-            it.measure(
-                Constraints.fixed(
-                    width = cachedPanePlacement.measuredWidth,
-                    height = partitionHeight
-                )
-            ).place(cachedPanePlacement.positionX, partitionTop)
-        }
-    }
-
-    private fun Placeable.PlacementScope.getLocalBounds(bounds: Rect): IntRect {
-        return bounds.translate(coordinates!!.windowToLocal(Offset.Zero)).roundToIntRect()
-    }
-}
-
-/**
- * A conditional [Modifier.clipToBounds] that will only clip when the given [adaptedValue] is
- * [PaneAdaptedValue.Hidden].
- */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private fun Modifier.clipToBounds(adaptedValue: PaneAdaptedValue): Modifier =
-    if (adaptedValue == PaneAdaptedValue.Hidden) this.clipToBounds() else this
-
-/**
- * The root composable of pane contents in a [ThreePaneScaffold] that supports default motions
- * during pane switching. It's recommended to use this composable to wrap your own contents when
- * passing them into pane parameters of the scaffold functions, therefore your panes can have a
- * nice default animation for free.
- *
- * See usage samples at:
- * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSample
- * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSampleWithExtraPane
- */
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-fun ThreePaneScaffoldScope.AnimatedPane(
-    modifier: Modifier,
-    content: (@Composable ThreePaneScaffoldScope.() -> Unit),
-) {
-    AnimatedVisibility(
-        visible = paneAdaptedValue == PaneAdaptedValue.Expanded,
-        modifier = modifier
-            .animatedPane()
-            .clipToBounds(paneAdaptedValue)
-            .then(
-                if (paneAdaptedValue == PaneAdaptedValue.Expanded) {
-                    Modifier.animateBounds(
-                        // TODO Figure out why we need to pass a non-null here to get the bounds
-                        //  animation going on the first navigation event that pass in the spec
-                        //  later on. To resolve this, we default to the paneSpringSpec().
-                        //  Otherwise, the first motion shows a snap instead of a smooth
-                        //  transition.
-                        positionAnimationSpec = positionAnimationSpec
-                            ?: ThreePaneScaffoldDefaults.PaneSpringSpec
-                    )
-                } else {
-                    Modifier
-                }
-            ),
-        enter = enterTransition,
-        exit = exitTransition,
-        label = "AnimatedVisibility: $animationToolingLabel"
-    ) {
-        content()
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class PaneMeasurable(
-    val measurable: Measurable,
-    val priority: Int,
-    val role: ThreePaneScaffoldRole,
-    defaultPreferredWidth: Int
-) : Measurable by measurable {
-    private val data = ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData())
-
-    var measuredWidth = if (data.preferredWidth == null || data.preferredWidth!!.isNaN()) {
-        defaultPreferredWidth
-    } else {
-        data.preferredWidth!!.toInt()
-    }
-
-    val isAnimatedPane = data.isAnimatedPane
-}
-
-/**
- * Scope for the panes of [ThreePaneScaffold].
- */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-interface ThreePaneScaffoldScope : PaneScaffoldScope {
-    /**
-     * The adapted value of the associated pane to the scope.
-     */
-    val paneAdaptedValue: PaneAdaptedValue
-
-    /**
-     * The position animation spec of the associated pane to the scope. [AnimatedPane] will use this
-     * value to perform pane animations during scaffold state changes.
-     */
-    val positionAnimationSpec: FiniteAnimationSpec<IntOffset>?
-
-    /**
-     * The [EnterTransition] of the associated pane. [AnimatedPane] will use this value to perform
-     * pane entering animations when it's showing during scaffold state changes.
-     */
-    val enterTransition: EnterTransition
-
-    /**
-     * The [ExitTransition] of the associated pane. [AnimatedPane] will use this value to perform
-     * pane exiting animations when it's hiding during scaffold state changes.
-     */
-    val exitTransition: ExitTransition
-
-    /**
-     * The label will be used by [AnimatedPane] to provide tooling labels to the foundation
-     * animation APIs like [AnimatedVisibility].
-     */
-    val animationToolingLabel: String
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class ThreePaneScaffoldScopeImpl : ThreePaneScaffoldScope, PaneScaffoldScopeImpl() {
-    override var paneAdaptedValue by mutableStateOf(PaneAdaptedValue.Hidden)
-    override var positionAnimationSpec: FiniteAnimationSpec<IntOffset>? by mutableStateOf(null)
-    override var enterTransition by mutableStateOf(EnterTransition.None)
-    override var exitTransition by mutableStateOf(ExitTransition.None)
-    override var animationToolingLabel by mutableStateOf("")
-}
-
-/**
- * Provides default values of [ThreePaneScaffold] and the calculation functions of
- * [ThreePaneScaffoldValue].
- */
-@ExperimentalMaterial3AdaptiveApi
-internal object ThreePaneScaffoldDefaults {
-    /**
-     * Denotes [ThreePaneScaffold] to use the list-detail pane-order to arrange its panes
-     * horizontally, which allocates panes in the order of secondary, primary, and tertiary from
-     * start to end.
-     */
-    // TODO(conradchen/sgibly): Consider moving this to the ListDetailPaneScaffoldDefaults
-    val ListDetailLayoutPaneOrder = ThreePaneScaffoldHorizontalOrder(
-        ThreePaneScaffoldRole.Secondary,
-        ThreePaneScaffoldRole.Primary,
-        ThreePaneScaffoldRole.Tertiary
-    )
-
-    /**
-     * Denotes [ThreePaneScaffold] to use the supporting-pane pane-order to arrange its panes
-     * horizontally, which allocates panes in the order of primary, secondary, and tertiary from
-     * start to end.
-     */
-    // TODO(conradchen/sgibly): Consider moving this to the SupportingPaneScaffoldDefaults
-    val SupportingPaneLayoutPaneOrder = ThreePaneScaffoldHorizontalOrder(
-        ThreePaneScaffoldRole.Primary,
-        ThreePaneScaffoldRole.Secondary,
-        ThreePaneScaffoldRole.Tertiary
-    )
-
-    /**
-     * The default preferred width of [ThreePaneScaffoldRole.Secondary]. See more details in
-     * [ThreePaneScaffoldScope.preferredWidth].
-     */
-    val SecondaryPanePreferredWidth = 412.dp
-
-    /**
-     * The default preferred width of [ThreePaneScaffoldRole.Tertiary]. See more details in
-     * [ThreePaneScaffoldScope.preferredWidth].
-     */
-    val TertiaryPanePreferredWidth = 412.dp
-
-    // Make it the same as the secondary and tertiary panes, so we can have a semi-50-50-split on
-    // narrower windows by default.
-    val PrimaryPanePreferredWidth = 412.dp
-
-    // TODO(conradchen): consider declaring a value class for priority
-    const val PrimaryPanePriority = 10
-    const val SecondaryPanePriority = 5
-    const val TertiaryPanePriority = 1
-
-    /**
-     * Creates a default [ThreePaneScaffoldAdaptStrategies].
-     *
-     * @param primaryPaneAdaptStrategy the adapt strategy of the primary pane
-     * @param secondaryPaneAdaptStrategy the adapt strategy of the secondary pane
-     * @param tertiaryPaneAdaptStrategy the adapt strategy of the tertiary pane
-     */
-    fun adaptStrategies(
-        primaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        secondaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-        tertiaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide,
-    ): ThreePaneScaffoldAdaptStrategies =
-        ThreePaneScaffoldAdaptStrategies(
-            primaryPaneAdaptStrategy,
-            secondaryPaneAdaptStrategy,
-            tertiaryPaneAdaptStrategy
-        )
-
-    /**
-     * A default [SpringSpec] for the panes motion.
-     */
-    // TODO(conradchen): open this to public when we support motion customization
-    val PaneSpringSpec: SpringSpec<IntOffset> =
-        spring(
-            dampingRatio = 0.8f,
-            stiffness = 600f,
-            visibilityThreshold = IntOffset.VisibilityThreshold
-        )
-
-    private val slideInFromLeft = slideInHorizontally(PaneSpringSpec) { -it }
-    private val slideInFromRight = slideInHorizontally(PaneSpringSpec) { it }
-    private val slideOutToLeft = slideOutHorizontally(PaneSpringSpec) { -it }
-    private val slideOutToRight = slideOutHorizontally(PaneSpringSpec) { it }
-
-    val panesLeftMotion = ThreePaneMotion(
-        PaneSpringSpec,
-        slideInFromLeft,
-        slideOutToRight,
-        slideInFromLeft,
-        slideOutToRight,
-        slideInFromLeft,
-        slideOutToRight
-    )
-
-    val panesRightMotion = ThreePaneMotion(
-        PaneSpringSpec,
-        slideInFromRight,
-        slideOutToLeft,
-        slideInFromRight,
-        slideOutToLeft,
-        slideInFromRight,
-        slideOutToLeft
-    )
-
-    // TODO(conradchen): figure out how to add delay and zOffset to spring animations
-    val replaceLeftPaneMotion = ThreePaneMotion(
-        PaneSpringSpec,
-        slideInFromLeft,
-        slideOutToLeft,
-        slideInFromLeft,
-        slideOutToLeft,
-        EnterTransition.None,
-        ExitTransition.None
-    )
-
-    // TODO(conradchen): figure out how to add delay and zOffset to spring animations
-    val replaceRightPaneMotion = ThreePaneMotion(
-        PaneSpringSpec,
-        EnterTransition.None,
-        ExitTransition.None,
-        slideInFromRight,
-        slideOutToRight,
-        slideInFromRight,
-        slideOutToRight
-    )
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldAdaptStrategies.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldAdaptStrategies.kt
deleted file mode 100644
index b0f43b1..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldAdaptStrategies.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-/**
- * The adaptation specs of [ThreePaneScaffold]. This class denotes how each pane of
- * [ThreePaneScaffold] should be adapted. It should be used as an input parameter of
- * [calculateThreePaneScaffoldValue] to decide the [ThreePaneScaffoldValue].
- *
- * @constructor create an instance of [ThreePaneScaffoldAdaptStrategies]
- * @param primaryPaneAdaptStrategy [AdaptStrategy] of the primary pane of [ThreePaneScaffold]
- * @param secondaryPaneAdaptStrategy [AdaptStrategy] of the secondary pane of [ThreePaneScaffold]
- * @param tertiaryPaneAdaptStrategy [AdaptStrategy] of the tertiary pane of [ThreePaneScaffold]
- */
-@ExperimentalMaterial3AdaptiveApi
-class ThreePaneScaffoldAdaptStrategies(
-    private val primaryPaneAdaptStrategy: AdaptStrategy,
-    private val secondaryPaneAdaptStrategy: AdaptStrategy,
-    private val tertiaryPaneAdaptStrategy: AdaptStrategy
-) {
-    operator fun get(role: ThreePaneScaffoldRole): AdaptStrategy {
-        return when (role) {
-            ThreePaneScaffoldRole.Primary -> primaryPaneAdaptStrategy
-            ThreePaneScaffoldRole.Secondary -> secondaryPaneAdaptStrategy
-            ThreePaneScaffoldRole.Tertiary -> tertiaryPaneAdaptStrategy
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ThreePaneScaffoldAdaptStrategies) return false
-        if (primaryPaneAdaptStrategy != other.primaryPaneAdaptStrategy) return false
-        if (secondaryPaneAdaptStrategy != other.secondaryPaneAdaptStrategy) return false
-        if (tertiaryPaneAdaptStrategy != other.tertiaryPaneAdaptStrategy) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = primaryPaneAdaptStrategy.hashCode()
-        result = 31 * result + secondaryPaneAdaptStrategy.hashCode()
-        result = 31 * result + tertiaryPaneAdaptStrategy.hashCode()
-        return result
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldDestinationItem.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldDestinationItem.kt
deleted file mode 100644
index 6ff7e00..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldDestinationItem.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.material3.adaptive
-
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.listSaver
-
-/**
- * An item representing a navigation destination in a [ThreePaneScaffold].
- *
- * @param pane the pane destination of the navigation.
- * @param content the optional content, or an id representing the content of the destination.
- * The type [T] must be storable in a Bundle.
- */
-@ExperimentalMaterial3AdaptiveApi
-class ThreePaneScaffoldDestinationItem<out T>(
-    val pane: ThreePaneScaffoldRole,
-    val content: T? = null,
-) {
-    companion object {
-        internal fun <T> saver(): Saver<ThreePaneScaffoldDestinationItem<T>, Any> = listSaver(
-            save = { listOf(it.pane, it.content) },
-            restore = {
-                @Suppress("UNCHECKED_CAST")
-                ThreePaneScaffoldDestinationItem(
-                    pane = it[0] as ThreePaneScaffoldRole,
-                    content = it[1] as T?
-                )
-            }
-        )
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ThreePaneScaffoldDestinationItem<*>) return false
-
-        if (pane != other.pane) return false
-        if (content != other.content) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = pane.hashCode()
-        result = 31 * result + (content?.hashCode() ?: 0)
-        return result
-    }
-
-    override fun toString(): String {
-        return "ThreePaneScaffoldDestinationItem(pane=$pane, content=$content)"
-    }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldHorizontalOrder.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldHorizontalOrder.kt
deleted file mode 100644
index 131e717..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldHorizontalOrder.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.unit.LayoutDirection
-
-/**
- * Represents the horizontal order of panes in a [ThreePaneScaffold] from start to end. Note that
- * the values of [firstPane], [secondPane] and [thirdPane] have to be different, otherwise
- * [IllegalArgumentException] will be thrown.
- *
- * @constructor create an instance of [ThreePaneScaffoldHorizontalOrder]
- * @param firstPane The first pane from the start of the [ThreePaneScaffold]
- * @param secondPane The second pane from the start of the [ThreePaneScaffold]
- * @param thirdPane The third pane from the start of the [ThreePaneScaffold]
- */
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class ThreePaneScaffoldHorizontalOrder(
-    val firstPane: ThreePaneScaffoldRole,
-    val secondPane: ThreePaneScaffoldRole,
-    val thirdPane: ThreePaneScaffoldRole
-) {
-    init {
-        require(firstPane != secondPane && secondPane != thirdPane && firstPane != thirdPane) {
-            "invalid ThreePaneScaffoldHorizontalOrder($firstPane, $secondPane, $thirdPane)" +
-                " - panes must be unique"
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ThreePaneScaffoldHorizontalOrder) return false
-        if (firstPane != other.firstPane) return false
-        if (secondPane != other.secondPane) return false
-        if (thirdPane != other.thirdPane) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = firstPane.hashCode()
-        result = 31 * result + secondPane.hashCode()
-        result = 31 * result + thirdPane.hashCode()
-        return result
-    }
-}
-
-/**
- * Converts a bidirectional order to a left-to-right order.
- */
-@ExperimentalMaterial3AdaptiveApi
-internal fun ThreePaneScaffoldHorizontalOrder.toLtrOrder(
-    layoutDirection: LayoutDirection
-): ThreePaneScaffoldHorizontalOrder {
-    return if (layoutDirection == LayoutDirection.Rtl) {
-        ThreePaneScaffoldHorizontalOrder(
-            thirdPane,
-            secondPane,
-            firstPane
-        )
-    } else {
-        this
-    }
-}
-
-@ExperimentalMaterial3AdaptiveApi
-internal inline fun ThreePaneScaffoldHorizontalOrder.forEach(
-    action: (ThreePaneScaffoldRole) -> Unit
-) {
-    action(firstPane)
-    action(secondPane)
-    action(thirdPane)
-}
-
-@ExperimentalMaterial3AdaptiveApi
-internal inline fun ThreePaneScaffoldHorizontalOrder.forEachIndexed(
-    action: (Int, ThreePaneScaffoldRole) -> Unit
-) {
-    action(0, firstPane)
-    action(1, secondPane)
-    action(2, thirdPane)
-}
-
-@ExperimentalMaterial3AdaptiveApi
-internal fun ThreePaneScaffoldHorizontalOrder.indexOf(role: ThreePaneScaffoldRole): Int {
-    forEachIndexed { i, r ->
-        if (r == role) {
-            return i
-        }
-    }
-    // should never reach this far
-    return 0
-}
-
-/**
- * The set of the available pane roles of [ThreePaneScaffold].
- */
-@ExperimentalMaterial3AdaptiveApi
-enum class ThreePaneScaffoldRole {
-    /**
-     * The primary pane of [ThreePaneScaffold]. It is supposed to have the highest priority during
-     * layout adaptation and usually contains the most important content of the screen, like content
-     * details in a list-detail settings.
-     */
-    Primary,
-
-    /**
-     * The secondary pane of [ThreePaneScaffold]. It is supposed to have the second highest priority
-     * during layout adaptation and usually contains the supplement content of the screen, like
-     * content list in a list-detail settings.
-     */
-    Secondary,
-
-    /**
-     * The tertiary pane of [ThreePaneScaffold]. It is supposed to have the lowest priority during
-     * layout adaptation and usually contains the additional info which will only be shown under
-     * user interaction.
-     */
-    Tertiary
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt
deleted file mode 100644
index 4f28529..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.util.fastForEachReversed
-
-@ExperimentalMaterial3AdaptiveApi
-private inline fun buildThreePaneScaffoldValue(
-    buildAction: (ThreePaneScaffoldRole) -> PaneAdaptedValue
-): ThreePaneScaffoldValue {
-    return ThreePaneScaffoldValue(
-        buildAction(ThreePaneScaffoldRole.Primary),
-        buildAction(ThreePaneScaffoldRole.Secondary),
-        buildAction(ThreePaneScaffoldRole.Tertiary)
-    )
-}
-
-/**
- * Calculates the current adapted value of [ThreePaneScaffold] according to the given
- * [maxHorizontalPartitions], [adaptStrategies] and [currentDestination]. The returned value can be
- * used as a unique representation of the current layout structure.
- *
- * The function will treat the current destination as the highest priority and then adapt the rest
- * panes according to the order of [ThreePaneScaffoldRole.Primary],
- * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there
- * are still remaining partitions to put the pane, the pane will be set as
- * [PaneAdaptedValue.Expanded], otherwise it will be adapted according to its associated
- * [AdaptStrategy].
- *
- * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
- *        how many expanded panes can be shown at the same time.
- * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
- *        the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
- * @param currentDestination The current destination item, which will be treated as having
- *        the highest priority, can be `null`.
- */
-@ExperimentalMaterial3AdaptiveApi
-fun calculateThreePaneScaffoldValue(
-    maxHorizontalPartitions: Int,
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
-    currentDestination: ThreePaneScaffoldDestinationItem<*>?,
-): ThreePaneScaffoldValue {
-    var expandedCount = if (currentDestination != null) 1 else 0
-    return buildThreePaneScaffoldValue { role ->
-        when {
-            role == currentDestination?.pane -> PaneAdaptedValue.Expanded
-            expandedCount < maxHorizontalPartitions -> {
-                expandedCount++
-                PaneAdaptedValue.Expanded
-            }
-
-            else -> adaptStrategies[role].adapt()
-        }
-    }
-}
-
-/**
- * Calculates the current adapted value of [ThreePaneScaffold] according to the given
- * [maxHorizontalPartitions], [adaptStrategies] and [destinationHistory]. The returned value can be
- * used as a unique representation of the current layout structure.
- *
- * The function will treat the current focus as the highest priority and then adapt the rest
- * panes according to the order of [ThreePaneScaffoldRole.Primary],
- * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there are still
- * remaining partitions to put the pane, the pane will be set as [PaneAdaptedValue.Expanded],
- * otherwise it will be adapted according to its associated [AdaptStrategy].
- *
- * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
- *        how many expanded panes can be shown at the same time.
- * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
- *        the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
- * @param destinationHistory The history of past destination items. The last destination will have
- *        the highest priority, and the second last destination will have the second highest
- *        priority, and so forth until all panes have a priority assigned. Note that the last
- *        destination is supposed to be the last item of the provided list.
- */
-@ExperimentalMaterial3AdaptiveApi
-fun calculateThreePaneScaffoldValue(
-    maxHorizontalPartitions: Int,
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
-    destinationHistory: List<ThreePaneScaffoldDestinationItem<*>>,
-): ThreePaneScaffoldValue {
-    var expandedCount = 0
-    var primaryPaneAdaptedValue: PaneAdaptedValue? = null
-    var secondaryPaneAdaptedValue: PaneAdaptedValue? = null
-    var tertiaryPaneAdaptedValue: PaneAdaptedValue? = null
-    destinationHistory.fastForEachReversed {
-        if (expandedCount >= maxHorizontalPartitions) {
-            return@fastForEachReversed
-        }
-        when (it.pane) {
-            ThreePaneScaffoldRole.Primary -> {
-                if (primaryPaneAdaptedValue == null) {
-                    primaryPaneAdaptedValue = PaneAdaptedValue.Expanded
-                    expandedCount++
-                }
-            }
-            ThreePaneScaffoldRole.Secondary -> {
-                if (secondaryPaneAdaptedValue == null) {
-                    secondaryPaneAdaptedValue = PaneAdaptedValue.Expanded
-                    expandedCount++
-                }
-            }
-            ThreePaneScaffoldRole.Tertiary -> {
-                if (tertiaryPaneAdaptedValue == null) {
-                    tertiaryPaneAdaptedValue = PaneAdaptedValue.Expanded
-                    expandedCount++
-                }
-            }
-        }
-    }
-    return ThreePaneScaffoldValue(
-        primary = primaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
-            expandedCount++
-            PaneAdaptedValue.Expanded
-        } else {
-            adaptStrategies[ThreePaneScaffoldRole.Primary].adapt()
-        },
-        secondary = secondaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
-            expandedCount++
-            PaneAdaptedValue.Expanded
-        } else {
-            adaptStrategies[ThreePaneScaffoldRole.Secondary].adapt()
-        },
-        tertiary = tertiaryPaneAdaptedValue ?: if (expandedCount < maxHorizontalPartitions) {
-            expandedCount++
-            PaneAdaptedValue.Expanded
-        } else {
-            adaptStrategies[ThreePaneScaffoldRole.Tertiary].adapt()
-        }
-    )
-}
-
-/**
- * The adapted value of [ThreePaneScaffold]. It contains each pane's adapted value.
- * [ThreePaneScaffold] will use the adapted values to decide which panes should be displayed
- * and how they should be displayed. With other input parameters of [ThreePaneScaffold] fixed,
- * each possible instance of this class should represent a unique state of [ThreePaneScaffold]
- * and developers can compare two [ThreePaneScaffoldValue] to decide if there is a layout structure
- * change.
- *
- * For a Material-opinionated layout, it's suggested to use [calculateThreePaneScaffoldValue] to
- * calculate the current scaffold value.
- *
- * @constructor create an instance of [ThreePaneScaffoldValue]
- * @param primary [PaneAdaptedValue] of the primary pane of [ThreePaneScaffold]
- * @param secondary [PaneAdaptedValue] of the secondary pane of [ThreePaneScaffold]
- * @param tertiary [PaneAdaptedValue] of the tertiary pane of [ThreePaneScaffold]
- */
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-class ThreePaneScaffoldValue(
-    val primary: PaneAdaptedValue,
-    val secondary: PaneAdaptedValue,
-    val tertiary: PaneAdaptedValue
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ThreePaneScaffoldValue) return false
-        if (primary != other.primary) return false
-        if (secondary != other.secondary) return false
-        if (tertiary != other.tertiary) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = primary.hashCode()
-        result = 31 * result + secondary.hashCode()
-        result = 31 * result + tertiary.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "ThreePaneScaffoldValue(primary=$primary, " +
-            "secondary=$secondary, " +
-            "tertiary=$tertiary)"
-    }
-
-    operator fun get(role: ThreePaneScaffoldRole): PaneAdaptedValue =
-        when (role) {
-            ThreePaneScaffoldRole.Primary -> primary
-            ThreePaneScaffoldRole.Secondary -> secondary
-            ThreePaneScaffoldRole.Tertiary -> tertiary
-        }
-}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt
deleted file mode 100644
index de1f979..0000000
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2023 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.compose.material3.adaptive
-
-import androidx.compose.runtime.Immutable
-import androidx.window.core.layout.WindowSizeClass
-
-/**
- * This class collects window info that affects adaptation decisions. An adaptive layout is supposed
- * to use the info from this class to decide how the layout is supposed to be adapted.
- *
- * @constructor create an instance of [WindowAdaptiveInfo]
- * @param windowSizeClass [WindowSizeClass] of the current window.
- * @param windowPosture [Posture] of the current window.
- */
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-class WindowAdaptiveInfo(
-    val windowSizeClass: WindowSizeClass,
-    val windowPosture: Posture
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowAdaptiveInfo) return false
-        if (windowSizeClass != other.windowSizeClass) return false
-        if (windowPosture != other.windowPosture) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = windowSizeClass.hashCode()
-        result = 31 * result + windowPosture.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "WindowAdaptiveInfo(windowSizeClass=$windowSizeClass, windowPosture=$windowPosture)"
-    }
-}
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index 09fa42b..804a2d2 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -42,14 +42,12 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-
-                api(project(":compose:foundation:foundation"))
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui-graphics"))
-                api(project(":compose:ui:ui-text"))
-
                 implementation(project(":compose:ui:ui-util"))
+                api("androidx.compose.foundation:foundation:1.6.0")
+                api("androidx.compose.foundation:foundation-layout:1.6.0")
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.ui:ui-graphics:1.6.0")
+                api("androidx.compose.ui:ui-text:1.6.0")
             }
         }
         androidMain.dependencies {
@@ -77,7 +75,6 @@
         desktopMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation(libs.kotlinStdlib)
             }
         }
 
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index ed1e25f..78970ad 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -40,10 +40,10 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation(project(":compose:ui:ui-util"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-unit"))
+                implementation("androidx.compose.ui:ui-util:1.6.0")
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.ui:ui:1.6.0")
+                api("androidx.compose.ui:ui-unit:1.6.0")
             }
         }
 
@@ -63,11 +63,6 @@
         skikoMain {
             dependsOn(commonMain)
             dependencies {
-                // Because dependencies are pinned in the android/common code.
-                implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
-                api("androidx.compose.runtime:runtime:1.6.0-rc01")
-                api("androidx.compose.ui:ui:1.6.0-rc01")
-                api("androidx.compose.ui:ui-unit:1.6.0-rc01")
             }
         }
 
@@ -97,7 +92,7 @@
             dependsOn(jvmTest)
             dependencies {
                 implementation(project(":compose:test-utils"))
-                implementation("androidx.compose.foundation:foundation:1.6.0-rc01")
+                implementation("androidx.compose.foundation:foundation:1.6.0")
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.junit)
diff --git a/compose/material3/material3/api/current.ignore b/compose/material3/material3/api/current.ignore
index a92e97e..53ee8d06 100644
--- a/compose/material3/material3/api/current.ignore
+++ b/compose/material3/material3/api/current.ignore
@@ -1,3 +1,3 @@
 // Baseline format: 1.0
-RemovedClass: androidx.compose.material3.CalendarModelKt:
-    Removed class androidx.compose.material3.CalendarModelKt
+RemovedClass: androidx.compose.material3.CalendarModel_androidKt:
+    Removed class androidx.compose.material3.CalendarModel_androidKt
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index b5cb8d2..5505320 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -236,10 +236,6 @@
     method @androidx.compose.runtime.Composable public static void TextButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class CalendarModel_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static String formatWithSkeleton(long utcTimeMillis, String skeleton, java.util.Locale locale, java.util.Map<java.lang.String,java.lang.Object> cache);
-  }
-
   @androidx.compose.runtime.Immutable public final class CardColors {
     ctor public CardColors(long containerColor, long contentColor, long disabledContainerColor, long disabledContentColor);
     method public androidx.compose.material3.CardColors copy(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
@@ -1493,9 +1489,11 @@
 
   @androidx.compose.runtime.Stable public final class SliderDefaults {
     method @androidx.compose.runtime.Composable public void Thumb(androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional long thumbSize);
-    method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
     method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderPositions sliderPositions, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
     field public static final androidx.compose.material3.SliderDefaults INSTANCE;
diff --git a/compose/material3/material3/api/restricted_current.ignore b/compose/material3/material3/api/restricted_current.ignore
index a92e97e..53ee8d06 100644
--- a/compose/material3/material3/api/restricted_current.ignore
+++ b/compose/material3/material3/api/restricted_current.ignore
@@ -1,3 +1,3 @@
 // Baseline format: 1.0
-RemovedClass: androidx.compose.material3.CalendarModelKt:
-    Removed class androidx.compose.material3.CalendarModelKt
+RemovedClass: androidx.compose.material3.CalendarModel_androidKt:
+    Removed class androidx.compose.material3.CalendarModel_androidKt
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index b5cb8d2..5505320 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -236,10 +236,6 @@
     method @androidx.compose.runtime.Composable public static void TextButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class CalendarModel_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static String formatWithSkeleton(long utcTimeMillis, String skeleton, java.util.Locale locale, java.util.Map<java.lang.String,java.lang.Object> cache);
-  }
-
   @androidx.compose.runtime.Immutable public final class CardColors {
     ctor public CardColors(long containerColor, long contentColor, long disabledContainerColor, long disabledContentColor);
     method public androidx.compose.material3.CardColors copy(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
@@ -1493,9 +1489,11 @@
 
   @androidx.compose.runtime.Stable public final class SliderDefaults {
     method @androidx.compose.runtime.Composable public void Thumb(androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional long thumbSize);
-    method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
     method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderPositions sliderPositions, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
     field public static final androidx.compose.material3.SliderDefaults INSTANCE;
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index ac010cb..aa50925 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 androidXMultiplatform {
@@ -41,17 +40,17 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation(project(":collection:collection"))
-                implementation(project(":compose:animation:animation-core"))
-
+                // Keep pinned unless there is a need for tip of tree behavior
+                implementation("androidx.collection:collection:1.4.0")
+                implementation("androidx.compose.animation:animation-core:1.6.0")
+                implementation("androidx.compose.ui:ui-util:1.6.0")
                 api(project(":compose:foundation:foundation"))
-                api(project(":compose:foundation:foundation-layout"))
-                api(project(":compose:material:material-icons-core"))
+                api("androidx.compose.foundation:foundation-layout:1.6.0")
+                api("androidx.compose.material:material-icons-core:1.6.0")
                 api(project(":compose:material:material-ripple"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-text"))
-                implementation(project(":compose:ui:ui-util"))
+                api("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.ui:ui:1.6.0")
+                api("androidx.compose.ui:ui-text:1.6.0")
             }
         }
 
@@ -69,14 +68,6 @@
         skikoMain {
             dependsOn(commonMain)
             dependencies {
-                api(project(":compose:animation:animation-core"))
-                api(project(":compose:runtime:runtime"))
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-text"))
-                api(project(":compose:foundation:foundation-layout"))
-
-                implementation(project(":compose:animation:animation"))
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
diff --git a/compose/material3/material3/integration-tests/material3-catalog/build.gradle b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
index c7aeee6..699cb55 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/build.gradle
+++ b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
@@ -39,9 +39,9 @@
     implementation project(":compose:ui:ui")
     implementation project(":compose:material:material")
     implementation project(":compose:material:material-icons-extended")
+    implementation project(":compose:material3:adaptive:adaptive-samples")
     implementation project(":compose:material3:material3")
     implementation project(":compose:material3:material3:material3-samples")
-    implementation project(":compose:material3:material3-adaptive:material3-adaptive-samples")
     implementation project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples")
     implementation project(":datastore:datastore-preferences")
     implementation project(":navigation:navigation-compose")
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index af68bda..53da836 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -90,6 +90,8 @@
 import androidx.compose.material3.samples.LegacyIndeterminateCircularProgressIndicatorSample
 import androidx.compose.material3.samples.LegacyIndeterminateLinearProgressIndicatorSample
 import androidx.compose.material3.samples.LegacyLinearProgressIndicatorSample
+import androidx.compose.material3.samples.LegacyRangeSliderSample
+import androidx.compose.material3.samples.LegacySliderSample
 import androidx.compose.material3.samples.LinearProgressIndicatorSample
 import androidx.compose.material3.samples.MenuSample
 import androidx.compose.material3.samples.MenuWithScrollStateSample
@@ -935,6 +937,13 @@
 private const val SlidersExampleSourceUrl = "$SampleSourceUrl/SliderSamples.kt"
 val SlidersExamples = listOf(
     Example(
+        name = ::LegacySliderSample.name,
+        description = SlidersExampleDescription,
+        sourceUrl = SlidersExampleSourceUrl
+    ) {
+        LegacySliderSample()
+    },
+    Example(
         name = ::SliderSample.name,
         description = SlidersExampleDescription,
         sourceUrl = SlidersExampleSourceUrl
@@ -963,6 +972,13 @@
         SliderWithCustomTrackAndThumb()
     },
     Example(
+        name = ::LegacyRangeSliderSample.name,
+        description = SlidersExampleDescription,
+        sourceUrl = SlidersExampleSourceUrl
+    ) {
+        LegacyRangeSliderSample()
+    },
+    Example(
         name = ::RangeSliderSample.name,
         description = SlidersExampleDescription,
         sourceUrl = SlidersExampleSourceUrl
diff --git a/compose/material3/material3/lint-baseline.xml b/compose/material3/material3/lint-baseline.xml
index dc2af50..4395fd6 100644
--- a/compose/material3/material3/lint-baseline.xml
+++ b/compose/material3/material3/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanThreadSleep"
@@ -40,24 +40,6 @@
     <issue
         id="BanThreadSleep"
         message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialRippleThemeTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(300)"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialRippleThemeTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
         errorLine1="            Thread.sleep(300)"
         errorLine2="                   ~~~~~">
         <location
@@ -284,24 +266,6 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="variable xCandidates with type List&lt;? extends Integer>: replace with IntList"
-        errorLine1="        val xCandidates = listOf("
-        errorLine2="        ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/MenuPosition.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable yCandidates with type List&lt;? extends Integer>: replace with IntList"
-        errorLine1="        val yCandidates = listOf("
-        errorLine2="        ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/MenuPosition.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
         message="variable tabContentWidths with type List&lt;Dp>: replace with FloatList"
         errorLine1="            val tabContentWidths = mutableListOf&lt;Dp>()"
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ColorSchemeSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ColorSchemeSamples.kt
new file mode 100644
index 0000000..74d5a88
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ColorSchemeSamples.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+@Composable
+@Sampled
+fun ColorSchemeFixedAccentColorSample() {
+    data class FixedAccentColors(
+        val primaryFixed: Color,
+        val onPrimaryFixed: Color,
+        val secondaryFixed: Color,
+        val onSecondaryFixed: Color,
+        val tertiaryFixed: Color,
+        val onTertiaryFixed: Color,
+        val primaryFixedDim: Color,
+        val secondaryFixedDim: Color,
+        val tertiaryFixedDim: Color,
+    )
+    val material3LightColors = lightColorScheme()
+    val material3DarkColors = darkColorScheme()
+    fun getFixedAccentColors() = FixedAccentColors(
+        primaryFixed = material3LightColors.primaryContainer,
+        onPrimaryFixed = material3LightColors.onPrimaryContainer,
+        secondaryFixed = material3LightColors.secondaryContainer,
+        onSecondaryFixed = material3LightColors.onSecondaryContainer,
+        tertiaryFixed = material3LightColors.tertiaryContainer,
+        onTertiaryFixed = material3LightColors.onTertiaryContainer,
+        primaryFixedDim = material3DarkColors.primary,
+        secondaryFixedDim = material3DarkColors.secondary,
+        tertiaryFixedDim = material3DarkColors.tertiary
+    )
+    val LocalFixedAccentColors = compositionLocalOf { getFixedAccentColors() }
+
+    @Composable
+    fun MyMaterialTheme(
+        fixedAccentColors: FixedAccentColors = LocalFixedAccentColors.current,
+        content: @Composable () -> Unit
+    ) {
+        MaterialTheme(
+            colorScheme = if (isSystemInDarkTheme()) material3DarkColors else material3LightColors
+        ) {
+            CompositionLocalProvider(LocalFixedAccentColors provides fixedAccentColors) {
+                // Content has access to fixedAccentColors in both light and dark theme.
+                content()
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index 5d8622f..34aa79f 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -17,12 +17,16 @@
 package androidx.compose.material3.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.requiredSizeIn
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.ButtonDefaults
@@ -36,16 +40,19 @@
 import androidx.compose.material3.SliderDefaults
 import androidx.compose.material3.SliderState
 import androidx.compose.material3.Text
+import androidx.compose.material3.ripple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import kotlin.math.roundToInt
 
@@ -63,6 +70,57 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+fun LegacySliderSample() {
+    var sliderPosition by remember { mutableStateOf(0f) }
+    val interactionSource = remember { MutableInteractionSource() }
+    val trackHeight = 4.dp
+    val thumbSize = DpSize(20.dp, 20.dp)
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        Text(text = "%.2f".format(sliderPosition))
+        Slider(
+            interactionSource = interactionSource,
+            modifier = Modifier
+                .semantics { contentDescription = "Localized Description" }
+                .requiredSizeIn(
+                    minWidth = thumbSize.width,
+                    minHeight = trackHeight
+                ),
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            thumb = {
+                val modifier = Modifier
+                    .size(thumbSize)
+                    .shadow(1.dp, CircleShape, clip = false)
+                    .indication(
+                        interactionSource = interactionSource,
+                        indication = ripple(
+                            bounded = false,
+                            radius = 20.dp
+                        )
+                    )
+                SliderDefaults.Thumb(
+                    interactionSource = interactionSource,
+                    modifier = modifier
+                )
+            },
+            track = {
+                val modifier = Modifier
+                    .height(trackHeight)
+                SliderDefaults.Track(
+                    sliderState = it,
+                    modifier = modifier,
+                    thumbTrackGapSize = 0.dp,
+                    trackInsideCornerSize = 0.dp,
+                    drawStopIndicator = null
+                )
+            }
+        )
+    }
+}
+
 @Preview
 @Sampled
 @Composable
@@ -194,6 +252,86 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Preview
+@Composable
+fun LegacyRangeSliderSample() {
+    val rangeSliderState = remember {
+        RangeSliderState(
+            0f,
+            100f,
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            }
+        )
+    }
+    val startInteractionSource = remember { MutableInteractionSource() }
+    val endInteractionSource = remember { MutableInteractionSource() }
+    val trackHeight = 4.dp
+    val thumbSize = DpSize(20.dp, 20.dp)
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        val rangeStart = "%.2f".format(rangeSliderState.activeRangeStart)
+        val rangeEnd = "%.2f".format(rangeSliderState.activeRangeEnd)
+        Text(text = "$rangeStart .. $rangeEnd")
+        RangeSlider(
+            state = rangeSliderState,
+            startInteractionSource = startInteractionSource,
+            endInteractionSource = endInteractionSource,
+            modifier = Modifier
+                .semantics { contentDescription = "Localized Description" }
+                .requiredSizeIn(
+                    minWidth = thumbSize.width,
+                    minHeight = trackHeight
+                ),
+            startThumb = {
+                val modifier = Modifier
+                    .size(thumbSize)
+                    .shadow(1.dp, CircleShape, clip = false)
+                    .indication(
+                        interactionSource = startInteractionSource,
+                        indication = ripple(
+                            bounded = false,
+                            radius = 20.dp
+                        )
+                    )
+                SliderDefaults.Thumb(
+                    interactionSource = startInteractionSource,
+                    modifier = modifier
+                )
+            },
+            endThumb = {
+                val modifier = Modifier
+                    .size(thumbSize)
+                    .shadow(1.dp, CircleShape, clip = false)
+                    .indication(
+                        interactionSource = endInteractionSource,
+                        indication = ripple(
+                            bounded = false,
+                            radius = 20.dp
+                        )
+                    )
+                SliderDefaults.Thumb(
+                    interactionSource = endInteractionSource,
+                    modifier = modifier
+                )
+            },
+            track = {
+                val modifier = Modifier
+                    .height(trackHeight)
+                SliderDefaults.Track(
+                    rangeSliderState = it,
+                    modifier = modifier,
+                    thumbTrackGapSize = 0.dp,
+                    trackInsideCornerSize = 0.dp,
+                    drawStopIndicator = null
+                )
+            }
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
 @Sampled
 @Composable
 fun StepRangeSliderSample() {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 883e39b..58e8956 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -48,7 +48,9 @@
     @get:Rule
     val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
 
-    val wrap = Modifier.requiredWidth(70.dp).wrapContentSize(Alignment.TopStart)
+    val wrap = Modifier
+        .requiredWidth(70.dp)
+        .wrapContentSize(Alignment.TopStart)
 
     private val wrapperTestTag = "sliderWrapper"
 
@@ -94,6 +96,63 @@
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
+    fun sliderTest_middle_no_gap() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Slider(
+                    state = remember { SliderState(0.5f) },
+                    track = {
+                        SliderDefaults.Track(
+                            sliderState = it,
+                            thumbTrackGapSize = 0.dp
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("slider_middle_no_gap")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun sliderTest_middle_no_inside_corner() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Slider(
+                    state = remember { SliderState(0.5f) },
+                    track = {
+                        SliderDefaults.Track(
+                            sliderState = it,
+                            trackInsideCornerSize = 0.dp
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("slider_middle_no_inside_corner")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun sliderTest_middle_no_stop_indicator() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Slider(
+                    state = remember { SliderState(0.5f) },
+                    track = {
+                        SliderDefaults.Track(
+                            sliderState = it,
+                            drawStopIndicator = null
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("slider_middle_no_stop_indicator")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
     fun sliderTest_middle_dark() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -218,6 +277,78 @@
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
+    fun rangeSliderTest_middle_no_gap() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                RangeSlider(
+                    state = remember {
+                        RangeSliderState(
+                            0.5f,
+                            1f
+                        )
+                    },
+                    track = {
+                        SliderDefaults.Track(
+                            rangeSliderState = it,
+                            thumbTrackGapSize = 0.dp
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("rangeSlider_middle_no_gap")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun rangeSliderTest_middle_no_inside_corner() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                RangeSlider(
+                    state = remember {
+                        RangeSliderState(
+                            0.5f,
+                            1f
+                        )
+                    },
+                    track = {
+                        SliderDefaults.Track(
+                            rangeSliderState = it,
+                            trackInsideCornerSize = 0.dp
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("rangeSlider_middle_no_inside_corner")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun rangeSliderTest_middle_no_stop_indicator() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                RangeSlider(
+                    state = remember {
+                        RangeSliderState(
+                            0.5f,
+                            1f
+                        )
+                    },
+                    track = {
+                        SliderDefaults.Track(
+                            rangeSliderState = it,
+                            drawStopIndicator = null
+                        )
+                    }
+                )
+            }
+        }
+        assertSliderAgainstGolden("rangeSlider_middle_no_stop_indicator")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
     fun rangeSliderTest_middle_steps_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
index 5996d67..e118d99 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -508,7 +508,7 @@
 
         rule.onNodeWithTag(tag)
             .assertWidthIsEqualTo(SliderTokens.HandleWidth)
-            .assertHeightIsEqualTo(SliderTokens.HandleHeight)
+            .assertHeightIsEqualTo(SliderTokens.InactiveTrackHeight)
     }
 
     @OptIn(ExperimentalMaterial3Api::class)
@@ -1325,7 +1325,7 @@
 
         rule.runOnIdle {
             Truth.assertThat(recompositionCounter.outerRecomposition).isEqualTo(1)
-            Truth.assertThat(recompositionCounter.innerRecomposition).isEqualTo(4)
+            Truth.assertThat(recompositionCounter.innerRecomposition).isEqualTo(3)
         }
     }
 
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
index 58c41be..83c03b3 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
@@ -16,12 +16,13 @@
 
 package androidx.compose.material3.carousel
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.lightColorScheme
@@ -30,11 +31,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -44,7 +47,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 class CarouselTest {
 
     private lateinit var carouselState: CarouselState
@@ -101,10 +104,10 @@
     internal fun Item(index: Int) {
         Box(
             modifier = Modifier
-            .fillMaxSize()
-            .background(Color.Blue)
-            .testTag("$index")
-            .focusable(),
+                .fillMaxSize()
+                .background(Color.Blue)
+                .testTag("$index")
+                .focusable(),
             contentAlignment = Alignment.Center
         ) {
             BasicText(text = index.toString())
@@ -114,30 +117,30 @@
     private fun createCarousel(
         initialItem: Int = 0,
         itemCount: () -> Int = { DefaultItemCount },
-        modifier: Modifier = Modifier,
+        modifier: Modifier = Modifier.width(412.dp).height(221.dp),
         orientation: Orientation = Orientation.Horizontal,
         content: @Composable CarouselScope.(item: Int) -> Unit = { Item(index = it) }
     ) {
         rule.setMaterialContent(lightColorScheme()) {
-            val state = rememberCarouselState(
-                initialItem = initialItem,
-                itemCount = itemCount,
-            ).also {
+            val state = rememberCarouselState(initialItem, itemCount).also {
                 carouselState = it
             }
-            if (orientation == Orientation.Horizontal) {
-                HorizontalCarousel(
-                    state = state,
-                    modifier = modifier.testTag(CarouselTestTag),
-                    content = content,
-                )
-            } else {
-                VerticalCarousel(
-                    state = state,
-                    modifier = modifier.testTag(CarouselTestTag),
-                    content = content,
-                )
-            }
+            val density = LocalDensity.current
+            Carousel(
+                state = state,
+                orientation = orientation,
+                keylineList = { availableSpace ->
+                    multiBrowseKeylineList(
+                        density = density,
+                        carouselMainAxisSize = availableSpace,
+                        preferredItemSize = with(density) { 186.dp.toPx() },
+                        itemSpacing = 0f,
+                    )
+                },
+                modifier = modifier.testTag(CarouselTestTag),
+                itemSpacing = 0.dp,
+                content = content,
+            )
         }
     }
 }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
index d5ed1b0..4cee7a2 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
@@ -22,7 +22,6 @@
 /**
  * Returns a [CalendarModel] to be used by the date picker.
  */
-@ExperimentalMaterial3Api
 internal actual fun createCalendarModel(locale: CalendarLocale): CalendarModel {
     return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
         CalendarModelImpl(locale)
@@ -45,8 +44,7 @@
  * @param locale the [CalendarLocale] to use when formatting the given timestamp
  * @param cache a [MutableMap] for caching formatter related results for better performance
  */
-@ExperimentalMaterial3Api
-actual fun formatWithSkeleton(
+internal actual fun formatWithSkeleton(
     utcTimeMillis: Long,
     skeleton: String,
     locale: CalendarLocale,
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt
deleted file mode 100644
index e386d4e..0000000
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2022 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.compose.material3
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ButtonPaparazziTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun default_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun default_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(onClick = { }, enabled = false) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(onClick = { }, enabled = false) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun elevated_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun elevated_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_elevated_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}, enabled = false) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_elevated_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}, enabled = false) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun button_with_icon_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun button_with_icon_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_with_icon_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
-                        enabled = false,
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_with_icon_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
-                        enabled = false,
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
index c5edd29..2fdb017 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
@@ -132,6 +132,44 @@
         assertThat(lerp(from, to, .5f)).isEqualTo(half)
     }
 
+    @Test
+    fun test_keylineListsShouldBeEqual() {
+        val keylineList1 = keylineListOf(120f, CarouselAlignment.Start) {
+            add(10f, true)
+            add(100f)
+            add(20f)
+            add(10f, true)
+        }
+        val keylineList2 = keylineListOf(120f, CarouselAlignment.Start) {
+            add(10f, true)
+            add(100f)
+            add(20f)
+            add(10f, true)
+        }
+
+        assertThat(keylineList1 == keylineList2).isTrue()
+        assertThat(keylineList1.hashCode()).isEqualTo(keylineList2.hashCode())
+    }
+
+    @Test
+    fun testDifferentSizedItem_keylineListsShouldNotBeEqual() {
+        val keylineList1 = keylineListOf(120f, CarouselAlignment.Start) {
+            add(11f, true)
+            add(100f)
+            add(20f)
+            add(10f, true)
+        }
+        val keylineList2 = keylineListOf(120f, CarouselAlignment.Start) {
+            add(10f, true)
+            add(100f)
+            add(20f)
+            add(10f, true)
+        }
+
+        assertThat(keylineList1 == keylineList2).isFalse()
+        assertThat(keylineList1.hashCode()).isNotEqualTo(keylineList2.hashCode())
+    }
+
     companion object {
         private const val LargeSize = 100f
         private const val SmallSize = 20f
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyTest.kt
deleted file mode 100644
index 8e0f286..0000000
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyTest.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.material3.carousel
-
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class MultiBrowseStrategyTest {
-
-    private val Density = Density(1f)
-
-    @Test
-    fun testStrategy_doesntResizeLargeWhenEnoughRoom() {
-
-        val itemSize = 120.dp // minSmallItemSize = 40.dp * 3
-        val strategyProvider = MultiBrowseStrategyProvider(itemSize)
-        val strategy = strategyProvider.createStrategy(
-            density = Density,
-            carouselMainAxisSize = 500f,
-            itemSpacing = 0)
-
-        assertThat(strategy?.itemMainAxisSize).isEqualTo(with(Density) { itemSize.toPx() })
-    }
-
-    @Test
-    fun testStrategy_resizesItemLargerThanContainerToFit1Small() {
-        val itemSize = 200f
-        val strategyProvider = MultiBrowseStrategyProvider(with(Density) { itemSize.toDp() })
-        val strategy = strategyProvider.createStrategy(
-            density = Density,
-            carouselMainAxisSize = 100f,
-            itemSpacing = 0)
-
-        val minSmallItemSize: Float = with(Density) { StrategyDefaults.minSmallSize.toPx() }
-        val keylines = strategy?.getDefaultKeylines()
-
-        // If the item size given is larger than the container, the adjusted keyline list from
-        // the MultibrowseStrategy should be [xSmall-Large-Small-xSmall]
-        assertThat(strategy?.itemMainAxisSize).isAtMost(100f)
-        assertThat(keylines).hasSize(4)
-        assertThat(keylines?.get(0)?.unadjustedOffset).isLessThan(0f)
-        assertThat(keylines?.get(keylines.lastIndex)?.unadjustedOffset).isGreaterThan(100f)
-        assertThat(keylines?.get(1)?.isFocal).isTrue()
-        assertThat(keylines?.get(2)?.size).isEqualTo(minSmallItemSize)
-    }
-
-    @Test
-    fun testStrategy_hasNoSmallItemsIfNotEnoughRoom() {
-        val minSmallItemSize: Float = with(Density) { StrategyDefaults.minSmallSize.toPx() }
-        val strategyProvider = MultiBrowseStrategyProvider(with(Density) { 200f.toDp() })
-        val strategy = strategyProvider.createStrategy(
-            density = Density,
-            carouselMainAxisSize = minSmallItemSize,
-            itemSpacing = 0)
-        val keylines = strategy?.getDefaultKeylines()
-
-        assertThat(strategy?.itemMainAxisSize).isEqualTo(minSmallItemSize)
-        assertThat(keylines?.firstFocal == keylines?.firstNonAnchor)
-        assertThat(keylines?.lastFocal == keylines?.lastNonAnchor)
-    }
-
-    @Test
-    fun testStrategy_isNullIfAvailableSpaceIsZero() {
-        val strategyProvider = MultiBrowseStrategyProvider(with(Density) { 200f.toDp() })
-        val strategy = strategyProvider.createStrategy(
-            density = Density,
-            carouselMainAxisSize = 0F,
-            itemSpacing = 0)
-
-        assertThat(strategy).isNull()
-    }
-
-    @Test
-    fun testStrategy_adjustsMediumSizeToBeProportional() {
-        val maxSmallItemSize: Float = with(Density) { StrategyDefaults.maxSmallSize.toPx() }
-        val targetItemSize = 200f
-        val carouselSize = targetItemSize * 2 + maxSmallItemSize * 2
-        val strategyProvider = MultiBrowseStrategyProvider(with(Density) { targetItemSize.toDp() })
-        val strategy = strategyProvider.createStrategy(
-            density = Density,
-            carouselMainAxisSize = carouselSize,
-            itemSpacing = 0)
-        val keylines = strategy?.getDefaultKeylines()
-
-        // Assert that there's only one small item, and a medium item that has a size between
-        // the large and small items
-        // We expect a keyline list of [xSmall-Large-Large-Medium-Small-xSmall]
-        assertThat(keylines).hasSize(6)
-        assertThat(keylines?.get(1)?.isFocal).isTrue()
-        assertThat(keylines?.get(2)?.isFocal).isTrue()
-        assertThat(keylines?.get(3)?.size).isLessThan(keylines?.get(2)?.size)
-        assertThat(keylines?.get(4)?.size).isLessThan(keylines?.get(3)?.size)
-    }
-}
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
new file mode 100644
index 0000000..5997b7d
--- /dev/null
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.carousel
+
+import androidx.compose.ui.unit.Density
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MultiBrowseTest {
+
+    private val Density = Density(1f)
+
+    @Test
+    fun testMultiBrowse_doesNotResizeLargeWhenEnoughRoom() {
+        val itemSize = 120f // minSmallItemSize = 40.dp * 3
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = 500f,
+            preferredItemSize = itemSize,
+            itemSpacing = 0f
+        )!!
+        val strategy = Strategy { keylineList }.apply(500f)
+
+        assertThat(strategy.itemMainAxisSize).isEqualTo(itemSize)
+    }
+
+    @Test
+    fun testMultiBrowse_resizesItemLargerThanContainerToFit1Small() {
+        val itemSize = 200f
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = 100f,
+            preferredItemSize = itemSize,
+            itemSpacing = 0f
+        )!!
+        val strategy = Strategy { keylineList }.apply(100f)
+        val minSmallItemSize: Float = with(Density) { StrategyDefaults.MinSmallSize.toPx() }
+        val keylines = strategy.getDefaultKeylines()
+
+        // If the item size given is larger than the container, the adjusted keyline list from
+        // the multi-browse keyline list should be [xSmall-Large-Small-xSmall]
+        assertThat(strategy.itemMainAxisSize).isAtMost(100f)
+        assertThat(keylines).hasSize(4)
+        assertThat(keylines[0].unadjustedOffset).isLessThan(0f)
+        assertThat(keylines[keylines.lastIndex].unadjustedOffset).isGreaterThan(100f)
+        assertThat(keylines[1].isFocal).isTrue()
+        assertThat(keylines[2].size).isEqualTo(minSmallItemSize)
+    }
+
+    @Test
+    fun testMultiBrowse_hasNoSmallItemsIfNotEnoughRoom() {
+        val minSmallItemSize: Float = with(Density) { StrategyDefaults.MinSmallSize.toPx() }
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = minSmallItemSize,
+            preferredItemSize = 200f,
+            itemSpacing = 0f
+        )!!
+        val strategy = Strategy { keylineList }.apply(minSmallItemSize)
+        val keylines = strategy.getDefaultKeylines()
+
+        assertThat(strategy.itemMainAxisSize).isEqualTo(minSmallItemSize)
+        assertThat(keylines.firstFocal == keylines.firstNonAnchor)
+        assertThat(keylines.lastFocal == keylines.lastNonAnchor)
+    }
+
+    @Test
+    fun testMultiBrowse_isNullIfAvailableSpaceIsZero() {
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = 0f,
+            preferredItemSize = 200f,
+            itemSpacing = 0f
+        )
+        assertThat(keylineList).isNull()
+    }
+
+    @Test
+    fun testMultiBrowse_adjustsMediumSizeToBeProportional() {
+        val maxSmallItemSize: Float = with(Density) { StrategyDefaults.MaxSmallSize.toPx() }
+        val preferredItemSize = 200f
+        val carouselSize = preferredItemSize * 2 + maxSmallItemSize * 2
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            preferredItemSize = preferredItemSize,
+            itemSpacing = 0f
+        )!!
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.getDefaultKeylines()
+
+        // Assert that there's only one small item, and a medium item that has a size between
+        // the large and small items
+        // We expect a keyline list of [xSmall-Large-Large-Medium-Small-xSmall]
+        assertThat(keylines).hasSize(6)
+        assertThat(keylines[1].isFocal).isTrue()
+        assertThat(keylines[2].isFocal).isTrue()
+        assertThat(keylines[3].size).isLessThan(keylines[2].size)
+        assertThat(keylines[4].size).isLessThan(keylines[3].size)
+    }
+}
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
index e83bd16..f598ea6 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3.carousel
 
+import androidx.compose.ui.unit.Density
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -24,6 +25,8 @@
 @RunWith(JUnit4::class)
 class StrategyTest {
 
+    val Density = Density(1f, 1f)
+
     @Test
     fun testStrategy_startAlignedStrategyShiftsEnd() {
         val itemCount = 10
@@ -31,10 +34,7 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylineList = createStartAlignedKeylineList()
 
-        val strategy = Strategy.create(
-            carouselMainAxisSize = large + medium + small,
-            keylineList = defaultKeylineList
-        )
+        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
 
         assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
             .isEqualTo(defaultKeylineList)
@@ -62,10 +62,7 @@
         val cutoff = 50f
         val defaultKeylineList = createStartAlignedCutoffKeylineList(cutoff = cutoff)
 
-        val strategy = Strategy.create(
-            carouselMainAxisSize = carouselMainAxisSize,
-            keylineList = defaultKeylineList
-        )
+        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
         val endKeylineList = strategy.getEndKeylines()
 
         assertThat(defaultKeylineList.lastNonAnchor.cutoff).isEqualTo(cutoff)
@@ -84,10 +81,7 @@
         val cutoff = 50f
         val defaultKeylineList = createEndAlignedCutoffKeylineList(cutoff = cutoff)
 
-        val strategy = Strategy.create(
-            carouselMainAxisSize = carouselMainAxisSize,
-            keylineList = defaultKeylineList
-        )
+        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
         val startKeylineList = strategy.getStartKeylines()
 
         assertThat(defaultKeylineList.firstNonAnchor.cutoff).isEqualTo(cutoff)
@@ -107,10 +101,7 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylines = createCenterAlignedKeylineList()
 
-        val strategy = Strategy.create(
-            carouselMainAxisSize = carouselMainAxisSize,
-            keylineList = defaultKeylines
-        )
+        val strategy = Strategy { defaultKeylines }.apply(carouselMainAxisSize)
 
         val startSteps = listOf(
             // default step - [xs | s s m l l m s s | xs]
@@ -199,10 +190,7 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylines = createCenterAlignedKeylineList()
 
-        val strategy = Strategy.create(
-            carouselMainAxisSize = carouselMainAxisSize,
-            keylineList = defaultKeylines
-        )
+        val strategy = Strategy { defaultKeylines }.apply(carouselMainAxisSize)
 
         val endSteps = listOf(
             // default step
@@ -310,6 +298,56 @@
             .isEqualTo(endSteps[2])
     }
 
+    @Test
+    fun testStrategy_sameAvailableSpaceCreatesEqualObjects() {
+        val itemSize = large
+        val itemSpacing = 0f
+        val strategy1 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        val strategy2 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        strategy1.apply(500f)
+        strategy2.apply(500f)
+
+        assertThat(strategy1 == strategy2).isTrue()
+        assertThat(strategy1.hashCode()).isEqualTo(strategy2.hashCode())
+    }
+
+    @Test
+    fun testStrategy_differentAvailableSpaceCreatesUnequalObjects() {
+        val itemSize = large
+        val itemSpacing = 0f
+        val strategy1 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        val strategy2 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        strategy1.apply(500f)
+        strategy2.apply(500f + 1f)
+
+        assertThat(strategy1 == strategy2).isFalse()
+        assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
+    }
+
+    @Test
+    fun testStrategy_invalidObjectDoesNotEqualValidObject() {
+        val itemSize = large
+        val itemSpacing = 0f
+        val strategy1 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        val strategy2 = Strategy { availableSpace ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+        }
+        strategy1.apply(500f)
+
+        assertThat(strategy1 == strategy2).isFalse()
+        assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
+    }
+
     private fun assertEqualWithFloatTolerance(
         tolerance: Float,
         actual: Keyline,
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
new file mode 100644
index 0000000..b29af44
--- /dev/null
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.carousel
+
+import androidx.compose.ui.unit.Density
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class UncontainedTest {
+
+    private val Density = Density(1f)
+
+    @Test
+    fun testLargeItem_withFullCarouselWidth() {
+        val itemSize = 500f
+        val carouselSize = 500f
+        val keylineList = uncontainedKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            itemSize = itemSize,
+            itemSpacing = 0f
+        )
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.getDefaultKeylines()
+        val anchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+
+        // A fullscreen layout should be [xSmall-large-xSmall] where the xSmall items are
+        // outside the bounds of the carousel container and the large item takes up the
+        // containers full width.
+        assertThat(keylines.size).isEqualTo(3)
+        assertThat(keylines[0].offset).isEqualTo(-anchorSize / 2f)
+        assertThat(keylines[1].size).isEqualTo(carouselSize)
+        assertThat(keylines[2].offset).isEqualTo(carouselSize + anchorSize / 2f)
+    }
+
+    @Test
+    fun testLargeItem_largerThanFullCarouselWidth() {
+        val carouselSize = 400f
+        val itemSize = 500f
+        val keylineList = uncontainedKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            itemSize = itemSize,
+            itemSpacing = 0f
+        )
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.getDefaultKeylines()
+        val anchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+
+        // The layout should be [xSmall-large-xSmall] where the xSmall items are
+        // outside the bounds of the carousel container and the large item takes up the
+        // containers full width.
+        assertThat(keylines.size).isEqualTo(3)
+        assertThat(keylines[0].offset).isEqualTo(-anchorSize / 2f)
+        assertThat(keylines[1].size).isEqualTo(carouselSize)
+        assertThat(keylines[2].offset).isEqualTo(carouselSize + anchorSize / 2f)
+    }
+
+    @Test
+    fun testRemainingSpaceWithItemSize_fitsItemWithThirdCutoff() {
+        val carouselSize = 400f
+        // With size 125px, 3 large items can fit with in 400px, with 25px left. 25px * 3 = 75px,
+        // which will be the size of the medium item since it can be a third cut off and it is less
+        // than the threshold percentage * large item size.
+        val itemSize = 125f
+        val keylineList = uncontainedKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            itemSize = itemSize,
+            itemSpacing = 0f
+        )
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.getDefaultKeylines()
+
+        // The layout should be [xSmall-large-large-large-medium-xSmall] where medium is a size
+        // such that a third of it is cut off.
+        assertThat(keylines.size).isEqualTo(6)
+        assertThat(keylines[1].size).isEqualTo(itemSize)
+        assertThat(keylines[2].size).isEqualTo(itemSize)
+        assertThat(keylines[3].size).isEqualTo(itemSize)
+        // The cutoff size should be a size that has a third of itself cut off with the given
+        // remaining space, which is 25f as explained above.
+        assertThat(keylines[4].size).isEqualTo(25f * 1.5f)
+        assertThat(keylines[4].offset).isEqualTo(393.75f) // itemSize * 3 + (25 * 1.5f / 2)
+        assertThat(keylines[0].size).isEqualTo(18.75f) // half the med size is the anchor size
+        assertThat(keylines[0].offset).isEqualTo(-9.375f) // -18.75f/2
+        assertThat(keylines[5].offset)
+            .isEqualTo(421.875f) // itemSize*3 + 25f * 1.5f + 18.75f/2
+    }
+
+    @Test
+    fun testRemainingSpaceWithItemSize_fitsMediumItemWithCutoff() {
+        val carouselSize = 400f
+        // With size 105px, 3 large items can fit with in 400px, with 85px left over.  85*3 = 255
+        // which is well over the size of the large item, so the medium size will be limited to
+        // whichever is larger between 85% of the large size, or 110% of the remainingSpace to make
+        // it at most 10% cut off.
+        val itemSize = 105f
+        val keylineList = uncontainedKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            itemSize = itemSize,
+            itemSpacing = 0f
+        )
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.getDefaultKeylines()
+
+        // The layout should be [xSmall-large-large-large-medium-xSmall]
+        assertThat(keylines.size).isEqualTo(6)
+        assertThat(keylines[1].size).isEqualTo(itemSize)
+        assertThat(keylines[2].size).isEqualTo(itemSize)
+        assertThat(keylines[3].size).isEqualTo(itemSize)
+        // remainingSpace * 120%
+        assertThat(keylines[4].size).isEqualTo(85 * 1.2f)
+        assertThat(keylines[0].size).isEqualTo(85 * 1.2f * 0.5f)
+        assertThat(keylines[5].size).isEqualTo(85 * 1.2f * 0.5f)
+        assertThat(keylines[0].offset).isEqualTo(-(85 * 1.2f * 0.5f) / 2f)
+        assertThat(keylines[5].offset)
+            .isEqualTo(itemSize * 3 + 85 * 1.2f + (85 * 1.2f * 0.5f) / 2f)
+    }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
index ee1c6b4..9b4e39b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
@@ -23,7 +23,7 @@
  *
  * @param locale a [CalendarLocale] that will be used by the created model
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 internal expect fun createCalendarModel(locale: CalendarLocale): CalendarModel
 
 /**
@@ -40,8 +40,8 @@
  * @param locale the [CalendarLocale] to use when formatting the given timestamp
  * @param cache a [MutableMap] for caching formatter related results for better performance
  */
-@ExperimentalMaterial3Api
-expect fun formatWithSkeleton(
+@OptIn(ExperimentalMaterial3Api::class)
+internal expect fun formatWithSkeleton(
     utcTimeMillis: Long,
     skeleton: String,
     locale: CalendarLocale,
@@ -53,7 +53,7 @@
  *
  * @param locale a [CalendarLocale] to be used by this model
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 internal abstract class CalendarModel(val locale: CalendarLocale) {
 
     // A map for caching formatter related results for better performance
@@ -218,7 +218,7 @@
  * @param dayOfMonth the date's day of month
  * @param utcTimeMillis the date representation in _UTC_ milliseconds from the epoch
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 internal data class CalendarDate(
     val year: Int,
     val month: Int,
@@ -248,7 +248,7 @@
  * first day of the month
  * @param startUtcTimeMillis the first day of the month in _UTC_ milliseconds from the epoch
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 internal data class CalendarMonth(
     val year: Int,
     val month: Int,
@@ -286,7 +286,6 @@
  * This data class hold the delimiter that is used by the current [CalendarLocale] when representing
  * dates in a short format, as well as a date pattern with and without a delimiter.
  */
-@ExperimentalMaterial3Api
 @Immutable
 internal data class DateInputFormat(
     val patternWithDelimiters: String,
@@ -313,7 +312,6 @@
  *  - dd.MM.yyyy
  *  - MM/dd/yyyy
  */
-@ExperimentalMaterial3Api
 internal fun datePatternAsInputFormat(localeFormat: String): DateInputFormat {
     val patternWithDelimiters = localeFormat.replace(Regex("[^dMy/\\-.]"), "")
         .replace(Regex("d{1,2}"), "dd")
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
index 8c473af..854a7a9 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
@@ -41,6 +41,9 @@
  * The Material color system and custom schemes provide default values for color as a starting point
  * for customization.
  *
+ * Example of extending color scheme, including implementing Fixed Accent colors:
+ * @sample androidx.compose.material3.samples.ColorSchemeFixedAccentColorSample
+ *
  * To learn more about colors, see [Material Design colors](https://m3.material.io/styles/color/overview).
  *
  * @property primary The primary color is the color displayed most frequently across your app’s
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
index ebd3fdb..940a168 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
@@ -485,7 +485,7 @@
 private val IncreaseSemanticsBounds: Modifier = Modifier
     .layout { measurable, constraints ->
         val paddingPx = SemanticsBoundsPadding.roundToPx()
-        // We need to add vertical padding to the semantics bounds in other to meet
+        // We need to add vertical padding to the semantics bounds in order to meet
         // screenreader green box minimum size, but we also want to
         // preserve a visual appearance and layout size below that minimum
         // in order to maintain backwards compatibility. This custom
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index 91da018..20afbb5 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.hoverable
-import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -56,10 +55,14 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.PointMode
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.graphics.compositeOver
@@ -78,6 +81,7 @@
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.setProgress
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -108,7 +112,7 @@
  * Sliders reflect a range of values along a bar, from which users may select a single value.
  * They are ideal for adjusting settings such as volume, brightness, or applying image filters.
  *
- * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png)
+ * ![Sliders image](https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Flqe2zb2b-1.png?alt=media)
  *
  * Use continuous sliders to allow users to make meaningful selections that don’t
  * require a specific value:
@@ -190,7 +194,7 @@
  * Sliders reflect a range of values along a bar, from which users may select a single value.
  * They are ideal for adjusting settings such as volume, brightness, or applying image filters.
  *
- * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png)
+ * ![Sliders image](https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Flqe2zb2b-1.png?alt=media)
  *
  * Use continuous sliders to allow users to make meaningful selections that don’t
  * require a specific value:
@@ -298,7 +302,7 @@
  * Sliders reflect a range of values along a bar, from which users may select a single value.
  * They are ideal for adjusting settings such as volume, brightness, or applying image filters.
  *
- * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png)
+ * ![Sliders image](https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Flqe2zb2b-1.png?alt=media)
  *
  * Use continuous sliders to allow users to make meaningful selections that don’t
  * require a specific value:
@@ -698,8 +702,8 @@
         modifier = modifier
             .minimumInteractiveComponentSize()
             .requiredSizeIn(
-                minWidth = SliderTokens.HandleWidth,
-                minHeight = SliderTokens.HandleHeight
+                minWidth = ThumbWidth,
+                minHeight = TrackHeight
             )
             .sliderSemantics(
                 state,
@@ -718,7 +722,7 @@
             it.layoutId == SliderComponents.TRACK
         }.measure(
             constraints.offset(
-                horizontal = - thumbPlaceable.width
+                horizontal = -thumbPlaceable.width
             ).copy(minHeight = 0)
         )
 
@@ -726,6 +730,7 @@
         val sliderHeight = max(trackPlaceable.height, thumbPlaceable.height)
 
         state.updateDimensions(
+            trackPlaceable.height.toFloat(),
             thumbPlaceable.width.toFloat(),
             sliderWidth
         )
@@ -800,8 +805,8 @@
         modifier = modifier
             .minimumInteractiveComponentSize()
             .requiredSizeIn(
-                minWidth = SliderTokens.HandleWidth,
-                minHeight = SliderTokens.HandleHeight
+                minWidth = ThumbWidth,
+                minHeight = TrackHeight
             )
             .then(pressDrag)
     ) { measurables, constraints ->
@@ -821,7 +826,7 @@
             it.layoutId == RangeSliderComponents.TRACK
         }.measure(
             constraints.offset(
-                horizontal = - (startThumbPlaceable.width + endThumbPlaceable.width) / 2
+                horizontal = -(startThumbPlaceable.width + endThumbPlaceable.width) / 2
             ).copy(minHeight = 0)
         )
 
@@ -833,6 +838,7 @@
             endThumbPlaceable.height
         )
 
+        state.trackHeight = trackPlaceable.height.toFloat()
         state.startThumbWidth = startThumbPlaceable.width.toFloat()
         state.endThumbWidth = endThumbPlaceable.width.toFloat()
         state.totalWidth = sliderWidth
@@ -975,6 +981,7 @@
      * @param enabled controls the enabled state of this slider. When `false`, this component will
      * not respond to user input, and it will appear visually disabled and disabled to
      * accessibility services.
+     * @param thumbSize the size of the thumb.
      */
     @Composable
     fun Thumb(
@@ -998,26 +1005,16 @@
             }
         }
 
-        val elevation = if (interactions.isNotEmpty()) {
-            ThumbPressedElevation
+        val size = if (interactions.isNotEmpty()) {
+            thumbSize.copy(width = thumbSize.width / 2)
         } else {
-            ThumbDefaultElevation
+            thumbSize
         }
-        val shape = SliderTokens.HandleShape.value
-
         Spacer(
             modifier
-                .size(thumbSize)
-                .indication(
-                    interactionSource = interactionSource,
-                    indication = rippleOrFallbackImplementation(
-                        bounded = false,
-                        radius = SliderTokens.StateLayerSize / 2
-                    )
-                )
+                .size(size)
                 .hoverable(interactionSource = interactionSource)
-                .shadow(if (enabled) elevation else 0.dp, shape, clip = false)
-                .background(colors.thumbColor(enabled), shape)
+                .background(colors.thumbColor(enabled), SliderTokens.HandleShape.value)
         )
     }
 
@@ -1088,16 +1085,16 @@
                 it > sliderPositions.activeRange.endInclusive ||
                     it < sliderPositions.activeRange.start
             }.forEach { (outsideFraction, list) ->
-                    drawPoints(
-                        list.fastMap {
-                            Offset(lerp(sliderStart, sliderEnd, it).x, center.y)
-                        },
-                        PointMode.Points,
-                        (if (outsideFraction) inactiveTickColor else activeTickColor),
-                        tickSize,
-                        StrokeCap.Round
-                    )
-                }
+                drawPoints(
+                    list.fastMap {
+                        Offset(lerp(sliderStart, sliderEnd, it).x, center.y)
+                    },
+                    PointMode.Points,
+                    (if (outsideFraction) inactiveTickColor else activeTickColor),
+                    tickSize,
+                    StrokeCap.Round
+                )
+            }
         }
     }
 
@@ -1112,6 +1109,16 @@
      * not respond to user input, and it will appear visually disabled and disabled to
      * accessibility services.
      */
+    @Deprecated(
+        message = "Use the overload that takes `thumbTrackGapSize`, `trackInsideCornerSize` and " +
+            "`drawStopIndicator`, see `LegacySliderSample` on how to restore the previous " +
+            "behavior",
+        replaceWith = ReplaceWith(
+            "Track(sliderState, modifier, colors, enabled, thumbTrackGapSize, " +
+                "trackInsideCornerSize, drawStopIndicator)"
+        ),
+        level = DeprecationLevel.HIDDEN
+    )
     @Composable
     @ExperimentalMaterial3Api
     fun Track(
@@ -1120,6 +1127,48 @@
         colors: SliderColors = colors(),
         enabled: Boolean = true
     ) {
+        Track(
+            sliderState,
+            modifier,
+            colors,
+            enabled,
+            thumbTrackGapSize = ThumbTrackGapSize,
+            trackInsideCornerSize = TrackInsideCornerSize
+        )
+    }
+
+    /**
+     * The Default track for [Slider]
+     *
+     * @param sliderState [SliderState] which is used to obtain the current active track.
+     * @param modifier the [Modifier] to be applied to the track.
+     * @param colors [SliderColors] that will be used to resolve the colors used for this track in
+     * different states. See [SliderDefaults.colors].
+     * @param enabled controls the enabled state of this slider. When `false`, this component will
+     * not respond to user input, and it will appear visually disabled and disabled to
+     * accessibility services.
+     * @param thumbTrackGapSize size of the gap between the thumb and the track.
+     * @param trackInsideCornerSize size of the corners towards the thumb when a gap is set.
+     * @param drawStopIndicator lambda that will be called to draw the stop indicator at the end of
+     * the track.
+     */
+    @ExperimentalMaterial3Api
+    @Composable
+    fun Track(
+        sliderState: SliderState,
+        modifier: Modifier = Modifier,
+        colors: SliderColors = colors(),
+        enabled: Boolean = true,
+        thumbTrackGapSize: Dp = ThumbTrackGapSize,
+        trackInsideCornerSize: Dp = TrackInsideCornerSize,
+        drawStopIndicator: (DrawScope.(Offset) -> Unit)? = {
+            drawStopIndicator(
+                offset = it,
+                color = colors.activeTrackColor,
+                size = TrackStopIndicatorSize
+            )
+        }
+    ) {
         val inactiveTrackColor = colors.trackColor(enabled, active = false)
         val activeTrackColor = colors.trackColor(enabled, active = true)
         val inactiveTickColor = colors.tickColor(enabled, active = false)
@@ -1136,7 +1185,13 @@
                 inactiveTrackColor,
                 activeTrackColor,
                 inactiveTickColor,
-                activeTickColor
+                activeTickColor,
+                sliderState.trackHeight.toDp(),
+                sliderState.thumbWidth.toDp(),
+                thumbTrackGapSize,
+                trackInsideCornerSize,
+                drawStopIndicator,
+                isRangeSlider = false
             )
         }
     }
@@ -1152,6 +1207,16 @@
      * not respond to user input, and it will appear visually disabled and disabled to
      * accessibility services.
      */
+    @Deprecated(
+        message = "Use the overload that takes `thumbTrackGapSize`, `trackInsideCornerSize` and " +
+            "`drawStopIndicator`, see `LegacyRangeSliderSample` on how to restore the " +
+            "previous behavior",
+        replaceWith = ReplaceWith(
+            "Track(rangeSliderState, modifier, colors, enabled, thumbTrackGapSize, " +
+                "trackInsideCornerSize, drawStopIndicator)"
+        ),
+        level = DeprecationLevel.HIDDEN
+    )
     @OptIn(ExperimentalMaterial3Api::class)
     @Composable
     fun Track(
@@ -1160,6 +1225,48 @@
         colors: SliderColors = colors(),
         enabled: Boolean = true
     ) {
+        Track(
+            rangeSliderState,
+            modifier,
+            colors,
+            enabled,
+            thumbTrackGapSize = ThumbTrackGapSize,
+            trackInsideCornerSize = TrackInsideCornerSize
+        )
+    }
+
+    /**
+     * The Default track for [RangeSlider]
+     *
+     * @param rangeSliderState [RangeSliderState] which is used to obtain the current active track.
+     * @param modifier the [Modifier] to be applied to the track.
+     * @param colors [SliderColors] that will be used to resolve the colors used for this track in
+     * different states. See [SliderDefaults.colors].
+     * @param enabled controls the enabled state of this slider. When `false`, this component will
+     * not respond to user input, and it will appear visually disabled and disabled to
+     * accessibility services.
+     * @param thumbTrackGapSize size of the gap between the thumbs and the track.
+     * @param trackInsideCornerSize size of the corners towards the thumbs when a gap is set.
+     * @param drawStopIndicator lambda that will be called to draw the stop indicator at the
+     * start/end of the track.
+     */
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    fun Track(
+        rangeSliderState: RangeSliderState,
+        modifier: Modifier = Modifier,
+        colors: SliderColors = colors(),
+        enabled: Boolean = true,
+        thumbTrackGapSize: Dp = ThumbTrackGapSize,
+        trackInsideCornerSize: Dp = TrackInsideCornerSize,
+        drawStopIndicator: (DrawScope.(Offset) -> Unit)? = {
+            drawStopIndicator(
+                offset = it,
+                color = colors.activeTrackColor,
+                size = TrackStopIndicatorSize
+            )
+        }
+    ) {
         val inactiveTrackColor = colors.trackColor(enabled, active = false)
         val activeTrackColor = colors.trackColor(enabled, active = true)
         val inactiveTickColor = colors.tickColor(enabled, active = false)
@@ -1176,7 +1283,13 @@
                 inactiveTrackColor,
                 activeTrackColor,
                 inactiveTickColor,
-                activeTickColor
+                activeTickColor,
+                rangeSliderState.trackHeight.toDp(),
+                rangeSliderState.startThumbWidth.toDp(),
+                thumbTrackGapSize,
+                trackInsideCornerSize,
+                drawStopIndicator,
+                isRangeSlider = true,
             )
         }
     }
@@ -1188,7 +1301,13 @@
         inactiveTrackColor: Color,
         activeTrackColor: Color,
         inactiveTickColor: Color,
-        activeTickColor: Color
+        activeTickColor: Color,
+        height: Dp,
+        thumbWidth: Dp,
+        thumbTrackGapSize: Dp,
+        trackInsideCornerSize: Dp,
+        drawStopIndicator: (DrawScope.(Offset) -> Unit)?,
+        isRangeSlider: Boolean
     ) {
         val isRtl = layoutDirection == LayoutDirection.Rtl
         val sliderLeft = Offset(0f, center.y)
@@ -1196,14 +1315,8 @@
         val sliderStart = if (isRtl) sliderRight else sliderLeft
         val sliderEnd = if (isRtl) sliderLeft else sliderRight
         val tickSize = TickSize.toPx()
-        val trackStrokeWidth = TrackHeight.toPx()
-        drawLine(
-            inactiveTrackColor,
-            sliderStart,
-            sliderEnd,
-            trackStrokeWidth,
-            StrokeCap.Round
-        )
+        val trackStrokeWidth = height.toPx()
+
         val sliderValueEnd = Offset(
             sliderStart.x +
                 (sliderEnd.x - sliderStart.x) * activeRangeEnd,
@@ -1216,23 +1329,111 @@
             center.y
         )
 
-        drawLine(
-            activeTrackColor,
-            sliderValueStart,
-            sliderValueEnd,
-            trackStrokeWidth,
-            StrokeCap.Round
-        )
+        val cornerSize = trackStrokeWidth / 2
+        val insideCornerSize = trackInsideCornerSize.toPx()
+        val gap =
+            if (thumbTrackGapSize > 0.dp) thumbWidth.toPx() / 2 + thumbTrackGapSize.toPx() else 0f
 
-        for (tick in tickFractions) {
+        if (isRangeSlider && sliderValueStart.x > sliderStart.x + gap + cornerSize) {
+            val start = sliderStart.x
+            val end = sliderValueStart.x - gap
+            drawTrackPath(
+                Offset.Zero,
+                Size(end - start, trackStrokeWidth),
+                inactiveTrackColor,
+                cornerSize,
+                insideCornerSize
+            )
+            drawStopIndicator?.invoke(this, Offset(start + cornerSize, center.y))
+        }
+        if (sliderValueEnd.x < sliderEnd.x - gap - cornerSize) {
+            val start = sliderValueEnd.x + gap
+            val end = sliderEnd.x
+            drawTrackPath(
+                Offset(start, 0f),
+                Size(end - start, trackStrokeWidth),
+                inactiveTrackColor,
+                insideCornerSize,
+                cornerSize
+            )
+            drawStopIndicator?.invoke(this, Offset(end - cornerSize, center.y))
+        }
+        val activeTrackStart =
+            if (isRangeSlider) sliderValueStart.x + gap else 0f
+        val activeTrackEnd = sliderValueEnd.x - gap
+        val startCornerRadius = if (isRangeSlider) insideCornerSize else cornerSize
+        if (activeTrackEnd - activeTrackStart > startCornerRadius + gap) {
+            drawTrackPath(
+                Offset(activeTrackStart, 0f),
+                Size(activeTrackEnd - activeTrackStart, trackStrokeWidth),
+                activeTrackColor,
+                startCornerRadius,
+                insideCornerSize
+            )
+        }
+
+        tickFractions.forEachIndexed { index, tick ->
+            // skip ticks that fall on the stop indicator
+            if (drawStopIndicator != null) {
+                if ((isRangeSlider && index == 0) || index == tickFractions.size - 1) {
+                    return@forEachIndexed
+                }
+            }
+
             val outsideFraction = tick > activeRangeEnd || tick < activeRangeStart
+            val start = Offset(sliderStart.x + gap, sliderStart.y)
+            val end = Offset(sliderEnd.x - gap, sliderEnd.y)
             drawCircle(
                 color = if (outsideFraction) inactiveTickColor else activeTickColor,
-                center = Offset(lerp(sliderStart, sliderEnd, tick).x, center.y),
+                center = Offset(lerp(start, end, tick).x, center.y),
                 radius = tickSize / 2f
             )
         }
     }
+
+    private fun DrawScope.drawTrackPath(
+        offset: Offset,
+        size: Size,
+        color: Color,
+        startCornerRadius: Float,
+        endCornerRadius: Float
+    ) {
+        val startCorner = RoundRect(
+            rect = Rect(
+                offset,
+                size = Size(startCornerRadius * 2, size.height)
+            ), cornerRadius = CornerRadius(startCornerRadius)
+        )
+        val track =
+            Rect(
+                Offset(offset.x + startCornerRadius, 0f),
+                size = Size(size.width - startCornerRadius - endCornerRadius, size.height)
+            )
+        val endCorner = RoundRect(
+            rect = Rect(
+                Offset(offset.x + startCornerRadius + track.width - endCornerRadius, 0f),
+                size = Size(endCornerRadius * 2, size.height)
+            ), cornerRadius = CornerRadius(endCornerRadius)
+        )
+
+        val path = Path()
+        path.addRoundRect(startCorner)
+        path.addRect(track)
+        path.addRoundRect(endCorner)
+        drawPath(path, color)
+    }
+
+    private fun DrawScope.drawStopIndicator(
+        offset: Offset,
+        size: Dp,
+        color: Color
+    ) {
+        drawCircle(
+            color = color,
+            center = offset,
+            radius = size.toPx() / 2f
+        )
+    }
 }
 
 private fun snapValueToTick(
@@ -1702,15 +1903,14 @@
 }
 
 // Internal to be referred to in tests
+internal val TrackHeight = SliderTokens.InactiveTrackHeight
 internal val ThumbWidth = SliderTokens.HandleWidth
 private val ThumbHeight = SliderTokens.HandleHeight
 private val ThumbSize = DpSize(ThumbWidth, ThumbHeight)
-private val ThumbDefaultElevation = 1.dp
-private val ThumbPressedElevation = 6.dp
 private val TickSize = SliderTokens.TickMarksContainerSize
-
-// Internal to be referred to in tests
-internal val TrackHeight = SliderTokens.InactiveTrackHeight
+private val ThumbTrackGapSize: Dp = 6.dp
+private val TrackInsideCornerSize: Dp = 2.dp
+private val TrackStopIndicatorSize: Dp = 4.dp
 
 private enum class SliderComponents {
     THUMB,
@@ -1843,7 +2043,8 @@
     internal val tickFractions = stepsToTickFractions(steps)
     private var totalWidth by mutableIntStateOf(0)
     internal var isRtl = false
-    private var thumbWidth by mutableFloatStateOf(0f)
+    internal var trackHeight by mutableFloatStateOf(0f)
+    internal var thumbWidth by mutableFloatStateOf(0f)
 
     internal val coercedValueAsFraction
         get() = calcFraction(
@@ -1856,9 +2057,11 @@
         private set
 
     internal fun updateDimensions(
+        newTrackHeight: Float,
         newThumbWidth: Float,
         newTotalWidth: Int
     ) {
+        trackHeight = newTrackHeight
         thumbWidth = newThumbWidth
         totalWidth = newTotalWidth
     }
@@ -1957,6 +2160,7 @@
 
     internal val tickFractions = stepsToTickFractions(steps)
 
+    internal var trackHeight by mutableFloatStateOf(0f)
     internal var startThumbWidth by mutableFloatStateOf(0f)
     internal var endThumbWidth by mutableFloatStateOf(0f)
     internal var totalWidth by mutableIntStateOf(0)
@@ -2144,5 +2348,5 @@
  * Check for if a given [SliderRange] is not [SliderRange.Unspecified].
  */
 @Stable
-internal val SliderRange.isSpecified: Boolean get() =
-    packedValue != SliderRange.Unspecified.packedValue
+internal val SliderRange.isSpecified: Boolean
+    get() = packedValue != SliderRange.Unspecified.packedValue
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
index f27a6b6..9f8c902 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
@@ -17,6 +17,8 @@
 package androidx.compose.material3.carousel
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.PageSize
 import androidx.compose.foundation.pager.VerticalPager
@@ -24,6 +26,7 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ShapeDefaults
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Rect
@@ -33,31 +36,217 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
 import kotlin.math.roundToInt
 
 /**
- * A enumeration of ways items can be aligned along a carousel's main axis
+ * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
+ *
+ * A horizontal carousel meant to display many items at once for quick browsing of smaller content
+ * like album art or photo thumbnails.
+ *
+ * Note that this carousel may adjust the size of large items. In order to ensure a mix of large,
+ * medium, and small items fit perfectly into the available space and are arranged in a
+ * visually pleasing way, this carousel finds the nearest number of large items that
+ * will fit the container and adjusts their size to fit, if necessary.
+ *
+ * For more information, see <a href="https://material.io/components/carousel/overview">design
+ * guidelines</a>.
+ *
+ * @param state The state object to be used to control the carousel's state
+ * @param preferredItemSize The size fully visible items would like to be in the main axis. This
+ * size is a target and will likely be adjusted by carousel in order to fit a whole number of
+ * items within the container
+ * @param modifier A modifier instance to be applied to this carousel container
+ * @param itemSpacing The amount of space used to separate items in the carousel
+ * @param minSmallSize The minimum allowable size of small masked items
+ * @param maxSmallSize The maximum allowable size of small masked items
+ * @param content The carousel's content Composable
+ *
+ * TODO: Add sample link
  */
-internal enum class CarouselAlignment {
-    /** Start aligned carousels place focal items at the start/top of the container */
-    Start,
-    /** Center aligned carousels place focal items in the middle of the container */
-    Center,
-    /** End aligned carousels place focal items at the end/bottom of the container */
-    End
+@ExperimentalMaterial3Api
+@Composable
+internal fun HorizontalMultiBrowseCarousel(
+    state: CarouselState,
+    preferredItemSize: Dp,
+    modifier: Modifier = Modifier,
+    itemSpacing: Dp = 0.dp,
+    minSmallSize: Dp = StrategyDefaults.MinSmallSize,
+    maxSmallSize: Dp = StrategyDefaults.MaxSmallSize,
+    content: @Composable CarouselScope.(itemIndex: Int) -> Unit
+) {
+    val density = LocalDensity.current
+    Carousel(
+        state = state,
+        orientation = Orientation.Horizontal,
+        keylineList = { availableSpace ->
+            with(density) {
+                multiBrowseKeylineList(
+                    density = this,
+                    carouselMainAxisSize = availableSpace,
+                    preferredItemSize = preferredItemSize.toPx(),
+                    itemSpacing = itemSpacing.toPx(),
+                    minSmallSize = minSmallSize.toPx(),
+                    maxSmallSize = maxSmallSize.toPx()
+                )
+            }
+        },
+        modifier = modifier,
+        itemSpacing = itemSpacing,
+        content = content
+    )
 }
 
 /**
- * An enumeration of orientations that determine a carousel's main axis
+ * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
+ *
+ * A horizontal carousel that displays its items with the given size except for one item at the end
+ * that is cut off.
+ *
+ * Note that the item size will be bound by the size of the carousel. Otherwise, this carousel lays
+ * out as many items as it can in the given size, and changes the size of the last cut off item such
+ * that there is a range of motion when items scroll off the edge.
+ *
+ * For more information, see <a href="https://material.io/components/carousel/overview">design
+ * guidelines</a>.
+ *
+ * @param state The state object to be used to control the carousel's state
+ * @param itemSize The size of items in the carousel
+ * @param modifier A modifier instance to be applied to this carousel container
+ * @param itemSpacing The amount of space used to separate items in the carousel
+ * @param content The carousel's content Composable
+ *
+ * TODO: Add sample link
  */
-internal enum class Orientation {
-    /** Vertical orientation representing Y axis */
-    Vertical,
-    /** Horizontal orientation representing X axis */
-    Horizontal
+@ExperimentalMaterial3Api
+@Composable
+internal fun HorizontalUncontainedCarousel(
+    state: CarouselState,
+    itemSize: Dp,
+    modifier: Modifier = Modifier,
+    itemSpacing: Dp = 0.dp,
+    content: @Composable CarouselScope.(itemIndex: Int) -> Unit
+) {
+    val density = LocalDensity.current
+    Carousel(
+        state = state,
+        orientation = Orientation.Horizontal,
+        keylineList = {
+            with(density) {
+                uncontainedKeylineList(
+                    density = this,
+                    carouselMainAxisSize = state.pagerState.layoutInfo.viewportSize.width.toFloat(),
+                    itemSize = itemSize.toPx(),
+                    itemSpacing = itemSpacing.toPx(),
+                )
+            }
+        },
+        modifier = modifier,
+        itemSpacing = itemSpacing,
+        content = content
+    )
+}
+
+/**
+ * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
+ *
+ * Carousels contain a collection of items that changes sizes according to their placement and the
+ * chosen strategy.
+ *
+ * @param state The state object to be used to control the carousel's state.
+ * @param modifier A modifier instance to be applied to this carousel outer layout
+ * @param keylineList The list of keylines that are fixed positions along the scrolling axis which
+ * define the state an item should be in when its center is co-located with the keyline's position.
+ * @param itemSpacing The amount of space used to separate items in the carousel
+ * @param orientation The layout orientation of the carousel
+ * @param content The carousel's content Composable where each call is passed the index, from the
+ * total item count, of the item being composed
+ * TODO: Add sample link
+ */
+@ExperimentalMaterial3Api
+@Composable
+internal fun Carousel(
+    state: CarouselState,
+    orientation: Orientation,
+    keylineList: (availableSpace: Float) -> KeylineList?,
+    modifier: Modifier = Modifier,
+    itemSpacing: Dp = 0.dp,
+    content: @Composable CarouselScope.(itemIndex: Int) -> Unit
+) {
+    val pageSize = remember(keylineList) { CarouselPageSize(keylineList) }
+
+    // TODO: Update beyond bounds numbers according to Strategy
+    val outOfBoundsPageCount = 2
+    val carouselScope = CarouselScopeImpl
+
+    if (orientation == Orientation.Horizontal) {
+        HorizontalPager(
+            state = state.pagerState,
+            pageSize = pageSize,
+            pageSpacing = itemSpacing,
+            outOfBoundsPageCount = outOfBoundsPageCount,
+            modifier = modifier
+        ) { page ->
+            Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy)) {
+                carouselScope.content(page)
+            }
+        }
+    } else if (orientation == Orientation.Vertical) {
+        VerticalPager(
+            state = state.pagerState,
+            pageSize = pageSize,
+            pageSpacing = itemSpacing,
+            outOfBoundsPageCount = outOfBoundsPageCount,
+            modifier = modifier
+        ) { page ->
+            Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy)) {
+                carouselScope.content(page)
+            }
+        }
+    }
+}
+
+/**
+ * A [PageSize] implementation that maintains a strategy that is kept up-to-date with the
+ * latest available space of the container.
+ *
+ * @param keylineList The list of keylines that are fixed positions along the scrolling axis which
+ * define the state an item should be in when its center is co-located with the keyline's position.
+ */
+private class CarouselPageSize(keylineList: (availableSpace: Float) -> KeylineList?) : PageSize {
+    val strategy = Strategy(keylineList)
+    override fun Density.calculateMainAxisPageSize(availableSpace: Int, pageSpacing: Int): Int {
+        strategy.apply(availableSpace.toFloat())
+        return if (strategy.isValid()) {
+            strategy.itemMainAxisSize.roundToInt()
+        } else {
+            // If strategy does not have a valid arrangement, default to a
+            // full size item, as Pager does by default.
+            availableSpace
+        }
+    }
+}
+
+/**
+ * This class defines ways items can be aligned along a carousel's main axis.
+ */
+@JvmInline
+internal value class CarouselAlignment private constructor(internal val value: Int) {
+    companion object {
+        /** Start aligned carousels place focal items at the start/top of the container */
+        val Start = CarouselAlignment(-1)
+
+        /** Center aligned carousels place focal items in the middle of the container */
+        val Center = CarouselAlignment(0)
+
+        /** End aligned carousels place focal items at the end/bottom of the container */
+        val End = CarouselAlignment(1)
+    }
 }
 
 /**
@@ -65,25 +254,31 @@
  * of a Carousel.
  *
  * @param index the index of the item in the carousel
- * @param viewportSize the size of the carousel container
- * @param orientation the orientation of the carousel
- * @param itemsCount the total number of items in the carousel
- * @param scrollOffset the amount the carousel has been scrolled in pixels
+ * @param state the carousel state
  * @param strategy the strategy used to mask and translate items in the carousel
  */
+@Suppress("IllegalExperimentalApiUsage")
+@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
 internal fun Modifier.carouselItem(
     index: Int,
-    itemsCount: Int,
-    viewportSize: IntSize,
-    orientation: Orientation,
-    scrollOffset: Float,
+    state: CarouselState,
     strategy: Strategy
 ): Modifier {
+    val viewportSize = state.pagerState.layoutInfo.viewportSize
+    val orientation = state.pagerState.layoutInfo.orientation
     val isVertical = orientation == Orientation.Vertical
     val mainAxisCarouselSize = if (isVertical) viewportSize.height else viewportSize.width
-    if (mainAxisCarouselSize == 0) {
+
+    if (mainAxisCarouselSize == 0 || !strategy.isValid()) {
         return this
     }
+    // Scroll offset calculation using currentPage and currentPageOffsetFraction
+    val firstVisibleItemScrollOffset =
+        state.pagerState.currentPageOffsetFraction * strategy.itemMainAxisSize
+    val scrollOffset = (state.pagerState.currentPage * strategy.itemMainAxisSize) +
+        firstVisibleItemScrollOffset
+    val itemsCount = state.pagerState.pageCount
+
     val maxScrollOffset =
         itemsCount * strategy.itemMainAxisSize - mainAxisCarouselSize
     val keylines = strategy.getKeylineListForScrollOffset(scrollOffset, maxScrollOffset)
@@ -203,101 +398,3 @@
     val total = after.unadjustedOffset - before.unadjustedOffset
     return (unadjustedOffset - before.unadjustedOffset) / total
 }
-
-/**
- * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
- *
- * A Carousel that scrolls horizontally. Carousels contain a collection of items that changes sizes
- * according to their placement and the chosen strategy.
- *
- * @param state The state object to be used to control the carousel's state.
- * @param modifier A modifier instance to be applied to this carousel outer layout
- * @param content The carousel's content Composable.
- * TODO: Add sample link
- */
-@ExperimentalMaterial3Api
-@Composable
-internal fun HorizontalCarousel(
-    state: CarouselState,
-    modifier: Modifier = Modifier,
-    content: @Composable CarouselScope.(item: Int) -> Unit
-) = Carousel(
-        state = state,
-        modifier = modifier,
-        orientation = Orientation.Horizontal,
-        content = content
-)
-
-/**
- * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
- *
- * A Carousel that scrolls vertically. Carousels contain a collection of items that changes sizes
- * according to their placement and the chosen strategy.
- *
- * @param state The state object to be used to control the carousel's state.
- * @param modifier A modifier instance to be applied to this carousel outer layout
- * @param content The carousel's content Composable.
- * TODO: Add sample link
- */
-@ExperimentalMaterial3Api
-@Composable
-internal fun VerticalCarousel(
-    state: CarouselState,
-    modifier: Modifier = Modifier,
-    content: @Composable CarouselScope.(item: Int) -> Unit
-) = Carousel(
-        state = state,
-        modifier = modifier,
-        orientation = Orientation.Vertical,
-        content = content
-)
-
-/**
- * <a href=https://m3.material.io/components/carousel/overview" class="external" target="_blank">Material Design Carousel</a>
- *
- * Carousels contain a collection of items that changes sizes according to their placement and the
- * chosen strategy.
- *
- * @param state The state object to be used to control the carousel's state.
- * @param modifier A modifier instance to be applied to this carousel outer layout
- * @param orientation The layout orientation of the carousel
- * @param content The carousel's content Composable.
- * TODO: Add sample link
- */
-// TODO: b/321997456 - Remove lint suppression once version checks are added in lint or library
-// moves to beta
-@Suppress("IllegalExperimentalApiUsage")
-@OptIn(ExperimentalFoundationApi::class)
-@ExperimentalMaterial3Api
-@Composable
-internal fun Carousel(
-    state: CarouselState,
-    modifier: Modifier = Modifier,
-    orientation: Orientation = Orientation.Horizontal,
-    content: @Composable CarouselScope.(item: Int) -> Unit
-) {
-    // TODO: Update page size according to strategy
-    val pageSize = PageSize.Fill
-    // TODO: Update out of bounds page count numbers
-    val outOfBoundsPageCount = 1
-    val carouselScope = CarouselScopeImpl
-    if (orientation == Orientation.Horizontal) {
-        HorizontalPager(
-            state = state.pagerState,
-            pageSize = pageSize,
-            outOfBoundsPageCount = outOfBoundsPageCount,
-            modifier = modifier
-        ) { page ->
-            carouselScope.content(page)
-        }
-    } else if (orientation == Orientation.Vertical) {
-        VerticalPager(
-            state = state.pagerState,
-            pageSize = pageSize,
-            outOfBoundsPageCount = outOfBoundsPageCount,
-            modifier = modifier
-        ) { page ->
-            carouselScope.content(page)
-        }
-    }
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
index e48ec30..65b7ac4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
@@ -29,7 +29,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 
 /**
- * The state that can be used to control [VerticalCarousel] and [HorizontalCarousel].
+ * The state that can be used to control all types of carousels.
  *
  * @param currentItem the current item to be scrolled to.
  * @param currentItemOffsetFraction the current item offset as a fraction of the item size.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
deleted file mode 100644
index eeda9e63..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.material3.carousel
-
-import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastMapIndexed
-import kotlin.math.abs
-
-/**
- * A structure that is fixed at a specific [offset] along a scrolling axis and
- * defines properties of an item when its center is located at [offset].
- *
- * [Keyline] is the primary structure of any carousel. When multiple keylines are placed along a
- * carousel's axis and an item is scrolled, that item will always be between two keylines. The
- * item's distance between its two surrounding keylines can be used as a fraction to create an
- * interpolated keyline that the item uses to set its size and translation.
- *
- * @param size the size an item should be in pixels when its center is at [offset]
- * @param offset the location of the keyline along the scrolling axis and where the center of an
- * item should be (usually translated to) when it is at [unadjustedOffset] in the end-to-end
- * scrolling model
- * @param unadjustedOffset the location of:445
- * the keyline in the end-to-end scrolling model (when all
- * items are laid out with their full size and placed end-to-end)
- * @param isFocal whether an item at this keyline is focal or fully "viewable"
- * @param isAnchor true if this keyline is able to be shifted within a list of keylines
- * @param isPivot true if this is the keyline that was used to calculate all other keyline offsets
- * and unadjusted offsets in a list
- * @param cutoff the amount this item bleeds beyond the bounds of the container - 0 if the item
- * is fully in-bounds or fully out-of-bounds
- */
-internal data class Keyline(
-    val size: Float,
-    val offset: Float,
-    val unadjustedOffset: Float,
-    val isFocal: Boolean,
-    val isAnchor: Boolean,
-    val isPivot: Boolean,
-    val cutoff: Float,
-)
-
-/**
- * A [List] of [Keyline]s with additional functionality specific to carousel.
- *
- * Note that [KeylineList]'s constructor should only be used when creating an interpolated
- * KeylineList. If creating a new KeylineList - for a strategy or shifted step - prefer using the
- * [keylineListOf] method which will handle setting all offsets and unadjusted offsets based on
- * a pivot keyline.
- */
-internal class KeylineList internal constructor(
-    keylines: List<Keyline>
-) : List<Keyline> by keylines {
-
-    /**
-     * Returns the index of the pivot keyline used to calculate all other keyline offsets and
-     * unadjusted offsets.
-     */
-    val pivotIndex: Int = indexOfFirst { it.isPivot }
-
-    /** Returns the keyline used to calculate all other keyline offsets and unadjusted offsets. */
-    val pivot: Keyline = get(pivotIndex)
-
-    /**
-     * Returns the index of the first non-anchor keyline or -1 if the list does not contain a
-     * non-anchor keyline.
-     */
-    val firstNonAnchorIndex: Int = indexOfFirst { !it.isAnchor }
-
-    /**
-     * Returns the first non-anchor [Keyline].
-     * @throws [NoSuchElementException] if there are no non-anchor keylines.
-     */
-    val firstNonAnchor: Keyline = get(firstNonAnchorIndex)
-
-    /**
-     * Returns the index of the last non-anchor keyline or -1 if the list does not contain a
-     * non-anchor keyline.
-     */
-    val lastNonAnchorIndex: Int = indexOfLast { !it.isAnchor }
-
-    /**
-     * Returns the last non-anchor [Keyline].
-     * @throws [NoSuchElementException] if there are no non-anchor keylines.
-     */
-    val lastNonAnchor = get(lastNonAnchorIndex)
-
-    /**
-     * Returns the index of the first focal keyline or -1 if the list does not contain a
-     * focal keyline.
-     */
-    val firstFocalIndex = indexOfFirst { it.isFocal }
-
-    /**
-     * Returns the first focal [Keyline].
-     * @throws [NoSuchElementException] if there are no focal keylines.
-     */
-    val firstFocal: Keyline = getOrNull(firstFocalIndex)
-        ?: throw NoSuchElementException("All KeylineLists must have at least one focal keyline")
-
-    /**
-     * Returns the index of the last focal keyline or -1 if the list does not contain a
-     * focal keyline.
-     */
-    val lastFocalIndex: Int = indexOfLast { it.isFocal }
-
-    /**
-     * Returns the last focal [Keyline].
-     * @throws [NoSuchElementException] if there are no focal keylines.
-     */
-    val lastFocal = getOrNull(lastFocalIndex)
-        ?: throw NoSuchElementException("All KeylineLists must have at least one focal keyline")
-
-    /**
-     * Returns true if the first focal item's left/top is within the visible bounds of the container
-     * and is the first non-anchor keyline.
-     *
-     * When this is true, it means the focal range cannot be shifted left/top or is shifted as
-     * far left/top as possible. When this is false, there are keylines that can be swapped to
-     * shift the first focal item closer to the left/top of the container while still remaining
-     * visible.
-     */
-    fun isFirstFocalItemAtStartOfContainer(): Boolean {
-        val firstFocalLeft = firstFocal.offset - (firstFocal.size / 2)
-        return firstFocalLeft >= 0 && firstFocal == firstNonAnchor
-    }
-
-    /**
-     * Returns true if the last focal item's right/bottom is within the visible bounds of the
-     * container and is the last non-anchor keyline.
-     *
-     * When this is true, it means the focal range cannot be shifted right/bottom or is shifted as
-     * far right/bottom as possible. When this is false, there are keylines that can be swapped to
-     * shift the last focal item closer to the right/bottom of the container while still remaining
-     * visible.
-     */
-    fun isLastFocalItemAtEndOfContainer(carouselMainAxisSize: Float): Boolean {
-        val lastFocalRight = lastFocal.offset + (lastFocal.size / 2)
-        return lastFocalRight <= carouselMainAxisSize && lastFocal == lastNonAnchor
-    }
-
-    /**
-     * Returns the index of the first keyline after the focal range where the keyline's size is
-     * equal to [size] or the last index if no keyline is found.
-     *
-     * This is useful when moving keylines from one side of the focal range to the other (shifting).
-     * Find an index on the other side of the focal range where after moving the keyline, the
-     * keyline list will retain its original visual balance.
-     */
-    fun firstIndexAfterFocalRangeWithSize(size: Float): Int {
-        val from = lastFocalIndex
-        val to = lastIndex
-        return (from..to).firstOrNull { i -> this[i].size == size } ?: lastIndex
-    }
-
-    /**
-     * Returns the index of the last keyline before the focal range where the keyline's size is
-     * equal to [size] or 0 if no keyline is found.
-     *
-     * This is useful when moving keylines from one side of the focal range to the other (shifting).
-     * Find an index on the other side of the focal range where after moving the keyline, the
-     * keyline list will retain its original visual balance.
-     */
-    fun lastIndexBeforeFocalRangeWithSize(size: Float): Int {
-        val from = firstFocalIndex - 1
-        val to = 0
-        return (from downTo to).firstOrNull { i -> this[i].size == size } ?: to
-    }
-
-    /**
-     * Returns the last [Keyline] with an unadjustedOffset that is less than [unadjustedOffset] or
-     * the first keyline if none is found.
-     */
-    fun getKeylineBefore(unadjustedOffset: Float): Keyline {
-        for (index in indices.reversed()) {
-            val k = get(index)
-            if (k.unadjustedOffset < unadjustedOffset) {
-                return k
-            }
-        }
-
-        return first()
-    }
-
-    /**
-     * Returns the first [Keyline] with an unadjustedOffset that is greater than
-     * [unadjustedOffset] or the last keyline if none is found.
-     */
-    fun getKeylineAfter(unadjustedOffset: Float): Keyline {
-        return fastFirstOrNull { it.unadjustedOffset >= unadjustedOffset } ?: last()
-    }
-}
-
-/**
- * Returns a [KeylineList] by aligning the focal range relative to the carousel container.
- */
-internal fun keylineListOf(
-    carouselMainAxisSize: Float,
-    carouselAlignment: CarouselAlignment,
-    keylines: KeylineListScope.() -> Unit
-): KeylineList {
-    val keylineListScope = KeylineListScopeImpl()
-    keylines.invoke(keylineListScope)
-    return keylineListScope.createWithAlignment(carouselMainAxisSize, carouselAlignment)
-}
-
-/**
- * Returns a [KeylineList] by using a single pivot keyline to calculate the offset and unadjusted
- * offset of all keylines in the list.
- */
-internal fun keylineListOf(
-    carouselMainAxisSize: Float,
-    pivotIndex: Int,
-    pivotOffset: Float,
-    keylines: KeylineListScope.() -> Unit
-): KeylineList {
-    val keylineListScope = KeylineListScopeImpl()
-    keylines.invoke(keylineListScope)
-    return keylineListScope.createWithPivot(carouselMainAxisSize, pivotIndex, pivotOffset)
-}
-
-/** Receiver scope for creating a [KeylineList] using [keylineListOf] */
-internal interface KeylineListScope {
-
-    /**
-     * Adds a keyline to the resulting [KeylineList].
-     *
-     * Note that keylines are added in the order they will appear.
-     *
-     * @param size the size of an item in pixels at this keyline
-     * @param isAnchor true if this keyline should not be shifted - usually the first and last fully
-     * off-screen keylines
-     */
-    fun add(size: Float, isAnchor: Boolean = false)
-}
-
-private class KeylineListScopeImpl : KeylineListScope {
-
-    private data class TmpKeyline(val size: Float, val isAnchor: Boolean)
-
-    private var firstFocalIndex: Int = -1
-    private var focalItemSize: Float = 0f
-    private var pivotIndex: Int = -1
-    private var pivotOffset: Float = 0f
-    private val tmpKeylines = mutableListOf<TmpKeyline>()
-    override fun add(size: Float, isAnchor: Boolean) {
-        tmpKeylines.add(TmpKeyline(size, isAnchor))
-        // Save the first "focal" item by looking for the first index of the largest item added
-        // to the list. The last focal item index will be found when `create` is called by starting
-        // from firstFocalIndex and incrementing the index until the next item's size does not
-        // equal focalItemSize.
-        if (size > focalItemSize) {
-            firstFocalIndex = tmpKeylines.lastIndex
-            focalItemSize = size
-        }
-    }
-
-    fun createWithPivot(
-        carouselMainAxisSize: Float,
-        pivotIndex: Int,
-        pivotOffset: Float
-    ): KeylineList {
-        val keylines = createKeylinesWithPivot(
-            pivotIndex,
-            pivotOffset,
-            firstFocalIndex,
-            findLastFocalIndex(),
-            itemMainAxisSize = focalItemSize,
-            carouselMainAxisSize = carouselMainAxisSize,
-            tmpKeylines
-        )
-        return KeylineList(keylines)
-    }
-
-    fun createWithAlignment(
-        carouselMainAxisSize: Float,
-        carouselAlignment: CarouselAlignment
-    ): KeylineList {
-        val lastFocalIndex = findLastFocalIndex()
-        val focalItemCount = lastFocalIndex - firstFocalIndex
-
-        pivotIndex = firstFocalIndex
-        pivotOffset = when (carouselAlignment) {
-            CarouselAlignment.Start -> focalItemSize / 2
-            CarouselAlignment.Center -> {
-                (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount)
-            }
-            CarouselAlignment.End -> carouselMainAxisSize - (focalItemSize / 2)
-        }
-
-        val keylines = createKeylinesWithPivot(
-            pivotIndex,
-            pivotOffset,
-            firstFocalIndex,
-            lastFocalIndex,
-            itemMainAxisSize = focalItemSize,
-            carouselMainAxisSize = carouselMainAxisSize,
-            tmpKeylines
-        )
-        return KeylineList(keylines)
-    }
-
-    private fun findLastFocalIndex(): Int {
-        // Find the last focal index. Start from the first focal index and walk up the indices
-        // while items remain the same size as the first focal item size - finding a contiguous
-        // range of indices where item size is equal to focalItemSize.
-        var lastFocalIndex = firstFocalIndex
-        while (lastFocalIndex < tmpKeylines.lastIndex &&
-            tmpKeylines[lastFocalIndex + 1].size == focalItemSize) {
-            lastFocalIndex ++
-        }
-        return lastFocalIndex
-    }
-
-    /**
-     * Converts a list of [TmpKeyline] to a list of [Keyline]s whose offset, unadjusted offset,
-     * and cutoff are calculated from a pivot.
-     *
-     * Pivoting is useful when aligning the entire arrangement relative to the scrolling container.
-     * When creating a keyline list with the first focal keyline aligned to the start of the
-     * container, use the first focal item as the pivot and set the pivot offset to where that first
-     * focal item's center should be placed (carouselStart + (item size / 2)). All keylines
-     * before and after the pivot will have their offset, unadjusted offset, and cutoff calculated
-     * based on the pivot offset. When shifting keylines and moving the carousel's alignment from
-     * start to end, use setPivot to align the last focal keyline to the end of the container.
-     *
-     * @param pivotIndex the index of the keyline from [tmpKeylines] that is used to align the
-     * entire arrangement
-     * @param pivotOffset the offset along the scrolling axis where the pivot keyline should
-     * be placed and where keylines before and after will have their offset, unadjustedOffset, and
-     * cutoff calculated from
-     * @param firstFocalIndex the index of the first focal item in the [tmpKeylines] list
-     * @param lastFocalIndex the index of the last focal item in the [tmpKeylines] list
-     * @param itemMainAxisSize the size of focal, or fully unmasked/clipped, items
-     * @param carouselMainAxisSize the size of the carousel container in the scrolling axis
-     */
-    private fun createKeylinesWithPivot(
-        pivotIndex: Int,
-        pivotOffset: Float,
-        firstFocalIndex: Int,
-        lastFocalIndex: Int,
-        itemMainAxisSize: Float,
-        carouselMainAxisSize: Float,
-        tmpKeylines: List<TmpKeyline>
-    ): List<Keyline> {
-        val pivot = tmpKeylines[pivotIndex]
-        val keylines = mutableListOf<Keyline>()
-
-        val pivotCutoff: Float = when {
-            isCutoffLeft(pivot.size, pivotOffset) -> pivotOffset - (pivot.size / 2)
-            isCutoffRight(
-                pivot.size,
-                pivotOffset,
-                carouselMainAxisSize
-            ) -> (pivotOffset + (pivot.size / 2)) - carouselMainAxisSize
-
-            else -> 0f
-        }
-        keylines.add(
-            // Add the pivot keyline first
-            Keyline(
-                size = pivot.size,
-                offset = pivotOffset,
-                unadjustedOffset = pivotOffset,
-                isFocal = pivotIndex in firstFocalIndex..lastFocalIndex,
-                isAnchor = pivot.isAnchor,
-                isPivot = true,
-                cutoff = pivotCutoff
-            )
-        )
-
-        // Convert all TmpKeylines before the pivot to Keylines by calculating their offset,
-        // unadjustedOffset, and cutoff and insert them at the beginning of the keyline list,
-        // maintaining the tmpKeyline list's original order.
-        var offset = pivotOffset - (itemMainAxisSize / 2)
-        var unadjustedOffset = pivotOffset - (itemMainAxisSize / 2)
-        (pivotIndex - 1 downTo 0).forEach { originalIndex ->
-            val tmp = tmpKeylines[originalIndex]
-            val tmpOffset = offset - (tmp.size / 2)
-            val tmpUnadjustedOffset = unadjustedOffset - (itemMainAxisSize / 2)
-            val cutoff = if (isCutoffLeft(tmp.size, tmpOffset))
-                abs(tmpOffset - (tmp.size / 2)) else 0f
-            keylines.add(0,
-                Keyline(
-                    size = tmp.size,
-                    offset = tmpOffset,
-                    unadjustedOffset = tmpUnadjustedOffset,
-                    isFocal = originalIndex in firstFocalIndex..lastFocalIndex,
-                    isAnchor = tmp.isAnchor,
-                    isPivot = false,
-                    cutoff = cutoff
-                )
-            )
-
-            offset -= tmp.size
-            unadjustedOffset -= itemMainAxisSize
-        }
-
-        // Convert all TmpKeylines after the pivot to Keylines by calculating their offset,
-        // unadjustedOffset, and cutoff and inserting them at the end of the keyline list,
-        // maintaining the tmpKeyline list's original order.
-        offset = pivotOffset + (itemMainAxisSize / 2)
-        unadjustedOffset = pivotOffset + (itemMainAxisSize / 2)
-        (pivotIndex + 1 until tmpKeylines.size).forEach { originalIndex ->
-            val tmp = tmpKeylines[originalIndex]
-            val tmpOffset = offset + (tmp.size / 2)
-            val tmpUnadjustedOffset = unadjustedOffset + (itemMainAxisSize / 2)
-            val cutoff = if (isCutoffRight(tmp.size, tmpOffset, carouselMainAxisSize)) {
-                (tmpOffset + (tmp.size / 2)) - carouselMainAxisSize
-            } else {
-                0f
-            }
-            keylines.add(
-                Keyline(
-                    size = tmp.size,
-                    offset = tmpOffset,
-                    unadjustedOffset = tmpUnadjustedOffset,
-                    isFocal = originalIndex in firstFocalIndex..lastFocalIndex,
-                    isAnchor = tmp.isAnchor,
-                    isPivot = false,
-                    cutoff = cutoff
-                )
-            )
-
-            offset += tmp.size
-            unadjustedOffset += itemMainAxisSize
-        }
-
-        return keylines
-    }
-
-    /**
-     * Returns whether an item of [size] whose center is at [offset] is straddling the carousel
-     * container's left/top.
-     *
-     * This method will return false if the item is either fully visible (its left/top edge
-     * comes after the container's left/top) or fully invisible (its right/bottom edge comes before
-     * the container's left/top).
-     */
-    private fun isCutoffLeft(size: Float, offset: Float): Boolean {
-        return offset - (size / 2) < 0f && offset + (size / 2) > 0f
-    }
-
-    /**
-     * Returns whether an item of [size] whose center is at [offset] is straddling the carousel
-     * container's right/bottom edge.
-     *
-     * This method will return false if the item is either fully visible (its right/bottom edge
-     * comes before the container's right/bottom) or fully invisible (its left/top edge comes
-     * after the container's right/bottom).
-     */
-    private fun isCutoffRight(size: Float, offset: Float, carouselMainAxisSize: Float): Boolean {
-        return offset - (size / 2) < carouselMainAxisSize &&
-            offset + (size / 2) > carouselMainAxisSize
-    }
-}
-
-/**
- * Returns an interpolated [Keyline] whose values are all interpolated based on [fraction]
- * between the [start] and [end] keylines.
- */
-internal fun lerp(start: Keyline, end: Keyline, fraction: Float): Keyline {
-    return Keyline(
-        size = androidx.compose.ui.util.lerp(start.size, end.size, fraction),
-        offset = androidx.compose.ui.util.lerp(start.offset, end.offset, fraction),
-        unadjustedOffset = androidx.compose.ui.util.lerp(
-            start.unadjustedOffset,
-            end.unadjustedOffset,
-            fraction
-        ),
-        isFocal = if (fraction < .5f) start.isFocal else end.isFocal,
-        isAnchor = if (fraction < .5f) start.isAnchor else end.isAnchor,
-        isPivot = if (fraction < .5f) start.isPivot else end.isPivot,
-        cutoff = androidx.compose.ui.util.lerp(start.cutoff, end.cutoff, fraction)
-    )
-}
-
-/**
- * Returns an interpolated KeylineList between [from] and [to].
- *
- * Unlike creating a [KeylineList] using [keylineListOf], this method does not set unadjusted
- * offsets by calculating them from a pivot index. This method simply interpolates all values of
- * all keylines between the given pair.
- */
-internal fun lerp(
-    from: KeylineList,
-    to: KeylineList,
-    fraction: Float
-): KeylineList {
-    val interpolatedKeylines = from.fastMapIndexed { i, k ->
-        lerp(k, to[i], fraction)
-    }
-    return KeylineList(interpolatedKeylines)
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt
new file mode 100644
index 0000000..88a1ecc
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt
@@ -0,0 +1,528 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.carousel
+
+import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
+import kotlin.math.abs
+
+/**
+ * A structure that is fixed at a specific [offset] along a scrolling axis and
+ * defines properties of an item when its center is located at [offset].
+ *
+ * [Keyline] is the primary structure of any carousel. When multiple keylines are placed along a
+ * carousel's axis and an item is scrolled, that item will always be between two keylines. The
+ * item's distance between its two surrounding keylines can be used as a fraction to create an
+ * interpolated keyline that the item uses to set its size and translation.
+ *
+ * @param size the size an item should be in pixels when its center is at [offset]
+ * @param offset the location of the keyline along the scrolling axis and where the center of an
+ * item should be (usually translated to) when it is at [unadjustedOffset] in the end-to-end
+ * scrolling model
+ * @param unadjustedOffset the location of:445
+ * the keyline in the end-to-end scrolling model (when all
+ * items are laid out with their full size and placed end-to-end)
+ * @param isFocal whether an item at this keyline is focal or fully "viewable"
+ * @param isAnchor true if this keyline is able to be shifted within a list of keylines
+ * @param isPivot true if this is the keyline that was used to calculate all other keyline offsets
+ * and unadjusted offsets in a list
+ * @param cutoff the amount this item bleeds beyond the bounds of the container - 0 if the item
+ * is fully in-bounds or fully out-of-bounds
+ */
+internal data class Keyline(
+    val size: Float,
+    val offset: Float,
+    val unadjustedOffset: Float,
+    val isFocal: Boolean,
+    val isAnchor: Boolean,
+    val isPivot: Boolean,
+    val cutoff: Float,
+)
+
+/**
+ * A [List] of [Keyline]s with additional functionality specific to carousel.
+ *
+ * Note that [KeylineList]'s constructor should only be used when creating an interpolated
+ * KeylineList. If creating a new KeylineList - for a strategy or shifted step - prefer using the
+ * [keylineListOf] method which will handle setting all offsets and unadjusted offsets based on
+ * a pivot keyline.
+ */
+internal class KeylineList internal constructor(
+    keylines: List<Keyline>
+) : List<Keyline> by keylines {
+
+    /**
+     * Returns the index of the pivot keyline used to calculate all other keyline offsets and
+     * unadjusted offsets.
+     */
+    val pivotIndex: Int = indexOfFirst { it.isPivot }
+
+    /** Returns the keyline used to calculate all other keyline offsets and unadjusted offsets. */
+    val pivot: Keyline = get(pivotIndex)
+
+    /**
+     * Returns the index of the first non-anchor keyline or -1 if the list does not contain a
+     * non-anchor keyline.
+     */
+    val firstNonAnchorIndex: Int = indexOfFirst { !it.isAnchor }
+
+    /**
+     * Returns the first non-anchor [Keyline].
+     * @throws [NoSuchElementException] if there are no non-anchor keylines.
+     */
+    val firstNonAnchor: Keyline = get(firstNonAnchorIndex)
+
+    /**
+     * Returns the index of the last non-anchor keyline or -1 if the list does not contain a
+     * non-anchor keyline.
+     */
+    val lastNonAnchorIndex: Int = indexOfLast { !it.isAnchor }
+
+    /**
+     * Returns the last non-anchor [Keyline].
+     * @throws [NoSuchElementException] if there are no non-anchor keylines.
+     */
+    val lastNonAnchor = get(lastNonAnchorIndex)
+
+    /**
+     * Returns the index of the first focal keyline or -1 if the list does not contain a
+     * focal keyline.
+     */
+    val firstFocalIndex = indexOfFirst { it.isFocal }
+
+    /**
+     * Returns the first focal [Keyline].
+     * @throws [NoSuchElementException] if there are no focal keylines.
+     */
+    val firstFocal: Keyline = getOrNull(firstFocalIndex)
+        ?: throw NoSuchElementException("All KeylineLists must have at least one focal keyline")
+
+    /**
+     * Returns the index of the last focal keyline or -1 if the list does not contain a
+     * focal keyline.
+     */
+    val lastFocalIndex: Int = indexOfLast { it.isFocal }
+
+    /**
+     * Returns the last focal [Keyline].
+     * @throws [NoSuchElementException] if there are no focal keylines.
+     */
+    val lastFocal = getOrNull(lastFocalIndex)
+        ?: throw NoSuchElementException("All KeylineLists must have at least one focal keyline")
+
+    /**
+     * Returns true if the first focal item's left/top is within the visible bounds of the container
+     * and is the first non-anchor keyline.
+     *
+     * When this is true, it means the focal range cannot be shifted left/top or is shifted as
+     * far left/top as possible. When this is false, there are keylines that can be swapped to
+     * shift the first focal item closer to the left/top of the container while still remaining
+     * visible.
+     */
+    fun isFirstFocalItemAtStartOfContainer(): Boolean {
+        val firstFocalLeft = firstFocal.offset - (firstFocal.size / 2)
+        return firstFocalLeft >= 0 && firstFocal == firstNonAnchor
+    }
+
+    /**
+     * Returns true if the last focal item's right/bottom is within the visible bounds of the
+     * container and is the last non-anchor keyline.
+     *
+     * When this is true, it means the focal range cannot be shifted right/bottom or is shifted as
+     * far right/bottom as possible. When this is false, there are keylines that can be swapped to
+     * shift the last focal item closer to the right/bottom of the container while still remaining
+     * visible.
+     */
+    fun isLastFocalItemAtEndOfContainer(carouselMainAxisSize: Float): Boolean {
+        val lastFocalRight = lastFocal.offset + (lastFocal.size / 2)
+        return lastFocalRight <= carouselMainAxisSize && lastFocal == lastNonAnchor
+    }
+
+    /**
+     * Returns the index of the first keyline after the focal range where the keyline's size is
+     * equal to [size] or the last index if no keyline is found.
+     *
+     * This is useful when moving keylines from one side of the focal range to the other (shifting).
+     * Find an index on the other side of the focal range where after moving the keyline, the
+     * keyline list will retain its original visual balance.
+     */
+    fun firstIndexAfterFocalRangeWithSize(size: Float): Int {
+        val from = lastFocalIndex
+        val to = lastIndex
+        return (from..to).firstOrNull { i -> this[i].size == size } ?: lastIndex
+    }
+
+    /**
+     * Returns the index of the last keyline before the focal range where the keyline's size is
+     * equal to [size] or 0 if no keyline is found.
+     *
+     * This is useful when moving keylines from one side of the focal range to the other (shifting).
+     * Find an index on the other side of the focal range where after moving the keyline, the
+     * keyline list will retain its original visual balance.
+     */
+    fun lastIndexBeforeFocalRangeWithSize(size: Float): Int {
+        val from = firstFocalIndex - 1
+        val to = 0
+        return (from downTo to).firstOrNull { i -> this[i].size == size } ?: to
+    }
+
+    /**
+     * Returns the last [Keyline] with an unadjustedOffset that is less than [unadjustedOffset] or
+     * the first keyline if none is found.
+     */
+    fun getKeylineBefore(unadjustedOffset: Float): Keyline {
+        for (index in indices.reversed()) {
+            val k = get(index)
+            if (k.unadjustedOffset < unadjustedOffset) {
+                return k
+            }
+        }
+
+        return first()
+    }
+
+    /**
+     * Returns the first [Keyline] with an unadjustedOffset that is greater than
+     * [unadjustedOffset] or the last keyline if none is found.
+     */
+    fun getKeylineAfter(unadjustedOffset: Float): Keyline {
+        return fastFirstOrNull { it.unadjustedOffset >= unadjustedOffset } ?: last()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is KeylineList) return false
+        if (size != other.size) return false
+
+        fastForEachIndexed { i, keyline ->
+            if (keyline != other[i]) return false
+        }
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = 0
+        fastForEach { keyline -> result += 31 * keyline.hashCode() }
+        return result
+    }
+}
+
+/**
+ * Returns a [KeylineList] by aligning the focal range relative to the carousel container.
+ */
+internal fun keylineListOf(
+    carouselMainAxisSize: Float,
+    carouselAlignment: CarouselAlignment,
+    keylines: KeylineListScope.() -> Unit
+): KeylineList {
+    val keylineListScope = KeylineListScopeImpl()
+    keylines.invoke(keylineListScope)
+    return keylineListScope.createWithAlignment(carouselMainAxisSize, carouselAlignment)
+}
+
+/**
+ * Returns a [KeylineList] by using a single pivot keyline to calculate the offset and unadjusted
+ * offset of all keylines in the list.
+ */
+internal fun keylineListOf(
+    carouselMainAxisSize: Float,
+    pivotIndex: Int,
+    pivotOffset: Float,
+    keylines: KeylineListScope.() -> Unit
+): KeylineList {
+    val keylineListScope = KeylineListScopeImpl()
+    keylines.invoke(keylineListScope)
+    return keylineListScope.createWithPivot(carouselMainAxisSize, pivotIndex, pivotOffset)
+}
+
+/** Receiver scope for creating a [KeylineList] using [keylineListOf] */
+internal interface KeylineListScope {
+
+    /**
+     * Adds a keyline to the resulting [KeylineList].
+     *
+     * Note that keylines are added in the order they will appear.
+     *
+     * @param size the size of an item in pixels at this keyline
+     * @param isAnchor true if this keyline should not be shifted - usually the first and last fully
+     * off-screen keylines
+     */
+    fun add(size: Float, isAnchor: Boolean = false)
+}
+
+private class KeylineListScopeImpl : KeylineListScope {
+
+    private data class TmpKeyline(val size: Float, val isAnchor: Boolean)
+
+    private var firstFocalIndex: Int = -1
+    private var focalItemSize: Float = 0f
+    private var pivotIndex: Int = -1
+    private var pivotOffset: Float = 0f
+    private val tmpKeylines = mutableListOf<TmpKeyline>()
+    override fun add(size: Float, isAnchor: Boolean) {
+        tmpKeylines.add(TmpKeyline(size, isAnchor))
+        // Save the first "focal" item by looking for the first index of the largest item added
+        // to the list. The last focal item index will be found when `create` is called by starting
+        // from firstFocalIndex and incrementing the index until the next item's size does not
+        // equal focalItemSize.
+        if (size > focalItemSize) {
+            firstFocalIndex = tmpKeylines.lastIndex
+            focalItemSize = size
+        }
+    }
+
+    fun createWithPivot(
+        carouselMainAxisSize: Float,
+        pivotIndex: Int,
+        pivotOffset: Float
+    ): KeylineList {
+        val keylines = createKeylinesWithPivot(
+            pivotIndex,
+            pivotOffset,
+            firstFocalIndex,
+            findLastFocalIndex(),
+            itemMainAxisSize = focalItemSize,
+            carouselMainAxisSize = carouselMainAxisSize,
+            tmpKeylines
+        )
+        return KeylineList(keylines)
+    }
+
+    fun createWithAlignment(
+        carouselMainAxisSize: Float,
+        carouselAlignment: CarouselAlignment
+    ): KeylineList {
+        val lastFocalIndex = findLastFocalIndex()
+        val focalItemCount = lastFocalIndex - firstFocalIndex
+
+        pivotIndex = firstFocalIndex
+        pivotOffset = when (carouselAlignment) {
+            CarouselAlignment.Center -> {
+                (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount)
+            }
+            CarouselAlignment.End -> carouselMainAxisSize - (focalItemSize / 2)
+            // Else covers and defaults to CarouselAlignment.Start
+            else -> focalItemSize / 2
+        }
+
+        val keylines = createKeylinesWithPivot(
+            pivotIndex,
+            pivotOffset,
+            firstFocalIndex,
+            lastFocalIndex,
+            itemMainAxisSize = focalItemSize,
+            carouselMainAxisSize = carouselMainAxisSize,
+            tmpKeylines
+        )
+        return KeylineList(keylines)
+    }
+
+    private fun findLastFocalIndex(): Int {
+        // Find the last focal index. Start from the first focal index and walk up the indices
+        // while items remain the same size as the first focal item size - finding a contiguous
+        // range of indices where item size is equal to focalItemSize.
+        var lastFocalIndex = firstFocalIndex
+        while (lastFocalIndex < tmpKeylines.lastIndex &&
+            tmpKeylines[lastFocalIndex + 1].size == focalItemSize) {
+            lastFocalIndex ++
+        }
+        return lastFocalIndex
+    }
+
+    /**
+     * Converts a list of [TmpKeyline] to a list of [Keyline]s whose offset, unadjusted offset,
+     * and cutoff are calculated from a pivot.
+     *
+     * Pivoting is useful when aligning the entire arrangement relative to the scrolling container.
+     * When creating a keyline list with the first focal keyline aligned to the start of the
+     * container, use the first focal item as the pivot and set the pivot offset to where that first
+     * focal item's center should be placed (carouselStart + (item size / 2)). All keylines
+     * before and after the pivot will have their offset, unadjusted offset, and cutoff calculated
+     * based on the pivot offset. When shifting keylines and moving the carousel's alignment from
+     * start to end, use setPivot to align the last focal keyline to the end of the container.
+     *
+     * @param pivotIndex the index of the keyline from [tmpKeylines] that is used to align the
+     * entire arrangement
+     * @param pivotOffset the offset along the scrolling axis where the pivot keyline should
+     * be placed and where keylines before and after will have their offset, unadjustedOffset, and
+     * cutoff calculated from
+     * @param firstFocalIndex the index of the first focal item in the [tmpKeylines] list
+     * @param lastFocalIndex the index of the last focal item in the [tmpKeylines] list
+     * @param itemMainAxisSize the size of focal, or fully unmasked/clipped, items
+     * @param carouselMainAxisSize the size of the carousel container in the scrolling axis
+     */
+    private fun createKeylinesWithPivot(
+        pivotIndex: Int,
+        pivotOffset: Float,
+        firstFocalIndex: Int,
+        lastFocalIndex: Int,
+        itemMainAxisSize: Float,
+        carouselMainAxisSize: Float,
+        tmpKeylines: List<TmpKeyline>
+    ): List<Keyline> {
+        val pivot = tmpKeylines[pivotIndex]
+        val keylines = mutableListOf<Keyline>()
+
+        val pivotCutoff: Float = when {
+            isCutoffLeft(pivot.size, pivotOffset) -> pivotOffset - (pivot.size / 2)
+            isCutoffRight(
+                pivot.size,
+                pivotOffset,
+                carouselMainAxisSize
+            ) -> (pivotOffset + (pivot.size / 2)) - carouselMainAxisSize
+
+            else -> 0f
+        }
+        keylines.add(
+            // Add the pivot keyline first
+            Keyline(
+                size = pivot.size,
+                offset = pivotOffset,
+                unadjustedOffset = pivotOffset,
+                isFocal = pivotIndex in firstFocalIndex..lastFocalIndex,
+                isAnchor = pivot.isAnchor,
+                isPivot = true,
+                cutoff = pivotCutoff
+            )
+        )
+
+        // Convert all TmpKeylines before the pivot to Keylines by calculating their offset,
+        // unadjustedOffset, and cutoff and insert them at the beginning of the keyline list,
+        // maintaining the tmpKeyline list's original order.
+        var offset = pivotOffset - (itemMainAxisSize / 2)
+        var unadjustedOffset = pivotOffset - (itemMainAxisSize / 2)
+        (pivotIndex - 1 downTo 0).forEach { originalIndex ->
+            val tmp = tmpKeylines[originalIndex]
+            val tmpOffset = offset - (tmp.size / 2)
+            val tmpUnadjustedOffset = unadjustedOffset - (itemMainAxisSize / 2)
+            val cutoff = if (isCutoffLeft(tmp.size, tmpOffset))
+                abs(tmpOffset - (tmp.size / 2)) else 0f
+            keylines.add(0,
+                Keyline(
+                    size = tmp.size,
+                    offset = tmpOffset,
+                    unadjustedOffset = tmpUnadjustedOffset,
+                    isFocal = originalIndex in firstFocalIndex..lastFocalIndex,
+                    isAnchor = tmp.isAnchor,
+                    isPivot = false,
+                    cutoff = cutoff
+                )
+            )
+
+            offset -= tmp.size
+            unadjustedOffset -= itemMainAxisSize
+        }
+
+        // Convert all TmpKeylines after the pivot to Keylines by calculating their offset,
+        // unadjustedOffset, and cutoff and inserting them at the end of the keyline list,
+        // maintaining the tmpKeyline list's original order.
+        offset = pivotOffset + (itemMainAxisSize / 2)
+        unadjustedOffset = pivotOffset + (itemMainAxisSize / 2)
+        (pivotIndex + 1 until tmpKeylines.size).forEach { originalIndex ->
+            val tmp = tmpKeylines[originalIndex]
+            val tmpOffset = offset + (tmp.size / 2)
+            val tmpUnadjustedOffset = unadjustedOffset + (itemMainAxisSize / 2)
+            val cutoff = if (isCutoffRight(tmp.size, tmpOffset, carouselMainAxisSize)) {
+                (tmpOffset + (tmp.size / 2)) - carouselMainAxisSize
+            } else {
+                0f
+            }
+            keylines.add(
+                Keyline(
+                    size = tmp.size,
+                    offset = tmpOffset,
+                    unadjustedOffset = tmpUnadjustedOffset,
+                    isFocal = originalIndex in firstFocalIndex..lastFocalIndex,
+                    isAnchor = tmp.isAnchor,
+                    isPivot = false,
+                    cutoff = cutoff
+                )
+            )
+
+            offset += tmp.size
+            unadjustedOffset += itemMainAxisSize
+        }
+
+        return keylines
+    }
+
+    /**
+     * Returns whether an item of [size] whose center is at [offset] is straddling the carousel
+     * container's left/top.
+     *
+     * This method will return false if the item is either fully visible (its left/top edge
+     * comes after the container's left/top) or fully invisible (its right/bottom edge comes before
+     * the container's left/top).
+     */
+    private fun isCutoffLeft(size: Float, offset: Float): Boolean {
+        return offset - (size / 2) < 0f && offset + (size / 2) > 0f
+    }
+
+    /**
+     * Returns whether an item of [size] whose center is at [offset] is straddling the carousel
+     * container's right/bottom edge.
+     *
+     * This method will return false if the item is either fully visible (its right/bottom edge
+     * comes before the container's right/bottom) or fully invisible (its left/top edge comes
+     * after the container's right/bottom).
+     */
+    private fun isCutoffRight(size: Float, offset: Float, carouselMainAxisSize: Float): Boolean {
+        return offset - (size / 2) < carouselMainAxisSize &&
+            offset + (size / 2) > carouselMainAxisSize
+    }
+}
+
+/**
+ * Returns an interpolated [Keyline] whose values are all interpolated based on [fraction]
+ * between the [start] and [end] keylines.
+ */
+internal fun lerp(start: Keyline, end: Keyline, fraction: Float): Keyline {
+    return Keyline(
+        size = androidx.compose.ui.util.lerp(start.size, end.size, fraction),
+        offset = androidx.compose.ui.util.lerp(start.offset, end.offset, fraction),
+        unadjustedOffset = androidx.compose.ui.util.lerp(
+            start.unadjustedOffset,
+            end.unadjustedOffset,
+            fraction
+        ),
+        isFocal = if (fraction < .5f) start.isFocal else end.isFocal,
+        isAnchor = if (fraction < .5f) start.isAnchor else end.isAnchor,
+        isPivot = if (fraction < .5f) start.isPivot else end.isPivot,
+        cutoff = androidx.compose.ui.util.lerp(start.cutoff, end.cutoff, fraction)
+    )
+}
+
+/**
+ * Returns an interpolated KeylineList between [from] and [to].
+ *
+ * Unlike creating a [KeylineList] using [keylineListOf], this method does not set unadjusted
+ * offsets by calculating them from a pivot index. This method simply interpolates all values of
+ * all keylines between the given pair.
+ */
+internal fun lerp(
+    from: KeylineList,
+    to: KeylineList,
+    fraction: Float
+): KeylineList {
+    val interpolatedKeylines = from.fastMapIndexed { i, k ->
+        lerp(k, to[i], fraction)
+    }
+    return KeylineList(interpolatedKeylines)
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
new file mode 100644
index 0000000..7d281be
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.material3.carousel
+
+import androidx.compose.ui.unit.Density
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Creates a list of keylines that arranges items into a multi-browse configuration.
+ *
+ * Note that this function may adjust the size of large items. In order to ensure large, medium,
+ * and small items fit perfectly into the available space and are numbered/arranged in a
+ * visually pleasing and opinionated way, this strategy finds the nearest number of large items that
+ * will fit into an approved arrangement that requires the least amount of size adjustment
+ * necessary.
+ *
+ * For more information, see <a href="https://material.io/components/carousel/overview">design
+ * guidelines</a>.
+ *
+ * @param density The [Density] object that provides pixel density information of the device
+ * @param carouselMainAxisSize The carousel container's pixel size in the main scrolling axis
+ * @param preferredItemSize the desired size of large items, in pixels, in the main scrolling axis
+ * @param itemSpacing the spacing between items in pixels
+ * @param minSmallSize the minimum allowable size of small items in pixels
+ * @param maxSmallSize the maximum allowable size of small items in pixels
+ */
+internal fun multiBrowseKeylineList(
+    density: Density,
+    carouselMainAxisSize: Float,
+    preferredItemSize: Float,
+    itemSpacing: Float,
+    minSmallSize: Float = with(density) { StrategyDefaults.MinSmallSize.toPx() },
+    maxSmallSize: Float = with(density) { StrategyDefaults.MaxSmallSize.toPx() },
+): KeylineList? {
+    if (carouselMainAxisSize == 0f || preferredItemSize == 0f) {
+        return null
+    }
+
+    var smallCounts: IntArray = intArrayOf(1)
+    val mediumCounts: IntArray = intArrayOf(1, 0)
+
+    val targetLargeSize: Float = min(preferredItemSize + itemSpacing, carouselMainAxisSize)
+    // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size
+    // of the large item and medium items are sized between large and small items. Clamp the
+    // small target size within our min-max range and as close to 1/3 of the target large item
+    // size as possible.
+    val targetSmallSize: Float = (targetLargeSize / 3f + itemSpacing).coerceIn(
+        minSmallSize + itemSpacing,
+        maxSmallSize + itemSpacing
+    )
+    val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
+
+    if (carouselMainAxisSize < minSmallSize * 2) {
+        // If the available space is too small to fit a large item and small item (where a large
+        // item is bigger than a small item), allow arrangements with
+        // no small items.
+        smallCounts = intArrayOf(0)
+    }
+
+    // Find the minimum space left for large items after filling the carousel with the most
+    // permissible medium and small items to determine a plausible minimum large count.
+    val minAvailableLargeSpace = carouselMainAxisSize - targetMediumSize * mediumCounts.max() -
+        maxSmallSize * smallCounts.max()
+    val minLargeCount = max(
+        1,
+        floor(minAvailableLargeSpace / targetLargeSize).toInt())
+    val maxLargeCount = ceil(carouselMainAxisSize / targetLargeSize).toInt()
+
+    val largeCounts = IntArray(maxLargeCount - minLargeCount + 1) { maxLargeCount - it }
+    val anchorSize = with(density) { StrategyDefaults.AnchorSize.toPx() }
+    val arrangement = Arrangement.findLowestCostArrangement(
+        availableSpace = carouselMainAxisSize,
+        targetSmallSize = targetSmallSize,
+        minSmallSize = minSmallSize,
+        maxSmallSize = maxSmallSize,
+        smallCounts = smallCounts,
+        targetMediumSize = targetMediumSize,
+        mediumCounts = mediumCounts,
+        targetLargeSize = targetLargeSize,
+        largeCounts = largeCounts,
+    )
+
+    return if (arrangement == null) {
+        null
+    } else {
+        createStartAlignedKeylineList(
+            carouselMainAxisSize = carouselMainAxisSize,
+            anchorSize = anchorSize,
+            arrangement = arrangement
+        )
+    }
+}
+
+internal fun createStartAlignedKeylineList(
+    carouselMainAxisSize: Float,
+    anchorSize: Float,
+    arrangement: Arrangement
+): KeylineList {
+    return keylineListOf(carouselMainAxisSize, CarouselAlignment.Start) {
+        add(anchorSize, isAnchor = true)
+
+        repeat(arrangement.largeCount) { add(arrangement.largeSize) }
+        repeat(arrangement.mediumCount) { add(arrangement.mediumSize) }
+        repeat(arrangement.smallCount) { add(arrangement.smallSize) }
+
+        add(anchorSize, isAnchor = true)
+    }
+}
+
+/**
+ * Creates a list of keylines that arranges items into the 'uncontained' configuration. This
+ * configuration lays out as many items as it can in the given item size without getting cut off,
+ * and with the remaining space adds a cut off item with size constraints to ensure enough motion
+ * when scrolling off-screen.
+ *
+ * For more information, see <a href="https://material.io/components/carousel/overview">design
+ * guidelines</a>.
+ *
+ * @param density The [Density] object that provides pixel density information of the device
+ * @param carouselMainAxisSize The carousel container's pixel size in the main scrolling axis
+ * @param itemSize the size of large items, in pixels, in the main scrolling axis
+ * @param itemSpacing the spacing between items in pixels
+ */
+internal fun uncontainedKeylineList(
+    density: Density,
+    carouselMainAxisSize: Float,
+    itemSize: Float,
+    itemSpacing: Float,
+): KeylineList? {
+    if (carouselMainAxisSize == 0f || itemSize == 0f) {
+        return null
+    }
+
+    val largeItemSize = min(itemSize + itemSpacing, carouselMainAxisSize)
+    // Calculate how much space there is remaining after squeezing in as many large items as we can.
+    val largeCount = max(1, floor(carouselMainAxisSize / largeItemSize).toInt())
+    val remainingSpace: Float = carouselMainAxisSize - largeCount * largeItemSize
+
+    val mediumCount = if (remainingSpace > 0) 1 else 0
+
+    val defaultAnchorSize = with(density) { StrategyDefaults.AnchorSize.toPx() }
+    val mediumItemSize = calculateMediumChildSize(
+        minimumMediumSize = defaultAnchorSize,
+        largeItemSize = largeItemSize,
+        remainingSpace = remainingSpace)
+    val arrangement = Arrangement(
+        0,
+        0F,
+        0,
+        mediumItemSize,
+        mediumCount,
+        largeItemSize,
+        largeCount
+    )
+
+    val xSmallSize = min(defaultAnchorSize, itemSize)
+    // Make the anchor size half the cut off item size to make the motion at the left closer
+    // to the right where the cut off is.
+    val anchorSize: Float = max(xSmallSize, mediumItemSize * 0.5f)
+    return createStartAlignedKeylineList(
+        carouselMainAxisSize = carouselMainAxisSize,
+        anchorSize = anchorSize,
+        arrangement = arrangement)
+}
+
+/**
+ * Calculates a size of a medium item in the carousel that is not bigger than the large item
+ * size, and arbitrarily chooses a size small enough such that there is a size disparity between
+ * the medium and large sizes, but large enough to have a sufficient percentage cut off.
+ */
+private fun calculateMediumChildSize(
+    minimumMediumSize: Float,
+    largeItemSize: Float,
+    remainingSpace: Float
+): Float {
+    // With the remaining space, we want to add a 'medium' size item that gets sufficiently
+    // cut off. Ideally, it is large enough such that a third of the item is cut off, meaning
+    // that it is 1.5x the remaining space.
+    var mediumItemSize = minimumMediumSize
+    val sizeWithThirdCutOff = remainingSpace * 1.5f
+    mediumItemSize = max(sizeWithThirdCutOff, mediumItemSize)
+
+    // If the medium child is larger than the threshold percentage of the large child size,
+    // it's too similar and won't create sufficient motion when scrolling items between the large
+    // items and the medium item.
+    val largeItemThreshold: Float =
+        largeItemSize * StrategyDefaults.MediumLargeItemDiffThreshold
+    if (mediumItemSize > largeItemThreshold) {
+        // Choose whichever is bigger between the maximum threshold of the medium child size, or
+        // a size such that only 20% of the space is cut off.
+        val sizeWithFifthCutOff = remainingSpace * 1.2f
+        mediumItemSize = max(largeItemThreshold, sizeWithFifthCutOff)
+    }
+    return mediumItemSize
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyProvider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyProvider.kt
deleted file mode 100644
index f8d4db6..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/MultiBrowseStrategyProvider.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.material3.carousel
-
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.ceil
-import kotlin.math.floor
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * A [StrategyProvider] that provides the multi-browse strategy, which fits large, medium, and small
- * items into a layout for quick browsing of multiple items at once.
- *
- * Note that this strategy may adjust the size of large items. In order to ensure large, medium,
- * and small items fit perfectly into the available space and are numbered/arranged in a
- * visually pleasing and opinionated way, this strategy finds the nearest number of large items that
- * will fit into an approved arrangement that requires the least amount of size adjustment
- * necessary.
- *
- * For more information, see <a href="https://material.io/components/carousel/overview">design
- * guidelines</a>.
- */
-internal class MultiBrowseStrategyProvider(
-    private val targetLargeItemMainAxisSize: Dp,
-    private val minSmallSize: Dp = StrategyDefaults.minSmallSize,
-    private val maxSmallSize: Dp = StrategyDefaults.maxSmallSize
-) :
-    StrategyProvider() {
-
-    override fun createStrategy(
-        density: Density,
-        carouselMainAxisSize: Float,
-        itemSpacing: Int,
-    ): Strategy? {
-        if (carouselMainAxisSize == 0f || targetLargeItemMainAxisSize == 0.dp) {
-            return null
-        }
-
-        val targetLargeItemSize = with(density) { targetLargeItemMainAxisSize.toPx() }
-        val minSmallItemSize = with(density) { minSmallSize.toPx() }
-        val maxSmallItemSize = with(density) { maxSmallSize.toPx() }
-        var smallCounts: IntArray = intArrayOf(1)
-        val mediumCounts: IntArray = intArrayOf(1, 0)
-
-        val targetLargeSize: Float = min(targetLargeItemSize + itemSpacing, carouselMainAxisSize)
-        // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size
-        // of the large item and medium items are sized between large and small items. Clamp the
-        // small target size within our min-max range and as close to 1/3 of the target large item
-        // size as possible.
-        val targetSmallSize: Float = (targetLargeItemSize / 3f + itemSpacing).coerceIn(
-            minSmallItemSize + itemSpacing,
-            maxSmallItemSize + itemSpacing
-        )
-        val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
-
-        if (carouselMainAxisSize < minSmallItemSize * 2) {
-            // If the available space is too small to fit a large item and small item (where a large
-            // item is bigger than a small item), allow arrangements with
-            // no small items.
-            smallCounts = intArrayOf(0)
-        }
-
-        // Find the minimum space left for large items after filling the carousel with the most
-        // permissible medium and small items to determine a plausible minimum large count.
-        val minAvailableLargeSpace = carouselMainAxisSize - targetMediumSize * mediumCounts.max() -
-            maxSmallItemSize * smallCounts.max()
-        val minLargeCount = max(
-            1,
-            floor(minAvailableLargeSpace / targetLargeSize).toInt())
-        val maxLargeCount = ceil(carouselMainAxisSize / targetLargeSize).toInt()
-
-        val largeCounts = IntArray(maxLargeCount - minLargeCount + 1) { maxLargeCount - it }
-        val arrangement = Arrangement.findLowestCostArrangement(
-            availableSpace = carouselMainAxisSize,
-            targetSmallSize = targetSmallSize,
-            minSmallSize = minSmallItemSize,
-            maxSmallSize = maxSmallItemSize,
-            smallCounts = smallCounts,
-            targetMediumSize = targetMediumSize,
-            mediumCounts = mediumCounts,
-            targetLargeSize = targetLargeSize,
-            largeCounts = largeCounts,
-        ) ?: return null
-
-        return createStartAlignedStrategy(
-            availableSpace = carouselMainAxisSize,
-            arrangement = arrangement,
-            anchorSize = with(density) { StrategyDefaults.anchorSize.toPx() }
-        )
-    }
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
index 484c6ad..c15b8e1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
@@ -19,7 +19,6 @@
 import androidx.annotation.VisibleForTesting
 import androidx.collection.FloatList
 import androidx.collection.mutableFloatListOf
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.lerp
@@ -29,104 +28,124 @@
  * Contains default values used across Strategies
  */
 internal object StrategyDefaults {
-    val minSmallSize = 40.dp
-    val maxSmallSize = 56.dp
-    val anchorSize = 10.dp
+    val MinSmallSize = 40.dp
+    val MaxSmallSize = 56.dp
+    val AnchorSize = 10.dp
+    const val MediumLargeItemDiffThreshold = 0.85f
 }
 
 /**
- * Helper method to create a default start-aligned [Strategy] contained within the bounds of
- * [availableSpace] and based on the given [Arrangement].
+ * A class responsible for supplying carousel with a [KeylineList] that is corrected for scroll
+ * offset, layout direction, and snapping behaviors.
  *
- * @param availableSpace the available space to contain the [Strategy] within
- * @param arrangement the arrangement containing information on the sizes and counts of the
- * items in the [Strategy].
- * @param anchorSize the size that the anchor keylines should be in the strategy. The smaller
- * this is, the more the item will shrink as it moves off-screen.
+ * Strategy is created using a [keylineList] block that returns a default [KeylineList]. This is
+ * the list of keylines that define how items should be arranged, from left-to-right,
+ * to achieve the carousel's desired appearance. For example, a start-aligned large
+ * item, followed by a medium and a small item for a multi-browse carousel. Or a small item,
+ * a center-aligned large item, and a small item for a centered hero carousel. Strategy will
+ * use the [KeylineList] returned from the [keylineList] block to then derive new scroll, and layout
+ * direction-aware [KeylineList]s and supply them for use by carousel. For example, when a
+ * device is running in a right-to-left layout direction, Strategy will handle reversing the default
+ * [KeylineList]. Or if the default keylines use a center-aligned large item, Strategy will shift
+ * the large item to the start or end of the screen when the carousel is scrolled to the start or
+ * end of the list, letting all items become large without having them detach from the edges of
+ * the scroll container.
+ *
+ * @param keylineList a function that generates default keylines for this strategy based on the
+ * carousel's available space. This function will be called anytime availableSpace changes.
  */
-internal fun createStartAlignedStrategy(
-    availableSpace: Float,
-    arrangement: Arrangement,
-    anchorSize: Float,
-): Strategy {
-    val keylineList = keylineListOf(availableSpace, CarouselAlignment.Start) {
-        add(anchorSize, isAnchor = true)
-
-        repeat(arrangement.largeCount) { add(arrangement.largeSize) }
-        repeat(arrangement.mediumCount) { add(arrangement.mediumSize) }
-        repeat(arrangement.smallCount) { add(arrangement.smallSize) }
-
-        add(anchorSize, isAnchor = true)
-    }
-
-    return Strategy.create(availableSpace, keylineList)
-}
-
-/**
- * A class that provides [Strategy] instances to a scrollable component.
- *
- * [StrategyProvider.createStrategy] will be called any time properties which affect a carousel's
- * arrangement change. It is the implementation's responsibility to create an arrangement for the
- * given parameters and return a [Strategy] by calling [Strategy.create].
- */
-internal sealed class StrategyProvider() {
-
-    /**
-     * Create and return a new [Strategy] for the given carousel size.
-     *
-     * @param density The current density value
-     * @param carouselMainAxisSize the size of the carousel in the main axis in pixels
-     * @param itemSpacing The spacing in between the items that are not a part of the item size
-     */
-    internal abstract fun createStrategy(
-        density: Density,
-        carouselMainAxisSize: Float,
-        itemSpacing: Int,
-    ): Strategy?
-}
-
-/**
- * A class which supplies carousel with the appropriate [KeylineList] for any given scroll offset.
- *
- * All items in a carousel need the opportunity to pass through the focal keyline range. Depending
- * on where the focal range is located within the scrolling container, some items, like those at
- * the beginning or end of the list, might not reach the focal range. To account for this,
- * [Strategy] manages shifting the focal keylines to the beginning of the list when scrolled an
- * offset of 0 and the end of the list when scrolled to the list's max offset. [StrategyProvider]
- * needs only to create a "default" [KeylineList] (where keylines should be placed when scrolling
- * in the middle of the list) and call [Strategy.create] to have [Strategy] generate the steps
- * needed to move the focal range to the start and end of the scroll container. When scrolling, the
- * scrollable component can access the correct [KeylineList] for any given offset using
- * [getKeylineListForScrollOffset].
- *
- * @param defaultKeylines the [KeylineList] used when anywhere in the center of the list
- * @param startKeylineSteps a list of [KeylineList]s that will be moved through when approaching
- * the beginning of the list
- * @param endKeylineSteps a list o [KeylineList]s that will be moved through when appraoching
- * the end of the list
- * @param startShiftDistance the scroll distance it should take to move through all the steps in
- * [startKeylineSteps]
- * @param endShiftDistance the scroll distance it should take to move through all the steps in the
- * [endKeylineSteps]
- * @param startShiftPoints a list of floats between 0-1 that define the percentage of shift distance
- * at which the start keyline step at the corresponding index should be used
- * @param endShiftPoints a list of floats between 0-1 that define the percentage of shift distance
- * at which the end keyline step at the corresponding index should be used
- */
-internal class Strategy private constructor(
-    private val defaultKeylines: KeylineList,
-    private val startKeylineSteps: List<KeylineList>,
-    private val endKeylineSteps: List<KeylineList>,
-    private val startShiftDistance: Float,
-    private val endShiftDistance: Float,
-    private val startShiftPoints: FloatList,
-    private val endShiftPoints: FloatList
+internal class Strategy(
+    private val keylineList: (availableSpace: Float) -> KeylineList?
 ) {
 
+    /** The keylines generated from the [keylineList] block. */
+    private lateinit var defaultKeylines: KeylineList
     /**
-     * The size of items when in focus and fully unmasked.
+     * A list of [KeylineList]s that move the focal range from its position in [defaultKeylines]
+     * to the start of the carousel container, one keyline at a time.
      */
-    internal val itemMainAxisSize = defaultKeylines.firstFocal.size
+    private lateinit var startKeylineSteps: List<KeylineList>
+    /**
+     * A list of [KeylineList]s that move the focal range from its position in [defaultKeylines]
+     * to the end of the carousel container, one keyline at a time.
+     */
+    private lateinit var endKeylineSteps: List<KeylineList>
+    /** The scroll distance needed to move through all steps in [startKeylineSteps]. */
+    private var startShiftDistance: Float = 0f
+    /** The scroll distance needed to move through all steps in [endKeylineSteps]. */
+    private var endShiftDistance: Float = 0f
+    /**
+     * A list of floats whose index aligns with a [KeylineList] from [startKeylineSteps] and
+     * whose value is the percentage of [startShiftDistance] that should be scrolled when the
+     * start step is used.
+     */
+    private lateinit var startShiftPoints: FloatList
+    /**
+     * A list of floats whose index aligns with a [KeylineList] from [endKeylineSteps] and
+     * whose value is the percentage of [endShiftDistance] that should be scrolled when the
+     * end step is used.
+     */
+    private lateinit var endShiftPoints: FloatList
+
+    /** The available space in the main axis used in the most recent call to [apply]. */
+    private var availableSpace: Float = 0f
+    /** The size of items when in focus and fully unmasked. */
+    internal var itemMainAxisSize: Float = 0f
+
+    /**
+     * Whether this strategy holds a valid set of keylines that are ready for use.
+     *
+     * This is true after [apply] has been called and the [keylineList] block has returned a
+     * non-null [KeylineList].
+     */
+    fun isValid() = itemMainAxisSize != 0f
+
+    /**
+     * Updates this [Strategy] based on carousel's main axis available space.
+     *
+     * This method must be called before a strategy can be used by carousel.
+     *
+     * @param availableSpace the size of the carousel container in scrolling axis
+     */
+    internal fun apply(availableSpace: Float): Strategy {
+        // Skip computing new keylines and updating this strategy if
+        // available space has not changed.
+        if (this.availableSpace == availableSpace) {
+            return this
+        }
+
+        val keylineList = keylineList.invoke(availableSpace) ?: return this
+        val startKeylineSteps = getStartKeylineSteps(keylineList, availableSpace)
+        val endKeylineSteps =
+            getEndKeylineSteps(keylineList, availableSpace)
+
+        // TODO: Update this to use the first/last focal keylines to calculate shift?
+        val startShiftDistance = startKeylineSteps.last().first().unadjustedOffset -
+            keylineList.first().unadjustedOffset
+        val endShiftDistance = keylineList.last().unadjustedOffset -
+            endKeylineSteps.last().last().unadjustedOffset
+
+        this.defaultKeylines = keylineList
+        this.defaultKeylines = keylineList
+        this.startKeylineSteps = startKeylineSteps
+        this.endKeylineSteps = endKeylineSteps
+        this.startShiftDistance = startShiftDistance
+        this.endShiftDistance = endShiftDistance
+        this.startShiftPoints = getStepInterpolationPoints(
+            startShiftDistance,
+            startKeylineSteps,
+            true
+        )
+        this.endShiftPoints = getStepInterpolationPoints(
+            endShiftDistance,
+            endKeylineSteps,
+            false
+        )
+        this.availableSpace = availableSpace
+        this.itemMainAxisSize = defaultKeylines.firstFocal.size
+
+        return this
+    }
 
     /**
      * Returns the [KeylineList] that should be used for the current [scrollOffset].
@@ -207,57 +226,43 @@
         return startKeylineSteps.last()
     }
 
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Strategy) return false
+        // If neither strategy is valid, they should be considered equal
+        if (!isValid() && !other.isValid()) return true
+
+        if (isValid() != other.isValid()) return false
+        if (availableSpace != other.availableSpace) return false
+        if (itemMainAxisSize != other.itemMainAxisSize) return false
+        if (startShiftDistance != other.startShiftDistance) return false
+        if (endShiftDistance != other.endShiftDistance) return false
+        if (startShiftPoints != other.startShiftPoints) return false
+        if (endShiftPoints != other.endShiftPoints) return false
+        // Only check default keyline equality since all other keylines are
+        // derived from the defaults
+        if (defaultKeylines != other.defaultKeylines) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        if (!isValid()) return isValid().hashCode()
+
+        var result = isValid().hashCode()
+        result = 31 * result + availableSpace.hashCode()
+        result = 31 * result + itemMainAxisSize.hashCode()
+        result = 31 * result + startShiftDistance.hashCode()
+        result = 31 * result + endShiftDistance.hashCode()
+        result = 31 * result + startShiftPoints.hashCode()
+        result = 31 * result + endShiftPoints.hashCode()
+        result = 31 * result + defaultKeylines.hashCode()
+        return result
+    }
+
     companion object {
 
         /**
-         * Creates a new [Strategy] based on a default [keylineList].
-         *
-         * The [keylineList] passed to this method will be the keylines used when the carousel is
-         * scrolled anywhere in the middle of the list (not the beginning or end). From these
-         * default keylines, additional [KeylineList]s will be created which move the focal range
-         * to the beginning of the carousel container when scrolled to the beginning of the list and
-         * the end of the container when scrolled to the end of the list.
-         *
-         * @param carouselMainAxisSize the size of the carousel container in scrolling axis
-         * @param keylineList the default keylines that will be used to create the strategy
-         */
-        fun create(
-            /** The size of the carousel in the main axis. */
-            carouselMainAxisSize: Float,
-            /** The keylines along the main axis */
-            keylineList: KeylineList
-        ): Strategy {
-
-            val startKeylineSteps = getStartKeylineSteps(keylineList, carouselMainAxisSize)
-            val endKeylineSteps =
-                getEndKeylineSteps(keylineList, carouselMainAxisSize)
-
-            // TODO: Update this to use the first/last focal keylines to calculate shift?
-            val startShiftDistance = startKeylineSteps.last().first().unadjustedOffset -
-                keylineList.first().unadjustedOffset
-            val endShiftDistance = keylineList.last().unadjustedOffset -
-                endKeylineSteps.last().last().unadjustedOffset
-
-            return Strategy(
-                defaultKeylines = keylineList,
-                startKeylineSteps = startKeylineSteps,
-                endKeylineSteps = endKeylineSteps,
-                startShiftDistance = startShiftDistance,
-                endShiftDistance = endShiftDistance,
-                startShiftPoints = getStepInterpolationPoints(
-                    startShiftDistance,
-                    startKeylineSteps,
-                    true
-                ),
-                endShiftPoints = getStepInterpolationPoints(
-                    endShiftDistance,
-                    endKeylineSteps,
-                    false
-                )
-            )
-        }
-
-        /**
          * Generates discreet steps which move the focal range from its original position until
          * it reaches the start of the carousel container.
          *
@@ -501,7 +506,8 @@
             return ShiftPointRange(
                 fromStepIndex = 0,
                 toStepIndex = 0,
-                steppedInterpolation = 0f)
+                steppedInterpolation = 0f
+            )
         }
 
         private fun MutableList<Keyline>.move(srcIndex: Int, dstIndex: Int): MutableList<Keyline> {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
index 230111b..d9ecc67 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
@@ -34,12 +34,12 @@
     val FocusHandleColor = ColorSchemeKeyTokens.Primary
     val HandleColor = ColorSchemeKeyTokens.Primary
     val HandleElevation = ElevationTokens.Level1
-    val HandleHeight = 20.0.dp
+    val HandleHeight = 44.0.dp
     val HandleShape = ShapeKeyTokens.CornerFull
-    val HandleWidth = 20.0.dp
+    val HandleWidth = 4.0.dp
     val HoverHandleColor = ColorSchemeKeyTokens.Primary
     val InactiveTrackColor = ColorSchemeKeyTokens.SurfaceVariant
-    val InactiveTrackHeight = 4.0.dp
+    val InactiveTrackHeight = 16.0.dp
     val InactiveTrackShape = ShapeKeyTokens.CornerFull
     val LabelContainerColor = ColorSchemeKeyTokens.Primary
     val LabelContainerElevation = ElevationTokens.Level0
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
index 85374bc..b9425e7 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
@@ -19,7 +19,6 @@
 /**
  * Returns a [CalendarModel] to be used by the date picker.
  */
-@ExperimentalMaterial3Api
 internal actual fun createCalendarModel(locale: CalendarLocale): CalendarModel =
     LegacyCalendarModelImpl(locale)
 
@@ -31,8 +30,7 @@
  * @param locale the [CalendarLocale] to use when formatting the given timestamp
  * @param cache a [MutableMap] for caching formatter related results for better performance
  */
-@ExperimentalMaterial3Api
-actual fun formatWithSkeleton(
+internal actual fun formatWithSkeleton(
     utcTimeMillis: Long,
     skeleton: String,
     locale: CalendarLocale,
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
index aeb86fe..50bd616 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
@@ -143,7 +143,8 @@
         val sourcePsi = invocation.sourcePsi as? KtElement ?: return null
         analyze(sourcePsi) {
             val resolvedCall = sourcePsi.resolveCall()?.singleFunctionCallOrNull() ?: return null
-            val stateType = resolvedCall.typeArgumentsMapping.asIterable().single().value
+            val stateType =
+                resolvedCall.typeArgumentsMapping.asIterable().singleOrNull()?.value ?: return null
             return when {
                 stateType.isMarkedNullable -> null
                 else -> {
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
index fb7a05b..4b33f97 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
@@ -108,17 +108,15 @@
     ) {
         val rootExpression = methodInvocation.resolveRootExpression()
         val rootExpressionLocation = context.getLocation(rootExpression)
-
+        val name = "Move expression outside of `${method.name}`'s arguments " +
+            "and pass `Unit` explicitly"
         context.report(
             OpaqueUnitKey,
             argument,
             context.getLocation(argument),
             "Implicitly passing `Unit` as argument to ${parameter.name}",
             fix()
-                .name(
-                    "Move expression outside of `${method.name}`'s arguments " +
-                        "and pass `Unit` explicitly"
-                )
+                .name(name)
                 .composite(
                     if (rootExpression.isInPhysicalBlock()) {
                         // If we're in a block where we can add an expression without breaking any
@@ -141,6 +139,7 @@
                         // }
                         // ```
                         fix()
+                            .name(name)
                             .composite(
                                 fix()
                                     .replace()
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index 1dcfaa9..50cb9c3 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -32,7 +32,6 @@
 }
 
 dependencies {
-
     implementation(libs.kotlinStdlib)
 
     api(project(":compose:runtime:runtime"))
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index bec84be..ea7cfa6 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -942,7 +942,7 @@
     method public boolean addAll(java.util.Collection<? extends T> elements);
     method public void clear();
     method public boolean contains(T element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends T> elements);
     method public T get(int index);
     method public androidx.compose.runtime.snapshots.StateRecord getFirstStateRecord();
     method public int getSize();
@@ -954,10 +954,10 @@
     method public java.util.ListIterator<T> listIterator(int index);
     method public void prependStateRecord(androidx.compose.runtime.snapshots.StateRecord value);
     method public boolean remove(T element);
-    method public boolean removeAll(java.util.Collection<E> elements);
+    method public boolean removeAll(java.util.Collection<? extends T> elements);
     method public T removeAt(int index);
     method public void removeRange(int fromIndex, int toIndex);
-    method public boolean retainAll(java.util.Collection<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends T> elements);
     method public T set(int index, T element);
     method public java.util.List<T> subList(int fromIndex, int toIndex);
     method public java.util.List<T> toList();
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 64b6be9..5a5621f 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -992,7 +992,7 @@
     method public boolean addAll(java.util.Collection<? extends T> elements);
     method public void clear();
     method public boolean contains(T element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends T> elements);
     method public T get(int index);
     method public androidx.compose.runtime.snapshots.StateRecord getFirstStateRecord();
     method public int getSize();
@@ -1004,10 +1004,10 @@
     method public java.util.ListIterator<T> listIterator(int index);
     method public void prependStateRecord(androidx.compose.runtime.snapshots.StateRecord value);
     method public boolean remove(T element);
-    method public boolean removeAll(java.util.Collection<E> elements);
+    method public boolean removeAll(java.util.Collection<? extends T> elements);
     method public T removeAt(int index);
     method public void removeRange(int fromIndex, int toIndex);
-    method public boolean retainAll(java.util.Collection<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends T> elements);
     method public T set(int index, T element);
     method public java.util.List<T> subList(int fromIndex, int toIndex);
     method public java.util.List<T> toList();
diff --git a/compose/runtime/runtime/lint-baseline.xml b/compose/runtime/runtime/lint-baseline.xml
index 2fce8f0..3ec6029 100644
--- a/compose/runtime/runtime/lint-baseline.xml
+++ b/compose/runtime/runtime/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanSuppressTag"
@@ -66,24 +66,6 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="field groupInfos with type HashMap&lt;Integer, GroupInfo>: replace with IntObjectMap"
-        errorLine1="    private val groupInfos = run {"
-        errorLine2="    ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/Composer.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable result with type HashMap&lt;Integer, GroupInfo>: replace with IntObjectMap"
-        errorLine1="        val result = hashMapOf&lt;Int, GroupInfo>()"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/Composer.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
         message="field intParams with type List&lt;IntParameter>: replace with IntList"
         errorLine1="        val intParams = List(ints) { index -> IntParameter(index) }"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index ee4f5ad..c7756b2 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 12100
+    const val version: Int = 12200
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 1e24610..2204884 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -22,13 +22,15 @@
 import androidx.collection.MutableIntIntMap
 import androidx.collection.MutableIntObjectMap
 import androidx.collection.MutableScatterMap
+import androidx.collection.MutableScatterSet
+import androidx.collection.ScatterSet
+import androidx.collection.mutableScatterSetOf
 import androidx.compose.runtime.Composer.Companion.equals
 import androidx.compose.runtime.changelist.ChangeList
 import androidx.compose.runtime.changelist.ComposerChangeListWriter
 import androidx.compose.runtime.changelist.FixupList
-import androidx.compose.runtime.collection.IdentityArrayMap
-import androidx.compose.runtime.collection.IdentityArraySet
 import androidx.compose.runtime.collection.IntMap
+import androidx.compose.runtime.collection.ScopeMap
 import androidx.compose.runtime.internal.IntRef
 import androidx.compose.runtime.internal.persistentCompositionLocalHashMapOf
 import androidx.compose.runtime.snapshots.currentSnapshot
@@ -127,7 +129,6 @@
         multiMap<Any, KeyInfo>(keyInfos.size).also {
             for (index in 0 until keyInfos.size) {
                 val keyInfo = keyInfos[index]
-                @Suppress("ReplacePutWithAssignment")
                 it.put(keyInfo.joinedKey, keyInfo)
             }
         }
@@ -234,8 +235,10 @@
      * unconditionally invalid. If it contains instances it is only invalid if at least on of the
      * instances is changed. This is used to track `DerivedState<*>` changes and only treat the
      * scope as invalid if the instance has changed.
+     *
+     * Can contain a [ScatterSet] of instances, single instance or null.
      */
-    var instances: IdentityArraySet<Any>?
+    var instances: Any?
 ) {
     fun isInvalid(): Boolean = scope.isInvalidFor(instances)
 }
@@ -320,7 +323,7 @@
     internal val composition: ControlledComposition,
     internal val slotTable: SlotTable,
     internal val anchor: Anchor,
-    internal var invalidations: List<Pair<RecomposeScopeImpl, IdentityArraySet<Any>?>>,
+    internal var invalidations: List<Pair<RecomposeScopeImpl, Any?>>,
     internal val locals: PersistentCompositionLocalMap
 )
 
@@ -2141,7 +2144,6 @@
                 if (writer.groupKey(current) == compositionLocalMapKey &&
                     writer.groupObjectKey(current) == compositionLocalMap
                 ) {
-                    @Suppress("UNCHECKED_CAST")
                     val providers = writer.groupAux(current) as PersistentCompositionLocalMap
                     providerCache = providers
                     return providers
@@ -2155,7 +2157,6 @@
                 if (reader.groupKey(current) == compositionLocalMapKey &&
                     reader.groupObjectKey(current) == compositionLocalMap
                 ) {
-                    @Suppress("UNCHECKED_CAST")
                     val providers = providerUpdates?.get(current)
                         ?: reader.groupAux(current) as PersistentCompositionLocalMap
                     providerCache = providers
@@ -3382,7 +3383,7 @@
         from: ControlledComposition? = null,
         to: ControlledComposition? = null,
         index: Int? = null,
-        invalidations: List<Pair<RecomposeScopeImpl, IdentityArraySet<Any>?>> = emptyList(),
+        invalidations: List<Pair<RecomposeScopeImpl, Any?>> = emptyList(),
         block: () -> R
     ): R {
         val savedIsComposing = isComposing
@@ -3392,9 +3393,7 @@
             nodeIndex = 0
             invalidations.fastForEach { (scope, instances) ->
                 if (instances != null) {
-                    instances.fastForEach { instance ->
-                        tryImminentInvalidation(scope, instance)
-                    }
+                    tryImminentInvalidation(scope, instances)
                 } else {
                     tryImminentInvalidation(scope, null)
                 }
@@ -3437,7 +3436,7 @@
      * [content].
      */
     internal fun composeContent(
-        invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
+        invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
         content: @Composable () -> Unit
     ) {
         runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
@@ -3459,7 +3458,7 @@
      * applied by [ControlledComposition.applyChanges] to have an effect.
      */
     internal fun recompose(
-        invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>
+        invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
     ): Boolean {
         runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
         // even if invalidationsRequested is empty we still need to recompose if the Composer has
@@ -3467,7 +3466,7 @@
         // there were a change for a state which was used by the child composition. such changes
         // will be tracked and added into `invalidations` list.
         if (
-            invalidationsRequested.isNotEmpty() ||
+            invalidationsRequested.size > 0 ||
             invalidations.isNotEmpty() ||
             forciblyRecompose
         ) {
@@ -3478,16 +3477,23 @@
     }
 
     private fun doCompose(
-        invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
+        invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
         content: (@Composable () -> Unit)?
     ) {
         runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
         trace("Compose:recompose") {
             compositionToken = currentSnapshot().id
             providerUpdates = null
-            invalidationsRequested.forEach { scope, set ->
-                val location = scope.anchor?.location ?: return
-                invalidations.add(Invalidation(scope, location, set))
+            invalidationsRequested.map.forEach { scope, instances ->
+                scope as RecomposeScopeImpl
+                val location = scope.anchor?.location ?: return@forEach
+                invalidations.add(
+                    Invalidation(
+                        scope,
+                        location,
+                        instances.takeUnless { it === ScopeInvalidated }
+                    )
+                )
             }
             invalidations.sortWith(InvalidationLocationAscending)
             nodeIndex = 0
@@ -3497,7 +3503,6 @@
                 startRoot()
 
                 // vv Experimental for forced
-                @Suppress("UNCHECKED_CAST")
                 val savedContent = nextSlot()
                 if (savedContent !== content && content != null) {
                     updateValue(content as Any?)
@@ -3551,23 +3556,6 @@
         runtimeCheck(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" }
     }
 
-    /**
-     * Record whether any groups were stared. If no groups were started then the root group
-     * doesn't need to be started or ended either.
-     */
-    private var startedGroup = false
-
-    /**
-     * During late change calculation the group start/end is handled by [insertMovableContentReferences]
-     * directly instead of requiring implicit starts/end groups to be inserted.
-     */
-    private var implicitRootStart = true
-
-    /**
-     * A stack of the location of the groups that were started.
-     */
-    private val startedGroups = IntStack()
-
     private fun recordInsert(anchor: Anchor) {
         if (insertFixups.isEmpty()) {
             changeListWriter.insertSlots(anchor, insertTable)
@@ -3738,8 +3726,6 @@
      * A holder that will dispose of its [CompositionContext] when it leaves the composition
      * that will not have its reference made visible to user code.
      */
-    // This warning becomes an error if its advice is followed since Composer needs its type param
-    @Suppress("RemoveRedundantQualifierName")
     private class CompositionContextHolder(
         val ref: ComposerImpl.CompositionContextImpl
     ) : ReusableRememberObserver {
@@ -4255,16 +4241,28 @@
             Invalidation(
                 scope,
                 location,
-                instance?.let { i ->
-                    IdentityArraySet<Any>().also { it.add(i) }
-                }
+                // Only derived state instance is important for composition
+                instance.takeIf { it is DerivedState<*> }
             )
         )
     } else {
-        if (instance == null) {
-            get(index).instances = null
+        val invalidation = get(index)
+        // Only derived state instance is important for composition
+        if (instance is DerivedState<*>) {
+            when (val oldInstance = invalidation.instances) {
+                null -> invalidation.instances = instance
+                is MutableScatterSet<*> -> {
+                    @Suppress("UNCHECKED_CAST")
+                    oldInstance as MutableScatterSet<Any?>
+                    oldInstance.add(instance)
+                }
+
+                else -> {
+                    invalidation.instances = mutableScatterSetOf(oldInstance, instance)
+                }
+            }
         } else {
-            get(index).instances?.add(instance)
+            invalidation.instances = null
         }
     }
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 8a6091e..371faae 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -21,8 +21,6 @@
 import androidx.collection.MutableScatterSet
 import androidx.collection.mutableScatterSetOf
 import androidx.compose.runtime.changelist.ChangeList
-import androidx.compose.runtime.collection.IdentityArrayMap
-import androidx.compose.runtime.collection.IdentityArraySet
 import androidx.compose.runtime.collection.ScopeMap
 import androidx.compose.runtime.collection.fastForEach
 import androidx.compose.runtime.snapshots.ReaderKind
@@ -487,7 +485,7 @@
      * A map of observable objects to the [RecomposeScope]s that observe the object. If the key
      * object is modified the associated scopes should be invalidated.
      */
-    private val observations = ScopeMap<RecomposeScopeImpl>()
+    private val observations = ScopeMap<Any, RecomposeScopeImpl>()
 
     /**
      * Used for testing. Returns the objects that are observed
@@ -496,6 +494,12 @@
         @TestOnly @Suppress("AsCollectionCall") get() = observations.map.asMap().keys
 
     /**
+     * A set of scopes that were invalidated by a call from [recordModificationsOf].
+     * This set is only used in [addPendingInvalidationsLocked], and is reused between invocations.
+     */
+    private val invalidatedScopes = MutableScatterSet<RecomposeScopeImpl>()
+
+    /**
      * A set of scopes that were invalidated conditionally (that is they were invalidated by a
      * [derivedStateOf] object) by a call from [recordModificationsOf]. They need to be held in the
      * [observations] map until invalidations are drained for composition as a later call to
@@ -506,7 +510,7 @@
     /**
      * A map of object read during derived states to the corresponding derived state.
      */
-    private val derivedStates = ScopeMap<DerivedState<*>>()
+    private val derivedStates = ScopeMap<Any, DerivedState<*>>()
 
     /**
      * Used for testing. Returns dependencies of derived states that are currently observed.
@@ -545,7 +549,7 @@
      * scopes that were already dismissed by composition and should be ignored in the next call
      * to [recordModificationsOf].
      */
-    private val observationsProcessed = ScopeMap<RecomposeScopeImpl>()
+    private val observationsProcessed = ScopeMap<Any, RecomposeScopeImpl>()
 
     /**
      * A map of the invalid [RecomposeScope]s. If this map is non-empty the current state of
@@ -554,7 +558,7 @@
      * scope. The scope is checked with these instances to ensure the value has changed. This is
      * used to only invalidate the scope if a [derivedStateOf] object changes.
      */
-    private var invalidations = IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>()
+    private var invalidations = ScopeMap<RecomposeScopeImpl, Any>()
 
     /**
      * As [RecomposeScope]s are removed the corresponding entries in the observations set must be
@@ -723,17 +727,19 @@
 
     override fun composeContent(content: @Composable () -> Unit) {
         // TODO: This should raise a signal to any currently running recompose calls
-        // to halt and return
+        //   to halt and return
         guardChanges {
             synchronized(lock) {
                 drainPendingModificationsForCompositionLocked()
                 guardInvalidationsLocked { invalidations ->
                     val observer = observer()
-                    @Suppress("UNCHECKED_CAST")
-                    observer?.onBeginComposition(
-                        this,
-                        invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
-                    )
+                    if (observer != null) {
+                        @Suppress("UNCHECKED_CAST")
+                        observer.onBeginComposition(
+                            this,
+                            invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
+                        )
+                    }
                     composer.composeContent(invalidations, content)
                     observer?.onEndComposition(this)
                 }
@@ -819,13 +825,7 @@
     }
 
     override fun observesAnyOf(values: Set<Any>): Boolean {
-        if (values is IdentityArraySet<Any>) {
-            values.fastForEach { value ->
-                if (value in observations || value in derivedStates) return true
-            }
-            return false
-        }
-        for (value in values) {
+        values.fastForEach { value ->
             if (value in observations || value in derivedStates) return true
         }
         return false
@@ -833,11 +833,10 @@
 
     override fun prepareCompose(block: () -> Unit) = composer.prepareCompose(block)
 
-    private fun MutableScatterSet<RecomposeScopeImpl>?.addPendingInvalidationsLocked(
+    private fun addPendingInvalidationsLocked(
         value: Any,
         forgetConditionalScopes: Boolean
-    ): MutableScatterSet<RecomposeScopeImpl>? {
-        var set = this
+    ) {
         observations.forEachScopeOf(value) { scope ->
             if (
                 !observationsProcessed.remove(value, scope) &&
@@ -846,41 +845,36 @@
                 if (scope.isConditional && !forgetConditionalScopes) {
                     conditionallyInvalidatedScopes.add(scope)
                 } else {
-                    if (set == null) set = MutableScatterSet()
-                    set?.add(scope)
+                    invalidatedScopes.add(scope)
                 }
             }
         }
-        return set
     }
 
     private fun addPendingInvalidationsLocked(values: Set<Any>, forgetConditionalScopes: Boolean) {
-        var invalidated: MutableScatterSet<RecomposeScopeImpl>? = null
-
         values.fastForEach { value ->
             if (value is RecomposeScopeImpl) {
                 value.invalidateForResult(null)
             } else {
-                invalidated =
-                    invalidated.addPendingInvalidationsLocked(value, forgetConditionalScopes)
+                addPendingInvalidationsLocked(value, forgetConditionalScopes)
                 derivedStates.forEachScopeOf(value) {
-                    invalidated =
-                        invalidated.addPendingInvalidationsLocked(it, forgetConditionalScopes)
+                    addPendingInvalidationsLocked(it, forgetConditionalScopes)
                 }
             }
         }
 
+        val conditionallyInvalidatedScopes = conditionallyInvalidatedScopes
+        val invalidatedScopes = invalidatedScopes
         if (forgetConditionalScopes && conditionallyInvalidatedScopes.isNotEmpty()) {
             observations.removeScopeIf { scope ->
-                scope in conditionallyInvalidatedScopes || invalidated?.let { scope in it } == true
+                scope in conditionallyInvalidatedScopes || scope in invalidatedScopes
             }
             conditionallyInvalidatedScopes.clear()
             cleanUpDerivedStateObservations()
-        } else {
-            invalidated?.let {
-                observations.removeScopeIf { scope -> scope in it }
-                cleanUpDerivedStateObservations()
-            }
+        } else if (invalidatedScopes.isNotEmpty()) {
+            observations.removeScopeIf { scope -> scope in invalidatedScopes }
+            cleanUpDerivedStateObservations()
+            invalidatedScopes.clear()
         }
     }
 
@@ -1044,7 +1038,7 @@
     }
 
     private inline fun <T> guardInvalidationsLocked(
-        block: (changes: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>) -> T
+        block: (changes: ScopeMap<RecomposeScopeImpl, Any>) -> T
     ): T {
         val invalidations = takeInvalidations()
         return try {
@@ -1159,12 +1153,21 @@
                     return InvalidationResult.IMMINENT
                 }
 
-                // invalidations[scope] containing an explicit null means it was invalidated
-                // unconditionally.
+                // Observer requires a map of scope -> states, so we have to fill it if observer
+                // is set.
+                val observer = observer()
                 if (instance == null) {
-                    invalidations[scope] = null
+                    // invalidations[scope] containing ScopeInvalidated means it was invalidated
+                    // unconditionally.
+                    invalidations.set(scope, ScopeInvalidated)
+                } else if (observer == null && instance !is DerivedState<*>) {
+                    // If observer is not set, we only need to add derived states to invalidation,
+                    // as regular states are always going to invalidate.
+                    invalidations.set(scope, ScopeInvalidated)
                 } else {
-                    invalidations.addValue(scope, instance)
+                    if (!invalidations.anyScopeOf(scope) { it === ScopeInvalidated }) {
+                        invalidations.add(scope, instance)
+                    }
                 }
             }
             delegate
@@ -1193,9 +1196,9 @@
      * This takes ownership of the current invalidations and sets up a new array map to hold the
      * new invalidations.
      */
-    private fun takeInvalidations(): IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?> {
+    private fun takeInvalidations(): ScopeMap<RecomposeScopeImpl, Any> {
         val invalidations = invalidations
-        this.invalidations = IdentityArrayMap()
+        this.invalidations = ScopeMap()
         return invalidations
     }
 
@@ -1410,16 +1413,7 @@
     }
 }
 
-private fun <K : Any, V : Any> IdentityArrayMap<K, IdentityArraySet<V>?>.addValue(
-    key: K,
-    value: V
-) {
-    if (key in this) {
-        this[key]?.add(value)
-    } else {
-        this[key] = IdentityArraySet<V>().also { it.add(value) }
-    }
-}
+internal object ScopeInvalidated
 
 @ExperimentalComposeRuntimeApi
 internal class CompositionObserverHolder(
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
index 1a3716e..2af8671 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
@@ -18,7 +18,7 @@
 
 import androidx.collection.MutableObjectIntMap
 import androidx.collection.MutableScatterMap
-import androidx.compose.runtime.collection.IdentityArraySet
+import androidx.collection.ScatterSet
 import androidx.compose.runtime.snapshots.fastAny
 import androidx.compose.runtime.snapshots.fastForEach
 import androidx.compose.runtime.tooling.CompositionObserverHandle
@@ -314,18 +314,19 @@
     fun recordRead(instance: Any): Boolean {
         if (rereading) return false // Re-reading should force composition to update its tracking
 
-        val token = (trackedInstances ?: MutableObjectIntMap<Any>().also { trackedInstances = it })
-            .put(instance, currentToken, default = -1)
+        val trackedInstances = trackedInstances
+            ?: MutableObjectIntMap<Any>().also { trackedInstances = it }
 
+        val token = trackedInstances.put(instance, currentToken, default = -1)
         if (token == currentToken) {
             return true
         }
 
         if (instance is DerivedState<*>) {
-            val tracked = trackedDependencies ?: MutableScatterMap<DerivedState<*>, Any?>().also {
-                trackedDependencies = it
-            }
-            tracked[instance] = instance.currentRecord.currentValue
+            val trackedDependencies = trackedDependencies
+                ?: MutableScatterMap<DerivedState<*>, Any?>().also { trackedDependencies = it }
+
+            trackedDependencies[instance] = instance.currentRecord.currentValue
         }
 
         return false
@@ -342,25 +343,34 @@
      *
      * @param instances The set of objects reported as invalidating this scope.
      */
-    fun isInvalidFor(instances: IdentityArraySet<Any>?): Boolean {
+    fun isInvalidFor(instances: Any? /* State | ScatterSet<State> | null */): Boolean {
         // If a non-empty instances exists and contains only derived state objects with their
         // default values, then the scope should not be considered invalid. Otherwise the scope
         // should if it was invalidated by any other kind of instance.
         if (instances == null) return true
         val trackedDependencies = trackedDependencies ?: return true
-        if (
-            instances.isNotEmpty() &&
-            instances.all { instance ->
-                instance is DerivedState<*> && instance.let {
-                    @Suppress("UNCHECKED_CAST")
-                    it as DerivedState<Any?>
-                    val policy = it.policy ?: structuralEqualityPolicy()
-                    policy.equivalent(it.currentRecord.currentValue, trackedDependencies[it])
-                }
+
+        return when (instances) {
+            is DerivedState<*> -> {
+                instances.checkDerivedStateChanged(trackedDependencies)
             }
-        )
-            return false
-        return true
+            is ScatterSet<*> -> {
+                instances.isNotEmpty() &&
+                    instances.any {
+                        it !is DerivedState<*> || it.checkDerivedStateChanged(trackedDependencies)
+                    }
+            }
+            else -> true
+        }
+    }
+
+    private fun DerivedState<*>.checkDerivedStateChanged(
+        dependencies: MutableScatterMap<DerivedState<*>, Any?>
+    ): Boolean {
+        @Suppress("UNCHECKED_CAST")
+        this as DerivedState<Any?>
+        val policy = policy ?: structuralEqualityPolicy()
+        return !policy.equivalent(currentRecord.currentValue, dependencies[this])
     }
 
     fun rereadTrackedInstances() {
@@ -394,26 +404,21 @@
                 !skipped && instances.any { _, instanceToken -> instanceToken != token }
             ) { composition ->
                 if (
-                    currentToken == token && instances == trackedInstances &&
+                    currentToken == token &&
+                    instances == trackedInstances &&
                     composition is CompositionImpl
                 ) {
                     instances.removeIf { instance, instanceToken ->
-                        (instanceToken != token).also { remove ->
-                            if (remove) {
-                                composition.removeObservation(instance, this)
-                                (instance as? DerivedState<*>)?.let {
-                                    composition.removeDerivedStateObservation(it)
-                                    trackedDependencies?.let { dependencies ->
-                                        dependencies.remove(it)
-                                        if (dependencies.size == 0) {
-                                            trackedDependencies = null
-                                        }
-                                    }
-                                }
+                        val shouldRemove = instanceToken != token
+                        if (shouldRemove) {
+                            composition.removeObservation(instance, this)
+                            if (instance is DerivedState<*>) {
+                                composition.removeDerivedStateObservation(instance)
+                                trackedDependencies?.remove(instance)
                             }
                         }
+                        shouldRemove
                     }
-                    if (instances.size == 0) trackedInstances = null
                 }
             } else null
         }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 8ddff07..6a41089 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -16,10 +16,11 @@
 
 package androidx.compose.runtime
 
+import androidx.collection.MutableScatterSet
 import androidx.collection.mutableScatterSetOf
-import androidx.compose.runtime.collection.IdentityArraySet
 import androidx.compose.runtime.collection.fastForEach
 import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.collection.wrapIntoSet
 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentSetOf
 import androidx.compose.runtime.snapshots.MutableSnapshot
 import androidx.compose.runtime.snapshots.ReaderKind
@@ -211,7 +212,7 @@
         _knownCompositionsCache = newCache
         newCache
     }
-    private var snapshotInvalidations = IdentityArraySet<Any>()
+    private var snapshotInvalidations = MutableScatterSet<Any>()
     private val compositionInvalidations = mutableVectorOf<ControlledComposition>()
     private val compositionsAwaitingApply = mutableListOf<ControlledComposition>()
     private val compositionValuesAwaitingInsert = mutableListOf<MovableContentStateReference>()
@@ -299,7 +300,7 @@
     private fun deriveStateLocked(): CancellableContinuation<Unit>? {
         if (_state.value <= State.ShuttingDown) {
             clearKnownCompositionsLocked()
-            snapshotInvalidations = IdentityArraySet()
+            snapshotInvalidations = MutableScatterSet()
             compositionInvalidations.clear()
             compositionsAwaitingApply.clear()
             compositionValuesAwaitingInsert.clear()
@@ -315,7 +316,7 @@
                 State.Inactive
             }
             runnerJob == null -> {
-                snapshotInvalidations = IdentityArraySet()
+                snapshotInvalidations = MutableScatterSet()
                 compositionInvalidations.clear()
                 if (hasBroadcastFrameClockAwaitersLocked) State.InactivePendingWork
                 else State.Inactive
@@ -437,7 +438,8 @@
     private fun recordComposerModifications(): Boolean {
         val changes = synchronized(stateLock) {
             if (snapshotInvalidations.isEmpty()) return hasFrameWorkLocked
-            snapshotInvalidations.also { snapshotInvalidations = IdentityArraySet() }
+            snapshotInvalidations.wrapIntoSet()
+                .also { snapshotInvalidations = MutableScatterSet() }
         }
         val compositions = synchronized(stateLock) {
             knownCompositions
@@ -453,7 +455,7 @@
                     if (_state.value <= State.ShuttingDown) return@run
                 }
             }
-            snapshotInvalidations = IdentityArraySet()
+            snapshotInvalidations = MutableScatterSet()
             complete = true
         } finally {
             if (!complete) {
@@ -476,12 +478,12 @@
     private inline fun recordComposerModifications(
         onEachInvalidComposition: (ControlledComposition) -> Unit
     ) {
-        val changes = snapshotInvalidations
+        val changes = snapshotInvalidations.wrapIntoSet()
         if (changes.isNotEmpty()) {
             knownCompositions.fastForEach { composition ->
                 composition.recordModificationsOf(changes)
             }
-            snapshotInvalidations = IdentityArraySet()
+            snapshotInvalidations = MutableScatterSet()
         }
         compositionInvalidations.forEach(onEachInvalidComposition)
         compositionInvalidations.clear()
@@ -517,7 +519,8 @@
         val toApply = mutableListOf<ControlledComposition>()
         val toLateApply = mutableScatterSetOf<ControlledComposition>()
         val toComplete = mutableScatterSetOf<ControlledComposition>()
-        val modifiedValues = IdentityArraySet<Any>()
+        val modifiedValues = MutableScatterSet<Any>()
+        val modifiedValuesSet = modifiedValues.wrapIntoSet()
         val alreadyComposed = mutableScatterSetOf<ControlledComposition>()
 
         fun clearRecompositionState() {
@@ -623,7 +626,7 @@
                                 knownCompositions.fastForEach { value ->
                                     if (
                                         value !in alreadyComposed &&
-                                        value.observesAnyOf(modifiedValues)
+                                        value.observesAnyOf(modifiedValuesSet)
                                     ) {
                                         toRecompose += value
                                     }
@@ -741,7 +744,7 @@
 
                 compositionsAwaitingApply.clear()
                 compositionInvalidations.clear()
-                snapshotInvalidations = IdentityArraySet()
+                snapshotInvalidations = MutableScatterSet()
 
                 compositionValuesAwaitingInsert.clear()
                 compositionValuesRemoved.clear()
@@ -941,7 +944,7 @@
                     }
 
                     // Perform recomposition for any invalidated composers
-                    val modifiedValues = IdentityArraySet<Any>()
+                    val modifiedValues = MutableScatterSet<Any>()
                     try {
                         toRecompose.fastForEach { composer ->
                             performRecompose(composer, modifiedValues)?.let {
@@ -1175,7 +1178,7 @@
 
     private fun performRecompose(
         composition: ControlledComposition,
-        modifiedValues: IdentityArraySet<Any>?
+        modifiedValues: MutableScatterSet<Any>?
     ): ControlledComposition? {
         if (composition.isComposing ||
             composition.isDisposed ||
@@ -1187,7 +1190,7 @@
                     // Record write performed by a previous composition as if they happened during
                     // composition.
                     composition.prepareCompose {
-                        modifiedValues.fastForEach { composition.recordWriteOf(it) }
+                        modifiedValues.forEach { composition.recordWriteOf(it) }
                     }
                 }
                 composition.recompose()
@@ -1197,7 +1200,7 @@
 
     private fun performInsertValues(
         references: List<MovableContentStateReference>,
-        modifiedValues: IdentityArraySet<Any>?
+        modifiedValues: MutableScatterSet<Any>?
     ): List<ControlledComposition> {
         val tasks = references.fastGroupBy { it.composition }
         for ((composition, refs) in tasks) {
@@ -1242,7 +1245,7 @@
 
     private fun writeObserverOf(
         composition: ControlledComposition,
-        modifiedValues: IdentityArraySet<Any>?
+        modifiedValues: MutableScatterSet<Any>?
     ): (Any) -> Unit {
         return { value ->
             composition.recordWriteOf(value)
@@ -1252,7 +1255,7 @@
 
     private inline fun <T> composing(
         composition: ControlledComposition,
-        modifiedValues: IdentityArraySet<Any>?,
+        modifiedValues: MutableScatterSet<Any>?,
         block: () -> T
     ): T {
         val snapshot = Snapshot.takeMutableSnapshot(
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 16cef80..ab0082f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -364,6 +364,14 @@
                         val nearestScope = findEffectiveRecomposeScope(reader.currentGroup)
                         if (nearestScope != null) {
                             scopes.add(nearestScope)
+                            if (nearestScope.anchor?.location == reader.currentGroup) {
+                                // For the group that contains the restart group then, in some
+                                // cases, such as when the parameter names of a function change,
+                                // the restart lambda can be invalid if it is called. To avoid this
+                                // the scope parent scope needs to be invalidated too.
+                                val parentScope = findEffectiveRecomposeScope(reader.parent)
+                                parentScope?.let { scopes.add(it) }
+                            }
                         } else {
                             allScopesFound = false
                             scopes.clear()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
index b1daa6f..7cb5056 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
@@ -35,7 +35,6 @@
 import androidx.compose.runtime.SlotTable
 import androidx.compose.runtime.SlotWriter
 import androidx.compose.runtime.TestOnly
-import androidx.compose.runtime.collection.IdentityArraySet
 import androidx.compose.runtime.composeRuntimeError
 import androidx.compose.runtime.deactivateCurrentGroup
 import androidx.compose.runtime.internal.IntRef
@@ -952,9 +951,7 @@
                 // If the original owner ignores this then we need to record it in the
                 // reference
                 if (result == InvalidationResult.IGNORED) {
-                    reference.invalidations += scope to instance?.let {
-                        IdentityArraySet<Any>().also { it.add(it) }
-                    }
+                    reference.invalidations += scope to instance
                     return InvalidationResult.SCHEDULED
                 }
                 return result
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
deleted file mode 100644
index 565eb30..0000000
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 2021 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.compose.runtime.collection
-
-import androidx.compose.runtime.identityHashCode
-
-internal class IdentityArrayMap<Key : Any, Value : Any?>(capacity: Int = 16) {
-    var keys = arrayOfNulls<Any?>(capacity)
-        private set
-    var values = arrayOfNulls<Any?>(capacity)
-        private set
-    var size = 0
-        private set
-
-    fun isEmpty() = size == 0
-    fun isNotEmpty() = size > 0
-
-    operator fun contains(key: Key): Boolean = find(key) >= 0
-
-    operator fun get(key: Key): Value? {
-        val index = find(key)
-        @Suppress("UNCHECKED_CAST")
-        return if (index >= 0) values[index] as Value else null
-    }
-
-    operator fun set(key: Key, value: Value) {
-        val keys = keys
-        val values = values
-        val size = size
-
-        val index = find(key)
-        if (index >= 0) {
-            values[index] = value
-        } else {
-            val insertIndex = -(index + 1)
-            val resize = size == keys.size
-            val destKeys = if (resize) {
-                arrayOfNulls(size * 2)
-            } else keys
-            keys.copyInto(
-                destination = destKeys,
-                destinationOffset = insertIndex + 1,
-                startIndex = insertIndex,
-                endIndex = size
-            )
-            if (resize) {
-                keys.copyInto(
-                    destination = destKeys,
-                    endIndex = insertIndex
-                )
-            }
-            destKeys[insertIndex] = key
-            this.keys = destKeys
-            val destValues = if (resize) {
-                arrayOfNulls(size * 2)
-            } else values
-            values.copyInto(
-                destination = destValues,
-                destinationOffset = insertIndex + 1,
-                startIndex = insertIndex,
-                endIndex = size
-            )
-            if (resize) {
-                values.copyInto(
-                    destination = destValues,
-                    endIndex = insertIndex
-                )
-            }
-            destValues[insertIndex] = value
-            this.values = destValues
-            this.size++
-        }
-    }
-
-    fun remove(key: Key): Value? {
-        val index = find(key)
-        if (index >= 0) {
-            val value = values[index]
-            val size = size
-            val keys = keys
-            val values = values
-            keys.copyInto(
-                destination = keys,
-                destinationOffset = index,
-                startIndex = index + 1,
-                endIndex = size
-            )
-            values.copyInto(
-                destination = values,
-                destinationOffset = index,
-                startIndex = index + 1,
-                endIndex = size
-            )
-            val newSize = size - 1
-            keys[newSize] = null
-            values[newSize] = null
-            this.size = newSize
-            @Suppress("UNCHECKED_CAST")
-            return value as Value
-        }
-        return null
-    }
-
-    fun clear() {
-        size = 0
-        keys.fill(null)
-        values.fill(null)
-    }
-
-    inline fun removeIf(block: (key: Key, value: Value) -> Boolean) {
-        var current = 0
-        for (index in 0 until size) {
-            @Suppress("UNCHECKED_CAST")
-            val key = keys[index] as Key
-            @Suppress("UNCHECKED_CAST")
-            val value = values[index] as Value
-            if (!block(key, value)) {
-                if (current != index) {
-                    keys[current] = key
-                    values[current] = values[index]
-                }
-                current++
-            }
-        }
-        if (size > current) {
-            for (index in current until size) {
-                keys[index] = null
-                values[index] = null
-            }
-            size = current
-        }
-    }
-
-    inline fun removeValueIf(block: (value: Value) -> Boolean) {
-        removeIf { _, value -> block(value) }
-    }
-
-    inline fun forEach(block: (key: Key, value: Value) -> Unit) {
-        for (index in 0 until size) {
-            @Suppress("UNCHECKED_CAST")
-            block(keys[index] as Key, values[index] as Value)
-        }
-    }
-
-    /**
-     * Returns the index into [keys] of the found [key], or the negative index - 1 of the
-     * position in which it would be if it were found.
-     */
-    private fun find(key: Any?): Int {
-        val keyIdentity = identityHashCode(key)
-        var low = 0
-        var high = size - 1
-
-        val keys = keys
-        while (low <= high) {
-            val mid = (low + high).ushr(1)
-            val midKey = keys[mid]
-            val midKeyHash = identityHashCode(midKey)
-            when {
-                midKeyHash < keyIdentity -> low = mid + 1
-                midKeyHash > keyIdentity -> high = mid - 1
-                key === midKey -> return mid
-                else -> return findExactIndex(mid, key, keyIdentity)
-            }
-        }
-        return -(low + 1)
-    }
-
-    /**
-     * When multiple keys share the same [identityHashCode], then we must find the specific
-     * index of the target item. This method assumes that [midIndex] has already been checked
-     * for an exact match for [key], but will look at nearby values to find the exact item index.
-     * If no match is found, the negative index - 1 of the position in which it would be will
-     * be returned, which is always after the last key with the same [identityHashCode].
-     */
-    private fun findExactIndex(midIndex: Int, key: Any?, keyHash: Int): Int {
-        val keys = keys
-        val size = size
-
-        // hunt down first
-        for (i in midIndex - 1 downTo 0) {
-            val k = keys[i]
-            if (k === key) {
-                return i
-            }
-            if (identityHashCode(k) != keyHash) {
-                break // we've gone too far
-            }
-        }
-
-        for (i in midIndex + 1 until size) {
-            val k = keys[i]
-            if (k === key) {
-                return i
-            }
-            if (identityHashCode(k) != keyHash) {
-                // We've gone too far. We should insert here.
-                return -(i + 1)
-            }
-        }
-
-        // We should insert at the end
-        return -(size + 1)
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    fun asMap(): Map<Key, Value> = object : Map<Key, Value> {
-        override val entries: Set<Map.Entry<Key, Value>>
-            get() = object : Set<Map.Entry<Key, Value>> {
-                override val size: Int get() = [email protected]
-                override fun isEmpty(): Boolean = [email protected]()
-                override fun iterator(): Iterator<Map.Entry<Key, Value>> =
-                    sequence<Map.Entry<Key, Value>> {
-                        for (index in 0 until [email protected]) {
-                            yield(
-                                object : Map.Entry<Key, Value> {
-                                    override val key: Key =
-                                        [email protected][index] as Key
-                                    override val value: Value =
-                                        [email protected][index] as Value
-                                }
-                            )
-                        }
-                    }.iterator()
-                override fun containsAll(elements: Collection<Map.Entry<Key, Value>>): Boolean =
-                    elements.all { contains(it) }
-
-                override fun contains(element: Map.Entry<Key, Value>): Boolean =
-                    this@IdentityArrayMap[element.key] === element.value
-            }
-
-        override val keys: Set<Key> get() = object : Set<Key> {
-            override val size: Int get() = [email protected]
-            override fun isEmpty(): Boolean = [email protected]()
-            override fun iterator(): Iterator<Key> = sequence {
-                for (index in 0 until [email protected]) {
-                    yield([email protected][index] as Key)
-                }
-            }.iterator()
-
-            override fun containsAll(elements: Collection<Key>): Boolean {
-                for (key in elements) {
-                    if (!contains(key)) return false
-                }
-                return true
-            }
-
-            override fun contains(element: Key): Boolean = [email protected](element)
-        }
-
-        override val size: Int get() = [email protected]
-        override val values: Collection<Value> get() = object : Collection<Value> {
-            override val size: Int get() = [email protected]
-            override fun isEmpty(): Boolean = [email protected]()
-            override fun iterator(): Iterator<Value> = sequence {
-                for (index in 0 until [email protected]) {
-                    yield([email protected][index] as Value)
-                }
-            }.iterator()
-
-            override fun containsAll(elements: Collection<Value>): Boolean {
-                for (value in elements) {
-                    if (!contains(value)) return false
-                }
-                return true
-            }
-
-            override fun contains(element: Value): Boolean {
-                for (index in 0 until [email protected]) {
-                    if ([email protected][index] == element) return true
-                }
-                return false
-            }
-        }
-
-        override fun isEmpty(): Boolean = [email protected]()
-        override fun get(key: Key): Value? = this@IdentityArrayMap[key]
-        override fun containsValue(value: Value): Boolean =
-            [email protected](value)
-        override fun containsKey(key: Key): Boolean =
-            this@IdentityArrayMap[key] != null
-    }
-}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt
deleted file mode 100644
index 415a611..0000000
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright 2020 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.compose.runtime.collection
-
-import androidx.compose.runtime.identityHashCode
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
-/**
- * A set of values using an array as the backing store, ordered using [identityHashCode] for
- * both sorting and uniqueness.
- */
-@OptIn(ExperimentalContracts::class)
-internal class IdentityArraySet<T : Any> : Set<T> {
-    override var size = 0
-        private set
-
-    var values: Array<Any?> = arrayOfNulls(16)
-        private set
-
-    /**
-     * Returns true if the set contains [element]
-     */
-    override operator fun contains(element: T) = find(element) >= 0
-
-    /**
-     * Return the item at the given [index].
-     */
-    operator fun get(index: Int): T {
-        checkIndexBounds(index)
-
-        @Suppress("UNCHECKED_CAST")
-        return values[index] as T
-    }
-
-    /**
-     * Add [value] to the set and return `true` if it was added or `false` if it already existed.
-     */
-    fun add(value: T): Boolean {
-        val index: Int
-        val size = size
-        val values = values
-
-        if (size > 0) {
-            index = find(value)
-
-            if (index >= 0) {
-                return false
-            }
-        } else {
-            index = -1
-        }
-
-        val insertIndex = -(index + 1)
-
-        if (size == values.size) {
-            val newSorted = arrayOfNulls<Any>(values.size * 2)
-            values.copyInto(
-                destination = newSorted,
-                destinationOffset = insertIndex + 1,
-                startIndex = insertIndex,
-                endIndex = size
-            )
-            values.copyInto(
-                destination = newSorted,
-                endIndex = insertIndex
-            )
-            this.values = newSorted
-        } else {
-            values.copyInto(
-                destination = values,
-                destinationOffset = insertIndex + 1,
-                startIndex = insertIndex,
-                endIndex = size
-            )
-        }
-        this.values[insertIndex] = value
-        this.size++
-        return true
-    }
-
-    /**
-     * Remove all values from the set.
-     */
-    fun clear() {
-        values.fill(null)
-        size = 0
-    }
-
-    /**
-     * Call [block] for all items in the set.
-     */
-    inline fun fastForEach(block: (T) -> Unit) {
-        contract { callsInPlace(block) }
-        val values = values
-        for (i in 0 until size) {
-            @Suppress("UNCHECKED_CAST")
-            block(values[i] as T)
-        }
-    }
-
-    inline fun fastAny(block: (T) -> Boolean): Boolean {
-        contract { callsInPlace(block) }
-        val size = size
-        if (size == 0) return false
-        val values = values
-        for (i in 0 until size) {
-            @Suppress("UNCHECKED_CAST")
-            if (block(values[i] as T)) return true
-        }
-        return false
-    }
-
-    fun addAll(collection: Collection<T>) {
-        if (collection.isEmpty()) return
-
-        if (collection !is IdentityArraySet<T>) {
-            // Unknown collection, just add repeatedly
-            for (value in collection) {
-                add(value)
-            }
-        } else {
-            // Identity set, merge sorted arrays
-            val thisValues = values
-            val otherValues = collection.values
-            val thisSize = size
-            val otherSize = collection.size
-            val combinedSize = thisSize + otherSize
-
-            val needsResize = values.size < combinedSize
-            val elementsInOrder = thisSize == 0 ||
-                identityHashCode(thisValues[thisSize - 1]) < identityHashCode(otherValues[0])
-
-            if (!needsResize && elementsInOrder) {
-                // fast path, just copy target values
-                otherValues.copyInto(
-                    destination = thisValues,
-                    destinationOffset = thisSize,
-                    startIndex = 0,
-                    endIndex = otherSize
-                )
-                size += otherSize
-            } else {
-                // slow path, merge this and other values
-                val newArray = if (needsResize) {
-                    arrayOfNulls(if (thisSize > otherSize) thisSize * 2 else otherSize * 2)
-                } else {
-                    thisValues
-                }
-                var thisIndex = thisSize - 1
-                var otherIndex = otherSize - 1
-                var nextInsertIndex = combinedSize - 1
-                while (thisIndex >= 0 || otherIndex >= 0) {
-                    val nextValue = when {
-                        thisIndex < 0 -> otherValues[otherIndex--]
-                        otherIndex < 0 -> thisValues[thisIndex--]
-                        else -> {
-                            val thisValue = thisValues[thisIndex]
-                            val otherValue = otherValues[otherIndex]
-
-                            val thisHash = identityHashCode(thisValue)
-                            val otherHash = identityHashCode(otherValue)
-                            when {
-                                thisHash > otherHash -> {
-                                    thisIndex--
-                                    thisValue
-                                }
-                                thisHash < otherHash -> {
-                                    otherIndex--
-                                    otherValue
-                                }
-                                thisValue === otherValue -> {
-                                    // hash and the value are the same, advance both pointers
-                                    thisIndex--
-                                    otherIndex--
-                                    thisValue
-                                }
-                                else -> {
-                                    // collision, lookup if the same item is in the array
-                                    var i = thisIndex - 1
-                                    var foundDuplicate = false
-                                    while (i >= 0) {
-                                        val value = thisValues[i--]
-                                        if (identityHashCode(value) != otherHash) break
-                                        if (otherValue === value) {
-                                            foundDuplicate = true
-                                            break
-                                        }
-                                    }
-
-                                    if (foundDuplicate) {
-                                        // advance pointer and continue next iteration of outer
-                                        // merge loop.
-                                        otherIndex--
-                                        continue
-                                    } else {
-                                        // didn't find the duplicate, put other item in array.
-                                        otherIndex--
-                                        otherValue
-                                    }
-                                }
-                            }
-                        }
-                    }
-
-                    // insert value and continue
-                    newArray[nextInsertIndex--] = nextValue
-                }
-
-                if (nextInsertIndex >= 0) {
-                    // some values were duplicated, copy the merged part
-                    newArray.copyInto(
-                        newArray,
-                        destinationOffset = 0,
-                        startIndex = nextInsertIndex + 1,
-                        endIndex = combinedSize
-                    )
-                }
-                // newSize = endOffset - startOffset of copy above
-                val newSize = combinedSize - (nextInsertIndex + 1)
-                newArray.fill(null, fromIndex = newSize, toIndex = combinedSize)
-
-                values = newArray
-                size = newSize
-            }
-        }
-    }
-
-    /**
-     * Return true if the set is empty.
-     */
-    override fun isEmpty() = size == 0
-
-    /**
-     * Returns true if the set is not empty.
-     */
-    fun isNotEmpty() = size > 0
-
-    /**
-     * Remove [value] from the set.
-     */
-    fun remove(value: T): Boolean {
-        val index = find(value)
-        val values = values
-        val size = size
-
-        if (index >= 0) {
-            if (index < size - 1) {
-                values.copyInto(
-                    destination = values,
-                    destinationOffset = index,
-                    startIndex = index + 1,
-                    endIndex = size
-                )
-            }
-            values[size - 1] = null
-            this.size--
-            return true
-        }
-        return false
-    }
-
-    /**
-     * Removes all values that match [predicate].
-     */
-    inline fun removeValueIf(predicate: (T) -> Boolean) {
-        val values = values
-        val size = size
-
-        var destinationIndex = 0
-        for (i in 0 until size) {
-            @Suppress("UNCHECKED_CAST")
-            val item = values[i] as T
-            if (!predicate(item)) {
-                if (destinationIndex != i) {
-                    values[destinationIndex] = item
-                }
-                destinationIndex++
-            }
-        }
-        for (i in destinationIndex until size) {
-            values[i] = null
-        }
-        this.size = destinationIndex
-    }
-
-    /**
-     * Returns the index of [value] in the set or the negative index - 1 of the location where
-     * it would have been if it had been in the set.
-     */
-    private fun find(value: Any?): Int {
-        var low = 0
-        var high = size - 1
-        val valueIdentity = identityHashCode(value)
-        val values = values
-
-        while (low <= high) {
-            val mid = (low + high).ushr(1)
-            val midVal = values[mid]
-            val midIdentity = identityHashCode(midVal)
-            when {
-                midIdentity < valueIdentity -> low = mid + 1
-                midIdentity > valueIdentity -> high = mid - 1
-                midVal === value -> return mid
-                else -> return findExactIndex(mid, value, valueIdentity)
-            }
-        }
-        return -(low + 1)
-    }
-
-    /**
-     * When multiple items share the same [identityHashCode], then we must find the specific
-     * index of the target item. This method assumes that [midIndex] has already been checked
-     * for an exact match for [value], but will look at nearby values to find the exact item index.
-     * If no match is found, the negative index - 1 of the position in which it would be will
-     * be returned, which is always after the last item with the same [identityHashCode].
-     */
-    private fun findExactIndex(
-        midIndex: Int,
-        value: Any?,
-        valueHash: Int
-    ): Int {
-        val values = values
-        val size = size
-
-        // hunt down first
-        for (i in midIndex - 1 downTo 0) {
-            val v = values[i]
-            if (v === value) {
-                return i
-            }
-            if (identityHashCode(v) != valueHash) {
-                break // we've gone too far
-            }
-        }
-
-        for (i in midIndex + 1 until size) {
-            val v = values[i]
-            if (v === value) {
-                return i
-            }
-            if (identityHashCode(v) != valueHash) {
-                // We've gone too far. We should insert here.
-                return -(i + 1)
-            }
-        }
-
-        // We should insert at the end
-        return -(size + 1)
-    }
-
-    /**
-     * Verifies if index is in bounds
-     */
-    private fun checkIndexBounds(index: Int) {
-        if (index !in 0 until size) {
-            throw IndexOutOfBoundsException("Index $index, size $size")
-        }
-    }
-
-    /**
-     * Return true if all elements of [elements] are in the set.
-     */
-    override fun containsAll(elements: Collection<T>) = elements.all { contains(it) }
-
-    /**
-     * Return an iterator for the set.
-     */
-    @Suppress("UNCHECKED_CAST")
-    override fun iterator(): Iterator<T> = object : Iterator<T> {
-        var index = 0
-        override fun hasNext(): Boolean = index < size
-        override fun next(): T = [email protected][index++] as T
-    }
-
-    override fun toString(): String {
-        return joinToString(prefix = "[", postfix = "]") { it.toString() }
-    }
-}
-
-internal inline fun <T : Any> Set<T>.fastForEach(block: (T) -> Unit) {
-    if (this is IdentityArraySet<T>) {
-        fastForEach(block)
-    } else {
-        forEach(block)
-    }
-}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt
new file mode 100644
index 0000000..5854635
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.runtime.collection
+
+import androidx.collection.ScatterSet
+
+/**
+ * A wrapper for [ScatterSet] that implements [Set] APIs. This wrapper allows to use [ScatterSet]
+ * through external APIs and unwrap it back into [Set] for faster iteration / other operations.
+ */
+internal class ScatterSetWrapper<T>(
+    internal val set: ScatterSet<T>
+) : Set<T> {
+    override val size: Int
+        get() = set.size
+
+    override fun isEmpty(): Boolean = set.isEmpty()
+    override fun iterator(): Iterator<T> = iterator {
+        set.forEach {
+            yield(it)
+        }
+    }
+
+    override fun containsAll(elements: Collection<T>): Boolean =
+        elements.all { set.contains(it) }
+
+    override fun contains(element: T): Boolean =
+        set.contains(element)
+}
+
+internal fun <T> ScatterSet<T>.wrapIntoSet(): Set<T> = ScatterSetWrapper(this)
+
+internal inline fun <T : Any> Set<T>.fastForEach(block: (T) -> Unit) =
+    when (this) {
+        is ScatterSetWrapper<T> -> {
+            set.forEach(block)
+        }
+        else -> {
+            forEach(block)
+        }
+    }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScopeMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScopeMap.kt
index 1c3e1d8..a5817e3 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScopeMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScopeMap.kt
@@ -22,7 +22,7 @@
 /**
  * Maps values to a set of scopes.
  */
-internal class ScopeMap<T : Any> {
+internal class ScopeMap<Key : Any, Scope : Any> {
     val map = mutableScatterMapOf<Any, Any>()
 
     /**
@@ -33,21 +33,21 @@
     /**
      * Adds a [key]/[scope] pair to the map.
      */
-    fun add(key: Any, scope: T) {
+    fun add(key: Key, scope: Scope) {
         map.compute(key) { _, value ->
             when (value) {
                 null -> scope
                 is MutableScatterSet<*> -> {
                     @Suppress("UNCHECKED_CAST")
-                    (value as MutableScatterSet<T>).add(scope)
+                    (value as MutableScatterSet<Scope>).add(scope)
                     value
                 }
 
                 else -> {
                     if (value !== scope) {
-                        val set = MutableScatterSet<T>()
+                        val set = MutableScatterSet<Scope>()
                         @Suppress("UNCHECKED_CAST")
-                        set.add(value as T)
+                        set.add(value as Scope)
                         set.add(scope)
                         set
                     } else {
@@ -59,27 +59,41 @@
     }
 
     /**
+     * Replaces scopes for [key] with [value]
+     */
+    fun set(key: Key, value: Scope) {
+        map[key] = value
+    }
+
+    /**
      * Returns true if any scopes are associated with [element]
      */
-    operator fun contains(element: Any): Boolean = map.containsKey(element)
+    operator fun contains(element: Key): Boolean = map.containsKey(element)
 
     /**
      * Executes [block] for all scopes mapped to the given [key].
      */
-    inline fun forEachScopeOf(key: Any, block: (scope: T) -> Unit) {
+    inline fun forEachScopeOf(key: Key, block: (scope: Scope) -> Unit) {
         when (val value = map[key]) {
             null -> { /* do nothing */ }
             is MutableScatterSet<*> -> {
                 @Suppress("UNCHECKED_CAST")
-                (value as MutableScatterSet<T>).forEach(block)
+                (value as MutableScatterSet<Scope>).forEach(block)
             }
             else -> {
                 @Suppress("UNCHECKED_CAST")
-                block(value as T)
+                block(value as Scope)
             }
         }
     }
 
+    inline fun anyScopeOf(key: Key, block: (scope: Scope) -> Boolean): Boolean {
+        forEachScopeOf(key) {
+            if (block(it)) return true
+        }
+        return false
+    }
+
     /**
      * Removes all values and scopes from the map
      */
@@ -95,12 +109,12 @@
      * @param scope the scope being removed
      * @return true if the value was removed from the scope
      */
-    fun remove(key: Any, scope: T): Boolean {
+    fun remove(key: Key, scope: Scope): Boolean {
         val value = map[key] ?: return false
         return when (value) {
             is MutableScatterSet<*> -> {
                 @Suppress("UNCHECKED_CAST")
-                val set = value as MutableScatterSet<T>
+                val set = value as MutableScatterSet<Scope>
 
                 val removed = set.remove(scope)
                 if (removed && set.isEmpty()) {
@@ -120,18 +134,18 @@
      * Removes all scopes that match [predicate]. If all scopes for a given value have been
      * removed, that value is removed also.
      */
-    inline fun removeScopeIf(crossinline predicate: (scope: T) -> Boolean) {
+    inline fun removeScopeIf(crossinline predicate: (scope: Scope) -> Boolean) {
         map.removeIf { _, value ->
             when (value) {
                 is MutableScatterSet<*> -> {
                     @Suppress("UNCHECKED_CAST")
-                    val set = value as MutableScatterSet<T>
+                    val set = value as MutableScatterSet<Scope>
                     set.removeIf(predicate)
                     set.isEmpty()
                 }
                 else -> {
                     @Suppress("UNCHECKED_CAST")
-                    predicate(value as T)
+                    predicate(value as Scope)
                 }
             }
         }
@@ -141,7 +155,27 @@
      * Removes given scope from all sets. If all scopes for a given value are removed, that value
      * is removed as well.
      */
-    fun removeScope(scope: T) {
+    fun removeScope(scope: Scope) {
         removeScopeIf { it === scope }
     }
+
+    /**
+     * Converts values to regular Map to expose to instrumentation.
+     * WARNING: extremely slow, do no use in production!
+     */
+    fun asMap(): Map<Key, Set<Scope>> {
+        val result = hashMapOf<Key, Set<Scope>>()
+        map.forEach { key, value ->
+            @Suppress("UNCHECKED_CAST")
+            result[key as Key] = when (value) {
+                is MutableScatterSet<*> -> {
+                    val set = value as MutableScatterSet<Scope>
+                    @Suppress("AsCollectionCall")
+                    set.asSet()
+                }
+                else -> mutableSetOf(value as Scope)
+            }
+        }
+        return result
+    }
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index d08defb..23e538b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.runtime.snapshots
 
+import androidx.collection.MutableScatterSet
+import androidx.collection.mutableScatterSetOf
 import androidx.compose.runtime.AtomicInt
 import androidx.compose.runtime.AtomicReference
 import androidx.compose.runtime.Composable
@@ -24,7 +26,7 @@
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.SnapshotThreadLocal
 import androidx.compose.runtime.checkPrecondition
-import androidx.compose.runtime.collection.IdentityArraySet
+import androidx.compose.runtime.collection.wrapIntoSet
 import androidx.compose.runtime.currentThreadId
 import androidx.compose.runtime.internal.JvmDefaultWithCompatibility
 import androidx.compose.runtime.requirePrecondition
@@ -227,7 +229,7 @@
     /**
      * The set of state objects that have been modified in this snapshot.
      */
-    internal abstract val modified: IdentityArraySet<StateObject>?
+    internal abstract val modified: MutableScatterSet<StateObject>?
 
     /**
      * Notify the snapshot that all objects created in this snapshot to this point should be
@@ -816,7 +818,7 @@
         ) else null
 
         var observers = emptyList<(Set<Any>, Snapshot) -> Unit>()
-        var globalModified: IdentityArraySet<StateObject>? = null
+        var globalModified: MutableScatterSet<StateObject>? = null
         sync {
             validateOpen(this)
             if (modified == null || modified.size == 0) {
@@ -824,7 +826,7 @@
                 val previousGlobalSnapshot = currentGlobalSnapshot.get()
                 takeNewGlobalSnapshot(previousGlobalSnapshot, emptyLambda)
                 val previousModified = previousGlobalSnapshot.modified
-                if (!previousModified.isNullOrEmpty()) {
+                if (previousModified != null && previousModified.isNotEmpty()) {
                     observers = applyObservers
                     globalModified = previousModified
                 }
@@ -854,16 +856,19 @@
         applied = true
 
         // Notify any apply observers that changes applied were seen
-        if (!globalModified.isNullOrEmpty()) {
-            val nonNullGlobalModified = globalModified!!
-            observers.fastForEach {
-                it(nonNullGlobalModified, this)
+        if (globalModified != null) {
+            val nonNullGlobalModified = globalModified!!.wrapIntoSet()
+            if (nonNullGlobalModified.isNotEmpty()) {
+                observers.fastForEach {
+                    it(nonNullGlobalModified, this)
+                }
             }
         }
 
-        if (!modified.isNullOrEmpty()) {
+        if (modified != null && modified.isNotEmpty()) {
+            val modifiedSet = modified.wrapIntoSet()
             observers.fastForEach {
-                it(modified, this)
+                it(modifiedSet, this)
             }
         }
 
@@ -873,8 +878,8 @@
         sync {
             releasePinnedSnapshotsForCloseLocked()
             checkAndOverwriteUnusedRecordsLocked()
-            globalModified?.fastForEach { processForUnusedRecordsLocked(it) }
-            modified?.fastForEach { processForUnusedRecordsLocked(it) }
+            globalModified?.forEach { processForUnusedRecordsLocked(it) }
+            modified?.forEach { processForUnusedRecordsLocked(it) }
             merged?.fastForEach { processForUnusedRecordsLocked(it) }
             merged = null
         }
@@ -962,7 +967,7 @@
             // id to be forgotten as no state records will refer to it.
             this.modified = null
             val id = id
-            modified.fastForEach { state ->
+            modified.forEach { state ->
                 var current: StateRecord? = state.firstStateRecord
                 while (current != null) {
                     if (current.snapshotId == id || current.snapshotId in previousIds) {
@@ -997,12 +1002,12 @@
         val start = this.invalid.set(id).or(this.previousIds)
         val modified = modified!!
         var statesToRemove: MutableList<StateObject>? = null
-        modified.fastForEach { state ->
+        modified.forEach { state ->
             val first = state.firstStateRecord
             // If either current or previous cannot be calculated the object was created
             // in a nested snapshot that was committed then changed.
-            val current = readable(first, snapshotId, invalidSnapshots) ?: return@fastForEach
-            val previous = readable(first, id, start) ?: return@fastForEach
+            val current = readable(first, snapshotId, invalidSnapshots) ?: return@forEach
+            val previous = readable(first, id, start) ?: return@forEach
             if (current != previous) {
                 val applied = readable(first, id, this.invalid) ?: readError()
                 val merged = optimisticMerges?.get(current) ?: run {
@@ -1117,12 +1122,12 @@
     }
 
     override fun recordModified(state: StateObject) {
-        (modified ?: IdentityArraySet<StateObject>().also { modified = it }).add(state)
+        (modified ?: mutableScatterSetOf<StateObject>().also { modified = it }).add(state)
     }
 
     override var writeCount: Int = 0
 
-    override var modified: IdentityArraySet<StateObject>? = null
+    override var modified: MutableScatterSet<StateObject>? = null
 
     internal var merged: List<StateObject>? = null
 
@@ -1324,7 +1329,7 @@
     override fun hasPendingChanges(): Boolean = false
     override val writeObserver: ((Any) -> Unit)? get() = null
 
-    override var modified: IdentityArraySet<StateObject>?
+    override var modified: MutableScatterSet<StateObject>?
         get() = null
         @Suppress("UNUSED_PARAMETER")
         set(value) = unsupported()
@@ -1395,7 +1400,7 @@
         }
     }
 
-    override val modified: IdentityArraySet<StateObject>? get() = null
+    override val modified: MutableScatterSet<StateObject>? get() = null
     override val writeObserver: ((Any) -> Unit)? get() = null
     override fun recordModified(state: StateObject) = reportReadonlySnapshotWrite()
 
@@ -1594,7 +1599,7 @@
 
     override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
 
-    override var modified: IdentityArraySet<StateObject>?
+    override var modified: MutableScatterSet<StateObject>?
         get() = currentSnapshot.modified
         @Suppress("UNUSED_PARAMETER")
         set(value) = unsupported()
@@ -1706,7 +1711,7 @@
 
     override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
 
-    override var modified: IdentityArraySet<StateObject>?
+    override var modified: MutableScatterSet<StateObject>?
         get() = currentSnapshot.modified
         @Suppress("UNUSED_PARAMETER")
         set(value) = unsupported()
@@ -1895,7 +1900,7 @@
 private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
     var previousGlobalSnapshot = snapshotInitializer as GlobalSnapshot
 
-    var modified: IdentityArraySet<StateObject>? = null // Effectively val; can be with contracts
+    var modified: MutableScatterSet<StateObject>? = null // Effectively val; can be with contracts
     val result = sync {
         previousGlobalSnapshot = currentGlobalSnapshot.get()
         modified = previousGlobalSnapshot.modified
@@ -1911,7 +1916,7 @@
         try {
             val observers = applyObservers
             observers.fastForEach { observer ->
-                observer(it, previousGlobalSnapshot)
+                observer(it.wrapIntoSet(), previousGlobalSnapshot)
             }
         } finally {
             pendingApplyObserverCount.add(-1)
@@ -1920,7 +1925,7 @@
 
     sync {
         checkAndOverwriteUnusedRecordsLocked()
-        modified?.fastForEach { processForUnusedRecordsLocked(it) }
+        modified?.forEach { processForUnusedRecordsLocked(it) }
     }
 
     return result
@@ -2324,10 +2329,10 @@
     if (modified == null) return null
     val start = applyingSnapshot.invalid.set(applyingSnapshot.id).or(applyingSnapshot.previousIds)
     var result: MutableMap<StateRecord, StateRecord>? = null
-    modified.fastForEach { state ->
+    modified.forEach { state ->
         val first = state.firstStateRecord
-        val current = readable(first, id, invalidSnapshots) ?: return@fastForEach
-        val previous = readable(first, id, start) ?: return@fastForEach
+        val current = readable(first, id, invalidSnapshots) ?: return@forEach
+        val previous = readable(first, id, start) ?: return@forEach
         if (current != previous) {
             // Try to produce a merged state record
             val applied = readable(first, applyingSnapshot.id, applyingSnapshot.invalid)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 47c549d..a15a5f4 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -379,7 +379,7 @@
         /**
          * Values that have been read during the scope's [SnapshotStateObserver.observeReads].
          */
-        private val valueToScopes = ScopeMap<Any>()
+        private val valueToScopes = ScopeMap<Any, Any>()
 
         /**
          * Reverse index (scope -> values) for faster scope invalidation.
@@ -423,7 +423,7 @@
         /**
          * Invalidation index from state objects to derived states reading them.
          */
-        private val dependencyToDerivedStates = ScopeMap<DerivedState<*>>()
+        private val dependencyToDerivedStates = ScopeMap<Any, DerivedState<*>>()
 
         /**
          * Last derived state value recorded during read.
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt
deleted file mode 100644
index 705cc6b..0000000
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Copyright 2021 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.compose.runtime.collection
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-
-private class Key(val value: Int)
-
-class IdentityArrayMapTests {
-    private val keys = Array(100) { Key(it) }
-
-    @Test
-    fun canCreateEmptyMap() {
-        val map = IdentityArrayMap<Key, Any>()
-        assertTrue(map.isEmpty(), "map is not empty")
-    }
-
-    @Test
-    fun canSetAndGetValues() {
-        val map = IdentityArrayMap<Key, String>()
-        map[keys[1]] = "One"
-        map[keys[2]] = "Two"
-        assertEquals("One", map[keys[1]], "map key 1")
-        assertEquals("Two", map[keys[2]], "map key 2")
-        assertEquals(null, map[keys[3]], "map key 3")
-    }
-
-    @Test
-    fun canSetAndGetManyValues() {
-        val map = IdentityArrayMap<Key, String>()
-        repeat(keys.size) {
-            map[keys[it]] = it.toString()
-        }
-        repeat(keys.size) {
-            assertEquals(it.toString(), map[keys[it]], "map key $it")
-        }
-    }
-
-    @Test
-    fun canRemoveValues() {
-        val map = IdentityArrayMap<Key, Int>()
-        repeat(keys.size) {
-            map[keys[it]] = it
-        }
-        map.removeValueIf { value -> value % 2 == 0 }
-        assertEquals(keys.size / 2, map.size)
-        for (i in 1 until keys.size step 2) {
-            assertEquals(i, map[keys[i]], "map key $i")
-        }
-        for (i in 0 until keys.size step 2) {
-            assertEquals(null, map[keys[i]], "map key $i")
-        }
-        map.removeValueIf { true }
-        assertEquals(0, map.size, "map is not empty after removing everything")
-    }
-
-    @Test
-    fun canRemoveKeys() {
-        val map = IdentityArrayMap<Key, Int>()
-        repeat(keys.size) {
-            map[keys[it]] = it
-        }
-        map.removeIf { key, _ -> key.value % 2 == 0 }
-        assertEquals(keys.size / 2, map.size)
-        for (i in 1 until keys.size step 2) {
-            assertEquals(i, map[keys[i]], "map key $i")
-        }
-        for (i in 0 until keys.size step 2) {
-            assertEquals(null, map[keys[i]], "map key $i")
-        }
-        map.removeIf { _, _ -> true }
-        assertEquals(0, map.size, "map is not empty after removing everything")
-    }
-
-    @Test
-    fun canForEachKeysAndValues() {
-        val map = IdentityArrayMap<Key, String>()
-        repeat(100) {
-            map[keys[it]] = it.toString()
-        }
-        assertEquals(100, map.size)
-        var count = 0
-        map.forEach { key, value ->
-            assertEquals(key.value.toString(), value, "map key ${key.value}")
-            count++
-        }
-        assertEquals(map.size, count, "forEach didn't loop the expected number of times")
-    }
-
-    @Test
-    fun canRemoveItems() {
-        val map = IdentityArrayMap<Key, String>()
-        repeat(100) {
-            map[keys[it]] = it.toString()
-        }
-
-        repeat(100) {
-            assertEquals(100 - it, map.size)
-            val removed = map.remove(keys[it])
-            assertEquals(removed, it.toString(), "Expected to remove $it for ${keys[it]}")
-            if (it > 0) {
-                assertNull(
-                    map.remove(keys[it - 1]),
-                    "Expected item ${it - 1} to already be removed"
-                )
-            }
-        }
-    }
-
-    @Test // b/195621739
-    fun canRemoveWhenFull() {
-        val map = IdentityArrayMap<Key, String>()
-        repeat(16) {
-            map[keys[it]] = it.toString()
-        }
-        repeat(16) {
-            val key = keys[it]
-            val removed = map.remove(key)
-            assertNotNull(removed)
-            assertFalse(map.contains(key))
-        }
-        assertTrue(map.isEmpty())
-    }
-
-    @Test
-    fun canClear() {
-        val map = IdentityArrayMap<Key, String>()
-        repeat(16) {
-            map[keys[it]] = it.toString()
-        }
-        map.clear()
-        assertTrue(map.isEmpty())
-        assertEquals(0, map.size, "map size should be 0 after calling clear")
-    }
-
-    @Test
-    fun asMap_create() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        assertNotNull(mapInterface)
-    }
-
-    @Test
-    fun asMap_entries() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        assertNotNull(entries)
-    }
-
-    @Test
-    fun asMap_entries_size() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        assertEquals(count, entries.size)
-        map.remove(keys[5])
-        assertEquals(count - 1, entries.size)
-        map.clear()
-        assertEquals(0, entries.size)
-    }
-
-    @Test
-    fun asMap_entries_isEmpty() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        assertFalse(entries.isEmpty())
-        map.clear()
-        assertTrue(entries.isEmpty())
-    }
-
-    @Test
-    fun asMap_entries_iterator() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        var counted_entries = 0
-        for ((key, value) in entries) {
-            assertEquals(map[key], value)
-            counted_entries++
-        }
-        assertEquals(count, counted_entries)
-    }
-
-    @Test
-    fun asMap_entries_containsAll() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        val entryInstances = entries.toList()
-        val containsAllPositive = entries.containsAll(entryInstances)
-        assertTrue(containsAllPositive)
-        map.remove(keys[5])
-        val containsAllNegative = entries.containsAll(entryInstances)
-        assertFalse(containsAllNegative)
-    }
-
-    @Test
-    fun asMap_entries_contains() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val entries = mapInterface.entries
-        val entryInstances = entries.toList()
-        val containsPositive = entries.contains(entryInstances[5])
-        assertTrue(containsPositive)
-        map.remove(keys[5])
-        val entryFive = entryInstances.first { it.key == keys[5] }
-        val containsNegative = entries.contains(entryFive)
-        assertFalse(containsNegative)
-    }
-
-    @Test
-    fun asMap_keys() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val keys = mapInterface.keys
-        assertNotNull(keys)
-    }
-
-    @Test
-    fun asMap_keys_size() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val iKeys = mapInterface.keys
-        assertEquals(count, iKeys.size)
-        map.remove(keys[5])
-        assertEquals(count - 1, iKeys.size)
-    }
-
-    @Test
-    fun asMap_keys_isEmpty() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val keys = mapInterface.keys
-        assertFalse(keys.isEmpty())
-        map.clear()
-        assertTrue(keys.isEmpty())
-    }
-
-    @Test
-    fun asMap_keys_iterator() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val keys = mapInterface.keys
-        var counted_keys = 0
-        for (key in keys) {
-            assertTrue(map.contains(key))
-            counted_keys++
-        }
-        assertEquals(count, counted_keys)
-    }
-
-    @Test
-    fun asMap_keys_containsAll() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val iKeys = mapInterface.keys
-        val keyInstances = iKeys.toList()
-        assertTrue(iKeys.containsAll(keyInstances))
-        map.remove(keys[5])
-        assertFalse(iKeys.containsAll(keyInstances))
-    }
-
-    @Test
-    fun asMap_keys_contains() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val iKeys = mapInterface.keys
-        assertTrue(iKeys.contains(keys[5]))
-        map.remove(keys[5])
-        assertFalse(iKeys.contains(keys[5]))
-    }
-
-    @Test
-    fun asMap_size() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        assertEquals(count, mapInterface.size)
-        map.remove(keys[5])
-        assertEquals(count - 1, mapInterface.size)
-    }
-
-    @Test
-    fun asMap_values() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        assertNotNull(values)
-    }
-
-    @Test
-    fun asMap_values_size() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        assertEquals(count, values.size)
-        map.remove(keys[5])
-        assertEquals(count - 1, values.size)
-        map.clear()
-        assertEquals(0, values.size)
-    }
-
-    @Test
-    fun asMap_values_isEmpty() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        assertFalse(values.isEmpty())
-        map.clear()
-        assertTrue(values.isEmpty())
-    }
-
-    @Test
-    fun asMap_values_iterator() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        var countedValues = 0
-        for (value in values) {
-            var found = false
-            map.forEach { _, mapValue ->
-                if (value == mapValue) found = true
-            }
-            assertTrue(found)
-            countedValues++
-        }
-        assertEquals(count, countedValues)
-    }
-
-    @Test
-    fun asMap_values_containsAll() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        val valueInstances = values.toList()
-        assertTrue(values.containsAll(valueInstances))
-        map.remove(keys[5])
-        assertFalse(values.containsAll(valueInstances))
-    }
-
-    @Test
-    fun asMap_values_contains() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        val values = mapInterface.values
-        assertTrue(values.contains("5"))
-        map.remove(keys[5])
-        assertFalse(values.contains("5"))
-    }
-
-    @Test
-    fun asMap_isEmpty() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        assertFalse(mapInterface.isEmpty())
-        map.clear()
-        assertTrue(mapInterface.isEmpty())
-    }
-
-    @Test
-    fun asMap_get() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        for (key in keys.take(count)) {
-            assertEquals(map[key], mapInterface[key])
-        }
-    }
-
-    @Test
-    fun asMap_containsValue() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        for (i in 0 until count) {
-            assertTrue(mapInterface.containsValue(i.toString()))
-        }
-        assertFalse(mapInterface.containsValue("not there"))
-    }
-
-    @Test
-    fun asMap_containsKey() {
-        val count = 16
-        val map = IdentityArrayMap<Key, String>()
-        repeat(count) {
-            map[keys[it]] = it.toString()
-        }
-        val mapInterface = map.asMap()
-        repeat(count) {
-            assertTrue(mapInterface.containsKey(keys[it]))
-        }
-        assertFalse(mapInterface.containsKey(keys[count]))
-    }
-}
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt
deleted file mode 100644
index f9e614a..0000000
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright 2020 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.compose.runtime.collection
-
-import androidx.compose.runtime.identityHashCode
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNotSame
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-
-class IdentityArraySetTest {
-    private val set: IdentityArraySet<Stuff> = IdentityArraySet()
-
-    private val list = listOf(Stuff(10), Stuff(12), Stuff(1), Stuff(30), Stuff(10))
-
-    @Test
-    fun emptyConstruction() {
-        val s = IdentityArraySet<Stuff>()
-        assertEquals(0, s.size)
-    }
-
-    @Test
-    fun get_indexWithinBounds_shouldNotThrow() {
-        list.forEach { set.add(it) }
-
-        assertNotNull(set.get(0))
-    }
-
-    @Test
-    fun get_indexOutOfBounds_shouldThrow() {
-        list.forEach { set.add(it) }
-
-        // Index less than 0
-        assertFailsWith<IndexOutOfBoundsException> {
-            set.get(-1)
-        }
-
-        // Index greater than size
-        assertFailsWith<IndexOutOfBoundsException> {
-            set.get(list.size + 1)
-        }
-    }
-
-    @Test
-    fun addValueForward() {
-        list.forEach { set.add(it) }
-        assertEquals(list.size, set.size)
-        var previousItem = set[0]
-        for (i in 1 until set.size) {
-            val item = set[i]
-            assertTrue(identityHashCode(previousItem) < identityHashCode(item))
-            previousItem = item
-        }
-    }
-
-    @Test
-    fun addValueReversed() {
-        list.asReversed().forEach { set.add(it) }
-        assertEquals(list.size, set.size)
-        var previousItem = set[0]
-        for (i in 1 until set.size) {
-            val item = set[i]
-            assertTrue(identityHashCode(previousItem) < identityHashCode(item))
-            previousItem = item
-        }
-    }
-
-    @Test
-    fun addExistingValue() {
-        list.forEach { set.add(it) }
-        list.asReversed().forEach { set.add(it) }
-
-        assertEquals(list.size, set.size)
-        var previousItem = set[0]
-        for (i in 1 until set.size) {
-            val item = set[i]
-            assertTrue(identityHashCode(previousItem) < identityHashCode(item))
-            previousItem = item
-        }
-    }
-
-    @Test
-    fun clear() {
-        list.forEach { set.add(it) }
-        set.clear()
-
-        assertEquals(0, set.size)
-        set.values.forEach {
-            assertNull(it)
-        }
-    }
-
-    @Test
-    fun remove() {
-        list.forEach { set.add(it) }
-
-        // remove a value that doesn't exist:
-        val removed = set.remove(Stuff(10))
-        assertEquals(list.size, set.size)
-        assertFalse(removed)
-
-        // remove a value in the middle:
-        testRemoveValueAtIndex(set.size / 2)
-
-        // remove the last value
-        testRemoveValueAtIndex(set.size - 1)
-
-        // remove a first value
-        testRemoveValueAtIndex(0)
-    }
-
-    @Test
-    fun removeValueIf() {
-        list.forEach { set.add(it) }
-
-        set.removeValueIf { it.item == 10 }
-
-        // Make sure we've removed both items
-        assertEquals(list.size - 2, set.size)
-        set.fastForEach { assertNotEquals(10, it.item) }
-        assertNull(set.values[set.size])
-        assertNull(set.values[set.size + 1])
-    }
-
-    @Test
-    fun growSet() {
-        val verifierSet = mutableSetOf<Stuff>()
-        repeat(100) {
-            val stuff = Stuff(it)
-            set.add(stuff)
-            verifierSet.add(stuff)
-        }
-        assertEquals(100, set.size)
-        set.fastForEach { verifierSet.remove(it) }
-        assertEquals(0, verifierSet.size)
-    }
-
-    @Test
-    fun canUseAsSetOfT() {
-        val stuff = Array(100) { Stuff(it) }
-        for (i in 0 until 100 step 2) {
-            set.add(stuff[i])
-        }
-        val setOfT: Set<Stuff> = set
-        for (i in 0 until 100) {
-            val expected = i % 2 == 0
-            if (expected) {
-                assertTrue(stuff[i] in set)
-                assertTrue(stuff[i] in setOfT)
-            } else {
-                assertFalse(stuff[i] in set)
-                assertFalse(stuff[i] in setOfT)
-            }
-        }
-        for (element in setOfT) {
-            assertTrue(element.item % 2 == 0)
-            assertEquals(element, stuff[element.item])
-        }
-
-        set.add(stuff[1])
-        assertTrue(stuff[1] in setOfT)
-
-        assertTrue(setOfT.containsAll(listOf(stuff[0], stuff[1], stuff[2])))
-    }
-
-    @Test
-    fun addAll_Collection() {
-        set.addAll(list)
-
-        assertEquals(list.size, set.size)
-        for (value in list) {
-            assertTrue(value in set)
-        }
-    }
-
-    @Test
-    fun addAll_IdentityArraySet() {
-        val anotherSet = IdentityArraySet<Stuff>()
-        anotherSet.addAll(list)
-
-        set.addAll(anotherSet)
-
-        for (value in list) {
-            assertTrue(value in set)
-        }
-
-        set.addAll(anotherSet)
-
-        assertEquals(anotherSet.size, set.size)
-        for (value in list) {
-            assertTrue(value in set)
-        }
-
-        val stuff = Array(100) { Stuff(it) }
-        for (i in 0 until 100 step 2) {
-            anotherSet.add(stuff[i])
-        }
-        set.addAll(anotherSet)
-
-        for (i in stuff.indices) {
-            val value = stuff[i]
-            if (i % 2 == 0) {
-                assertTrue(value in set, "Expected to have element $i in $set")
-            } else {
-                assertFalse(value in set, "Didn't expect to have element $i in $set")
-            }
-        }
-
-        for (value in list) {
-            assertTrue(value in set)
-        }
-    }
-
-    private fun testRemoveValueAtIndex(index: Int) {
-        val value = set[index]
-        val initialSize = set.size
-        val removed = set.remove(value)
-        assertEquals(initialSize - 1, set.size)
-        assertTrue(removed)
-        assertNull(set.values[set.size])
-        set.fastForEach { assertNotSame(value, it) }
-    }
-
-    data class Stuff(val item: Int)
-}
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/ScopeMapTest.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/ScopeMapTest.kt
index 9c3887b..23db9fb 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/ScopeMapTest.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/ScopeMapTest.kt
@@ -23,14 +23,14 @@
 import kotlin.test.fail
 
 class ScopeMapTest {
-    private val map = ScopeMap<Scope>()
+    private val map = ScopeMap<Value, Scope>()
 
     private val scopeList = listOf(Scope(10), Scope(12), Scope(1), Scope(30), Scope(10))
     private val valueList = listOf(Value("A"), Value("B"))
 
     @Test
     fun emptyConstruction() {
-        val m = ScopeMap<Scope>()
+        val m = ScopeMap<Value, Scope>()
         assertEquals(0, m.size)
     }
 
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 1d9fa6a..1d45f14 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -41,8 +41,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-
-                implementation("androidx.compose.runtime:runtime:1.2.1")
+                implementation("androidx.compose.runtime:runtime:1.6.0")
                 implementation(project(":compose:ui:ui-util"))
             }
         }
@@ -70,7 +69,6 @@
         desktopMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation(project(":compose:runtime:runtime"))
             }
         }
 
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index be124d7..1319005 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -717,6 +717,16 @@
     property public final int NonZero;
   }
 
+  public final class PathHitTester {
+    ctor public PathHitTester();
+    method public operator boolean contains(long position);
+    method public void updatePath(androidx.compose.ui.graphics.Path path, optional float tolerance);
+  }
+
+  public final class PathHitTesterKt {
+    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional float tolerance);
+  }
+
   public interface PathIterator extends java.util.Iterator<androidx.compose.ui.graphics.PathSegment> kotlin.jvm.internal.markers.KMappedMarker {
     method public int calculateSize(optional boolean includeConvertedConics);
     method public androidx.compose.ui.graphics.PathIterator.ConicEvaluation getConicEvaluation();
@@ -802,6 +812,11 @@
     property public static final androidx.compose.ui.graphics.PathSegment DoneSegment;
   }
 
+  public final class PathSvgKt {
+    method public static void addSvg(androidx.compose.ui.graphics.Path, String pathData);
+    method public static String toSvg(androidx.compose.ui.graphics.Path, optional boolean asDocument);
+  }
+
   public final class PixelMap {
     ctor public PixelMap(int[] buffer, int width, int height, int bufferOffset, int stride);
     method public operator long get(@IntRange(from=0L) int x, @IntRange(from=0L) int y);
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 54725ff..8bf049a 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -188,6 +188,14 @@
     method public static android.graphics.Canvas.VertexMode toAndroidVertexMode(int);
   }
 
+  public final class BezierKt {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static long computeHorizontalBounds(androidx.compose.ui.graphics.PathSegment segment, float[] roots, optional int index);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float evaluateCubic(float p1, float p2, float t);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float evaluateY(androidx.compose.ui.graphics.PathSegment segment, float t);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float findFirstCubicRoot(float p0, float p1, float p2, float p3);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float findFirstRoot(androidx.compose.ui.graphics.PathSegment segment, float fraction);
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class BlendMode {
     field public static final androidx.compose.ui.graphics.BlendMode.Companion Companion;
   }
@@ -523,6 +531,33 @@
     method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageBitmap, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class Interval<T> {
+    ctor public Interval(float start, float end, optional T? data);
+    method public final operator boolean contains(float value);
+    method public final T? getData();
+    method public final float getEnd();
+    method public final float getStart();
+    method public final boolean overlaps(androidx.compose.ui.graphics.Interval<T> other);
+    method public final boolean overlaps(float start, float end);
+    property public final T? data;
+    property public final float end;
+    property public final float start;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class IntervalTree<T> {
+    ctor public IntervalTree();
+    method public void addInterval(float start, float end, T? data);
+    method public void clear();
+    method public operator boolean contains(float value);
+    method public operator boolean contains(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> interval);
+    method public androidx.compose.ui.graphics.Interval<T> findFirstOverlap(float start, optional float end);
+    method public androidx.compose.ui.graphics.Interval<T> findFirstOverlap(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> interval);
+    method public java.util.List<androidx.compose.ui.graphics.Interval<T>> findOverlaps(float start, optional float end, optional java.util.List<androidx.compose.ui.graphics.Interval<T>> results);
+    method public java.util.List<androidx.compose.ui.graphics.Interval<T>> findOverlaps(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> interval, optional java.util.List<androidx.compose.ui.graphics.Interval<T>> results);
+    method public operator java.util.Iterator<androidx.compose.ui.graphics.Interval<T>> iterator();
+    method public operator void plusAssign(androidx.compose.ui.graphics.Interval<T> interval);
+  }
+
   @androidx.compose.runtime.Immutable public final class LightingColorFilter extends androidx.compose.ui.graphics.ColorFilter {
     ctor public LightingColorFilter(long multiply, long add);
     method public long getAdd();
@@ -753,6 +788,16 @@
     property public final int NonZero;
   }
 
+  public final class PathHitTester {
+    ctor public PathHitTester();
+    method public operator boolean contains(long position);
+    method public void updatePath(androidx.compose.ui.graphics.Path path, optional float tolerance);
+  }
+
+  public final class PathHitTesterKt {
+    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional float tolerance);
+  }
+
   public interface PathIterator extends java.util.Iterator<androidx.compose.ui.graphics.PathSegment> kotlin.jvm.internal.markers.KMappedMarker {
     method public int calculateSize(optional boolean includeConvertedConics);
     method public androidx.compose.ui.graphics.PathIterator.ConicEvaluation getConicEvaluation();
@@ -838,6 +883,11 @@
     property public static final androidx.compose.ui.graphics.PathSegment DoneSegment;
   }
 
+  public final class PathSvgKt {
+    method public static void addSvg(androidx.compose.ui.graphics.Path, String pathData);
+    method public static String toSvg(androidx.compose.ui.graphics.Path, optional boolean asDocument);
+  }
+
   public final class PixelMap {
     ctor public PixelMap(int[] buffer, int width, int height, int bufferOffset, int stride);
     method public operator long get(@IntRange(from=0L) int x, @IntRange(from=0L) int y);
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 3f9cc28..a28e275 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -43,7 +43,7 @@
                 api(libs.androidx.annotation)
 
                 api(project(":compose:ui:ui-unit"))
-                implementation(project(":compose:runtime:runtime"))
+                implementation("androidx.compose.runtime:runtime:1.6.0")
                 implementation(project(":compose:ui:ui-util"))
                 implementation("androidx.collection:collection:1.4.0")
             }
@@ -59,7 +59,6 @@
             dependsOn(commonMain)
             dependencies {
                 api(libs.skikoCommon)
-                implementation(project(":compose:runtime:runtime"))
             }
         }
 
@@ -73,7 +72,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation("androidx.graphics:graphics-path:1.0.0-beta02")
-                api(libs.androidx.annotation)
                 api("androidx.annotation:annotation-experimental:1.4.0")
             }
         }
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt
new file mode 100644
index 0000000..e935584
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.geometry.Offset
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PathHitTest {
+    @Test
+    fun linesHit() {
+        val path = createSvgPath(SvgShape.Lines)
+        val hitTester = PathHitTester(path)
+        assertTrue(Offset(400f, 370f) in hitTester)
+        assertFalse(Offset(180f, 160f) in hitTester)
+    }
+
+    @Test
+    fun cubicsHit() {
+        val path = createSvgPath(SvgShape.Cubics)
+        val hitTester = PathHitTester(path)
+        assertTrue(Offset(100f, 275f) in hitTester) // cubics and lines
+        assertTrue(Offset(370f, 40f) in hitTester) // on a scanline with only cubics
+        assertFalse(Offset(370f, 280f) in hitTester) // cutout
+        assertFalse(Offset(380f, 600f) in hitTester) // outside
+    }
+
+    @Test
+    fun quadsHit() {
+        val path = createSvgPath(SvgShape.Quads)
+        val hitTester = PathHitTester(path)
+        assertTrue(Offset(50f, 180f) in hitTester) // quads and lines
+        assertTrue(Offset(225f, 15f) in hitTester) // on a scanline with only quads
+        assertFalse(Offset(270f, 175f) in hitTester) // cutout
+        assertFalse(Offset(275f, 425f) in hitTester) // outside
+    }
+
+    @Test
+    fun fillTypesHit() {
+        val nonZero = createSvgPath(SvgShape.FillTypes).apply { fillType = PathFillType.NonZero }
+        val evenOdd = createSvgPath(SvgShape.FillTypes)
+
+        val nonZeroHitTester = PathHitTester(nonZero)
+        val evenOddHitTester = PathHitTester(evenOdd)
+        assertTrue(Offset(350f, 350f) in nonZeroHitTester)
+        assertFalse(Offset(350f, 350f) in evenOddHitTester)
+    }
+}
+
+/**
+ * Creates a path from the specified shape, using the EvenOdd fill type to match SVG.
+ * The returned path has its origin set to 0,0 for convenience.
+ */
+private fun createSvgPath(svgShape: SvgShape) = Path().apply {
+    addSvg(svgShape.pathData)
+    val bounds = getBounds()
+    translate(Offset(-bounds.left, -bounds.top))
+    fillType = PathFillType.EvenOdd
+}
+
+/* ktlint-disable max-line-length */
+private enum class SvgShape(val pathData: String) {
+    Cubics(
+        "M958.729,822.904L958.729,1086.67C958.729,1159.45 899.635,1218.55 826.848,1218.55L563.086,1218.55L355.844,1444.63L450.045,1218.55L337.004,1218.55C264.217,1218.55 205.123,1159.45 205.123,1086.67L205.123,822.904C205.123,750.117 264.217,691.023 337.004,691.023L826.848,691.023C899.635,691.023 958.729,750.117 958.729,822.904ZM581.925,850.888C544.745,781.585 470.384,781.585 433.204,816.236C396.023,850.888 396.023,920.191 433.204,989.493C459.23,1041.47 526.155,1093.45 581.925,1128.1C637.696,1093.45 704.621,1041.47 730.647,989.493C767.829,920.191 767.829,850.888 730.647,816.236C693.467,781.585 619.106,781.585 581.925,850.888Z"
+    ),
+    Quads(
+        "M 664.72,242.306 L 664.72,423.585 Q 649.1189999999999,514.2250000000001 574.08,514.225 L 392.801,514.225 L 250.367,669.607 L 315.11,514.225 L 237.419,514.225 Q 146.779,498.62249999999995 146.779,423.585 L 146.779,242.306 Q 162.38,151.666 237.419,151.666 L 574.08,151.666 Q 664.72,167.267 664.72,242.306 M 375.55,220.052 Q 355.947,196.49699999999999 334.296,208.998 Q 310.741,228.5995 323.242,250.252 Q 294.48900000000003,239.65350000000004 281.988,261.306 Q 271.3895000000001,290.05899999999997 293.042,302.56 Q 262.842,307.758 262.842,332.76 Q 268.0400000000001,362.9599999999999 293.042,362.96 Q 269.48699999999997,382.5615 281.988,404.214 Q 301.58950000000004,427.769 323.242,415.268 Q 312.6435,444.02099999999996 334.296,456.522 Q 363.0490000000001,467.1189999999999 375.55,445.468 Q 380.74850000000004,475.66700000000003 405.749,475.667 Q 435.94899999999996,470.46849999999995 435.949,445.468 Q 455.552,469.02150000000006 477.203,456.522 Q 500.75800000000004,436.919 488.257,415.268 Q 517.01,425.865 529.511,404.214 Q 540.1095,375.461 518.457,362.96 Q 548.6569999999999,357.76200000000006 548.657,332.76 Q 543.4590000000001,302.56000000000006 518.457,302.56 Q 542.0120000000001,282.95849999999996 529.511,261.306 Q 509.9095,237.751 488.257,250.252 Q 498.8555,221.499 477.203,208.998 Q 448.45000000000005,198.3995 435.949,220.052 Q 430.751,189.85200000000003 405.749,189.852 Q 375.55000000000007,195.04999999999995 375.55,220.052Z"
+    ),
+    Lines(
+        "M741.323,698.969L825.045,956.64L1095.98,956.64L876.789,1115.89L960.511,1373.56L741.323,1214.31L522.134,1373.56L605.857,1115.89L386.668,956.64L657.6,956.64L741.323,698.969Z"
+    ),
+    FillTypes(
+        "M570.019,673.111L580.895,726.063C601.945,729.284 622.576,734.812 642.417,742.548L678.312,702.128C698.399,711.261 717.552,722.32 735.505,735.149L718.449,786.445C735.068,799.759 750.171,814.862 763.485,831.481L814.781,814.425C827.61,832.378 838.669,851.531 847.801,871.618L807.382,907.513C815.118,927.354 820.646,947.985 823.867,969.035L876.819,979.911C878.953,1001.87 878.953,1023.99 876.819,1045.95L823.867,1056.83C820.646,1077.88 815.118,1098.51 807.382,1118.35L847.801,1154.25C838.669,1174.33 827.61,1193.49 814.781,1211.44L763.485,1194.38C750.171,1211 735.068,1226.1 718.449,1239.42L735.505,1290.71C717.552,1303.54 698.399,1314.6 678.312,1323.74L642.417,1283.32C622.576,1291.05 601.945,1296.58 580.895,1299.8L570.019,1352.75C548.057,1354.89 525.94,1354.89 503.978,1352.75L493.101,1299.8C472.051,1296.58 451.42,1291.05 431.58,1283.32L395.684,1323.74C375.598,1314.6 356.444,1303.54 338.492,1290.71L355.548,1239.42C338.929,1226.1 323.826,1211 310.511,1194.38L259.215,1211.44C246.386,1193.49 235.328,1174.33 226.195,1154.25L266.614,1118.35C258.879,1098.51 253.351,1077.88 250.13,1056.83L197.178,1045.95C195.044,1023.99 195.044,1001.87 197.178,979.911L250.13,969.035C253.351,947.985 258.879,927.354 266.614,907.513L226.195,871.618C235.328,851.531 246.386,832.378 259.215,814.425L310.511,831.481C323.826,814.862 338.929,799.759 355.548,786.445L338.492,735.149C356.444,722.32 375.598,711.261 395.684,702.128L431.58,742.548C451.42,734.812 472.051,729.284 493.101,726.063L503.978,673.111C525.94,670.977 548.057,670.977 570.019,673.111ZM536.998,944.648C574.685,944.648 605.282,975.245 605.282,1012.93C605.282,1050.62 574.685,1081.22 536.998,1081.22C499.311,1081.22 468.714,1050.62 468.714,1012.93C468.714,975.245 499.311,944.648 536.998,944.648Z"
+    )
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathSvgTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathSvgTest.kt
new file mode 100644
index 0000000..c68723b
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathSvgTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 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.compose.ui.graphics
+
+import androidx.compose.ui.geometry.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.runner.RunWith
+
+/* ktlint-disable max-line-length */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PathSvgTest {
+    @Test
+    fun emptyPath() {
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0.0 0.0 0.0 0.0">
+            </svg>
+
+            """.trimIndent(),
+            Path().toSvg(asDocument = true)
+        )
+
+        assertTrue(Path().toSvg().isEmpty())
+    }
+
+    // API 21 has a different behavior for the bounds of an move only path
+    @Test
+    @SdkSuppress(minSdkVersion = 24)
+    fun singleMove() {
+        val svg = Path().apply { moveTo(10.0f, 10.0f) }.toSvg(asDocument = true)
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="10.0 10.0 0.0 0.0">
+              <path d="M10.0 10.0"/>
+            </svg>
+
+            """.trimIndent(),
+            svg
+        )
+    }
+
+    @Test
+    fun twoPaths() {
+        val svg = Path().apply {
+            addRect(Rect(0.0f, 0.0f, 10.0f, 10.0f), Path.Direction.Clockwise)
+            addRect(Rect(20.0f, 20.0f, 50.0f, 50.0f), Path.Direction.Clockwise)
+        }.toSvg(asDocument = true)
+
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0.0 0.0 50.0 50.0">
+              <path d="M0.0 0.0L10.0 0.0 10.0 10.0 0.0 10.0ZM20.0 20.0L50.0 20.0 50.0 50.0 20.0 50.0Z"/>
+            </svg>
+
+            """.trimIndent(),
+            svg
+        )
+    }
+
+    @Test
+    fun bezier() {
+        val svg = Path().apply {
+            moveTo(10.0f, 10.0f)
+            cubicTo(20.0f, 20.0f, 30.0f, 30.0f, 40.0f, 40.0f)
+            quadraticTo(50.0f, 50.0f, 60.0f, 60.0f)
+        }.toSvg(asDocument = true)
+
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="10.0 10.0 50.0 50.0">
+              <path d="M10.0 10.0C20.0 20.0 30.0 30.0 40.0 40.0Q50.0 50.0 60.0 60.0"/>
+            </svg>
+
+            """.trimIndent(),
+            svg
+        )
+    }
+
+    @Test
+    fun dataOnly() {
+        val svg = Path().apply {
+            addRect(Rect(0.0f, 0.0f, 10.0f, 10.0f), Path.Direction.Clockwise)
+            addRect(Rect(20.0f, 20.0f, 50.0f, 50.0f), Path.Direction.Clockwise)
+        }.toSvg()
+
+        assertEquals(
+            "M0.0 0.0L10.0 0.0 10.0 10.0 0.0 10.0ZM20.0 20.0L50.0 20.0 50.0 50.0 20.0 50.0Z",
+            svg
+        )
+    }
+
+    @Test
+    fun hole() {
+        val hole = Path().apply {
+            addRect(Rect(0.0f, 0.0f, 80.0f, 80.0f), Path.Direction.Clockwise)
+            addRect(Rect(20.0f, 20.0f, 50.0f, 50.0f), Path.Direction.Clockwise)
+        }
+
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0.0 0.0 80.0 80.0">
+              <path d="M0.0 0.0L80.0 0.0 80.0 80.0 0.0 80.0ZM20.0 20.0L50.0 20.0 50.0 50.0 20.0 50.0Z"/>
+            </svg>
+
+            """.trimIndent(),
+            hole.toSvg(asDocument = true)
+        )
+
+        hole.fillType = PathFillType.EvenOdd
+
+        assertEquals(
+            """
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0.0 0.0 80.0 80.0">
+              <path fill-rule="evenodd" d="M0.0 0.0L80.0 0.0 80.0 80.0 0.0 80.0ZM20.0 20.0L50.0 20.0 50.0 50.0 20.0 50.0Z"/>
+            </svg>
+
+            """.trimIndent(),
+            hole.toSvg(asDocument = true)
+        )
+    }
+
+    @Test
+    fun addSvg() {
+        val twoRects = Path().apply {
+            addSvg("M0.0 0.0L10.0 0.0 10.0 10.0 0.0 10.0ZM20.0 20.0L50.0 20.0 50.0 50.0 20.0 50.0Z")
+        }
+        val reference = Path().apply {
+            addRect(Rect(0.0f, 0.0f, 10.0f, 10.0f), Path.Direction.Clockwise)
+            addRect(Rect(20.0f, 20.0f, 50.0f, 50.0f), Path.Direction.Clockwise)
+        }
+        assertPathEquals(reference, twoRects)
+    }
+
+    @Test(IllegalArgumentException::class)
+    fun addInvalidSvg() {
+        Path().apply {
+            // 'K' is an invalid SVG instruction
+            addSvg("M0.0 0.0K10.0 0.0 10.0 10.0 0.0 10.0Z")
+        }
+    }
+
+    @Test
+    fun roundTrip() {
+        val original = Path().apply {
+            addRect(Rect(0.0f, 0.0f, 10.0f, 10.0f), Path.Direction.Clockwise)
+            addRect(Rect(20.0f, 20.0f, 50.0f, 50.0f), Path.Direction.Clockwise)
+        }
+        val svg = original.toSvg()
+        val path = Path().apply { addSvg(svg) }
+
+        assertPathEquals(original, path)
+    }
+}
+/* ktlint-enable max-line-length */
+
+private fun assertPathEquals(a: Path, b: Path) {
+    val ita = a.iterator()
+    val itb = b.iterator()
+
+    while (ita.hasNext()) {
+        assertTrue(itb.hasNext())
+        assertEquals(ita.next(), itb.next())
+    }
+
+    assertFalse(itb.hasNext())
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
new file mode 100644
index 0000000..f6b4e6e
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
@@ -0,0 +1,1048 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.annotation.RestrictTo
+import androidx.collection.FloatFloatPair
+import androidx.compose.ui.util.fastCbrt
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.fastMaxOf
+import androidx.compose.ui.util.fastMinOf
+import androidx.compose.ui.util.lerp
+import kotlin.math.abs
+import kotlin.math.acos
+import kotlin.math.cos
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sign
+import kotlin.math.sqrt
+
+private const val Tau = Math.PI * 2.0
+private const val Epsilon = 1e-7
+// We use a fairly high epsilon here because it's post double->float conversion
+// and because we use a fast approximation of cbrt(). The epsilon we use here is
+// the max error of fastCbrt() in the -1f..1f range.
+private const val FloatEpsilon = 8.3446500e-7f
+
+/**
+ * Evaluate the specified [segment] at position [t] and returns the X
+ * coordinate of the segment's curve at that position.
+ */
+private fun evaluateX(
+    segment: PathSegment,
+    t: Float
+): Float {
+    val points = segment.points
+
+    return when (segment.type) {
+        PathSegment.Type.Move -> points[0]
+
+        PathSegment.Type.Line -> {
+            evaluateLine(
+                points[0],
+                points[2],
+                t
+            )
+        }
+
+        PathSegment.Type.Quadratic -> {
+            evaluateQuadratic(
+                points[0],
+                points[2],
+                points[4],
+                t
+            )
+        }
+
+        // We convert all conics to cubics, won't happen
+        PathSegment.Type.Conic -> Float.NaN
+
+        PathSegment.Type.Cubic -> {
+            evaluateCubic(
+                points[0],
+                points[2],
+                points[4],
+                points[6],
+                t
+            )
+        }
+
+        PathSegment.Type.Close -> Float.NaN
+        PathSegment.Type.Done -> Float.NaN
+    }
+}
+
+/**
+ * Evaluate the specified [segment] at position [t] and returns the Y
+ * coordinate of the segment's curve at that position.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun evaluateY(
+    segment: PathSegment,
+    t: Float
+): Float {
+    val points = segment.points
+
+    return when (segment.type) {
+        PathSegment.Type.Move -> points[1]
+
+        PathSegment.Type.Line -> {
+            evaluateLine(
+                points[1],
+                points[3],
+                t
+            )
+        }
+
+        PathSegment.Type.Quadratic -> {
+            evaluateQuadratic(
+                points[1],
+                points[3],
+                points[5],
+                t
+            )
+        }
+
+        // We convert all conics to cubics, won't happen
+        PathSegment.Type.Conic -> Float.NaN
+
+        PathSegment.Type.Cubic -> {
+            evaluateCubic(
+                points[1],
+                points[3],
+                points[5],
+                points[7],
+                t
+            )
+        }
+
+        PathSegment.Type.Close -> Float.NaN
+        PathSegment.Type.Done -> Float.NaN
+    }
+}
+
+private fun evaluateLine(
+    p0y: Float,
+    p1y: Float,
+    t: Float
+) = (p1y - p0y) * t + p0y
+
+private fun evaluateQuadratic(
+    p0: Float,
+    p1: Float,
+    p2: Float,
+    t: Float
+): Float {
+    val by = 2.0f * (p1 - p0)
+    val ay = p2 - 2.0f * p1 + p0
+    return (ay * t + by) * t + p0
+}
+
+private fun evaluateCubic(
+    p0: Float,
+    p1: Float,
+    p2: Float,
+    p3: Float,
+    t: Float
+): Float {
+    val a = p3 + 3.0f * (p1 - p2) - p0
+    val b = 3.0f * (p2 - 2.0f * p1 + p0)
+    val c = 3.0f * (p1 - p0)
+    return ((a * t + b) * t + c) * t + p0
+}
+
+/**
+ * Evaluates a cubic Bézier curve at position [t] along the curve. The curve is
+ * defined by the start point (0, 0), the end point (0, 0) and two control points
+ * of respective coordinates [p1] and [p2].
+ */
+@Suppress("UnnecessaryVariable")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun evaluateCubic(
+    p1: Float,
+    p2: Float,
+    t: Float
+): Float {
+    val a = 1.0f / 3.0f + (p1 - p2)
+    val b = (p2 - 2.0f * p1)
+    val c = p1
+    return 3.0f * ((a * t + b) * t + c) * t
+}
+
+/**
+ * Finds the first real root of the specified [segment].
+ * If no root can be found, this method returns [Float.NaN].
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun findFirstRoot(
+    segment: PathSegment,
+    fraction: Float
+): Float {
+    val points = segment.points
+    return when (segment.type) {
+        PathSegment.Type.Move -> Float.NaN
+
+        PathSegment.Type.Line -> {
+            findFirstLineRoot(
+                points[0] - fraction,
+                points[2] - fraction,
+            )
+        }
+
+        PathSegment.Type.Quadratic -> findFirstQuadraticRoot(
+            points[0] - fraction,
+            points[2] - fraction,
+            points[4] - fraction
+        )
+
+        // We convert all conics to cubics, won't happen
+        PathSegment.Type.Conic -> Float.NaN
+
+        PathSegment.Type.Cubic -> findFirstCubicRoot(
+            points[0] - fraction,
+            points[2] - fraction,
+            points[4] - fraction,
+            points[6] - fraction
+        )
+
+        PathSegment.Type.Close -> Float.NaN
+        PathSegment.Type.Done -> Float.NaN
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun findFirstLineRoot(p0: Float, p1: Float) =
+    clampValidRootInUnitRange(-p0 / (p1 - p0))
+
+/**
+ * Finds the first real root of a quadratic Bézier curve:
+ * - [p0]: coordinate of the start point
+ * - [p1]: coordinate of the control point
+ * - [p2]: coordinate of the end point
+ *
+ * If no root can be found, this method returns [Float.NaN].
+ */
+private fun findFirstQuadraticRoot(
+    p0: Float,
+    p1: Float,
+    p2: Float
+): Float {
+    val a = p0.toDouble()
+    val b = p1.toDouble()
+    val c = p2.toDouble()
+    val d = a - 2.0 * b + c
+
+    if (d != 0.0) {
+        val v1 = -sqrt(b * b - a * c)
+        val v2 = -a + b
+
+        val root = clampValidRootInUnitRange((-(v1 + v2) / d).toFloat())
+        if (!root.isNaN()) return root
+
+        return clampValidRootInUnitRange(((v1 - v2) / d).toFloat())
+    } else if (b != c) {
+        return clampValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat())
+    }
+
+    return Float.NaN
+}
+
+/**
+ * Finds the first real root of a cubic Bézier curve:
+ * - [p0]: coordinate of the start point
+ * - [p1]: coordinate of the first control point
+ * - [p2]: coordinate of the second control point
+ * - [p3]: coordinate of the end point
+ *
+ * If no root can be found, this method returns [Float.NaN].
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun findFirstCubicRoot(
+    p0: Float,
+    p1: Float,
+    p2: Float,
+    p3: Float
+): Float {
+    // This function implements Cardano's algorithm as described in "A Primer on Bézier Curves":
+    // https://pomax.github.io/bezierinfo/#yforx
+    //
+    // The math used to find the roots is explained in "Solving the Cubic Equation":
+    // http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
+
+    var a = 3.0 * (p0 - 2.0 * p1 + p2)
+    var b = 3.0 * (p1 - p0)
+    var c = p0.toDouble()
+    val d = -p0 + 3.0 * (p1 - p2) + p3
+
+    // Not a cubic
+    if (d.closeTo(0.0)) {
+        // Not a quadratic
+        if (a.closeTo(0.0)) {
+            // No solutions
+            if (b.closeTo(0.0)) {
+                return Float.NaN
+            }
+            return clampValidRootInUnitRange((-c / b).toFloat())
+        } else {
+            val q = sqrt(b * b - 4.0 * a * c)
+            val a2 = 2.0 * a
+
+            val root = clampValidRootInUnitRange(((q - b) / a2).toFloat())
+            if (!root.isNaN()) return root
+
+            return clampValidRootInUnitRange(((-b - q) / a2).toFloat())
+        }
+    }
+
+    a /= d
+    b /= d
+    c /= d
+
+    val o3 = (3.0 * b - a * a) / 9.0
+    val q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
+    val discriminant = q2 * q2 + o3 * o3 * o3
+    val a3 = a / 3.0
+
+    if (discriminant < 0.0) {
+        val mp33 = -(o3 * o3 * o3)
+        val r = sqrt(mp33)
+        val t = -q2 / r
+        val cosPhi = t.fastCoerceIn(-1.0, 1.0)
+        val phi = acos(cosPhi)
+        val t1 = 2.0f * fastCbrt(r.toFloat())
+
+        var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a3).toFloat())
+        if (!root.isNaN()) return root
+
+        root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a3).toFloat())
+        if (!root.isNaN()) return root
+
+        return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a3).toFloat())
+    } else if (discriminant == 0.0) { // TODO: closeTo(0.0)?
+        val u1 = -fastCbrt(q2.toFloat())
+
+        val root = clampValidRootInUnitRange(2.0f * u1 - a3.toFloat())
+        if (!root.isNaN()) return root
+
+        return clampValidRootInUnitRange(-u1 - a3.toFloat())
+    }
+
+    val sd = sqrt(discriminant)
+    val u1 = fastCbrt((-q2 + sd).toFloat())
+    val v1 = fastCbrt((q2 + sd).toFloat())
+
+    return clampValidRootInUnitRange((u1 - v1 - a3).toFloat())
+}
+
+/**
+ * Finds the real root of a line defined by the X coordinates of its start ([p0])
+ * and end ([p1]) points. The root, if any, is written in the [roots] array at
+ * [index]. Returns 1 if a root was found, 0 otherwise.
+ */
+@Suppress("NOTHING_TO_INLINE")
+private inline fun findLineRoot(p0: Float, p1: Float, roots: FloatArray, index: Int = 0) =
+    writeValidRootInUnitRange(-p0 / (p1 - p0), roots, index)
+
+/**
+ * Finds the real roots of a quadratic Bézier curve. To find the roots, only the X
+ * coordinates of the four points are required:
+ * - [p0]: x coordinate of the start point
+ * - [p1]: x coordinate of the control point
+ * - [p2]: x coordinate of the end point
+ *
+ * Any root found is written in the [roots] array, starting at [index]. The
+ * function returns the number of roots found and written to the array.
+ */
+private fun findQuadraticRoots(
+    p0: Float,
+    p1: Float,
+    p2: Float,
+    roots: FloatArray,
+    index: Int = 0
+): Int {
+    val a = p0.toDouble()
+    val b = p1.toDouble()
+    val c = p2.toDouble()
+    val d = a - 2.0 * b + c
+
+    var rootCount = 0
+
+    if (d != 0.0) {
+        val v1 = -sqrt(b * b - a * c)
+        val v2 = -a + b
+
+        rootCount += writeValidRootInUnitRange(
+            (-(v1 + v2) / d).toFloat(), roots, index
+        )
+        rootCount += writeValidRootInUnitRange(
+            ((v1 - v2) / d).toFloat(), roots, index + rootCount
+        )
+
+        // Returns the roots sorted
+        if (rootCount > 1) {
+            val s = roots[index]
+            val t = roots[index + 1]
+            if (s > t) {
+                roots[index] = t
+                roots[index + 1] = s
+            } else if (s == t) {
+                // Don't report identical roots
+                rootCount--
+            }
+        }
+    } else if (b != c) {
+        rootCount += writeValidRootInUnitRange(
+            ((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat(), roots, index
+        )
+    }
+
+    return rootCount
+}
+
+/**
+ * Finds the roots of the derivative of the curve described by [segment].
+ * The roots, if any, are written in the [roots] array starting at [index].
+ * The function returns the number of roots founds and written into the array.
+ * The [roots] array must be able to hold at least 5 floats starting at [index].
+ */
+private fun findDerivativeRoots(
+    segment: PathSegment,
+    roots: FloatArray,
+    index: Int = 0,
+): Int {
+    val points = segment.points
+    return when (segment.type) {
+        PathSegment.Type.Move -> 0
+
+        PathSegment.Type.Line -> 0
+
+        PathSegment.Type.Quadratic -> {
+            // Line derivative of a quadratic function
+            // We do the computation inline to avoid using arrays of other data
+            // structures to return the result
+            val d0 = 2 * (points[2] - points[0])
+            val d1 = 2 * (points[4] - points[2])
+            findLineRoot(d0, d1, roots, index)
+        }
+
+        // We convert all conics to cubics, won't happen
+        PathSegment.Type.Conic -> 0
+
+        PathSegment.Type.Cubic -> {
+            // Quadratic derivative of a cubic function
+            // We do the computation inline to avoid using arrays of other data
+            // structures to return the result
+            val d0 = 3.0f * (points[2] - points[0])
+            val d1 = 3.0f * (points[4] - points[2])
+            val d2 = 3.0f * (points[6] - points[4])
+            val count = findQuadraticRoots(d0, d1, d2, roots, index)
+
+            // Compute the second derivative as a line
+            val dd0 = 2.0f * (d1 - d0)
+            val dd1 = 2.0f * (d2 - d1)
+            // Return the sum of the roots count
+            count + findLineRoot(dd0, dd1, roots, index + count)
+        }
+
+        PathSegment.Type.Close -> 0
+        PathSegment.Type.Done -> 0
+    }
+}
+
+/**
+ * Computes the horizontal bounds of the specified [segment] and returns
+ * a pair of floats containing the lowest bound as the first value, and
+ * the highest bound as the second value.
+ *
+ * The [roots] array is used as a scratch array and must be able to hold
+ * at least 5 floats.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun computeHorizontalBounds(
+    segment: PathSegment,
+    roots: FloatArray,
+    index: Int = 0
+): FloatFloatPair {
+    val count = findDerivativeRoots(segment, roots, index)
+    var minX = min(segment.startX, segment.endX)
+    var maxX = max(segment.startX, segment.endX)
+
+    for (i in 0 until count) {
+        val t = roots[i]
+        val x = evaluateX(segment, t)
+        minX = min(minX, x)
+        maxX = max(maxX, x)
+    }
+
+    return FloatFloatPair(minX, maxX)
+}
+
+/**
+ * Computes the vertical bounds of the specified [segment] and returns
+ * a pair of floats containing the lowest bound as the first value, and
+ * the highest bound as the second value.
+ *
+ * The [roots] array is used as a scratch array and must be able to hold
+ * at least 5 floats.
+ */
+internal fun computeVerticalBounds(
+    segment: PathSegment,
+    roots: FloatArray,
+    index: Int = 0
+): FloatFloatPair {
+    val count = findDerivativeRoots(segment, roots, index)
+    var minX = min(segment.startY, segment.endY)
+    var maxX = max(segment.startY, segment.endY)
+
+    for (i in 0 until count) {
+        val t = roots[i]
+        val x = evaluateY(segment, t)
+        minX = min(minX, x)
+        maxX = max(maxX, x)
+    }
+
+    return FloatFloatPair(minX, maxX)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun Double.closeTo(b: Double, epsilon: Double = Epsilon) = abs(this - b) < epsilon
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun Float.closeTo(b: Float, epsilon: Float = FloatEpsilon) =
+    abs(this - b) < epsilon
+
+/**
+ * Returns [r] if it's in the [0..1] range, and [Float.NaN] otherwise. To account
+ * for numerical imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon]
+ * range are considered to be in the [0..1] range and clamped appropriately.
+ */
+@Suppress("NOTHING_TO_INLINE")
+private inline fun clampValidRootInUnitRange(r: Float): Float = if (r < 0.0f) {
+    if (r >= -FloatEpsilon) 0.0f else Float.NaN
+} else if (r > 1.0f) {
+    if (r <= 1.0f + FloatEpsilon) 1.0f else Float.NaN
+} else {
+    r
+}
+
+/**
+ * Writes [r] in the [roots] array at [index], if it's in the [0..1] range. To account
+ * for numerical imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon]
+ * range are considered to be in the [0..1] range and clamped appropriately. Returns 0 if
+ * no value was written, 1 otherwise.
+ */
+private fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
+    val v = clampValidRootInUnitRange(r)
+    roots[index] = v
+    return if (v.isNaN()) 0 else 1
+}
+
+/**
+ * Computes the winding value for a position [x]/[y] and the line defined by the [points]
+ * array. The array must contain at least 4 floats defining the start and end points of the
+ * line as pairs of x/y coordinates.
+ */
+internal fun lineWinding(points: FloatArray, x: Float, y: Float): Int {
+    val x0 = points[0]
+    var y0 = points[1]
+    val yo = y0
+    val x1 = points[2]
+    var y1 = points[3]
+
+    // Compute dy before we swap
+    val dy = y1 - y0
+    var direction = 1
+
+    if (y0 > y1) {
+        y0 = y1
+        y1 = yo
+        direction = -1
+    }
+
+    // We exclude the end point
+    if (y < y0 || y >= y1) {
+        return 0
+    }
+
+    // TODO: check if on the curve
+
+    val crossProduct = (x1 - x0) * (y - yo) - dy * (x - x0)
+    // The point is on the line
+    if (crossProduct == 0.0f) {
+        // TODO: check if we are on the line but exclude x1 and y1
+        direction = 0
+    } else if (crossProduct.sign.toInt() == direction) {
+        direction = 0
+    }
+
+    return direction
+}
+
+/**
+ * Returns whether the quadratic Bézier curve defined the start, control, and points
+ * [y0], [y1], and [y2] is monotonic on the Y axis.
+ */
+private fun isQuadraticMonotonic(y0: Float, y1: Float, y2: Float): Boolean =
+    (y0 - y1).sign + (y1 - y2).sign != 0.0f
+
+/**
+ * Computes the winding value for a position [x]/[y] and the quadratic Bézier curve defined by
+ * the [points] array. The array must contain at least 6 floats defining the start, control,
+ * and end points of the curve as pairs of x/y coordinates.
+ *
+ * The [tmpQuadratics] array is a scratch array used to hold temporary values and must
+ * contain at least 10 floats. Its content can be ignored after calling this function.
+ *
+ * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is
+ * used to hold temporary values and its content can be ignored after calling this
+ * function.
+ */
+internal fun quadraticWinding(
+    points: FloatArray,
+    x: Float,
+    y: Float,
+    tmpQuadratics: FloatArray,
+    tmpRoots: FloatArray
+): Int {
+    val y0 = points[1]
+    val y1 = points[3]
+    val y2 = points[5]
+
+    if (isQuadraticMonotonic(y0, y1, y2)) {
+        return monotonicQuadraticWinding(points, 0, x, y, tmpRoots)
+    }
+
+    val rootCount = quadraticToMonotonicQuadratics(points, tmpQuadratics)
+
+    var winding = monotonicQuadraticWinding(tmpQuadratics, 0, x, y, tmpRoots)
+    if (rootCount > 0) {
+        winding += monotonicQuadraticWinding(tmpQuadratics, 4, x, y, tmpRoots)
+    }
+    return winding
+}
+
+/**
+ * Computes the winding value of a _monotonic_ quadratic Bézier curve for the given
+ * [x] and [y] coordinates. The curve is defined as 6 floats in the [points] array
+ * corresponding to the start, control, and end points. The floats are stored at
+ * position [offset] in the array, meaning the array must hold at least [offset] + 6
+ * values.
+ *
+ * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is
+ * used to hold temporary values and its content can be ignored after calling this
+ * function.
+ */
+private fun monotonicQuadraticWinding(
+    points: FloatArray,
+    offset: Int,
+    x: Float,
+    y: Float,
+    tmpRoots: FloatArray
+): Int {
+    var y0 = points[offset + 1]
+    var y2 = points[offset + 5]
+
+    var direction = 1
+    if (y0 > y2) {
+        val swap = y2
+        y2 = y0
+        y0 = swap
+        direction = -1
+    }
+
+    // Exclude the end point
+    if (y < y0 || y >= y2) return 0
+
+    // TODO: check if on the curve
+
+    y0 = points[offset + 1]
+    val y1 = points[offset + 3]
+    y2 = points[offset + 5]
+
+    val rootCount = findQuadraticRoots(
+        y0 - 2.0f * y1 + y2,
+        2.0f * (y1 - y0),
+        y0 - y,
+        tmpRoots
+    )
+
+    val xt = if (rootCount == 0) {
+        points[(1 - direction) * 2]
+    } else {
+        evaluateQuadratic(points[0], points[2], points[4], tmpRoots[0])
+    }
+
+    if (xt.closeTo(x)) {
+        if (x != points[4] || y != y2) {
+            // TODO: on the curve
+            return 0
+        }
+    }
+
+    return if (xt < x) direction else 0
+}
+
+/**
+ * Splits the specified [quadratic] Bézier curve into 1 or 2 monotonic quadratic
+ * Bézier curves. The results are stored in the [dst] array. Both the input
+ * [quadratic] and the output [dst] arrays store the curves as 3 pairs of floats
+ * defined by the start, control, and end points. In the [dst] array, successive curves
+ * share a point: the end point of the first curve is the start point of the second
+ * curve. As a result this function will output at most 10 values in the [dst] array
+ * (6 floats per curve, minus 2 for a shared point).
+ *
+ * The function returns the number of splits: if 0 is returned, the [dst] array contains
+ * a single quadratic curve, if 1 is returned, the array contains 2 curves with a shared
+ * point.
+ */
+private fun quadraticToMonotonicQuadratics(quadratic: FloatArray, dst: FloatArray): Int {
+    val y0 = quadratic[1]
+    var y1 = quadratic[3]
+    val y2 = quadratic[5]
+
+    if (!isQuadraticMonotonic(y0, y1, y2)) {
+        val t = unitDivide(y0 - y1, y0 - y1 - y1 + y2)
+        if (!t.isNaN()) {
+            splitQuadraticAt(quadratic, dst, t)
+            return 1
+        }
+        // force the curve to be monotonic since the division above failed
+        y1 = if (abs(y0 - y1) < abs(y1 - y2)) y0 else y2
+    }
+
+    quadratic.copyInto(dst, 0, 0, 6)
+    dst[3] = y1
+
+    return 0
+}
+
+/**
+ * Splits the specified [src] quadratic Bézier curve into two quadratic Bézier curves
+ * at position [t] (in the range 0..1), and stores the results in [dst]. The [dst]
+ * array must hold at least 10 floats. See [quadraticToMonotonicQuadratics] for more
+ * details.
+ */
+private fun splitQuadraticAt(src: FloatArray, dst: FloatArray, t: Float) {
+    val p0x = src[0]
+    val p0y = src[1]
+    val p1x = src[2]
+    val p1y = src[3]
+    val p2x = src[4]
+    val p2y = src[5]
+
+    val abx = lerp(p0x, p1x, t)
+    val aby = lerp(p0y, p1y, t)
+
+    dst[0] = p0x
+    dst[1] = p0y
+    dst[2] = abx
+    dst[3] = aby
+
+    val bcx = lerp(p1x, p2x, t)
+    val bcy = lerp(p1y, p2y, t)
+
+    val abcx = lerp(abx, bcx, t)
+    val abcy = lerp(aby, bcy, t)
+
+    dst[4] = abcx
+    dst[5] = abcy
+    dst[6] = bcx
+    dst[7] = bcy
+    dst[8] = p2x
+    dst[9] = p2y
+}
+
+/**
+ * Performs the division [x]/[y] and returns the result. If the division is invalid,
+ * for instance if it would leads to [Float.POSITIVE_INFINITY] or if it underflows,
+ * this function returns [Float.NaN].
+ */
+private fun unitDivide(x: Float, y: Float): Float {
+    var n = x
+    var d = y
+
+    if (n < 0) {
+        n = -n
+        d = -d
+    }
+
+    if (d == 0.0f || n == 0.0f || n >= d) {
+        return Float.NaN
+    }
+
+    val r = n / d
+    if (r == 0.0f) {
+        return Float.NaN
+    }
+
+    return r
+}
+
+/**
+ * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by
+ * the [points] array. The array must contain at least 8 floats defining the start, 2 control,
+ * and end points of the curve as pairs of x/y coordinates.
+ *
+ * The [tmpCubics] array is a scratch array used to hold temporary values and must
+ * contain at least 20 floats. Its content can be ignored after calling this function.
+ *
+ * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is
+ * used to hold temporary values and its content can be ignored after calling this
+ * function.
+ */
+internal fun cubicWinding(
+    points: FloatArray,
+    x: Float,
+    y: Float,
+    tmpCubics: FloatArray,
+    tmpRoots: FloatArray
+): Int {
+    val splits = cubicToMonotonicCubics(points, tmpCubics, tmpRoots)
+
+    var winding = 0
+    for (i in 0..splits) {
+        winding += monotonicCubicWinding(tmpCubics, i * 3 * 2, x, y)
+    }
+    return winding
+}
+
+/**
+ * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by
+ * the [points] array, starting at the specified [offset]. The array must contain at least
+ * 10 floats after [offset] defining the start, control, and end points of the curve as pairs
+ * of x/y coordinates.
+ */
+private fun monotonicCubicWinding(points: FloatArray, offset: Int, x: Float, y: Float): Int {
+    var y0 = points[offset + 1]
+    var y3 = points[offset + 7]
+
+    var direction = 1
+    if (y0 > y3) {
+        val swap = y3
+        y3 = y0
+        y0 = swap
+        direction = -1
+    }
+
+    // Exclude the end point
+    if (y < y0 || y >= y3) return 0
+
+    // TODO: check if on the curve
+
+    val x0 = points[offset + 0]
+    val x1 = points[offset + 2]
+    val x2 = points[offset + 4]
+    val x3 = points[offset + 6]
+
+    // Reject if outside of the bounds
+    val min = fastMinOf(x0, x1, x2, x3)
+    if (x < min) return 0
+
+    val max = fastMaxOf(x0, x1, x2, x3)
+    if (x > max) return direction
+
+    // Re-fetch y0 and y3 since we may have swapped them
+    y0 = points[offset + 1]
+    val y1 = points[offset + 3]
+    val y2 = points[offset + 5]
+    y3 = points[offset + 7]
+
+    val root = findFirstCubicRoot(
+        y0 - y,
+        y1 - y,
+        y2 - y,
+        y3 - y,
+    )
+    if (root.isNaN()) return 0
+
+    val xt = evaluateCubic(x0, x1, x2, x3, root)
+    if (xt.closeTo(x)) {
+        if (x != x3 || y != y3) {
+            // TODO: on the curve
+            return 0
+        }
+    }
+
+    return if (xt < x) direction else 0
+}
+
+/**
+ * Splits the specified [cubic] Bézier curve into 1, 2, or 3 monotonic cubic Bézier curves.
+ * The results are stored in the [dst] array. Both the input [cubic] and the output [dst]
+ * arrays store the curves as 4 pairs of floats defined by the start, 2 control, and end
+ * points. In the [dst] array, successive curves share a point: the end point of the first
+ * curve is the start point of the second curve. As a result this function will output at
+ * most 20 values in the [dst] array (8 floats per curve, minus 2 for each shared point).
+ *
+ * The function returns the number of splits: if 0 is returned, the [dst] array contains
+ * a single cubic curve, if 1 is returned, the array contains 2 curves with a shared
+ * point, and if 2 is returned, the array contains 3 curves with 2 shared points.
+ *
+ * The [tmpRoot] array is a scratch array that must contain at least 2 values. It is
+ * used to hold temporary values and its content can be ignored after calling this
+ * function.
+ */
+private fun cubicToMonotonicCubics(cubic: FloatArray, dst: FloatArray, tmpRoot: FloatArray): Int {
+    val rootCount = findCubicExtremaY(cubic, tmpRoot)
+
+    // Split the curve at the extrema
+    if (rootCount == 0) {
+        // The cubic segment is already monotonic, copy it as-is
+        cubic.copyInto(dst, 0, 0, 8)
+    } else {
+        var lastT = 0.0f
+        var dstOffset = 0
+        var src = cubic
+
+        for (i in 0 until rootCount) {
+            var t = tmpRoot[i]
+            t = ((t - lastT) / (1.0f - lastT)).fastCoerceIn(0.0f, 1.0f)
+            lastT = t
+            splitCubicAt(src, dstOffset, dst, dstOffset, t)
+            src = dst
+            dstOffset += 6
+        }
+    }
+
+    // NOTE: Should we flatten the extrema?
+
+    return rootCount
+}
+
+/**
+ * Finds the roots of the cubic function which coincide with the specified [cubic]
+ * Bézier curve's extrema on the Y axis. The roots are written in the specified
+ * [dstRoots] array which must hold at least 2 floats. This function returns the number
+ * of roots found: 0, 1, or 2.
+ */
+private fun findCubicExtremaY(cubic: FloatArray, dstRoots: FloatArray): Int {
+    val a = cubic[1]
+    val b = cubic[3]
+    val c = cubic[5]
+    val d = cubic[7]
+
+    val A = d - a + 3.0f * (b - c)
+    val B = 2.0f * (a - b - b - c)
+    val C = b - a
+
+    return findQuadraticRoots(A, B, C, dstRoots, 0)
+}
+
+/**
+ * Splits the cubic Bézier curve, specified by 4 pairs of floats (8 values) in the [src]
+ * array starting at the index [srcOffset], at position [t] (in the 0..1 range). The
+ * results are written in the [dst] array starting at index [dstOffset]. This function
+ * always outputs 2 curves sharing a point in the [dst] array, for a total of 14 float
+ * values: 8 for the first curve, 7 for the second curve (the end point of the first
+ * curve is shared as the start point of the second curve).
+ */
+private fun splitCubicAt(
+    src: FloatArray,
+    srcOffset: Int,
+    dst: FloatArray,
+    dstOffset: Int,
+    t: Float
+) {
+    if (t >= 1.0f) {
+        src.copyInto(dst, dstOffset, srcOffset, 8)
+        val x = src[srcOffset + 6]
+        val y = src[srcOffset + 7]
+        dst[dstOffset + 8] = x
+        dst[dstOffset + 9] = y
+        dst[dstOffset + 10] = x
+        dst[dstOffset + 11] = y
+        dst[dstOffset + 12] = x
+        dst[dstOffset + 13] = y
+        return
+    }
+
+    val p0x = src[srcOffset + 0]
+    val p0y = src[srcOffset + 1]
+
+    dst[dstOffset + 0] = p0x
+    dst[dstOffset + 1] = p0y
+
+    val p1x = src[srcOffset + 2]
+    val p1y = src[srcOffset + 3]
+
+    val abx = lerp(p0x, p1x, t)
+    val aby = lerp(p0y, p1y, t)
+
+    dst[dstOffset + 2] = abx
+    dst[dstOffset + 3] = aby
+
+    val p2x = src[srcOffset + 4]
+    val p2y = src[srcOffset + 5]
+
+    val bcx = lerp(p1x, p2x, t)
+    val bcy = lerp(p1y, p2y, t)
+    val abcx = lerp(abx, bcx, t)
+    val abcy = lerp(aby, bcy, t)
+
+    dst[dstOffset + 4] = abcx
+    dst[dstOffset + 5] = abcy
+
+    val p3x = src[srcOffset + 6]
+    val p3y = src[srcOffset + 7]
+
+    val cdx = lerp(p2x, p3x, t)
+    val cdy = lerp(p2y, p3y, t)
+    val bcdx = lerp(bcx, cdx, t)
+    val bcdy = lerp(bcy, cdy, t)
+    val abcdx = lerp(abcx, bcdx, t)
+    val abcdy = lerp(abcy, bcdy, t)
+
+    dst[dstOffset + 6] = abcdx
+    dst[dstOffset + 7] = abcdy
+
+    dst[dstOffset + 8] = bcdx
+    dst[dstOffset + 9] = bcdy
+
+    dst[dstOffset + 10] = cdx
+    dst[dstOffset + 11] = cdy
+
+    dst[dstOffset + 12] = p3x
+    dst[dstOffset + 13] = p3y
+}
+
+private inline val PathSegment.startX: Float
+    get() = points[0]
+
+private val PathSegment.endX: Float
+    get() = points[when (type) {
+        PathSegment.Type.Move -> 0
+        PathSegment.Type.Line -> 2
+        PathSegment.Type.Quadratic -> 4
+        PathSegment.Type.Conic -> 4
+        PathSegment.Type.Cubic -> 6
+        PathSegment.Type.Close -> 0
+        PathSegment.Type.Done -> 0
+    }]
+
+private inline val PathSegment.startY: Float
+    get() = points[1]
+
+private val PathSegment.endY: Float
+    get() = points[when (type) {
+        PathSegment.Type.Move -> 0
+        PathSegment.Type.Line -> 3
+        PathSegment.Type.Quadratic -> 5
+        PathSegment.Type.Conic -> 5
+        PathSegment.Type.Cubic -> 7
+        PathSegment.Type.Close -> 0
+        PathSegment.Type.Done -> 0
+    }]
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/IntervalTree.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/IntervalTree.kt
new file mode 100644
index 0000000..04badc2
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/IntervalTree.kt
@@ -0,0 +1,407 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.annotation.RestrictTo
+import kotlin.math.max
+import kotlin.math.min
+
+// TODO: We should probably move this to androidx.collection
+
+/**
+ * Interval in an [IntervalTree]. The interval is defined between a [start] and an [end]
+ * coordinate, whose meanings are defined by the caller. An interval can also hold
+ * arbitrary [data] to be used to looking at the result of queries with
+ * [IntervalTree.findOverlaps].
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+open class Interval<T>(val start: Float, val end: Float, val data: T? = null) {
+    /**
+     * Returns trues if this interval overlaps with another interval.
+     */
+    fun overlaps(other: Interval<T>) = start <= other.end && end >= other.start
+
+    /**
+     * Returns trues if this interval overlaps with the interval defined by [start]
+     * and [end]. [start] must be less than or equal to [end].
+     */
+    fun overlaps(start: Float, end: Float) = this.start <= end && this.end >= start
+
+    /**
+     * Returns true if this interval contains [value].
+     */
+    operator fun contains(value: Float) = value in start..end
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as Interval<*>
+
+        if (start != other.start) return false
+        if (end != other.end) return false
+        if (data != other.data) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = start.hashCode()
+        result = 31 * result + end.hashCode()
+        result = 31 * result + (data?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun toString(): String {
+        return "Interval(start=$start, end=$end, data=$data)"
+    }
+}
+
+/**
+ * Represents an empty/invalid interval.
+ */
+internal val EmptyInterval: Interval<Any?> = Interval(Float.MAX_VALUE, Float.MIN_VALUE, null)
+
+/**
+ * An interval tree holds a list of intervals and allows for fast queries of intervals
+ * that overlap any given interval. This can be used for instance to perform fast spatial
+ * queries like finding all the segments in a path that overlap with a given vertical
+ * interval.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+class IntervalTree<T> {
+    // Note: this interval tree is implemented as a binary red/black tree that gets
+    // re-balanced on updates. There's nothing notable about this particular data
+    // structure beyond what can be found in various descriptions of binary search
+    // trees and red/black trees
+
+    private val terminator = Node(Float.MAX_VALUE, Float.MIN_VALUE, null, TreeColor.Black)
+    private var root = terminator
+    private val stack = ArrayList<Node>()
+
+    /**
+     * Clears this tree and prepares it for reuse. After calling [clear], any call to
+     * [findOverlaps] returns false.
+     */
+    fun clear() {
+        root = terminator
+    }
+
+    /**
+     * Finds the first interval that overlaps with the specified [interval]. If no overlap can
+     * be found, return [EmptyInterval].
+     */
+    fun findFirstOverlap(interval: ClosedFloatingPointRange<Float>) =
+        findFirstOverlap(interval.start, interval.endInclusive)
+
+    /**
+     * Finds the first interval that overlaps with the interval defined by [start] and [end].
+     * If no overlap can be found, return [EmptyInterval]. [start] *must* be lesser than or
+     * equal to [end].
+     */
+    fun findFirstOverlap(
+        start: Float,
+        end: Float = start
+    ): Interval<T> {
+        if (root !== terminator) {
+            forEach(start, end) { interval ->
+                return interval
+            }
+        }
+        @Suppress("UNCHECKED_CAST")
+        return EmptyInterval as Interval<T>
+    }
+
+    /**
+     * Finds all the intervals that overlap with the specified [interval]. If [results]
+     * is specified, [results] is returned, otherwise a new [MutableList] is returned.
+     */
+    fun findOverlaps(
+        interval: ClosedFloatingPointRange<Float>,
+        results: MutableList<Interval<T>> = mutableListOf()
+    ) = findOverlaps(interval.start, interval.endInclusive, results)
+
+    /**
+     * Finds all the intervals that overlap with the interval defined by [start] and [end].
+     * [start] *must* be lesser than or equal to [end]. If [results] is specified, [results]
+     * is returned, otherwise a new [MutableList] is returned.
+     */
+    fun findOverlaps(
+        start: Float,
+        end: Float = start,
+        results: MutableList<Interval<T>> = mutableListOf()
+    ): MutableList<Interval<T>> {
+        forEach(start, end) { interval ->
+            results.add(interval)
+        }
+        return results
+    }
+
+    /**
+     * Executes [block] for each interval that overlaps the specified [interval].
+     */
+    internal inline fun forEach(
+        interval: ClosedFloatingPointRange<Float>,
+        block: (Interval<T>) -> Unit
+    ) = forEach(interval.start, interval.endInclusive, block)
+
+    /**
+     * Executes [block] for each interval that overlaps with the interval defined by [start]
+     * and [end]. [start] *must* be lesser than or equal to [end].
+     */
+    internal inline fun forEach(
+        start: Float,
+        end: Float = start,
+        block: (Interval<T>) -> Unit
+    ) {
+        if (root !== terminator) {
+            val s = stack
+            s.add(root)
+            while (s.size > 0) {
+                val node = s.removeLast()
+                if (node.overlaps(start, end)) block(node)
+                if (node.left !== terminator && node.left.max >= start) {
+                    s.add(node.left)
+                }
+                if (node.right !== terminator && node.right.min <= end) {
+                    s.add(node.right)
+                }
+            }
+            s.clear()
+        }
+    }
+
+    /**
+     * Returns true if [value] is inside any of the intervals in this tree.
+     */
+    operator fun contains(value: Float) = findFirstOverlap(value, value) !== EmptyInterval
+
+    /**
+     * Returns true if the specified [interval] overlaps with any of the intervals
+     * in this tree.
+     */
+    operator fun contains(interval: ClosedFloatingPointRange<Float>) =
+        findFirstOverlap(interval.start, interval.endInclusive) !== EmptyInterval
+
+    operator fun iterator(): Iterator<Interval<T>> {
+        return object : Iterator<Interval<T>> {
+            var next = root.lowestNode()
+
+            override fun hasNext(): Boolean {
+                return next !== terminator
+            }
+
+            override fun next(): Interval<T> {
+                val node = next
+                next = next.next()
+                return node
+            }
+        }
+    }
+
+    /**
+     * Adds the specified [Interval] to the interval tree.
+     */
+    operator fun plusAssign(interval: Interval<T>) {
+        addInterval(interval.start, interval.end, interval.data)
+    }
+
+    /**
+     * Adds the interval defined between a [start] and an [end] coordinate.
+     *
+     * @param start The start coordinate of the interval
+     * @param end The end coordinate of the interval, must be >= [start]
+     * @param data Data to associate with the interval
+     */
+    fun addInterval(start: Float, end: Float, data: T?) {
+        val node = Node(start, end, data, TreeColor.Red)
+
+        // Update the tree without doing any balancing
+        var current = root
+        var parent = terminator
+
+        while (current !== terminator) {
+            parent = current
+            current = if (node.start <= current.start) {
+                current.left
+            } else {
+                current.right
+            }
+        }
+
+        node.parent = parent
+
+        if (parent === terminator) {
+            root = node
+        } else {
+            if (node.start <= parent.start) {
+                parent.left = node
+            } else {
+                parent.right = node
+            }
+        }
+
+        updateNodeData(node)
+
+        rebalance(node)
+    }
+
+    private fun rebalance(target: Node) {
+        var node = target
+
+        while (node !== root && node.parent.color == TreeColor.Red) {
+            val ancestor = node.parent.parent
+            if (node.parent === ancestor.left) {
+                val right = ancestor.right
+                if (right.color == TreeColor.Red) {
+                    right.color = TreeColor.Black
+                    node.parent.color = TreeColor.Black
+                    ancestor.color = TreeColor.Red
+                    node = ancestor
+                } else {
+                    if (node === node.parent.right) {
+                        node = node.parent
+                        rotateLeft(node)
+                    }
+                    node.parent.color = TreeColor.Black
+                    ancestor.color = TreeColor.Red
+                    rotateRight(ancestor)
+                }
+            } else {
+                val left = ancestor.left
+                if (left.color == TreeColor.Red) {
+                    left.color = TreeColor.Black
+                    node.parent.color = TreeColor.Black
+                    ancestor.color = TreeColor.Red
+                    node = ancestor
+                } else {
+                    if (node === node.parent.left) {
+                        node = node.parent
+                        rotateRight(node)
+                    }
+                    node.parent.color = TreeColor.Black
+                    ancestor.color = TreeColor.Red
+                    rotateLeft(ancestor)
+                }
+            }
+        }
+
+        root.color = TreeColor.Black
+    }
+
+    private fun rotateLeft(node: Node) {
+        val right = node.right
+        node.right = right.left
+
+        if (right.left !== terminator) {
+            right.left.parent = node
+        }
+
+        right.parent = node.parent
+
+        if (node.parent === terminator) {
+            root = right
+        } else {
+            if (node.parent.left === node) {
+                node.parent.left = right
+            } else {
+                node.parent.right = right
+            }
+        }
+
+        right.left = node
+        node.parent = right
+
+        updateNodeData(node)
+    }
+
+    private fun rotateRight(node: Node) {
+        val left = node.left
+        node.left = left.right
+
+        if (left.right !== terminator) {
+            left.right.parent = node
+        }
+
+        left.parent = node.parent
+
+        if (node.parent === terminator) {
+            root = left
+        } else {
+            if (node.parent.right === node) {
+                node.parent.right = left
+            } else {
+                node.parent.left = left
+            }
+        }
+
+        left.right = node
+        node.parent = left
+
+        updateNodeData(node)
+    }
+
+    private fun updateNodeData(node: Node) {
+        var current = node
+        while (current !== terminator) {
+            current.min = min(current.start, min(current.left.min, current.right.min))
+            current.max = max(current.end, max(current.left.max, current.right.max))
+            current = current.parent
+        }
+    }
+
+    internal enum class TreeColor {
+        Red, Black
+    }
+
+    internal inner class Node(
+        start: Float,
+        end: Float,
+        data: T?,
+        var color: TreeColor
+    ) : Interval<T>(start, end, data) {
+        var min: Float = start
+        var max: Float = end
+
+        var left: Node = terminator
+        var right: Node = terminator
+        var parent: Node = terminator
+
+        fun lowestNode(): Node {
+            var node = this
+            while (node.left !== terminator) {
+                node = node.left
+            }
+            return node
+        }
+
+        fun next(): Node {
+            if (right !== terminator) {
+                return right.lowestNode()
+            }
+
+            var a = this
+            var b = parent
+            while (b !== terminator && a === b.right) {
+                a = b
+                b = b.parent
+            }
+
+            return b
+        }
+    }
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt
new file mode 100644
index 0000000..a380113
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+
+/**
+ * Creates a new [PathHitTester] to query whether certain x/y coordinates lie inside a
+ * given [Path]. A [PathHitTester] is optimized to perform multiple queries against a
+ * single path.
+ *
+ * The result of a query depends on the [fill type][Path.fillType] of the path.
+ *
+ * If the content of [path] changes, you must call [PathHitTester.updatePath] or create
+ * a new [PathHitTester] as [PathHitTester] will cache precomputed values to speed up
+ * queries.
+ *
+ * If [path] contains conic curves, they are converted to quadratic curves during the
+ * query process. The tolerance of that conversion is defined by [tolerance]. The
+ * tolerance should be appropriate to the coordinate systems used by the caller. For
+ * instance if the path is defined in pixels, 0.5 (half a pixel) or 1.0 (a pixel) are
+ * appropriate tolerances. If the path is normalized and defined in the domain 0..1,
+ * the caller should choose a more appropriate tolerance close to or equal to one
+ * "query unit".
+ *
+ * @param path The [Path] to run queries against.
+ * @param tolerance When [path] contains conic curves, defines the maximum distance between
+ *        the original conic curve and its quadratic approximations. Set to 0.5 by default.
+ */
+fun PathHitTester(path: Path, tolerance: Float = 0.5f) = PathHitTester().apply {
+    updatePath(path, tolerance)
+}
+
+private val EmptyPath = Path()
+
+/**
+ * A [PathHitTester] is used to query whether certain x/y coordinates lie inside a
+ * given [Path]. A [PathHitTester] is optimized to perform multiple queries against a
+ * single path.
+ */
+class PathHitTester {
+    private var path = EmptyPath
+    private var tolerance = 0.5f
+
+    // The bounds of [path], precomputed
+    private var bounds = Rect.Zero
+
+    // When cached is set to true, the path's segments are cached inside an [IntervalTree]
+    // to speed up hit testing by performing computations against the segments that cross
+    // the y axis of the test position
+    private val intervals = IntervalTree<PathSegment>()
+
+    // Scratch buffers used to avoid allocations when performing a hit test
+    private val curves = FloatArray(20)
+    private val roots = FloatArray(2)
+
+    /**
+     * Sets the [Path] to run queries against.
+     *
+     * If [path] contains conic curves, they are converted to quadratic curves during the
+     * query process. This value defines the tolerance of that conversion.
+     *
+     * The tolerance should be appropriate to the coordinate systems used by the caller.
+     * For instance if the path is defined in pixels, 0.5 (half a pixel) or 1.0 (a pixel)
+     * are appropriate tolerances. If the path is normalized and defined in the domain 0..1,
+     * the caller should choose a more appropriate tolerance close to or equal to one
+     * "query unit".
+     *
+     * @param path The [Path] to run queries against.
+     * @param tolerance When [path] contains conic curves, defines the maximum distance between
+     *        the original conic curve and its quadratic approximations. Set to 0.5 by default.
+     */
+    fun updatePath(path: Path, tolerance: Float = 0.5f) {
+        this.path = path
+        this.tolerance = tolerance
+        bounds = path.getBounds()
+
+        intervals.clear()
+        // TODO: We should handle conics ourselves, which would allow us to cheaply query
+        //       the number of segments in the path, which would in turn allow us to allocate
+        //       all of our data structures with an appropriate size to store everything in
+        //       a single array for instance
+        val iterator = path.iterator(PathIterator.ConicEvaluation.AsQuadratics, tolerance)
+        for (segment in iterator) {
+            when (segment.type) {
+                PathSegment.Type.Line,
+                PathSegment.Type.Quadratic,
+                PathSegment.Type.Cubic -> {
+                    val (min, max) = computeVerticalBounds(segment, curves)
+                    intervals.addInterval(min, max, segment)
+                }
+                PathSegment.Type.Done -> break
+                else -> {}
+            }
+        }
+    }
+
+    /**
+     * Queries whether the specified [position] is inside this [Path]. The
+     * [path's fill type][Path.fillType] is taken into account to determine if the point lies
+     * inside this path or not.
+     *
+     * @param position The x/y coordinates of the point to test.
+     * @return True if [position] is inside this path, false otherwise.
+     */
+    operator fun contains(position: Offset): Boolean {
+        // TODO: If/when Compose supports inverse fill types, compute this value
+        val isInverse = false
+
+        if (path.isEmpty || position !in bounds) {
+            return isInverse
+        }
+
+        val (x, y) = position
+        val curvesArray = curves
+        val rootsArray = roots
+
+        var winding = 0
+
+        intervals.forEach(y) { interval ->
+            val segment = interval.data!!
+            val points = segment.points
+            when (segment.type) {
+                PathSegment.Type.Line -> {
+                    winding += lineWinding(points, x, y)
+                }
+                PathSegment.Type.Quadratic -> {
+                    winding += quadraticWinding(points, x, y, curvesArray, rootsArray)
+                }
+                PathSegment.Type.Cubic -> {
+                    winding += cubicWinding(points, x, y, curvesArray, rootsArray)
+                }
+                PathSegment.Type.Done -> return@forEach
+                else -> {} // Nothing to do for Move, Conic
+            }
+        }
+
+        val isEvenOdd = path.fillType == PathFillType.EvenOdd
+        if (isEvenOdd) {
+            winding = winding and 1
+        }
+
+        if (winding != 0) {
+            return !isInverse
+        }
+
+        // TODO: handle cases where the point is on the curve
+
+        return false
+    }
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathIterator.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathIterator.kt
index e7e3a3c..1432405 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathIterator.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathIterator.kt
@@ -112,6 +112,7 @@
      * and fills [outPoints] with the points specific to the segment type. Each pair of floats in
      * the [outPoints] array represents a point for the given segment. The number of pairs of floats
      * depends on the [PathSegment.Type]:
+     *
      * - [Move][PathSegment.Type.Move]: 1 pair (indices 0 to 1)
      * - [Line][PathSegment.Type.Line]: 2 pairs (indices 0 to 3)
      * - [Quadratic][PathSegment.Type.Quadratic]: 3 pairs (indices 0 to 5)
@@ -121,6 +122,7 @@
      * - [Cubic][PathSegment.Type.Cubic]: 4 pairs (indices 0 to 7)
      * - [Close][PathSegment.Type.Close]: 0 pair
      * - [Done][PathSegment.Type.Done]: 0 pair
+     *
      * This method does not allocate any memory.
      *
      * @param outPoints A [FloatArray] large enough to hold 8 floats starting at [offset],
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSvg.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSvg.kt
new file mode 100644
index 0000000..f417220
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSvg.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.graphics.vector.PathParser
+
+/**
+ * Adds the specified SVG [path data](https://www.w3.org/TR/SVG2/paths.html#PathData) to
+ * this [Path]. The SVG path data encodes a series of instructions that will be applied
+ * to this path. For instance, the following path data:
+ *
+ * `M200,300 Q400,50 600,300 L1000,300`
+ *
+ * Will generate the following series of instructions for this path:
+ *
+ * ```
+ * moveTo(200f, 300f)
+ * quadraticTo(400f, 50f, 600f, 300f)
+ * lineTo(1000f, 300f)
+ * ```
+ *
+ * To convert a [Path] to its SVG path data representation, please refer to [Path.toSvg].
+ *
+ * @throws IllegalArgumentException if the path data contains an invalid instruction
+ *
+ * @see toSvg
+ */
+fun Path.addSvg(pathData: String) {
+    // TODO: PathParser will allocate a bunch of PathNodes which aren't necessary here,
+    //       we should instead have an internal version of parsePathString() that adds
+    //       commands directly to a path without creating intermediate nodes
+    PathParser().parsePathString(pathData).toPath(this)
+}
+
+/**
+ * Returns an SVG representation of this path. The caller can choose whether the returned
+ * SVG represents a fully-formed SVG document or only the
+ * [path data](https://www.w3.org/TR/SVG2/paths.html#PathData). By default, only the path
+ * data is returned which can be used either with [Path.addSvg] or
+ * [androidx.compose.ui.graphics.vector.PathParser].
+ *
+ * @param asDocument When set to true, this function returns a fully-formed SVG document,
+ *        otherwise returns only the path data.
+ *
+ * @see androidx.compose.ui.graphics.vector.PathParser
+ * @see addSvg
+ */
+fun Path.toSvg(asDocument: Boolean = false) = buildString {
+    val bounds = [email protected]()
+
+    if (asDocument) {
+        append("""<svg xmlns="http://www.w3.org/2000/svg" """)
+        appendLine("""viewBox="${bounds.left} ${bounds.top} ${bounds.width} ${bounds.height}">""")
+    }
+
+    val iterator = [email protected]()
+    val points = FloatArray(8)
+    var lastType = PathSegment.Type.Done
+
+    if (iterator.hasNext()) {
+        if (asDocument) {
+            if ([email protected] == PathFillType.EvenOdd) {
+                append("""  <path fill-rule="evenodd" d="""")
+            } else {
+                append("""  <path d="""")
+            }
+        }
+
+        while (iterator.hasNext()) {
+            val type = iterator.next(points)
+            when (type) {
+                PathSegment.Type.Move -> {
+                    append("${command(PathSegment.Type.Move, lastType)}${points[0]} ${points[1]}")
+                }
+                PathSegment.Type.Line -> {
+                    append("${command(PathSegment.Type.Line, lastType)}${points[2]} ${points[3]}")
+                }
+                PathSegment.Type.Quadratic -> {
+                    append(command(PathSegment.Type.Quadratic, lastType))
+                    append("${points[2]} ${points[3]} ${points[4]} ${points[5]}")
+                }
+                PathSegment.Type.Conic -> continue // We convert conics to quadratics
+                PathSegment.Type.Cubic -> {
+                    append(command(PathSegment.Type.Cubic, lastType))
+                    append("${points[2]} ${points[3]} ")
+                    append("${points[4]} ${points[5]} ")
+                    append("${points[6]} ${points[7]}")
+                }
+                PathSegment.Type.Close -> {
+                    append(command(PathSegment.Type.Close, lastType))
+                }
+                PathSegment.Type.Done -> continue // Won't happen inside this loop
+            }
+            lastType = type
+        }
+
+        if (asDocument) {
+            appendLine(""""/>""")
+        }
+    }
+    if (asDocument) {
+        appendLine("""</svg>""")
+    }
+}
+
+private fun command(type: PathSegment.Type, lastType: PathSegment.Type) =
+    if (type != lastType) {
+        when (type) {
+            PathSegment.Type.Move -> "M"
+            PathSegment.Type.Line -> "L"
+            PathSegment.Type.Quadratic -> "Q"
+            PathSegment.Type.Cubic -> "C"
+            PathSegment.Type.Close -> "Z"
+            else -> ""
+        }
+    } else " "
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
index 2fa840f..9325c34 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
@@ -65,8 +65,11 @@
     block: DrawScope.() -> Unit
 ) {
     drawContext.transform.inset(left, top, right, bottom)
-    block()
-    drawContext.transform.inset(-left, -top, -right, -bottom)
+    try {
+        block()
+    } finally {
+        drawContext.transform.inset(-left, -top, -right, -bottom)
+    }
 }
 
 /**
@@ -83,8 +86,11 @@
     block: DrawScope.() -> Unit
 ) {
     drawContext.transform.inset(inset, inset, inset, inset)
-    block()
-    drawContext.transform.inset(-inset, -inset, -inset, -inset)
+    try {
+        block()
+    } finally {
+        drawContext.transform.inset(-inset, -inset, -inset, -inset)
+    }
 }
 
 /**
@@ -119,8 +125,11 @@
     block: DrawScope.() -> Unit
 ) {
     drawContext.transform.translate(left, top)
-    block()
-    drawContext.transform.translate(-left, -top)
+    try {
+        block()
+    } finally {
+        drawContext.transform.translate(-left, -top)
+    }
 }
 
 /**
@@ -265,10 +274,13 @@
     // and reset it afterwards
     val previousSize = size
     canvas.save()
-    transformBlock(transform)
-    drawBlock()
-    canvas.restore()
-    size = previousSize
+    try {
+        transformBlock(transform)
+        drawBlock()
+    } finally {
+        canvas.restore()
+        size = previousSize
+    }
 }
 
 /**
@@ -307,13 +319,16 @@
         this.size = size
     }
     canvas.save()
-    this.block()
-    canvas.restore()
-    drawContext.apply {
-        this.density = prevDensity
-        this.layoutDirection = prevLayoutDirection
-        this.canvas = prevCanvas
-        this.size = prevSize
+    try {
+        this.block()
+    } finally {
+        canvas.restore()
+        drawContext.apply {
+            this.density = prevDensity
+            this.layoutDirection = prevLayoutDirection
+            this.canvas = prevCanvas
+            this.size = prevSize
+        }
     }
 }
 
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
index e8dc93a..352bd15 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
@@ -216,7 +216,6 @@
                 ctrlX = segmentX
                 ctrlY = segmentY
                 target.close()
-                target.moveTo(currentX, currentY)
             }
 
             is RelativeMoveTo -> {
diff --git a/compose/ui/ui-inspection/build.gradle b/compose/ui/ui-inspection/build.gradle
index e409f2db..ecf1f1c 100644
--- a/compose/ui/ui-inspection/build.gradle
+++ b/compose/ui/ui-inspection/build.gradle
@@ -51,6 +51,7 @@
     implementation(libs.kotlinReflect, {
         exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
     })
+    implementation("androidx.collection:collection:1.4.0")
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.kotlinCoroutinesAndroid)
     androidTestImplementation(libs.testCore)
diff --git a/compose/ui/ui-inspection/lint-baseline.xml b/compose/ui/ui-inspection/lint-baseline.xml
index a1c812d..dd2c3e3d 100644
--- a/compose/ui/ui-inspection/lint-baseline.xml
+++ b/compose/ui/ui-inspection/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="PrimitiveInCollection"
@@ -19,211 +19,4 @@
             file="src/main/java/androidx/compose/ui/inspection/util/AnchorMap.kt"/>
     </issue>
 
-    <issue
-        id="PrimitiveInCollection"
-        message="field viewsToSkip with type List&lt;Long>: replace with LongList"
-        errorLine1="    val viewsToSkip: List&lt;Long> ="
-        errorLine2="                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;Long> of getViewsToSkip: replace with LongList"
-        errorLine1="    val viewsToSkip: List&lt;Long> ="
-        errorLine2="                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type Map&lt;Long, InspectorNode> of getLookup: replace with LongObjectMap"
-        errorLine1="        val lookup: Map&lt;Long, InspectorNode>"
-        errorLine2="            ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable var785951a with type Map&lt;Long, ? extends InspectorNode>: replace with LongObjectMap"
-        errorLine1="            get() = _lookup ?: trees.flatMap { it.nodes }"
-        errorLine2="                    ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field _lookup with type Map&lt;Long, InspectorNode>: replace with LongObjectMap"
-        errorLine1="        private var _lookup: Map&lt;Long, InspectorNode>? = null"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="constructor CacheTree has parameter viewsToSkip with type List&lt;Long>: replace with LongList"
-        errorLine1="        val viewsToSkip: List&lt;Long>"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field viewsToSkip with type List&lt;Long>: replace with LongList"
-        errorLine1="        val viewsToSkip: List&lt;Long>"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;Long> of getViewsToSkip: replace with LongList"
-        errorLine1="        val viewsToSkip: List&lt;Long>"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field _cachedNodes with type Map&lt;Long, CacheData>: replace with LongObjectMap"
-        errorLine1="    private val _cachedNodes = mutableMapOf&lt;Long, CacheData>()"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type Map&lt;Long, CacheData> of getCachedNodes: replace with LongObjectMap"
-        errorLine1="    private val cachedNodes: MutableMap&lt;Long, CacheData>"
-        errorLine2="                ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable data with type Map&lt;Long, ? extends CacheData>: replace with LongObjectMap"
-        errorLine1="        val data = ThreadUtils.runOnMainThread {"
-        errorLine2="        ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable composeViewsByRoot with type Map&lt;Long, ? extends List&lt;? extends AndroidComposeViewWrapper>>: replace with LongObjectMap"
-        errorLine1="            val composeViewsByRoot = composeViews.groupBy { it.rootView.uniqueDrawingId }"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable data with type Map&lt;Long, ? extends CacheData>: replace with LongObjectMap"
-        errorLine1="            val data = composeViewsByRoot.mapValues { (_, composeViews) ->"
-        errorLine2="            ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="method asIntArray has parameter $this$asIntArray with type List&lt;Integer>: replace with IntList"
-        errorLine1="fun List&lt;Int>.asIntArray() ="
-        errorLine2="    ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/util/IntArray.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field semanticsMap with type Map&lt;Integer, List&lt;RawParameter>>: replace with IntObjectMap"
-        errorLine1="    /** Map from semantics id to a list of merged semantics information */"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field unmergedSemanticsMap with type Map&lt;Integer, List&lt;RawParameter>>: replace with IntObjectMap"
-        errorLine1="    /* Map of seemantics id to a list of unmerged semantics information */"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field found with type Map&lt;Long, InspectorNode>: replace with LongObjectMap"
-        errorLine1="        /**"
-        errorLine2="        ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="constructor NodeParameterReference has parameter indices with type List&lt;Integer>: replace with IntList"
-        errorLine1="        indices: List&lt;Int>"
-        errorLine2="                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/NodeParameterReference.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field systemPackages with type Set&lt;Integer>: replace with IntSet"
-        errorLine1="val systemPackages = setOf("
-        errorLine2="^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type Set&lt;Integer> of getSystemPackages: replace with IntSet"
-        errorLine1="val systemPackages = setOf("
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field valueIndex with type List&lt;Integer>: replace with IntList"
-        errorLine1="        private val valueIndex = mutableListOf&lt;Int>()"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field rootValueIndexCache with type Map&lt;Long, IdentityHashMap&lt;Object, NodeParameterReference>>: replace with LongObjectMap"
-        errorLine1="        private val rootValueIndexCache ="
-        errorLine2="        ^">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="field innerMap with type Map&lt;String, Integer>: replace with ObjectIntMap"
-        errorLine1="    private val innerMap = mutableMapOf&lt;String, Int>()"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/proto/StringTable.kt"/>
-    </issue>
-
 </issues>
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index 2da7184..02021e6 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -36,8 +36,10 @@
 import androidx.compose.material.AlertDialog
 import androidx.compose.material.Button
 import androidx.compose.material.Icon
+import androidx.compose.material.LinearProgressIndicator
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.ModalDrawer
+import androidx.compose.material.Scaffold
 import androidx.compose.material.Surface
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
@@ -1011,6 +1013,30 @@
         }
     }
 
+    @Test
+    fun testScaffold() {
+        val slotTableRecord = CompositionDataRecord.create()
+
+        show {
+            Inspectable(slotTableRecord) {
+                Scaffold {
+                    Column {
+                        LinearProgressIndicator(progress = 0.3F)
+                    }
+                }
+            }
+        }
+        val androidComposeView = findAndroidComposeView()
+        androidComposeView.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
+        val builder = LayoutInspectorTree()
+        builder.hideSystemNodes = false
+        builder.includeAllParameters = true
+        val linearProgressIndicator = builder.convert(androidComposeView)
+            .flatMap { flatten(it) }
+            .firstOrNull { it.name == "LinearProgressIndicator" }
+        assertThat(linearProgressIndicator).isNotNull()
+    }
+
     @Suppress("SameParameterValue")
     private fun validate(
         result: List<InspectorNode>,
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index 63c4e0b..d3375b6 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.ui.inspection.inspector
 
+import androidx.collection.MutableIntList
+import androidx.collection.intListOf
+import androidx.collection.mutableIntListOf
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
@@ -53,6 +56,7 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.inspection.util.removeLast
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.platform.inspectable
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
@@ -713,6 +717,7 @@
 
     @Test
     fun testWrappedModifier() {
+        @Suppress("DEPRECATION")
         fun Modifier.frame(color: Color) = inspectable(
             debugInspectorInfo {
                 name = "frame"
@@ -982,7 +987,7 @@
             parameter,
             parameter.name,
             value,
-            mutableListOf(),
+            mutableIntListOf(),
             maxRecursions,
             maxInitialIterableSize
         )
@@ -1019,7 +1024,9 @@
     }
 
     private fun ref(vararg reference: Int): NodeParameterReference =
-        NodeParameterReference(NODE_ID, ANCHOR_HASH, ParameterKind.Normal, PARAM_INDEX, reference)
+        NodeParameterReference(
+            NODE_ID, ANCHOR_HASH, ParameterKind.Normal, PARAM_INDEX, intListOf(*reference)
+        )
 
     private fun validate(
         parameter: NodeParameter,
@@ -1034,7 +1041,7 @@
         parameter: NodeParameter,
         name: String,
         value: Any,
-        indices: MutableList<Int>,
+        indices: MutableIntList,
         maxRecursions: Int,
         maxInitialIterableSize: Int
     ) {
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 5dd79bc..490242b6 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -19,6 +19,12 @@
 import android.util.Log
 import android.view.View
 import android.view.inspector.WindowInspector
+import androidx.collection.LongList
+import androidx.collection.LongObjectMap
+import androidx.collection.MutableLongObjectMap
+import androidx.collection.longObjectMapOf
+import androidx.collection.mutableIntListOf
+import androidx.collection.mutableLongObjectMapOf
 import androidx.compose.ui.inspection.compose.AndroidComposeViewWrapper
 import androidx.compose.ui.inspection.compose.convertToParameterGroup
 import androidx.compose.ui.inspection.compose.flatten
@@ -36,6 +42,7 @@
 import androidx.compose.ui.inspection.proto.toComposableRoot
 import androidx.compose.ui.inspection.util.NO_ANCHOR_ID
 import androidx.compose.ui.inspection.util.ThreadUtils
+import androidx.compose.ui.inspection.util.groupByToLongObjectMap
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.unit.IntOffset
 import androidx.inspection.ArtTooling
@@ -54,6 +61,7 @@
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetParameterDetailsResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetParametersCommand
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetParametersResponse
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ParameterGroup
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Response
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.UnknownCommandResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.UpdateSettingsCommand
@@ -86,20 +94,30 @@
         val trees: List<CacheTree>
     ) {
         /** The cached nodes as a map from node id to InspectorNode */
-        val lookup: Map<Long, InspectorNode>
-            get() = _lookup ?: trees.flatMap { it.nodes }
-                .flatMap { it.flatten() }
-                .associateBy { it.id }
-                .also { _lookup = it }
+        val lookup: LongObjectMap<InspectorNode>
+            get() = _lookup ?: createLookup()
 
-        private var _lookup: Map<Long, InspectorNode>? = null
+        private fun createLookup(): LongObjectMap<InspectorNode> {
+            val result = mutableLongObjectMapOf<InspectorNode>()
+            val stack = mutableListOf<InspectorNode>()
+            trees.forEach { stack.addAll(it.nodes) }
+            while (stack.isNotEmpty()) {
+                val node = stack.removeLast()
+                stack.addAll(node.children)
+                result.put(node.id, node)
+            }
+            _lookup = result
+            return result
+        }
+
+        private var _lookup: LongObjectMap<InspectorNode>? = null
     }
 
     /** Cache data for a tree of [InspectorNode]s under a [viewParent] */
     internal class CacheTree(
         val viewParent: View,
         val nodes: List<InspectorNode>,
-        val viewsToSkip: List<Long>
+        val viewsToSkip: LongList
     )
 
     private val layoutInspectorTree = LayoutInspectorTree()
@@ -111,11 +129,11 @@
 
     // Sidestep threading concerns by only ever accessing cachedNodes on the inspector thread
     private val inspectorThread = Thread.currentThread()
-    private val _cachedNodes = mutableMapOf<Long, CacheData>()
+    private val _cachedNodes = mutableLongObjectMapOf<CacheData>()
     private var cachedGeneration = 0
     private var cachedSystemComposablesSkipped = false
     private var cachedHasAllParameters = false
-    private val cachedNodes: MutableMap<Long, CacheData>
+    private val cachedNodes: MutableLongObjectMap<CacheData>
         get() {
             check(Thread.currentThread() == inspectorThread) {
                 "cachedNodes should be accessed by the inspector thread"
@@ -244,18 +262,21 @@
                 getAllParametersCommand.skipSystemComposables,
                 true,
                 getAllParametersCommand.generation
-            )?.lookup?.values ?: emptyList()
+            )?.lookup ?: longObjectMapOf()
 
         callback.reply {
             val stringTable = StringTable()
-            val parameterGroups = allComposables.map { composable ->
-                composable.convertToParameterGroup(
-                    composable,
-                    layoutInspectorTree,
-                    getAllParametersCommand.rootViewId,
-                    getAllParametersCommand.maxRecursions.orElse(MAX_RECURSIONS),
-                    getAllParametersCommand.maxInitialIterableSize.orElse(MAX_ITERABLE_SIZE),
-                    stringTable
+            val parameterGroups = mutableListOf<ParameterGroup>()
+            allComposables.forEachValue { composable ->
+                parameterGroups.add(
+                    composable.convertToParameterGroup(
+                        composable,
+                        layoutInspectorTree,
+                        getAllParametersCommand.rootViewId,
+                        getAllParametersCommand.maxRecursions.orElse(MAX_RECURSIONS),
+                        getAllParametersCommand.maxInitialIterableSize.orElse(MAX_ITERABLE_SIZE),
+                        stringTable
+                    )
                 )
             }
 
@@ -271,12 +292,14 @@
         getParameterDetailsCommand: GetParameterDetailsCommand,
         callback: CommandCallback
     ) {
+        val indices = mutableIntListOf()
+        getParameterDetailsCommand.reference.compositeIndexList.forEach { indices.add(it) }
         val reference = NodeParameterReference(
             getParameterDetailsCommand.reference.composableId,
             getParameterDetailsCommand.reference.anchorHash,
             getParameterDetailsCommand.reference.kind.convert(),
             getParameterDetailsCommand.reference.parameterIndex,
-            getParameterDetailsCommand.reference.compositeIndexList
+            indices
         )
         val foundComposable =
             if (delayParameterExtractions && !cachedHasAllParameters &&
@@ -362,11 +385,18 @@
             layoutInspectorTree.resetAccumulativeState()
             layoutInspectorTree.includeAllParameters = includeAllParameters
             val composeViews = getAndroidComposeViews(rootViewId, skipSystemComposables, generation)
-            val composeViewsByRoot = composeViews.groupBy { it.rootView.uniqueDrawingId }
-            val data = composeViewsByRoot.mapValues { (_, composeViews) ->
-                CacheData(
-                    composeViews.first().rootView,
-                    composeViews.map { CacheTree(it.viewParent, it.createNodes(), it.viewsToSkip) }
+            val composeViewsByRoot =
+                mutableLongObjectMapOf<MutableList<AndroidComposeViewWrapper>>()
+            composeViews.groupByToLongObjectMap(composeViewsByRoot) { it.rootView.uniqueDrawingId }
+            val data = mutableLongObjectMapOf<CacheData>()
+            composeViewsByRoot.forEach { key, value ->
+                data.put(key,
+                    CacheData(
+                        value.first().rootView,
+                        value.map {
+                            CacheTree(it.viewParent, it.createNodes(), it.viewsToSkip)
+                        }
+                    )
                 )
             }
             layoutInspectorTree.resetAccumulativeState()
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 4f65cc5..85f5c4b 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -19,6 +19,8 @@
 import android.content.res.Resources
 import android.view.View
 import android.view.ViewGroup
+import androidx.collection.LongList
+import androidx.collection.mutableLongListOf
 import androidx.compose.ui.R
 import androidx.compose.ui.inspection.framework.ancestors
 import androidx.compose.ui.inspection.framework.getChildren
@@ -96,10 +98,7 @@
         if (!skipSystemComposables) composeView
         else composeView.ancestors().first { !it.isSystemView() || it.isRoot() }
 
-    val viewsToSkip: List<Long> =
-        composeView.getChildren()
-            .filter { it.getTag(R.id.hide_in_inspector_tag) != null }
-            .map { it.uniqueDrawingId }
+    val viewsToSkip: LongList = createViewsToSkip(composeView)
 
     private val inspectorNodes = layoutInspectorTree.apply {
         this.hideSystemNodes = skipSystemComposables
@@ -110,4 +109,14 @@
 
     fun findParameters(anchorId: Int): InspectorNode? =
         layoutInspectorTree.findParameters(composeView, anchorId)
+
+    private fun createViewsToSkip(viewGroup: ViewGroup): LongList {
+        val result = mutableLongListOf()
+        viewGroup.getChildren().forEach { view ->
+            if (view.getTag(R.id.hide_in_inspector_tag) != null) {
+                result.add(view.uniqueDrawingId)
+            }
+        }
+        return result
+    }
 }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index 8a72392..bf4d9e9 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -18,6 +18,10 @@
 
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import androidx.collection.LongList
+import androidx.collection.mutableIntObjectMapOf
+import androidx.collection.mutableLongListOf
+import androidx.collection.mutableLongObjectMapOf
 import androidx.compose.runtime.tooling.CompositionData
 import androidx.compose.runtime.tooling.CompositionGroup
 import androidx.compose.ui.InternalComposeUiApi
@@ -96,9 +100,9 @@
     /** Map from owner node to child trees that are about to be stitched to this owner */
     private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
     /** Map from semantics id to a list of merged semantics information */
-    private val semanticsMap = mutableMapOf<Int, List<RawParameter>>()
+    private val semanticsMap = mutableIntObjectMapOf<List<RawParameter>>()
     /* Map of seemantics id to a list of unmerged semantics information */
-    private val unmergedSemanticsMap = mutableMapOf<Int, List<RawParameter>>()
+    private val unmergedSemanticsMap = mutableIntObjectMapOf<List<RawParameter>>()
     /** Set of tree nodes that were stitched into another tree */
     private val stitched =
         Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
@@ -563,13 +567,30 @@
         return anchorId.toLong() - Int.MAX_VALUE.toLong() + RESERVED_FOR_GENERATED_IDS
     }
 
-    private fun belongsToView(layoutNodes: List<LayoutInfo>, view: View): Boolean =
-        layoutNodes.asSequence().flatMap { node ->
-            node.getModifierInfo().asSequence()
-                .map { it.extra }
-                .filterIsInstance<GraphicLayerInfo>()
-                .map { it.ownerViewId }
-        }.contains(view.uniqueDrawingId)
+    /**
+     * Returns true if the [layoutNodes] belong under the specified [view].
+     *
+     * For: popups & Dialogs we may encounter parts of a compose tree that belong under
+     * a different sub-composition. Consider these nodes to "belong" to the current sub-composition
+     * under [view] if the ownerViews contains [view] or doesn't contain any owner views at all.
+     */
+    private fun belongsToView(layoutNodes: List<LayoutInfo>, view: View): Boolean {
+        val ownerViewIds = ownerViews(layoutNodes)
+        return ownerViewIds.isEmpty() || ownerViewIds.contains(view.uniqueDrawingId)
+    }
+
+    private fun ownerViews(layoutNodes: List<LayoutInfo>): LongList {
+        val ownerViewIds = mutableLongListOf()
+        layoutNodes.forEach { node ->
+            node.getModifierInfo().forEach { info ->
+                val extra = info.extra
+                if (extra is GraphicLayerInfo) {
+                    ownerViewIds.add(extra.ownerViewId)
+                }
+            }
+        }
+        return ownerViewIds
+    }
 
     private fun addParameters(context: SourceContext, node: MutableInspectorNode) {
         context.parameters.forEach {
@@ -691,7 +712,7 @@
          * Map from View owner to a pair of [InspectorNode] indicating the actual root,
          * and the node where the content should be stitched in.
          */
-        private val found = mutableMapOf<Long, InspectorNode>()
+        private val found = mutableLongObjectMapOf<InspectorNode>()
 
         /** Call this before converting a SlotTree for an AndroidComposeView */
         fun clear() {
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameterReference.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameterReference.kt
index aedb52b..6644401 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameterReference.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameterReference.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.ui.inspection.inspector
 
-import androidx.compose.ui.inspection.util.asIntArray
+import androidx.collection.IntList
 
 /**
  * A reference to a parameter to a [NodeParameter]
@@ -33,16 +33,8 @@
     val anchorId: Int,
     val kind: ParameterKind,
     val parameterIndex: Int,
-    val indices: IntArray
+    val indices: IntList
 ) {
-    constructor (
-        nodeId: Long,
-        anchorId: Int,
-        kind: ParameterKind,
-        parameterIndex: Int,
-        indices: List<Int>
-    ) : this(nodeId, anchorId, kind, parameterIndex, indices.asIntArray())
-
     // For testing:
     override fun toString(): String {
         val suffix = if (indices.isNotEmpty()) ", ${indices.joinToString()}" else ""
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt
index d18b803..df3b3cf 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/PackageHashes.kt
@@ -3,13 +3,14 @@
 package androidx.compose.ui.inspection.inspector
 
 import androidx.annotation.VisibleForTesting
+import androidx.collection.intSetOf
 import kotlin.math.absoluteValue
 
 @VisibleForTesting
 fun packageNameHash(packageName: String) =
     packageName.fold(0) { hash, char -> hash * 31 + char.code }.absoluteValue
 
-val systemPackages = setOf(
+val systemPackages = intSetOf(
     -1,
     packageNameHash("androidx.compose.animation"),
     packageNameHash("androidx.compose.animation.core"),
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
index 4e7077f..0e8eb42 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
@@ -18,6 +18,8 @@
 
 import android.util.Log
 import android.view.View
+import androidx.collection.mutableIntListOf
+import androidx.collection.mutableLongObjectMapOf
 import androidx.compose.runtime.internal.ComposableLambda
 import androidx.compose.ui.AbsoluteAlignment
 import androidx.compose.ui.Modifier
@@ -29,6 +31,8 @@
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.inspection.inspector.ParameterType.DimensionDp
+import androidx.compose.ui.inspection.util.copy
+import androidx.compose.ui.inspection.util.removeLast
 import androidx.compose.ui.platform.InspectableModifier
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.text.AnnotatedString
@@ -331,10 +335,10 @@
         private var maxRecursions = 0
         private var maxInitialIterableSize = 0
         private var recursions = 0
-        private val valueIndex = mutableListOf<Int>()
+        private val valueIndex = mutableIntListOf()
         private val valueLazyReferenceMap = IdentityHashMap<Any, MutableList<NodeParameter>>()
         private val rootValueIndexCache =
-            mutableMapOf<Long, IdentityHashMap<Any, NodeParameterReference>>()
+            mutableLongObjectMapOf<IdentityHashMap<Any, NodeParameterReference>>()
         private var valueIndexMap = IdentityHashMap<Any, NodeParameterReference>()
 
         fun create(
@@ -376,12 +380,12 @@
             )
             var parent: Pair<String, Any?>? = null
             var new = Pair(name, value)
-            for (i in reference.indices) {
+            reference.indices.forEach { index ->
                 parent = new
-                new = find(new.first, new.second, i) ?: return null
+                new = find(new.first, new.second, index) ?: return null
             }
             recursions = 0
-            valueIndex.addAll(reference.indices.asSequence())
+            valueIndex.addAll(reference.indices)
             val parameter = if (startIndex == 0) {
                 create(new.first, new.second, parent?.second)
             } else {
@@ -619,7 +623,7 @@
         }
 
         private fun valueIndexToReference(): NodeParameterReference =
-            NodeParameterReference(nodeId, anchorId, kind, parameterIndex, valueIndex)
+            NodeParameterReference(nodeId, anchorId, kind, parameterIndex, valueIndex.copy())
 
         private fun createEmptyParameter(name: String): NodeParameter =
             NodeParameter(name, ParameterType.String, "")
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
index d046f8a..01966b3 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
@@ -262,7 +262,7 @@
         composableId = reference.nodeId
         anchorHash = reference.anchorId
         parameterIndex = reference.parameterIndex
-        addAllCompositeIndex(reference.indices.asIterable())
+        reference.indices.forEach { addCompositeIndex(it) }
     }.build()
 }
 
@@ -272,7 +272,7 @@
         root.addAllNodes(nodes.map {
             it.toComposableNode(context)
         })
-        root.addAllViewsToSkip(viewsToSkip)
+        viewsToSkip.forEach { root.addViewsToSkip(it) }
     }.build()
 
 fun Iterable<NodeParameter>.convertAll(stringTable: StringTable): List<Parameter> {
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/StringTable.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/StringTable.kt
index df076ad..4af2c85 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/StringTable.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/StringTable.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.inspection.proto
 
+import androidx.collection.mutableObjectIntMapOf
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol
 
 /**
@@ -26,18 +27,22 @@
  * of text is across the layout tree will be the same.
  */
 class StringTable {
-    private val innerMap = mutableMapOf<String, Int>()
+    private val innerMap = mutableObjectIntMapOf<String>()
 
     fun put(str: String): Int {
-        return innerMap.computeIfAbsent(str) { innerMap.size + 1 }
+        return innerMap.getOrPut(str) { innerMap.size + 1 }
     }
 
     fun toStringEntries(): List<LayoutInspectorComposeProtocol.StringEntry> {
-        return innerMap.entries.map { entry ->
-            LayoutInspectorComposeProtocol.StringEntry.newBuilder().apply {
-                str = entry.key
-                id = entry.value
-            }.build()
+        val result = mutableListOf<LayoutInspectorComposeProtocol.StringEntry>()
+        innerMap.forEach { key, value ->
+            result.add(
+                LayoutInspectorComposeProtocol.StringEntry.newBuilder().apply {
+                    str = key
+                    id = value
+                }.build()
+            )
         }
+        return result
     }
 }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/CollectionUtil.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/CollectionUtil.kt
new file mode 100644
index 0000000..bc94583
--- /dev/null
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/CollectionUtil.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 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.compose.ui.inspection.util
+
+import androidx.collection.IntList
+import androidx.collection.MutableIntList
+import androidx.collection.MutableLongObjectMap
+import androidx.collection.mutableIntListOf
+
+fun MutableIntList.removeLast() {
+    val last = lastIndex
+    if (last < 0) throw NoSuchElementException("List is empty.") else removeAt(last)
+}
+
+fun <T, M : MutableLongObjectMap<MutableList<T>>> Iterable<T>.groupByToLongObjectMap(
+    destination: M,
+    keySelector: (T) -> Long
+): M {
+    for (element in this) {
+        val key = keySelector(element)
+        val list = destination.getOrPut(key) { ArrayList() }
+        list.add(element)
+    }
+    return destination
+}
+
+fun IntList.copy(): IntList {
+    val result = mutableIntListOf()
+    forEach { result.add(it) }
+    return result
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/IntArray.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/IntArray.kt
deleted file mode 100644
index bce161e..0000000
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/IntArray.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2021 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.compose.ui.inspection.util
-
-private val EMPTY_INT_ARRAY = intArrayOf()
-
-fun List<Int>.asIntArray() =
-    if (isNotEmpty()) toIntArray() else EMPTY_INT_ARRAY
diff --git a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetector.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetector.kt
index 9988830..a563fca 100644
--- a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetector.kt
+++ b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetector.kt
@@ -28,6 +28,7 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.isIncorrectImplicitReturnInLambda
 import com.intellij.psi.PsiMethod
 import java.util.EnumSet
 import org.jetbrains.uast.UCallExpression
@@ -44,7 +45,8 @@
         if (!method.isInPackageName(Names.Ui.Pointer.PackageName)) return
         val methodParent = skipParenthesizedExprUp(node.uastParent)
         val isAssignedToVariable = methodParent is ULocalVariable
-        val isReturnExpression = methodParent is UReturnExpression
+        val isReturnExpression = methodParent is UReturnExpression &&
+            !methodParent.isIncorrectImplicitReturnInLambda()
 
         if (isAssignedToVariable || isReturnExpression) {
             context.report(
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetectorTest.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetectorTest.kt
index 1ee146d..1bda222 100644
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetectorTest.kt
+++ b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ReturnFromAwaitPointerEventScopeDetectorTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.lint.test.bytecodeStub
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
@@ -292,6 +293,7 @@
             ),
             *stubs,
         )
+            .skipTestModes(TestMode.BODY_REMOVAL)
             .run()
             .expect(
                 """
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index c41faf1..a7b8e52 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -62,16 +62,14 @@
             }
         }
 
-
         androidMain {
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.activity:activity:1.2.1")
-                implementation "androidx.activity:activity-compose:1.3.0"
                 api("androidx.test.ext:junit:1.1.5")
-		        implementation("androidx.annotation:annotation:1.1.0")
-
-                implementation(project(":compose:runtime:runtime-saveable"))
+                implementation("androidx.activity:activity-compose:1.3.0")
+                implementation("androidx.annotation:annotation:1.1.0")
+                implementation("androidx.compose.runtime:runtime-saveable:1.6.0")
                 implementation("androidx.lifecycle:lifecycle-common:2.5.1")
                 implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
                 implementation("androidx.test:core:1.5.0")
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 18d3ea7..68eb8e6 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -42,7 +42,7 @@
                 api(project(":compose:ui:ui"))
                 api(project(":compose:ui:ui-text"))
                 api(project(":compose:ui:ui-unit"))
-                api(project(":compose:runtime:runtime"))
+                api("androidx.compose.runtime:runtime:1.6.0")
                 api(libs.kotlinStdlib)
 
                 implementation(project(":compose:ui:ui-util"))
@@ -63,20 +63,17 @@
             }
         }
 
-
         androidMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation "androidx.activity:activity-compose:1.3.0"
-
                 api(project(":compose:ui:ui-graphics"))
-
+                implementation("androidx.activity:activity-compose:1.3.0")
                 implementation("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core-ktx:1.12.0")
                 implementation("androidx.test.espresso:espresso-core:3.5.0")
                 implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
-            	implementation("androidx.test:monitor:1.6.1")
-	        }
+                implementation("androidx.test:monitor:1.6.1")
+            }
         }
 
         desktopMain {
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/GlobalAssertionsTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/GlobalAssertionsTest.kt
index 8f2a7d1..6b00a30 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/GlobalAssertionsTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/GlobalAssertionsTest.kt
@@ -37,41 +37,44 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalTestApi::class)
 class GlobalAssertionsTest {
-  @get:Rule val composeTestRule = createComposeRule()
+    @get:Rule
+    val composeTestRule = createComposeRule()
 
-  @Before fun setUp() {}
+    @Before
+    fun setUp() {
+    }
 
-  @Test
-  fun performClick_withGlobalAssertion_triggersGlobalAssertion() {
-    composeTestRule.setContent { CountingButton() }
-    var capturedSni: SemanticsNodeInteraction? = null
+    @Test
+    fun performClick_withGlobalAssertion_triggersGlobalAssertion() {
+        composeTestRule.setContent { CountingButton() }
+        var capturedSni: SemanticsNodeInteraction? = null
 
-    addGlobalAssertion(/* name= */ "Fred") { sni -> capturedSni = sni }
-    composeTestRule.onNodeWithText("Increment counter").performClick()
+        addGlobalAssertion(/* name= */ "Fred") { sni -> capturedSni = sni }
+        composeTestRule.onNodeWithText("Increment counter").performClick()
 
-    composeTestRule.onNodeWithText("Clicks: 1").assertExists()
-    capturedSni!!.assertTextContains("Increment counter")
-  }
+        composeTestRule.onNodeWithText("Clicks: 1").assertExists()
+        capturedSni!!.assertTextContains("Increment counter")
+    }
 
-  @Test
-  fun performClick_withGlobalAssertionRemoved_doesNotTriggersGlobalAssertion() {
-    composeTestRule.setContent { CountingButton() }
-    var capturedSni: SemanticsNodeInteraction? = null
+    @Test
+    fun performClick_withGlobalAssertionRemoved_doesNotTriggersGlobalAssertion() {
+        composeTestRule.setContent { CountingButton() }
+        var capturedSni: SemanticsNodeInteraction? = null
 
-    addGlobalAssertion(/* name= */ "Fred") { sni -> capturedSni = sni }
-    removeGlobalAssertion(/* name= */ "Fred")
-    composeTestRule.onNodeWithText("Increment counter").performClick()
+        addGlobalAssertion(/* name= */ "Fred") { sni -> capturedSni = sni }
+        removeGlobalAssertion(/* name= */ "Fred")
+        composeTestRule.onNodeWithText("Increment counter").performClick()
 
-    composeTestRule.onNodeWithText("Clicks: 1").assertExists()
-    assertThat(capturedSni).isNull()
-  }
+        composeTestRule.onNodeWithText("Clicks: 1").assertExists()
+        assertThat(capturedSni).isNull()
+    }
 }
 
 @Composable
 internal fun CountingButton() {
-  var counter by remember { mutableStateOf(0) }
-  Column {
-    Button(onClick = { counter++ }) { Text("Increment counter") }
-    Text(text = "Clicks: $counter")
-  }
+    var counter by remember { mutableStateOf(0) }
+    Column {
+        Button(onClick = { counter++ }) { Text("Increment counter") }
+        Text(text = "Clicks: $counter")
+    }
 }
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/BitmapCapturingRetryLogicTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/BitmapCapturingRetryLogicTest.kt
new file mode 100644
index 0000000..31cbcc4
--- /dev/null
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/BitmapCapturingRetryLogicTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.test
+
+import android.view.PixelCopy
+import androidx.compose.testutils.expectError
+import androidx.compose.ui.test.android.PixelCopyException
+import androidx.compose.ui.test.android.runWithRetryWhenNoData
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BitmapCapturingRetryLogicTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun pixelCopyRequest_succeeded_noRetries() {
+        var attempt = 0
+
+        expectError<PixelCopyException>(false) {
+            runWithRetryWhenNoData {
+                try {
+                    // success
+                } finally {
+                    attempt++
+                }
+            }
+        }
+
+        assertThat(attempt).isEqualTo(1)
+    }
+
+    @Test
+    fun pixelCopyRequest_succeeded_afterRetry_whenNoData() {
+        var attempt = 0
+
+        expectError<PixelCopyException>(false) {
+            runWithRetryWhenNoData {
+                try {
+                    if (attempt == 0) {
+                        throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
+                    } else {
+                        // success
+                    }
+                } finally {
+                    attempt++
+                }
+            }
+        }
+    }
+
+    @Test
+    fun pixelCopyRequest_retry_whenNoData() {
+        var attempt = 0
+
+        expectError<PixelCopyException> {
+            runWithRetryWhenNoData {
+                try {
+                    throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
+                } finally {
+                    attempt++
+                }
+            }
+        }
+        assertThat(attempt).isEqualTo(3)
+    }
+
+    @Test
+    fun pixelCopyRequest_error_rethrow() {
+        expectError<PixelCopyException> {
+            runWithRetryWhenNoData {
+                throw PixelCopyException(PixelCopy.ERROR_UNKNOWN)
+            }
+        }
+    }
+
+    @Test
+    fun pixelCopyRequest_error_rethrow_afterRetry() {
+        var attempt = 0
+
+        expectError<PixelCopyException> {
+            runWithRetryWhenNoData {
+                try {
+                    if (attempt == 0) {
+                        throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
+                    } else {
+                        throw PixelCopyException(PixelCopy.ERROR_UNKNOWN)
+                    }
+                } finally {
+                    attempt++
+                }
+            }
+        }
+        assertThat(attempt).isEqualTo(2)
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/InfiniteAnimationPolicyTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/InfiniteAnimationPolicyTest.kt
new file mode 100644
index 0000000..bf7de9e
--- /dev/null
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/InfiniteAnimationPolicyTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 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.compose.ui.test
+
+import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
+import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
+import androidx.compose.ui.platform.InfiniteAnimationPolicy
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class InfiniteAnimationPolicyTest {
+
+    @Test
+    fun withInfiniteAnimationFrameNanos_policyIsApplied() {
+        withInfiniteAnimationFrame_policyIsApplied {
+            withInfiniteAnimationFrameNanos {}
+        }
+    }
+
+    @Test
+    fun withInfiniteAnimationFrameMillis_policyIsApplied() {
+        withInfiniteAnimationFrame_policyIsApplied {
+            withInfiniteAnimationFrameMillis {}
+        }
+    }
+
+    private fun <R> withInfiniteAnimationFrame_policyIsApplied(block: suspend () -> R) {
+        var applied = false
+        val policy = object : InfiniteAnimationPolicy {
+            override suspend fun <R> onInfiniteOperation(block: suspend () -> R): R {
+                applied = true
+                // We don't need to run the `block()` in our test, but we do need a return value
+                // of R. Throw a CancellationException to cancel the coroutine instead.
+                throw CancellationException()
+
+                // The reason why we can't run block() here, is because block() calls through to
+                // `DefaultMonotonicFrameClock.withFrameNanos`, which in host side tests
+                // dispatches on the main thread. But we're already blocking the main thread, so
+                // that would deadlock.
+            }
+        }
+
+        val caught = runCatching {
+            runBlocking(policy) {
+                block()
+            }
+        }
+
+        assertThat(applied).isTrue()
+        assertThat(caught.exceptionOrNull()).isInstanceOf(CancellationException::class.java)
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt
new file mode 100644
index 0000000..74e2049
--- /dev/null
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt
@@ -0,0 +1,728 @@
+/*
+ * Copyright 2023 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.compose.ui.test
+
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.testutils.WithTouchSlop
+import androidx.compose.testutils.expectError
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.findRootCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.espresso.AppNotIdleException
+import androidx.test.espresso.IdlingPolicies
+import androidx.test.espresso.IdlingPolicy
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.roundToInt
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@Config(minSdk = 21)
+@OptIn(ExperimentalTestApi::class)
+class RobolectricComposeTest {
+    private var masterTimeout: IdlingPolicy? = null
+
+    @Before
+    fun setup() {
+        masterTimeout = IdlingPolicies.getMasterIdlingPolicy()
+    }
+
+    @After
+    fun tearDown() {
+        masterTimeout?.let {
+            IdlingPolicies.setMasterPolicyTimeout(it.idleTimeout, it.idleTimeoutUnit)
+        }
+    }
+
+    @Composable
+    private fun ClickCounter(
+        clicks: MutableState<Int> = remember { mutableStateOf(0) }
+    ) {
+        Column {
+            Button(onClick = { clicks.value++ }) {
+                Text("Click me")
+            }
+            Text("Click count: ${clicks.value}")
+        }
+    }
+
+    /**
+     * Check that basic scenarios work: a composition that is recomposed due to a state change.
+     */
+    @Test
+    fun testStateChange() = runComposeUiTest {
+        val clicks = mutableStateOf(0)
+        setContent { ClickCounter(clicks) }
+        onNodeWithText("Click me").assertExists()
+
+        clicks.value++
+        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
+
+        clicks.value++
+        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 2")
+    }
+
+    /**
+     * Check that basic scenarios with input work: a composition that receives touch input and
+     * changes state as a result of that, triggering recomposition.
+     */
+    @Test
+    fun testInputInjection() = runComposeUiTest {
+        setContent { ClickCounter() }
+        onNodeWithText("Click me").assertExists()
+
+        onNodeWithText("Click me").performClick()
+        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
+
+        onNodeWithText("Click me").performClick()
+        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 2")
+    }
+
+    /**
+     * Check that animation scenarios work: a composition with an animation in its initial state
+     * is idle, stays non-idle while the animation animates to a new target and is idle again
+     * after that.
+     */
+    @Test
+    fun testAnimation() = runComposeUiTest {
+        var target by mutableStateOf(0f)
+        setContent {
+            val offset = animateFloatAsState(target)
+            Box(Modifier.fillMaxSize()) {
+                Box(
+                    Modifier
+                        .size(10.dp)
+                        .offset(x = offset.value.dp)
+                        .testTag("box")
+                )
+            }
+        }
+        onNodeWithTag("box").assertLeftPositionInRootIsEqualTo(0.dp)
+        target = 100f
+        onNodeWithTag("box").assertLeftPositionInRootIsEqualTo(100.dp)
+    }
+
+    /**
+     * Check that we catch a potential infinite composition loop caused by a measure lambda that
+     * triggers itself.
+     */
+    @Test(timeout = 10000)
+    fun testTimeout() = runComposeUiTest {
+        IdlingPolicies.setMasterPolicyTimeout(2, TimeUnit.SECONDS)
+        expectError<AppNotIdleException>(
+            expectedMessage = "Compose did not get idle after [0-9]* attempts in 2 SECONDS\\..*"
+        ) {
+            setContent {
+                var x by remember { mutableStateOf(0) }
+                Box(Modifier.requiredSize(100.dp)) {
+                    Layout({ Box(Modifier.size(10.dp)) }) { measurables, constraints ->
+                        val placeables = measurables.map { it.measure(constraints) }
+
+                        // read x, so we need to relayout when x changes
+                        val offset = if (x >= 0) 0 else -1
+                        val width = offset + placeables.maxOf { it.width }
+                        val height = offset + placeables.maxOf { it.height }
+
+                        // woops, we're always changing x during layout!
+                        x = if (x == 0) 1 else 0
+
+                        layout(width, height) {
+                            placeables.forEach { it.place(0, 0) }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Check that scrolling and controlling the clock works: a scrollable receives a swipe while
+     * the clock is paused, when the clock is resumed it performs the fling.
+     */
+    @OptIn(ExperimentalFoundationApi::class)
+    @Test
+    fun testControlledScrolling() = runComposeUiTest {
+        // Define constants used in the test
+        val n = 100
+        val touchSlop = 16f
+        val scrollState = ScrollState(0)
+        val flingBehavior = SimpleFlingBehavior(deltas = 20 downTo 1)
+
+        // Set content: a list where the fling is always the same, regardless of the swipe
+        setContent {
+            WithTouchSlop(touchSlop = touchSlop) {
+                // turn off visual overscroll for calculation correctness
+                CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+                    Box(Modifier.fillMaxSize()) {
+                        Column(
+                            Modifier
+                                .requiredSize(200.dp)
+                                .verticalScroll(
+                                    scrollState,
+                                    flingBehavior = flingBehavior
+                                )
+                                .testTag("list")
+                        ) {
+                            repeat(n) {
+                                Spacer(
+                                    Modifier
+                                        .fillMaxWidth()
+                                        .height(30.dp)
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Stop auto advancing and perform a swipe. The list will "freeze" in the position where
+        // it was at the end of the swipe
+        mainClock.autoAdvance = false
+        onNodeWithTag("list").performTouchInput {
+            down(bottomCenter)
+            repeat(10) {
+                moveTo(bottomCenter - percentOffset(y = (it + 1) / 10f))
+            }
+            up()
+        }
+        waitForIdle()
+
+        // Check that we're in that frozen position
+        val expectedViewPortSize = with(density) { 200.dp.toPx() }
+        val expectedSwipeDistance = (expectedViewPortSize - touchSlop).roundToInt()
+        assertThat(scrollState.value).isEqualTo(expectedSwipeDistance)
+
+        // "Unfreeze" the list and let the fling run. The list will stop at
+        // `flingBehavior.totalDistance` pixels further than where it was frozen.
+        mainClock.autoAdvance = true
+        waitForIdle()
+        val expectedFlingDistance = flingBehavior.totalDistance
+        assertThat(scrollState.value).isEqualTo(expectedSwipeDistance + expectedFlingDistance)
+    }
+
+    // Regression test for b/227120770
+    @Test
+    fun testTextFieldInteraction() = runComposeUiTest {
+        val text = "a"
+        var updatedText = ""
+        setContent {
+            TextField(value = text, onValueChange = { updatedText = it })
+        }
+        onNodeWithText(text).assertIsDisplayed()
+        // If selection isn't set on initial state, it will be 0.
+        onNodeWithText(text).performTextInputSelection(TextRange(1))
+        onNodeWithText(text).performTextInput("b")
+        runOnIdle {
+            assertThat(updatedText).isEqualTo("ab")
+        }
+    }
+
+    /**
+     * A simple [FlingBehavior] that scrolls one [delta][deltas] every frame regardless of velocity.
+     */
+    private class SimpleFlingBehavior(private val deltas: IntProgression) : FlingBehavior {
+        val totalDistance = deltas.sum()
+
+        override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+            for (delta in deltas) {
+                withFrameNanos {
+                    scrollBy(delta.toFloat())
+                }
+            }
+            return 0f
+        }
+    }
+
+    @Test
+    fun testComposeMainQueueTrampolining() = runComposeUiTest {
+        val trampoliningIterations = 10
+        var phase by mutableStateOf(0)
+        var done = false
+        val handler = Handler(Looper.getMainLooper())
+
+        setContent {
+            // Alternate scheduling work to the main queue and scheduling work for Compose.
+            // waitForIdle shouldn't return until both Compose and the main queue have nothing to
+            // do.
+            if (phase in 1 until trampoliningIterations) {
+                DisposableEffect(phase) {
+                    handler.post { phase++ }
+                    onDispose {}
+                }
+            } else if (phase == trampoliningIterations) {
+                DisposableEffect(Unit) {
+                    done = true
+                    onDispose {}
+                }
+            }
+        }
+
+        runOnIdle {
+            assertThat(phase).isEqualTo(0)
+            assertThat(done).isFalse()
+        }
+
+        // Trigger the trampolining.
+        phase = 1
+
+        // If Robolectric and Compose don't coordinate idleness correctly, waitForIdle will return
+        // before finishing the chain of trampolined work, and we won't be in the expected terminal
+        // state.
+        runOnIdle {
+            assertThat(phase).isEqualTo(trampoliningIterations)
+            assertThat(done).isTrue()
+        }
+    }
+
+    @Test
+    fun testWaitForPopupWindow() = runComposeUiTest {
+        var expanded by mutableStateOf(false)
+
+        setContent {
+            Box(Modifier.requiredSize(20.dp)) {
+                if (expanded) {
+                    Popup {
+                        DropdownMenuItem(modifier = Modifier.testTag("MenuContent"), onClick = {}) {
+                            Text("Option 1")
+                        }
+                    }
+                }
+            }
+        }
+
+        onNodeWithTag("MenuContent").assertDoesNotExist()
+
+        expanded = true
+
+        onNodeWithTag("MenuContent").assertIsDisplayed()
+    }
+
+    /*
+     * Two tests properly demonstrating how to properly advance the clock (advanceTimeBy()) while
+     * testing pointer input events using performTouchInput() with espresso and/or Robolectric).
+     */
+    @Test
+    fun areTwoTapsFired_dispatchTwoDelayedTapsWithPerformTouchInput_assertTrue() =
+        runComposeUiTest {
+            var composableTouchCount = 0
+            var composableTapCount = 0
+            var composableDoubleTapCount = 0
+            var composableLongTapCount = 0
+
+            val setupLatch = CountDownLatch(1)
+            val tapLatch = CountDownLatch(2)
+
+            setContent {
+                Box(
+                    modifier = Modifier
+                        .testTag("mainBox")
+                        .fillMaxSize()
+                        .pointerInput(Unit) {
+                            detectTapGestures(
+                                onPress = {
+                                    ++composableTouchCount
+                                },
+                                onTap = {
+                                    tapLatch.countDown()
+                                    ++composableTapCount
+                                },
+                                onDoubleTap = {
+                                    ++composableDoubleTapCount
+                                },
+                                onLongPress = {
+                                    ++composableLongTapCount
+                                }
+                            )
+                        }
+                        .onGloballyPositioned {
+                            setupLatch.countDown()
+                        }
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .fillMaxSize()
+                    )
+                }
+            }
+            assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
+
+            onNodeWithTag("mainBox").assertIsDisplayed()
+
+            runOnIdle {
+                onNodeWithTag("mainBox").performTouchInput {
+                    down(center)
+                    up()
+                }
+            }
+
+            // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward
+            // when using detectTapGestures {} as parts of it rely on changes in the clock
+            // (double tap, etc.).
+            mainClock.advanceTimeBy(400)
+
+            runOnIdle {
+                onNodeWithTag("mainBox").performTouchInput {
+                    down(center)
+                    up()
+                }
+            }
+
+            // Delay again to trigger second single tap (times out double tap detector)
+            mainClock.advanceTimeBy(400)
+
+            assertTrue(tapLatch.await(10, TimeUnit.SECONDS))
+
+            assertThat(composableTouchCount).isEqualTo(2)
+            assertThat(composableTapCount).isEqualTo(2)
+            assertThat(composableDoubleTapCount).isEqualTo(0)
+            assertThat(composableLongTapCount).isEqualTo(0)
+        }
+
+    @Test
+    fun isDoubleTapFired_dispatchTwoTapsWithPerformTouchInput_assertTrue() = runComposeUiTest {
+        var composableTouchCount = 0
+        var composableTapCount = 0
+        var composableDoubleTapCount = 0
+        var composableLongTapCount = 0
+
+        val setupLatch = CountDownLatch(1)
+        val doubleTapLatch = CountDownLatch(1)
+
+        setContent {
+            Box(
+                modifier = Modifier
+                    .testTag("mainBox")
+                    .fillMaxSize()
+                    .pointerInput(Unit) {
+                        detectTapGestures(
+                            onPress = {
+                                ++composableTouchCount
+                            },
+                            onTap = {
+                                ++composableTapCount
+                            },
+                            onDoubleTap = {
+                                doubleTapLatch.countDown()
+                                ++composableDoubleTapCount
+                            },
+                            onLongPress = {
+                                ++composableLongTapCount
+                            }
+                        )
+                    }
+                    .onGloballyPositioned {
+                        setupLatch.countDown()
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                )
+            }
+        }
+        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
+
+        onNodeWithTag("mainBox").assertIsDisplayed()
+
+        runOnIdle {
+            onNodeWithTag("mainBox").performTouchInput {
+                down(center)
+                up()
+            }
+        }
+
+        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
+        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
+        mainClock.advanceTimeBy(100)
+
+        runOnIdle {
+            onNodeWithTag("mainBox").performTouchInput { down(center) }
+            onNodeWithTag("mainBox").performTouchInput { up() }
+        }
+
+        // Delay but just enough to stay inside double tap timeframe
+        mainClock.advanceTimeBy(100)
+
+        assertTrue(doubleTapLatch.await(10, TimeUnit.SECONDS))
+
+        assertThat(composableTouchCount).isEqualTo(2)
+        assertThat(composableTapCount).isEqualTo(0)
+        assertThat(composableDoubleTapCount).isEqualTo(1)
+        assertThat(composableLongTapCount).isEqualTo(0)
+    }
+
+    /*
+     * Two tests properly demonstrating how to properly advance the clock (advanceTimeBy()) while
+     * testing pointer input events using manually created MotionEvents with espresso and/or
+     * Robolectric).
+     */
+    @Test
+    fun isTapFired_dispatchTapWithMotionEvents_assertTrue() = runComposeUiTest {
+        var composableTouchCount = 0
+        var composableTapCount = 0
+        var composableDoubleTapCount = 0
+        var composableLongTapCount = 0
+
+        val setupLatch = CountDownLatch(1)
+        val tapLatch = CountDownLatch(1)
+
+        var bottomBoxInnerCoordinates: LayoutCoordinates? = null
+        var topLevelContainerView: View? = null
+
+        setContent {
+            topLevelContainerView = LocalView.current
+
+            Box(
+                modifier = Modifier
+                    .testTag("mainBox")
+                    .fillMaxSize()
+                    .pointerInput(Unit) {
+                        detectTapGestures(
+                            onPress = {
+                                ++composableTouchCount
+                            },
+                            onTap = {
+                                tapLatch.countDown()
+                                ++composableTapCount
+                            },
+                            onDoubleTap = {
+                                ++composableDoubleTapCount
+                            },
+                            onLongPress = {
+                                ++composableLongTapCount
+                            }
+                        )
+                    }
+                    .onGloballyPositioned {
+                        setupLatch.countDown()
+                        bottomBoxInnerCoordinates = it
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                )
+            }
+        }
+        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
+
+        onNodeWithTag("mainBox").assertIsDisplayed()
+
+        val root = bottomBoxInnerCoordinates!!.findRootCoordinates()
+        val topBoxOffset = root.localPositionOf(bottomBoxInnerCoordinates!!, Offset.Zero)
+        val topBoxFingerPointerPropertiesId = 0
+        val topBoxPointerProperties =
+            MotionEvent.PointerProperties().also {
+                it.id = topBoxFingerPointerPropertiesId
+                it.toolType = MotionEvent.TOOL_TYPE_FINGER
+            }
+        val coords = MotionEvent.PointerCoords()
+        coords.x = topBoxOffset.x
+        coords.y = topBoxOffset.y
+
+        val motionEventDown = MotionEventBuilder.newBuilder()
+            .setEventTime(0)
+            .setAction(MotionEvent.ACTION_DOWN)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        val motionEventUp = MotionEventBuilder.newBuilder()
+            .setEventTime(100)
+            .setAction(MotionEvent.ACTION_UP)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        topLevelContainerView?.dispatchTouchEvent(motionEventDown)
+        topLevelContainerView?.dispatchTouchEvent(motionEventUp)
+
+        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
+        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
+        // Delay to trigger second single tap (times out double tap detector)
+        mainClock.advanceTimeBy(400)
+
+        assertTrue(tapLatch.await(10, TimeUnit.SECONDS))
+
+        assertThat(composableTouchCount).isEqualTo(1)
+        assertThat(composableTapCount).isEqualTo(1)
+        assertThat(composableDoubleTapCount).isEqualTo(0)
+        assertThat(composableLongTapCount).isEqualTo(0)
+    }
+
+    @Test
+    fun isDoubleTapFired_dispatchTwoTapsWithMotionEvents_assertTrue() = runComposeUiTest {
+        var composableTouchCount = 0
+        var composableTapCount = 0
+        var composableDoubleTapCount = 0
+        var composableLongTapCount = 0
+
+        val setupLatch = CountDownLatch(1)
+        val doubleTapLatch = CountDownLatch(1)
+
+        var bottomBoxInnerCoordinates: LayoutCoordinates? = null
+        var topLevelContainerView: View? = null
+
+        setContent {
+            topLevelContainerView = LocalView.current
+
+            Box(
+                modifier = Modifier
+                    .testTag("mainBox")
+                    .fillMaxSize()
+                    .pointerInput(Unit) {
+                        detectTapGestures(
+                            onPress = {
+                                ++composableTouchCount
+                            },
+                            onTap = {
+                                ++composableTapCount
+                            },
+                            onDoubleTap = {
+                                doubleTapLatch.countDown()
+                                ++composableDoubleTapCount
+                            },
+                            onLongPress = {
+                                ++composableLongTapCount
+                            }
+                        )
+                    }
+                    .onGloballyPositioned {
+                        setupLatch.countDown()
+                        bottomBoxInnerCoordinates = it
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                )
+            }
+        }
+        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
+
+        val root = bottomBoxInnerCoordinates!!.findRootCoordinates()
+        val topBoxOffset = root.localPositionOf(bottomBoxInnerCoordinates!!, Offset.Zero)
+        val topBoxFingerPointerPropertiesId = 0
+        val topBoxPointerProperties =
+            MotionEvent.PointerProperties().also {
+                it.id = topBoxFingerPointerPropertiesId
+                it.toolType = MotionEvent.TOOL_TYPE_FINGER
+            }
+        val coords = MotionEvent.PointerCoords()
+        coords.x = topBoxOffset.x
+        coords.y = topBoxOffset.y
+
+        val motionEventDown1 = MotionEventBuilder.newBuilder()
+            .setEventTime(0)
+            .setAction(MotionEvent.ACTION_DOWN)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        val motionEventUp1 = MotionEventBuilder.newBuilder()
+            .setEventTime(50)
+            .setAction(MotionEvent.ACTION_UP)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        topLevelContainerView?.dispatchTouchEvent(motionEventDown1)
+        topLevelContainerView?.dispatchTouchEvent(motionEventUp1)
+
+        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
+        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
+        mainClock.advanceTimeBy(100)
+
+        val motionEventDown2 = MotionEventBuilder.newBuilder()
+            .setEventTime(100)
+            .setAction(MotionEvent.ACTION_DOWN)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        val motionEventUp2 = MotionEventBuilder.newBuilder()
+            .setEventTime(150)
+            .setAction(MotionEvent.ACTION_UP)
+            .setActionIndex(0)
+            .setPointer(topBoxPointerProperties, coords)
+            .build()
+
+        topLevelContainerView?.dispatchTouchEvent(motionEventDown2)
+        topLevelContainerView?.dispatchTouchEvent(motionEventUp2)
+
+        // Delay but just enough to stay inside double tap timeframe
+        mainClock.advanceTimeBy(100)
+
+        assertTrue(doubleTapLatch.await(2, TimeUnit.SECONDS))
+
+        assertThat(composableTouchCount).isEqualTo(2)
+        assertThat(composableTapCount).isEqualTo(0)
+        assertThat(composableDoubleTapCount).isEqualTo(1)
+        assertThat(composableLongTapCount).isEqualTo(0)
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/ViewVisibilityRobolectricTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/ViewVisibilityRobolectricTest.kt
new file mode 100644
index 0000000..cdc490b
--- /dev/null
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/ViewVisibilityRobolectricTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 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.compose.ui.test
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.dp
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
+import org.robolectric.annotation.Config
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@Config(minSdk = 21)
+@OptIn(ExperimentalTestApi::class)
+class ViewVisibilityRobolectricTest(private val visibility: Int) {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "visibility={0}")
+        fun params() = listOf(
+            View.VISIBLE,
+            View.INVISIBLE,
+            View.GONE
+        )
+    }
+
+    private var offset by mutableStateOf(0)
+
+    private fun toggleState() {
+        offset = 10
+    }
+
+    @Test
+    fun noTimeout_hostView_visibility() {
+        runComposeUiTest {
+            setContent {
+                val hostView = LocalView.current
+                SideEffect {
+                    hostView.visibility = visibility
+                }
+                TestContent()
+            }
+
+            val expectDisplayed = visibility == View.VISIBLE
+            checkUi(expectDisplayed)
+            toggleState()
+            checkUi(expectDisplayed)
+        }
+    }
+
+    @Test
+    fun noTimeout_composeView_visibility() {
+        runAndroidComposeUiTest<ComponentActivity> {
+            runOnUiThread {
+                val activity = activity!!
+                val composeView = ComposeView(activity)
+                composeView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+                composeView.visibility = visibility
+                composeView.setContent {
+                    TestContent()
+                }
+                activity.setContentView(composeView)
+            }
+
+            val expectDisplayed = visibility == View.VISIBLE
+            checkUi(expectDisplayed)
+            toggleState()
+            checkUi(expectDisplayed)
+        }
+    }
+
+    @Composable
+    private fun TestContent() {
+        // Read state in layout and not in measure, so a state change will trigger layout, but
+        // not measure. Because measure is not affected, the containing View doesn't need to be
+        // remeasured and Compose will do its measure/layout pass in the draw pass, meaning the
+        // View will be invalidated but no layout will be requested.
+        Layout({
+            Box(Modifier.size(10.dp))
+        }, Modifier.fillMaxSize().testTag("box")) { measurables, constraints ->
+            val placeable = measurables.first().measure(constraints)
+            layout(constraints.maxWidth, constraints.maxHeight) {
+                placeable.place(offset, 0)
+            }
+        }
+    }
+
+    private fun SemanticsNodeInteractionsProvider.checkUi(expectDisplayed: Boolean) {
+        // It should always exist
+        onNodeWithTag("box").assertExists()
+        // But only be displayed in tests where visibility = View.VISIBLE
+        if (expectDisplayed) {
+            onNodeWithTag("box").assertIsDisplayed()
+        } else {
+            onNodeWithTag("box").assertIsNotDisplayed()
+        }
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
new file mode 100644
index 0000000..71d02be
--- /dev/null
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2022 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.compose.ui.test.inputdispatcher
+
+import android.view.KeyEvent
+import android.view.MotionEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.test.AndroidInputDispatcher
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.InputDispatcher
+import androidx.compose.ui.test.MouseButton
+import androidx.compose.ui.test.RobolectricMinSdk
+import androidx.compose.ui.test.ScrollWheel
+import androidx.compose.ui.test.util.assertHasValidEventTimes
+import androidx.compose.ui.test.util.verifyKeyEvent
+import androidx.compose.ui.test.util.verifyMouseEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+/**
+ * Tests if [AndroidInputDispatcher.enqueueKeyDown], [AndroidInputDispatcher.enqueueKeyUp]  and
+ * friends work.
+ */
+@RunWith(AndroidJUnit4::class)
+@Config(minSdk = RobolectricMinSdk)
+@OptIn(ExperimentalTestApi::class)
+class KeyAndMouseEventsTest : InputDispatcherTest() {
+
+    companion object {
+
+        // Positions
+        private val position1 = Offset(1f, 1f)
+
+        // Keys and meta state masks
+        private const val keyDown = KeyEvent.ACTION_DOWN
+        private const val keyUp = KeyEvent.ACTION_UP
+
+        private val functionKey = Key.Function
+        private const val functionKeyMetaMask = KeyEvent.META_FUNCTION_ON
+
+        private val leftCtrl = Key.CtrlLeft
+        private const val leftCtrlMask = KeyEvent.META_CTRL_LEFT_ON or KeyEvent.META_CTRL_ON
+        private val rightCtrl = Key.CtrlRight
+        private const val rightCtrlMask = KeyEvent.META_CTRL_RIGHT_ON or KeyEvent.META_CTRL_ON
+
+        private val leftAlt = Key.AltLeft
+        private const val leftAltMask = KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON
+        private val rightAlt = Key.AltRight
+        private const val rightAltMask = KeyEvent.META_ALT_RIGHT_ON or KeyEvent.META_ALT_ON
+
+        private val leftMeta = Key.MetaLeft
+        private const val leftMetaMask = KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON
+        private val rightMeta = Key.MetaRight
+        private const val rightMetaMask = KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON
+
+        private val leftShift = Key.ShiftLeft
+        private const val leftShiftMask = KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON
+        private val rightShift = Key.ShiftRight
+        private const val rightShiftMask = KeyEvent.META_SHIFT_RIGHT_ON or KeyEvent.META_SHIFT_ON
+
+        private val capsLock = Key.CapsLock
+        private const val capsLockMask = KeyEvent.META_CAPS_LOCK_ON
+        private val numLock = Key.NumLock
+        private const val numLockMask = KeyEvent.META_NUM_LOCK_ON
+        private val scrollLock = Key.ScrollLock
+        private const val scrollLockMask = KeyEvent.META_SCROLL_LOCK_ON
+
+        private const val allLockMasks = capsLockMask or numLockMask or scrollLockMask
+
+        private const val allMetaMasks = functionKeyMetaMask or leftCtrlMask or rightCtrlMask or
+            leftAltMask or rightAltMask or leftMetaMask or rightMetaMask or
+            leftShiftMask or rightShiftMask
+
+        private const val allMasks = allLockMasks or allMetaMasks
+    }
+
+    @Test
+    fun functionKey_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(functionKey, functionKeyMetaMask)
+
+    @Test
+    fun leftCtrl_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(leftCtrl, leftCtrlMask)
+
+    @Test
+    fun rightCtrl_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(rightCtrl, rightCtrlMask)
+
+    @Test
+    fun leftAlt_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(leftAlt, leftAltMask)
+
+    @Test
+    fun rightAlt_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(rightAlt, rightAltMask)
+
+    @Test
+    fun leftMeta_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(leftMeta, leftMetaMask)
+
+    @Test
+    fun rightMeta_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(rightMeta, rightMetaMask)
+
+    @Test
+    fun leftShift_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(leftShift, leftShiftMask)
+
+    @Test
+    fun rightShift_metaState_generatedCorrectly() =
+        verifyMetaKeyClickState(rightShift, rightShiftMask)
+
+    @Test
+    fun capsLock_metaState_generatedCorrectly() =
+        verifyLockKeyClickState(capsLock, capsLockMask)
+
+    @Test
+    fun numLock_metaState_generatedCorrectly() =
+        verifyLockKeyClickState(numLock, numLockMask)
+
+    @Test
+    fun scrollLock_metaState_generatedCorrectly() =
+        verifyLockKeyClickState(scrollLock, scrollLockMask)
+
+    @Test
+    fun lockKeys_metaState_combinedCorrectly_inMousePress() {
+        enqueueKeyPress(capsLock)
+        enqueueKeyPress(numLock)
+        enqueueKeyPress(scrollLock)
+        subject.verifyMousePosition(Offset.Zero)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.flush()
+
+        assertEquals(8, recorder.events.size)
+        recorder.events[5].verifyKeyEvent(keyUp, scrollLock.nativeKeyCode,
+            expectedMetaState = allLockMasks)
+        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allLockMasks)
+        recorder.events[7].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allLockMasks)
+    }
+
+    @Test
+    fun metaKeys_metaState_combinedCorrectly_inMousePress() {
+        subject.enqueueKeyDown(functionKey)
+        subject.enqueueKeyDown(leftCtrl)
+        subject.enqueueKeyDown(rightCtrl)
+        subject.enqueueKeyDown(leftAlt)
+        subject.enqueueKeyDown(rightAlt)
+        subject.enqueueKeyDown(leftMeta)
+        subject.enqueueKeyDown(rightMeta)
+        subject.enqueueKeyDown(leftShift)
+        subject.enqueueKeyDown(rightShift)
+        subject.verifyMousePosition(Offset.Zero)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.flush()
+
+        assertEquals(11, recorder.events.size)
+        recorder.events[8].verifyKeyEvent(keyDown, rightShift.nativeKeyCode,
+            expectedMetaState = allMetaMasks)
+        recorder.events[9].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMetaMasks)
+        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMetaMasks)
+    }
+
+    @Test
+    fun metaAndLockKeys_metaState_combinedCorrectly_inMousePress() {
+        enqueueKeyPress(capsLock) // key up + down = 2
+        enqueueKeyPress(numLock) // + key up + down = 4
+        enqueueKeyPress(scrollLock) // + key up + down = 6
+        subject.enqueueKeyDown(functionKey) // + key down = 7
+        subject.enqueueKeyDown(leftCtrl) // + key down = 8
+        subject.enqueueKeyDown(rightCtrl) // + key down = 9
+        subject.enqueueKeyDown(leftAlt) // + key down = 10
+        subject.enqueueKeyDown(rightAlt) // + key down = 11
+        subject.enqueueKeyDown(leftMeta) // + key down = 12
+        subject.enqueueKeyDown(rightMeta) // + key down = 13
+        subject.enqueueKeyDown(leftShift) // + key down = 14
+        subject.enqueueKeyDown(rightShift) // + key down = 15
+        subject.verifyMousePosition(Offset.Zero)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId) // + mouse down + press = 17
+        subject.flush()
+
+        assertEquals(17, recorder.events.size)
+        recorder.events[14].verifyKeyEvent(keyDown, rightShift.nativeKeyCode,
+            expectedMetaState = allMasks)
+        recorder.events[15].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMasks)
+        recorder.events[16].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMasks)
+    }
+
+    @Test
+    fun scroll_vertically() {
+        scrollTest(ScrollWheel.Vertical) { Pair(MotionEvent.AXIS_VSCROLL, -it) }
+    }
+
+    @Test
+    fun scroll_horizontally() {
+        scrollTest(ScrollWheel.Horizontal) { Pair(MotionEvent.AXIS_HSCROLL, it) }
+    }
+
+    private fun scrollTest(scrollWheel: ScrollWheel, scrollAxis: (Float) -> Pair<Int, Float>) {
+
+        var expectedEvents = 0
+        enqueueKeyPress(capsLock) // key up + down = 2
+        enqueueKeyPress(numLock) // + key up + down = 4
+        enqueueKeyPress(scrollLock) // + key up + down = 6
+        subject.enqueueKeyDown(functionKey) // + key down = 7
+        subject.enqueueKeyDown(leftCtrl) // + key down = 8
+        subject.enqueueKeyDown(rightCtrl) // + key down = 9
+        subject.enqueueKeyDown(leftAlt) // + key down = 10
+        subject.enqueueKeyDown(rightAlt) // + key down = 11
+        subject.enqueueKeyDown(leftMeta) // + key down = 12
+        subject.enqueueKeyDown(rightMeta) // + key down = 13
+        subject.enqueueKeyDown(leftShift) // + key down = 14
+        subject.enqueueKeyDown(rightShift) // + key down = 15
+        expectedEvents += 15
+        subject.verifyMousePosition(Offset.Zero)
+        subject.enqueueMouseMove(position1)
+        subject.verifyMousePosition(position1)
+        expectedEvents += 2 // enter + hover
+        subject.advanceEventTime()
+        subject.enqueueMouseScroll(1f, scrollWheel)
+        expectedEvents += 2 // hover + scroll
+        subject.advanceEventTime()
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        expectedEvents += 3 // exit + down + press
+        subject.advanceEventTime()
+        subject.enqueueMouseScroll(2f, scrollWheel)
+        expectedEvents += 2 // move + scroll
+        subject.advanceEventTime()
+        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
+        expectedEvents += 4 // release + up + enter + hover
+        subject.advanceEventTime()
+        subject.enqueueMouseScroll(3f, scrollWheel)
+        expectedEvents += 2 // hover + scroll
+        subject.flush()
+
+        recorder.assertHasValidEventTimes()
+        Truth.assertThat(recorder.events).hasSize(expectedEvents)
+        val events = recorder.events.toMutableList()
+
+        // Remove key presses - they aren't being tested here.
+        events.removeFirst(15)
+
+        // enter + hover
+        var t = 0L
+        var buttonState = 0
+        events.removeFirst(2).let { (enterEvent, hoverEvent) ->
+            enterEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_ENTER, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            hoverEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_MOVE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+        }
+
+        // hover + scroll
+        t += InputDispatcher.eventPeriodMillis
+        events.removeFirst(2).let { (hoverEvent, scrollEvent) ->
+            hoverEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_MOVE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            scrollEvent.verifyMouseEvent(
+                MotionEvent.ACTION_SCROLL, t,
+                position1, buttonState, scrollAxis(1f), expectedMetaState = allMasks)
+        }
+
+        // exit + down + press
+        t = 0L // down resets downTime
+        buttonState = MotionEvent.BUTTON_PRIMARY
+        events.removeFirst(3).let { (exitEvent, downEvent, pressEvent) ->
+            exitEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_EXIT, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            downEvent.verifyMouseEvent(
+                MotionEvent.ACTION_DOWN, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            pressEvent.verifyMouseEvent(
+                MotionEvent.ACTION_BUTTON_PRESS, t,
+                position1, buttonState, expectedMetaState = allMasks)
+        }
+
+        // move + scroll
+        t += InputDispatcher.eventPeriodMillis
+        events.removeFirst(2).let { (moveEvent, scrollEvent) ->
+            moveEvent.verifyMouseEvent(
+                MotionEvent.ACTION_MOVE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            scrollEvent.verifyMouseEvent(
+                MotionEvent.ACTION_SCROLL, t,
+                position1, buttonState, scrollAxis(2f), expectedMetaState = allMasks)
+        }
+
+        // release + up + enter + hover
+        t += InputDispatcher.eventPeriodMillis
+        buttonState = 0
+        events.removeFirst(4).let { (releaseEvent, upEvent, enterEvent, hoverEvent) ->
+            releaseEvent.verifyMouseEvent(
+                MotionEvent.ACTION_BUTTON_RELEASE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            upEvent.verifyMouseEvent(
+                MotionEvent.ACTION_UP, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            enterEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_ENTER, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            hoverEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_MOVE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+        }
+
+        // hover + scroll
+        t += InputDispatcher.eventPeriodMillis
+        events.removeFirst(2).let { (hoverEvent, scrollEvent) ->
+            hoverEvent.verifyMouseEvent(
+                MotionEvent.ACTION_HOVER_MOVE, t,
+                position1, buttonState, expectedMetaState = allMasks)
+            scrollEvent.verifyMouseEvent(
+                MotionEvent.ACTION_SCROLL, t,
+                position1, buttonState, scrollAxis(3f), expectedMetaState = allMasks)
+        }
+    }
+
+    private fun AndroidInputDispatcher.verifyMousePosition(expectedPosition: Offset) {
+        Truth.assertWithMessage("currentMousePosition")
+            .that(currentMousePosition).isEqualTo(expectedPosition)
+    }
+
+    private fun <E> MutableList<E>.removeFirst(n: Int): List<E> {
+        return mutableListOf<E>().also { result ->
+            repeat(n) { result.add(removeFirst()) }
+        }
+    }
+
+    private fun enqueueKeyPress(key: Key) {
+        subject.enqueueKeyDown(key)
+        subject.enqueueKeyUp(key)
+    }
+
+    private fun verifyMetaKeyClickState(key: Key, expectedMetaState: Int = 0) {
+        subject.enqueueKeyDown(key)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
+        subject.enqueueKeyUp(key)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.flush()
+
+        recorder.assertHasValidEventTimes()
+        Truth.assertThat(recorder.events).hasSize(11)
+
+        // Key Down
+        recorder.events[0].verifyKeyEvent(keyDown, key.nativeKeyCode,
+            expectedMetaState = expectedMetaState)
+
+        // Mouse Press
+        recorder.events[1].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
+        recorder.events[2].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
+
+        // Mouse Release
+        recorder.events[3].verifyMouseEvent(MotionEvent.ACTION_BUTTON_RELEASE, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[4].verifyMouseEvent(MotionEvent.ACTION_UP, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[5].verifyMouseEvent(MotionEvent.ACTION_HOVER_ENTER, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_HOVER_MOVE, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+
+        // Key Release
+        recorder.events[7].verifyKeyEvent(keyUp, key.nativeKeyCode)
+
+        // Mouse Press
+        recorder.events[8].verifyMouseEvent(MotionEvent.ACTION_HOVER_EXIT, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+        recorder.events[9].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+    }
+
+    private fun verifyLockKeyClickState(key: Key, expectedMetaState: Int = 0) {
+        enqueueKeyPress(key)
+        subject.verifyMousePosition(Offset.Zero)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
+        enqueueKeyPress(key)
+        subject.enqueueMousePress(MouseButton.Primary.buttonId)
+        subject.flush()
+
+        recorder.assertHasValidEventTimes()
+        Truth.assertThat(recorder.events).hasSize(13)
+
+        // Key Toggle On
+        recorder.events[0].verifyKeyEvent(keyDown, key.nativeKeyCode,
+            expectedMetaState = expectedMetaState)
+        recorder.events[1].verifyKeyEvent(keyUp, key.nativeKeyCode,
+            expectedMetaState = expectedMetaState)
+
+        // Mouse Press
+        recorder.events[2].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
+        recorder.events[3].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
+
+        // Mouse Release
+        recorder.events[4].verifyMouseEvent(MotionEvent.ACTION_BUTTON_RELEASE, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[5].verifyMouseEvent(MotionEvent.ACTION_UP, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_HOVER_ENTER, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+        recorder.events[7].verifyMouseEvent(MotionEvent.ACTION_HOVER_MOVE, 0L,
+            Offset.Zero, 0, expectedMetaState = expectedMetaState)
+
+        // Key Toggle Off
+        recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode)
+        recorder.events[9].verifyKeyEvent(keyUp, key.nativeKeyCode)
+
+        // Mouse Press
+        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_HOVER_EXIT, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+        recorder.events[11].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+        recorder.events[12].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
+            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/multimodal/KeyAndMouseEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/multimodal/KeyAndMouseEventsTest.kt
deleted file mode 100644
index 6d52228..0000000
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/multimodal/KeyAndMouseEventsTest.kt
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright 2022 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.compose.ui.test.inputdispatcher.multimodal
-
-import android.view.KeyEvent
-import android.view.MotionEvent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.nativeKeyCode
-import androidx.compose.ui.test.AndroidInputDispatcher
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.InputDispatcher
-import androidx.compose.ui.test.MouseButton
-import androidx.compose.ui.test.RobolectricMinSdk
-import androidx.compose.ui.test.ScrollWheel
-import androidx.compose.ui.test.inputdispatcher.InputDispatcherTest
-import androidx.compose.ui.test.util.assertHasValidEventTimes
-import androidx.compose.ui.test.util.verifyKeyEvent
-import androidx.compose.ui.test.util.verifyMouseEvent
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-
-/**
- * Tests if [AndroidInputDispatcher.enqueueKeyDown], [AndroidInputDispatcher.enqueueKeyUp]  and
- * friends work.
- */
-@RunWith(AndroidJUnit4::class)
-@Config(minSdk = RobolectricMinSdk)
-@OptIn(ExperimentalTestApi::class)
-class KeyAndMouseEventsTest : InputDispatcherTest() {
-
-    companion object {
-
-        // Positions
-        private val position1 = Offset(1f, 1f)
-
-        // Keys and meta state masks
-        private const val keyDown = KeyEvent.ACTION_DOWN
-        private const val keyUp = KeyEvent.ACTION_UP
-
-        private val functionKey = Key.Function
-        private const val functionKeyMetaMask = KeyEvent.META_FUNCTION_ON
-
-        private val leftCtrl = Key.CtrlLeft
-        private const val leftCtrlMask = KeyEvent.META_CTRL_LEFT_ON or KeyEvent.META_CTRL_ON
-        private val rightCtrl = Key.CtrlRight
-        private const val rightCtrlMask = KeyEvent.META_CTRL_RIGHT_ON or KeyEvent.META_CTRL_ON
-
-        private val leftAlt = Key.AltLeft
-        private const val leftAltMask = KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON
-        private val rightAlt = Key.AltRight
-        private const val rightAltMask = KeyEvent.META_ALT_RIGHT_ON or KeyEvent.META_ALT_ON
-
-        private val leftMeta = Key.MetaLeft
-        private const val leftMetaMask = KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON
-        private val rightMeta = Key.MetaRight
-        private const val rightMetaMask = KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON
-
-        private val leftShift = Key.ShiftLeft
-        private const val leftShiftMask = KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON
-        private val rightShift = Key.ShiftRight
-        private const val rightShiftMask = KeyEvent.META_SHIFT_RIGHT_ON or KeyEvent.META_SHIFT_ON
-
-        private val capsLock = Key.CapsLock
-        private const val capsLockMask = KeyEvent.META_CAPS_LOCK_ON
-        private val numLock = Key.NumLock
-        private const val numLockMask = KeyEvent.META_NUM_LOCK_ON
-        private val scrollLock = Key.ScrollLock
-        private const val scrollLockMask = KeyEvent.META_SCROLL_LOCK_ON
-
-        private const val allLockMasks = capsLockMask or numLockMask or scrollLockMask
-
-        private const val allMetaMasks = functionKeyMetaMask or leftCtrlMask or rightCtrlMask or
-            leftAltMask or rightAltMask or leftMetaMask or rightMetaMask or
-            leftShiftMask or rightShiftMask
-
-        private const val allMasks = allLockMasks or allMetaMasks
-    }
-
-    @Test
-    fun functionKey_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(functionKey, functionKeyMetaMask)
-
-    @Test
-    fun leftCtrl_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(leftCtrl, leftCtrlMask)
-
-    @Test
-    fun rightCtrl_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(rightCtrl, rightCtrlMask)
-
-    @Test
-    fun leftAlt_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(leftAlt, leftAltMask)
-
-    @Test
-    fun rightAlt_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(rightAlt, rightAltMask)
-
-    @Test
-    fun leftMeta_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(leftMeta, leftMetaMask)
-
-    @Test
-    fun rightMeta_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(rightMeta, rightMetaMask)
-
-    @Test
-    fun leftShift_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(leftShift, leftShiftMask)
-
-    @Test
-    fun rightShift_metaState_generatedCorrectly() =
-        verifyMetaKeyClickState(rightShift, rightShiftMask)
-
-    @Test
-    fun capsLock_metaState_generatedCorrectly() =
-        verifyLockKeyClickState(capsLock, capsLockMask)
-
-    @Test
-    fun numLock_metaState_generatedCorrectly() =
-        verifyLockKeyClickState(numLock, numLockMask)
-
-    @Test
-    fun scrollLock_metaState_generatedCorrectly() =
-        verifyLockKeyClickState(scrollLock, scrollLockMask)
-
-    @Test
-    fun lockKeys_metaState_combinedCorrectly_inMousePress() {
-        enqueueKeyPress(capsLock)
-        enqueueKeyPress(numLock)
-        enqueueKeyPress(scrollLock)
-        subject.verifyMousePosition(Offset.Zero)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.flush()
-
-        assertEquals(8, recorder.events.size)
-        recorder.events[5].verifyKeyEvent(keyUp, scrollLock.nativeKeyCode,
-            expectedMetaState = allLockMasks)
-        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allLockMasks)
-        recorder.events[7].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allLockMasks)
-    }
-
-    @Test
-    fun metaKeys_metaState_combinedCorrectly_inMousePress() {
-        subject.enqueueKeyDown(functionKey)
-        subject.enqueueKeyDown(leftCtrl)
-        subject.enqueueKeyDown(rightCtrl)
-        subject.enqueueKeyDown(leftAlt)
-        subject.enqueueKeyDown(rightAlt)
-        subject.enqueueKeyDown(leftMeta)
-        subject.enqueueKeyDown(rightMeta)
-        subject.enqueueKeyDown(leftShift)
-        subject.enqueueKeyDown(rightShift)
-        subject.verifyMousePosition(Offset.Zero)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.flush()
-
-        assertEquals(11, recorder.events.size)
-        recorder.events[8].verifyKeyEvent(keyDown, rightShift.nativeKeyCode,
-            expectedMetaState = allMetaMasks)
-        recorder.events[9].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMetaMasks)
-        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMetaMasks)
-    }
-
-    @Test
-    fun metaAndLockKeys_metaState_combinedCorrectly_inMousePress() {
-        enqueueKeyPress(capsLock) // key up + down = 2
-        enqueueKeyPress(numLock) // + key up + down = 4
-        enqueueKeyPress(scrollLock) // + key up + down = 6
-        subject.enqueueKeyDown(functionKey) // + key down = 7
-        subject.enqueueKeyDown(leftCtrl) // + key down = 8
-        subject.enqueueKeyDown(rightCtrl) // + key down = 9
-        subject.enqueueKeyDown(leftAlt) // + key down = 10
-        subject.enqueueKeyDown(rightAlt) // + key down = 11
-        subject.enqueueKeyDown(leftMeta) // + key down = 12
-        subject.enqueueKeyDown(rightMeta) // + key down = 13
-        subject.enqueueKeyDown(leftShift) // + key down = 14
-        subject.enqueueKeyDown(rightShift) // + key down = 15
-        subject.verifyMousePosition(Offset.Zero)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId) // + mouse down + press = 17
-        subject.flush()
-
-        assertEquals(17, recorder.events.size)
-        recorder.events[14].verifyKeyEvent(keyDown, rightShift.nativeKeyCode,
-            expectedMetaState = allMasks)
-        recorder.events[15].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMasks)
-        recorder.events[16].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = allMasks)
-    }
-
-    @Test
-    fun scroll_vertically() {
-        scrollTest(ScrollWheel.Vertical) { Pair(MotionEvent.AXIS_VSCROLL, -it) }
-    }
-
-    @Test
-    fun scroll_horizontally() {
-        scrollTest(ScrollWheel.Horizontal) { Pair(MotionEvent.AXIS_HSCROLL, it) }
-    }
-
-    private fun scrollTest(scrollWheel: ScrollWheel, scrollAxis: (Float) -> Pair<Int, Float>) {
-
-        var expectedEvents = 0
-        enqueueKeyPress(capsLock) // key up + down = 2
-        enqueueKeyPress(numLock) // + key up + down = 4
-        enqueueKeyPress(scrollLock) // + key up + down = 6
-        subject.enqueueKeyDown(functionKey) // + key down = 7
-        subject.enqueueKeyDown(leftCtrl) // + key down = 8
-        subject.enqueueKeyDown(rightCtrl) // + key down = 9
-        subject.enqueueKeyDown(leftAlt) // + key down = 10
-        subject.enqueueKeyDown(rightAlt) // + key down = 11
-        subject.enqueueKeyDown(leftMeta) // + key down = 12
-        subject.enqueueKeyDown(rightMeta) // + key down = 13
-        subject.enqueueKeyDown(leftShift) // + key down = 14
-        subject.enqueueKeyDown(rightShift) // + key down = 15
-        expectedEvents += 15
-        subject.verifyMousePosition(Offset.Zero)
-        subject.enqueueMouseMove(position1)
-        subject.verifyMousePosition(position1)
-        expectedEvents += 2 // enter + hover
-        subject.advanceEventTime()
-        subject.enqueueMouseScroll(1f, scrollWheel)
-        expectedEvents += 2 // hover + scroll
-        subject.advanceEventTime()
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        expectedEvents += 3 // exit + down + press
-        subject.advanceEventTime()
-        subject.enqueueMouseScroll(2f, scrollWheel)
-        expectedEvents += 2 // move + scroll
-        subject.advanceEventTime()
-        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
-        expectedEvents += 4 // release + up + enter + hover
-        subject.advanceEventTime()
-        subject.enqueueMouseScroll(3f, scrollWheel)
-        expectedEvents += 2 // hover + scroll
-        subject.flush()
-
-        recorder.assertHasValidEventTimes()
-        Truth.assertThat(recorder.events).hasSize(expectedEvents)
-        val events = recorder.events.toMutableList()
-
-        // Remove key presses - they aren't being tested here.
-        events.removeFirst(15)
-
-        // enter + hover
-        var t = 0L
-        var buttonState = 0
-        events.removeFirst(2).let { (enterEvent, hoverEvent) ->
-            enterEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_ENTER, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            hoverEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_MOVE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-        }
-
-        // hover + scroll
-        t += InputDispatcher.eventPeriodMillis
-        events.removeFirst(2).let { (hoverEvent, scrollEvent) ->
-            hoverEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_MOVE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            scrollEvent.verifyMouseEvent(
-                MotionEvent.ACTION_SCROLL, t,
-                position1, buttonState, scrollAxis(1f), expectedMetaState = allMasks)
-        }
-
-        // exit + down + press
-        t = 0L // down resets downTime
-        buttonState = MotionEvent.BUTTON_PRIMARY
-        events.removeFirst(3).let { (exitEvent, downEvent, pressEvent) ->
-            exitEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_EXIT, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            downEvent.verifyMouseEvent(
-                MotionEvent.ACTION_DOWN, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            pressEvent.verifyMouseEvent(
-                MotionEvent.ACTION_BUTTON_PRESS, t,
-                position1, buttonState, expectedMetaState = allMasks)
-        }
-
-        // move + scroll
-        t += InputDispatcher.eventPeriodMillis
-        events.removeFirst(2).let { (moveEvent, scrollEvent) ->
-            moveEvent.verifyMouseEvent(
-                MotionEvent.ACTION_MOVE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            scrollEvent.verifyMouseEvent(
-                MotionEvent.ACTION_SCROLL, t,
-                position1, buttonState, scrollAxis(2f), expectedMetaState = allMasks)
-        }
-
-        // release + up + enter + hover
-        t += InputDispatcher.eventPeriodMillis
-        buttonState = 0
-        events.removeFirst(4).let { (releaseEvent, upEvent, enterEvent, hoverEvent) ->
-            releaseEvent.verifyMouseEvent(
-                MotionEvent.ACTION_BUTTON_RELEASE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            upEvent.verifyMouseEvent(
-                MotionEvent.ACTION_UP, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            enterEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_ENTER, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            hoverEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_MOVE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-        }
-
-        // hover + scroll
-        t += InputDispatcher.eventPeriodMillis
-        events.removeFirst(2).let { (hoverEvent, scrollEvent) ->
-            hoverEvent.verifyMouseEvent(
-                MotionEvent.ACTION_HOVER_MOVE, t,
-                position1, buttonState, expectedMetaState = allMasks)
-            scrollEvent.verifyMouseEvent(
-                MotionEvent.ACTION_SCROLL, t,
-                position1, buttonState, scrollAxis(3f), expectedMetaState = allMasks)
-        }
-    }
-
-    private fun AndroidInputDispatcher.verifyMousePosition(expectedPosition: Offset) {
-        Truth.assertWithMessage("currentMousePosition")
-            .that(currentMousePosition).isEqualTo(expectedPosition)
-    }
-
-    private fun <E> MutableList<E>.removeFirst(n: Int): List<E> {
-        return mutableListOf<E>().also { result ->
-            repeat(n) { result.add(removeFirst()) }
-        }
-    }
-
-    private fun enqueueKeyPress(key: Key) {
-        subject.enqueueKeyDown(key)
-        subject.enqueueKeyUp(key)
-    }
-
-    private fun verifyMetaKeyClickState(key: Key, expectedMetaState: Int = 0) {
-        subject.enqueueKeyDown(key)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
-        subject.enqueueKeyUp(key)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.flush()
-
-        recorder.assertHasValidEventTimes()
-        Truth.assertThat(recorder.events).hasSize(11)
-
-        // Key Down
-        recorder.events[0].verifyKeyEvent(keyDown, key.nativeKeyCode,
-            expectedMetaState = expectedMetaState)
-
-        // Mouse Press
-        recorder.events[1].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
-        recorder.events[2].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
-
-        // Mouse Release
-        recorder.events[3].verifyMouseEvent(MotionEvent.ACTION_BUTTON_RELEASE, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[4].verifyMouseEvent(MotionEvent.ACTION_UP, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[5].verifyMouseEvent(MotionEvent.ACTION_HOVER_ENTER, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_HOVER_MOVE, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-
-        // Key Release
-        recorder.events[7].verifyKeyEvent(keyUp, key.nativeKeyCode)
-
-        // Mouse Press
-        recorder.events[8].verifyMouseEvent(MotionEvent.ACTION_HOVER_EXIT, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-        recorder.events[9].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-    }
-
-    private fun verifyLockKeyClickState(key: Key, expectedMetaState: Int = 0) {
-        enqueueKeyPress(key)
-        subject.verifyMousePosition(Offset.Zero)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.enqueueMouseRelease(MouseButton.Primary.buttonId)
-        enqueueKeyPress(key)
-        subject.enqueueMousePress(MouseButton.Primary.buttonId)
-        subject.flush()
-
-        recorder.assertHasValidEventTimes()
-        Truth.assertThat(recorder.events).hasSize(13)
-
-        // Key Toggle On
-        recorder.events[0].verifyKeyEvent(keyDown, key.nativeKeyCode,
-            expectedMetaState = expectedMetaState)
-        recorder.events[1].verifyKeyEvent(keyUp, key.nativeKeyCode,
-            expectedMetaState = expectedMetaState)
-
-        // Mouse Press
-        recorder.events[2].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
-        recorder.events[3].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY, expectedMetaState = expectedMetaState)
-
-        // Mouse Release
-        recorder.events[4].verifyMouseEvent(MotionEvent.ACTION_BUTTON_RELEASE, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[5].verifyMouseEvent(MotionEvent.ACTION_UP, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[6].verifyMouseEvent(MotionEvent.ACTION_HOVER_ENTER, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-        recorder.events[7].verifyMouseEvent(MotionEvent.ACTION_HOVER_MOVE, 0L,
-            Offset.Zero, 0, expectedMetaState = expectedMetaState)
-
-        // Key Toggle Off
-        recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode)
-        recorder.events[9].verifyKeyEvent(keyUp, key.nativeKeyCode)
-
-        // Mouse Press
-        recorder.events[10].verifyMouseEvent(MotionEvent.ACTION_HOVER_EXIT, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-        recorder.events[11].verifyMouseEvent(MotionEvent.ACTION_DOWN, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-        recorder.events[12].verifyMouseEvent(MotionEvent.ACTION_BUTTON_PRESS, 0L,
-            Offset.Zero, MotionEvent.BUTTON_PRIMARY)
-    }
-}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
deleted file mode 100644
index ffa8528..0000000
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2023 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.compose.ui.test.junit4
-
-import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
-import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
-import androidx.compose.ui.platform.InfiniteAnimationPolicy
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.runBlocking
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class InfiniteAnimationPolicyTest {
-
-    @Test
-    fun withInfiniteAnimationFrameNanos_policyIsApplied() {
-        withInfiniteAnimationFrame_policyIsApplied {
-            withInfiniteAnimationFrameNanos {}
-        }
-    }
-
-    @Test
-    fun withInfiniteAnimationFrameMillis_policyIsApplied() {
-        withInfiniteAnimationFrame_policyIsApplied {
-            withInfiniteAnimationFrameMillis {}
-        }
-    }
-
-    private fun <R> withInfiniteAnimationFrame_policyIsApplied(block: suspend () -> R) {
-        var applied = false
-        val policy = object : InfiniteAnimationPolicy {
-            override suspend fun <R> onInfiniteOperation(block: suspend () -> R): R {
-                applied = true
-                // We don't need to run the `block()` in our test, but we do need a return value
-                // of R. Throw a CancellationException to cancel the coroutine instead.
-                throw CancellationException()
-
-                // The reason why we can't run block() here, is because block() calls through to
-                // `DefaultMonotonicFrameClock.withFrameNanos`, which in host side tests
-                // dispatches on the main thread. But we're already blocking the main thread, so
-                // that would deadlock.
-            }
-        }
-
-        val caught = runCatching {
-            runBlocking(policy) {
-                block()
-            }
-        }
-
-        assertThat(applied).isTrue()
-        assertThat(caught.exceptionOrNull()).isInstanceOf(CancellationException::class.java)
-    }
-}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
deleted file mode 100644
index 6fbc231..0000000
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
+++ /dev/null
@@ -1,739 +0,0 @@
-/*
- * Copyright 2023 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.compose.ui.test.junit4
-
-import android.os.Handler
-import android.os.Looper
-import android.view.MotionEvent
-import android.view.View
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.LocalOverscrollConfiguration
-import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ScrollScope
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Button
-import androidx.compose.material.DropdownMenuItem
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.withFrameNanos
-import androidx.compose.testutils.WithTouchSlop
-import androidx.compose.testutils.expectError
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.findRootCoordinates
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextInputSelection
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.runComposeUiTest
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Popup
-import androidx.test.core.view.MotionEventBuilder
-import androidx.test.espresso.AppNotIdleException
-import androidx.test.espresso.IdlingPolicies
-import androidx.test.espresso.IdlingPolicy
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import kotlin.math.roundToInt
-import org.junit.After
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-
-@RunWith(AndroidJUnit4::class)
-@Config(minSdk = 21)
-@OptIn(ExperimentalTestApi::class)
-class RobolectricComposeTest {
-    private var masterTimeout: IdlingPolicy? = null
-
-    @Before
-    fun setup() {
-        masterTimeout = IdlingPolicies.getMasterIdlingPolicy()
-    }
-
-    @After
-    fun tearDown() {
-        masterTimeout?.let {
-            IdlingPolicies.setMasterPolicyTimeout(it.idleTimeout, it.idleTimeoutUnit)
-        }
-    }
-
-    @Composable
-    private fun ClickCounter(
-        clicks: MutableState<Int> = remember { mutableStateOf(0) }
-    ) {
-        Column {
-            Button(onClick = { clicks.value++ }) {
-                Text("Click me")
-            }
-            Text("Click count: ${clicks.value}")
-        }
-    }
-
-    /**
-     * Check that basic scenarios work: a composition that is recomposed due to a state change.
-     */
-    @Test
-    fun testStateChange() = runComposeUiTest {
-        val clicks = mutableStateOf(0)
-        setContent { ClickCounter(clicks) }
-        onNodeWithText("Click me").assertExists()
-
-        clicks.value++
-        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
-
-        clicks.value++
-        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 2")
-    }
-
-    /**
-     * Check that basic scenarios with input work: a composition that receives touch input and
-     * changes state as a result of that, triggering recomposition.
-     */
-    @Test
-    fun testInputInjection() = runComposeUiTest {
-        setContent { ClickCounter() }
-        onNodeWithText("Click me").assertExists()
-
-        onNodeWithText("Click me").performClick()
-        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
-
-        onNodeWithText("Click me").performClick()
-        onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 2")
-    }
-
-    /**
-     * Check that animation scenarios work: a composition with an animation in its initial state
-     * is idle, stays non-idle while the animation animates to a new target and is idle again
-     * after that.
-     */
-    @Test
-    fun testAnimation() = runComposeUiTest {
-        var target by mutableStateOf(0f)
-        setContent {
-            val offset = animateFloatAsState(target)
-            Box(Modifier.fillMaxSize()) {
-                Box(
-                    Modifier
-                        .size(10.dp)
-                        .offset(x = offset.value.dp)
-                        .testTag("box")
-                )
-            }
-        }
-        onNodeWithTag("box").assertLeftPositionInRootIsEqualTo(0.dp)
-        target = 100f
-        onNodeWithTag("box").assertLeftPositionInRootIsEqualTo(100.dp)
-    }
-
-    /**
-     * Check that we catch a potential infinite composition loop caused by a measure lambda that
-     * triggers itself.
-     */
-    @Test(timeout = 10000)
-    fun testTimeout() = runComposeUiTest {
-        IdlingPolicies.setMasterPolicyTimeout(2, TimeUnit.SECONDS)
-        expectError<AppNotIdleException>(
-            expectedMessage = "Compose did not get idle after [0-9]* attempts in 2 SECONDS\\..*"
-        ) {
-            setContent {
-                var x by remember { mutableStateOf(0) }
-                Box(Modifier.requiredSize(100.dp)) {
-                    Layout({ Box(Modifier.size(10.dp)) }) { measurables, constraints ->
-                        val placeables = measurables.map { it.measure(constraints) }
-
-                        // read x, so we need to relayout when x changes
-                        val offset = if (x >= 0) 0 else -1
-                        val width = offset + placeables.maxOf { it.width }
-                        val height = offset + placeables.maxOf { it.height }
-
-                        // woops, we're always changing x during layout!
-                        x = if (x == 0) 1 else 0
-
-                        layout(width, height) {
-                            placeables.forEach { it.place(0, 0) }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Check that scrolling and controlling the clock works: a scrollable receives a swipe while
-     * the clock is paused, when the clock is resumed it performs the fling.
-     */
-    @OptIn(ExperimentalFoundationApi::class)
-    @Test
-    fun testControlledScrolling() = runComposeUiTest {
-        // Define constants used in the test
-        val n = 100
-        val touchSlop = 16f
-        val scrollState = ScrollState(0)
-        val flingBehavior = SimpleFlingBehavior(deltas = 20 downTo 1)
-
-        // Set content: a list where the fling is always the same, regardless of the swipe
-        setContent {
-            WithTouchSlop(touchSlop = touchSlop) {
-                // turn off visual overscroll for calculation correctness
-                CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
-                    Box(Modifier.fillMaxSize()) {
-                        Column(
-                            Modifier
-                                .requiredSize(200.dp)
-                                .verticalScroll(
-                                    scrollState,
-                                    flingBehavior = flingBehavior
-                                )
-                                .testTag("list")
-                        ) {
-                            repeat(n) {
-                                Spacer(
-                                    Modifier
-                                        .fillMaxWidth()
-                                        .height(30.dp)
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Stop auto advancing and perform a swipe. The list will "freeze" in the position where
-        // it was at the end of the swipe
-        mainClock.autoAdvance = false
-        onNodeWithTag("list").performTouchInput {
-            down(bottomCenter)
-            repeat(10) {
-                moveTo(bottomCenter - percentOffset(y = (it + 1) / 10f))
-            }
-            up()
-        }
-        waitForIdle()
-
-        // Check that we're in that frozen position
-        val expectedViewPortSize = with(density) { 200.dp.toPx() }
-        val expectedSwipeDistance = (expectedViewPortSize - touchSlop).roundToInt()
-        assertThat(scrollState.value).isEqualTo(expectedSwipeDistance)
-
-        // "Unfreeze" the list and let the fling run. The list will stop at
-        // `flingBehavior.totalDistance` pixels further than where it was frozen.
-        mainClock.autoAdvance = true
-        waitForIdle()
-        val expectedFlingDistance = flingBehavior.totalDistance
-        assertThat(scrollState.value).isEqualTo(expectedSwipeDistance + expectedFlingDistance)
-    }
-
-    // Regression test for b/227120770
-    @Test
-    fun testTextFieldInteraction() = runComposeUiTest {
-        val text = "a"
-        var updatedText = ""
-        setContent {
-            TextField(value = text, onValueChange = { updatedText = it })
-        }
-        onNodeWithText(text).assertIsDisplayed()
-        // If selection isn't set on initial state, it will be 0.
-        onNodeWithText(text).performTextInputSelection(TextRange(1))
-        onNodeWithText(text).performTextInput("b")
-        runOnIdle {
-            assertThat(updatedText).isEqualTo("ab")
-        }
-    }
-
-    /**
-     * A simple [FlingBehavior] that scrolls one [delta][deltas] every frame regardless of velocity.
-     */
-    private class SimpleFlingBehavior(private val deltas: IntProgression) : FlingBehavior {
-        val totalDistance = deltas.sum()
-
-        override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
-            for (delta in deltas) {
-                withFrameNanos {
-                    scrollBy(delta.toFloat())
-                }
-            }
-            return 0f
-        }
-    }
-
-    @Test
-    fun testComposeMainQueueTrampolining() = runComposeUiTest {
-        val trampoliningIterations = 10
-        var phase by mutableStateOf(0)
-        var done = false
-        val handler = Handler(Looper.getMainLooper())
-
-        setContent {
-            // Alternate scheduling work to the main queue and scheduling work for Compose.
-            // waitForIdle shouldn't return until both Compose and the main queue have nothing to
-            // do.
-            if (phase in 1 until trampoliningIterations) {
-                DisposableEffect(phase) {
-                    handler.post { phase++ }
-                    onDispose {}
-                }
-            } else if (phase == trampoliningIterations) {
-                DisposableEffect(Unit) {
-                    done = true
-                    onDispose {}
-                }
-            }
-        }
-
-        runOnIdle {
-            assertThat(phase).isEqualTo(0)
-            assertThat(done).isFalse()
-        }
-
-        // Trigger the trampolining.
-        phase = 1
-
-        // If Robolectric and Compose don't coordinate idleness correctly, waitForIdle will return
-        // before finishing the chain of trampolined work, and we won't be in the expected terminal
-        // state.
-        runOnIdle {
-            assertThat(phase).isEqualTo(trampoliningIterations)
-            assertThat(done).isTrue()
-        }
-    }
-
-    @Test
-    fun testWaitForPopupWindow() = runComposeUiTest {
-        var expanded by mutableStateOf(false)
-
-        setContent {
-            Box(Modifier.requiredSize(20.dp)) {
-                if (expanded) {
-                    Popup {
-                        DropdownMenuItem(modifier = Modifier.testTag("MenuContent"), onClick = {}) {
-                            Text("Option 1")
-                        }
-                    }
-                }
-            }
-        }
-
-        onNodeWithTag("MenuContent").assertDoesNotExist()
-
-        expanded = true
-
-        onNodeWithTag("MenuContent").assertIsDisplayed()
-    }
-
-    /*
-     * Two tests properly demonstrating how to properly advance the clock (advanceTimeBy()) while
-     * testing pointer input events using performTouchInput() with espresso and/or Robolectric).
-     */
-    @Test
-    fun areTwoTapsFired_dispatchTwoDelayedTapsWithPerformTouchInput_assertTrue() =
-        runComposeUiTest {
-            var composableTouchCount = 0
-            var composableTapCount = 0
-            var composableDoubleTapCount = 0
-            var composableLongTapCount = 0
-
-            val setupLatch = CountDownLatch(1)
-            val tapLatch = CountDownLatch(2)
-
-            setContent {
-                Box(
-                    modifier = Modifier
-                        .testTag("mainBox")
-                        .fillMaxSize()
-                        .pointerInput(Unit) {
-                            detectTapGestures(
-                                onPress = {
-                                    ++composableTouchCount
-                                },
-                                onTap = {
-                                    tapLatch.countDown()
-                                    ++composableTapCount
-                                },
-                                onDoubleTap = {
-                                    ++composableDoubleTapCount
-                                },
-                                onLongPress = {
-                                    ++composableLongTapCount
-                                }
-                            )
-                        }
-                        .onGloballyPositioned {
-                            setupLatch.countDown()
-                        }
-                ) {
-                    Box(
-                        modifier = Modifier
-                            .fillMaxSize()
-                    )
-                }
-            }
-            assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
-
-            onNodeWithTag("mainBox").assertIsDisplayed()
-
-            runOnIdle {
-                onNodeWithTag("mainBox").performTouchInput {
-                    down(center)
-                    up()
-                }
-            }
-
-            // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward
-            // when using detectTapGestures {} as parts of it rely on changes in the clock
-            // (double tap, etc.).
-            mainClock.advanceTimeBy(400)
-
-            runOnIdle {
-                onNodeWithTag("mainBox").performTouchInput {
-                    down(center)
-                    up()
-                }
-            }
-
-            // Delay again to trigger second single tap (times out double tap detector)
-            mainClock.advanceTimeBy(400)
-
-            assertTrue(tapLatch.await(10, TimeUnit.SECONDS))
-
-            assertThat(composableTouchCount).isEqualTo(2)
-            assertThat(composableTapCount).isEqualTo(2)
-            assertThat(composableDoubleTapCount).isEqualTo(0)
-            assertThat(composableLongTapCount).isEqualTo(0)
-        }
-
-    @Test
-    fun isDoubleTapFired_dispatchTwoTapsWithPerformTouchInput_assertTrue() = runComposeUiTest {
-        var composableTouchCount = 0
-        var composableTapCount = 0
-        var composableDoubleTapCount = 0
-        var composableLongTapCount = 0
-
-        val setupLatch = CountDownLatch(1)
-        val doubleTapLatch = CountDownLatch(1)
-
-        setContent {
-            Box(
-                modifier = Modifier
-                    .testTag("mainBox")
-                    .fillMaxSize()
-                    .pointerInput(Unit) {
-                        detectTapGestures(
-                            onPress = {
-                                ++composableTouchCount
-                            },
-                            onTap = {
-                                ++composableTapCount
-                            },
-                            onDoubleTap = {
-                                doubleTapLatch.countDown()
-                                ++composableDoubleTapCount
-                            },
-                            onLongPress = {
-                                ++composableLongTapCount
-                            }
-                        )
-                    }
-                    .onGloballyPositioned {
-                        setupLatch.countDown()
-                    }
-            ) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxSize()
-                )
-            }
-        }
-        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
-
-        onNodeWithTag("mainBox").assertIsDisplayed()
-
-        runOnIdle {
-            onNodeWithTag("mainBox").performTouchInput {
-                down(center)
-                up()
-            }
-        }
-
-        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
-        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
-        mainClock.advanceTimeBy(100)
-
-        runOnIdle {
-            onNodeWithTag("mainBox").performTouchInput { down(center) }
-            onNodeWithTag("mainBox").performTouchInput { up() }
-        }
-
-        // Delay but just enough to stay inside double tap timeframe
-        mainClock.advanceTimeBy(100)
-
-        assertTrue(doubleTapLatch.await(10, TimeUnit.SECONDS))
-
-        assertThat(composableTouchCount).isEqualTo(2)
-        assertThat(composableTapCount).isEqualTo(0)
-        assertThat(composableDoubleTapCount).isEqualTo(1)
-        assertThat(composableLongTapCount).isEqualTo(0)
-    }
-
-    /*
-     * Two tests properly demonstrating how to properly advance the clock (advanceTimeBy()) while
-     * testing pointer input events using manually created MotionEvents with espresso and/or
-     * Robolectric).
-     */
-    @Test
-    fun isTapFired_dispatchTapWithMotionEvents_assertTrue() = runComposeUiTest {
-        var composableTouchCount = 0
-        var composableTapCount = 0
-        var composableDoubleTapCount = 0
-        var composableLongTapCount = 0
-
-        val setupLatch = CountDownLatch(1)
-        val tapLatch = CountDownLatch(1)
-
-        var bottomBoxInnerCoordinates: LayoutCoordinates? = null
-        var topLevelContainerView: View? = null
-
-        setContent {
-            topLevelContainerView = LocalView.current
-
-            Box(
-                modifier = Modifier
-                    .testTag("mainBox")
-                    .fillMaxSize()
-                    .pointerInput(Unit) {
-                        detectTapGestures(
-                            onPress = {
-                                ++composableTouchCount
-                            },
-                            onTap = {
-                                tapLatch.countDown()
-                                ++composableTapCount
-                            },
-                            onDoubleTap = {
-                                ++composableDoubleTapCount
-                            },
-                            onLongPress = {
-                                ++composableLongTapCount
-                            }
-                        )
-                    }
-                    .onGloballyPositioned {
-                        setupLatch.countDown()
-                        bottomBoxInnerCoordinates = it
-                    }
-            ) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxSize()
-                )
-            }
-        }
-        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
-
-        onNodeWithTag("mainBox").assertIsDisplayed()
-
-        val root = bottomBoxInnerCoordinates!!.findRootCoordinates()
-        val topBoxOffset = root.localPositionOf(bottomBoxInnerCoordinates!!, Offset.Zero)
-        val topBoxFingerPointerPropertiesId = 0
-        val topBoxPointerProperties =
-            MotionEvent.PointerProperties().also {
-                it.id = topBoxFingerPointerPropertiesId
-                it.toolType = MotionEvent.TOOL_TYPE_FINGER
-            }
-        val coords = MotionEvent.PointerCoords()
-        coords.x = topBoxOffset.x
-        coords.y = topBoxOffset.y
-
-        val motionEventDown = MotionEventBuilder.newBuilder()
-            .setEventTime(0)
-            .setAction(MotionEvent.ACTION_DOWN)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        val motionEventUp = MotionEventBuilder.newBuilder()
-            .setEventTime(100)
-            .setAction(MotionEvent.ACTION_UP)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        topLevelContainerView?.dispatchTouchEvent(motionEventDown)
-        topLevelContainerView?.dispatchTouchEvent(motionEventUp)
-
-        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
-        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
-        // Delay to trigger second single tap (times out double tap detector)
-        mainClock.advanceTimeBy(400)
-
-        assertTrue(tapLatch.await(10, TimeUnit.SECONDS))
-
-        assertThat(composableTouchCount).isEqualTo(1)
-        assertThat(composableTapCount).isEqualTo(1)
-        assertThat(composableDoubleTapCount).isEqualTo(0)
-        assertThat(composableLongTapCount).isEqualTo(0)
-    }
-
-    @Test
-    fun isDoubleTapFired_dispatchTwoTapsWithMotionEvents_assertTrue() = runComposeUiTest {
-        var composableTouchCount = 0
-        var composableTapCount = 0
-        var composableDoubleTapCount = 0
-        var composableLongTapCount = 0
-
-        val setupLatch = CountDownLatch(1)
-        val doubleTapLatch = CountDownLatch(1)
-
-        var bottomBoxInnerCoordinates: LayoutCoordinates? = null
-        var topLevelContainerView: View? = null
-
-        setContent {
-            topLevelContainerView = LocalView.current
-
-            Box(
-                modifier = Modifier
-                    .testTag("mainBox")
-                    .fillMaxSize()
-                    .pointerInput(Unit) {
-                        detectTapGestures(
-                            onPress = {
-                                ++composableTouchCount
-                            },
-                            onTap = {
-                                ++composableTapCount
-                            },
-                            onDoubleTap = {
-                                doubleTapLatch.countDown()
-                                ++composableDoubleTapCount
-                            },
-                            onLongPress = {
-                                ++composableLongTapCount
-                            }
-                        )
-                    }
-                    .onGloballyPositioned {
-                        setupLatch.countDown()
-                        bottomBoxInnerCoordinates = it
-                    }
-            ) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxSize()
-                )
-            }
-        }
-        assertTrue(setupLatch.await(10, TimeUnit.SECONDS))
-
-        val root = bottomBoxInnerCoordinates!!.findRootCoordinates()
-        val topBoxOffset = root.localPositionOf(bottomBoxInnerCoordinates!!, Offset.Zero)
-        val topBoxFingerPointerPropertiesId = 0
-        val topBoxPointerProperties =
-            MotionEvent.PointerProperties().also {
-                it.id = topBoxFingerPointerPropertiesId
-                it.toolType = MotionEvent.TOOL_TYPE_FINGER
-            }
-        val coords = MotionEvent.PointerCoords()
-        coords.x = topBoxOffset.x
-        coords.y = topBoxOffset.y
-
-        val motionEventDown1 = MotionEventBuilder.newBuilder()
-            .setEventTime(0)
-            .setAction(MotionEvent.ACTION_DOWN)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        val motionEventUp1 = MotionEventBuilder.newBuilder()
-            .setEventTime(50)
-            .setAction(MotionEvent.ACTION_UP)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        topLevelContainerView?.dispatchTouchEvent(motionEventDown1)
-        topLevelContainerView?.dispatchTouchEvent(motionEventUp1)
-
-        // In testing (Espresso, Robolectric, etc.), it's important to move the clock forward when
-        // using detectTapGestures {} as parts of it rely on changes in the clock (double tap, etc.)
-        mainClock.advanceTimeBy(100)
-
-        val motionEventDown2 = MotionEventBuilder.newBuilder()
-            .setEventTime(100)
-            .setAction(MotionEvent.ACTION_DOWN)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        val motionEventUp2 = MotionEventBuilder.newBuilder()
-            .setEventTime(150)
-            .setAction(MotionEvent.ACTION_UP)
-            .setActionIndex(0)
-            .setPointer(topBoxPointerProperties, coords)
-            .build()
-
-        topLevelContainerView?.dispatchTouchEvent(motionEventDown2)
-        topLevelContainerView?.dispatchTouchEvent(motionEventUp2)
-
-        // Delay but just enough to stay inside double tap timeframe
-        mainClock.advanceTimeBy(100)
-
-        assertTrue(doubleTapLatch.await(2, TimeUnit.SECONDS))
-
-        assertThat(composableTouchCount).isEqualTo(2)
-        assertThat(composableTapCount).isEqualTo(0)
-        assertThat(composableDoubleTapCount).isEqualTo(1)
-        assertThat(composableLongTapCount).isEqualTo(0)
-    }
-}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
deleted file mode 100644
index 0ef6250..0000000
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2023 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.compose.ui.test.junit4
-
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import androidx.activity.ComponentActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsNotDisplayed
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.runAndroidComposeUiTest
-import androidx.compose.ui.test.runComposeUiTest
-import androidx.compose.ui.unit.dp
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.ParameterizedRobolectricTestRunner
-import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
-import org.robolectric.annotation.Config
-
-@RunWith(ParameterizedRobolectricTestRunner::class)
-@Config(minSdk = 21)
-@OptIn(ExperimentalTestApi::class)
-class ViewVisibilityRobolectricTest(private val visibility: Int) {
-    companion object {
-        @JvmStatic
-        @Parameters(name = "visibility={0}")
-        fun params() = listOf(
-            View.VISIBLE,
-            View.INVISIBLE,
-            View.GONE
-        )
-    }
-
-    private var offset by mutableStateOf(0)
-
-    private fun toggleState() {
-        offset = 10
-    }
-
-    @Test
-    fun noTimeout_hostView_visibility() {
-        runComposeUiTest {
-            setContent {
-                val hostView = LocalView.current
-                SideEffect {
-                    hostView.visibility = visibility
-                }
-                TestContent()
-            }
-
-            val expectDisplayed = visibility == View.VISIBLE
-            checkUi(expectDisplayed)
-            toggleState()
-            checkUi(expectDisplayed)
-        }
-    }
-
-    @Test
-    fun noTimeout_composeView_visibility() {
-        runAndroidComposeUiTest<ComponentActivity> {
-            runOnUiThread {
-                val activity = activity!!
-                val composeView = ComposeView(activity)
-                composeView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-                composeView.visibility = visibility
-                composeView.setContent {
-                    TestContent()
-                }
-                activity.setContentView(composeView)
-            }
-
-            val expectDisplayed = visibility == View.VISIBLE
-            checkUi(expectDisplayed)
-            toggleState()
-            checkUi(expectDisplayed)
-        }
-    }
-
-    @Composable
-    private fun TestContent() {
-        // Read state in layout and not in measure, so a state change will trigger layout, but
-        // not measure. Because measure is not affected, the containing View doesn't need to be
-        // remeasured and Compose will do its measure/layout pass in the draw pass, meaning the
-        // View will be invalidated but no layout will be requested.
-        Layout({
-            Box(Modifier.size(10.dp))
-        }, Modifier.fillMaxSize().testTag("box")) { measurables, constraints ->
-            val placeable = measurables.first().measure(constraints)
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                placeable.place(offset, 0)
-            }
-        }
-    }
-
-    private fun SemanticsNodeInteractionsProvider.checkUi(expectDisplayed: Boolean) {
-        // It should always exist
-        onNodeWithTag("box").assertExists()
-        // But only be displayed in tests where visibility = View.VISIBLE
-        if (expectDisplayed) {
-            onNodeWithTag("box").assertIsDisplayed()
-        } else {
-            onNodeWithTag("box").assertIsNotDisplayed()
-        }
-    }
-}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/util/BitmapCapturingRetryLogicTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/util/BitmapCapturingRetryLogicTest.kt
deleted file mode 100644
index 3a6a1b3..0000000
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/util/BitmapCapturingRetryLogicTest.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.compose.ui.test.util
-
-import android.view.PixelCopy
-import androidx.compose.testutils.expectError
-import androidx.compose.ui.test.android.PixelCopyException
-import androidx.compose.ui.test.android.runWithRetryWhenNoData
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class BitmapCapturingRetryLogicTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun pixelCopyRequest_succeeded_noRetries() {
-        var attempt = 0
-
-        expectError<PixelCopyException>(false) {
-            runWithRetryWhenNoData {
-                try {
-                    // success
-                } finally {
-                    attempt++
-                }
-            }
-        }
-
-        assertThat(attempt).isEqualTo(1)
-    }
-
-    @Test
-    fun pixelCopyRequest_succeeded_afterRetry_whenNoData() {
-        var attempt = 0
-
-        expectError<PixelCopyException>(false) {
-            runWithRetryWhenNoData {
-                try {
-                    if (attempt == 0) {
-                        throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
-                    } else {
-                        // success
-                    }
-                } finally {
-                    attempt++
-                }
-            }
-        }
-    }
-
-    @Test
-    fun pixelCopyRequest_retry_whenNoData() {
-        var attempt = 0
-
-        expectError<PixelCopyException> {
-            runWithRetryWhenNoData {
-                try {
-                    throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
-                } finally {
-                    attempt++
-                }
-            }
-        }
-        assertThat(attempt).isEqualTo(3)
-    }
-
-    @Test
-    fun pixelCopyRequest_error_rethrow() {
-        expectError<PixelCopyException> {
-            runWithRetryWhenNoData {
-                throw PixelCopyException(PixelCopy.ERROR_UNKNOWN)
-            }
-        }
-    }
-
-    @Test
-    fun pixelCopyRequest_error_rethrow_afterRetry() {
-        var attempt = 0
-
-        expectError<PixelCopyException> {
-            runWithRetryWhenNoData {
-                try {
-                    if (attempt == 0) {
-                        throw PixelCopyException(PixelCopy.ERROR_SOURCE_NO_DATA)
-                    } else {
-                        throw PixelCopyException(PixelCopy.ERROR_UNKNOWN)
-                    }
-                } finally {
-                    attempt++
-                }
-            }
-        }
-        assertThat(attempt).isEqualTo(2)
-    }
-}
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GlobalAssertions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GlobalAssertions.kt
index 222a3e3..18ebb02 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GlobalAssertions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GlobalAssertions.kt
@@ -35,7 +35,7 @@
  */
 @ExperimentalTestApi
 fun addGlobalAssertion(name: String, assertion: (SemanticsNodeInteraction) -> Unit) {
-  GlobalAssertionsCollection.put(name, assertion)
+    GlobalAssertionsCollection.put(name, assertion)
 }
 
 /**
@@ -45,7 +45,7 @@
  */
 @ExperimentalTestApi
 fun removeGlobalAssertion(name: String) {
-  GlobalAssertionsCollection.remove(name)
+    GlobalAssertionsCollection.remove(name)
 }
 
 /**
@@ -56,8 +56,8 @@
  */
 @ExperimentalTestApi
 fun SemanticsNodeInteraction.invokeGlobalAssertions(): SemanticsNodeInteraction {
-  GlobalAssertionsCollection.invoke(this)
-  return this
+    GlobalAssertionsCollection.invoke(this)
+    return this
 }
 
 /**
@@ -69,37 +69,37 @@
 @ExperimentalTestApi
 fun SemanticsNodeInteractionCollection.invokeGlobalAssertions():
     SemanticsNodeInteractionCollection {
-  GlobalAssertionsCollection.invoke(this)
-  return this
+    GlobalAssertionsCollection.invoke(this)
+    return this
 }
 
 /** Assertions intended to be executed before test actions. */
 internal object GlobalAssertionsCollection {
-  const val TAG = "GlobalAssertions"
+    const val TAG = "GlobalAssertions"
 
-  /** Map of assertion names to their functions */
-  private val globalAssertions = mutableMapOf<String, (SemanticsNodeInteraction) -> Unit>()
+    /** Map of assertion names to their functions */
+    private val globalAssertions = mutableMapOf<String, (SemanticsNodeInteraction) -> Unit>()
 
-  /** Implementation of [addGlobalAssertion] */
-  internal fun put(name: String, assertion: (SemanticsNodeInteraction) -> Unit) {
-    globalAssertions[name] = assertion
-  }
-
-  /** Implementation of [removeGlobalAssertion] */
-  internal fun remove(name: String) {
-    globalAssertions.remove(name)
-  }
-
-  /** Executes every assertion on the given node. */
-  internal fun invoke(sni: SemanticsNodeInteraction) {
-    for (entry in globalAssertions.entries) {
-      printToLog(TAG, "Executing \"${entry.key}\"")
-      entry.value.invoke(sni)
+    /** Implementation of [addGlobalAssertion] */
+    internal fun put(name: String, assertion: (SemanticsNodeInteraction) -> Unit) {
+        globalAssertions[name] = assertion
     }
-  }
 
-  /** Executes every assertion on the first node of the given collection. */
-  internal fun invoke(snic: SemanticsNodeInteractionCollection) {
-    invoke(snic.onFirst())
-  }
+    /** Implementation of [removeGlobalAssertion] */
+    internal fun remove(name: String) {
+        globalAssertions.remove(name)
+    }
+
+    /** Executes every assertion on the given node. */
+    internal fun invoke(sni: SemanticsNodeInteraction) {
+        for (entry in globalAssertions.entries) {
+            printToLog(TAG, "Executing \"${entry.key}\"")
+            entry.value.invoke(sni)
+        }
+    }
+
+    /** Executes every assertion on the first node of the given collection. */
+    internal fun invoke(snic: SemanticsNodeInteractionCollection) {
+        invoke(snic.onFirst())
+    }
 }
diff --git a/compose/ui/ui-text-google-fonts/build.gradle b/compose/ui/ui-text-google-fonts/build.gradle
index ce24693..1c199e3 100644
--- a/compose/ui/ui-text-google-fonts/build.gradle
+++ b/compose/ui/ui-text-google-fonts/build.gradle
@@ -32,9 +32,7 @@
 }
 
 dependencies {
-
     implementation(libs.kotlinStdlib)
-
     implementation("androidx.compose.runtime:runtime:1.2.1")
     implementation(project(":compose:ui:ui-text"))
     implementation("androidx.core:core:1.8.0")
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 20df631..c01ca7d 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -1286,7 +1286,7 @@
     ctor public LocaleList(String languageTags);
     ctor public LocaleList(java.util.List<androidx.compose.ui.text.intl.Locale> localeList);
     method public operator boolean contains(androidx.compose.ui.text.intl.Locale element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends androidx.compose.ui.text.intl.Locale> elements);
     method public operator androidx.compose.ui.text.intl.Locale get(int i);
     method public java.util.List<androidx.compose.ui.text.intl.Locale> getLocaleList();
     method public int getSize();
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index bd6ae2a..00b4928 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -1286,7 +1286,7 @@
     ctor public LocaleList(String languageTags);
     ctor public LocaleList(java.util.List<androidx.compose.ui.text.intl.Locale> localeList);
     method public operator boolean contains(androidx.compose.ui.text.intl.Locale element);
-    method public boolean containsAll(java.util.Collection<E> elements);
+    method public boolean containsAll(java.util.Collection<? extends androidx.compose.ui.text.intl.Locale> elements);
     method public operator androidx.compose.ui.text.intl.Locale get(int i);
     method public java.util.List<androidx.compose.ui.text.intl.Locale> getLocaleList();
     method public int getSize();
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 0eeb331..6641935 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -47,8 +47,8 @@
                 api(project(":compose:ui:ui-unit"))
 
                 // when updating the runtime version please also update the runtime-saveable version
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:runtime:runtime-saveable"))
+                implementation("androidx.compose.runtime:runtime:1.6.0")
+                implementation("androidx.compose.runtime:runtime-saveable:1.6.0")
 
                 implementation(project(":compose:ui:ui-util"))
             }
@@ -63,8 +63,6 @@
             dependsOn(commonMain)
             dependencies {
                 api(libs.skikoCommon)
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:runtime:runtime-saveable"))
             }
         }
 
@@ -75,7 +73,6 @@
             }
         }
 
-
         androidMain {
             dependsOn(commonMain)
             dependsOn(jvmMain)
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index 1f20915..ad21576 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -41,7 +41,7 @@
             dependencies {
                 implementation(libs.kotlinStdlib)
 
-                api(project(":compose:runtime:runtime"))
+                api("androidx.compose.runtime:runtime:1.6.0")
                 api(project(":compose:ui:ui"))
             }
         }
@@ -62,7 +62,6 @@
         skikoMain {
             dependsOn(commonMain)
             dependencies {
-
             }
         }
 
diff --git a/compose/ui/ui-tooling-preview/build.gradle b/compose/ui/ui-tooling-preview/build.gradle
index 0b81b0c..e48247e 100644
--- a/compose/ui/ui-tooling-preview/build.gradle
+++ b/compose/ui/ui-tooling-preview/build.gradle
@@ -40,7 +40,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                api(project(":compose:runtime:runtime"))
+                api("androidx.compose.runtime:runtime:1.6.0")
             }
         }
 
@@ -58,7 +58,6 @@
         skikoMain {
             dependsOn(commonMain)
             dependencies {
-                api(project(":compose:runtime:runtime"))
             }
         }
 
@@ -73,7 +72,6 @@
             dependsOn(skikoMain)
             dependsOn(jvmMain)
             dependencies {
-
             }
         }
 
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 682722e..a8680ca 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -40,8 +40,8 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
+                api("androidx.compose.runtime:runtime:1.6.0")
                 api(project(":compose:ui:ui-tooling-preview"))
-                api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui"))
                 api(project(":compose:ui:ui-tooling-data"))
             }
@@ -49,7 +49,6 @@
 
         commonTest {
             dependencies {
-
             }
         }
 
diff --git a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
index 27494ed..5bf1b37 100644
--- a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
+++ b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
@@ -191,6 +191,8 @@
                     ...|LazyColumnPreview.kt:49
                     ...|LazyColumnPreview.kt:50
                     ....|LazyColumnPreview.kt:53
+                    ....|LazyColumnPreview.kt:53
+                    ....|LazyColumnPreview.kt:53
                     .....|LazyColumnPreview.kt:54
                     ....|LazyColumnPreview.kt:53
                     .....|LazyColumnPreview.kt:54
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index b5946c7..7338576 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -40,12 +40,11 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                api(project(":compose:ui:ui-geometry"))
-
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui-util"))
-                implementation("androidx.collection:collection:1.4.0")
                 api("androidx.annotation:annotation:1.1.0")
+                api(project(":compose:ui:ui-geometry"))
+                implementation("androidx.collection:collection:1.4.0")
+                implementation("androidx.compose.runtime:runtime:1.6.0")
+                implementation(project(":compose:ui:ui-util"))
             }
         }
 
@@ -75,7 +74,6 @@
         desktopMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation(project(":compose:runtime:runtime"))
             }
         }
 
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 75078d8..47724d2 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2043,6 +2043,27 @@
     property public static final androidx.compose.ui.layout.HorizontalAlignmentLine LastBaseline;
   }
 
+  public sealed interface ApproachIntrinsicMeasureScope extends androidx.compose.ui.layout.IntrinsicMeasureScope {
+    method public long getLookaheadConstraints();
+    method public long getLookaheadSize();
+    property public abstract long lookaheadConstraints;
+    property public abstract long lookaheadSize;
+  }
+
+  public interface ApproachLayoutModifierNode extends androidx.compose.ui.node.LayoutModifierNode {
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.layout.MeasureResult approachMeasure(androidx.compose.ui.layout.ApproachMeasureScope, androidx.compose.ui.layout.Measurable measurable, long constraints);
+    method public boolean isMeasurementApproachComplete(long lookaheadSize);
+    method public default boolean isPlacementApproachComplete(androidx.compose.ui.layout.Placeable.PlacementScope, androidx.compose.ui.layout.LayoutCoordinates lookaheadCoordinates);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int maxApproachIntrinsicHeight(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int width);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int maxApproachIntrinsicWidth(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int height);
+    method public default androidx.compose.ui.layout.MeasureResult measure(androidx.compose.ui.layout.MeasureScope, androidx.compose.ui.layout.Measurable measurable, long constraints);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int minApproachIntrinsicHeight(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int width);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int minApproachIntrinsicWidth(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int height);
+  }
+
+  @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface ApproachMeasureScope extends androidx.compose.ui.layout.ApproachIntrinsicMeasureScope androidx.compose.ui.layout.MeasureScope {
+  }
+
   public interface BeyondBoundsLayout {
     method public <T> T? layout(int direction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.BeyondBoundsLayout.BeyondBoundsScope,? extends T?> block);
   }
@@ -2122,9 +2143,7 @@
     ctor public HorizontalRuler();
   }
 
-  @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface IntermediateMeasureScope extends androidx.compose.ui.layout.LookaheadScope kotlinx.coroutines.CoroutineScope androidx.compose.ui.layout.MeasureScope {
-    method public long getLookaheadSize();
-    property public abstract long lookaheadSize;
+  @Deprecated @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public interface IntermediateMeasureScope extends androidx.compose.ui.layout.ApproachMeasureScope kotlinx.coroutines.CoroutineScope androidx.compose.ui.layout.LookaheadScope {
   }
 
   public interface IntrinsicMeasurable {
@@ -2240,7 +2259,8 @@
 
   public final class LookaheadScopeKt {
     method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static void LookaheadScope(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier approachLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,java.lang.Boolean> isMeasurementApproachComplete, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.Placeable.PlacementScope,? super androidx.compose.ui.layout.LayoutCoordinates,java.lang.Boolean> isPlacementApproachComplete, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.ApproachMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> approachMeasure);
+    method @Deprecated @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
   }
 
   public interface Measurable extends androidx.compose.ui.layout.IntrinsicMeasurable {
@@ -2512,9 +2532,11 @@
 
   public final class ModifierLocalModifierNodeKt {
     method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf();
-    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?>... keys);
+    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?> key1, androidx.compose.ui.modifier.ModifierLocal<?> key2, androidx.compose.ui.modifier.ModifierLocal<?>... keys);
+    method @Deprecated public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?>... keys);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<T> key);
-    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
+    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?> entry1, kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?> entry2, kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
+    method @Deprecated public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<T>,? extends T> entry);
   }
 
@@ -2556,6 +2578,7 @@
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static androidx.compose.ui.unit.Density requireDensity(androidx.compose.ui.node.DelegatableNode);
+    method public static androidx.compose.ui.layout.LayoutCoordinates requireLayoutCoordinates(androidx.compose.ui.node.DelegatableNode);
     method public static androidx.compose.ui.unit.LayoutDirection requireLayoutDirection(androidx.compose.ui.node.DelegatableNode);
   }
 
@@ -2902,7 +2925,7 @@
   public final class InspectableValueKt {
     method public static inline kotlin.jvm.functions.Function1<androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> debugInspectorInfo(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> definitions);
     method public static kotlin.jvm.functions.Function1<androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> getNoInspectorInfo();
-    method public static inline androidx.compose.ui.Modifier inspectable(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method @Deprecated public static inline androidx.compose.ui.Modifier inspectable(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
     method public static boolean isDebugInspectorInfoEnabled();
     method public static void setDebugInspectorInfoEnabled(boolean);
     property public static final kotlin.jvm.functions.Function1<androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> NoInspectorInfo;
@@ -3642,6 +3665,7 @@
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled, optional boolean usePlatformDefaultWidth);
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional boolean clippingEnabled);
+    ctor public PopupProperties(int flags, optional boolean inheritSecurePolicy, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional boolean excludeFromSystemGesture, optional boolean usePlatformDefaultWidth);
     method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 1832ed0..6a2c2fc 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2043,6 +2043,27 @@
     property public static final androidx.compose.ui.layout.HorizontalAlignmentLine LastBaseline;
   }
 
+  public sealed interface ApproachIntrinsicMeasureScope extends androidx.compose.ui.layout.IntrinsicMeasureScope {
+    method public long getLookaheadConstraints();
+    method public long getLookaheadSize();
+    property public abstract long lookaheadConstraints;
+    property public abstract long lookaheadSize;
+  }
+
+  public interface ApproachLayoutModifierNode extends androidx.compose.ui.node.LayoutModifierNode {
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.layout.MeasureResult approachMeasure(androidx.compose.ui.layout.ApproachMeasureScope, androidx.compose.ui.layout.Measurable measurable, long constraints);
+    method public boolean isMeasurementApproachComplete(long lookaheadSize);
+    method public default boolean isPlacementApproachComplete(androidx.compose.ui.layout.Placeable.PlacementScope, androidx.compose.ui.layout.LayoutCoordinates lookaheadCoordinates);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int maxApproachIntrinsicHeight(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int width);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int maxApproachIntrinsicWidth(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int height);
+    method public default androidx.compose.ui.layout.MeasureResult measure(androidx.compose.ui.layout.MeasureScope, androidx.compose.ui.layout.Measurable measurable, long constraints);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int minApproachIntrinsicHeight(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int width);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public default int minApproachIntrinsicWidth(androidx.compose.ui.layout.ApproachIntrinsicMeasureScope, androidx.compose.ui.layout.IntrinsicMeasurable measurable, int height);
+  }
+
+  @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface ApproachMeasureScope extends androidx.compose.ui.layout.ApproachIntrinsicMeasureScope androidx.compose.ui.layout.MeasureScope {
+  }
+
   public interface BeyondBoundsLayout {
     method public <T> T? layout(int direction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.BeyondBoundsLayout.BeyondBoundsScope,? extends T?> block);
   }
@@ -2122,9 +2143,7 @@
     ctor public HorizontalRuler();
   }
 
-  @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface IntermediateMeasureScope extends androidx.compose.ui.layout.LookaheadScope kotlinx.coroutines.CoroutineScope androidx.compose.ui.layout.MeasureScope {
-    method public long getLookaheadSize();
-    property public abstract long lookaheadSize;
+  @Deprecated @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public interface IntermediateMeasureScope extends androidx.compose.ui.layout.ApproachMeasureScope kotlinx.coroutines.CoroutineScope androidx.compose.ui.layout.LookaheadScope {
   }
 
   public interface IntrinsicMeasurable {
@@ -2243,7 +2262,8 @@
 
   public final class LookaheadScopeKt {
     method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static void LookaheadScope(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier approachLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,java.lang.Boolean> isMeasurementApproachComplete, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.Placeable.PlacementScope,? super androidx.compose.ui.layout.LayoutCoordinates,java.lang.Boolean> isPlacementApproachComplete, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.ApproachMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> approachMeasure);
+    method @Deprecated @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
   }
 
   public interface Measurable extends androidx.compose.ui.layout.IntrinsicMeasurable {
@@ -2519,9 +2539,11 @@
 
   public final class ModifierLocalModifierNodeKt {
     method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf();
-    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?>... keys);
+    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?> key1, androidx.compose.ui.modifier.ModifierLocal<?> key2, androidx.compose.ui.modifier.ModifierLocal<?>... keys);
+    method @Deprecated public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<?>... keys);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<T> key);
-    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
+    method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?> entry1, kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?> entry2, kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
+    method @Deprecated public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<?>,?>... entries);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<T>,? extends T> entry);
   }
 
@@ -2609,6 +2631,7 @@
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static androidx.compose.ui.unit.Density requireDensity(androidx.compose.ui.node.DelegatableNode);
+    method public static androidx.compose.ui.layout.LayoutCoordinates requireLayoutCoordinates(androidx.compose.ui.node.DelegatableNode);
     method public static androidx.compose.ui.unit.LayoutDirection requireLayoutDirection(androidx.compose.ui.node.DelegatableNode);
   }
 
@@ -2955,7 +2978,7 @@
   public final class InspectableValueKt {
     method public static inline kotlin.jvm.functions.Function1<androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> debugInspectorInfo(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> definitions);
     method public static kotlin.jvm.functions.Function1<androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> getNoInspectorInfo();
-    method public static inline androidx.compose.ui.Modifier inspectable(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method @Deprecated public static inline androidx.compose.ui.Modifier inspectable(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
     method @kotlin.PublishedApi internal static androidx.compose.ui.Modifier inspectableWrapper(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, androidx.compose.ui.Modifier wrapped);
     method public static boolean isDebugInspectorInfoEnabled();
     method public static void setDebugInspectorInfoEnabled(boolean);
@@ -3702,6 +3725,7 @@
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled, optional boolean usePlatformDefaultWidth);
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional boolean clippingEnabled);
+    ctor public PopupProperties(int flags, optional boolean inheritSecurePolicy, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional boolean excludeFromSystemGesture, optional boolean usePlatformDefaultWidth);
     method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt
new file mode 100644
index 0000000..2f557b3
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.benchmark
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NestedScrollingBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val nestedScrollingCaseFactory = { NestedScrollingTestCase() }
+
+    @Test
+    fun nested_scroll_propagation() {
+        benchmarkRule.runBenchmarkFor(nestedScrollingCaseFactory) {
+            runOnUiThread {
+                doFramesUntilNoChangesPending()
+            }
+
+            benchmarkRule.measureRepeatedOnUiThread {
+                getTestCase().toggleState()
+                runWithTimingDisabled {
+                    assertNoPendingChanges()
+                    getTestCase().assertPostToggle()
+                }
+            }
+        }
+    }
+}
+
+class NestedScrollingTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+    private var collectedDeltasOuter = Offset.Zero
+    private var collectedDeltasMiddle = Offset.Zero
+    private var collectedVelocityOuter = Velocity.Zero
+    private var collectedVelocityMiddle = Velocity.Zero
+
+    private val outerConnection = object : NestedScrollConnection {
+        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+            collectedDeltasOuter += available
+            return super.onPreScroll(available, source)
+        }
+
+        override suspend fun onPreFling(available: Velocity): Velocity {
+            collectedVelocityOuter += available
+            return super.onPreFling(available)
+        }
+    }
+
+    private val middleConnection = object : NestedScrollConnection {
+        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+            collectedDeltasMiddle += available
+            return super.onPreScroll(available, source)
+        }
+
+        override suspend fun onPreFling(available: Velocity): Velocity {
+            collectedVelocityMiddle += available
+            return super.onPreFling(available)
+        }
+    }
+
+    private val dispatcher = NestedScrollDispatcher()
+    private val noOpConnection = object : NestedScrollConnection {}
+    private val delta = Offset(200f, 200f)
+    private val velocity = Velocity(2000f, 200f)
+    private var scrollResult = Offset.Zero
+    private var velocityResult = Velocity.Zero
+    private val IntermediateConnection = object : NestedScrollConnection {}
+
+    @Composable
+    override fun MeasuredContent() {
+        Box(
+            modifier = Modifier
+                .nestedScroll(outerConnection)
+        ) {
+            Box(
+                modifier = Modifier
+                    .nestedScroll(middleConnection)
+            ) {
+                NestedBox(boxLevel = 20) {
+                    Box(
+                        modifier = Modifier
+                            .nestedScroll(noOpConnection, dispatcher)
+                    )
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun NestedBox(boxLevel: Int, leafContent: @Composable () -> Unit) {
+        if (boxLevel == 0) {
+            Box {
+                leafContent()
+            }
+            return
+        }
+
+        Box(modifier = Modifier.nestedScroll(IntermediateConnection)) {
+            NestedBox(boxLevel = boxLevel - 1, leafContent)
+        }
+    }
+
+    override fun toggleState() {
+        scrollResult = dispatcher.dispatchPreScroll(delta, NestedScrollSource.Drag)
+        scrollResult = dispatcher.dispatchPostScroll(delta, scrollResult, NestedScrollSource.Drag)
+
+        runBlocking {
+            velocityResult = dispatcher.dispatchPreFling(velocity)
+            velocityResult = dispatcher.dispatchPostFling(velocity, velocityResult)
+        }
+    }
+
+    fun assertPostToggle() {
+        assert(collectedDeltasOuter != Offset.Zero)
+        assert(collectedDeltasMiddle != Offset.Zero)
+        assert(collectedVelocityOuter != Velocity.Zero)
+        assert(collectedVelocityMiddle != Velocity.Zero)
+
+        assert(collectedDeltasOuter == scrollResult)
+        assert(collectedVelocityOuter == velocityResult)
+    }
+}
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/TrailingLambdaBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/TrailingLambdaBenchmark.kt
new file mode 100644
index 0000000..78cacc3
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/TrailingLambdaBenchmark.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.compose.ui.benchmark
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TrailingLambdaBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun withTrailingLambdas_compose() {
+        benchmarkRule.benchmarkFirstCompose { WithTrailingLambdas() }
+    }
+
+    @Test
+    fun withTrailingLambdas_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose({ WithTrailingLambdas() })
+    }
+
+    @Test
+    fun withoutTrailingLambdas_compose() {
+        benchmarkRule.benchmarkFirstCompose { WithoutTrailingLambdas() }
+    }
+
+    @Test
+    fun withoutTrailingLambdas_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose({ WithoutTrailingLambdas() })
+    }
+}
+
+private sealed class TrailingLambdaTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+
+    var numberState: MutableState<Int>? = null
+
+    @Composable
+    override fun MeasuredContent() {
+        val number = remember { mutableStateOf(5) }
+        numberState = number
+
+        val content = @Composable {
+            Box(Modifier.width(10.dp))
+        }
+
+        Column {
+            repeat(10) {
+                Content(number = number.value, content = content)
+            }
+        }
+    }
+
+    override fun toggleState() {
+        with(numberState!!) {
+            value = if (value == 5) 10 else 5
+        }
+    }
+
+    @Composable
+    abstract fun Content(number: Int, content: @Composable () -> Unit)
+}
+
+private class WithTrailingLambdas : TrailingLambdaTestCase() {
+    @Composable
+    override fun Content(number: Int, content: @Composable () -> Unit) {
+        EmptyComposable(number = number) {
+            content()
+        }
+    }
+}
+
+private class WithoutTrailingLambdas : TrailingLambdaTestCase() {
+    @Composable
+    override fun Content(number: Int, content: @Composable () -> Unit) {
+        EmptyComposable(number = number, content = content)
+    }
+}
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun EmptyComposable(number: Int, content: @Composable () -> Unit) {
+}
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 69ae2b2..5835d87 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -46,14 +46,14 @@
                 api("androidx.annotation:annotation:1.6.0")
                 implementation("androidx.collection:collection:1.4.0")
                 // when updating the runtime version please also update the runtime-saveable version
-                implementation(project(":compose:runtime:runtime"))
-                api(project(":compose:runtime:runtime-saveable"))
+                implementation("androidx.compose.runtime:runtime:1.6.0")
+                api("androidx.compose.runtime:runtime-saveable:1.6.0")
 
-                api project(":compose:ui:ui-geometry")
-                api project(":compose:ui:ui-graphics")
-                api project(":compose:ui:ui-text")
-                api project(":compose:ui:ui-unit")
-                api project(":compose:ui:ui-util")
+                api(project(":compose:ui:ui-geometry"))
+                api(project(":compose:ui:ui-graphics"))
+                api(project(":compose:ui:ui-text"))
+                api(project(":compose:ui:ui-unit"))
+                api(project(":compose:ui:ui-util"))
             }
         }
 
@@ -73,7 +73,6 @@
         skikoMain {
             dependsOn(commonMain)
             dependencies {
-                api(project(":compose:ui:ui-graphics"))
                 api(libs.skikoCommon)
             }
         }
@@ -107,7 +106,6 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation(libs.kotlinStdlib)
-                implementation(libs.kotlinStdlibJdk8)
                 api(libs.kotlinCoroutinesSwing)
             }
         }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt
index cea159c..bf2fe7d 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt
@@ -16,18 +16,20 @@
 
 package androidx.compose.ui.demos.autofill
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.MaterialTheme
+import androidx.compose.material.OutlinedTextField
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.autofill.AutofillNode
@@ -37,69 +39,41 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalAutofill
 import androidx.compose.ui.platform.LocalAutofillTree
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
 
 @Composable
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalComposeUiApi::class)
 fun ExplicitAutofillTypesDemo() {
-    Column {
-        val nameState = remember { mutableStateOf("Enter name here") }
-        val emailState = remember { mutableStateOf("Enter email here") }
-        val autofill = LocalAutofill.current
-        val labelStyle = MaterialTheme.typography.subtitle1
-        val textStyle = MaterialTheme.typography.h6
+    var name by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue(""))
+    }
+    var email by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue(""))
+    }
 
-        Text("Name", style = labelStyle)
+    Column {
         Autofill(
             autofillTypes = listOf(AutofillType.PersonFullName),
-            onFill = { nameState.value = it }
-        ) { autofillNode ->
-            BasicTextField(
-                modifier = Modifier.onFocusChanged {
-                    autofill?.apply {
-                        if (it.isFocused) {
-                            requestAutofillForNode(autofillNode)
-                        } else {
-                            cancelAutofillForNode(autofillNode)
-                        }
-                    }
-                },
-                value = nameState.value,
-                keyboardOptions = KeyboardOptions(
-                    keyboardType = KeyboardType.Text,
-                    imeAction = ImeAction.Default
-                ),
-                onValueChange = { nameState.value = it },
-                textStyle = textStyle
+            onFill = { name = TextFieldValue(it) }
+        ) {
+            OutlinedTextField(
+                value = name,
+                onValueChange = { name = it },
+                label = { Text("Name") },
             )
         }
 
-        Spacer(Modifier.height(40.dp))
+        Spacer(Modifier.height(10.dp))
 
-        Text("Email", style = labelStyle)
         Autofill(
             autofillTypes = listOf(AutofillType.EmailAddress),
-            onFill = { emailState.value = it }
-        ) { autofillNode ->
-            BasicTextField(
-                modifier = Modifier.onFocusChanged {
-                    autofill?.run {
-                        if (it.isFocused) {
-                            requestAutofillForNode(autofillNode)
-                        } else {
-                            cancelAutofillForNode(autofillNode)
-                        }
-                    }
-                },
-                value = emailState.value,
-                keyboardOptions = KeyboardOptions(
-                    keyboardType = KeyboardType.Text,
-                    imeAction = ImeAction.Default
-                ),
-                onValueChange = { emailState.value = it },
-                textStyle = textStyle
+            onFill = { email = TextFieldValue(it) }
+        ) {
+            OutlinedTextField(
+                value = email,
+                onValueChange = { email = it },
+                label = { Text("Email") },
             )
         }
     }
@@ -110,18 +84,29 @@
 private fun Autofill(
     autofillTypes: List<AutofillType>,
     onFill: ((String) -> Unit),
-    content: @Composable (AutofillNode) -> Unit
+    content: @Composable BoxScope.() -> Unit
 ) {
-    val autofillNode = AutofillNode(onFill = onFill, autofillTypes = autofillTypes)
-
+    val autofill = LocalAutofill.current
     val autofillTree = LocalAutofillTree.current
-    autofillTree += autofillNode
+    val autofillNode = remember(autofillTypes, onFill) {
+        AutofillNode(onFill = onFill, autofillTypes = autofillTypes)
+    }
 
     Box(
-        Modifier.onGloballyPositioned {
-            autofillNode.boundingBox = it.boundsInWindow()
-        }
-    ) {
-        content(autofillNode)
+        modifier = Modifier
+            .onFocusChanged {
+                if (it.isFocused) {
+                    autofill?.requestAutofillForNode(autofillNode)
+                } else {
+                    autofill?.cancelAutofillForNode(autofillNode)
+                }
+            }
+            .onGloballyPositioned { autofillNode.boundingBox = it.boundsInWindow() },
+        content = content
+    )
+
+    DisposableEffect(autofillNode) {
+        autofillTree.children[autofillNode.id] = autofillNode
+        onDispose { autofillTree.children.remove(autofillNode.id) }
     }
 }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropComposeInAndroid.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropComposeInAndroid.kt
index 39495d1..5d357f3 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropComposeInAndroid.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropComposeInAndroid.kt
@@ -232,20 +232,16 @@
 
         myContext = peekAvailableContext()!!
 
-        findViewById<TextView>(R.id.text1).text =
-            "Demonstrates correct interop with simple tapping and a window manager"
-        findViewById<TextView>(R.id.text2).text =
-            "The top/outer text and button are Android, and the button dynamically triggers " +
-                "adding/removing a ComposeView via the WindowManager. The inner ComposeView also " +
-                "contains a button that tracks the number of clicks."
-
         button = findViewById<Button>(R.id.button)
         button.setOnClickListener {
-            viewLoaded = if (viewLoaded != null) {
-                myContext.removeWindow(viewLoaded!!)
-                null
+            if (viewLoaded == null) {
+                viewLoaded = myContext.addWindow()
             } else {
-                myContext.addWindow()
+                if (viewLoaded!!.isAttachedToWindow) {
+                    myContext.removeWindow(viewLoaded!!)
+                } else {
+                    myContext.addWindow(viewLoaded)
+                }
             }
         }
     }
@@ -280,7 +276,9 @@
         get() = mViewModelStore
 }
 
-private fun Context.buildWindowView(content: @Composable (composeView: View) -> Unit): View {
+private fun Context.buildWindowView(
+    content: @Composable (composeView: View) -> Unit
+): View {
     val lifecycleOwner = ComposeViewLifecycleOwner()
 
     lifecycleOwner.performRestore(null)
@@ -307,9 +305,11 @@
     }
 }
 
-private fun Context.addWindow(): View {
+private fun Context.addWindow(passedView: View? = null): View {
     val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
-    val view = buildWindowView { SimpleClickableButton() }
+
+    // Reuse existing view (otherwise, create a new one).
+    val view = passedView ?: buildWindowView { SimpleClickableButton() }
 
     val layoutParas = WindowManager.LayoutParams()
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/compose_in_android_tap_reload.xml b/compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/compose_in_android_tap_reload.xml
index 6d4d50a..724c966 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/compose_in_android_tap_reload.xml
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/compose_in_android_tap_reload.xml
@@ -16,24 +16,29 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:padding="15.dp">
 
     <TextView
         android:id="@+id/text1"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="0"/>
+        android:layout_weight="0"
+        android:text="@string/compose_in_android_dynamically_loading_compose_using_window_manager_title"/>
 
     <TextView
         android:id="@+id/text2"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="0"/>
+        android:layout_weight="0"
+        android:text="@string/compose_in_android_dynamically_loading_compose_using_window_manager_details"/>
 
     <Button
         android:id="@+id/button"
-        android:text="Click to unload/load ComposeView"
+        android:text="@string/compose_in_android_dynamically_loading_compose_using_window_manager_button_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="0"/>
+        android:layout_weight="0"
+        android:paddingEnd="10.dp"/>
+
 </LinearLayout>
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/res/values/strings.xml b/compose/ui/ui/integration-tests/ui-demos/src/main/res/values/strings.xml
new file mode 100644
index 0000000..fdf9cc5
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 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.
+  -->
+
+<resources>
+    <string name="compose_in_android_dynamically_loading_compose_using_window_manager_details">The top/outer text and button are Views. The button adds/removes a ComposeView via the WindowManager. The inner ComposeView also contains a button that tracks the number of clicks.</string>
+    <string name="compose_in_android_dynamically_loading_compose_using_window_manager_title">Demonstrates interop with simple tapping and a window manager</string>
+    <string name="compose_in_android_dynamically_loading_compose_using_window_manager_button_text">Click to unload/load ComposeView</string>
+</resources>
\ No newline at end of file
diff --git a/compose/ui/ui/lint-baseline.xml b/compose/ui/ui/lint-baseline.xml
index a00bbef..6b4b404 100644
--- a/compose/ui/ui/lint-baseline.xml
+++ b/compose/ui/ui/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanThreadSleep"
@@ -93,15 +93,6 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="field alignmentLines with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
-        errorLine1="            override val alignmentLines = alignmentLines"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
         message="field alignmentLineMap with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
         errorLine1="    private val alignmentLineMap: MutableMap&lt;AlignmentLine, Int> = hashMapOf()"
         errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -183,33 +174,6 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="field alignmentLines with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
-        errorLine1="        override val alignmentLines = alignmentLines"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable oldLines with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
-        errorLine1="                    val oldLines = oldAlignmentLines"
-        errorLine2="                    ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable var43bc2abb with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
-        errorLine1="                    val oldLines = oldAlignmentLines"
-        errorLine2="                                   ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
         message="field oldAlignmentLines with type Map&lt;AlignmentLine, Integer>: replace with ObjectIntMap"
         errorLine1="    private var oldAlignmentLines: MutableMap&lt;AlignmentLine, Int>? = null"
         errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/InspectableModifierSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/InspectableModifierSample.kt
index f0654e5..52df79c 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/InspectableModifierSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/InspectableModifierSample.kt
@@ -29,6 +29,7 @@
 
 @Sampled
 @Composable
+@Suppress("DEPRECATION")
 fun InspectableModifierSample() {
 
     /**
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
index a77df1b..036583e 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
@@ -21,6 +21,8 @@
 import androidx.annotation.Sampled
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
@@ -44,99 +46,134 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
+import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
+@OptIn(ExperimentalAnimatableApi::class)
 @Sampled
 @Composable
-fun IntermediateLayoutSample() {
+fun approachLayoutSample() {
     // Creates a custom modifier that animates the constraints and measures child with the
-    // animated constraints. This modifier is built on top of `Modifier.intermediateLayout`, which
-    // allows access to the lookahead size of the layout. A resize animation will be kicked off
-    // whenever the lookahead size changes, to animate children from current size to lookahead size.
-    // Fixed constraints created based on the animation value will be used to measure
-    // child, so the child layout gradually changes its size and potentially its child's placement
-    // to fit within the animated constraints.
-    fun Modifier.animateConstraints() = composed {
-        // Creates a size animation
-        var sizeAnimation: Animatable<IntSize, AnimationVector2D>? by remember {
-            mutableStateOf(null)
+    // animated constraints. This modifier is built on top of `Modifier.approachLayout` to approach
+    // th destination size determined by the lookahead pass. A resize animation will be kicked off
+    // whenever the lookahead size changes, to animate children from current size to destination
+    // size. Fixed constraints created based on the animation value will be used to measure
+    // child, so the child layout gradually changes its animated constraints until the approach
+    // completes.
+    fun Modifier.animateConstraints(
+        sizeAnimation: DeferredTargetAnimation<IntSize, AnimationVector2D>,
+        coroutineScope: CoroutineScope
+    ) = this.approachLayout(
+        isMeasurementApproachComplete = { lookaheadSize ->
+            // Update the target of the size animation.
+            sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
+            // Return true if the size animation has no pending target change and has finished
+            // running.
+            sizeAnimation.isIdle
         }
+    ) { measurable, _ ->
+        // In the measurement approach, the goal is to gradually reach the destination size
+        // (i.e. lookahead size). To achieve that, we use an animation to track the current
+        // size, and animate to the destination size whenever it changes. Once the animation
+        // finishes, the approach is complete.
 
-        this.intermediateLayout { measurable, _ ->
-            // When layout changes, the lookahead pass will calculate a new final size for the
-            // child layout. This lookahead size can be used to animate the size
-            // change, such that the animation starts from the current size and gradually
-            // change towards `lookaheadSize`.
-            if (lookaheadSize != sizeAnimation?.targetValue) {
-                sizeAnimation?.run {
-                    launch { animateTo(lookaheadSize) }
-                } ?: Animatable(lookaheadSize, IntSize.VectorConverter).let {
-                    sizeAnimation = it
-                }
-            }
-            val (width, height) = sizeAnimation!!.value
-            // Creates a fixed set of constraints using the animated size
-            val animatedConstraints = Constraints.fixed(width, height)
-            // Measure child with animated constraints.
-            val placeable = measurable.measure(animatedConstraints)
-            layout(placeable.width, placeable.height) {
-                placeable.place(0, 0)
-            }
+        // First, update the target of the animation, and read the current animated size.
+        val (width, height) = sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
+        // Then create fixed size constraints using the animated size
+        val animatedConstraints = Constraints.fixed(width, height)
+        // Measure child with animated constraints.
+        val placeable = measurable.measure(animatedConstraints)
+        layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
         }
     }
 
     var fullWidth by remember { mutableStateOf(false) }
+
+    // Creates a size animation with a target unknown at the time of instantiation.
+    val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
+    val coroutineScope = rememberCoroutineScope()
     Row(
         (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp))
             .height(200.dp)
             // Use the custom modifier created above to animate the constraints passed
             // to the child, and therefore resize children in an animation.
-            .animateConstraints()
+            .animateConstraints(sizeAnimation, coroutineScope)
             .clickable { fullWidth = !fullWidth }) {
         Box(
             Modifier
                 .weight(1f)
                 .fillMaxHeight()
-                .background(Color.Red)
+                .background(Color(0xffff6f69)),
         )
         Box(
             Modifier
                 .weight(2f)
                 .fillMaxHeight()
-                .background(Color.Yellow)
+                .background(Color(0xffffcc5c))
         )
     }
 }
 
+@OptIn(ExperimentalAnimatableApi::class)
 @Sampled
 @Composable
 fun LookaheadLayoutCoordinatesSample() {
-    // Creates a custom modifier to animate the local position of the layout within the
-    // given LookaheadScope, whenever the relative position changes.
-    fun Modifier.animatePlacementInScope(lookaheadScope: LookaheadScope) = composed {
-        // Creates an offset animation
-        var offsetAnimation: Animatable<IntOffset, AnimationVector2D>? by remember {
-            mutableStateOf(
-                null
+    /**
+     * Creates a custom implementation of ApproachLayoutModifierNode to approach the placement
+     * of the layout using an animation.
+     */
+    class AnimatedPlacementModifierNode(
+        var lookaheadScope: LookaheadScope
+    ) : ApproachLayoutModifierNode, Modifier.Node() {
+        // Creates an offset animation, the target of which will be known during placement.
+        val offsetAnimation: DeferredTargetAnimation<IntOffset, AnimationVector2D> =
+            DeferredTargetAnimation(
+                IntOffset.VectorConverter
             )
+
+        override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+            // Since we only animate the placement here, we can consider measurement approach
+            // complete.
+            return true
         }
 
-        this.intermediateLayout { measurable, constraints ->
+        // Returns true when the offset animation is complete, false otherwise.
+        override fun Placeable.PlacementScope.isPlacementApproachComplete(
+            lookaheadCoordinates: LayoutCoordinates
+        ): Boolean {
+            val target = with(lookaheadScope) {
+                lookaheadScopeCoordinates.localLookaheadPositionOf(lookaheadCoordinates).round()
+            }
+            offsetAnimation.updateTarget(target, coroutineScope)
+            return offsetAnimation.isIdle
+        }
+
+        @ExperimentalComposeUiApi
+        override fun ApproachMeasureScope.approachMeasure(
+            measurable: Measurable,
+            constraints: Constraints
+        ): MeasureResult {
             val placeable = measurable.measure(constraints)
-            layout(placeable.width, placeable.height) {
-                // Converts coordinates of the current layout to LookaheadCoordinates
+            return layout(placeable.width, placeable.height) {
                 val coordinates = coordinates
                 if (coordinates != null) {
                     // Calculates the target offset within the lookaheadScope
@@ -147,23 +184,18 @@
                     }
 
                     // Uses the target offset to start an offset animation
-                    if (target != offsetAnimation?.targetValue) {
-                        offsetAnimation?.run {
-                            launch { animateTo(target) }
-                        } ?: Animatable(target, IntOffset.VectorConverter).let {
-                            offsetAnimation = it
-                        }
-                    }
+                    val animatedOffset = offsetAnimation.updateTarget(target, coroutineScope)
                     // Calculates the *current* offset within the given LookaheadScope
-                    val placementOffset =
+                    val placementOffset = with(lookaheadScope) {
                         lookaheadScopeCoordinates.localPositionOf(
                             coordinates,
                             Offset.Zero
                         ).round()
+                    }
                     // Calculates the delta between animated position in scope and current
                     // position in scope, and places the child at the delta offset. This puts
                     // the child layout at the animated position.
-                    val (x, y) = requireNotNull(offsetAnimation).run { value - placementOffset }
+                    val (x, y) = animatedOffset - placementOffset
                     placeable.place(x, y)
                 } else {
                     placeable.place(0, 0)
@@ -172,6 +204,19 @@
         }
     }
 
+    // Creates a custom node element for the AnimatedPlacementModifierNode above.
+    data class AnimatePlacementNodeElement(val lookaheadScope: LookaheadScope) :
+        ModifierNodeElement<AnimatedPlacementModifierNode>() {
+
+        override fun update(node: AnimatedPlacementModifierNode) {
+            node.lookaheadScope = lookaheadScope
+        }
+
+        override fun create(): AnimatedPlacementModifierNode {
+            return AnimatedPlacementModifierNode(lookaheadScope)
+        }
+    }
+
     val colors = listOf(
         Color(0xffff6f69), Color(0xffffcc5c), Color(0xff264653), Color(0xff2a9d84)
     )
@@ -187,7 +232,9 @@
                         Modifier
                             .padding(15.dp)
                             .size(100.dp, 80.dp)
-                            .animatePlacementInScope(this)
+                            .then(
+                                AnimatePlacementNodeElement(this)
+                            )
                             .background(color, RoundedCornerShape(20))
                     )
                 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index d9e4578..64797a5 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -70,10 +70,10 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.selection.toggleable
+import androidx.compose.foundation.text.BasicSecureTextField
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text2.BasicSecureTextField
-import androidx.compose.foundation.text2.input.rememberTextFieldState
+import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.BottomAppBar
 import androidx.compose.material.Button
@@ -3122,6 +3122,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun selectionEventBeforeTraverseEvent_whenTraverseTextField() {
         val text = "h"
         setContent {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index ed3d26d..ce24620 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.ui
 
+import android.content.Context
 import android.content.Intent
 import android.graphics.Bitmap
 import android.os.Build
@@ -110,6 +111,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
@@ -3776,6 +3778,41 @@
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
     }
 
+    @Test
+    fun attachingLayerDoesNotCauseRelayout() {
+        var latch = CountDownLatch(1)
+        lateinit var root: RequestLayoutTrackingFrameLayout
+        lateinit var composeView: ComposeView
+        var showLayer by mutableStateOf(false)
+
+        activityTestRule.runOnUiThread {
+            root = RequestLayoutTrackingFrameLayout(activity)
+            composeView = ComposeView(activity)
+
+            activity.setContentView(root)
+            root.addView(composeView)
+            composeView.setContent {
+                val modifier = if (showLayer) Modifier.graphicsLayer() else Modifier
+                Box(Modifier.drawBehind { latch.countDown() }.then(modifier))
+            }
+        }
+
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        activityTestRule.runOnUiThread {
+            Truth.assertThat(root.requestLayoutCalled).isTrue()
+            latch = CountDownLatch(1)
+            root.requestLayoutCalled = false
+            showLayer = true
+        }
+
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        activityTestRule.runOnUiThread {
+            Truth.assertThat(root.requestLayoutCalled).isFalse()
+        }
+    }
+
     private fun Modifier.layout(onLayout: () -> Unit) = layout { measurable, constraints ->
         val placeable = measurable.measure(constraints)
         layout(placeable.width, placeable.height) {
@@ -4479,3 +4516,12 @@
 fun Modifier.latch(countDownLatch: CountDownLatch) = drawBehind {
     countDownLatch.countDown()
 }
+
+private class RequestLayoutTrackingFrameLayout(context: Context) : FrameLayout(context) {
+    var requestLayoutCalled = false
+
+    override fun requestLayout() {
+        super.requestLayout()
+        requestLayoutCalled = true
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index d53a555..d594371 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -80,6 +80,7 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.testutils.waitForFutureFrame
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -101,9 +102,8 @@
 class AndroidPointerInputTest {
     @Suppress("DEPRECATION")
     @get:Rule
-    val rule = androidx.test.rule.ActivityTestRule(
-        AndroidPointerInputTestActivity::class.java
-    )
+    val rule =
+        androidx.test.rule.ActivityTestRule(AndroidPointerInputTestActivity::class.java)
 
     private lateinit var container: OpenComposeView
 
@@ -1348,6 +1348,10 @@
         pointerEvent = null // Reset before each event
         dispatchMouseEvent(ACTION_HOVER_EXIT, box2LayoutCoordinates!!)
 
+        // Hover exit events in Compose are always delayed two frames to ensure Compose does not
+        // trigger them if they are followed by a press in the next frame. This accounts for that.
+        rule.waitForFutureFrame(2)
+
         rule.runOnUiThread {
             assertThat(exitBox2).isTrue()
             assertThat(pointerEvent).isNotNull()
@@ -1385,6 +1389,367 @@
         assertTrue(totalEventLatch.await(1, TimeUnit.SECONDS))
     }
 
+    /*
+     * Tests that a bad ACTION_HOVER_EXIT MotionEvent is ignored in Compose when it directly
+     * proceeds an ACTION_SCROLL MotionEvent. This happens in some versions of Android Studio when
+     * mirroring is used (b/314269723).
+     *
+     * The event order of MotionEvents:
+     *   - Hover enter on box 1
+     *   - Hover exit on box 1 (bad event)
+     *   - Scroll on box 1
+     */
+    @Test
+    fun scrollMotionEvent_proceededImmediatelyByHoverExit_shouldNotTriggerHoverExit() {
+        // --> Arrange
+        val scrollDelta = Offset(0.35f, 0.65f)
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(4)
+
+        // Events for Box 1
+        var enterBox1 = false
+        var scrollBox1 = false
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger = false
+
+        var pointerEvent: PointerEvent? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                Column(
+                    Modifier
+                        .fillMaxSize()
+                        .onGloballyPositioned {
+                            setUpFinishedLatch.countDown()
+                        }
+                ) {
+                    // Box 1
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                box1LayoutCoordinates = it
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+
+                                        when (pointerEvent!!.type) {
+                                            PointerEventType.Enter -> {
+                                                enterBox1 = true
+                                            }
+
+                                            PointerEventType.Exit -> {
+                                                enterBox1 = false
+                                            }
+
+                                            PointerEventType.Scroll -> {
+                                                scrollBox1 = true
+                                            }
+
+                                            else -> {
+                                                eventsThatShouldNotTrigger = true
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                    ) { }
+
+                    // Box 2
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+                                        // Should never do anything with this UI element.
+                                        eventsThatShouldNotTrigger = true
+                                    }
+                                }
+                            }
+                    ) { }
+
+                    // Box 3
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+                                        // Should never do anything with this UI element.
+                                        eventsThatShouldNotTrigger = true
+                                    }
+                                }
+                            }
+                    ) { }
+                }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // Hover Enter on Box 1
+        dispatchMouseEvent(ACTION_HOVER_ENTER, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(enterBox1).isTrue()
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+            assertHoverEvent(pointerEvent!!, isEnter = true)
+        }
+
+        // We do not use dispatchMouseEvent() to dispatch the following two events, because the
+        // actions need to be executed in immediate succession
+        rule.runOnUiThread {
+            val root = box1LayoutCoordinates!!.findRootCoordinates()
+            val pos = root.localPositionOf(box1LayoutCoordinates!!, Offset.Zero)
+
+            // Bad hover exit event on Box 1
+            val exitMotionEvent = MotionEvent(
+                0,
+                ACTION_HOVER_EXIT,
+                1,
+                0,
+                arrayOf(PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }),
+                arrayOf(PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y))
+            )
+
+            // Main scroll event on Box 1
+            val scrollMotionEvent = MotionEvent(
+                0,
+                ACTION_SCROLL,
+                1,
+                0,
+                arrayOf(PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }),
+                arrayOf(PointerCoords(pos.x, pos.y, scrollDelta.x, scrollDelta.y))
+            )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchHoverEvent(exitMotionEvent)
+            androidComposeView.dispatchGenericMotionEvent(scrollMotionEvent)
+        }
+
+        rule.runOnUiThread {
+            assertThat(enterBox1).isTrue()
+            assertThat(scrollBox1).isTrue()
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
+    /*
+     * Tests an ACTION_HOVER_EXIT MotionEvent is ignored in Compose when it proceeds an
+     * ACTION_DOWN MotionEvent (in a measure of milliseconds only).
+     *
+     * The event order of MotionEvents:
+     *   - Hover enter on box 1
+     *   - Loop 10 times:
+     *     - Hover enter on box 1
+     *     - Down on box 1
+     *     - Up on box 1
+     */
+    @Test
+    fun hoverExitBeforeDownMotionEvent_shortDelayBetweenMotionEvents_shouldNotTriggerHoverExit() {
+        // --> Arrange
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(4)
+
+        // Events for Box 1
+        var enterBox1 = false
+        var pressBox1 = false
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger = false
+
+        var pointerEvent: PointerEvent? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                Column(
+                    Modifier
+                        .fillMaxSize()
+                        .onGloballyPositioned {
+                            setUpFinishedLatch.countDown()
+                        }
+                ) {
+                    // Box 1
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                box1LayoutCoordinates = it
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+
+                                        when (pointerEvent!!.type) {
+                                            PointerEventType.Enter -> {
+                                                enterBox1 = true
+                                            }
+
+                                            PointerEventType.Exit -> {
+                                                enterBox1 = false
+                                            }
+
+                                            PointerEventType.Press -> {
+                                                pressBox1 = true
+                                            }
+
+                                            PointerEventType.Release -> {
+                                                pressBox1 = false
+                                            }
+
+                                            else -> {
+                                                eventsThatShouldNotTrigger = true
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                    ) { }
+
+                    // Box 2
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+                                        // Should never do anything with this UI element.
+                                        eventsThatShouldNotTrigger = true
+                                    }
+                                }
+                            }
+                    ) { }
+
+                    // Box 3
+                    Box(
+                        Modifier
+                            .size(50.dp)
+                            .onGloballyPositioned {
+                                setUpFinishedLatch.countDown()
+                            }
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        pointerEvent = awaitPointerEvent()
+                                        // Should never do anything with this UI element.
+                                        eventsThatShouldNotTrigger = true
+                                    }
+                                }
+                            }
+                    ) { }
+                }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // Hover Enter on Box 1
+        dispatchMouseEvent(ACTION_HOVER_ENTER, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(enterBox1).isTrue()
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+            assertHoverEvent(pointerEvent!!, isEnter = true)
+        }
+
+        pointerEvent = null // Reset before each event
+
+        for (index in 0 until 10) {
+            // We do not use dispatchMouseEvent() to dispatch the following two events, because the
+            // actions need to be executed in immediate succession.
+            rule.runOnUiThread {
+                val root = box1LayoutCoordinates!!.findRootCoordinates()
+                val pos = root.localPositionOf(box1LayoutCoordinates!!, Offset.Zero)
+
+                // Exit on Box 1 right before action down. This happens normally on devices, so we
+                // are recreating it here. However, Compose ignores the exit if it is right before
+                // a down (right before meaning within a couple milliseconds). We verify that it
+                // did in fact ignore this exit.
+                val exitMotionEvent = MotionEvent(
+                    0,
+                    ACTION_HOVER_EXIT,
+                    1,
+                    0,
+                    arrayOf(
+                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
+                    ),
+                    arrayOf(
+                        PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y)
+                    )
+                )
+
+                // Press on Box 1
+                val downMotionEvent = MotionEvent(
+                    0,
+                    ACTION_DOWN,
+                    1,
+                    0,
+                    arrayOf(
+                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
+                    ),
+                    arrayOf(
+                        PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y)
+                    )
+                )
+
+                val androidComposeView =
+                    findAndroidComposeView(container) as AndroidComposeView
+
+                // Execute events
+                androidComposeView.dispatchHoverEvent(exitMotionEvent)
+                androidComposeView.dispatchTouchEvent(downMotionEvent)
+            }
+
+            // In Compose, a hover exit MotionEvent is ignored if it is quickly followed
+            // by a press.
+            rule.runOnUiThread {
+                assertThat(enterBox1).isTrue()
+                assertThat(pressBox1).isTrue()
+                assertThat(eventsThatShouldNotTrigger).isFalse()
+                assertThat(pointerEvent).isNotNull()
+            }
+
+            // Release on Box 1
+            pointerEvent = null // Reset before each event
+            dispatchTouchEvent(ACTION_UP, box1LayoutCoordinates!!)
+
+            rule.runOnUiThread {
+                assertThat(enterBox1).isTrue()
+                assertThat(pressBox1).isFalse()
+                assertThat(eventsThatShouldNotTrigger).isFalse()
+                assertThat(pointerEvent).isNotNull()
+            }
+        }
+
+        rule.runOnUiThread {
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
     @Test
     fun dispatchHoverMove() {
         var layoutCoordinates: LayoutCoordinates? = null
@@ -2009,6 +2374,10 @@
         dispatchStylusEvents(coords, Offset.Zero, ACTION_HOVER_ENTER)
         dispatchStylusEvents(coords, Offset.Zero, ACTION_HOVER_EXIT)
 
+        // Hover exit events in Compose are always delayed two frames to ensure Compose does not
+        // trigger them if they are followed by a press in the next frame. This accounts for that.
+        rule.waitForFutureFrame(2)
+
         rule.runOnUiThread {
             assertThat(eventLog).hasSize(2)
             assertThat(eventLog[0].type).isEqualTo(PointerEventType.Enter)
@@ -2094,7 +2463,6 @@
         }
     }
 
-    // TODO (jjw): Another option
     @Test
     fun syntheticEventSentAfterUp() {
         val eventLog = mutableListOf<PointerEvent>()
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt
new file mode 100644
index 0000000..66eb9a1
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt
@@ -0,0 +1,645 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.layout
+
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.Modifier.Node
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.LayoutCoordinatesStub
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import kotlin.test.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ApproachLayoutTest {
+    @get:Rule
+    val rule = createAndroidComposeRule<ComponentActivity>()
+
+    @get:Rule
+    val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
+
+    // Test that measurement approach has no effect on parent or child when
+    // isMeasurementApproachComplete returns true
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun toggleIsMeasurementApproachComplete() {
+        var isComplete by mutableStateOf(true)
+        var parentLookaheadSize = IntSize(-1, -1)
+        var childLookaheadConstraints: Constraints? = null
+        var childLookaheadSize = IntSize(-1, -1)
+        // This fraction change triggers a lookahead pass, which will be required to
+        // do a `isMeasurementApproachComplete` after its prior completion.
+        var fraction by mutableStateOf(0.5f)
+        var lookaheadPositionInParent = androidx.compose.ui.geometry.Offset(Float.NaN, Float.NaN)
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                Box(Modifier
+                    .layout { measurable, constraints ->
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                if (isLookingAhead) {
+                                    parentLookaheadSize = IntSize(width, height)
+                                } else {
+                                    // Verify the approach size the same as lookahead when approach
+                                    // completes, and that they differ before completion.
+                                    if (isComplete) {
+                                        assertEquals(parentLookaheadSize.width, width)
+                                        assertEquals(parentLookaheadSize.height, height)
+                                    } else {
+                                        assertNotEquals(parentLookaheadSize.width, width)
+                                        assertNotEquals(parentLookaheadSize.height, height)
+                                    }
+                                }
+                                layout(width, height) { place(0, 0) }
+                            }
+                    }
+                    .approachLayout(
+                        isMeasurementApproachComplete = { isComplete }
+                    ) { measurable, _ ->
+                        // Intentionally use different constraints, placement and report different
+                        // measure result than lookahead, to verify that they have no effect on
+                        // the layout after completion.
+                        val constraints = Constraints.fixed(
+                            lookaheadSize.width - 20, lookaheadSize.height - 20
+                        )
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                layout(lookaheadSize.width - 20, lookaheadSize.height - 20) {
+                                    place(20, 20)
+                                }
+                            }
+                    }
+                    .layout { measurable, constraints ->
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                if (isLookingAhead) {
+                                    childLookaheadConstraints = constraints
+                                    childLookaheadSize = IntSize(width, height)
+                                } else {
+                                    if (isComplete) {
+                                        assertEquals(childLookaheadSize.width, width)
+                                        assertEquals(childLookaheadSize.height, height)
+                                        assertEquals(childLookaheadConstraints, constraints)
+                                    } else {
+                                        assertNotEquals(childLookaheadSize.width, width)
+                                        assertNotEquals(childLookaheadSize.height, height)
+                                        assertNotEquals(childLookaheadConstraints, constraints)
+                                    }
+                                }
+                                layout(width, height) {
+                                    if (isLookingAhead) {
+                                        lookaheadPositionInParent =
+                                            coordinates?.positionInParent()
+                                                ?: lookaheadPositionInParent
+                                    } else {
+                                        coordinates?.let {
+                                            if (isComplete) {
+                                                assertEquals(
+                                                    lookaheadPositionInParent,
+                                                    it.positionInParent()
+                                                )
+                                            } else {
+                                                assertNotEquals(
+                                                    lookaheadPositionInParent,
+                                                    it.positionInParent()
+                                                )
+                                            }
+                                        }
+                                    }
+                                    place(0, 0)
+                                }
+                            }
+                    }
+                    .fillMaxSize()
+                )
+            }
+        }
+        rule.runOnIdle {
+            fraction = 0.75f
+            isComplete = false
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            isComplete = true
+        }
+    }
+
+    // Test that placement approach has no effect when _both measure & place approaches_ complete
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun toggleIsPlacementApproachComplete() {
+        var isMeasurementApproachComplete by mutableStateOf(true)
+        var isPlacementApproachComplete by mutableStateOf(false)
+        var parentLookaheadSize = IntSize(-1, -1)
+        var childLookaheadConstraints: Constraints? = null
+        var childLookaheadSize = IntSize(-1, -1)
+        // This fraction change triggers a lookahead pass, which will be required to
+        // do a `isMeasurementApproachComplete` after its prior completion.
+        var fraction by mutableStateOf(0.5f)
+        var lookaheadPositionInParent = androidx.compose.ui.geometry.Offset(Float.NaN, Float.NaN)
+        var approachPositionInParent = androidx.compose.ui.geometry.Offset(Float.NaN, Float.NaN)
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                Box(Modifier
+                    .layout { measurable, constraints ->
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                if (isLookingAhead) {
+                                    parentLookaheadSize = IntSize(width, height)
+                                } else {
+                                    // Verify the approach size the same as lookahead when approach
+                                    // completes, and that they differ before completion.
+                                    if (isMeasurementApproachComplete) {
+                                        assertEquals(parentLookaheadSize.width, width)
+                                        assertEquals(parentLookaheadSize.height, height)
+                                    } else {
+                                        assertNotEquals(parentLookaheadSize.width, width)
+                                        assertNotEquals(parentLookaheadSize.height, height)
+                                    }
+                                }
+                                layout(width, height) { place(0, 0) }
+                            }
+                    }
+                    .approachLayout(
+                        isMeasurementApproachComplete = { isMeasurementApproachComplete },
+                        isPlacementApproachComplete = { isPlacementApproachComplete }
+                    ) { measurable, _ ->
+                        // Intentionally use different constraints, placement and report different
+                        // measure result than lookahead, to verify that they have no effect on
+                        // the layout after completion.
+                        val constraints = Constraints.fixed(
+                            lookaheadSize.width - 20, lookaheadSize.height - 20
+                        )
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                layout(lookaheadSize.width - 20, lookaheadSize.height - 20) {
+                                    place(20, 20)
+                                }
+                            }
+                    }
+                    .layout { measurable, constraints ->
+                        measurable
+                            .measure(constraints)
+                            .run {
+                                if (isLookingAhead) {
+                                    childLookaheadConstraints = constraints
+                                    childLookaheadSize = IntSize(width, height)
+                                } else {
+                                    if (isMeasurementApproachComplete) {
+                                        assertEquals(childLookaheadSize.width, width)
+                                        assertEquals(childLookaheadSize.height, height)
+                                        assertEquals(childLookaheadConstraints, constraints)
+                                    } else {
+                                        assertNotEquals(childLookaheadSize.width, width)
+                                        assertNotEquals(childLookaheadSize.height, height)
+                                        assertNotEquals(childLookaheadConstraints, constraints)
+                                    }
+                                }
+                                layout(width, height) {
+                                    if (isLookingAhead) {
+                                        lookaheadPositionInParent =
+                                            coordinates?.positionInParent()
+                                                ?: lookaheadPositionInParent
+                                    } else {
+                                        coordinates?.let {
+                                            approachPositionInParent =
+                                                coordinates?.positionInParent()
+                                                    ?: approachPositionInParent
+                                        }
+                                    }
+                                    place(0, 0)
+                                }
+                            }
+                    }
+                    .fillMaxSize()
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertNotEquals(Offset(Float.NaN, Float.NaN), lookaheadPositionInParent)
+            assertNotEquals(Offset(Float.NaN, Float.NaN), approachPositionInParent)
+            // Initial condition: placement incomplete, measurement complete
+            assertNotEquals(
+                lookaheadPositionInParent,
+                approachPositionInParent
+            )
+        }
+
+        rule.runOnIdle {
+            fraction = 0.75f
+            // Reverse placement and measurement completion, expect placement to be re-run
+            isPlacementApproachComplete = true
+            isMeasurementApproachComplete = false
+        }
+        rule.runOnIdle {
+            // Updated condition: placement complete, measurement incomplete
+            assertNotEquals(
+                lookaheadPositionInParent,
+                approachPositionInParent
+            )
+        }
+        rule.runOnIdle {
+            isMeasurementApproachComplete = true
+        }
+        rule.runOnIdle {
+            // Both measurement and placement are complete.
+            assertEquals(
+                lookaheadPositionInParent,
+                approachPositionInParent
+            )
+        }
+
+        rule.runOnIdle {
+            fraction = 0.85f
+            // Reverse placement and measurement completion, expect placement to be re-run
+            isPlacementApproachComplete = true
+            isMeasurementApproachComplete = false
+        }
+        rule.runOnIdle {
+            // Updated condition: placement complete, measurement incomplete
+            assertNotEquals(
+                lookaheadPositionInParent,
+                approachPositionInParent
+            )
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun activeParentNestedApproachNode() {
+        var parentMeasureApproachComplete by mutableStateOf(false)
+        var childMeasureApproachComplete by mutableStateOf(true)
+        var parentLookaheadConstraints: Constraints? = null
+        var parentApproachConstraints: Constraints? = null
+        var parentLookaheadSize: IntSize? = null
+        var parentApproachSize: IntSize? = null
+
+        var childLookaheadConstraints: Constraints? = null
+        var childApproachConstraints: Constraints? = null
+        var childLookaheadSize: IntSize? = null
+        var childApproachSize: IntSize? = null
+        val parentApproachNode = object : TestApproachLayoutModifierNode() {
+            override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+                return parentMeasureApproachComplete
+            }
+
+            @ExperimentalComposeUiApi
+            override fun ApproachMeasureScope.approachMeasure(
+                measurable: Measurable,
+                constraints: Constraints
+            ): MeasureResult {
+                return measurable.measure(Constraints.fixed(600, 600)).run {
+                    layout(600, 600) {
+                        place(0, 0)
+                    }
+                }
+            }
+        }
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                Box(
+                    Modifier
+                        .fillMaxSize(
+                            // This forces a lookahead pass when approach complete is changed,
+                            // because in the future we will only permit complete becoming true
+                            // after a lookahead pass.
+                            if (childMeasureApproachComplete)
+                                1f
+                            else if (parentMeasureApproachComplete)
+                                0.9f
+                            else
+                                0.95f
+                        )
+                        .requiredSize(700.dp, 700.dp)
+                        .then(
+                            TestApproachElement(parentApproachNode)
+                        ),
+                    propagateMinConstraints = true
+                ) {
+                    Box(
+                        Modifier
+                            .layout { measurable, constraints ->
+                                if (isLookingAhead) {
+                                    parentLookaheadConstraints = constraints
+                                } else {
+                                    parentApproachConstraints = constraints
+                                }
+                                measurable
+                                    .measure(constraints)
+                                    .run {
+                                        if (isLookingAhead) {
+                                            parentLookaheadSize = IntSize(width, height)
+                                        } else {
+                                            parentApproachSize = IntSize(width, height)
+                                        }
+                                        layout(width, height) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }
+                            .approachLayout({ childMeasureApproachComplete }) { m, _ ->
+                                m
+                                    .measure(Constraints.fixed(500, 500))
+                                    .run {
+                                        layout(500, 500) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }
+                            .layout { measurable, constraints ->
+                                if (isLookingAhead) {
+                                    childLookaheadConstraints = constraints
+                                } else {
+                                    childApproachConstraints = constraints
+                                }
+                                measurable
+                                    .measure(constraints)
+                                    .run {
+                                        if (isLookingAhead) {
+                                            childLookaheadSize = IntSize(width, height)
+                                        } else {
+                                            childApproachSize = IntSize(width, height)
+                                        }
+                                        layout(width, height) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }) {
+                        Box(Modifier.fillMaxSize())
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            assertEquals(IntSize(700, 700), parentLookaheadSize)
+            assertEquals(IntSize(700, 700), childLookaheadSize)
+            assertEquals(Constraints.fixed(700, 700), parentLookaheadConstraints)
+            assertEquals(Constraints.fixed(700, 700), childLookaheadConstraints)
+
+            assertEquals(IntSize(500, 500), childApproachSize)
+            assertEquals(IntSize(600, 600), parentApproachSize)
+            assertEquals(Constraints.fixed(600, 600), parentApproachConstraints)
+            assertEquals(Constraints.fixed(500, 500), childApproachConstraints)
+        }
+
+        rule.runOnIdle {
+            childMeasureApproachComplete = false
+            parentMeasureApproachComplete = false
+        }
+
+        rule.runOnIdle {
+            assertEquals(IntSize(700, 700), parentLookaheadSize)
+            assertEquals(IntSize(700, 700), childLookaheadSize)
+            assertEquals(Constraints.fixed(700, 700), parentLookaheadConstraints)
+            assertEquals(Constraints.fixed(700, 700), childLookaheadConstraints)
+
+            assertEquals(IntSize(500, 500), childApproachSize)
+            assertEquals(IntSize(600, 600), parentApproachSize)
+            assertEquals(Constraints.fixed(600, 600), parentApproachConstraints)
+            assertEquals(Constraints.fixed(500, 500), childApproachConstraints)
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun activeChildNestedApproachNode() {
+        var parentMeasureApproachComplete by mutableStateOf(true)
+        var childMeasureApproachComplete by mutableStateOf(false)
+        var parentLookaheadConstraints: Constraints? = null
+        var parentApproachConstraints: Constraints? = null
+        var parentLookaheadSize: IntSize? = null
+        var parentApproachSize: IntSize? = null
+
+        var childLookaheadConstraints: Constraints? = null
+        var childApproachConstraints: Constraints? = null
+        var childLookaheadSize: IntSize? = null
+        var childApproachSize: IntSize? = null
+        val parentApproachNode = object : TestApproachLayoutModifierNode() {
+            override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+                return parentMeasureApproachComplete
+            }
+
+            @ExperimentalComposeUiApi
+            override fun ApproachMeasureScope.approachMeasure(
+                measurable: Measurable,
+                constraints: Constraints
+            ): MeasureResult {
+                return measurable.measure(Constraints.fixed(600, 600)).run {
+                    layout(600, 600) {
+                        place(0, 0)
+                    }
+                }
+            }
+        }
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                Box(
+                    Modifier
+                        .fillMaxSize(
+                            // This forces a lookahead pass when approach complete is changed,
+                            // because in the future we will only permit complete becoming true
+                            // after a lookahead pass.
+                            if (childMeasureApproachComplete)
+                                1f
+                            else if (parentMeasureApproachComplete)
+                                0.9f
+                            else
+                                0.95f
+                        )
+                        .requiredSize(700.dp, 700.dp)
+                        .then(
+                            TestApproachElement(parentApproachNode)
+                        ),
+                    propagateMinConstraints = true
+                ) {
+                    Box(
+                        Modifier
+                            .layout { measurable, constraints ->
+                                if (isLookingAhead) {
+                                    parentLookaheadConstraints = constraints
+                                } else {
+                                    parentApproachConstraints = constraints
+                                }
+                                measurable
+                                    .measure(constraints)
+                                    .run {
+                                        if (isLookingAhead) {
+                                            parentLookaheadSize = IntSize(width, height)
+                                        } else {
+                                            parentApproachSize = IntSize(width, height)
+                                        }
+                                        layout(width, height) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }
+                            .approachLayout({ childMeasureApproachComplete }) { m, _ ->
+                                m
+                                    .measure(Constraints.fixed(500, 500))
+                                    .run {
+                                        layout(500, 500) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }
+                            .layout { measurable, constraints ->
+                                if (isLookingAhead) {
+                                    childLookaheadConstraints = constraints
+                                } else {
+                                    childApproachConstraints = constraints
+                                }
+                                measurable
+                                    .measure(constraints)
+                                    .run {
+                                        if (isLookingAhead) {
+                                            childLookaheadSize = IntSize(width, height)
+                                        } else {
+                                            childApproachSize = IntSize(width, height)
+                                        }
+                                        layout(width, height) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }) {
+                        Box(Modifier.fillMaxSize())
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            // Child approach is active, parent completed
+            assertEquals(IntSize(700, 700), parentLookaheadSize)
+            assertEquals(IntSize(700, 700), childLookaheadSize)
+            assertEquals(Constraints.fixed(700, 700), parentLookaheadConstraints)
+            assertEquals(Constraints.fixed(700, 700), childLookaheadConstraints)
+
+            assertEquals(IntSize(500, 500), childApproachSize)
+            assertEquals(IntSize(700, 700), parentApproachSize)
+            assertEquals(Constraints.fixed(700, 700), parentApproachConstraints)
+            assertEquals(Constraints.fixed(500, 500), childApproachConstraints)
+        }
+
+        rule.runOnIdle {
+            childMeasureApproachComplete = false
+            parentMeasureApproachComplete = false
+        }
+
+        rule.runOnIdle {
+            assertEquals(IntSize(700, 700), parentLookaheadSize)
+            assertEquals(IntSize(700, 700), childLookaheadSize)
+            assertEquals(Constraints.fixed(700, 700), parentLookaheadConstraints)
+            assertEquals(Constraints.fixed(700, 700), childLookaheadConstraints)
+
+            assertEquals(IntSize(500, 500), childApproachSize)
+            assertEquals(IntSize(600, 600), parentApproachSize)
+            assertEquals(Constraints.fixed(600, 600), parentApproachConstraints)
+            assertEquals(Constraints.fixed(500, 500), childApproachConstraints)
+        }
+    }
+
+    @Test
+    fun testDefaultPlacementApproachComplete() {
+        var measurementComplete = true
+        val node = object : ApproachLayoutModifierNode {
+            override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+                return measurementComplete
+            }
+
+            @ExperimentalComposeUiApi
+            override fun ApproachMeasureScope.approachMeasure(
+                measurable: Measurable,
+                constraints: Constraints
+            ): MeasureResult {
+                return measurable.measure(constraints).run {
+                    layout(width, height) {
+                        place(0, 0)
+                    }
+                }
+            }
+
+            override val node: Node = object : Node() {}
+        }
+
+        assertEquals(true, node.isMeasurementApproachComplete(IntSize.Zero))
+        with(TestPlacementScope()) {
+            with(node) {
+                isPlacementApproachComplete(LayoutCoordinatesStub())
+            }
+        }.also {
+            assertEquals(true, it)
+        }
+
+        measurementComplete = false
+        assertEquals(false, node.isMeasurementApproachComplete(IntSize.Zero))
+        with(TestPlacementScope()) {
+            with(node) {
+                isPlacementApproachComplete(LayoutCoordinatesStub())
+            }
+        }.also {
+            assertEquals(true, it)
+        }
+    }
+
+    private class TestPlacementScope : Placeable.PlacementScope() {
+        override val parentWidth: Int
+            get() = TODO("Not yet implemented")
+        override val parentLayoutDirection: LayoutDirection
+            get() = TODO("Not yet implemented")
+    }
+
+    private data class TestApproachElement(
+        var approachNode: TestApproachLayoutModifierNode
+    ) : ModifierNodeElement<TestApproachLayoutModifierNode>() {
+        override fun create(): TestApproachLayoutModifierNode {
+            return approachNode
+        }
+
+        override fun update(node: TestApproachLayoutModifierNode) {
+        }
+    }
+
+    abstract class TestApproachLayoutModifierNode : Node(), ApproachLayoutModifierNode
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 4de0f4c..7b64fa9 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowColumn
 import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.FlowRowOverflow
 import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -63,6 +64,7 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -99,9 +101,10 @@
 import junit.framework.TestCase.assertTrue
 import kotlin.math.roundToInt
 import kotlin.random.Random
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
 import kotlin.test.assertNotNull
 import kotlinx.coroutines.launch
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -117,6 +120,18 @@
     @get:Rule
     val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
 
+    private var testFinished = true
+
+    @BeforeTest
+    fun setup() {
+        testFinished = false
+    }
+
+    @AfterTest
+    fun cleanUp() {
+        testFinished = true
+    }
+
     @Test
     fun randomLookaheadPlacementOrder() {
         val nodeList = List(10) { node() }
@@ -191,17 +206,30 @@
                 SubcomposeLayout(
                     Modifier
                         .requiredSize(targetSize.width.dp, targetSize.height.dp)
-                        .intermediateLayout { measurable, _ ->
-                            val intermediateConstraints = Constraints.fixed(
-                                expectedSizes[iteration].width,
-                                expectedSizes[iteration].height
-                            )
-                            measurable
-                                .measure(intermediateConstraints)
-                                .run {
-                                    layout(width, height) { place(0, 0) }
-                                }
-                        }) { constraints ->
+                        .createIntermediateElement(object : TestApproachLayoutModifierNode() {
+                            override fun isMeasurementApproachComplete(
+                                lookaheadSize: IntSize
+                            ): Boolean {
+                                return iteration > 6
+                            }
+
+                            @ExperimentalComposeUiApi
+                            override fun ApproachMeasureScope.approachMeasure(
+                                measurable: Measurable,
+                                constraints: Constraints
+                            ): MeasureResult {
+                                val intermediateConstraints = Constraints.fixed(
+                                    expectedSizes[iteration].width,
+                                    expectedSizes[iteration].height
+                                )
+                                return measurable
+                                    .measure(intermediateConstraints)
+                                    .run {
+                                        layout(width, height) { place(0, 0) }
+                                    }
+                            }
+                        })
+                ) { constraints ->
                     val placeable = subcompose(0) {
                         Box(Modifier.fillMaxSize())
                     }[0].measure(constraints)
@@ -334,9 +362,16 @@
 
     private fun Modifier.animateSize(): Modifier = composed {
         var anim: Animatable<IntSize, AnimationVector2D>? by remember { mutableStateOf(null) }
-        this.intermediateLayout { measurable, _ ->
+        val scope = rememberCoroutineScope()
+        this.approachLayout(
+            { lookaheadSize ->
+                val animation = anim ?: Animatable(lookaheadSize, IntSize.VectorConverter)
+                anim = animation
+                animation.targetValue != lookaheadSize || animation.isRunning
+            }
+        ) { measurable, _ ->
             anim = anim?.apply {
-                launch {
+                scope.launch {
                     if (lookaheadSize != targetValue) {
                         animateTo(lookaheadSize, tween(200))
                     }
@@ -386,12 +421,26 @@
                         Modifier
                             .padding(top = 100.dp)
                             .fillMaxSize()
-                            .intermediateLayout { measurable, constraints ->
-                                measureWithLambdas(
-                                    preMeasure = { parentMeasure = ++counter },
-                                    prePlacement = { parentPlace = ++counter }
-                                ).invoke(this, measurable, constraints)
-                            }
+                            .createIntermediateElement(object :
+                                TestApproachLayoutModifierNode() {
+
+                                override fun isMeasurementApproachComplete(
+                                    lookaheadSize: IntSize
+                                ): Boolean {
+                                    return rootPostPlace >= 12
+                                }
+
+                                @ExperimentalComposeUiApi
+                                override fun ApproachMeasureScope.approachMeasure(
+                                    measurable: Measurable,
+                                    constraints: Constraints
+                                ): MeasureResult {
+                                    return measureWithLambdas(
+                                        preMeasure = { parentMeasure = ++counter },
+                                        prePlacement = { parentPlace = ++counter }
+                                    ).invoke(this, measurable, constraints)
+                                }
+                            })
                             .layout(
                                 measureWithLambdas(
                                     preMeasure = {
@@ -415,12 +464,27 @@
                                     Modifier
                                         .size(100.dp)
                                         .background(Color.Red)
-                                        .intermediateLayout { measurable, constraints ->
-                                            measureWithLambdas(
-                                                preMeasure = { childMeasure = ++counter },
-                                                prePlacement = { childPlace = ++counter }
-                                            ).invoke(this, measurable, constraints)
-                                        }
+                                        .createIntermediateElement(object :
+                                            TestApproachLayoutModifierNode() {
+
+                                            override fun isMeasurementApproachComplete(
+                                                lookaheadSize: IntSize
+                                            ): Boolean {
+                                                return rootPostPlace >= 12
+                                            }
+
+                                            @ExperimentalComposeUiApi
+                                            override fun
+                                                ApproachMeasureScope.approachMeasure(
+                                                measurable: Measurable,
+                                                constraints: Constraints
+                                            ): MeasureResult {
+                                                return measureWithLambdas(
+                                                    preMeasure = { childMeasure = ++counter },
+                                                    prePlacement = { childPlace = ++counter }
+                                                ).invoke(this, measurable, constraints)
+                                            }
+                                        })
                                         .layout(
                                             measure = measureWithLambdas(
                                                 preMeasure = {
@@ -487,7 +551,9 @@
                         ) {
                             MyLookaheadLayout {
                                 Box(modifier = Modifier
-                                    .intermediateLayout { measurable, constraints ->
+                                    .approachLayout(
+                                        isMeasurementApproachComplete = { scaleFactor > 3f }
+                                    ) { measurable, constraints ->
                                         assertEquals(width, lookaheadSize.width)
                                         assertEquals(height, lookaheadSize.height)
                                         val placeable = measurable.measure(constraints)
@@ -1339,7 +1405,8 @@
                     modifier = Modifier.fillMaxSize(),
                     horizontalArrangement = Arrangement.Center,
                     verticalArrangement = Arrangement.Center,
-                    maxItemsInEachRow = 3
+                    maxItemsInEachRow = 3,
+                    overflow = FlowRowOverflow.Visible
                 ) {
                     Box(
                         modifier = Modifier
@@ -1559,57 +1626,59 @@
         var boxId by mutableStateOf(1)
         rule.setContent {
             CompositionLocalProvider(LocalDensity.provides(Density(1f))) {
-                val movableContent = remember {
-                    movableContentOf {
-                        Box(
-                            Modifier
-                                .intermediateLayout { measurable, constraints ->
-                                    measurable
-                                        .measure(constraints)
-                                        .run {
-                                            layout(width, height) {
-                                                coordinates?.let {
-                                                    positionInScope =
-                                                        lookaheadScopeCoordinates
-                                                            .localLookaheadPositionOf(
-                                                                it
-                                                            )
-                                                            .round()
+                LookaheadScope {
+                    val movableContent = remember {
+                        movableContentOf {
+                            Box(
+                                Modifier
+                                    .intermediateLayout { measurable, constraints ->
+                                        measurable
+                                            .measure(constraints)
+                                            .run {
+                                                layout(width, height) {
+                                                    coordinates?.let {
+                                                        positionInScope =
+                                                            lookaheadScopeCoordinates
+                                                                .localLookaheadPositionOf(
+                                                                    it
+                                                                )
+                                                                .round()
+                                                    }
                                                 }
                                             }
-                                        }
-                                }
-                                .size(200.dp))
+                                    }
+                                    .size(200.dp))
+                        }
                     }
-                }
-                Box {
-                    LookaheadScope {
-                        Box(Modifier.offset(100.dp, 5.dp)) {
-                            if (boxId == 1) {
-                                movableContent()
+                    Box {
+                        LookaheadScope {
+                            Box(Modifier.offset(100.dp, 5.dp)) {
+                                if (boxId == 1) {
+                                    movableContent()
+                                }
                             }
                         }
                     }
-                }
-                Box(Modifier.offset(40.dp, 200.dp)) {
-                    if (boxId == 2) {
-                        movableContent()
+                    Box(Modifier.offset(40.dp, 200.dp)) {
+                        if (boxId == 2) {
+                            movableContent()
+                        }
                     }
-                }
-                Box(Modifier
-                    .offset(50.dp, 50.dp)
-                    .intermediateLayout { measurable, constraints ->
-                        measurable
-                            .measure(constraints)
-                            .run {
-                                layout(width, height) {
-                                    place(0, 0)
+                    Box(Modifier
+                        .offset(50.dp, 50.dp)
+                        .intermediateLayout { measurable, constraints ->
+                            measurable
+                                .measure(constraints)
+                                .run {
+                                    layout(width, height) {
+                                        place(0, 0)
+                                    }
                                 }
-                            }
-                    }
-                    .offset(60.dp, 60.dp)) {
-                    if (boxId == 3) {
-                        movableContent()
+                        }
+                        .offset(60.dp, 60.dp)) {
+                        if (boxId == 3) {
+                            movableContent()
+                        }
                     }
                 }
             }
@@ -1618,13 +1687,11 @@
         assertEquals(IntOffset(100, 5), positionInScope)
         boxId++
         rule.waitForIdle()
-        // Expect no offset when moving intermediateLayout out of LookaheadScope, as the implicitly
-        // created lookahead scope will have the same coordinates as intermediateLayout
-        assertEquals(IntOffset(0, 0), positionInScope)
+        assertEquals(IntOffset(40, 200), positionInScope)
         boxId++
         rule.waitForIdle()
         // Expect the lookaheadScope to be created by the ancestor intermediateLayoutModifier
-        assertEquals(IntOffset(60, 60), positionInScope)
+        assertEquals(IntOffset(110, 110), positionInScope)
     }
 
     @Test
@@ -1719,59 +1786,6 @@
     }
 
     @Test
-    fun lookaheadScopeInImplicitScope() {
-        rule.setContent {
-            Box(Modifier.offset(20.dp, 30.dp)) {
-                Box(
-                    Modifier
-                        .offset(50.dp, 25.dp)
-                        .intermediateLayout { measurable, constraints ->
-                            measureWithLambdas(prePlacement = {
-                                assertEquals(
-                                    coordinates!!,
-                                    lookaheadScopeCoordinates
-                                )
-                            })(measurable, constraints)
-                        }
-                        .offset(15.dp, 20.dp)
-                ) {
-                    LookaheadScope {
-                        val explicitLookaheadScope = this
-                        Box(
-                            Modifier
-                                .intermediateLayout { measurable, constraints ->
-                                    measureWithLambdas(prePlacement = {
-                                        val innerLookaheadCoords =
-                                            with(explicitLookaheadScope) {
-                                                lookaheadScopeCoordinates
-                                            }
-                                        assertEquals(
-                                            innerLookaheadCoords,
-                                            lookaheadScopeCoordinates
-                                        )
-                                    })(measurable, constraints)
-                                }
-                                .size(50.dp)
-                                .intermediateLayout { measurable, constraints ->
-                                    measureWithLambdas(prePlacement = {
-                                        val innerLookaheadCoords =
-                                            with(explicitLookaheadScope) {
-                                                lookaheadScopeCoordinates
-                                            }
-                                        assertEquals(
-                                            innerLookaheadCoords,
-                                            lookaheadScopeCoordinates
-                                        )
-                                    })(measurable, constraints)
-                                }
-                        )
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
     fun nestedVirtualNodeFromLookaheadScope() {
         var small by mutableStateOf(true)
         fun size1(): Int = if (small) 20 else 50
@@ -1916,36 +1930,21 @@
             }
         }
 
-    @Ignore("b/276805422")
     @Test
     fun subcomposeLayoutInLookahead() {
         val expectedConstraints = mutableStateListOf(
-            Constraints.fixed(0, 0),
-            Constraints.fixed(0, 0),
-            Constraints.fixed(0, 0)
+            Constraints.fixed(100, 800),
+            Constraints.fixed(300, 400),
+            Constraints.fixed(1000, 200)
         )
         val expectedPlacements = mutableStateListOf(
+            IntOffset(-50, 1200),
             IntOffset.Zero,
-            IntOffset.Zero,
-            IntOffset.Zero
+            IntOffset(800, -200)
         )
 
-        fun generateRandomConstraintsAndPlacements() {
-            repeat(3) {
-                expectedConstraints[it] = Constraints.fixed(
-                    Random.nextInt(100, 1000),
-                    Random.nextInt(100, 1000)
-                )
-                expectedPlacements[it] = IntOffset(
-                    Random.nextInt(-200, 1200),
-                    Random.nextInt(-200, 1200)
-                )
-            }
-        }
-
         val actualConstraints = arrayOfNulls<Constraints?>(3)
         val actualPlacements = arrayOfNulls<IntOffset?>(3)
-        generateRandomConstraintsAndPlacements()
         rule.setContent {
             LookaheadScope {
                 SubcomposeLayout {
@@ -1983,16 +1982,13 @@
             }
         }
 
-        repeat(5) {
-            rule.runOnIdle {
-                repeat(expectedPlacements.size) {
-                    assertEquals(expectedConstraints[it], actualConstraints[it])
-                }
-                repeat(expectedPlacements.size) {
-                    assertEquals(expectedPlacements[it], actualPlacements[it])
-                }
+        rule.runOnIdle {
+            repeat(expectedPlacements.size) {
+                assertEquals(expectedConstraints[it], actualConstraints[it])
             }
-            generateRandomConstraintsAndPlacements()
+            repeat(expectedPlacements.size) {
+                assertEquals(expectedPlacements[it], actualPlacements[it])
+            }
         }
     }
 
@@ -2539,15 +2535,25 @@
         var onPlacedCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
         with(scope) {
             this@composed
-                .intermediateLayout { measurable, constraints ->
-                    assertFalse(isLookingAhead)
-                    lookaheadSize = this.lookaheadSize
-                    measureWithLambdas(
-                        prePlacement = {
-                            lookaheadLayoutCoordinates = lookaheadScopeCoordinates
-                        }
-                    ).invoke(this, measurable, constraints)
-                }
+                .createIntermediateElement(object : TestApproachLayoutModifierNode() {
+                    override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+                        return true
+                    }
+
+                    @ExperimentalComposeUiApi
+                    override fun ApproachMeasureScope.approachMeasure(
+                        measurable: Measurable,
+                        constraints: Constraints
+                    ): MeasureResult {
+                        assertFalse(isLookingAhead)
+                        lookaheadSize = this.lookaheadSize
+                        return measureWithLambdas(
+                            prePlacement = {
+                                lookaheadLayoutCoordinates = lookaheadScopeCoordinates
+                            }
+                        ).invoke(this, measurable, constraints)
+                    }
+                })
                 .onPlaced { it ->
                     onPlacedCoordinates = it
                 }
@@ -2639,4 +2645,27 @@
             postPlacement()
         }
     }
+
+    internal fun Modifier.intermediateLayout(
+        measure: ApproachMeasureScope.(
+            measurable: Measurable,
+            constraints: Constraints,
+        ) -> MeasureResult,
+    ): Modifier = approachLayout({ testFinished }, approachMeasure = measure)
 }
+
+private fun Modifier.createIntermediateElement(node: TestApproachLayoutModifierNode): Modifier =
+    this then TestIntermediateElement(node)
+
+private data class TestIntermediateElement(
+    var intermediateNode: TestApproachLayoutModifierNode
+) : ModifierNodeElement<TestApproachLayoutModifierNode>() {
+    override fun create(): TestApproachLayoutModifierNode {
+        return intermediateNode
+    }
+
+    override fun update(node: TestApproachLayoutModifierNode) {
+    }
+}
+
+abstract class TestApproachLayoutModifierNode : Modifier.Node(), ApproachLayoutModifierNode
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGloballyPositionedTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGloballyPositionedTest.kt
index e49bbc4a..f80881e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGloballyPositionedTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGloballyPositionedTest.kt
@@ -269,6 +269,179 @@
     }
 
     @Test
+    fun globalPositionedModifierUpdateDoesNotInvalidateLayout() {
+        var lambda1Called = false
+        var lambda2Called = false
+        var layoutCalled = false
+        var placementCalled = false
+        val lambda1: (LayoutCoordinates) -> Unit = { lambda1Called = true }
+        val lambda2: (LayoutCoordinates) -> Unit = { lambda2Called = true }
+
+        val changeLambda = mutableStateOf(true)
+
+        val layoutModifier = Modifier.layout { measurable, constraints ->
+            layoutCalled = true
+            val placeable = measurable.measure(constraints)
+            layout(placeable.width, placeable.height) {
+                placementCalled = true
+                placeable.place(0, 0)
+            }
+        }
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .then(layoutModifier)
+                    .size(10.dp)
+                    .onGloballyPositioned(if (changeLambda.value) lambda1 else lambda2)
+            )
+        }
+
+        rule.runOnIdle {
+            assertTrue(lambda1Called)
+            assertTrue(layoutCalled)
+            assertTrue(placementCalled)
+            assertFalse(lambda2Called)
+        }
+
+        lambda1Called = false
+        lambda2Called = false
+        layoutCalled = false
+        placementCalled = false
+        changeLambda.value = false
+
+        rule.runOnIdle {
+            assertTrue(lambda2Called)
+            assertFalse(lambda1Called)
+            assertFalse(layoutCalled)
+            assertFalse(placementCalled)
+        }
+    }
+
+    @Test
+    fun callbacksAreCalledOnlyOnceWhenLambdaChangesAndLayoutChanges() {
+        var lambda1Called = false
+        val lambda1: (LayoutCoordinates) -> Unit = {
+            assert(!lambda1Called)
+            lambda1Called = true
+        }
+
+        var lambda2Called = false
+        val lambda2: (LayoutCoordinates) -> Unit = {
+            assert(!lambda2Called)
+            lambda2Called = true
+        }
+
+        val changeLambda = mutableStateOf(true)
+        val size = mutableStateOf(100.dp)
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .size(size.value)
+                    .onGloballyPositioned(if (changeLambda.value) lambda1 else lambda2)
+            )
+        }
+
+        rule.runOnIdle {
+            assertTrue(lambda1Called)
+            assertFalse(lambda2Called)
+        }
+
+        lambda1Called = false
+        lambda2Called = false
+        size.value = 120.dp
+        changeLambda.value = false
+
+        rule.runOnIdle {
+            assertTrue(lambda2Called)
+            assertFalse(lambda1Called)
+        }
+    }
+
+    // change layout below callback, callback only gets called ones
+    @Test
+    fun callbacksAreCalledOnlyOnceWhenLayoutBelowItAndLambdaChanged() {
+        var lambda1Called = false
+        val lambda1: (LayoutCoordinates) -> Unit = {
+            assert(!lambda1Called)
+            lambda1Called = true
+        }
+
+        var lambda2Called = false
+        val lambda2: (LayoutCoordinates) -> Unit = {
+            assert(!lambda2Called)
+            lambda2Called = true
+        }
+
+        val changeLambda = mutableStateOf(true)
+        val size = mutableStateOf(10.dp)
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .padding(10.dp)
+                    .onGloballyPositioned(if (changeLambda.value) lambda1 else lambda2)
+                    .padding(size.value)
+                    .size(10.dp)
+            )
+        }
+
+        rule.runOnIdle {
+            assertTrue(lambda1Called)
+            assertFalse(lambda2Called)
+        }
+
+        lambda1Called = false
+        lambda2Called = false
+        size.value = 20.dp
+        changeLambda.value = false
+
+        rule.runOnIdle {
+            assertTrue(lambda2Called)
+            assertFalse(lambda1Called)
+        }
+    }
+
+    @Test
+    fun multipleGloballyPositionedModifiersResultsInOnlySingleCallToLambdas() {
+        var lambda1Called = false
+        val lambda1: (LayoutCoordinates) -> Unit = {
+            assert(!lambda1Called)
+            lambda1Called = true
+        }
+
+        var lambda2Called = false
+        val lambda2: (LayoutCoordinates) -> Unit = {
+            assert(!lambda2Called)
+            lambda2Called = true
+        }
+
+        val changeLambda = mutableStateOf(true)
+        val size = mutableStateOf(100.dp)
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .size(size.value)
+                    .onGloballyPositioned(if (changeLambda.value) lambda1 else lambda2)
+                    .onGloballyPositioned(if (changeLambda.value) lambda2 else lambda1)
+            )
+        }
+
+        rule.runOnIdle {
+            assertTrue(lambda1Called)
+            assertTrue(lambda2Called)
+        }
+
+        lambda1Called = false
+        lambda2Called = false
+        changeLambda.value = false
+
+        rule.runOnIdle {
+            assertTrue(lambda2Called)
+            assertTrue(lambda1Called)
+        }
+    }
+
+    @Test
     fun testPositionInParent() {
         val positionedLatch = CountDownLatch(1)
         var coordinates: LayoutCoordinates? = null
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
index 7fa8477..2e93bb2 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
@@ -832,7 +832,9 @@
                 Layout(content = {
                     Box(
                         Modifier
-                            .intermediateLayout { measurable, constraints ->
+                            .approachLayout({
+                                intermediateLayoutBlockCalls > 20
+                            }) { measurable, constraints ->
                                 val p = measurable.measure(constraints)
                                 layout(p.width, p.height) {
                                     coordinates?.let(coordinatesAction)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
deleted file mode 100644
index e69de29..0000000
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
+++ /dev/null
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireLayoutCoordinatesTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireLayoutCoordinatesTest.kt
new file mode 100644
index 0000000..3d1a48d
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireLayoutCoordinatesTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toOffset
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RequireLayoutCoordinatesTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun requireLayoutCoordinates_throws_whenNotAttached() {
+        lateinit var modifier: TestModifierNode
+        rule.setContent {
+            Box(
+                Modifier
+                    .then(TestModifier { modifier = it })
+                    .size(1.dp)
+            )
+        }
+
+        rule.runOnIdle {
+            assertIs<IllegalStateException>(modifier.coordinatesFromInit.exceptionOrNull())
+        }
+    }
+
+    @Test
+    fun requireLayoutCoordinates_returnsCoordinates_whenAttached() {
+        lateinit var modifier: TestModifierNode
+        rule.setContent {
+            Box(
+                Modifier
+                    .offset(10.dp, 20.dp)
+                    .requiredSize(30.dp, 40.dp)
+                    .then(TestModifier { modifier = it })
+            )
+        }
+
+        rule.runOnIdle {
+            val coordinates = assertNotNull(modifier.requireLayoutCoordinates())
+            assertThat(coordinates.isAttached).isTrue()
+
+            with(rule.density) {
+                assertThat(coordinates.positionInRoot())
+                    .isEqualTo(IntOffset(10.dp.roundToPx(), 20.dp.roundToPx()).toOffset())
+                assertThat(coordinates.size)
+                    .isEqualTo(IntSize(30.dp.roundToPx(), 40.dp.roundToPx()))
+            }
+        }
+    }
+
+    @Test
+    fun requireLayoutCoordinates_returnsCoordinatesFromNearestLayoutModifier() {
+        lateinit var modifier: TestModifierNode
+        rule.setContent {
+            Layout(
+                Modifier
+                    .requiredSize(10.dp)
+                    .then(TestModifier { modifier = it })
+                    .requiredSize(5.dp)
+            ) { _, _ ->
+                layout(2.dp.roundToPx(), 2.dp.roundToPx()) {}
+            }
+        }
+
+        rule.runOnIdle {
+            val coordinates = assertNotNull(modifier.requireLayoutCoordinates())
+
+            with(rule.density) {
+                assertThat(coordinates.size)
+                    .isEqualTo(IntSize(5.dp.roundToPx(), 5.dp.roundToPx()))
+            }
+        }
+    }
+
+    @Test
+    fun requireLayoutCoordinates_throws_afterDetached() {
+        var attachModifier by mutableStateOf(true)
+        lateinit var modifier: TestModifierNode
+        rule.setContent {
+            Box(
+                Modifier
+                    .then(if (attachModifier) TestModifier { modifier = it } else Modifier)
+                    .size(1.dp)
+            )
+        }
+
+        rule.waitUntil("requireLayoutCoordinates returns") {
+            runCatching { modifier.requireLayoutCoordinates() }.isSuccess
+        }
+        attachModifier = false
+
+        rule.runOnIdle {
+            assertFailsWith<IllegalStateException> {
+                modifier.requireLayoutCoordinates()
+            }
+        }
+    }
+
+    private data class TestModifier(
+        val onModifier: (TestModifierNode) -> Unit
+    ) : ModifierNodeElement<TestModifierNode>() {
+        override fun create(): TestModifierNode = TestModifierNode().also(onModifier)
+
+        override fun update(node: TestModifierNode) {}
+    }
+
+    private class TestModifierNode : Modifier.Node() {
+        val coordinatesFromInit = runCatching { requireLayoutCoordinates() }
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/InspectableValueTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/InspectableValueTest.kt
index 802339d..1cf6d5c 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/InspectableValueTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/InspectableValueTest.kt
@@ -45,6 +45,7 @@
         isDebugInspectorInfoEnabled = false
     }
 
+    @Suppress("DEPRECATION")
     fun Modifier.simple(padding: Int, border: Dp) = inspectable(
         debugInspectorInfo {
             name = "simple"
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index 79a7170..312d906 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -134,7 +134,6 @@
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.instanceOf
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -1760,6 +1759,43 @@
         }
     }
 
+    @Test
+    @LargeTest
+    fun androidView_attachingDoesNotCauseRelayout() {
+        lateinit var root: RequestLayoutTrackingFrameLayout
+        lateinit var composeView: ComposeView
+        lateinit var viewInsideCompose: View
+        var showAndroidView by mutableStateOf(false)
+
+        rule.activityRule.scenario.onActivity { activity ->
+            root = RequestLayoutTrackingFrameLayout(activity)
+            composeView = ComposeView(activity)
+            viewInsideCompose = View(activity)
+
+            activity.setContentView(root)
+            root.addView(composeView)
+            composeView.setContent {
+                Box(Modifier.fillMaxSize()) {
+                    if (showAndroidView) {
+                        AndroidView({ viewInsideCompose })
+                    }
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            assertThat(viewInsideCompose.parent).isNull()
+            assertThat(root.requestLayoutCalled).isTrue()
+            root.requestLayoutCalled = false
+            showAndroidView = true
+        }
+
+        rule.runOnIdle {
+            assertThat(viewInsideCompose.parent).isNotNull()
+            assertThat(root.requestLayoutCalled).isFalse()
+        }
+    }
+
     @ExperimentalComposeUiApi
     @Composable
     private inline fun <T : View> ReusableAndroidViewWithLifecycleTracking(
@@ -1874,4 +1910,13 @@
             value,
             displayMetrics
         ).roundToInt()
+
+    private class RequestLayoutTrackingFrameLayout(context: Context) : FrameLayout(context) {
+        var requestLayoutCalled = false
+
+        override fun requestLayout() {
+            super.requestLayout()
+            requestLayoutCalled = true
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropConnectionTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropConnectionTest.kt
index ea089f4..3d01d37 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropConnectionTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropConnectionTest.kt
@@ -29,9 +29,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.ModifierLocalNestedScroll
 import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.modifier.modifierLocalProvider
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
@@ -136,9 +134,11 @@
     @Test
     fun swipeNoOpComposeScrollable_insideNestedScrollingParentView_shouldNotScrollView() {
         // arrange
+
         createViewComposeActivity {
             TestListWithNestedScroll(
-                items, Modifier.modifierLocalProvider(ModifierLocalNestedScroll) { null })
+                items, Modifier
+            )
         }
 
         // act: scroll compose side
@@ -148,8 +148,6 @@
 
         // assert: compose list is scrolled
         rule.onNodeWithTag(topItemTag).assertDoesNotExist()
-        // assert: toolbar  not collapsed
-        onView(withId(R.id.fab)).check(matches(isDisplayed()))
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
index f4aec1d..44d7703 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
@@ -114,6 +114,37 @@
         assertThat(isSecureFlagEnabledForPopup()).isEqualTo(setSecureFlagOnActivity)
     }
 
+    @Test
+    fun toggleFlagOnPopup_customFlagsOverload() {
+        var properties: PopupProperties by mutableStateOf(
+            PopupProperties(
+                flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+                inheritSecurePolicy = false,
+            )
+        )
+
+        rule.setContent {
+            TestPopup(properties)
+        }
+
+        assertThat(isSecureFlagEnabledForPopup()).isFalse()
+
+        // Toggle flag
+        properties = PopupProperties(
+            flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                WindowManager.LayoutParams.FLAG_SECURE,
+            inheritSecurePolicy = false,
+        )
+        assertThat(isSecureFlagEnabledForPopup()).isTrue()
+
+        // Set to inherit
+        properties = PopupProperties(
+            flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+            inheritSecurePolicy = true,
+        )
+        assertThat(isSecureFlagEnabledForPopup()).isEqualTo(setSecureFlagOnActivity)
+    }
+
     @Composable
     fun TestPopup(popupProperties: PopupProperties) {
         SimpleContainer {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupTest.kt
index c8cde80..cf421ef 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupTest.kt
@@ -18,6 +18,7 @@
 import android.view.View
 import android.view.View.MEASURED_STATE_TOO_SMALL
 import android.view.ViewGroup
+import android.view.WindowManager
 import android.widget.FrameLayout
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -417,6 +418,37 @@
     }
 
     @Test
+    fun customFlags() {
+        val flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
+            WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
+            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+
+        rule.setContent {
+            PopupTestTag(testTag) {
+                Popup(
+                    properties = PopupProperties(
+                        flags = flags,
+                        inheritSecurePolicy = false,
+                    )
+                ) {
+                    Box(Modifier.size(50.dp))
+                }
+            }
+        }
+
+        // Make sure that current measurement/drawing is finished
+        rule.runOnIdle { }
+        val popupMatcher = PopupLayoutMatcher(testTag)
+        Espresso.onView(instanceOf(Owner::class.java))
+            .inRoot(popupMatcher)
+            .check(matches(isDisplayed()))
+        val capturedFlags = popupMatcher.lastSeenWindowParams!!.flags
+
+        assertThat(capturedFlags and flags).isEqualTo(flags)
+    }
+
+    @Test
     fun didNotMeasureTooSmallLast() {
         rule.setContent {
             PopupTestTag(testTag) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
index 6f618e4..e8a88af 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
@@ -21,10 +21,8 @@
 import android.util.SparseArray
 import android.view.View
 import android.view.ViewStructure
-import android.view.autofill.AutofillId
 import android.view.autofill.AutofillManager
 import android.view.autofill.AutofillValue
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.util.fastMap
@@ -142,89 +140,3 @@
         }
     }
 }
-
-/**
- * This class is here to ensure that the classes that use this API will get verified and can be
- * AOT compiled. It is expected that this class will soft-fail verification, but the classes
- * which use this method will pass.
- */
-@RequiresApi(26)
-internal object AutofillApi26Helper {
-    @RequiresApi(26)
-    @DoNotInline
-    fun setAutofillId(structure: ViewStructure, parent: AutofillId, virtualId: Int) =
-        structure.setAutofillId(parent, virtualId)
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun getAutofillId(structure: ViewStructure) = structure.autofillId
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun setAutofillType(structure: ViewStructure, type: Int) = structure.setAutofillType(type)
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun setAutofillHints(structure: ViewStructure, hints: Array<String>) =
-        structure.setAutofillHints(hints)
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun isText(value: AutofillValue) = value.isText
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun isDate(value: AutofillValue) = value.isDate
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun isList(value: AutofillValue) = value.isList
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun isToggle(value: AutofillValue) = value.isToggle
-
-    @RequiresApi(26)
-    @DoNotInline
-    fun textValue(value: AutofillValue): CharSequence = value.textValue
-}
-
-/**
- * This class is here to ensure that the classes that use this API will get verified and can be
- * AOT compiled. It is expected that this class will soft-fail verification, but the classes
- * which use this method will pass.
- */
-@RequiresApi(23)
-internal object AutofillApi23Helper {
-    @RequiresApi(23)
-    @DoNotInline
-    fun newChild(structure: ViewStructure, index: Int): ViewStructure? =
-        structure.newChild(index)
-
-    @RequiresApi(23)
-    @DoNotInline
-    fun addChildCount(structure: ViewStructure, num: Int) =
-        structure.addChildCount(num)
-
-    @RequiresApi(23)
-    @DoNotInline
-    fun setId(
-        structure: ViewStructure,
-        id: Int,
-        packageName: String?,
-        typeName: String?,
-        entryName: String?
-    ) = structure.setId(id, packageName, typeName, entryName)
-
-    @RequiresApi(23)
-    @DoNotInline
-    fun setDimens(
-        structure: ViewStructure,
-        left: Int,
-        top: Int,
-        scrollX: Int,
-        scrollY: Int,
-        width: Int,
-        height: Int
-    ) = structure.setDimens(left, top, scrollX, scrollY, width, height)
-}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt
new file mode 100644
index 0000000..3e2b3cc
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.autofill
+
+import android.view.ViewStructure
+import android.view.autofill.AutofillId
+import android.view.autofill.AutofillValue
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(26)
+internal object AutofillApi26Helper {
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillId(structure: ViewStructure, parent: AutofillId, virtualId: Int) =
+        structure.setAutofillId(parent, virtualId)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun getAutofillId(structure: ViewStructure) = structure.autofillId
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillType(structure: ViewStructure, type: Int) = structure.setAutofillType(type)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillHints(structure: ViewStructure, hints: Array<String>) =
+        structure.setAutofillHints(hints)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isText(value: AutofillValue) = value.isText
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isDate(value: AutofillValue) = value.isDate
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isList(value: AutofillValue) = value.isList
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isToggle(value: AutofillValue) = value.isToggle
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun textValue(value: AutofillValue): CharSequence = value.textValue
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(23)
+internal object AutofillApi23Helper {
+    @RequiresApi(23)
+    @DoNotInline
+    fun newChild(structure: ViewStructure, index: Int): ViewStructure? =
+        structure.newChild(index)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun addChildCount(structure: ViewStructure, num: Int) =
+        structure.addChildCount(num)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun setId(
+        structure: ViewStructure,
+        id: Int,
+        packageName: String?,
+        typeName: String?,
+        entryName: String?
+    ) = structure.setId(id, packageName, typeName, entryName)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun setDimens(
+        structure: ViewStructure,
+        left: Int,
+        top: Int,
+        scrollX: Int,
+        scrollY: Int,
+        width: Int,
+        height: Int
+    ) = structure.setDimens(left, top, scrollX, scrollY, width, height)
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 5454da7..4e0daa98 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -207,6 +207,8 @@
 internal var platformTextInputServiceInterceptor:
         (PlatformTextInputService) -> PlatformTextInputService = { it }
 
+private const val ONE_FRAME_120_HERTZ_IN_MILLISECONDS = 8L
+
 @Suppress("ViewConstructor", "VisibleForTests", "ConstPropertyName", "NullAnnotationGroup")
 @OptIn(ExperimentalComposeUiApi::class, InternalComposeUiApi::class)
 internal class AndroidComposeView(
@@ -344,9 +346,6 @@
     private val motionEventAdapter = MotionEventAdapter()
     private val pointerInputEventProcessor = PointerInputEventProcessor(root)
 
-    // TODO(mount): reinstate when coroutines are supported by IR compiler
-    // private val ownerScope = CoroutineScope(Dispatchers.Main.immediate + Job())
-
     /**
      * Used for updating LocalConfiguration when configuration changes - consume LocalConfiguration
      * instead of changing this observer if you are writing a component that adapts to
@@ -947,6 +946,34 @@
         }
     }
 
+    override fun addView(child: View?) = addView(child, -1)
+
+    override fun addView(child: View?, index: Int) =
+        addView(child, index, child!!.layoutParams ?: generateDefaultLayoutParams())
+
+    override fun addView(child: View?, width: Int, height: Int) =
+        addView(
+            child,
+            -1,
+            generateDefaultLayoutParams().also { it.width = width; it.height = height }
+        )
+
+    override fun addView(child: View?, params: LayoutParams?) = addView(child, -1, params)
+
+    /**
+     * Directly adding _real_ [View]s to this view is not supported for external consumers, so we
+     * can use the non-layout-invalidating [addViewInLayout] for when we need to add utility
+     * container views, such as [viewLayersContainer].
+     */
+    override fun addView(child: View?, index: Int, params: LayoutParams?) {
+        addViewInLayout(
+            child,
+            index,
+            params,
+            /* preventRequestLayout = */ true
+        )
+    }
+
     /**
      * Called to inform the owner that a new Android [View] was [attached][Owner.onAttach]
      * to the hierarchy.
@@ -1507,10 +1534,7 @@
         viewTreeObserver.addOnTouchModeChangeListener(touchModeChangeListener)
 
         if (SDK_INT >= S) {
-            AndroidComposeViewTranslationCallbackS.setViewTranslationCallback(
-                this,
-                AndroidComposeViewTranslationCallback()
-            )
+            AndroidComposeViewTranslationCallbackS.setViewTranslationCallback(this)
         }
     }
 
@@ -1562,17 +1586,32 @@
         )
     }
 
-    override fun dispatchGenericMotionEvent(event: MotionEvent) = when (event.actionMasked) {
-        ACTION_SCROLL -> when {
-            isBadMotionEvent(event) || !isAttachedToWindow ->
-                super.dispatchGenericMotionEvent(event)
-
-            event.isFromSource(SOURCE_ROTARY_ENCODER) -> handleRotaryEvent(event)
-
-            else -> handleMotionEvent(event).dispatchedToAPointerInputModifier
+    override fun dispatchGenericMotionEvent(motionEvent: MotionEvent): Boolean {
+        if (hoverExitReceived) {
+            removeCallbacks(sendHoverExitEvent)
+            // Ignore ACTION_HOVER_EXIT if it is directly followed by an ACTION_SCROLL.
+            // Note: In some versions of Android Studio with screen mirroring, studio will
+            // incorrectly add an ACTION_HOVER_EXIT during a scroll event which causes
+            // issues (b/314269723), so we ignore the exit in that case.
+            if (motionEvent.actionMasked == ACTION_SCROLL) {
+                hoverExitReceived = false
+            } else {
+                sendHoverExitEvent.run()
+            }
         }
 
-        else -> super.dispatchGenericMotionEvent(event)
+        return when (motionEvent.actionMasked) {
+            ACTION_SCROLL -> when {
+                isBadMotionEvent(motionEvent) || !isAttachedToWindow ->
+                    super.dispatchGenericMotionEvent(motionEvent)
+
+                motionEvent.isFromSource(SOURCE_ROTARY_ENCODER) -> handleRotaryEvent(motionEvent)
+
+                else -> handleMotionEvent(motionEvent).dispatchedToAPointerInputModifier
+            }
+
+            else -> super.dispatchGenericMotionEvent(motionEvent)
+        }
     }
 
     // TODO(shepshapard): Test this method.
@@ -1964,7 +2003,11 @@
                     previousMotionEvent?.recycle()
                     previousMotionEvent = MotionEvent.obtainNoHistory(event)
                     hoverExitReceived = true
-                    post(sendHoverExitEvent)
+                    // There are cases where the hover exit will incorrectly trigger because this
+                    // post is called right before the end of the frame and the new frame checks for
+                    // a press/down event (which hasn't occurred yet). Therefore, we delay the post
+                    // call a small amount to account for that.
+                    postDelayed(sendHoverExitEvent, ONE_FRAME_120_HERTZ_IN_MILLISECONDS)
                     return false
                 }
             }
@@ -2125,26 +2168,26 @@
          */
         val savedStateRegistryOwner: SavedStateRegistryOwner
     )
+}
 
-    @RequiresApi(S)
-    private class AndroidComposeViewTranslationCallback : ViewTranslationCallback {
-        override fun onShowTranslation(view: View): Boolean {
-            val androidComposeView = view as AndroidComposeView
-            androidComposeView.contentCaptureManager.onShowTranslation()
-            return true
-        }
+@RequiresApi(S)
+private object AndroidComposeViewTranslationCallback : ViewTranslationCallback {
+    override fun onShowTranslation(view: View): Boolean {
+        val androidComposeView = view as AndroidComposeView
+        androidComposeView.contentCaptureManager.onShowTranslation()
+        return true
+    }
 
-        override fun onHideTranslation(view: View): Boolean {
-            val androidComposeView = view as AndroidComposeView
-            androidComposeView.contentCaptureManager.onHideTranslation()
-            return true
-        }
+    override fun onHideTranslation(view: View): Boolean {
+        val androidComposeView = view as AndroidComposeView
+        androidComposeView.contentCaptureManager.onHideTranslation()
+        return true
+    }
 
-        override fun onClearTranslation(view: View): Boolean {
-            val androidComposeView = view as AndroidComposeView
-            androidComposeView.contentCaptureManager.onClearTranslation()
-            return true
-        }
+    override fun onClearTranslation(view: View): Boolean {
+        val androidComposeView = view as AndroidComposeView
+        androidComposeView.contentCaptureManager.onClearTranslation()
+        return true
     }
 }
 
@@ -2202,8 +2245,8 @@
 internal object AndroidComposeViewTranslationCallbackS {
     @DoNotInline
     @RequiresApi(S)
-    fun setViewTranslationCallback(view: View, translationCallback: ViewTranslationCallback) {
-        view.setViewTranslationCallback(translationCallback)
+    fun setViewTranslationCallback(view: View) {
+        view.setViewTranslationCallback(AndroidComposeViewTranslationCallback)
     }
 
     @DoNotInline
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
index 27336f6..6716cc4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
@@ -62,6 +62,11 @@
         setMeasuredDimension(0, 0)
     }
 
+    @Suppress("MissingSuperCall")
+    override fun requestLayout() {
+        // we don't layout our children
+    }
+
     override fun dispatchDraw(canvas: android.graphics.Canvas) {
         // We must updateDisplayListIfDirty for all invalidated Views.
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index c2879c2..5c2c808 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -92,34 +92,32 @@
 /**
  * Properties used to customize the behavior of a [Popup].
  *
- * @property focusable Whether the popup is focusable. When true, the popup will receive IME
- * events and key presses, such as when the back button is pressed.
+ * @property flags Behavioral flags of the popup, which will be passed to the popup window's
+ * [WindowManager.LayoutParams]. See [WindowManager.LayoutParams.flags] for customization options.
+ * If [inheritSecurePolicy] is true, the value of the [WindowManager.LayoutParams.FLAG_SECURE]
+ * bit will not be determined until the popup is constructed.
+ * @property inheritSecurePolicy Whether [WindowManager.LayoutParams.FLAG_SECURE] should be set
+ * according to [SecureFlagPolicy.Inherit]. Other [SecureFlagPolicy] behaviors should be set via
+ * [flags] directly.
  * @property dismissOnBackPress Whether the popup can be dismissed by pressing the back button.
- * If true, pressing the back button will call onDismissRequest. Note that [focusable] must be
- * set to true in order to receive key events such as the back button - if the popup is not
- * focusable then this property does nothing.
+ * If true, pressing the back button will call onDismissRequest. Note that the popup must be
+ * [focusable] in order to receive key events such as the back button. If the popup is not
+ * [focusable], then this property does nothing.
  * @property dismissOnClickOutside Whether the popup can be dismissed by clicking outside the
  * popup's bounds. If true, clicking outside the popup will call onDismissRequest.
- * @property securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the popup's
- * window.
  * @property excludeFromSystemGesture A flag to check whether to set the systemGestureExclusionRects.
  * The default is true.
- * @property clippingEnabled Whether to allow the popup window to extend beyond the bounds of the
- * screen. By default the window is clipped to the screen boundaries. Setting this to false will
- * allow windows to be accurately positioned.
- * The default value is true.
  * @property usePlatformDefaultWidth Whether the width of the popup's content should be limited to
  * the platform default, which is smaller than the screen width.
  */
 @Immutable
 actual class PopupProperties constructor(
-    actual val focusable: Boolean = false,
+    internal val flags: Int,
+    internal val inheritSecurePolicy: Boolean = true,
     actual val dismissOnBackPress: Boolean = true,
     actual val dismissOnClickOutside: Boolean = true,
-    val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
     val excludeFromSystemGesture: Boolean = true,
-    actual val clippingEnabled: Boolean = true,
-    val usePlatformDefaultWidth: Boolean = false
+    val usePlatformDefaultWidth: Boolean = false,
 ) {
     actual constructor(
         focusable: Boolean,
@@ -149,32 +147,93 @@
         securePolicy = securePolicy,
         excludeFromSystemGesture = excludeFromSystemGesture,
         clippingEnabled = clippingEnabled,
-        usePlatformDefaultWidth = false
+        usePlatformDefaultWidth = false,
     )
 
+    /**
+     * Constructs a [PopupProperties] with the given behaviors. This constructor is to support
+     * multiplatform and maintain backwards compatibility. Consider the overload that takes a
+     * [flags] parameter if more precise control over the popup flags is desired.
+     *
+     * @param focusable Whether the popup is focusable. When true, the popup will receive IME
+     * events and key presses, such as when the back button is pressed.
+     * @param dismissOnBackPress Whether the popup can be dismissed by pressing the back button.
+     * If true, pressing the back button will call onDismissRequest. Note that [focusable] must be
+     * set to true in order to receive key events such as the back button. If the popup is not
+     * focusable, then this property does nothing.
+     * @param dismissOnClickOutside Whether the popup can be dismissed by clicking outside the
+     * popup's bounds. If true, clicking outside the popup will call onDismissRequest.
+     * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the
+     * popup's window.
+     * @param excludeFromSystemGesture A flag to check whether to set the
+     * systemGestureExclusionRects. The default is true.
+     * @param clippingEnabled Whether to allow the popup window to extend beyond the bounds of the
+     * screen. By default the window is clipped to the screen boundaries. Setting this to false will
+     * allow windows to be accurately positioned. The default value is true.
+     * @param usePlatformDefaultWidth Whether the width of the popup's content should be limited to
+     * the platform default, which is smaller than the screen width.
+     */
+    constructor(
+        focusable: Boolean = false,
+        dismissOnBackPress: Boolean = true,
+        dismissOnClickOutside: Boolean = true,
+        securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
+        excludeFromSystemGesture: Boolean = true,
+        clippingEnabled: Boolean = true,
+        usePlatformDefaultWidth: Boolean = false,
+    ) : this (
+        flags = createFlags(focusable, securePolicy, clippingEnabled),
+        inheritSecurePolicy = securePolicy == SecureFlagPolicy.Inherit,
+        dismissOnBackPress = dismissOnBackPress,
+        dismissOnClickOutside = dismissOnClickOutside,
+        excludeFromSystemGesture = excludeFromSystemGesture,
+        usePlatformDefaultWidth = usePlatformDefaultWidth,
+    )
+
+    /**
+     * Whether the popup is focusable. When true, the popup will receive IME events and key
+     * presses, such as when the back button is pressed.
+     */
+    actual val focusable: Boolean
+        get() = (flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0
+
+    /**
+     * Policy for how [WindowManager.LayoutParams.FLAG_SECURE] is set on the popup's window.
+     */
+    val securePolicy: SecureFlagPolicy
+        get() = when {
+            inheritSecurePolicy -> SecureFlagPolicy.Inherit
+            (flags and WindowManager.LayoutParams.FLAG_SECURE) == 0 -> SecureFlagPolicy.SecureOff
+            else -> SecureFlagPolicy.SecureOn
+        }
+
+    /**
+     * Whether the popup window is clipped to the screen boundaries, or allowed to extend beyond
+     * the bounds of the screen.
+     */
+    actual val clippingEnabled: Boolean
+        get() = (flags and WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) == 0
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is PopupProperties) return false
 
-        if (focusable != other.focusable) return false
+        if (flags != other.flags) return false
+        if (inheritSecurePolicy != other.inheritSecurePolicy) return false
         if (dismissOnBackPress != other.dismissOnBackPress) return false
         if (dismissOnClickOutside != other.dismissOnClickOutside) return false
-        if (securePolicy != other.securePolicy) return false
         if (excludeFromSystemGesture != other.excludeFromSystemGesture) return false
-        if (clippingEnabled != other.clippingEnabled) return false
         if (usePlatformDefaultWidth != other.usePlatformDefaultWidth) return false
 
         return true
     }
 
     override fun hashCode(): Int {
-        var result = dismissOnBackPress.hashCode()
-        result = 31 * result + focusable.hashCode()
+        var result = flags
+        result = 31 * result + inheritSecurePolicy.hashCode()
         result = 31 * result + dismissOnBackPress.hashCode()
         result = 31 * result + dismissOnClickOutside.hashCode()
-        result = 31 * result + securePolicy.hashCode()
         result = 31 * result + excludeFromSystemGesture.hashCode()
-        result = 31 * result + clippingEnabled.hashCode()
         result = 31 * result + usePlatformDefaultWidth.hashCode()
         return result
     }
@@ -340,6 +399,27 @@
     }
 }
 
+private const val PopupPropertiesBaseFlags: Int =
+    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+
+private fun createFlags(
+    focusable: Boolean,
+    securePolicy: SecureFlagPolicy,
+    clippingEnabled: Boolean,
+): Int {
+    var flags = PopupPropertiesBaseFlags
+    if (!focusable) {
+        flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+    }
+    if (securePolicy == SecureFlagPolicy.SecureOn) {
+        flags = flags or WindowManager.LayoutParams.FLAG_SECURE
+    }
+    if (!clippingEnabled) {
+        flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+    }
+    return flags
+}
+
 // TODO(b/139861182): This is a hack to work around Popups not using Semantics for test tags
 //  We should either remove it, or come up with an abstracted general solution that isn't specific
 //  to Popup
@@ -585,61 +665,31 @@
         backCallback = null
     }
 
-    /**
-     * Set whether the popup can grab a focus and support dismissal.
-     */
-    private fun setIsFocusable(isFocusable: Boolean) = applyNewFlags(
-        if (!isFocusable) {
-            params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-        } else {
-            params.flags and (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv())
-        }
-    )
-
-    private fun setSecurePolicy(securePolicy: SecureFlagPolicy) {
-        val secureFlagEnabled =
-            securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
-        applyNewFlags(
-            if (secureFlagEnabled) {
-                params.flags or WindowManager.LayoutParams.FLAG_SECURE
-            } else {
-                params.flags and (WindowManager.LayoutParams.FLAG_SECURE.inv())
-            }
-        )
-    }
-
-    private fun setClippingEnabled(clippingEnabled: Boolean) = applyNewFlags(
-        if (clippingEnabled) {
-            params.flags and (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS.inv())
-        } else {
-            params.flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-        }
-    )
-
     fun updateParameters(
         onDismissRequest: (() -> Unit)?,
         properties: PopupProperties,
         testTag: String,
-        layoutDirection: LayoutDirection
+        layoutDirection: LayoutDirection,
     ) {
         this.onDismissRequest = onDismissRequest
+        this.testTag = testTag
+        updatePopupProperties(properties)
+        superSetLayoutDirection(layoutDirection)
+    }
+
+    private fun updatePopupProperties(properties: PopupProperties) {
+        if (this.properties == properties) return
+
         if (properties.usePlatformDefaultWidth && !this.properties.usePlatformDefaultWidth) {
             // Undo fixed size in internalOnLayout, which would suppress size changes when
             // usePlatformDefaultWidth is true.
             params.width = WindowManager.LayoutParams.WRAP_CONTENT
             params.height = WindowManager.LayoutParams.WRAP_CONTENT
-            popupLayoutHelper.updateViewLayout(windowManager, this, params)
         }
-        this.properties = properties
-        this.testTag = testTag
-        setIsFocusable(properties.focusable)
-        setSecurePolicy(properties.securePolicy)
-        setClippingEnabled(properties.clippingEnabled)
-        superSetLayoutDirection(layoutDirection)
-    }
 
-    private fun applyNewFlags(flags: Int) {
-        params.flags = flags
+        this.properties = properties
+        params.flags = properties.flagsWithSecureFlagInherited(composeView.isFlagSecureEnabled())
+
         popupLayoutHelper.updateViewLayout(windowManager, this, params)
     }
 
@@ -789,17 +839,7 @@
             // Start to position the popup in the top left corner, a new position will be calculated
             gravity = Gravity.START or Gravity.TOP
 
-            // Flags specific to android.widget.PopupWindow
-            flags = flags and (
-                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
-                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
-                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
-                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                ).inv()
-
-            // Enables us to intercept outside clicks even when popup is not focusable
-            flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+            flags = properties.flagsWithSecureFlagInherited(composeView.isFlagSecureEnabled())
 
             type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL
 
@@ -909,6 +949,14 @@
     return false
 }
 
+private fun PopupProperties.flagsWithSecureFlagInherited(
+    isParentFlagSecureEnabled: Boolean,
+): Int = if (this.inheritSecurePolicy && isParentFlagSecureEnabled) {
+    this.flags or WindowManager.LayoutParams.FLAG_SECURE
+} else {
+    this.flags
+}
+
 private fun Rect.toIntBounds() = IntRect(
     left = left,
     top = top,
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/modifier/ModifierLocalMapTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/modifier/ModifierLocalMapTest.kt
new file mode 100644
index 0000000..9aa0d93
--- /dev/null
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/modifier/ModifierLocalMapTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.modifier
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.LayoutDirection
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class ModifierLocalMapTest {
+    @Test fun `empty modifier local map`() {
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf()
+
+        // Assert.
+        assertThat(modifierLocalMap).isEqualTo(EmptyMap)
+    }
+
+    @Test fun `modifier local map with a single null entry`() {
+        // Arrange.
+        val modifierLocal = modifierLocalOf { "" }
+
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf(modifierLocal)
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(SingleLocalMap::class.java)
+        assertThat(modifierLocalMap[modifierLocal]).isNull()
+    }
+    @Test fun `modifier local map with two null entries`() {
+        // Arrange.
+        val stringModifierLocal = modifierLocalOf<String?> { "" }
+        val colorModifierLocal = modifierLocalOf<Color?> { Color.Unspecified }
+
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf(stringModifierLocal, colorModifierLocal)
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(MultiLocalMap::class.java)
+        assertThat(modifierLocalMap[stringModifierLocal]).isNull()
+        assertThat(modifierLocalMap[colorModifierLocal]).isNull()
+    }
+
+    @Test fun `modifier local map with three null entries`() {
+        // Arrange.
+        val stringModifierLocal = modifierLocalOf { "" }
+        val colorModifierLocal = modifierLocalOf { Color.Unspecified }
+        val directionModifierLocal = modifierLocalOf { LayoutDirection.Ltr }
+
+        // Act.
+        val modifierLocalMap =
+            modifierLocalMapOf(stringModifierLocal, colorModifierLocal, directionModifierLocal)
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(MultiLocalMap::class.java)
+        assertThat(modifierLocalMap[stringModifierLocal]).isNull()
+        assertThat(modifierLocalMap[colorModifierLocal]).isNull()
+        assertThat(modifierLocalMap[directionModifierLocal]).isNull()
+    }
+
+    @Test fun `modifier local map with a single entry`() {
+        // Arrange.
+        val modifierLocal = modifierLocalOf { "" }
+
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf(modifierLocal to "single")
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(SingleLocalMap::class.java)
+        assertThat(modifierLocalMap[modifierLocal]).isEqualTo("single")
+    }
+
+    @Test fun `modifier local map with two values`() {
+        // Arrange.
+        val stringModifierLocal = modifierLocalOf { "" }
+        val colorModifierLocal = modifierLocalOf { Color.Unspecified }
+
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf(
+            stringModifierLocal to "first",
+            colorModifierLocal to Color.Red
+        )
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(MultiLocalMap::class.java)
+        assertThat(modifierLocalMap[stringModifierLocal]).isEqualTo("first")
+        assertThat(modifierLocalMap[colorModifierLocal]).isEqualTo(Color.Red)
+    }
+
+    @Test fun `modifier local map with three values`() {
+        // Arrange.
+        val stringModifierLocal = modifierLocalOf { "" }
+        val colorModifierLocal = modifierLocalOf { Color.Unspecified }
+        val directionModifierLocal = modifierLocalOf { LayoutDirection.Ltr }
+
+        // Act.
+        val modifierLocalMap = modifierLocalMapOf(
+            stringModifierLocal to "first",
+            colorModifierLocal to Color.Red,
+            directionModifierLocal to LayoutDirection.Rtl
+        )
+
+        // Assert.
+        assertThat(modifierLocalMap).isInstanceOf(MultiLocalMap::class.java)
+        assertThat(modifierLocalMap[stringModifierLocal]).isEqualTo("first")
+        assertThat(modifierLocalMap[colorModifierLocal]).isEqualTo(Color.Red)
+        assertThat(modifierLocalMap[directionModifierLocal]).isEqualTo(LayoutDirection.Rtl)
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Expect.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Expect.kt
index e4b50a2..46900a3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Expect.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Expect.kt
@@ -21,6 +21,8 @@
 
 internal expect fun areObjectsOfSameType(a: Any, b: Any): Boolean
 
+internal expect fun classKeyForObject(a: Any): Any
+
 /**
  * Reflectively resolves the properties and name of [element], and populates it in the receiver.
  * This function is used by [ModifierNodeElement] as a default implementation to provide inspection
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
index bc482d1..e6e4c44 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
@@ -182,7 +182,7 @@
      * @see androidx.compose.ui.node.ParentDataModifierNode
      * @see androidx.compose.ui.node.LayoutAwareModifierNode
      * @see androidx.compose.ui.node.GlobalPositionAwareModifierNode
-     * @see androidx.compose.ui.node.IntermediateLayoutModifierNode
+     * @see androidx.compose.ui.node.ApproachLayoutModifierNode
      */
     abstract class Node : DelegatableNode {
         @Suppress("LeakingThis")
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Shadow.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Shadow.kt
index 1cc3122..f3703e0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Shadow.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Shadow.kt
@@ -18,13 +18,15 @@
 
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.BlockGraphicsLayerModifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.DefaultShadowColor
+import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
@@ -99,24 +101,39 @@
     ambientColor: Color = DefaultShadowColor,
     spotColor: Color = DefaultShadowColor,
 ) = if (elevation > 0.dp || clip) {
-    inspectable(
-        inspectorInfo = debugInspectorInfo {
-            name = "shadow"
-            properties["elevation"] = elevation
-            properties["shape"] = shape
-            properties["clip"] = clip
-            properties["ambientColor"] = ambientColor
-            properties["spotColor"] = spotColor
-        }
-    ) {
-        graphicsLayer {
-            this.shadowElevation = elevation.toPx()
-            this.shape = shape
-            this.clip = clip
-            this.ambientShadowColor = ambientColor
-            this.spotShadowColor = spotColor
-        }
-    }
+    this then ShadowGraphicsLayerElement(elevation, shape, clip, ambientColor, spotColor)
 } else {
     this
 }
+
+internal data class ShadowGraphicsLayerElement(
+    val elevation: Dp,
+    val shape: Shape,
+    val clip: Boolean,
+    val ambientColor: Color,
+    val spotColor: Color,
+) : ModifierNodeElement<BlockGraphicsLayerModifier>() {
+
+    private fun createBlock(): GraphicsLayerScope.() -> Unit = {
+        this.shadowElevation = [email protected]()
+        this.shape = [email protected]
+        this.clip = [email protected]
+        this.ambientShadowColor = [email protected]
+        this.spotShadowColor = [email protected]
+    }
+    override fun create() = BlockGraphicsLayerModifier(createBlock())
+
+    override fun update(node: BlockGraphicsLayerModifier) {
+        node.layerBlock = createBlock()
+        node.invalidateLayerBlock()
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "shadow"
+        properties["elevation"] = elevation
+        properties["shape"] = shape
+        properties["clip"] = clip
+        properties["ambientColor"] = ambientColor
+        properties["spotColor"] = spotColor
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
index b59ce12..80c4ea8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
@@ -554,7 +554,7 @@
     }
 }
 
-private class BlockGraphicsLayerModifier(
+internal class BlockGraphicsLayerModifier(
     var layerBlock: GraphicsLayerScope.() -> Unit,
 ) : LayoutModifierNode, Modifier.Node() {
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
index 651727c..457a782 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
@@ -20,7 +20,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Velocity
@@ -114,7 +113,7 @@
  */
 class NestedScrollDispatcher {
 
-    internal var modifierLocalNode: ModifierLocalModifierNode? = null
+    internal var nestedScrollNode: NestedScrollNode? = null
 
     // lambda to calculate the most outer nested scroll scope for this dispatcher on demand
     internal var calculateNestedScrollScope: () -> CoroutineScope? = { scope }
@@ -153,9 +152,7 @@
      * nested scrolling parent above
      */
     internal val parent: NestedScrollConnection?
-        get() = modifierLocalNode?.run {
-            ModifierLocalNestedScroll.current
-        }
+        get() = nestedScrollNode?.parentNestedScrollNode
 
     /**
      * Dispatch pre scroll pass. This triggers [NestedScrollConnection.onPreScroll] on all the
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
index 8a1337e..c1a3460 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
@@ -18,15 +18,12 @@
 
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
-import androidx.compose.ui.modifier.modifierLocalMapOf
-import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.unit.Velocity
 import kotlinx.coroutines.CoroutineScope
 
-internal val ModifierLocalNestedScroll = modifierLocalOf<NestedScrollNode?> { null }
-
 /**
  * This creates a Nested Scroll Modifier node that can be delegated to. In most case you should
  * use [Modifier.nestedScroll] since that implementation also uses this. Use this factory to create
@@ -45,7 +42,7 @@
 internal class NestedScrollNode(
     var connection: NestedScrollConnection,
     dispatcher: NestedScrollDispatcher?
-) : ModifierLocalModifierNode, NestedScrollConnection, DelegatableNode, Modifier.Node() {
+) : TraversableNode, NestedScrollConnection, Modifier.Node() {
 
     // Resolved dispatcher for re-use in case of null dispatcher is passed.
     private var resolvedDispatcher: NestedScrollDispatcher
@@ -54,17 +51,19 @@
         resolvedDispatcher = dispatcher ?: NestedScrollDispatcher() // Resolve null dispatcher
     }
 
-    private val parentModifierLocal: NestedScrollNode?
-        get() = if (isAttached) ModifierLocalNestedScroll.current else null
+    internal val parentNestedScrollNode: NestedScrollNode?
+        get() = if (isAttached)
+            findNearestAncestor()
+        else
+            null
 
     private val parentConnection: NestedScrollConnection?
-        get() = if (isAttached) ModifierLocalNestedScroll.current else null
+        get() = if (isAttached) parentNestedScrollNode else null
 
-    // Avoid get() to prevent constant allocations for static map.
-    override val providedValues = modifierLocalMapOf(entry = ModifierLocalNestedScroll to this)
+    override val traverseKey: Any = "androidx.compose.ui.input.nestedscroll.NestedScrollNode"
 
     private val nestedCoroutineScope: CoroutineScope
-        get() = parentModifierLocal?.nestedCoroutineScope
+        get() = parentNestedScrollNode?.nestedCoroutineScope
             ?: resolvedDispatcher.scope
             ?: throw IllegalStateException(
                 "in order to access nested coroutine scope you need to attach dispatcher to the " +
@@ -144,7 +143,7 @@
      * to reset the dispatcher properties accordingly.
      */
     private fun updateDispatcherFields() {
-        resolvedDispatcher.modifierLocalNode = this
+        resolvedDispatcher.nestedScrollNode = this
         resolvedDispatcher.calculateNestedScrollScope = { nestedCoroutineScope }
         resolvedDispatcher.scope = coroutineScope
     }
@@ -152,8 +151,8 @@
     private fun resetDispatcherFields() {
         // only null this out if the modifier local node is what we set it to, since it is possible
         // it has already been reused in a different node
-        if (resolvedDispatcher.modifierLocalNode === this)
-            resolvedDispatcher.modifierLocalNode = null
+        if (resolvedDispatcher.nestedScrollNode === this)
+            resolvedDispatcher.nestedScrollNode = null
     }
 
     internal fun updateNode(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachLayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachLayoutModifierNode.kt
new file mode 100644
index 0000000..c026b1f
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachLayoutModifierNode.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.compose.ui.layout
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.NodeMeasuringIntrinsics
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * [ApproachLayoutModifierNode] is designed to support gradually approaching the destination layout
+ * calculated in the lookahead pass. This can be particularly helpful when the destination layout is
+ * anticipated to change drastically and would consequently result in visual disruptions.
+ *
+ * In order to create a smooth approach, an interpolation (often through animations) can be used
+ * in [approachMeasure] to interpolate the measurement or placement from a previously recorded size
+ * and/or position to the destination/target size and/or position. The destination size is
+ * available in [ApproachMeasureScope] as [ApproachMeasureScope.lookaheadSize]. And the target
+ * position can also be acquired in [ApproachMeasureScope] during placement by using
+ * [LookaheadScope.localLookaheadPositionOf] with the layout's
+ * [Placeable.PlacementScope.coordinates]. The sample code below illustrates how that can be
+ * achieved.
+ *
+ * During the lookahead pass, [measure] will be invoked. By default [measure] simply passes the
+ * incoming constraints to its child, and returns the child measure result to parent without any
+ * modification. The default behavior for [measure] is simply a pass through of constraints and
+ * measure results without modification. This can be overridden as needed. [approachMeasure]
+ * will be invoked during the approach pass after lookahead.
+ *
+ * [isMeasurementApproachComplete] signals whether the measurement has already reached the
+ * destination size. It will be queried after the destination has been determined by the lookahead
+ * pass, before [approachMeasure] is invoked. The lookahead size is provided to
+ * [isMeasurementApproachComplete] for convenience in deciding whether the destination size has
+ * been reached.
+ *
+ * [isPlacementApproachComplete] indicates whether the position has approached
+ * destination defined by the lookahead, hence it's a signal to the system for whether additional
+ * approach placements are necessary. [isPlacementApproachComplete] will be invoked after the
+ * destination position has been determined by lookahead pass, and before the placement phase in
+ * [approachMeasure].
+ *
+ * **IMPORTANT**:
+ * When both [isMeasurementApproachComplete] and [isPlacementApproachComplete] become true, the
+ * approach is considered complete. Approach pass will subsequently snap the measurement and
+ * placement to lookahead measurement and placement. Once approach is complete, [approachMeasure]
+ * may never be invoked until either [isMeasurementApproachComplete] or
+ * [isPlacementApproachComplete] becomes false again. Therefore it is important to ensure
+ * [approachMeasure] and [measure] result in the same measurement and placement when the approach is
+ * complete. Otherwise, there may be visual discontinuity when we snap the measurement and placement
+ * to lookahead.
+ *
+ * It is important to be accurate in [isPlacementApproachComplete] and
+ * [isMeasurementApproachComplete]. A prolonged indication of incomplete approach will prevent the
+ * system from potentially skipping approach pass when possible.
+ *
+ * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
+ */
+interface ApproachLayoutModifierNode : LayoutModifierNode {
+    /**
+     * [isMeasurementApproachComplete] signals whether the measurement has already reached the
+     * destination size. It will be queried after the destination has been determined by the
+     * lookahead pass, before [approachMeasure] is invoked. The lookahead size is provided to
+     * [isMeasurementApproachComplete] for convenience in deciding whether the destination size has
+     * been reached.
+     *
+     * Note: It is important to be accurate in [isPlacementApproachComplete] and
+     * [isMeasurementApproachComplete]. A prolonged indication of incomplete approach will prevent
+     * the system from potentially skipping approach pass when possible.
+     */
+    fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean
+
+    /**
+     * [isPlacementApproachComplete] indicates whether the position has approached destination
+     * defined by the lookahead, hence it's a signal to the system for whether additional
+     * approach placements are necessary. [isPlacementApproachComplete] will be invoked after the
+     * destination position has been determined by lookahead pass, and before the placement phase in
+     * [approachMeasure].
+     *
+     * Note: It is important to be accurate in [isPlacementApproachComplete] and
+     * [isMeasurementApproachComplete]. A prolonged indication of incomplete approach will prevent
+     * the system from potentially skipping approach pass when possible.
+     *
+     * By default, [isPlacementApproachComplete] returns true.
+     */
+    fun Placeable.PlacementScope.isPlacementApproachComplete(
+        lookaheadCoordinates: LayoutCoordinates
+    ): Boolean {
+        return true
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult = measurable.measure(constraints).run {
+        layout(width, height) {
+            place(0, 0)
+        }
+    }
+
+    /**
+     * [approachMeasure] defines how the measurement and placement of the layout approach the
+     * destination size and position. In order to achieve a smooth approach from the current size
+     * and position to the destination, an interpolation (often through animations) can be used
+     * in [approachMeasure] to interpolate the measurement or placement from a previously recorded
+     * size and position to the destination/target size and position. The destination size is
+     * available in [ApproachMeasureScope] as [ApproachMeasureScope.lookaheadSize]. And the target
+     * position can also be acquired in [ApproachMeasureScope] during placement by using
+     * [LookaheadScope.localLookaheadPositionOf] with the layout's
+     * [Placeable.PlacementScope.coordinates]. Please see sample code below for how that can be
+     * achieved.
+     *
+     * Note: [approachMeasure] is only guaranteed to be invoked when either
+     * [isPlacementApproachComplete] or [isMeasurementApproachComplete] is false. Otherwise, the
+     * system will consider the approach complete (i.e. destination reached) and may skip the
+     * approach pass when possible.
+     *
+     * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
+     */
+    @ExperimentalComposeUiApi
+    fun ApproachMeasureScope.approachMeasure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult
+
+    /**
+     * The function used to calculate minIntrinsicWidth for the approach pass changes.
+     */
+    @ExperimentalComposeUiApi
+    fun ApproachIntrinsicMeasureScope.minApproachIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = NodeMeasuringIntrinsics.minWidth(
+        NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints ->
+            approachMeasure(intrinsicMeasurable, constraints)
+        },
+        this,
+        measurable,
+        height
+    )
+
+    /**
+     * The function used to calculate minIntrinsicHeight for the approach pass changes.
+     */
+    @ExperimentalComposeUiApi
+    fun ApproachIntrinsicMeasureScope.minApproachIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = NodeMeasuringIntrinsics.minHeight(
+        NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints ->
+            approachMeasure(intrinsicMeasurable, constraints)
+        },
+        this,
+        measurable,
+        width
+    )
+
+    /**
+     * The function used to calculate maxIntrinsicWidth for the approach pass changes.
+     */
+    @ExperimentalComposeUiApi
+    fun ApproachIntrinsicMeasureScope.maxApproachIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = NodeMeasuringIntrinsics.maxWidth(
+        NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints ->
+            approachMeasure(intrinsicMeasurable, constraints)
+        },
+        this,
+        measurable,
+        height
+    )
+
+    /**
+     * The function used to calculate maxIntrinsicHeight for the approach pass changes.
+     */
+    @ExperimentalComposeUiApi
+    fun ApproachIntrinsicMeasureScope.maxApproachIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = NodeMeasuringIntrinsics.maxHeight(
+        NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints ->
+            approachMeasure(intrinsicMeasurable, constraints)
+        },
+        this,
+        measurable,
+        width
+    )
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
new file mode 100644
index 0000000..94a755c
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package androidx.compose.ui.layout
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.node.LayoutModifierNodeCoordinator
+import androidx.compose.ui.node.NodeCoordinator
+import androidx.compose.ui.node.checkMeasuredSize
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * The receiver scope of a layout's intrinsic approach measurements lambdas.
+ */
+sealed interface ApproachIntrinsicMeasureScope : IntrinsicMeasureScope {
+
+    /**
+     * Constraints used to measure the layout in the lookahead pass.
+     */
+    val lookaheadConstraints: Constraints
+
+    /**
+     * Size of the [ApproachLayoutModifierNode] measured during the lookahead pass using
+     * [lookaheadConstraints]. This size can be used as the target size for the
+     * [ApproachLayoutModifierNode] to approach the destination (i.e. lookahead) size.
+     */
+    val lookaheadSize: IntSize
+}
+
+/**
+ * [ApproachMeasureScope] provides access to lookahead results to allow
+ * [ApproachLayoutModifierNode] to leverage lookahead results to define how
+ * measurements and placements approach their destination.
+ *
+ * [ApproachMeasureScope.lookaheadSize] provides the target size of the layout.
+ * By knowing the target size and position, layout adjustments such as animations can be defined
+ * in [ApproachLayoutModifierNode] to morph the layout gradually in both size and position
+ * to arrive at its precalculated bounds.
+ */
+@ExperimentalComposeUiApi
+sealed interface ApproachMeasureScope : ApproachIntrinsicMeasureScope, MeasureScope
+
+internal class ApproachMeasureScopeImpl(
+    val coordinator: LayoutModifierNodeCoordinator,
+    var approachNode: ApproachLayoutModifierNode,
+) : ApproachMeasureScope, MeasureScope by coordinator, LookaheadScope {
+    override val lookaheadConstraints: Constraints
+        get() = requireNotNull(coordinator.lookaheadConstraints) {
+            "Error: Lookahead constraints requested before lookahead measure."
+        }
+    override val lookaheadSize: IntSize
+        get() = coordinator.lookaheadDelegate!!.measureResult.let { IntSize(it.width, it.height) }
+
+    internal var approachMeasureRequired: Boolean = false
+
+    override fun LayoutCoordinates.toLookaheadCoordinates(): LayoutCoordinates {
+        if (this is LookaheadLayoutCoordinates) return this
+        if (this is NodeCoordinator) {
+            return lookaheadDelegate?.lookaheadLayoutCoordinates ?: this
+        }
+        throw IllegalArgumentException("Unsupported LayoutCoordinates: $this")
+    }
+
+    override val Placeable.PlacementScope.lookaheadScopeCoordinates: LayoutCoordinates
+        get() {
+            val lookaheadRoot = coordinator.layoutNode.lookaheadRoot
+            requireNotNull(lookaheadRoot) {
+                "Error: Requesting LookaheadScopeCoordinates is not permitted from outside of a" +
+                    " LookaheadScope."
+            }
+            return if (lookaheadRoot.isVirtualLookaheadRoot) {
+                lookaheadRoot.parent?.innerCoordinator
+                // Root node is in a lookahead scope
+                    ?: lookaheadRoot.children[0].outerCoordinator
+            } else {
+                lookaheadRoot.outerCoordinator
+            }
+        }
+
+    override fun layout(
+        width: Int,
+        height: Int,
+        alignmentLines: Map<AlignmentLine, Int>,
+        rulers: (RulerScope.() -> Unit)?,
+        placementBlock: Placeable.PlacementScope.() -> Unit
+    ): MeasureResult {
+        checkMeasuredSize(width, height)
+        return object : MeasureResult {
+            override val width = width
+            override val height = height
+
+            @Suppress("PrimitiveInCollection")
+            override val alignmentLines = alignmentLines
+            override val rulers = rulers
+            override fun placeChildren() {
+                coordinator.placementScope.placementBlock()
+            }
+        }
+    }
+
+    // Intermediate layout pass is post-lookahead. Therefore isLookingAhead is always false.
+    override val isLookingAhead: Boolean
+        get() = false
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
deleted file mode 100644
index af32b35..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright 2023 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.compose.ui.layout
-
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.GraphicsLayerScope
-import androidx.compose.ui.internal.checkPreconditionNotNull
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.NodeMeasuringIntrinsics
-import androidx.compose.ui.node.Nodes
-import androidx.compose.ui.node.checkMeasuredSize
-import androidx.compose.ui.node.requireLayoutNode
-import androidx.compose.ui.node.visitAncestors
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * This establishes an internal IntermediateLayoutModifierNode. This node implicitly creates
- * a [LookaheadScope], unless there is already a [LookaheadScope] in its ancestor. This allows
- * lookahead to function "locally" without an explicit [LookaheadScope] defined.
- *
- * [coroutineScope] is a CoroutineScope that we provide to the IntermediateMeasureBlock for
- * the intermediate changes to launch from. It is scoped to the lifecycle of the
- * Modifier.intermediateLayout.
- */
-@OptIn(ExperimentalComposeUiApi::class)
-internal class IntermediateLayoutModifierNode(
-    internal var measureBlock: IntermediateMeasureScope.(
-        measurable: Measurable,
-        constraints: Constraints,
-    ) -> MeasureResult
-) : LayoutModifierNode, Modifier.Node() {
-
-    // This is the union scope of LookaheadScope, CoroutineScope and MeasureScope that will be
-    // used as the receiver for user-provided measure block.
-    private val intermediateMeasureScope = IntermediateMeasureScopeImpl()
-
-    // If there's no lookahead scope in the ancestor, this is the lookahead scope that
-    // we'll provide to the intermediateLayout modifier
-    private val localLookaheadScope: LookaheadScopeImpl = LookaheadScopeImpl {
-        coordinator!!
-    }
-
-    /**
-     * Closest LookaheadScope in the ancestor. Defaults to local lookahead scope, and
-     * gets modified if there was already a parent scope.
-     */
-    private var closestLookaheadScope: LookaheadScope = localLookaheadScope
-
-    // TODO: This needs to be wired up with a user provided lambda to explicitly tell us when the
-    // intermediate changes are finished. The functionality to support this has been implemented,
-    // but the API change to get this lambda from devs has to be deferred until Modifier.Node
-    // delegate design is finished.
-    var isIntermediateChangeActive: Boolean = true
-
-    // Caches the lookahead constraints in order to snap to lookahead constraints in main pass
-    // when the intermediate changes are finished.
-    private var lookaheadConstraints: Constraints? = null
-
-    // Measurable & Placeable that serves as a middle layer between intermediateLayout logic and
-    // child measurable/placeable. This allows the middle layer to overwrite any changes in
-    // intermediateLayout when [IntermediateMeasureBlock#isIntermediateChangeActive] = false.
-    // This ensures a convergence between main pass and lookahead pass when there's no
-    // intermediate changes.
-    private var intermediateMeasurable: IntermediateMeasurablePlaceable? = null
-
-    override fun onAttach() {
-        val coordinates = coordinator?.lookaheadDelegate?.lookaheadLayoutCoordinates
-        checkPreconditionNotNull(coordinates) { "could not fetch lookahead coordinates" }
-
-        val closestLookaheadRoot = requireLayoutNode().lookaheadRoot
-        closestLookaheadScope = if (closestLookaheadRoot?.isVirtualLookaheadRoot == true) {
-            // The closest explicit scope in the tree will be the closest scope, as all
-            // descendant intermediateLayoutModifiers will be using that as their LookaheadScope
-            LookaheadScopeImpl {
-                closestLookaheadRoot.parent!!.innerCoordinator.coordinates
-            }
-        } else {
-            // If no explicit scope is ever defined, then fallback to implicitly created scopes
-            var ancestorNode: IntermediateLayoutModifierNode? = null
-            visitAncestors(Nodes.IntermediateMeasure) {
-                // Find the closest ancestor, and return
-                ancestorNode = it
-                return@visitAncestors
-            }
-            ancestorNode?.localLookaheadScope ?: localLookaheadScope
-        }
-    }
-
-    /**
-     * This gets call in the lookahead pass. Since intermediateLayout is designed to only make
-     * transient changes that don't affect lookahead, we simply pass through for the lookahead
-     * pass.
-     */
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult = measurable.measure(constraints).run {
-        layout(width, height) {
-            place(0, 0)
-        }
-    }
-
-    /**
-     * This gets called in the main pass to allow intermediate measurements & placements gradually
-     * converging to the lookahead results.
-     */
-    fun MeasureScope.intermediateMeasure(
-        measurable: Measurable,
-        constraints: Constraints,
-        lookaheadSize: IntSize,
-        lookaheadConstraints: Constraints,
-    ): MeasureResult {
-        intermediateMeasureScope.lookaheadSize = lookaheadSize
-        [email protected] = lookaheadConstraints
-
-        return (intermediateMeasurable ?: IntermediateMeasurablePlaceable(measurable)).apply {
-            intermediateMeasurable = this
-            wrappedMeasurable = measurable
-        }.let { wrappedMeasurable ->
-            intermediateMeasureScope.measureBlock(wrappedMeasurable, constraints)
-        }
-    }
-
-    /**
-     * The function used to calculate minIntrinsicWidth for intermediate changes.
-     */
-    internal fun IntrinsicMeasureScope.minIntermediateIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int = NodeMeasuringIntrinsics.minWidth(
-        { intrinsicMeasurable, constraints ->
-            intermediateMeasureScope.measureBlock(intrinsicMeasurable, constraints)
-        },
-        this,
-        measurable,
-        height
-    )
-
-    /**
-     * The function used to calculate minIntrinsicHeight for intermediate changes.
-     */
-    internal fun IntrinsicMeasureScope.minIntermediateIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int = NodeMeasuringIntrinsics.minHeight(
-        { intrinsicMeasurable, constraints ->
-            intermediateMeasureScope.measureBlock(intrinsicMeasurable, constraints)
-        },
-        this,
-        measurable,
-        width
-    )
-
-    /**
-     * The function used to calculate maxIntrinsicWidth for intermediate changes.
-     */
-    internal fun IntrinsicMeasureScope.maxIntermediateIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int = NodeMeasuringIntrinsics.maxWidth(
-        { intrinsicMeasurable, constraints ->
-            intermediateMeasureScope.measureBlock(intrinsicMeasurable, constraints)
-        },
-        this,
-        measurable,
-        height
-    )
-
-    /**
-     * The function used to calculate maxIntrinsicHeight for intermediate changes.
-     */
-    internal fun IntrinsicMeasureScope.maxIntermediateIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int = NodeMeasuringIntrinsics.maxHeight(
-        { intrinsicMeasurable, constraints ->
-            intermediateMeasureScope.measureBlock(intrinsicMeasurable, constraints)
-        },
-        this,
-        measurable,
-        width
-    )
-
-    /**
-     * This class serves as a layer between measure and layout logic defined in the [measureBlock]
-     * and the child measurable (i.e. the next LayoutModifierNodeCoordinator). This class allows
-     * us to prevent any change in the [measureBlock] from impacting the child when there is _no_
-     * active changes in the given CoroutineScope.
-     */
-    private inner class IntermediateMeasurablePlaceable(
-        var wrappedMeasurable: Measurable
-    ) : Measurable, Placeable() {
-        var wrappedPlaceable: Placeable? = null
-        override fun measure(constraints: Constraints): Placeable {
-            wrappedPlaceable = if (isIntermediateChangeActive) {
-                wrappedMeasurable.measure(constraints).also {
-                    measurementConstraints = constraints
-                    measuredSize = IntSize(it.width, it.height)
-                }
-            } else {
-                // If the intermediate change isn't active, we'll measure with
-                // lookahead constraints and return lookahead size.
-                wrappedMeasurable.measure(lookaheadConstraints!!).also {
-                    measurementConstraints = lookaheadConstraints!!
-                    // isIntermediateChangeActive could change from false to true between
-                    // measurement & returning measure results. Use case: animateContentSize
-                    measuredSize = if (isIntermediateChangeActive) {
-                        IntSize(it.width, it.height)
-                    } else {
-                        intermediateMeasureScope.lookaheadSize
-                    }
-                }
-            }
-            return this
-        }
-
-        override fun placeAt(
-            position: IntOffset,
-            zIndex: Float,
-            layerBlock: (GraphicsLayerScope.() -> Unit)?
-        ) {
-            val offset =
-                if (isIntermediateChangeActive) position else IntOffset.Zero
-            with(node.coordinator!!.placementScope) {
-                layerBlock?.let {
-                    wrappedPlaceable?.placeWithLayer(
-                        offset,
-                        zIndex,
-                        it
-                    )
-                } ?: wrappedPlaceable?.place(offset, zIndex)
-            }
-        }
-
-        override val parentData: Any?
-            get() = wrappedMeasurable.parentData
-
-        override fun get(alignmentLine: AlignmentLine): Int =
-            wrappedPlaceable!!.get(alignmentLine)
-
-        override fun minIntrinsicWidth(height: Int): Int =
-            wrappedMeasurable.minIntrinsicWidth(height)
-
-        override fun maxIntrinsicWidth(height: Int): Int =
-            wrappedMeasurable.maxIntrinsicWidth(height)
-
-        override fun minIntrinsicHeight(width: Int): Int =
-            wrappedMeasurable.minIntrinsicHeight(width)
-
-        override fun maxIntrinsicHeight(width: Int): Int =
-            wrappedMeasurable.maxIntrinsicHeight(width)
-    }
-
-    @ExperimentalComposeUiApi
-    private inner class IntermediateMeasureScopeImpl : IntermediateMeasureScope,
-        CoroutineScope {
-        override var lookaheadSize: IntSize = IntSize.Zero
-
-        override fun LayoutCoordinates.toLookaheadCoordinates(): LayoutCoordinates =
-            with(closestLookaheadScope) { [email protected]() }
-
-        override val Placeable.PlacementScope.lookaheadScopeCoordinates: LayoutCoordinates
-            get() = with(closestLookaheadScope) {
-                [email protected]
-            }
-
-        override fun layout(
-            width: Int,
-            height: Int,
-            alignmentLines: Map<AlignmentLine, Int>,
-            rulers: (RulerScope.() -> Unit)?,
-            placementBlock: Placeable.PlacementScope.() -> Unit
-        ): MeasureResult {
-            checkMeasuredSize(width, height)
-            return object : MeasureResult {
-                override val width = width
-                override val height = height
-                override val alignmentLines = alignmentLines
-                override val rulers = rulers
-                override fun placeChildren() {
-                    coordinator!!.placementScope.placementBlock()
-                }
-            }
-        }
-
-        // Intermediate layout pass is post-lookahead. Therefore return false here.
-        override val isLookingAhead: Boolean
-            get() = false
-
-        override val layoutDirection: LayoutDirection
-            get() = coordinator!!.layoutDirection
-        override val density: Float
-            get() = coordinator!!.density
-        override val fontScale: Float
-            get() = coordinator!!.fontScale
-        override val coroutineContext: CoroutineContext
-            get() = coroutineScope.coroutineContext
-    }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index c3710d8..70d6cec 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -78,6 +78,7 @@
 ) {
     val compositeKeyHash = currentCompositeKeyHash
     val localMap = currentComposer.currentCompositionLocalMap
+    val materialized = currentComposer.materialize(modifier)
     ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
@@ -85,8 +86,8 @@
             set(localMap, SetResolvedCompositionLocals)
             @OptIn(ExperimentalComposeUiApi::class)
             set(compositeKeyHash, SetCompositeKeyHash)
+            set(materialized, SetModifier)
         },
-        skippableUpdate = materializerOf(modifier),
         content = content
     )
 }
@@ -390,3 +391,35 @@
         }
     }
 }
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal class ApproachIntrinsicsMeasureScope(
+    intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
+    override val layoutDirection: LayoutDirection,
+) : ApproachMeasureScope, ApproachIntrinsicMeasureScope by intrinsicMeasureScope {
+    override fun layout(
+        width: Int,
+        height: Int,
+        alignmentLines: Map<AlignmentLine, Int>,
+        rulers: (RulerScope.() -> Unit)?,
+        placementBlock: Placeable.PlacementScope.() -> Unit
+    ): MeasureResult {
+        val w = width.coerceAtLeast(0)
+        val h = height.coerceAtLeast(0)
+        checkMeasuredSize(w, h)
+        return object : MeasureResult {
+            override val width: Int
+                get() = w
+            override val height: Int
+                get() = h
+            override val alignmentLines: Map<AlignmentLine, Int>
+                get() = alignmentLines
+            override val rulers: (RulerScope.() -> Unit)?
+                get() = rulers
+
+            override fun placeChildren() {
+                // Intrinsics should never be placed
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
index 764c811..b5c58fa 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalComposeUiApi::class)
+
 package androidx.compose.ui.layout
 
 import androidx.compose.runtime.Applier
@@ -30,17 +32,22 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 
 /**
- * [LookaheadScope] starts a scope in which all layouts scope will receive a lookahead pass
- * preceding the main measure/layout pass. This lookahead pass will calculate the layout
- * size and position for all child layouts, and make the lookahead results available in
- * [Modifier.intermediateLayout]. [Modifier.intermediateLayout] gets invoked in the main
- * pass to allow transient layout changes in the main pass that gradually morph the layout
- * over the course of multiple frames until it catches up with lookahead.
+ * [LookaheadScope] creates a scope in which all layouts will first determine their destination
+ * layout through a lookahead pass, followed by an _approach_ pass to run the measurement
+ * and placement approach defined in [approachLayout] or [ApproachLayoutModifierNode], in order to
+ * gradually reach the destination.
+ *
+ * Note: [LookaheadScope] does not introduce a new [Layout] to the [content] passed in.
+ * All the [Layout]s in the [content] will have the same parent as they would without
+ * [LookaheadScope].
  *
  * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
+ * @see ApproachLayoutModifierNode
+ * @see approachLayout
  *
  * @param content The child composable to be laid out.
  */
@@ -65,36 +72,102 @@
     )
 }
 
-/**
- * Creates an intermediate layout intended to help morph the layout from the current layout
- * to the lookahead (i.e. pre-calculated future) layout.
- *
- * This modifier will be invoked _after_ lookahead pass and will have access to the lookahead
- * results in [measure]. Therefore:
- * 1) [intermediateLayout] measure/layout logic will not affect lookahead pass, but only be
- * invoked during the main measure/layout pass,
- * and 2) [measure] block can define intermediate changes that morphs the layout in the
- * main pass gradually until it converges lookahead pass.
- *
- * @sample androidx.compose.ui.samples.IntermediateLayoutSample
- */
 @ExperimentalComposeUiApi
+@Deprecated(
+    "IntermediateMeasureScope has been renamed to ApproachMeasureScope",
+    replaceWith = ReplaceWith("ApproachMeasureScope")
+)
+interface IntermediateMeasureScope : ApproachMeasureScope, CoroutineScope, LookaheadScope
+
+@ExperimentalComposeUiApi
+@Deprecated(
+    "intermediateLayout has been replaced with approachLayout, and requires an" +
+        "additional parameter to signal if the approach is complete.",
+    replaceWith = ReplaceWith(
+        "approachLayout(isMeasurementApproachComplete = ," +
+            "approachMeasure = measure)"
+    )
+)
 fun Modifier.intermediateLayout(
+    @Suppress("DEPRECATION")
     measure: IntermediateMeasureScope.(
         measurable: Measurable,
         constraints: Constraints,
     ) -> MeasureResult,
-): Modifier = this then IntermediateLayoutElement(measure)
+) = this then IntermediateLayoutElement(measure)
 
+/**
+ * Creates an approach layout intended to help gradually approach the destination layout calculated
+ * in the lookahead pass. This can be particularly helpful when the destination layout is
+ * anticipated to change drastically and would consequently result in visual disruptions.
+ *
+ * In order to create a smooth approach, an interpolation (often through animations) can be used
+ * in [approachMeasure] to interpolate the measurement or placement from a previously recorded size
+ * and/or position to the destination/target size and/or position. The destination size is
+ * available in [ApproachMeasureScope] as [ApproachMeasureScope.lookaheadSize]. And the target
+ * position can also be acquired in [ApproachMeasureScope] during placement by using
+ * [LookaheadScope.localLookaheadPositionOf] with the layout's
+ * [Placeable.PlacementScope.coordinates]. The sample code below illustrates how that can be
+ * achieved.
+ *
+ * [isMeasurementApproachComplete] signals whether the measurement has already reached the
+ * destination size. It will be queried after the destination has been determined by the lookahead
+ * pass, before [approachMeasure] is invoked. The lookahead size is provided to
+ * [isMeasurementApproachComplete] for convenience in deciding whether the destination size has
+ * been reached.
+ *
+ * [isPlacementApproachComplete] indicates whether the position has approached
+ * destination defined by the lookahead, hence it's a signal to the system for whether additional
+ * approach placements are necessary. [isPlacementApproachComplete] will be invoked after the
+ * destination position has been determined by lookahead pass, and before the placement phase in
+ * [approachMeasure].
+ *
+ * Once both [isMeasurementApproachComplete] and [isPlacementApproachComplete] return true, the
+ * system may skip approach pass until additional approach passes are necessary as indicated by
+ * [isMeasurementApproachComplete] and [isPlacementApproachComplete].
+ *
+ * **IMPORTANT**:
+ * It is important to be accurate in [isPlacementApproachComplete] and
+ * [isMeasurementApproachComplete]. A prolonged indication of incomplete approach will prevent the
+ * system from potentially skipping approach pass when possible.
+ *
+ * @see ApproachLayoutModifierNode
+ * @sample androidx.compose.ui.samples.approachLayoutSample
+ */
+@ExperimentalComposeUiApi
+fun Modifier.approachLayout(
+    isMeasurementApproachComplete: (lookaheadSize: IntSize) -> Boolean,
+    isPlacementApproachComplete: Placeable.PlacementScope.(
+        lookaheadCoordinates: LayoutCoordinates
+    ) -> Boolean = defaultPlacementApproachComplete,
+    approachMeasure: ApproachMeasureScope.(
+        measurable: Measurable,
+        constraints: Constraints,
+    ) -> MeasureResult,
+): Modifier = this then ApproachLayoutElement(
+    isMeasurementApproachComplete = isMeasurementApproachComplete,
+    isPlacementApproachComplete = isPlacementApproachComplete,
+    approachMeasure = approachMeasure
+)
+
+private val defaultPlacementApproachComplete: Placeable.PlacementScope.(
+    lookaheadCoordinates: LayoutCoordinates
+) -> Boolean = { true }
+
+@Suppress("DEPRECATION")
 @OptIn(ExperimentalComposeUiApi::class)
 private data class IntermediateLayoutElement(
     val measure: IntermediateMeasureScope.(
         measurable: Measurable,
         constraints: Constraints,
     ) -> MeasureResult,
-) : ModifierNodeElement<IntermediateLayoutModifierNode>() {
-    override fun create() = IntermediateLayoutModifierNode(measure)
-    override fun update(node: IntermediateLayoutModifierNode) {
+) : ModifierNodeElement<IntermediateLayoutModifierNodeImpl>() {
+    override fun create() =
+        IntermediateLayoutModifierNodeImpl(
+            measure,
+        )
+
+    override fun update(node: IntermediateLayoutModifierNodeImpl) {
         node.measureBlock = measure
     }
 
@@ -104,35 +177,107 @@
     }
 }
 
-/**
- * [IntermediateMeasureScope] provides access to lookahead results to allow
- * [intermediateLayout] to leverage lookahead results to define intermediate measurements
- * and placements to gradually converge with lookahead.
- *
- * [IntermediateMeasureScope.lookaheadSize] provides the target size of the layout.
- * [IntermediateMeasureScope] is also a [LookaheadScope], thus allowing layouts to
- * read their lookahead [LayoutCoordinates] during placement using
- * [LookaheadScope.toLookaheadCoordinates], as well as the lookahead [LayoutCoordinates] of the
- * closest lookahead scope via [LookaheadScope.lookaheadScopeCoordinates].
- * By knowing the target size and position, layout adjustments such as animations can be defined
- * in [intermediateLayout] to morph the layout gradually in both size and position
- * to arrive at its precalculated bounds.
- *
- * Note that [IntermediateMeasureScope] is the closest lookahead scope in the tree.
- * This [LookaheadScope] enables convenient query of the layout's relative position to the
- * [LookaheadScope]. Hence it becomes straightforward to animate position relative to the closest
- * scope, which usually yields a natural looking animation, unless there are specific UX
- * requirements to change position relative to a particular [LookaheadScope].
- *
- * [IntermediateMeasureScope] is a CoroutineScope, as a convenient scope for all the
- * coroutine-based intermediate changes (e.g. animations) to be launched from.
- */
-@ExperimentalComposeUiApi
-sealed interface IntermediateMeasureScope : LookaheadScope, CoroutineScope, MeasureScope {
-    /**
-     * Indicates the target size of the [intermediateLayout].
-     */
-    val lookaheadSize: IntSize
+@Suppress("DEPRECATION")
+@OptIn(ExperimentalComposeUiApi::class)
+private class IntermediateLayoutModifierNodeImpl(
+    var measureBlock: IntermediateMeasureScope.(
+        measurable: Measurable,
+        constraints: Constraints,
+    ) -> MeasureResult,
+) : ApproachLayoutModifierNode, Modifier.Node() {
+    private var intermediateMeasureScope: IntermediateMeasureScopeImpl? = null
+
+    private inner class IntermediateMeasureScopeImpl(
+        val approachScope: ApproachMeasureScopeImpl
+    ) : IntermediateMeasureScope, LookaheadScope by approachScope,
+        ApproachMeasureScope by approachScope, CoroutineScope {
+        override val coroutineContext: CoroutineContext
+            get() = this@IntermediateLayoutModifierNodeImpl.coroutineScope.coroutineContext
+    }
+
+    override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+        // Important: Returning false here is strongly discouraged as it'll prevent layout
+        // performance optimization. This ModifierNodeImpl is only intended to help devs transition
+        // over to the new ApproachLayoutNodeModifier, and it'll be removed after a couple of
+        // releases.
+        return false
+    }
+
+    override fun ApproachMeasureScope.approachMeasure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val scope = intermediateMeasureScope
+        val newScope = if (scope?.approachScope != this) {
+            IntermediateMeasureScopeImpl(this as ApproachMeasureScopeImpl)
+        } else {
+            scope
+        }
+        intermediateMeasureScope = newScope
+        return with(newScope) {
+            measureBlock(measurable, constraints)
+        }
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private data class ApproachLayoutElement(
+    val approachMeasure: ApproachMeasureScope.(
+        measurable: Measurable,
+        constraints: Constraints,
+    ) -> MeasureResult,
+    val isMeasurementApproachComplete: (IntSize) -> Boolean,
+    val isPlacementApproachComplete: Placeable.PlacementScope.(
+        lookaheadCoordinates: LayoutCoordinates
+    ) -> Boolean = defaultPlacementApproachComplete,
+) : ModifierNodeElement<ApproachLayoutModifierNodeImpl>() {
+    override fun create() =
+        ApproachLayoutModifierNodeImpl(
+            approachMeasure,
+            isMeasurementApproachComplete,
+            isPlacementApproachComplete
+        )
+
+    override fun update(node: ApproachLayoutModifierNodeImpl) {
+        node.measureBlock = approachMeasure
+        node.isMeasurementApproachComplete = isMeasurementApproachComplete
+        node.isPlacementApproachComplete = isPlacementApproachComplete
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "approachLayout"
+        properties["approachMeasure"] = approachMeasure
+        properties["isMeasurementApproachComplete"] = isMeasurementApproachComplete
+        properties["isPlacementApproachComplete"] = isPlacementApproachComplete
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private class ApproachLayoutModifierNodeImpl(
+    var measureBlock: ApproachMeasureScope.(
+        measurable: Measurable,
+        constraints: Constraints,
+    ) -> MeasureResult,
+    var isMeasurementApproachComplete: (IntSize) -> Boolean,
+    var isPlacementApproachComplete:
+    Placeable.PlacementScope.(LayoutCoordinates) -> Boolean,
+) : ApproachLayoutModifierNode, Modifier.Node() {
+    override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+        return isMeasurementApproachComplete.invoke(lookaheadSize)
+    }
+
+    override fun Placeable.PlacementScope.isPlacementApproachComplete(
+        lookaheadCoordinates: LayoutCoordinates
+    ): Boolean {
+        return isPlacementApproachComplete.invoke(this, lookaheadCoordinates)
+    }
+
+    override fun ApproachMeasureScope.approachMeasure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        return measureBlock(measurable, constraints)
+    }
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MultiContentMeasurePolicy.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MultiContentMeasurePolicy.kt
index d44a15af..a7d54c4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MultiContentMeasurePolicy.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MultiContentMeasurePolicy.kt
@@ -197,32 +197,43 @@
 @PublishedApi
 internal fun createMeasurePolicy(
     measurePolicy: MultiContentMeasurePolicy
-): MeasurePolicy =
-    with(measurePolicy) {
-        object : MeasurePolicy {
-            override fun MeasureScope.measure(
-                measurables: List<Measurable>,
-                constraints: Constraints
-            ) = measure(getChildrenOfVirtualChildren(this), constraints)
+): MeasurePolicy = MultiContentMeasurePolicyImpl(measurePolicy)
 
-            override fun IntrinsicMeasureScope.minIntrinsicWidth(
-                measurables: List<IntrinsicMeasurable>,
-                height: Int
-            ) = minIntrinsicWidth(getChildrenOfVirtualChildren(this), height)
-
-            override fun IntrinsicMeasureScope.minIntrinsicHeight(
-                measurables: List<IntrinsicMeasurable>,
-                width: Int
-            ) = minIntrinsicHeight(getChildrenOfVirtualChildren(this), width)
-
-            override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-                measurables: List<IntrinsicMeasurable>,
-                height: Int
-            ) = maxIntrinsicWidth(getChildrenOfVirtualChildren(this), height)
-
-            override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-                measurables: List<IntrinsicMeasurable>,
-                width: Int
-            ) = maxIntrinsicHeight(getChildrenOfVirtualChildren(this), width)
-        }
+internal data class MultiContentMeasurePolicyImpl(
+    val measurePolicy: MultiContentMeasurePolicy
+) : MeasurePolicy {
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ) = with(measurePolicy) {
+        measure(getChildrenOfVirtualChildren(this@measure), constraints)
     }
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = with(measurePolicy) {
+        minIntrinsicWidth(getChildrenOfVirtualChildren(this@minIntrinsicWidth), height)
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = with(measurePolicy) {
+        minIntrinsicHeight(getChildrenOfVirtualChildren(this@minIntrinsicHeight), width)
+    }
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = with(measurePolicy) {
+        maxIntrinsicWidth(getChildrenOfVirtualChildren(this@maxIntrinsicWidth), height)
+    }
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = with(measurePolicy) {
+        maxIntrinsicHeight(getChildrenOfVirtualChildren(this@maxIntrinsicHeight), width)
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
index 9117fcb..0fec577 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.Nodes
 import androidx.compose.ui.node.visitAncestors
+import androidx.compose.ui.util.fastMap
 
 /**
  * An opaque key-value holder of [ModifierLocal]s to be used with [ModifierLocalModifierNode].
@@ -77,11 +78,13 @@
 }
 
 internal class MultiLocalMap(
+    entry1: Pair<ModifierLocal<*>, Any?>,
     vararg entries: Pair<ModifierLocal<*>, Any?>
 ) : ModifierLocalMap() {
     private val map = mutableStateMapOf<ModifierLocal<*>, Any?>()
 
     init {
+        map += entry1
         map.putAll(entries.toMap())
     }
 
@@ -99,7 +102,7 @@
 
 internal object EmptyMap : ModifierLocalMap() {
     override fun <T> set(key: ModifierLocal<T>, value: T) = error("")
-    override fun <T> get(key: ModifierLocal<T>): T? = error("")
+    override fun <T> get(key: ModifierLocal<T>): T = error("")
     override fun contains(key: ModifierLocal<*>): Boolean = false
 }
 
@@ -199,8 +202,14 @@
  * Creates a [ModifierLocalMap] with several keys, all initialized with values of null
  */
 fun modifierLocalMapOf(
+    key1: ModifierLocal<*>,
+    key2: ModifierLocal<*>,
     vararg keys: ModifierLocal<*>
-): ModifierLocalMap = MultiLocalMap(*keys.map { it to null }.toTypedArray())
+): ModifierLocalMap = MultiLocalMap(
+    key1 to null,
+    key2 to null,
+    *keys.map { it to null }.toTypedArray()
+)
 
 /**
  * Creates a [ModifierLocalMap] with multiple keys and values. The provided [entries] should have
@@ -208,5 +217,36 @@
  * corresponding value.
  */
 fun modifierLocalMapOf(
+    entry1: Pair<ModifierLocal<*>, Any>,
+    entry2: Pair<ModifierLocal<*>, Any>,
     vararg entries: Pair<ModifierLocal<*>, Any>
-): ModifierLocalMap = MultiLocalMap(*entries)
+): ModifierLocalMap = MultiLocalMap(entry1, entry2, *entries)
+
+// b/280116113.
+@Deprecated(
+    message = "Use a different overloaded version of this function",
+    level = DeprecationLevel.HIDDEN
+)
+fun modifierLocalMapOf(
+    vararg keys: ModifierLocal<*>
+): ModifierLocalMap = when (keys.size) {
+        0 -> EmptyMap
+        1 -> SingleLocalMap(keys.first())
+        else -> MultiLocalMap(
+            keys.first() to null,
+            *keys.drop(1).fastMap { it to null }.toTypedArray()
+        )
+    }
+
+// b/280116113.
+@Deprecated(
+    message = "Use a different overloaded version of this function",
+    level = DeprecationLevel.HIDDEN
+)
+fun modifierLocalMapOf(
+    vararg entries: Pair<ModifierLocal<*>, Any>
+): ModifierLocalMap = when (entries.size) {
+    0 -> EmptyMap
+    1 -> MultiLocalMap(entries.first())
+    else -> MultiLocalMap(entries.first(), *entries.drop(1).toTypedArray())
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
index 255fa31..3699ba1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 
@@ -331,6 +332,26 @@
 fun DelegatableNode.requireLayoutDirection(): LayoutDirection = requireLayoutNode().layoutDirection
 
 /**
+ * Returns the [LayoutCoordinates] of this node.
+ *
+ * To get a signal when the [LayoutCoordinates] become available, or when its parent places it,
+ * implement [LayoutAwareModifierNode].
+ *
+ * @throws IllegalStateException When either this node is not attached, or the [LayoutCoordinates]
+ * object is not attached.
+ */
+fun DelegatableNode.requireLayoutCoordinates(): LayoutCoordinates {
+    checkPrecondition(node.isAttached) {
+        "Cannot get LayoutCoordinates, Modifier.Node is not attached."
+    }
+    val coordinates = requireCoordinator(Nodes.Layout).coordinates
+    checkPrecondition(coordinates.isAttached) {
+        "LayoutCoordinates is not attached."
+    }
+    return coordinates
+}
+
+/**
  * Invalidates the subtree of this layout, including layout, drawing, parent data, etc.
  *
  * Calling this method can be a relatively expensive operation as it will cause the
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
index 0d0481c..4efd075 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
@@ -36,6 +36,7 @@
         // definition.
         aggregateChildKindSet = 0
     }
+
     // BackwardsCompatNode uses this to determine if it is in a "chain update" or not. If attach
     // has been run on the tail node, then we can assume that it is a chain update. Importantly,
     // this is different than using isAttached.
@@ -116,17 +117,25 @@
         }
     }
 
-    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
-        // before rerunning the user's measure block reset previous measuredByParent for children
-        layoutNode.forEachChild {
-            it.measurePassDelegate.measuredByParent = LayoutNode.UsageByParent.NotUsed
-        }
+    override fun measure(constraints: Constraints): Placeable {
+        @Suppress("NAME_SHADOWING") val constraints =
+            if (forceMeasureWithLookaheadConstraints) {
+                lookaheadDelegate!!.constraints
+            } else {
+                constraints
+            }
+        return performingMeasure(constraints) {
+            // before rerunning the user's measure block reset previous measuredByParent for children
+            layoutNode.forEachChild {
+                it.measurePassDelegate.measuredByParent = LayoutNode.UsageByParent.NotUsed
+            }
 
-        measureResult = with(layoutNode.measurePolicy) {
-            measure(layoutNode.childMeasurables, constraints)
+            measureResult = with(layoutNode.measurePolicy) {
+                measure(layoutNode.childMeasurables, constraints)
+            }
+            onMeasured()
+            this
         }
-        onMeasured()
-        this
     }
 
     override fun minIntrinsicWidth(height: Int) =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutAwareModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutAwareModifierNode.kt
index 6637cb9..48a6883 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutAwareModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutAwareModifierNode.kt
@@ -39,6 +39,9 @@
      * [onPlaced] is called after the parent [LayoutModifier] and parent layout has
      * been placed and before child [LayoutModifier] is placed. This allows child
      * [LayoutModifier] to adjust its own placement based on where the parent is.
+     *
+     * If you only need to access the current [LayoutCoordinates] at a single point in time from
+     * outside this method, use [currentLayoutCoordinates].
      */
     fun onPlaced(coordinates: LayoutCoordinates) {}
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
index e296920..641eaae 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
@@ -16,9 +16,13 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.ApproachIntrinsicMeasureScope
+import androidx.compose.ui.layout.ApproachIntrinsicsMeasureScope
+import androidx.compose.ui.layout.ApproachMeasureScope
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.IntrinsicsMeasureScope
@@ -158,12 +162,104 @@
 
 internal fun LayoutModifierNode.requestRemeasure() = requireLayoutNode().requestRemeasure()
 
+@OptIn(ExperimentalComposeUiApi::class)
 internal object NodeMeasuringIntrinsics {
     // Fun interface for measure block to avoid autoBoxing of Constraints
     internal fun interface MeasureBlock {
         fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult
     }
 
+    internal fun interface ApproachMeasureBlock {
+        fun ApproachMeasureScope.measure(
+            measurable: Measurable,
+            constraints: Constraints
+        ): MeasureResult
+    }
+
+    internal fun minWidth(
+        measureBlock: ApproachMeasureBlock,
+        intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
+        intrinsicMeasurable: IntrinsicMeasurable,
+        h: Int
+    ): Int {
+        val measurable = DefaultIntrinsicMeasurable(
+            intrinsicMeasurable,
+            IntrinsicMinMax.Min,
+            IntrinsicWidthHeight.Width
+        )
+        val constraints = Constraints(maxHeight = h)
+        val layoutResult = with(measureBlock) {
+            ApproachIntrinsicsMeasureScope(
+                intrinsicMeasureScope,
+                intrinsicMeasureScope.layoutDirection
+            ).measure(measurable, constraints)
+        }
+        return layoutResult.width
+    }
+
+    internal fun minHeight(
+        measureBlock: ApproachMeasureBlock,
+        intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
+        intrinsicMeasurable: IntrinsicMeasurable,
+        w: Int
+    ): Int {
+        val measurable = DefaultIntrinsicMeasurable(
+            intrinsicMeasurable,
+            IntrinsicMinMax.Min,
+            IntrinsicWidthHeight.Height
+        )
+        val constraints = Constraints(maxWidth = w)
+        val layoutResult = with(measureBlock) {
+            ApproachIntrinsicsMeasureScope(
+                intrinsicMeasureScope,
+                intrinsicMeasureScope.layoutDirection
+            ).measure(measurable, constraints)
+        }
+        return layoutResult.height
+    }
+
+    internal fun maxWidth(
+        measureBlock: ApproachMeasureBlock,
+        intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
+        intrinsicMeasurable: IntrinsicMeasurable,
+        h: Int
+    ): Int {
+        val measurable = DefaultIntrinsicMeasurable(
+            intrinsicMeasurable,
+            IntrinsicMinMax.Max,
+            IntrinsicWidthHeight.Width
+        )
+        val constraints = Constraints(maxHeight = h)
+        val layoutResult = with(measureBlock) {
+            ApproachIntrinsicsMeasureScope(
+                intrinsicMeasureScope,
+                intrinsicMeasureScope.layoutDirection
+            ).measure(measurable, constraints)
+        }
+        return layoutResult.width
+    }
+
+    internal fun maxHeight(
+        measureBlock: ApproachMeasureBlock,
+        intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
+        intrinsicMeasurable: IntrinsicMeasurable,
+        w: Int
+    ): Int {
+        val measurable = DefaultIntrinsicMeasurable(
+            intrinsicMeasurable,
+            IntrinsicMinMax.Max,
+            IntrinsicWidthHeight.Height
+        )
+        val constraints = Constraints(maxWidth = w)
+        val layoutResult = with(measureBlock) {
+            ApproachIntrinsicsMeasureScope(
+                intrinsicMeasureScope,
+                intrinsicMeasureScope.layoutDirection
+            ).measure(measurable, constraints)
+        }
+        return layoutResult.height
+    }
+
     internal fun minWidth(
         measureBlock: MeasureBlock,
         intrinsicMeasureScope: IntrinsicMeasureScope,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
index 5639fba..ae43021 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
@@ -25,13 +25,14 @@
 import androidx.compose.ui.graphics.PaintingStyle
 import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScopeImpl
 import androidx.compose.ui.layout.HorizontalAlignmentLine
-import androidx.compose.ui.layout.IntermediateLayoutModifierNode
 import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
 
 @OptIn(ExperimentalComposeUiApi::class)
 internal class LayoutModifierNodeCoordinator(
@@ -39,19 +40,37 @@
     measureNode: LayoutModifierNode,
 ) : NodeCoordinator(layoutNode) {
     var layoutModifierNode: LayoutModifierNode = measureNode
-        internal set
+        internal set(value) {
+            if (value is ApproachLayoutModifierNode) {
+                approachMeasureScope = approachMeasureScope?.also {
+                    it.approachNode = value
+                } ?: ApproachMeasureScopeImpl(this, value)
+            } else {
+                approachMeasureScope = null
+            }
+            field = value
+        }
 
     override val tail: Modifier.Node
         get() = layoutModifierNode.node
 
     val wrappedNonNull: NodeCoordinator get() = wrapped!!
 
-    private var lookaheadConstraints: Constraints? = null
+    internal var lookaheadConstraints: Constraints? = null
 
     override var lookaheadDelegate: LookaheadDelegate? =
         if (layoutNode.lookaheadRoot != null) LookaheadDelegateForLayoutModifierNode() else null
 
     /**
+     * Lazily initialized IntermediateMeasureScope. This is only initialized when the
+     * current modifier is an ApproachLayoutModifierNode.
+     */
+    internal var approachMeasureScope: ApproachMeasureScopeImpl? =
+        (measureNode as? ApproachLayoutModifierNode)?.let {
+            ApproachMeasureScopeImpl(this, it)
+        }
+
+    /**
      * LookaheadDelegate impl for when the modifier is any [LayoutModifier] except
      * IntermediateLayoutModifier. This impl will invoke [LayoutModifier.measure] for
      * the lookahead measurement.
@@ -118,50 +137,85 @@
     }
 
     override fun measure(constraints: Constraints): Placeable {
-        performingMeasure(constraints) {
-            with(layoutModifierNode) {
-                measureResult = if (this is IntermediateLayoutModifierNode) {
-                    intermediateMeasure(
-                        wrappedNonNull,
-                        constraints,
-                        lookaheadDelegate!!.measureResult.let { IntSize(it.width, it.height) },
-                        lookaheadConstraints!!
-                    )
-                } else {
-                    measure(wrappedNonNull, constraints)
-                }
-                this@LayoutModifierNodeCoordinator
+        @Suppress("NAME_SHADOWING")
+        val constraints = if (forceMeasureWithLookaheadConstraints) {
+            requireNotNull(lookaheadConstraints) {
+                "Lookahead constraints cannot be null in approach pass."
             }
+        } else {
+            constraints
+        }
+        performingMeasure(constraints) {
+            measureResult = approachMeasureScope?.let { scope ->
+                // approachMeasureScope is created/updated when layoutModifierNode is set. An
+                // ApproachLayoutModifierNode will lead to a non-null approachMeasureScope.
+                with(scope.approachNode) {
+                    scope.approachMeasureRequired = !isMeasurementApproachComplete(
+                        scope.lookaheadSize
+                    ) || constraints != lookaheadConstraints
+                    if (!scope.approachMeasureRequired) {
+                        // In the future we'll skip the invocation of this measure block when
+                        // no approach is needed. For now, we'll ignore the constraints change
+                        // in the measure block when it's declared approach complete.
+                        wrappedNonNull.forceMeasureWithLookaheadConstraints = true
+                    }
+                    val result = scope.approachMeasure(wrappedNonNull, constraints)
+                    wrappedNonNull.forceMeasureWithLookaheadConstraints = false
+                    val reachedLookaheadSize = result.width == lookaheadDelegate!!.width &&
+                        result.height == lookaheadDelegate!!.height
+                    if (!scope.approachMeasureRequired &&
+                        wrappedNonNull.size == wrappedNonNull.lookaheadDelegate?.size &&
+                        !reachedLookaheadSize
+                    ) {
+                        object : MeasureResult by result {
+                            override val width = lookaheadDelegate!!.width
+                            override val height = lookaheadDelegate!!.height
+                        }
+                    } else {
+                        result
+                    }
+                }
+            } ?: with(layoutModifierNode) {
+                measure(wrappedNonNull, constraints)
+            }
+            this@LayoutModifierNodeCoordinator
         }
         onMeasured()
         return this
     }
 
-    override fun minIntrinsicWidth(height: Int): Int {
-        return (layoutModifierNode as? IntermediateLayoutModifierNode)?.run {
-            minIntermediateIntrinsicWidth(wrappedNonNull, height)
+    override fun minIntrinsicWidth(height: Int): Int =
+        approachMeasureScope?.run {
+            with(approachNode) {
+                minApproachIntrinsicWidth([email protected], height)
+            }
         } ?: with(layoutModifierNode) {
             minIntrinsicWidth(wrappedNonNull, height)
         }
-    }
 
     override fun maxIntrinsicWidth(height: Int): Int =
-        (layoutModifierNode as? IntermediateLayoutModifierNode)?.run {
-            maxIntermediateIntrinsicWidth(wrappedNonNull, height)
+        approachMeasureScope?.run {
+            with(approachNode) {
+                maxApproachIntrinsicWidth([email protected], height)
+            }
         } ?: with(layoutModifierNode) {
             maxIntrinsicWidth(wrappedNonNull, height)
         }
 
     override fun minIntrinsicHeight(width: Int): Int =
-        (layoutModifierNode as? IntermediateLayoutModifierNode)?.run {
-            minIntermediateIntrinsicHeight(wrappedNonNull, width)
+        approachMeasureScope?.run {
+            with(approachNode) {
+                minApproachIntrinsicHeight([email protected], width)
+            }
         } ?: with(layoutModifierNode) {
             minIntrinsicHeight(wrappedNonNull, width)
         }
 
     override fun maxIntrinsicHeight(width: Int): Int =
-        (layoutModifierNode as? IntermediateLayoutModifierNode)?.run {
-            maxIntermediateIntrinsicHeight(wrappedNonNull, width)
+        approachMeasureScope?.run {
+            with(approachNode) {
+                maxApproachIntrinsicHeight([email protected], width)
+            }
         } ?: with(layoutModifierNode) {
             maxIntrinsicHeight(wrappedNonNull, width)
         }
@@ -179,7 +233,20 @@
         // our position in order ot know how to offset the value we provided).
         if (isShallowPlacing) return
         onPlaced()
+        approachMeasureScope?.let {
+            with(it.approachNode) {
+                val approachComplete = with(placementScope) {
+                    isPlacementApproachComplete(
+                        lookaheadDelegate!!.lookaheadLayoutCoordinates
+                    ) && !it.approachMeasureRequired &&
+                        size == lookaheadDelegate?.size &&
+                        wrappedNonNull.size == wrappedNonNull.lookaheadDelegate?.size
+                }
+                wrappedNonNull.forcePlaceWithLookaheadOffset = approachComplete
+            }
+        }
         measureResult.placeChildren()
+        wrappedNonNull.forceMeasureWithLookaheadConstraints = false
     }
 
     override fun calculateAlignmentLine(alignmentLine: AlignmentLine): Int {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 8bd59f3..1ab33b5 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -413,6 +413,7 @@
         //  on a per-node level. This should preserve current behavior for now.
         requireOwner().onSemanticsChange()
     }
+
     internal val collapsedSemantics: SemanticsConfiguration?
         get() {
             if (!nodes.has(Nodes.Semantics) || _collapsedSemantics != null) {
@@ -475,6 +476,10 @@
             // Favor lookahead root from parent than locally created scope, unless current node
             // is a virtual lookahead root
             lookaheadRoot = _foldedParent?.lookaheadRoot ?: lookaheadRoot
+            if (lookaheadRoot == null && nodes.has(Nodes.IntermediateMeasure)) {
+                // This could happen when movableContent containing intermediateLayout is moved
+                lookaheadRoot = this
+            }
         }
         if (!isDeactivated) {
             nodes.markAsAttached()
@@ -855,10 +860,8 @@
             field = value
             nodes.updateFrom(value)
             layoutDelegate.updateParentData()
-            if (nodes.has(Nodes.IntermediateMeasure)) {
-                if (lookaheadRoot == null) {
-                    lookaheadRoot = this
-                }
+            if (lookaheadRoot == null && nodes.has(Nodes.IntermediateMeasure)) {
+                lookaheadRoot = this
             }
         }
 
@@ -1051,6 +1054,12 @@
         }
     }
 
+    internal fun invalidateOnPositioned() {
+        // If we've already scheduled a measure, the positioned callbacks will get called anyway
+        if (layoutPending || measurePending || needsOnPositionedDispatch) return
+        requireOwner().requestOnPositionedCallback(this)
+    }
+
     private fun invalidateFocusOnAttach() {
         if (nodes.has(FocusTarget or FocusProperties or FocusEvent)) {
             nodes.headToTail {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
index a27624f..2c70aab 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
@@ -359,6 +359,12 @@
     override val coordinates: LayoutCoordinates
         get() = lookaheadLayoutCoordinates
 
+    internal val size: IntSize
+        get() = IntSize(width, height)
+
+    internal val constraints: Constraints
+        get() = measurementConstraints
+
     val lookaheadLayoutCoordinates = LookaheadLayoutCoordinates(this)
     override val alignmentLinesOwner: AlignmentLinesOwner
         get() = coordinator.layoutNode.layoutDelegate.lookaheadAlignmentLinesOwner!!
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index 223ff3b..1323a40 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -671,6 +671,7 @@
      */
     fun onNodeDetached(node: LayoutNode) {
         relayoutNodes.remove(node)
+        onPositionedDispatcher.remove(node)
     }
 
     private val LayoutNode.measureAffectsParent
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index c94caa9..9ebbc4f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -61,6 +61,8 @@
     LayoutCoordinates,
     OwnerScope {
 
+    internal var forcePlaceWithLookaheadOffset: Boolean = false
+    internal var forceMeasureWithLookaheadConstraints: Boolean = false
     abstract val tail: Modifier.Node
 
     internal var wrapped: NodeCoordinator? = null
@@ -168,6 +170,7 @@
                 ) {
                     alignmentLinesOwner.alignmentLines.onAlignmentsChanged()
 
+                    @Suppress("PrimitiveInCollection")
                     val oldLines = oldAlignmentLines
                         ?: (mutableMapOf<AlignmentLine, Int>().also { oldAlignmentLines = it })
                     oldLines.clear()
@@ -308,7 +311,11 @@
         zIndex: Float,
         layerBlock: (GraphicsLayerScope.() -> Unit)?
     ) {
-        placeSelf(position, zIndex, layerBlock)
+        if (forcePlaceWithLookaheadOffset) {
+            placeSelf(lookaheadDelegate!!.position, zIndex, layerBlock)
+        } else {
+            placeSelf(position, zIndex, layerBlock)
+        }
     }
 
     private fun placeSelf(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
index b884763..3cde115 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
@@ -16,8 +16,10 @@
 
 package androidx.compose.ui.node
 
+import androidx.collection.mutableObjectIntMapOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.classKeyForObject
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.focus.FocusEventModifierNode
 import androidx.compose.ui.focus.FocusProperties
@@ -32,7 +34,7 @@
 import androidx.compose.ui.input.rotary.RotaryInputModifierNode
 import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.internal.checkPreconditionNotNull
-import androidx.compose.ui.layout.IntermediateLayoutModifierNode
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
 import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.OnGloballyPositionedModifier
 import androidx.compose.ui.layout.OnPlacedModifier
@@ -87,7 +89,7 @@
     @JvmStatic
     inline val GlobalPositionAware get() = NodeKind<GlobalPositionAwareModifierNode>(0b1 shl 8)
     @JvmStatic
-    inline val IntermediateMeasure get() = NodeKind<IntermediateLayoutModifierNode>(0b1 shl 9)
+    inline val IntermediateMeasure get() = NodeKind<ApproachLayoutModifierNode>(0b1 shl 9)
     @JvmStatic
     inline val FocusTarget get() = NodeKind<FocusTargetNode>(0b1 shl 10)
     @JvmStatic
@@ -152,65 +154,68 @@
     return mask
 }
 
+private val classToKindSetMap = mutableObjectIntMapOf<Any>()
 @OptIn(ExperimentalComposeUiApi::class)
 internal fun calculateNodeKindSetFrom(node: Modifier.Node): Int {
     // This function does not take delegates into account, as a result, the kindSet will never
     // change, so if it is non-zero, it means we've already calculated it and we can just bail
     // early here.
     if (node.kindSet != 0) return node.kindSet
-    var mask = Nodes.Any.mask
-    if (node is LayoutModifierNode) {
-        mask = mask or Nodes.Layout
+    return classToKindSetMap.getOrPut(classKeyForObject(node)) {
+        var mask = Nodes.Any.mask
+        if (node is LayoutModifierNode) {
+            mask = mask or Nodes.Layout
+        }
+        if (node is DrawModifierNode) {
+            mask = mask or Nodes.Draw
+        }
+        if (node is SemanticsModifierNode) {
+            mask = mask or Nodes.Semantics
+        }
+        if (node is PointerInputModifierNode) {
+            mask = mask or Nodes.PointerInput
+        }
+        if (node is ModifierLocalModifierNode) {
+            mask = mask or Nodes.Locals
+        }
+        if (node is ParentDataModifierNode) {
+            mask = mask or Nodes.ParentData
+        }
+        if (node is LayoutAwareModifierNode) {
+            mask = mask or Nodes.LayoutAware
+        }
+        if (node is GlobalPositionAwareModifierNode) {
+            mask = mask or Nodes.GlobalPositionAware
+        }
+        if (node is ApproachLayoutModifierNode) {
+            mask = mask or Nodes.IntermediateMeasure
+        }
+        if (node is FocusTargetNode) {
+            mask = mask or Nodes.FocusTarget
+        }
+        if (node is FocusPropertiesModifierNode) {
+            mask = mask or Nodes.FocusProperties
+        }
+        if (node is FocusEventModifierNode) {
+            mask = mask or Nodes.FocusEvent
+        }
+        if (node is KeyInputModifierNode) {
+            mask = mask or Nodes.KeyInput
+        }
+        if (node is RotaryInputModifierNode) {
+            mask = mask or Nodes.RotaryInput
+        }
+        if (node is CompositionLocalConsumerModifierNode) {
+            mask = mask or Nodes.CompositionLocalConsumer
+        }
+        if (node is SoftKeyboardInterceptionModifierNode) {
+            mask = mask or Nodes.SoftKeyboardKeyInput
+        }
+        if (node is TraversableNode) {
+            mask = mask or Nodes.Traversable
+        }
+        mask
     }
-    if (node is DrawModifierNode) {
-        mask = mask or Nodes.Draw
-    }
-    if (node is SemanticsModifierNode) {
-        mask = mask or Nodes.Semantics
-    }
-    if (node is PointerInputModifierNode) {
-        mask = mask or Nodes.PointerInput
-    }
-    if (node is ModifierLocalModifierNode) {
-        mask = mask or Nodes.Locals
-    }
-    if (node is ParentDataModifierNode) {
-        mask = mask or Nodes.ParentData
-    }
-    if (node is LayoutAwareModifierNode) {
-        mask = mask or Nodes.LayoutAware
-    }
-    if (node is GlobalPositionAwareModifierNode) {
-        mask = mask or Nodes.GlobalPositionAware
-    }
-    if (node is IntermediateLayoutModifierNode) {
-        mask = mask or Nodes.IntermediateMeasure
-    }
-    if (node is FocusTargetNode) {
-        mask = mask or Nodes.FocusTarget
-    }
-    if (node is FocusPropertiesModifierNode) {
-        mask = mask or Nodes.FocusProperties
-    }
-    if (node is FocusEventModifierNode) {
-        mask = mask or Nodes.FocusEvent
-    }
-    if (node is KeyInputModifierNode) {
-        mask = mask or Nodes.KeyInput
-    }
-    if (node is RotaryInputModifierNode) {
-        mask = mask or Nodes.RotaryInput
-    }
-    if (node is CompositionLocalConsumerModifierNode) {
-        mask = mask or Nodes.CompositionLocalConsumer
-    }
-    if (node is SoftKeyboardInterceptionModifierNode) {
-        mask = mask or Nodes.SoftKeyboardKeyInput
-    }
-    if (node is TraversableNode) {
-        mask = mask or Nodes.Traversable
-    }
-    return mask
 }
 
 @Suppress("ConstPropertyName")
@@ -264,7 +269,7 @@
         }
     }
     if (Nodes.GlobalPositionAware in selfKindSet && node is GlobalPositionAwareModifierNode) {
-        node.requireLayoutNode().invalidateMeasurements()
+        node.requireLayoutNode().invalidateOnPositioned()
     }
     if (Nodes.Draw in selfKindSet && node is DrawModifierNode) {
         node.invalidateDraw()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
index 4b34850..7f5ef02 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
@@ -33,6 +33,10 @@
         node.needsOnPositionedDispatch = true
     }
 
+    fun remove(node: LayoutNode) {
+        layoutNodes.remove(node)
+    }
+
     fun onRootNodePositioned(rootNode: LayoutNode) {
         layoutNodes.clear()
         layoutNodes += rootNode
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InspectableValue.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InspectableValue.kt
index 02bedf9..970de2b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InspectableValue.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InspectableValue.kt
@@ -140,6 +140,14 @@
  *
  * @sample androidx.compose.ui.samples.InspectableModifierSample
  */
+@Suppress("DeprecatedCallableAddReplaceWith")
+@Deprecated(
+    "This API will create more invalidations of your modifier than necessary, so it's " +
+        "use is discouraged. Implementing the inspectableProperties method on " +
+        "ModifierNodeElement is the recommended zero-cost alternative to exposing properties " +
+        "on a Modifier to tooling.",
+    level = DeprecationLevel.WARNING,
+)
 inline fun Modifier.inspectable(
     noinline inspectorInfo: InspectorInfo.() -> Unit,
     factory: Modifier.() -> Modifier
diff --git a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.jvm.kt b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.jvm.kt
index 84f9030..f85672e 100644
--- a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.jvm.kt
+++ b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.jvm.kt
@@ -24,6 +24,10 @@
     return a::class.java === b::class.java
 }
 
+internal actual fun classKeyForObject(a: Any): Any {
+    return a.javaClass
+}
+
 // TODO: For non-JVM platforms, you can revive the kotlin-reflect implementation from
 //  https://android-review.googlesource.com/c/platform/frameworks/support/+/2441379
 internal actual fun InspectorInfo.tryPopulateReflectively(
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 57a13ef..6d8023a 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2386,17 +2386,17 @@
 
   public static interface Pools.Pool<T> {
     method public T? acquire();
-    method public boolean release(T);
+    method public boolean release(T instance);
   }
 
   public static class Pools.SimplePool<T> implements androidx.core.util.Pools.Pool<T> {
-    ctor public Pools.SimplePool(int);
-    method public T! acquire();
-    method public boolean release(T);
+    ctor public Pools.SimplePool(@IntRange(from=1L) int maxPoolSize);
+    method public T? acquire();
+    method public boolean release(T instance);
   }
 
   public static class Pools.SynchronizedPool<T> extends androidx.core.util.Pools.SimplePool<T> {
-    ctor public Pools.SynchronizedPool(int);
+    ctor public Pools.SynchronizedPool(int maxPoolSize);
   }
 
   public interface Predicate<T> {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 839dde9..cf1f7da 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2782,17 +2782,17 @@
 
   public static interface Pools.Pool<T> {
     method public T? acquire();
-    method public boolean release(T);
+    method public boolean release(T instance);
   }
 
   public static class Pools.SimplePool<T> implements androidx.core.util.Pools.Pool<T> {
-    ctor public Pools.SimplePool(int);
-    method public T! acquire();
-    method public boolean release(T);
+    ctor public Pools.SimplePool(@IntRange(from=1L) int maxPoolSize);
+    method public T? acquire();
+    method public boolean release(T instance);
   }
 
   public static class Pools.SynchronizedPool<T> extends androidx.core.util.Pools.SimplePool<T> {
-    ctor public Pools.SynchronizedPool(int);
+    ctor public Pools.SynchronizedPool(int maxPoolSize);
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class Preconditions {
diff --git a/core/core/lint-baseline.xml b/core/core/lint-baseline.xml
index 2df4e9c..d6fbdd3 100644
--- a/core/core/lint-baseline.xml
+++ b/core/core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="NewApi"
@@ -579,15 +579,6 @@
 
     <issue
         id="NewApi"
-        message="Class requires API level 26 (current min is 19): `OreoCallback`"
-        errorLine1="        if (callback instanceof OreoCallback &amp;&amp; Build.VERSION.SDK_INT >= 26) {"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/core/widget/TextViewCompat.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level 20 (current min is 19): `android.view.View#requestApplyInsets`"
         errorLine1="    post { requestApplyInsets() }"
         errorLine2="           ~~~~~~~~~~~~~~~~~~">
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsCompatActivityTest.kt
index 8193c29..c9fa22f 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsCompatActivityTest.kt
@@ -17,11 +17,11 @@
 package androidx.core.view
 
 import android.content.Intent
-import android.content.pm.ActivityInfo
 import android.os.Build
 import android.view.View
 import android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
 import android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+import androidx.annotation.RequiresApi
 import androidx.core.graphics.Insets
 import androidx.core.test.R
 import androidx.core.view.WindowInsetsCompat.Type
@@ -58,10 +58,7 @@
 @Suppress("DEPRECATION") // Testing deprecated methods
 @LargeTest
 @RunWith(Parameterized::class)
-public class WindowInsetsCompatActivityTest(
-    private val softInputMode: Int,
-    private val orientation: Int
-) {
+public class WindowInsetsCompatActivityTest(private val softInputMode: Int) {
     private lateinit var scenario: ActivityScenario<WindowInsetsCompatActivity>
 
     @Before
@@ -69,13 +66,6 @@
         scenario = ActivityScenario.launch(WindowInsetsCompatActivity::class.java)
 
         scenario.withActivity {
-            // Update the orientation based on the test parameter, we do this first since it
-            // may recreate the Activity
-            requestedOrientation = orientation
-        }
-        onIdle()
-
-        scenario.withActivity {
             // Update the soft input mode based on the test parameter
             window.setSoftInputMode(softInputMode)
         }
@@ -368,10 +358,8 @@
         @Parameterized.Parameters
         public fun data(): List<Array<Int>> =
             listOf(
-                arrayOf(SOFT_INPUT_ADJUST_PAN, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE),
-                arrayOf(SOFT_INPUT_ADJUST_PAN, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT),
-                arrayOf(SOFT_INPUT_ADJUST_RESIZE, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE),
-                arrayOf(SOFT_INPUT_ADJUST_RESIZE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+                arrayOf(SOFT_INPUT_ADJUST_PAN),
+                arrayOf(SOFT_INPUT_ADJUST_RESIZE),
             )
     }
 }
@@ -400,6 +388,7 @@
     return received.get()
 }
 
+@RequiresApi(20)
 private fun View.requestAndAwaitInsets(): WindowInsetsCompat {
     val latch = CountDownLatch(1)
     val received = AtomicReference<WindowInsetsCompat>()
diff --git a/core/core/src/main/java/androidx/core/content/IntentCompat.java b/core/core/src/main/java/androidx/core/content/IntentCompat.java
index b4f7a2a..4cbd632 100644
--- a/core/core/src/main/java/androidx/core/content/IntentCompat.java
+++ b/core/core/src/main/java/androidx/core/content/IntentCompat.java
@@ -200,7 +200,7 @@
 
     /**
      * Retrieve extended data from the intent.
-     *
+     * <p>
      * Compatibility behavior:
      * <ul>
      *     <li>SDK 34 and later, this method matches platform behavior.
@@ -221,6 +221,7 @@
     public static <T> T getParcelableExtra(@NonNull Intent in, @Nullable String name,
             @NonNull Class<T> clazz) {
         if (Build.VERSION.SDK_INT >= 34) {
+            // Don't call this API on SDK 33 due to b/232589966.
             return Api33Impl.getParcelableExtra(in, name, clazz);
         } else {
             T extra = in.getParcelableExtra(name);
@@ -230,7 +231,7 @@
 
     /**
      * Retrieve extended data from the intent.
-     *
+     * <p>
      * Compatibility behavior:
      * <ul>
      *     <li>SDK 34 and later, this method matches platform behavior.
@@ -252,6 +253,7 @@
     public static Parcelable[] getParcelableArrayExtra(@NonNull Intent in, @Nullable String name,
             @NonNull Class<? extends Parcelable> clazz) {
         if (Build.VERSION.SDK_INT >= 34) {
+            // Don't call this API on SDK 33 due to b/232589966.
             return Api33Impl.getParcelableArrayExtra(in, name, clazz);
         } else {
             return in.getParcelableArrayExtra(name);
@@ -260,7 +262,7 @@
 
     /**
      * Retrieve extended data from the intent.
-     *
+     * <p>
      * Compatibility behavior:
      * <ul>
      *     <li>SDK 34 and later, this method matches platform behavior.
@@ -284,6 +286,7 @@
     public static <T> ArrayList<T> getParcelableArrayListExtra(
             @NonNull Intent in, @Nullable String name, @NonNull Class<? extends T> clazz) {
         if (Build.VERSION.SDK_INT >= 34) {
+            // Don't call this API on SDK 33 due to b/232589966.
             return Api33Impl.getParcelableArrayListExtra(in, name, clazz);
         } else {
             return (ArrayList<T>) in.getParcelableArrayListExtra(name);
@@ -314,6 +317,7 @@
     public static <T extends Serializable> T getSerializableExtra(@NonNull Intent in,
             @Nullable String key, @NonNull Class<T> clazz) {
         if (Build.VERSION.SDK_INT >= 34) {
+            // Don't call this API on SDK 33 due to b/232589966.
             return Api33Impl.getSerializableExtra(in, key, clazz);
         } else {
             Serializable serializable = in.getSerializableExtra(key);
diff --git a/core/core/src/main/java/androidx/core/util/Pools.java b/core/core/src/main/java/androidx/core/util/Pools.java
deleted file mode 100644
index e0f487a..0000000
--- a/core/core/src/main/java/androidx/core/util/Pools.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2013 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.core.util;
-
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Helper class for creating pools of objects. An example use looks like this:
- * <pre>
- * public class MyPooledClass {
- *
- *     private static final SynchronizedPool<MyPooledClass> sPool =
- *             new SynchronizedPool<MyPooledClass>(10);
- *
- *     public static MyPooledClass obtain() {
- *         MyPooledClass instance = sPool.acquire();
- *         return (instance != null) ? instance : new MyPooledClass();
- *     }
- *
- *     public void recycle() {
- *          // Clear state if needed.
- *          sPool.release(this);
- *     }
- *
- *     . . .
- * }
- * </pre>
- *
- */
-public final class Pools {
-
-    /**
-     * Interface for managing a pool of objects.
-     *
-     * @param <T> The pooled type.
-     */
-    public interface Pool<T> {
-
-        /**
-         * @return An instance from the pool if such, null otherwise.
-         */
-        @Nullable
-        T acquire();
-
-        /**
-         * Release an instance to the pool.
-         *
-         * @param instance The instance to release.
-         * @return Whether the instance was put in the pool.
-         *
-         * @throws IllegalStateException If the instance is already in the pool.
-         */
-        boolean release(@NonNull T instance);
-    }
-
-    private Pools() {
-        /* do nothing - hiding constructor */
-    }
-
-    /**
-     * Simple (non-synchronized) pool of objects.
-     *
-     * @param <T> The pooled type.
-     */
-    public static class SimplePool<T> implements Pool<T> {
-        private final Object[] mPool;
-
-        private int mPoolSize;
-
-        /**
-         * Creates a new instance.
-         *
-         * @param maxPoolSize The max pool size.
-         *
-         * @throws IllegalArgumentException If the max pool size is less than zero.
-         */
-        public SimplePool(int maxPoolSize) {
-            if (maxPoolSize <= 0) {
-                throw new IllegalArgumentException("The max pool size must be > 0");
-            }
-            mPool = new Object[maxPoolSize];
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public T acquire() {
-            if (mPoolSize > 0) {
-                final int lastPooledIndex = mPoolSize - 1;
-                T instance = (T) mPool[lastPooledIndex];
-                mPool[lastPooledIndex] = null;
-                mPoolSize--;
-                return instance;
-            }
-            return null;
-        }
-
-        @Override
-        public boolean release(@NonNull T instance) {
-            if (isInPool(instance)) {
-                throw new IllegalStateException("Already in the pool!");
-            }
-            if (mPoolSize < mPool.length) {
-                mPool[mPoolSize] = instance;
-                mPoolSize++;
-                return true;
-            }
-            return false;
-        }
-
-        private boolean isInPool(@NonNull T instance) {
-            for (int i = 0; i < mPoolSize; i++) {
-                if (mPool[i] == instance) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Synchronized) pool of objects.
-     *
-     * @param <T> The pooled type.
-     */
-    public static class SynchronizedPool<T> extends SimplePool<T> {
-        private final Object mLock = new Object();
-
-        /**
-         * Creates a new instance.
-         *
-         * @param maxPoolSize The max pool size.
-         *
-         * @throws IllegalArgumentException If the max pool size is less than zero.
-         */
-        public SynchronizedPool(int maxPoolSize) {
-            super(maxPoolSize);
-        }
-
-        @Override
-        public T acquire() {
-            synchronized (mLock) {
-                return super.acquire();
-            }
-        }
-
-        @Override
-        public boolean release(@NonNull T instance) {
-            synchronized (mLock) {
-                return super.release(instance);
-            }
-        }
-    }
-}
diff --git a/core/core/src/main/java/androidx/core/util/Pools.kt b/core/core/src/main/java/androidx/core/util/Pools.kt
new file mode 100644
index 0000000..03192e3
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/util/Pools.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 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.core.util
+
+import androidx.annotation.IntRange
+
+/**
+ * Helper class for creating pools of objects. An example use looks like this:
+ * ```
+ * class MyPooledClass {
+ *
+ *     fun recycle() {
+ *         // Clear state if needed, then return this instance to the Pool
+ *         pool.release(this)
+ *     }
+ *
+ *     companion object {
+ *         private val pool = Pools.SynchronizedPool<MyPooledClass>(10)
+ *
+ *         fun obtain() : MyPooledClass {
+ *             // Get an instance from the Pool or
+ *             // construct a new one if none are available
+ *             return pool.acquire() ?: MyPooledClass()
+ *         }
+ *     }
+ * }
+ * ```
+ */
+class Pools private constructor() {
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param T The pooled type.
+     */
+    interface Pool<T : Any> {
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        fun acquire(): T?
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        fun release(instance: T): Boolean
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param maxPoolSize The maximum pool size
+     * @param T The pooled type.
+     */
+    open class SimplePool<T : Any>(
+        /**
+         * The max pool size
+         */
+        @IntRange(from = 1) maxPoolSize: Int
+    ) : Pool<T> {
+        private val pool: Array<Any?>
+        private var poolSize = 0
+
+        init {
+            require(maxPoolSize > 0) { "The max pool size must be > 0" }
+            pool = arrayOfNulls(maxPoolSize)
+        }
+
+        override fun acquire(): T? {
+            if (poolSize > 0) {
+                val lastPooledIndex = poolSize - 1
+                @Suppress("UNCHECKED_CAST")
+                val instance = pool[lastPooledIndex] as T
+                pool[lastPooledIndex] = null
+                poolSize--
+                return instance
+            }
+            return null
+        }
+
+        override fun release(instance: T): Boolean {
+            check(!isInPool(instance)) { "Already in the pool!" }
+            if (poolSize < pool.size) {
+                pool[poolSize] = instance
+                poolSize++
+                return true
+            }
+            return false
+        }
+
+        private fun isInPool(instance: T): Boolean {
+            for (i in 0 until poolSize) {
+                if (pool[i] === instance) {
+                    return true
+                }
+            }
+            return false
+        }
+    }
+
+    /**
+     * Synchronized pool of objects.
+     *
+     * @param maxPoolSize The maximum pool size
+     * @param T The pooled type.
+     */
+    open class SynchronizedPool<T : Any>(maxPoolSize: Int) : SimplePool<T>(maxPoolSize) {
+        private val lock = Any()
+        override fun acquire(): T? {
+            synchronized(lock) { return super.acquire() }
+        }
+
+        override fun release(instance: T): Boolean {
+            synchronized(lock) { return super.release(instance) }
+        }
+    }
+}
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index 35fbc3b..c4331d6 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -756,8 +756,12 @@
     method public static final androidx.credentials.provider.CredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public final CharSequence? getAffiliatedDomain();
     method public final androidx.credentials.provider.BeginGetCredentialOption getBeginGetCredentialOption();
+    method public final CharSequence getEntryGroupId();
+    method public final boolean isDefaultIconPreferredAsSingleProvider();
     property public final CharSequence? affiliatedDomain;
     property public final androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption;
+    property public final CharSequence entryGroupId;
+    property public final boolean isDefaultIconPreferredAsSingleProvider;
     field public static final androidx.credentials.provider.CredentialEntry.Companion Companion;
   }
 
@@ -776,7 +780,8 @@
   }
 
   @RequiresApi(26) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
-    ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor @Deprecated public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence entryGroupId, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.CustomCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public android.graphics.drawable.Icon getIcon();
     method public java.time.Instant? getLastUsedTime();
@@ -801,6 +806,8 @@
     ctor public CustomCredentialEntry.Builder(android.content.Context context, String type, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption);
     method public androidx.credentials.provider.CustomCredentialEntry build();
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.CustomCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
+    method public androidx.credentials.provider.CustomCredentialEntry.Builder setEntryGroupId(CharSequence entryGroupId);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setSubtitle(CharSequence? subtitle);
@@ -821,7 +828,7 @@
 
   @RequiresApi(26) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor @Deprecated public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
-    ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence? affiliatedDomain);
+    ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence? affiliatedDomain, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.PasswordCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -845,6 +852,7 @@
     method public androidx.credentials.provider.PasswordCredentialEntry build();
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setAffiliatedDomain(CharSequence? affiliatedDomain);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.PasswordCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setDisplayName(CharSequence? displayName);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
@@ -901,7 +909,8 @@
   }
 
   @RequiresApi(26) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
-    ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor @Deprecated public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.PublicKeyCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -924,6 +933,7 @@
     ctor public PublicKeyCredentialEntry.Builder(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption);
     method public androidx.credentials.provider.PublicKeyCredentialEntry build();
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setDisplayName(CharSequence? displayName);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index 35fbc3b..c4331d6 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -756,8 +756,12 @@
     method public static final androidx.credentials.provider.CredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public final CharSequence? getAffiliatedDomain();
     method public final androidx.credentials.provider.BeginGetCredentialOption getBeginGetCredentialOption();
+    method public final CharSequence getEntryGroupId();
+    method public final boolean isDefaultIconPreferredAsSingleProvider();
     property public final CharSequence? affiliatedDomain;
     property public final androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption;
+    property public final CharSequence entryGroupId;
+    property public final boolean isDefaultIconPreferredAsSingleProvider;
     field public static final androidx.credentials.provider.CredentialEntry.Companion Companion;
   }
 
@@ -776,7 +780,8 @@
   }
 
   @RequiresApi(26) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
-    ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor @Deprecated public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence entryGroupId, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.CustomCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public android.graphics.drawable.Icon getIcon();
     method public java.time.Instant? getLastUsedTime();
@@ -801,6 +806,8 @@
     ctor public CustomCredentialEntry.Builder(android.content.Context context, String type, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption);
     method public androidx.credentials.provider.CustomCredentialEntry build();
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.CustomCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
+    method public androidx.credentials.provider.CustomCredentialEntry.Builder setEntryGroupId(CharSequence entryGroupId);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
     method public androidx.credentials.provider.CustomCredentialEntry.Builder setSubtitle(CharSequence? subtitle);
@@ -821,7 +828,7 @@
 
   @RequiresApi(26) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor @Deprecated public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
-    ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence? affiliatedDomain);
+    ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional CharSequence? affiliatedDomain, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.PasswordCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -845,6 +852,7 @@
     method public androidx.credentials.provider.PasswordCredentialEntry build();
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setAffiliatedDomain(CharSequence? affiliatedDomain);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.PasswordCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setDisplayName(CharSequence? displayName);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.PasswordCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
@@ -901,7 +909,8 @@
   }
 
   @RequiresApi(26) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
-    ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor @Deprecated public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
+    ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed, optional boolean isDefaultIconPreferredAsSingleProvider);
     method public static androidx.credentials.provider.PublicKeyCredentialEntry? fromCredentialEntry(android.service.credentials.CredentialEntry credentialEntry);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -924,6 +933,7 @@
     ctor public PublicKeyCredentialEntry.Builder(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption);
     method public androidx.credentials.provider.PublicKeyCredentialEntry build();
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
+    method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setDefaultIconPreferredAsSingleProvider(boolean isDefaultIconPreferredAsSingleProvider);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setDisplayName(CharSequence? displayName);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setIcon(android.graphics.drawable.Icon icon);
     method public androidx.credentials.provider.PublicKeyCredentialEntry.Builder setLastUsedTime(java.time.Instant? lastUsedTime);
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
index 4065bf6..3654984 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.credentials.provider.ui;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -44,46 +43,41 @@
 import org.junit.runner.RunWith;
 
 import java.time.Instant;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26)
 @SmallTest
 public class CustomCredentialEntryJavaTest {
     private static final CharSequence TITLE = "title";
     private static final CharSequence SUBTITLE = "subtitle";
-
     private static final String TYPE = "custom_type";
     private static final CharSequence TYPE_DISPLAY_NAME = "Password";
+    private static final String ENTRY_GROUP_ID = "entryGroupId";
+    private static final boolean DEFAULT_SINGLE_PROVIDER_ICON_BIT = false;
+    private static final boolean SINGLE_PROVIDER_ICON_BIT = true;
     private static final Long LAST_USED_TIME = 10L;
     private static final Icon ICON = Icon.createWithBitmap(Bitmap.createBitmap(
             100, 100, Bitmap.Config.ARGB_8888));
     private static final boolean IS_AUTO_SELECT_ALLOWED = true;
     private final BeginGetCredentialOption mBeginCredentialOption =
             new BeginGetCustomCredentialOption(
-            "id", "custom_type", new Bundle());
-
+                    "id", "custom_type", new Bundle());
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Intent mIntent = new Intent();
     private final PendingIntent mPendingIntent =
             PendingIntent.getActivity(mContext, 0, mIntent,
                     PendingIntent.FLAG_IMMUTABLE);
-
     @Test
     public void build_requiredParameters_success() {
         CustomCredentialEntry entry = constructEntryWithRequiredParams();
-
         assertNotNull(entry);
         assertEntryWithRequiredParams(entry);
     }
-
     @Test
     public void build_allParameters_success() {
         CustomCredentialEntry entry = constructEntryWithAllParams();
-
         assertNotNull(entry);
         assertEntryWithAllParams(entry);
     }
-
     @Test
     public void build_nullTitle_throwsNPE() {
         assertThrows("Expected null title to throw NPE",
@@ -92,7 +86,6 @@
                         mContext, TYPE, null, mPendingIntent, mBeginCredentialOption
                 ));
     }
-
     @Test
     public void build_nullContext_throwsNPE() {
         assertThrows("Expected null title to throw NPE",
@@ -101,7 +94,6 @@
                         null, TYPE, TITLE, mPendingIntent, mBeginCredentialOption
                 ).build());
     }
-
     @Test
     public void build_nullPendingIntent_throwsNPE() {
         assertThrows("Expected null pending intent to throw NPE",
@@ -110,7 +102,6 @@
                         mContext, TYPE, TITLE, null, mBeginCredentialOption
                 ).build());
     }
-
     @Test
     public void build_nullBeginOption_throwsNPE() {
         assertThrows("Expected null option to throw NPE",
@@ -119,7 +110,6 @@
                         mContext, TYPE, TITLE, mPendingIntent, null
                 ).build());
     }
-
     @Test
     public void build_emptyTitle_throwsIAE() {
         assertThrows("Expected empty title to throw IAE",
@@ -128,7 +118,6 @@
                         mContext, TYPE, "", mPendingIntent, mBeginCredentialOption
                 ).build());
     }
-
     @Test
     public void build_emptyType_throwsIAE() {
         assertThrows("Expected empty type to throw NPE",
@@ -137,54 +126,84 @@
                         mContext, "", TITLE, mPendingIntent, mBeginCredentialOption
                 ).build());
     }
-
     @Test
     public void build_nullIcon_defaultIconSet() {
         CustomCredentialEntry entry = constructEntryWithRequiredParams();
-
         assertThat(TestUtilsKt.equals(entry.getIcon(),
                 Icon.createWithResource(mContext, R.drawable.ic_other_sign_in))).isTrue();
     }
+    @Test
+    public void builder_setPreferredDefaultIconBit_retrieveSetIconBit() {
+        boolean expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT;
+        CustomCredentialEntry entry = new CustomCredentialEntry.Builder(
+                mContext, TYPE, TITLE, mPendingIntent,
+                mBeginCredentialOption)
+                .setDefaultIconPreferredAsSingleProvider(expectedPreferredDefaultIconBit)
+                .build();
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider())
+                .isEqualTo(expectedPreferredDefaultIconBit);
+    }
+
+    @Test
+    public void builder_constructDefault_containsOnlySetPropertiesAndDefaultValues() {
+        CustomCredentialEntry entry = constructEntryWithRequiredParams();
+
+        assertEntryWithRequiredParams(entry);
+    }
+
+    @Test
+    public void builder_setEmptyEntryGroupId_throwIAE() {
+        assertThrows("Expected null title to throw IAE",
+                IllegalArgumentException.class,
+                () -> new CustomCredentialEntry.Builder(
+                        mContext, TYPE, TITLE, mPendingIntent,
+                        mBeginCredentialOption)
+                        .setEntryGroupId("").build()
+        );
+    }
+
+    @Test
+    public void builder_setNonEmptyEntryGroupId_retrieveSetEntryGroupId() {
+        CharSequence expectedEntryGroupId = "pikes-peak";
+
+        CustomCredentialEntry entry = new CustomCredentialEntry.Builder(
+                mContext, TYPE, TITLE, mPendingIntent,
+                mBeginCredentialOption)
+                .setEntryGroupId(expectedEntryGroupId).build();
+
+        assertThat(entry.getEntryGroupId()).isEqualTo(expectedEntryGroupId);
+    }
 
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_requiredParams_success() {
         CustomCredentialEntry originalEntry = constructEntryWithRequiredParams();
-
         Slice slice = CustomCredentialEntry.toSlice(originalEntry);
         CustomCredentialEntry entry = CustomCredentialEntry.fromSlice(
                 slice);
-
         assertNotNull(entry);
         assertEntryWithRequiredParamsFromSlice(entry);
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_allParams_success() {
         CustomCredentialEntry originalEntry = constructEntryWithAllParams();
-
         Slice slice = CustomCredentialEntry.toSlice(originalEntry);
         CustomCredentialEntry entry = CustomCredentialEntry.fromSlice(slice);
-
         assertNotNull(entry);
         assertEntryWithAllParamsFromSlice(entry);
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     public void fromCredentialEntry_allParams_success() {
         CustomCredentialEntry originalEntry = constructEntryWithAllParams();
         Slice slice = CustomCredentialEntry.toSlice(originalEntry);
         assertNotNull(slice);
-
         CustomCredentialEntry entry = CustomCredentialEntry.fromCredentialEntry(
                 new CredentialEntry("id", slice));
-
         assertNotNull(entry);
         assertEntryWithAllParamsFromSlice(entry);
     }
-
     private CustomCredentialEntry constructEntryWithRequiredParams() {
         return new CustomCredentialEntry.Builder(
                 mContext,
@@ -194,7 +213,6 @@
                 mBeginCredentialOption
         ).build();
     }
-
     private CustomCredentialEntry constructEntryWithAllParams() {
         return new CustomCredentialEntry.Builder(
                 mContext,
@@ -206,21 +224,28 @@
                 .setLastUsedTime(Instant.ofEpochMilli(LAST_USED_TIME))
                 .setAutoSelectAllowed(IS_AUTO_SELECT_ALLOWED)
                 .setTypeDisplayName(TYPE_DISPLAY_NAME)
+                .setEntryGroupId(ENTRY_GROUP_ID)
+                .setDefaultIconPreferredAsSingleProvider(SINGLE_PROVIDER_ICON_BIT)
                 .build();
     }
-
     private void assertEntryWithRequiredParams(CustomCredentialEntry entry) {
         assertThat(TITLE.equals(entry.getTitle()));
         assertThat(TYPE.equals(entry.getType()));
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(TITLE);
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                DEFAULT_SINGLE_PROVIDER_ICON_BIT);
     }
-
     private void assertEntryWithRequiredParamsFromSlice(CustomCredentialEntry entry) {
         assertThat(TITLE.equals(entry.getTitle()));
         assertThat(TYPE.equals(entry.getType()));
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(TITLE);
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                DEFAULT_SINGLE_PROVIDER_ICON_BIT);
     }
-
     private void assertEntryWithAllParams(CustomCredentialEntry entry) {
         assertThat(TITLE.equals(entry.getTitle()));
         assertThat(TYPE.equals(entry.getType()));
@@ -232,8 +257,11 @@
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginCredentialOption.getType()).isEqualTo(entry.getType());
         assertThat(mBeginCredentialOption).isEqualTo(entry.getBeginGetCredentialOption());
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(ENTRY_GROUP_ID);
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                SINGLE_PROVIDER_ICON_BIT);
     }
-
     private void assertEntryWithAllParamsFromSlice(CustomCredentialEntry entry) {
         assertThat(TITLE.equals(entry.getTitle()));
         assertThat(TYPE.equals(entry.getType()));
@@ -244,5 +272,9 @@
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed());
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginCredentialOption.getType()).isEqualTo(entry.getType());
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(ENTRY_GROUP_ID);
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                SINGLE_PROVIDER_ICON_BIT);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
index 6c9c502..c9f7331 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 package androidx.credentials.provider.ui
-
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
@@ -40,7 +39,6 @@
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
-
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 26)
 @SmallTest
@@ -53,27 +51,21 @@
     @SdkSuppress(minSdkVersion = 28)
     fun constructor_requiredParams_success() {
         val entry = constructEntryWithRequiredParams()
-
         assertNotNull(entry)
         assertEntryWithRequiredParams(entry)
     }
-
     @Test
     fun constructor_allParams_success() {
         val entry = constructEntryWithAllParams()
-
         assertNotNull(entry)
         assertEntryWithAllParams(entry)
     }
-
     @Test
     fun constructor_allParameters_success() {
         val entry: CustomCredentialEntry = constructEntryWithAllParams()
-
         assertNotNull(entry)
         assertEntryWithAllParams(entry)
     }
-
     @Test
     fun constructor_emptyTitle_throwsIAE() {
         assertThrows(
@@ -87,7 +79,6 @@
             )
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 28)
     fun constructor_emptyType_throwsIAE() {
@@ -101,12 +92,10 @@
             )
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 23)
     fun constructor_nullIcon_defaultIconSet() {
         val entry = constructEntryWithRequiredParams()
-
         assertThat(
             equals(
                 entry.icon,
@@ -114,56 +103,164 @@
             )
         ).isTrue()
     }
+    @Test
+    fun constructor_setPreferredDefaultIconBit_retrieveSetPreferredDefaultIconBit() {
+        val expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT
+        val entry = CustomCredentialEntry(
+            mContext,
+            TITLE,
+            mPendingIntent,
+            BEGIN_OPTION,
+            isDefaultIconPreferredAsSingleProvider = SINGLE_PROVIDER_ICON_BIT
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider)
+            .isEqualTo(expectedPreferredDefaultIconBit)
+    }
+    @Test
+    fun constructor_preferredIconBitNotProvided_retrieveDefaultPreferredIconBit() {
+        val entry = CustomCredentialEntry(
+            mContext,
+            TITLE,
+            mPendingIntent,
+            BEGIN_OPTION,
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+    }
+
+    @Test
+    fun constructor_emptyEntryGroupId_defaultEntryGroupIdSet() {
+        val expectedEntryGroupId = TITLE
+
+        val entry = CustomCredentialEntry(
+            mContext,
+            expectedEntryGroupId,
+            mPendingIntent,
+            BEGIN_OPTION,
+            entryGroupId = ""
+        )
+
+        assertThat(entry.entryGroupId).isEqualTo(expectedEntryGroupId)
+    }
+
+    @Test
+    fun constructor_nonEmptyEntryGroupIdSet_getSetEntryGroupId() {
+        val expectedEntryGroupId = "expected-dedupe"
+
+        val entry = CustomCredentialEntry(
+            mContext,
+            expectedEntryGroupId,
+            mPendingIntent,
+            BEGIN_OPTION,
+            entryGroupId = expectedEntryGroupId
+        )
+
+        assertThat(entry.entryGroupId).isEqualTo(expectedEntryGroupId)
+    }
+
+    @Test
+    fun constructor_entryGroupIdNotProvided_getDefaultTitle() {
+        val entry = CustomCredentialEntry(
+            mContext,
+            TITLE,
+            mPendingIntent,
+            BEGIN_OPTION,
+        )
+
+        assertThat(entry.entryGroupId).isEqualTo(TITLE)
+    }
+
+    @Test
+    fun builder_constructDefault_containsOnlySetPropertiesAndDefaultValues() {
+        val entry = CustomCredentialEntry.Builder(
+            mContext, TYPE, TITLE, mPendingIntent, BEGIN_OPTION).build()
+
+        assertThat(entry.title).isEqualTo(TITLE)
+        assertThat(entry.pendingIntent).isEqualTo(mPendingIntent)
+        assertThat(entry.beginGetCredentialOption).isEqualTo(BEGIN_OPTION)
+        assertThat(entry.subtitle).isNull()
+        assertThat(entry.typeDisplayName).isNull()
+        assertThat(entry.lastUsedTime).isNull()
+        assertThat(entry.icon.toString())
+            .isEqualTo(Icon.createWithResource(mContext, R.drawable.ic_other_sign_in).toString())
+        assertThat(entry.isAutoSelectAllowed).isFalse()
+        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.entryGroupId).isEqualTo(TITLE)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+    }
+    @Test
+    fun builder_setNonEmpyDeduplicationId_retrieveSetDeduplicationId() {
+        val expectedIconBit = SINGLE_PROVIDER_ICON_BIT
+        val entry = CustomCredentialEntry.Builder(
+            mContext, TYPE, TITLE, mPendingIntent, BEGIN_OPTION)
+            .setDefaultIconPreferredAsSingleProvider(SINGLE_PROVIDER_ICON_BIT).build()
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(expectedIconBit)
+    }
+
+    @Test
+    fun builder_setEmptyEntryGroupId_throwIAE() {
+        assertThrows(
+            "Expected empty dedupe id in setter to throw IAE",
+            IllegalArgumentException::class.java
+        ) {
+            CustomCredentialEntry.Builder(
+                mContext, TYPE, TITLE, mPendingIntent, BEGIN_OPTION)
+                .setEntryGroupId("").build()
+        }
+    }
+
+    @Test
+    fun builder_setNonEmpyEntryGroupId_retrieveSetEntryGroupId() {
+        val expectedEntryGroupId = "noe-valley"
+
+        val entry = CustomCredentialEntry.Builder(
+            mContext, TYPE, TITLE, mPendingIntent, BEGIN_OPTION)
+            .setEntryGroupId(expectedEntryGroupId).build()
+
+        assertThat(entry.entryGroupId).isEqualTo(expectedEntryGroupId)
+    }
 
     @Test
     @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_requiredParams_success() {
         val originalEntry = constructEntryWithRequiredParams()
-
         val slice = CustomCredentialEntry.toSlice(
             originalEntry)
         assertNotNull(slice)
         val entry = fromSlice(slice!!)
-
         assertNotNull(entry)
         if (entry != null) {
             assertEntryWithRequiredParamsFromSlice(entry)
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_allParams_success() {
         val originalEntry = constructEntryWithAllParams()
-
         val slice = CustomCredentialEntry.toSlice(
-        originalEntry)
+            originalEntry)
         assertNotNull(slice)
         val entry = fromSlice(slice!!)
-
         assertNotNull(entry)
         if (entry != null) {
             assertEntryWithAllParamsFromSlice(entry)
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     fun fromCredentialEntry_allParams_success() {
         val originalEntry = constructEntryWithAllParams()
         val slice = toSlice(originalEntry)
-
         assertNotNull(slice)
         val entry = slice?.let { CredentialEntry("id", it) }?.let {
             fromCredentialEntry(
                 it
             )
         }
-
         assertNotNull(entry)
         assertEntryWithAllParamsFromSlice(entry!!)
     }
-
     private fun constructEntryWithRequiredParams(): CustomCredentialEntry {
         return CustomCredentialEntry(
             mContext,
@@ -172,7 +269,6 @@
             BEGIN_OPTION
         )
     }
-
     private fun constructEntryWithAllParams(): CustomCredentialEntry {
         return CustomCredentialEntry(
             mContext,
@@ -183,10 +279,11 @@
             TYPE_DISPLAY_NAME,
             Instant.ofEpochMilli(LAST_USED_TIME),
             ICON,
-            IS_AUTO_SELECT_ALLOWED
+            IS_AUTO_SELECT_ALLOWED,
+            ENTRY_GROUP_ID,
+            SINGLE_PROVIDER_ICON_BIT
         )
     }
-
     private fun assertEntryWithAllParams(entry: CustomCredentialEntry) {
         assertThat(TITLE == entry.title)
         assertThat(TYPE == entry.type)
@@ -196,8 +293,9 @@
         assertThat(Instant.ofEpochMilli(LAST_USED_TIME)).isEqualTo(entry.lastUsedTime)
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(SINGLE_PROVIDER_ICON_BIT)
+        assertThat(ENTRY_GROUP_ID).isEqualTo(entry.entryGroupId)
     }
-
     private fun assertEntryWithAllParamsFromSlice(entry: CustomCredentialEntry) {
         assertThat(TITLE == entry.title)
         assertThat(TYPE == entry.type)
@@ -208,21 +306,26 @@
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(BEGIN_OPTION.type).isEqualTo(entry.type)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(SINGLE_PROVIDER_ICON_BIT)
+        assertThat(ENTRY_GROUP_ID).isEqualTo(entry.entryGroupId)
     }
-
     private fun assertEntryWithRequiredParams(entry: CustomCredentialEntry) {
         assertThat(TITLE == entry.title)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(BEGIN_OPTION.type).isEqualTo(entry.type)
         assertThat(BEGIN_OPTION).isEqualTo(entry.beginGetCredentialOption)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.entryGroupId).isEqualTo(TITLE)
     }
-
     private fun assertEntryWithRequiredParamsFromSlice(entry: CustomCredentialEntry) {
         assertThat(TITLE == entry.title)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(BEGIN_OPTION.type).isEqualTo(entry.type)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.entryGroupId).isEqualTo(TITLE)
     }
-
     companion object {
         private val TITLE: CharSequence = "title"
         private val BEGIN_OPTION: BeginGetCredentialOption = BeginGetCustomCredentialOption(
@@ -237,5 +340,8 @@
             )
         )
         private const val IS_AUTO_SELECT_ALLOWED = true
+        private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
+        private const val SINGLE_PROVIDER_ICON_BIT = true
+        private const val ENTRY_GROUP_ID = "entryGroupId"
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
index 12bb685..9b24056 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.credentials.provider.ui;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -45,7 +44,6 @@
 
 import java.time.Instant;
 import java.util.HashSet;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26)
 @SmallTest
@@ -53,42 +51,35 @@
     private static final CharSequence USERNAME = "title";
     private static final CharSequence DISPLAYNAME = "subtitle";
     private static final CharSequence TYPE_DISPLAY_NAME = "Password";
-
     private static final String AFFILIATED_DOMAIN = "affiliation-name";
     private static final Long LAST_USED_TIME = 10L;
-
+    private static final boolean DEFAULT_SINGLE_PROVIDER_ICON_BIT = false;
+    private static final boolean SINGLE_PROVIDER_ICON_BIT = true;
     private static final boolean IS_AUTO_SELECT_ALLOWED = true;
-
     private static final Icon ICON = Icon.createWithBitmap(Bitmap.createBitmap(
             100, 100, Bitmap.Config.ARGB_8888));
     private final BeginGetPasswordOption mBeginGetPasswordOption = new BeginGetPasswordOption(
             new HashSet<>(),
             Bundle.EMPTY, "id");
-
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Intent mIntent = new Intent();
     private final PendingIntent mPendingIntent =
             PendingIntent.getActivity(mContext, 0, mIntent,
                     PendingIntent.FLAG_IMMUTABLE);
-
     @Test
     public void build_requiredParams_success() {
         PasswordCredentialEntry entry = constructEntryWithRequiredParamsOnly();
-
         assertNotNull(entry);
         assertThat(entry.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
         assertEntryWithRequiredParamsOnly(entry, false);
     }
-
     @Test
     public void build_allParams_success() {
         PasswordCredentialEntry entry = constructEntryWithAllParams();
-
         assertNotNull(entry);
         assertThat(entry.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
         assertEntryWithAllParams(entry);
     }
-
     @Test
     public void build_nullContext_throwsNPE() {
         assertThrows("Expected null context to throw NPE",
@@ -97,7 +88,6 @@
                         null, USERNAME, mPendingIntent, mBeginGetPasswordOption
                 ).build());
     }
-
     @Test
     public void build_nullUsername_throwsNPE() {
         assertThrows("Expected null username to throw NPE",
@@ -106,7 +96,6 @@
                         mContext, null, mPendingIntent, mBeginGetPasswordOption
                 ).build());
     }
-
     @Test
     public void build_nullPendingIntent_throwsNPE() {
         assertThrows("Expected null pending intent to throw NPE",
@@ -115,7 +104,6 @@
                         mContext, USERNAME, null, mBeginGetPasswordOption
                 ).build());
     }
-
     @Test
     public void build_nullBeginOption_throwsNPE() {
         assertThrows("Expected null option to throw NPE",
@@ -124,7 +112,6 @@
                         mContext, USERNAME, mPendingIntent, null
                 ).build());
     }
-
     @Test
     public void build_emptyUsername_throwsIAE() {
         assertThrows("Expected empty username to throw IllegalArgumentException",
@@ -132,45 +119,35 @@
                 () -> new PasswordCredentialEntry.Builder(
                         mContext, "", mPendingIntent, mBeginGetPasswordOption).build());
     }
-
     @Test
     public void build_nullIcon_defaultIconSet() {
         PasswordCredentialEntry entry = new PasswordCredentialEntry
                 .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
-
         assertThat(TestUtilsKt.equals(entry.getIcon(),
                 Icon.createWithResource(mContext, R.drawable.ic_password))).isTrue();
     }
-
     @Test
     public void build_nullTypeDisplayName_defaultDisplayNameSet() {
         PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
-                        mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
-
+                mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
         assertThat(entry.getTypeDisplayName()).isEqualTo(
                 mContext.getString(
                         R.string.android_credentials_TYPE_PASSWORD_CREDENTIAL)
         );
     }
-
     @Test
     public void build_isAutoSelectAllowedDefault_false() {
         PasswordCredentialEntry entry = constructEntryWithRequiredParamsOnly();
-
         assertFalse(entry.isAutoSelectAllowed());
     }
-
     @Test
     public void constructor_defaultAffiliatedDomain() {
         PasswordCredentialEntry entry = constructEntryWithRequiredParamsOnly();
-
         assertThat(entry.getAffiliatedDomain()).isNull();
     }
-
     @Test
     public void constructor_nonEmptyAffiliatedDomainSet_nonEmptyAffiliatedDomainRetrieved() {
         String expectedAffiliatedDomain = "non-empty";
-
         PasswordCredentialEntry entryWithAffiliatedDomain = new PasswordCredentialEntry(
                 mContext,
                 USERNAME,
@@ -180,83 +157,80 @@
                 Instant.ofEpochMilli(LAST_USED_TIME),
                 ICON,
                 false,
-                expectedAffiliatedDomain
+                expectedAffiliatedDomain,
+                false
         );
-
         assertThat(entryWithAffiliatedDomain.getAffiliatedDomain())
                 .isEqualTo(expectedAffiliatedDomain);
     }
-
     @Test
     public void builder_constructDefault_containsOnlyDefaultValuesForSettableParameters() {
         PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(mContext, USERNAME,
                 mPendingIntent, mBeginGetPasswordOption).build();
-
         assertThat(entry.getAffiliatedDomain()).isNull();
         assertThat(entry.getDisplayName()).isNull();
         assertThat(entry.getLastUsedTime()).isNull();
         assertThat(entry.isAutoSelectAllowed()).isFalse();
+        assertThat(entry.getEntryGroupId()).isEqualTo(USERNAME);
     }
-
     @Test
     public void builder_setAffiliatedDomainNull_retrieveNullAffiliatedDomain() {
         PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(mContext, USERNAME,
                 mPendingIntent, mBeginGetPasswordOption).setAffiliatedDomain(null).build();
-
         assertThat(entry.getAffiliatedDomain()).isNull();
     }
-
     @Test
     public void builder_setAffiliatedDomainNonNull_retrieveNonNullAffiliatedDomain() {
         String expectedAffiliatedDomain = "affiliated-domain";
-
         PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
                 mContext,
                 USERNAME,
                 mPendingIntent,
                 mBeginGetPasswordOption
         ).setAffiliatedDomain(expectedAffiliatedDomain).build();
-
         assertThat(entry.getAffiliatedDomain()).isEqualTo(expectedAffiliatedDomain);
     }
-
+    @Test
+    public void builder_setPreferredDefaultIconBit_retrieveSetIconBit() {
+        boolean expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT;
+        PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
+                mContext,
+                USERNAME,
+                mPendingIntent,
+                mBeginGetPasswordOption
+        ).setDefaultIconPreferredAsSingleProvider(expectedPreferredDefaultIconBit)
+                .build();
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider())
+                .isEqualTo(expectedPreferredDefaultIconBit);
+    }
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_requiredParams_success() {
         PasswordCredentialEntry originalEntry = constructEntryWithRequiredParamsOnly();
-
         PasswordCredentialEntry entry = PasswordCredentialEntry.fromSlice(
                 PasswordCredentialEntry.toSlice(originalEntry));
-
         assertNotNull(entry);
         assertEntryWithRequiredParamsOnly(entry, true);
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_allParams_success() {
         PasswordCredentialEntry originalEntry = constructEntryWithAllParams();
-
         PasswordCredentialEntry entry = PasswordCredentialEntry.fromSlice(
                 PasswordCredentialEntry.toSlice(originalEntry));
-
         assertNotNull(entry);
         assertEntryWithAllParams(entry);
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     public void fromCredentialEntry_allParams_success() {
         PasswordCredentialEntry originalEntry = constructEntryWithAllParams();
-
         PasswordCredentialEntry entry = PasswordCredentialEntry.fromCredentialEntry(
                 new CredentialEntry("id",
                         PasswordCredentialEntry.toSlice(originalEntry)));
-
         assertNotNull(entry);
         assertEntryWithAllParams(entry);
     }
-
     private PasswordCredentialEntry constructEntryWithRequiredParamsOnly() {
         return new PasswordCredentialEntry.Builder(
                 mContext,
@@ -264,7 +238,6 @@
                 mPendingIntent,
                 mBeginGetPasswordOption).build();
     }
-
     private PasswordCredentialEntry constructEntryWithAllParams() {
         return new PasswordCredentialEntry.Builder(
                 mContext,
@@ -276,17 +249,19 @@
                 .setIcon(ICON)
                 .setAutoSelectAllowed(IS_AUTO_SELECT_ALLOWED)
                 .setAffiliatedDomain(AFFILIATED_DOMAIN)
+                .setDefaultIconPreferredAsSingleProvider(SINGLE_PROVIDER_ICON_BIT)
                 .build();
     }
-
     private void assertEntryWithRequiredParamsOnly(PasswordCredentialEntry entry,
             Boolean assertOptionIdOnly) {
         assertThat(USERNAME.equals(entry.getUsername()));
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginGetPasswordOption.getType()).isEqualTo(entry.getType());
         assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                DEFAULT_SINGLE_PROVIDER_ICON_BIT);
+        assertThat(entry.getEntryGroupId()).isEqualTo(USERNAME);
     }
-
     private void assertEntryWithAllParams(PasswordCredentialEntry entry) {
         assertThat(USERNAME.equals(entry.getUsername()));
         assertThat(DISPLAYNAME.equals(entry.getDisplayName()));
@@ -297,5 +272,8 @@
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginGetPasswordOption.getType()).isEqualTo(entry.getType());
         assertThat(entry.getAffiliatedDomain()).isEqualTo(AFFILIATED_DOMAIN);
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                SINGLE_PROVIDER_ICON_BIT);
+        assertThat(entry.getEntryGroupId()).isEqualTo(USERNAME);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
index b99ef3b..96fd725 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 package androidx.credentials.provider.ui
-
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
@@ -39,7 +38,6 @@
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
-
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 26)
 @SmallTest
@@ -50,25 +48,20 @@
         mContext, 0, mIntent,
         PendingIntent.FLAG_IMMUTABLE
     )
-
     @Test
     fun constructor_requiredParams_success() {
         val entry = constructEntryWithRequiredParamsOnly()
-
         assertNotNull(entry)
         assertThat(entry.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
         assertEntryWithRequiredParamsOnly(entry)
     }
-
     @Test
     fun constructor_allParams_success() {
         val entry = constructEntryWithAllParams()
-
         assertNotNull(entry)
         assertThat(entry.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
         assertEntryWithAllParams(entry)
     }
-
     @Test
     @Suppress("DEPRECATION")
     fun constructor_emptyUsername_throwsIAE() {
@@ -81,12 +74,10 @@
             )
         }
     }
-
     @Test
     fun constructor_nullIcon_defaultIconSet() {
         val entry = PasswordCredentialEntry.Builder(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION).build()
-
         assertThat(
             equals(
                 entry.icon,
@@ -94,40 +85,32 @@
             )
         ).isTrue()
     }
-
     @Test
     @Suppress("DEPRECATION")
     fun constructor_nullTypeDisplayName_defaultDisplayNameSet() {
         val entry = PasswordCredentialEntry(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION)
-
         assertThat(entry.typeDisplayName).isEqualTo(
             mContext.getString(
                 R.string.android_credentials_TYPE_PASSWORD_CREDENTIAL
             )
         )
     }
-
     @Test
     fun constructor_isAutoSelectAllowedDefault_false() {
         val entry = constructEntryWithRequiredParamsOnly()
         val entry1 = constructEntryWithAllParams()
-
         assertFalse(entry.isAutoSelectAllowed)
         assertFalse(entry1.isAutoSelectAllowed)
     }
-
     @Test
     fun constructor_defaultAffiliatedDomain() {
         val defaultEntry = constructEntryWithRequiredParamsOnly()
-
         assertThat(defaultEntry.affiliatedDomain).isNull()
     }
-
     @Test
     fun constructor_nonEmptyAffiliatedDomainSet_nonEmptyAffiliatedDomainRetrieved() {
         val expectedAffiliatedDomain = "non-empty"
-
         val entryWithAffiliationType = PasswordCredentialEntry(
             mContext,
             USERNAME,
@@ -138,12 +121,44 @@
             ICON,
             affiliatedDomain = expectedAffiliatedDomain
         )
-
         assertThat(entryWithAffiliationType.affiliatedDomain).isEqualTo(expectedAffiliatedDomain)
     }
+    @Test
+    fun constructor_setPreferredDefaultIconBit_retrieveSetPreferredDefaultIconBit() {
+        val expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT
+        val entry = PasswordCredentialEntry(
+            mContext,
+            USERNAME,
+            mPendingIntent,
+            BEGIN_OPTION,
+            DISPLAYNAME,
+            LAST_USED_TIME,
+            ICON,
+            isDefaultIconPreferredAsSingleProvider = expectedPreferredDefaultIconBit
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider)
+            .isEqualTo(expectedPreferredDefaultIconBit)
+    }
+    @Test
+    fun constructor_preferredIconBitNotProvided_retrieveDefaultPreferredIconBit() {
+        val entry = PasswordCredentialEntry(
+            mContext,
+            USERNAME,
+            mPendingIntent,
+            BEGIN_OPTION
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+    }
 
     @Test
-    fun builder_constructDefault_containsOnlyDefaultValuesForSettableParameters() {
+    fun constructor_allRequiredParamsUsed_defaultUsernameEntryGroupIdRetrieved() {
+        val entry = constructEntryWithAllParams()
+
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
+    }
+    @Test
+    fun builder_constructDefault_containsOnlySetPropertiesAndDefaultValues() {
         val entry = PasswordCredentialEntry.Builder(
             mContext,
             USERNAME,
@@ -151,12 +166,20 @@
             BEGIN_OPTION
         ).build()
 
-        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.username).isEqualTo(USERNAME)
         assertThat(entry.displayName).isNull()
+        assertThat(entry.typeDisplayName).isEqualTo(mContext.getString(
+            R.string.android_credentials_TYPE_PASSWORD_CREDENTIAL
+        ))
+        assertThat(entry.pendingIntent).isEqualTo(mPendingIntent)
         assertThat(entry.lastUsedTime).isNull()
+        assertThat(entry.icon.toString()).isEqualTo(
+            Icon.createWithResource(mContext, R.drawable.ic_password).toString())
         assertThat(entry.isAutoSelectAllowed).isFalse()
+        assertThat(entry.beginGetCredentialOption).isEqualTo(BEGIN_OPTION)
+        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
     }
-
     @Test
     fun builder_setAffiliatedDomainNull_retrieveNullAffiliatedDomain() {
         val entry = PasswordCredentialEntry.Builder(
@@ -165,10 +188,8 @@
             mPendingIntent,
             BEGIN_OPTION
         ).setAffiliatedDomain(null).build()
-
         assertThat(entry.affiliatedDomain).isNull()
     }
-
     @Test
     fun builder_setAffiliatedDomainNonNull_retrieveNonNullAffiliatedDomain() {
         val expectedAffiliatedDomain = "name"
@@ -178,45 +199,36 @@
             mPendingIntent,
             BEGIN_OPTION
         ).setAffiliatedDomain(expectedAffiliatedDomain).build()
-
         assertThat(entry.affiliatedDomain).isEqualTo(expectedAffiliatedDomain)
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     fun fromSlice_success() {
         val originalEntry = constructEntryWithAllParams()
         val slice = PasswordCredentialEntry.toSlice(originalEntry)
         assertNotNull(slice)
-
         val entry = fromSlice(slice!!)
-
         assertNotNull(entry)
         entry?.let {
             assertEntryWithAllParams(entry)
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     fun fromCredentialEntry_success() {
         val originalEntry = constructEntryWithAllParams()
         val slice = PasswordCredentialEntry.toSlice(originalEntry)
-
         assertNotNull(slice)
-
         val entry = slice?.let { CredentialEntry("id", it) }?.let {
             PasswordCredentialEntry.fromCredentialEntry(
                 it
             )
         }
-
         assertNotNull(entry)
         entry?.let {
             assertEntryWithAllParams(entry)
         }
     }
-
     @Suppress("DEPRECATION")
     private fun constructEntryWithRequiredParamsOnly(): PasswordCredentialEntry {
         return PasswordCredentialEntry(
@@ -226,7 +238,6 @@
             BEGIN_OPTION
         )
     }
-
     private fun constructEntryWithAllParams(): PasswordCredentialEntry {
         return PasswordCredentialEntry(
             mContext,
@@ -237,16 +248,18 @@
             LAST_USED_TIME,
             ICON,
             IS_AUTO_SELECT_ALLOWED,
-            AFFILIATED_DOMAIN
+            AFFILIATED_DOMAIN,
+            SINGLE_PROVIDER_ICON_BIT
         )
     }
-
     private fun assertEntryWithRequiredParamsOnly(entry: PasswordCredentialEntry) {
         assertThat(USERNAME == entry.username)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
     }
-
     private fun assertEntryWithAllParams(entry: PasswordCredentialEntry) {
         assertThat(USERNAME == entry.username)
         assertThat(DISPLAYNAME == entry.displayName)
@@ -260,8 +273,9 @@
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(entry.isAutoSelectAllowed).isEqualTo(IS_AUTO_SELECT_ALLOWED)
         assertThat(entry.affiliatedDomain).isEqualTo(AFFILIATED_DOMAIN)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
     }
-
     companion object {
         private val USERNAME: CharSequence = "title"
         private val DISPLAYNAME: CharSequence = "subtitle"
@@ -277,5 +291,7 @@
         )
         private val IS_AUTO_SELECT_ALLOWED = false
         private val AFFILIATED_DOMAIN = "affiliation-name"
+        private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
+        private const val SINGLE_PROVIDER_ICON_BIT = true
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
index e3fa133..cc50aad 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.credentials.provider.ui;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -43,7 +42,6 @@
 import org.junit.runner.RunWith;
 
 import java.time.Instant;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26)
 @SmallTest
@@ -52,143 +50,118 @@
     private static final CharSequence DISPLAYNAME = "subtitle";
     private static final CharSequence TYPE_DISPLAY_NAME = "Password";
     private static final Long LAST_USED_TIME = 10L;
-    private static final Icon ICON = Icon.createWithBitmap(Bitmap.createBitmap(
-            100, 100, Bitmap.Config.ARGB_8888));
+    private static final boolean DEFAULT_SINGLE_PROVIDER_ICON_BIT = false;
+    private static final boolean SINGLE_PROVIDER_ICON_BIT = true;
+    private static final Icon ICON = Icon.createWithBitmap(
+            Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
     private static final boolean IS_AUTO_SELECT_ALLOWED = true;
     private final BeginGetPublicKeyCredentialOption mBeginOption =
-            new BeginGetPublicKeyCredentialOption(
-                    new Bundle(), "id", "{\"key1\":{\"key2\":{\"key3\":\"value3\"}}}");
-
+            new BeginGetPublicKeyCredentialOption(new Bundle(), "id",
+                    "{\"key1\":{\"key2\":{\"key3\":\"value3\"}}}");
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Intent mIntent = new Intent();
-    private final PendingIntent mPendingIntent =
-            PendingIntent.getActivity(mContext, 0, mIntent,
-                    PendingIntent.FLAG_IMMUTABLE);
-
+    private final PendingIntent mPendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+            PendingIntent.FLAG_IMMUTABLE);
     @Test
     public void build_requiredParamsOnly_success() {
         PublicKeyCredentialEntry entry = constructWithRequiredParamsOnly();
-
         assertNotNull(entry);
         assertThat(entry.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertEntryWithRequiredParams(entry);
     }
-
     @Test
     public void build_allParams_success() {
         PublicKeyCredentialEntry entry = constructWithAllParams();
-
         assertNotNull(entry);
         assertThat(entry.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertEntryWithAllParams(entry);
     }
-
     @Test
     public void build_withNullUsername_throwsNPE() {
-        assertThrows("Expected null username to throw NPE",
-                NullPointerException.class,
-                () -> new PublicKeyCredentialEntry.Builder(
-                        mContext, null, mPendingIntent, mBeginOption
-                ).build());
+        assertThrows("Expected null username to throw NPE", NullPointerException.class,
+                () -> new PublicKeyCredentialEntry.Builder(mContext, null, mPendingIntent,
+                        mBeginOption).build());
     }
-
     @Test
     public void build_withNullBeginOption_throwsNPE() {
-        assertThrows("Expected null option to throw NPE",
-                NullPointerException.class,
-                () -> new PublicKeyCredentialEntry.Builder(
-                        mContext, USERNAME, mPendingIntent, null
-                ).build());
+        assertThrows("Expected null option to throw NPE", NullPointerException.class,
+                () -> new PublicKeyCredentialEntry.Builder(mContext, USERNAME, mPendingIntent,
+                        null).build());
     }
-
     @Test
     public void build_withNullPendingIntent_throwsNPE() {
-        assertThrows("Expected null pending intent to throw NPE",
-                NullPointerException.class,
-                () -> new PublicKeyCredentialEntry.Builder(
-                        mContext, USERNAME, null, mBeginOption
-                ).build());
+        assertThrows("Expected null pending intent to throw NPE", NullPointerException.class,
+                () -> new PublicKeyCredentialEntry.Builder(mContext, USERNAME, null,
+                        mBeginOption).build());
     }
-
     @Test
     public void build_withEmptyUsername_throwsIAE() {
         assertThrows("Expected empty username to throw IllegalArgumentException",
                 IllegalArgumentException.class,
-                () -> new PublicKeyCredentialEntry.Builder(
-                        mContext, "", mPendingIntent, mBeginOption).build());
+                () -> new PublicKeyCredentialEntry.Builder(mContext, "", mPendingIntent,
+                        mBeginOption).build());
     }
-
     @Test
     public void build_withNullIcon_defaultIconSet() {
-        PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
-                .Builder(
-                mContext, USERNAME, mPendingIntent, mBeginOption).build();
-
+        PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry.Builder(mContext, USERNAME,
+                mPendingIntent, mBeginOption).build();
         assertThat(TestUtilsKt.equals(entry.getIcon(),
                 Icon.createWithResource(mContext, R.drawable.ic_passkey))).isTrue();
     }
-
     @Test
     public void build_nullTypeDisplayName_defaultDisplayNameSet() {
-        PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry.Builder(
-                        mContext, USERNAME, mPendingIntent, mBeginOption).build();
-
+        PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry.Builder(mContext, USERNAME,
+                mPendingIntent, mBeginOption).build();
         assertThat(entry.getTypeDisplayName()).isEqualTo(
-                mContext.getString(
-                        R.string.androidx_credentials_TYPE_PUBLIC_KEY_CREDENTIAL)
-        );
+                mContext.getString(R.string.androidx_credentials_TYPE_PUBLIC_KEY_CREDENTIAL));
     }
-
+    @Test
+    public void builder_setPreferredDefaultIconBit_retrieveSetIconBit() {
+        boolean expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT;
+        PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry.Builder(mContext, USERNAME,
+                mPendingIntent, mBeginOption).setDefaultIconPreferredAsSingleProvider(
+                expectedPreferredDefaultIconBit).build();
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                expectedPreferredDefaultIconBit);
+    }
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_success() {
-        PublicKeyCredentialEntry originalEntry = constructWithAllParams();
-
+        PublicKeyCredentialEntry originalEntry = constructWithRequiredParamsOnly();
         PublicKeyCredentialEntry entry = PublicKeyCredentialEntry.fromSlice(
                 PublicKeyCredentialEntry.toSlice(originalEntry));
-
         assertNotNull(entry);
         assertEntryWithRequiredParams(entry);
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     public void fromCredentialEntry_success() {
         PublicKeyCredentialEntry originalEntry = constructWithAllParams();
         Slice slice = PublicKeyCredentialEntry.toSlice(originalEntry);
         assertNotNull(slice);
-
         PublicKeyCredentialEntry entry = PublicKeyCredentialEntry.fromCredentialEntry(
                 new android.service.credentials.CredentialEntry("id", slice));
-
         assertNotNull(entry);
-        assertEntryWithRequiredParams(entry);
+        assertEntryWithAllParams(entry);
     }
-
     private PublicKeyCredentialEntry constructWithRequiredParamsOnly() {
-        return new PublicKeyCredentialEntry.Builder(
-                mContext,
-                USERNAME,
-                mPendingIntent,
-                mBeginOption
-        ).build();
+        return new PublicKeyCredentialEntry.Builder(mContext, USERNAME, mPendingIntent,
+                mBeginOption).build();
     }
-
     private PublicKeyCredentialEntry constructWithAllParams() {
-        return new PublicKeyCredentialEntry.Builder(
-                mContext, USERNAME, mPendingIntent, mBeginOption)
-                .setAutoSelectAllowed(IS_AUTO_SELECT_ALLOWED)
-                .setDisplayName(DISPLAYNAME)
-                .setLastUsedTime(Instant.ofEpochMilli(LAST_USED_TIME))
-                .setIcon(ICON)
-                .build();
+        return new PublicKeyCredentialEntry.Builder(mContext, USERNAME, mPendingIntent,
+                mBeginOption).setAutoSelectAllowed(IS_AUTO_SELECT_ALLOWED).setDisplayName(
+                DISPLAYNAME).setLastUsedTime(Instant.ofEpochMilli(LAST_USED_TIME)).setIcon(
+                ICON).setDefaultIconPreferredAsSingleProvider(SINGLE_PROVIDER_ICON_BIT).build();
     }
-
     private void assertEntryWithRequiredParams(PublicKeyCredentialEntry entry) {
         assertThat(USERNAME.equals(entry.getUsername()));
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                DEFAULT_SINGLE_PROVIDER_ICON_BIT);
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(USERNAME);
     }
-
     private void assertEntryWithAllParams(PublicKeyCredentialEntry entry) {
         assertThat(USERNAME.equals(entry.getUsername()));
         assertThat(DISPLAYNAME.equals(entry.getDisplayName()));
@@ -197,5 +170,9 @@
         assertThat(Instant.ofEpochMilli(LAST_USED_TIME)).isEqualTo(entry.getLastUsedTime());
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed());
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider()).isEqualTo(
+                SINGLE_PROVIDER_ICON_BIT);
+        assertThat(entry.getAffiliatedDomain()).isNull();
+        assertThat(entry.getEntryGroupId()).isEqualTo(USERNAME);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
index f3ec96d..07df803 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 package androidx.credentials.provider.ui
-
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
@@ -41,7 +40,6 @@
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
-
 @SdkSuppress(minSdkVersion = 26)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -52,16 +50,13 @@
         mContext, 0, mIntent,
         PendingIntent.FLAG_IMMUTABLE
     )
-
     @Test
     fun constructor_requiredParamsOnly_success() {
         val entry = constructWithRequiredParamsOnly()
-
         assertNotNull(entry)
         assertThat(entry.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertEntryWithRequiredParams(entry)
     }
-
     @Test
     fun constructor_allParams_success() {
         val entry = constructWithAllParams()
@@ -69,7 +64,6 @@
         assertThat(entry.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertEntryWithAllParams(entry)
     }
-
     @Test
     fun constructor_emptyUsername_throwsIAE() {
         assertThrows(
@@ -81,13 +75,11 @@
             )
         }
     }
-
     @Test
     fun constructor_nullIcon_defaultIconSet() {
         val entry = PublicKeyCredentialEntry(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION
         )
-
         assertThat(
             equals(
                 entry.icon,
@@ -95,7 +87,6 @@
             )
         ).isTrue()
     }
-
     @Test
     fun constructor_nullTypeDisplayName_defaultDisplayNameSet() {
         val entry = PublicKeyCredentialEntry(
@@ -107,34 +98,86 @@
             )
         )
     }
+    @Test
+    fun constructor_setPreferredDefaultIconBit_retrieveSetPreferredDefaultIconBit() {
+        val expectedPreferredDefaultIconBit = SINGLE_PROVIDER_ICON_BIT
+        val entry = PublicKeyCredentialEntry(
+            mContext,
+            USERNAME,
+            mPendingIntent,
+            BEGIN_OPTION,
+            DISPLAYNAME,
+            Instant.ofEpochMilli(LAST_USED_TIME),
+            ICON,
+            IS_AUTO_SELECT_ALLOWED,
+            isDefaultIconPreferredAsSingleProvider = expectedPreferredDefaultIconBit
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider)
+            .isEqualTo(expectedPreferredDefaultIconBit)
+    }
+    @Test
+    fun constructor_preferredIconBitNotProvided_retrieveDefaultPreferredIconBit() {
+        val entry = PublicKeyCredentialEntry(
+            mContext,
+            USERNAME,
+            mPendingIntent,
+            BEGIN_OPTION
+        )
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+    }
+    @Test
+    fun constructor_allRequiredParamsUsed_defaultUsernameEntryGroupIdRetrieved() {
+        val entry = constructWithAllParams()
+
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
+    }
+
+    @Test
+    fun builder_constructDefault_containsOnlySetPropertiesAndDefaultValues() {
+        val entry = PublicKeyCredentialEntry.Builder(
+            mContext,
+            USERNAME,
+            mPendingIntent,
+            BEGIN_OPTION
+        ).build()
+
+        assertThat(entry.username).isEqualTo(USERNAME)
+        assertThat(entry.displayName).isNull()
+        assertThat(entry.typeDisplayName).isEqualTo(mContext.getString(
+            R.string.androidx_credentials_TYPE_PUBLIC_KEY_CREDENTIAL
+        ))
+        assertThat(entry.pendingIntent).isEqualTo(mPendingIntent)
+        assertThat(entry.lastUsedTime).isNull()
+        assertThat(entry.icon.toString()).isEqualTo(
+            Icon.createWithResource(mContext, R.drawable.ic_passkey).toString())
+        assertThat(entry.isAutoSelectAllowed).isFalse()
+        assertThat(entry.beginGetCredentialOption).isEqualTo(BEGIN_OPTION)
+        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
+    }
 
     @Test
     @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
         val originalEntry = constructWithAllParams()
         val slice = PublicKeyCredentialEntry.toSlice(originalEntry)
-
         assertNotNull(slice)
         val entry = fromSlice(slice!!)
-
         assertNotNull(entry)
         entry?.let {
-            assertEntryWithRequiredParams(entry)
+            assertEntryWithAllParams(entry)
         }
     }
-
     @Test
     @SdkSuppress(minSdkVersion = 34)
     fun fromCredentialEntry_success() {
         val originalEntry = constructWithAllParams()
-
         val entry = toSlice(originalEntry)?.let { CredentialEntry("id", it) }
             ?.let { fromCredentialEntry(it) }
-
         Assert.assertNotNull(entry)
-        assertEntryWithRequiredParams(entry!!)
+        assertEntryWithAllParams(entry!!)
     }
-
     private fun constructWithRequiredParamsOnly(): PublicKeyCredentialEntry {
         return PublicKeyCredentialEntry(
             mContext,
@@ -143,7 +186,6 @@
             BEGIN_OPTION
         )
     }
-
     private fun constructWithAllParams(): PublicKeyCredentialEntry {
         return PublicKeyCredentialEntry(
             mContext,
@@ -153,15 +195,18 @@
             DISPLAYNAME,
             Instant.ofEpochMilli(LAST_USED_TIME),
             ICON,
-            IS_AUTO_SELECT_ALLOWED
+            IS_AUTO_SELECT_ALLOWED,
+            SINGLE_PROVIDER_ICON_BIT
         )
     }
-
     private fun assertEntryWithRequiredParams(entry: PublicKeyCredentialEntry) {
         assertThat(USERNAME == entry.username)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(
+            DEFAULT_SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
     }
-
     private fun assertEntryWithAllParams(entry: PublicKeyCredentialEntry) {
         assertThat(USERNAME == entry.username)
         assertThat(DISPLAYNAME == entry.displayName)
@@ -170,6 +215,9 @@
         assertThat(Instant.ofEpochMilli(LAST_USED_TIME)).isEqualTo(entry.lastUsedTime)
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
+        assertThat(entry.isDefaultIconPreferredAsSingleProvider).isEqualTo(SINGLE_PROVIDER_ICON_BIT)
+        assertThat(entry.affiliatedDomain).isNull()
+        assertThat(entry.entryGroupId).isEqualTo(USERNAME)
     }
 
     companion object {
@@ -183,5 +231,7 @@
         private val ICON = Icon.createWithBitmap(Bitmap.createBitmap(
             100, 100, Bitmap.Config.ARGB_8888))
         private const val IS_AUTO_SELECT_ALLOWED = true
+        private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
+        private const val SINGLE_PROVIDER_ICON_BIT = true
     }
 }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
index 498f9b1..9eef85b 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
@@ -22,16 +22,42 @@
 import androidx.annotation.RestrictTo
 import androidx.credentials.PasswordCredential.Companion.TYPE_PASSWORD_CREDENTIAL
 import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import androidx.credentials.R
 
 /**
  * Base class for a credential entry to be displayed on
  * the selector.
+ *
+ * The [entryGroupId] allows the credential selector display to, in the case of multiple entries
+ * across providers that have the same [entryGroupId] value, trim down to a single, most recently
+ * used provider on the primary card, meant for quick authentication. This will also be used for
+ * entry grouping display logic. However, if the user desires, it is possible to expand back the
+ * entries and select the provider of their choice. This should be something directly linked to the
+ * credential (e.g. [PublicKeyCredentialEntry] and [PasswordCredentialEntry] utilize 'username'),
+ * and should allow variance only as far as the case of letters (i.e. [email protected] and
+ * [email protected]). These guidelines should be followed in cases where [CustomCredentialEntry] are
+ * created.
+ *
+ * @property type the type of the credential associated with this entry, e.g. a
+ * [BeginGetPasswordOption] will have type [TYPE_PASSWORD_CREDENTIAL]
+ * @property beginGetCredentialOption the option from the original [BeginGetCredentialRequest],
+ * for which this credential entry is being added
+ * @property entryGroupId an ID used for deduplication or to group entries during display
+ * @property affiliatedDomain the user visible affiliated domain, a CharSequence
+ * representation of a web domain or an app package name that the given credential in this
+ * entry is associated with when it is different from the requesting entity, default null
+ * @property isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
+ * default credential type icon when you are the only available provider; see individual subclasses
+ * for these default icons (e.g. for [PublicKeyCredentialEntry], it is based on
+ * [R.drawable.ic_password])
  */
 abstract class CredentialEntry internal constructor(
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     open val type: String,
     val beginGetCredentialOption: BeginGetCredentialOption,
+    val entryGroupId: CharSequence,
     val affiliatedDomain: CharSequence? = null,
+    val isDefaultIconPreferredAsSingleProvider: Boolean,
 ) {
 
     @RequiresApi(34)
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
index e751bd20..796f64e 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
@@ -44,7 +44,7 @@
  * used by the user. Note that this value will only be distinguishable up to the milli
  * second mark. If two entries have the same millisecond precision, they will be considered to
  * have been used at the same time
- * @param icon the icon to be displayed with this entry on the UI, must be created using
+ * @property icon the icon to be displayed with this entry on the UI, must be created using
  * [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
  * the latter consumes more memory and may cause undefined behavior due to memory implications
  * on internal transactions; defaulted to a fallback custom credential icon if not provided
@@ -57,6 +57,18 @@
  * selected if it is the only one on the UI. Note that setting this value
  * to true does not guarantee this behavior. The developer must also set this
  * to true, and the framework must determine that only one entry is present
+ * @property affiliatedDomain the user visible affiliated domain, a CharSequence
+ * representation of a web domain or an app package name that the given credential in this
+ * entry is associated with when it is different from the requesting entity, default null
+ * @property entryGroupId an ID used for deduplication or grouping entries during display, by
+ * default set to [title]; for more info on this id, see [CredentialEntry]
+ * @param isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
+ * default credential type icon (see the default value of [icon]) when you are
+ * the only available provider; false by default
+ *
+ * @throws IllegalArgumentException If [type] or [title] are empty
+ *
+ * @see CredentialEntry
  */
 @RequiresApi(26)
 class CustomCredentialEntry internal constructor(
@@ -70,11 +82,17 @@
     val icon: Icon,
     val lastUsedTime: Instant?,
     beginGetCredentialOption: BeginGetCredentialOption,
+    entryGroupId: CharSequence? = title,
+    affiliatedDomain: CharSequence? = null,
+    isDefaultIconPreferredAsSingleProvider: Boolean,
     private val autoSelectAllowedFromOption: Boolean = false,
-    private val isDefaultIcon: Boolean = false
+    private val isDefaultIcon: Boolean = false,
 ) : CredentialEntry(
     type,
-    beginGetCredentialOption
+    beginGetCredentialOption,
+    entryGroupId ?: title,
+    affiliatedDomain,
+    isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
 ) {
     init {
         require(type.isNotEmpty()) { "type must not be empty" }
@@ -103,6 +121,68 @@
      * @param isAutoSelectAllowed whether this entry is allowed to be auto
      * selected if it is the only one on the UI, only takes effect if the app requesting for
      * credentials also opts for auto select
+     *
+     * @throws IllegalArgumentException if [type] or [title] are empty
+     */
+    @Deprecated("Use the constructor that allows setting all parameters.",
+        replaceWith = ReplaceWith("CustomCredentialEntry(context, title, pendingIntent," +
+            "beginGetCredentialOption, subtitle, typeDisplayName, lastUsedTime, icon, " +
+            "isAutoSelectAllowed, entryGroupId, isDefaultIconPreferredAsSingleProvider)"),
+        level = DeprecationLevel.HIDDEN
+    )
+    constructor(
+        context: Context,
+        title: CharSequence,
+        pendingIntent: PendingIntent,
+        beginGetCredentialOption: BeginGetCredentialOption,
+        subtitle: CharSequence? = null,
+        typeDisplayName: CharSequence? = null,
+        lastUsedTime: Instant? = null,
+        icon: Icon = Icon.createWithResource(context, R.drawable.ic_other_sign_in),
+        @Suppress("AutoBoxing")
+        isAutoSelectAllowed: Boolean = false,
+    ) : this(
+        beginGetCredentialOption.type,
+        title,
+        pendingIntent,
+        isAutoSelectAllowed,
+        subtitle,
+        typeDisplayName,
+        icon,
+        lastUsedTime,
+        beginGetCredentialOption,
+        isDefaultIconPreferredAsSingleProvider = false
+    )
+
+    /**
+     * @constructor constructs an instance of [CustomCredentialEntry]
+     *
+     * @param context the context of the calling app, required to retrieve fallback resources
+     * @param title the title shown with this entry on the selector UI
+     * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+     * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
+     * system to attach the final request
+     * @param beginGetCredentialOption the option from the original [BeginGetCredentialRequest],
+     * for which this credential entry is being added
+     * @param subtitle the subTitle shown with this entry on the selector UI
+     * @param lastUsedTime the last used time the credential underlying this entry was
+     * used by the user, distinguishable up to the milli second mark only such that if two
+     * entries have the same millisecond precision, they will be considered to have been used at
+     * the same time
+     * @param typeDisplayName the friendly name to be displayed on the UI for
+     * the type of the credential
+     * @param icon the icon to be displayed with this entry on the selector UI, if not set a
+     * default icon representing a custom credential type is set by the library
+     * @param isAutoSelectAllowed whether this entry is allowed to be auto
+     * selected if it is the only one on the UI, only takes effect if the app requesting for
+     * credentials also opts for auto select
+     * @param entryGroupId an ID to uniquely mark this entry for deduplication or to group entries
+     * during display, set to [title] by default
+     * @param isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
+     * default credential type icon (see the default value of [icon]) when you are
+     * the only available provider; false by default
+     *
+     * @throws IllegalArgumentException If [type] or [title] are empty
      */
     constructor(
         context: Context,
@@ -114,7 +194,9 @@
         lastUsedTime: Instant? = null,
         icon: Icon = Icon.createWithResource(context, R.drawable.ic_other_sign_in),
         @Suppress("AutoBoxing")
-        isAutoSelectAllowed: Boolean = false
+        isAutoSelectAllowed: Boolean = false,
+        entryGroupId: CharSequence = title,
+        isDefaultIconPreferredAsSingleProvider: Boolean = false
     ) : this(
         beginGetCredentialOption.type,
         title,
@@ -124,7 +206,9 @@
         typeDisplayName,
         icon,
         lastUsedTime,
-        beginGetCredentialOption
+        beginGetCredentialOption,
+        entryGroupId.ifEmpty { title },
+        isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
     )
 
     @RequiresApi(34)
@@ -153,11 +237,21 @@
             val icon = entry.icon
             val isAutoSelectAllowed = entry.isAutoSelectAllowed
             val beginGetCredentialOption = entry.beginGetCredentialOption
+            val entryGroupId = entry.entryGroupId
+            val affiliatedDomain = entry.affiliatedDomain
+            val isDefaultIconPreferredAsSingleProvider =
+                entry.isDefaultIconPreferredAsSingleProvider
 
             val autoSelectAllowed = if (isAutoSelectAllowed == true) {
-                AUTO_SELECT_TRUE_STRING
+                TRUE_STRING
             } else {
-                AUTO_SELECT_FALSE_STRING
+                FALSE_STRING
+            }
+
+            val isUsingDefaultIconPreferred = if (isDefaultIconPreferredAsSingleProvider) {
+                TRUE_STRING
+            } else {
+                FALSE_STRING
             }
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
@@ -185,10 +279,22 @@
                     /*subType=*/null,
                     listOf(SLICE_HINT_OPTION_ID)
                 )
+                .addText(
+                    entryGroupId, /*subTypes=*/null,
+                    listOf(SLICE_HINT_DEDUPLICATION_ID)
+                )
+                .addText(
+                    affiliatedDomain, /*subTypes=*/null,
+                    listOf(SLICE_HINT_AFFILIATED_DOMAIN)
+                )
                 .addIcon(
                     icon, /*subType=*/null,
                     listOf(SLICE_HINT_ICON)
                 )
+                .addText(
+                    isUsingDefaultIconPreferred, /*subType=*/null,
+                    listOf(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)
+                )
 
             try {
                 if (icon.resId == R.drawable.ic_other_sign_in) {
@@ -247,8 +353,11 @@
             var lastUsedTime: Instant? = null
             var autoSelectAllowed = false
             var beginGetCredentialOptionId: CharSequence? = null
+            var entryGroupId: CharSequence? = null
             var autoSelectAllowedFromOption = false
+            var isDefaultIconPreferredAsSingleProvider = false
             var isDefaultIcon = false
+            var affiliatedDomain: CharSequence? = null
 
             slice.items.forEach {
                 if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
@@ -267,13 +376,22 @@
                     lastUsedTime = Instant.ofEpochMilli(it.long)
                 } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) {
                     val autoSelectValue = it.text
-                    if (autoSelectValue == AUTO_SELECT_TRUE_STRING) {
+                    if (autoSelectValue == TRUE_STRING) {
                         autoSelectAllowed = true
                     }
+                } else if (it.hasHint(SLICE_HINT_DEDUPLICATION_ID)) {
+                    entryGroupId = it.text
                 } else if (it.hasHint(SLICE_HINT_AUTO_SELECT_FROM_OPTION)) {
                     autoSelectAllowedFromOption = true
+                } else if (it.hasHint(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)) {
+                    val defaultIconValue = it.text
+                    if (defaultIconValue == TRUE_STRING) {
+                        isDefaultIconPreferredAsSingleProvider = true
+                    }
                 } else if (it.hasHint(SLICE_HINT_DEFAULT_ICON_RES_ID)) {
                     isDefaultIcon = true
+                } else if (it.hasHint(SLICE_HINT_AFFILIATED_DOMAIN)) {
+                    affiliatedDomain = it.text
                 }
             }
 
@@ -292,8 +410,11 @@
                         type,
                         Bundle()
                     ),
-                    autoSelectAllowedFromOption,
-                    isDefaultIcon
+                    entryGroupId = entryGroupId,
+                    isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = affiliatedDomain,
+                    autoSelectAllowedFromOption = autoSelectAllowedFromOption,
+                    isDefaultIcon = isDefaultIcon,
                 )
             } catch (e: Exception) {
                 Log.i(TAG, "fromSlice failed with: " + e.message)
@@ -326,18 +447,27 @@
         private const val SLICE_HINT_AUTO_ALLOWED =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
 
+        private const val SLICE_HINT_IS_DEFAULT_ICON_PREFERRED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_IS_DEFAULT_ICON_PREFERRED"
+
         private const val SLICE_HINT_OPTION_ID =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
 
         private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
 
+        private const val SLICE_HINT_DEDUPLICATION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEDUPLICATION_ID"
+
+        private const val SLICE_HINT_AFFILIATED_DOMAIN =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AFFILIATED_DOMAIN"
+
         private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
 
-        private const val AUTO_SELECT_TRUE_STRING = "true"
+        private const val TRUE_STRING = "true"
 
-        private const val AUTO_SELECT_FALSE_STRING = "false"
+        private const val FALSE_STRING = "false"
 
         private const val REVISION_ID = 1
 
@@ -420,6 +550,8 @@
         private var typeDisplayName: CharSequence? = null
         private var icon: Icon? = null
         private var autoSelectAllowed = false
+        private var entryGroupId: CharSequence = title
+        private var isDefaultIconPreferredAsSingleProvider = false
 
         /** Sets a displayName to be shown on the UI with this entry. */
         fun setSubtitle(subtitle: CharSequence?): Builder {
@@ -444,7 +576,7 @@
 
         /**
          * Sets whether the entry should be auto-selected.
-         * The value is false by default
+         * The value is false by default.
          */
         @Suppress("MissingGetterMatchingBuilder")
         fun setAutoSelectAllowed(autoSelectAllowed: Boolean): Builder {
@@ -453,6 +585,18 @@
         }
 
         /**
+         * Sets an ID to uniquely mark this entry for deduplication or for grouping entries during
+         * display; if not set, will default to [title].
+         *
+         * @throws IllegalArgumentException If the entryGroupId is empty
+         */
+        fun setEntryGroupId(entryGroupId: CharSequence): Builder {
+            require(entryGroupId.isNotEmpty()) { "entryGroupId must not be empty" }
+            this.entryGroupId = entryGroupId
+            return this
+        }
+
+        /**
          * Sets the last used time of this account. This information will be used to sort the
          * entries on the selector.
          */
@@ -461,6 +605,17 @@
             return this
         }
 
+        /**
+         * When set to true, the UI prefers to render the default credential type icon when you are
+         * the single available provider; false by default.
+         */
+        fun setDefaultIconPreferredAsSingleProvider(
+            isDefaultIconPreferredAsSingleProvider: Boolean
+        ): Builder {
+            this.isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+            return this
+        }
+
         /** Builds an instance of [CustomCredentialEntry] */
         fun build(): CustomCredentialEntry {
             if (icon == null && Build.VERSION.SDK_INT >= 23) {
@@ -475,7 +630,9 @@
                 typeDisplayName,
                 icon!!,
                 lastUsedTime,
-                beginGetCredentialOption
+                beginGetCredentialOption,
+                entryGroupId = entryGroupId,
+                isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
             )
         }
     }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
index d7550f9..9facc3a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
@@ -50,23 +50,26 @@
  * @property lastUsedTime the last used time of this entry, distinguishable up to the milli
  * second mark, such that if two entries have the same millisecond precision,
  * they will be considered to have been used at the same time
- * @param icon the icon to be displayed with this entry on the UI, must be created using
+ * @property icon the icon to be displayed with this entry on the UI, must be created using
  * [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
  * the latter consumes more memory and may cause undefined behavior due to memory implications
  * on internal transactions; defaulted to a fallback password credential icon if not provided
- * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * @property pendingIntent the [PendingIntent] that will get invoked when the user selects this
  * entry, must be created with a unique request code per entry,
  * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
  * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
  * times
  * @property isAutoSelectAllowed whether this entry is allowed to be auto
  * selected if it is the only one on the UI. Note that setting this value
- * to true does not guarantee this behavior. The developer must also set this
- * to true, and the framework must determine that this is the only entry available for the user.
+ * to true does not guarantee this behavior. The developer must also set this to true, and the
+ * framework must determine that this is the only entry available for the user.
+ * @property entryGroupId an ID used for deduplication or grouping entries during display, always
+ * set to [username]; for more info on this id, see [CredentialEntry]
  *
- * @throws IllegalArgumentException if [username] is empty
+ * @throws IllegalArgumentException If [username] is empty
  *
  * @see CustomCredentialEntry
+ * @see CredentialEntry
  */
 @RequiresApi(26)
 class PasswordCredentialEntry internal constructor(
@@ -78,14 +81,17 @@
     val icon: Icon,
     val isAutoSelectAllowed: Boolean,
     beginGetPasswordOption: BeginGetPasswordOption,
+    entryGroupId: CharSequence? = username,
+    isDefaultIconPreferredAsSingleProvider: Boolean,
     affiliatedDomain: CharSequence? = null,
     private val autoSelectAllowedFromOption: Boolean = false,
-    private val isDefaultIcon: Boolean = false,
-
+    private val isDefaultIcon: Boolean = false
 ) : CredentialEntry(
     PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
     beginGetPasswordOption,
-    affiliatedDomain
+    entryGroupId ?: username,
+    affiliatedDomain,
+    isDefaultIconPreferredAsSingleProvider
 ) {
     init {
         require(username.isNotEmpty()) { "username must not be empty" }
@@ -104,7 +110,7 @@
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
      * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
      * system to attach the final request
-     * @param beginGetPasswordOption the option from the original [BeginGetCredentialResponse],
+     * @param beginGetPasswordOption the option from the original [BeginGetCredentialRequest],
      * for which this credential entry is being added
      * @param displayName the displayName of the account holding the password credential
      * @param lastUsedTime the last used time the credential underlying this entry was
@@ -119,8 +125,11 @@
      * @param affiliatedDomain the user visible affiliated domain, a CharSequence
      * representation of a web domain or an app package name that the given credential in this
      * entry is associated with when it is different from the requesting entity, default null
+     * @param isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
+     * default credential type icon (see the default value of [icon]) when you are the
+     * only available provider; false by default
      *
-     * @throws IllegalArgumentException if [username] is empty
+     * @throws IllegalArgumentException If [username] is empty
      * @throws NullPointerException If [context], [username], [pendingIntent], or
      * [beginGetPasswordOption] is null
      */
@@ -134,6 +143,7 @@
         icon: Icon = Icon.createWithResource(context, R.drawable.ic_password),
         isAutoSelectAllowed: Boolean = false,
         affiliatedDomain: CharSequence? = null,
+        isDefaultIconPreferredAsSingleProvider: Boolean = false,
     ) : this(
         username,
         displayName,
@@ -145,7 +155,8 @@
         icon,
         isAutoSelectAllowed,
         beginGetPasswordOption,
-        affiliatedDomain,
+        affiliatedDomain = affiliatedDomain,
+        isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
     )
 
     /**
@@ -156,7 +167,7 @@
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
      * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
      * system to attach the final request
-     * @param beginGetPasswordOption the option from the original [BeginGetCredentialResponse],
+     * @param beginGetPasswordOption the option from the original [BeginGetCredentialRequest],
      * for which this credential entry is being added
      * @param displayName the displayName of the account holding the password credential
      * @param lastUsedTime the last used time the credential underlying this entry was
@@ -169,11 +180,19 @@
      * selected if it is the only one on the UI, only takes effect if the app requesting for
      * credentials also opts for auto select
      *
-     * @throws IllegalArgumentException if [username] is empty
+     * @throws IllegalArgumentException If [username] is empty
      * @throws NullPointerException If [context], [username], [pendingIntent], or
      * [beginGetPasswordOption] is null
      */
-    @Deprecated("The constructor containing the affiliatedDomain bit should be utilized instead.")
+    @Deprecated(
+        "Use the constructor that allows setting all parameters.",
+        replaceWith = ReplaceWith(
+            "PasswordCredentialEntry(context, username, " +
+                "pendingIntent, beginGetPasswordOption, displayName, lastUsedTime, icon, " +
+                "isAutoSelectAllowed, affiliatedDomain, isDefaultIconPreferredAsSingleProvider)"
+        ),
+        level = DeprecationLevel.HIDDEN
+    )
     constructor(
         context: Context,
         username: CharSequence,
@@ -194,6 +213,7 @@
         icon,
         isAutoSelectAllowed,
         beginGetPasswordOption,
+        isDefaultIconPreferredAsSingleProvider = false
     )
 
     @RequiresApi(34)
@@ -223,12 +243,17 @@
             val isAutoSelectAllowed = entry.isAutoSelectAllowed
             val beginGetPasswordCredentialOption = entry.beginGetCredentialOption
             val affiliatedDomain = entry.affiliatedDomain
+            val entryGroupId = entry.entryGroupId
+            var isDefaultIconPreferredAsSingleProvider =
+                entry.isDefaultIconPreferredAsSingleProvider
 
             val autoSelectAllowed = if (isAutoSelectAllowed) {
-                AUTO_SELECT_TRUE_STRING
+                TRUE_STRING
             } else {
-                AUTO_SELECT_FALSE_STRING
+                FALSE_STRING
             }
+            val isUsingDefaultIcon =
+                if (isDefaultIconPreferredAsSingleProvider) TRUE_STRING else FALSE_STRING
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
                     type, REVISION_ID
@@ -260,9 +285,17 @@
                     listOf(SLICE_HINT_ICON)
                 )
                 .addText(
-                    affiliatedDomain, /*subType=*/null,
+                    entryGroupId, /*subTypes=*/null,
+                    listOf(SLICE_HINT_DEDUPLICATION_ID)
+                )
+                .addText(
+                    affiliatedDomain, /*subTypes=*/null,
                     listOf(SLICE_HINT_AFFILIATED_DOMAIN)
                 )
+                .addText(
+                    isUsingDefaultIcon, /*subType=*/null,
+                    listOf(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)
+                )
             try {
                 if (icon.resId == R.drawable.ic_password) {
                     sliceBuilder.addInt(
@@ -320,8 +353,10 @@
             var autoSelectAllowed = false
             var autoSelectAllowedFromOption = false
             var beginGetPasswordOptionId: CharSequence? = null
-            var isDefaultIcon = false
+            var isDefaultIconPreferredAsSingleProvider = false
             var affiliatedDomain: CharSequence? = null
+            var entryGroupId: CharSequence? = null
+            var isDefaultIcon = false
 
             slice.items.forEach {
                 if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
@@ -340,15 +375,22 @@
                     lastUsedTime = Instant.ofEpochMilli(it.long)
                 } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) {
                     val autoSelectValue = it.text
-                    if (autoSelectValue == AUTO_SELECT_TRUE_STRING) {
+                    if (autoSelectValue == TRUE_STRING) {
                         autoSelectAllowed = true
                     }
                 } else if (it.hasHint(SLICE_HINT_AUTO_SELECT_FROM_OPTION)) {
                     autoSelectAllowedFromOption = true
-                } else if (it.hasHint(SLICE_HINT_DEFAULT_ICON_RES_ID)) {
-                    isDefaultIcon = true
                 } else if (it.hasHint(SLICE_HINT_AFFILIATED_DOMAIN)) {
                     affiliatedDomain = it.text
+                } else if (it.hasHint(SLICE_HINT_DEDUPLICATION_ID)) {
+                    entryGroupId = it.text
+                } else if (it.hasHint(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)) {
+                    val defaultIconValue = it.text
+                    if (defaultIconValue == TRUE_STRING) {
+                        isDefaultIconPreferredAsSingleProvider = true
+                    }
+                } else if (it.hasHint(SLICE_HINT_DEFAULT_ICON_RES_ID)) {
+                    isDefaultIcon = true
                 }
             }
 
@@ -365,9 +407,11 @@
                         Bundle(),
                         beginGetPasswordOptionId!!.toString()
                     ),
-                    affiliatedDomain,
-                    autoSelectAllowedFromOption,
-                    isDefaultIcon
+                    entryGroupId = entryGroupId,
+                    isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = affiliatedDomain,
+                    autoSelectAllowedFromOption = autoSelectAllowedFromOption,
+                    isDefaultIcon = isDefaultIcon,
                 )
             } catch (e: Exception) {
                 Log.i(TAG, "fromSlice failed with: " + e.message)
@@ -406,15 +450,21 @@
         private const val SLICE_HINT_AUTO_ALLOWED =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
 
+        private const val SLICE_HINT_IS_DEFAULT_ICON_PREFERRED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_IS_DEFAULT_ICON_PREFERRED"
+
         private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
 
+        private const val SLICE_HINT_DEDUPLICATION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEDUPLICATION_ID"
+
         private const val SLICE_HINT_AFFILIATED_DOMAIN =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AFFILIATED_DOMAIN"
 
-        private const val AUTO_SELECT_TRUE_STRING = "true"
+        private const val TRUE_STRING = "true"
 
-        private const val AUTO_SELECT_FALSE_STRING = "false"
+        private const val FALSE_STRING = "false"
 
         private const val REVISION_ID = 1
 
@@ -484,7 +534,7 @@
      *
      * @throws NullPointerException If [context], [username], [pendingIntent], or
      * [beginGetPasswordOption] is null
-     * @throws IllegalArgumentException if [username] is empty
+     * @throws IllegalArgumentException If [username] is empty
      */
     class Builder(
         private val context: Context,
@@ -497,6 +547,7 @@
         private var icon: Icon? = null
         private var autoSelectAllowed = false
         private var affiliatedDomain: CharSequence? = null
+        private var isDefaultIconPreferredAsSingleProvider: Boolean = false
 
         /** Sets a displayName to be shown on the UI with this entry. */
         fun setDisplayName(displayName: CharSequence?): Builder {
@@ -540,6 +591,17 @@
             return this
         }
 
+        /**
+         * When set to true, the UI prefers to render the default credential type icon when you are
+         * the single available provider; false by default.
+         */
+        fun setDefaultIconPreferredAsSingleProvider(
+            isDefaultIconPreferredAsSingleProvider: Boolean
+        ): Builder {
+            this.isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+            return this
+        }
+
         /** Builds an instance of [PasswordCredentialEntry] */
         fun build(): PasswordCredentialEntry {
             if (icon == null && Build.VERSION.SDK_INT >= 23) {
@@ -557,7 +619,8 @@
                 icon!!,
                 autoSelectAllowed,
                 beginGetPasswordOption,
-                affiliatedDomain
+                affiliatedDomain = affiliatedDomain,
+                isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
             )
         }
     }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
index 3773ebd..d70ca3d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
@@ -50,21 +50,28 @@
  * @property lastUsedTime the last used time of this entry. Note that this value will only be
  * distinguishable up to the milli second mark. If two entries have the same millisecond precision,
  * they will be considered to have been used at the same time
- * @param icon the icon to be displayed with this entry on the UI, must be created using
+ * @property icon the icon to be displayed with this entry on the UI, must be created using
  * [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
  * the latter consumes more memory and may cause undefined behavior due to memory implications
  * on internal transactions; defaulted to a fallback public key credential icon if not provided
- * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * @property pendingIntent the [PendingIntent] that will get invoked when the user selects this
  * entry, must be created with a unique request code per entry,
  * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
  * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
  * times
  * @property isAutoSelectAllowed whether this entry is allowed to be auto
  * selected if it is the only one on the UI. Note that setting this value
- * to true does not guarantee this behavior. The developer must also set this
- * to true, and the framework must determine that it is safe to auto select.
+ * to true does not guarantee this behavior. The developer must also set this to true, and the
+ * framework must determine that this is the only entry available for the user.
+ * @property affiliatedDomain the user visible affiliated domain, a CharSequence
+ * representation of a web domain or an app package name that the given credential in this
+ * entry is associated with when it is different from the requesting entity, default null
+ * @property entryGroupId an ID used for deduplication or grouping entries during display, always
+ * set to [username]; for more info on this id, see [CredentialEntry]
  *
- * @throws IllegalArgumentException if [username] is empty
+ * @throws IllegalArgumentException If [username] is empty
+ *
+ * @see CredentialEntry
  */
 @RequiresApi(26)
 class PublicKeyCredentialEntry internal constructor(
@@ -76,11 +83,17 @@
     val lastUsedTime: Instant?,
     val isAutoSelectAllowed: Boolean,
     beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption,
+    entryGroupId: CharSequence? = username,
+    affiliatedDomain: CharSequence? = null,
+    isDefaultIconPreferredAsSingleProvider: Boolean,
     private val autoSelectAllowedFromOption: Boolean = false,
     private val isDefaultIcon: Boolean = false
 ) : CredentialEntry(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    beginGetPublicKeyCredentialOption
+    beginGetPublicKeyCredentialOption,
+    entryGroupId ?: username,
+    affiliatedDomain,
+    isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
 ) {
 
     init {
@@ -99,6 +112,60 @@
      * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
      * times
      * @param beginGetPublicKeyCredentialOption the option from the original
+     * [BeginGetCredentialRequest], for which this credential entry is being added
+     * @param displayName the displayName of the account holding the public key credential
+     * @param lastUsedTime the last used time the credential underlying this entry was
+     * used by the user, distinguishable up to the milli second mark only such that if two
+     * entries have the same millisecond precision, they will be considered to have been used at
+     * the same time
+     * @param icon the icon to be displayed with this entry on the selector, if not set, a
+     * default icon representing a public key credential type is set by the library
+     * @param isAutoSelectAllowed whether this entry is allowed to be auto
+     * selected if it is the only one on the UI, only takes effect if the app requesting for
+     * credentials also opts for auto select
+     * @param isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
+     * default credential type icon (see the default value of [icon]) when you are the
+     * only available provider; false by default
+     *
+     * @throws NullPointerException If [context], [username], [pendingIntent], or
+     * [beginGetPublicKeyCredentialOption] is null
+     * @throws IllegalArgumentException if [username] is empty
+     */
+    constructor(
+        context: Context,
+        username: CharSequence,
+        pendingIntent: PendingIntent,
+        beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption,
+        displayName: CharSequence? = null,
+        lastUsedTime: Instant? = null,
+        icon: Icon = Icon.createWithResource(context, R.drawable.ic_passkey),
+        isAutoSelectAllowed: Boolean = false,
+        isDefaultIconPreferredAsSingleProvider: Boolean = false,
+    ) : this(
+        username,
+        displayName,
+        context.getString(
+            R.string.androidx_credentials_TYPE_PUBLIC_KEY_CREDENTIAL
+        ),
+        pendingIntent,
+        icon,
+        lastUsedTime,
+        isAutoSelectAllowed,
+        beginGetPublicKeyCredentialOption,
+        isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+    )
+
+    /**
+     * @constructor constructs an instance of [PublicKeyCredentialEntry]
+     *
+     * @param context the context of the calling app, required to retrieve fallback resources
+     * @param username the username of the account holding the public key credential
+     * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+     * entry, must be created with a unique request code per entry,
+     * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+     * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+     * times
+     * @param beginGetPublicKeyCredentialOption the option from the original
      * [BeginGetCredentialResponse], for which this credential entry is being added
      * @param displayName the displayName of the account holding the public key credential
      * @param lastUsedTime the last used time the credential underlying this entry was
@@ -115,6 +182,12 @@
      * [beginGetPublicKeyCredentialOption] is null
      * @throws IllegalArgumentException if [username] is empty
      */
+    @Deprecated("Use the constructor that allows setting all parameters.",
+        replaceWith = ReplaceWith("PublicKeyCredentialEntry(context, username, pendingIntent," +
+            "beginGetPublicKeyCredentialOption, displayName, lastUsedTime, icon, " +
+            "isAutoSelectAllowed, isDefaultIconPreferredAsSingleProvider)"),
+        level = DeprecationLevel.HIDDEN
+    )
     constructor(
         context: Context,
         username: CharSequence,
@@ -134,7 +207,8 @@
         icon,
         lastUsedTime,
         isAutoSelectAllowed,
-        beginGetPublicKeyCredentialOption
+        beginGetPublicKeyCredentialOption,
+        isDefaultIconPreferredAsSingleProvider = false
     )
 
     @RequiresApi(34)
@@ -164,11 +238,20 @@
             val icon = entry.icon
             val isAutoSelectAllowed = entry.isAutoSelectAllowed
             val beginGetPublicKeyCredentialOption = entry.beginGetCredentialOption
+            val entryGroupId = entry.entryGroupId
+            val affiliatedDomain = entry.affiliatedDomain
+            val isDefaultIconPreferredAsSingleProvider =
+                entry.isDefaultIconPreferredAsSingleProvider
 
             val autoSelectAllowed = if (isAutoSelectAllowed) {
-                AUTO_SELECT_TRUE_STRING
+                TRUE_STRING
             } else {
-                AUTO_SELECT_FALSE_STRING
+                FALSE_STRING
+            }
+            val isUsingDefaultIcon = if (isDefaultIconPreferredAsSingleProvider) {
+                TRUE_STRING
+            } else {
+                FALSE_STRING
             }
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
@@ -200,6 +283,18 @@
                     icon, /*subType=*/null,
                     listOf(SLICE_HINT_ICON)
                 )
+                .addText(
+                    entryGroupId, /*subTypes=*/null,
+                    listOf(SLICE_HINT_DEDUPLICATION_ID)
+                )
+                .addText(
+                    affiliatedDomain, /*subTypes=*/null,
+                    listOf(SLICE_HINT_AFFILIATED_DOMAIN)
+                )
+                .addText(
+                    isUsingDefaultIcon, /*subType=*/null,
+                    listOf(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)
+                )
             try {
                 if (icon.resId == R.drawable.ic_passkey) {
                     sliceBuilder.addInt(
@@ -256,7 +351,10 @@
             var autoSelectAllowed = false
             var beginGetPublicKeyCredentialOptionId: CharSequence? = null
             var autoSelectAllowedFromOption = false
+            var isDefaultIconPreferredAsSingleProvider = false
             var isDefaultIcon = false
+            var entryGroupId: CharSequence? = null
+            var affiliatedDomain: CharSequence? = null
 
             slice.items.forEach {
                 if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
@@ -275,13 +373,22 @@
                     lastUsedTime = Instant.ofEpochMilli(it.long)
                 } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) {
                     val autoSelectValue = it.text
-                    if (autoSelectValue == AUTO_SELECT_TRUE_STRING) {
+                    if (autoSelectValue == TRUE_STRING) {
                         autoSelectAllowed = true
                     }
                 } else if (it.hasHint(SLICE_HINT_AUTO_SELECT_FROM_OPTION)) {
                     autoSelectAllowedFromOption = true
+                } else if (it.hasHint(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)) {
+                    val defaultIconValue = it.text
+                    if (defaultIconValue == TRUE_STRING) {
+                        isDefaultIconPreferredAsSingleProvider = true
+                    }
                 } else if (it.hasHint(SLICE_HINT_DEFAULT_ICON_RES_ID)) {
                     isDefaultIcon = true
+                } else if (it.hasHint(SLICE_HINT_DEDUPLICATION_ID)) {
+                    entryGroupId = it.text
+                } else if (it.hasHint(SLICE_HINT_AFFILIATED_DOMAIN)) {
+                    affiliatedDomain = it.text
                 }
             }
 
@@ -298,8 +405,11 @@
                         Bundle(),
                         beginGetPublicKeyCredentialOptionId!!.toString()
                     ),
-                    autoSelectAllowedFromOption,
-                    isDefaultIcon
+                    entryGroupId = entryGroupId,
+                    isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = affiliatedDomain,
+                    autoSelectAllowedFromOption = autoSelectAllowedFromOption,
+                    isDefaultIcon = isDefaultIcon,
                 )
             } catch (e: Exception) {
                 Log.i(TAG, "fromSlice failed with: " + e.message)
@@ -332,6 +442,9 @@
         private const val SLICE_HINT_AUTO_ALLOWED =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
 
+        private const val SLICE_HINT_IS_DEFAULT_ICON_PREFERRED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_IS_DEFAULT_ICON_PREFERRED"
+
         private const val SLICE_HINT_OPTION_ID =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
 
@@ -341,9 +454,15 @@
         private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
             "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
 
-        private const val AUTO_SELECT_TRUE_STRING = "true"
+        private const val SLICE_HINT_AFFILIATED_DOMAIN =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AFFILIATED_DOMAIN"
 
-        private const val AUTO_SELECT_FALSE_STRING = "false"
+        private const val SLICE_HINT_DEDUPLICATION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEDUPLICATION_ID"
+
+        private const val TRUE_STRING = "true"
+
+        private const val FALSE_STRING = "false"
 
         private const val REVISION_ID = 1
 
@@ -413,6 +532,7 @@
         private var lastUsedTime: Instant? = null
         private var icon: Icon? = null
         private var autoSelectAllowed: Boolean = false
+        private var isDefaultIconPreferredAsSingleProvider: Boolean = false
 
         /** Sets a displayName to be shown on the UI with this entry */
         fun setDisplayName(displayName: CharSequence?): Builder {
@@ -446,6 +566,17 @@
             return this
         }
 
+        /**
+         * When set to true, the UI prefers to render the default credential type icon when you are
+         * the single available provider; false by default.
+         */
+        fun setDefaultIconPreferredAsSingleProvider(
+            isDefaultIconPreferredAsSingleProvider: Boolean
+        ): Builder {
+            this.isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+            return this
+        }
+
         /** Builds an instance of [PublicKeyCredentialEntry] */
         fun build(): PublicKeyCredentialEntry {
             if (icon == null && Build.VERSION.SDK_INT >= 23) {
@@ -462,7 +593,8 @@
                 icon!!,
                 lastUsedTime,
                 autoSelectAllowed,
-                beginGetPublicKeyCredentialOption
+                beginGetPublicKeyCredentialOption,
+                isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
             )
         }
     }
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 799d2bd..0c69500 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -405,17 +405,18 @@
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebViewCompat\.java:[0-9]+ Missing @param tag for parameter `webview` in DFunction removeWebMessageListener
 WARN: .*\/unzippedJvmSources\/androidx\/work\/testing\/TestListenableWorkerBuilder\.kt:UnknownLine Missing @param tag for parameter `tags` in DFunction TestListenableWorkerBuilder
 WARN: .*\/unzippedJvmSources\/androidx\/work\/testing\/TestWorkerBuilder\.kt:UnknownLine Missing @param tag for parameter `tags` in DFunction TestWorkerBuilder
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `placeholdersEnabled` in DFunction LoadInitialParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedInitialKey` in DFunction LoadInitialParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadInitialParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `key` in DFunction LoadParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `nextPageKey` in DFunction onResult
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `previousPageKey` in DFunction onResult
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `placeholdersEnabled` in DFunction LoadInitialParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadInitialParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `key` in DFunction LoadParams
-WARN: .*\/unzippedJvmSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `placeholdersEnabled` in DFunction LoadInitialParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedInitialKey` in DFunction LoadInitialParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadInitialParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `key` in DFunction LoadParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `nextPageKey` in DFunction onResult
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `previousPageKey` in DFunction onResult
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `placeholdersEnabled` in DFunction LoadInitialParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadInitialParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `key` in DFunction LoadParams
+WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/PageKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadParams
+WARN: .*\/unzippedMultiplatformSources\/commonMain\/androidx\/paging\/LoadState\.kt:[0-9]+ Failed to resolve See androidx\.paging\.PagedList\.retry in DClass Error\. Did you mean androidx\.paging\.PagedList#retry\?
 WARN: .*\/unzippedMultiplatformSources\/androidMain\/androidx\/compose\/animation\/graphics\/res\/AnimatedVectorPainterResources\.android\.kt:[0-9]+ Missing @param tag for parameter `animatedImageVector` in DFunction rememberAnimatedVectorPainter
 WARN: .*\/unzippedMultiplatformSources\/androidMain\/androidx\/compose\/material\/AndroidMenu\.android\.kt:[0-9]+ Missing @param tag for parameter `content` in DFunction DropdownMenuItem
 WARN: .*\/unzippedMultiplatformSources\/androidMain\/androidx\/compose\/ui\/graphics\/AndroidPath\.android\.kt:[0-9]+ Missing @param tag for parameter `operation` in DFunction op
diff --git a/development/simplify-build-failure/simplify-build-failure.sh b/development/simplify-build-failure/simplify-build-failure.sh
index 078b6fd..c5d80c8 100755
--- a/development/simplify-build-failure/simplify-build-failure.sh
+++ b/development/simplify-build-failure/simplify-build-failure.sh
@@ -135,6 +135,8 @@
   fi
   if [ "$arg" == "--check-lines-in" ]; then
     subfilePath="$1"
+    # normalize path
+    subfilePath="$(realpath $subfilePath --relative-to=.)"
     shift
     continue
   fi
diff --git a/development/update_studio.sh b/development/update_studio.sh
index d9b419b..04c35d1 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -7,8 +7,8 @@
 
 # Versions that the user should update when running this script
 echo Getting Studio version and link
-AGP_VERSION=${1:-8.3.0-beta01}
-STUDIO_VERSION_STRING=${2:-"Android Studio Iguana | 2023.2.1 Beta 1"}
+AGP_VERSION=${1:-8.4.0-alpha09}
+STUDIO_VERSION_STRING=${2:-"Android Studio Jellyfish | 2023.3.1 Canary 9"}
 
 # Get studio version number from version name
 STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep "<iframe " | sed "s/.* src=\"\([^\"]*\)\".*/\1/g"`
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 7414d46..d61a894 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -15,10 +15,10 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.9.0-alpha02")
-    docs("androidx.activity:activity-compose:1.9.0-alpha02")
-    samples("androidx.activity:activity-compose-samples:1.9.0-alpha02")
-    docs("androidx.activity:activity-ktx:1.9.0-alpha02")
+    docs("androidx.activity:activity:1.9.0-alpha03")
+    docs("androidx.activity:activity-compose:1.9.0-alpha03")
+    samples("androidx.activity:activity-compose-samples:1.9.0-alpha03")
+    docs("androidx.activity:activity-ktx:1.9.0-alpha03")
     // ads-identifier is deprecated
     docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
@@ -48,7 +48,7 @@
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
     docs("androidx.bluetooth:bluetooth:1.0.0-alpha02")
     docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha02")
-    docs("androidx.browser:browser:1.8.0-beta01")
+    docs("androidx.browser:browser:1.8.0-beta02")
     docs("androidx.camera:camera-camera2:1.4.0-alpha04")
     docs("androidx.camera:camera-core:1.4.0-alpha04")
     docs("androidx.camera:camera-effects:1.4.0-alpha04")
@@ -69,59 +69,59 @@
     docs("androidx.cardview:cardview:1.0.0")
     kmpDocs("androidx.collection:collection:1.4.0")
     docs("androidx.collection:collection-ktx:1.4.0-rc01")
-    kmpDocs("androidx.compose.animation:animation:1.7.0-alpha01")
-    kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha01")
-    kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha01")
-    samples("androidx.compose.animation:animation-samples:1.7.0-alpha01")
-    samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha01")
-    samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha01")
-    kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha01")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha01")
-    samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.material3:material3:1.2.0-rc01")
+    kmpDocs("androidx.compose.animation:animation:1.7.0-alpha02")
+    kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha02")
+    kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha02")
+    samples("androidx.compose.animation:animation-samples:1.7.0-alpha02")
+    samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha02")
+    samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha02")
+    kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha02")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha02")
+    samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.material3:material3:1.2.0")
     kmpDocs("androidx.compose.material3:material3-adaptive:1.0.0-alpha05")
     kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.0.0-alpha02")
-    samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0-rc01")
-    samples("androidx.compose.material3:material3-adaptive-samples:1.2.0-rc01")
-    samples("androidx.compose.material3:material3-samples:1.2.0-rc01")
-    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-rc01")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-rc01")
-    kmpDocs("androidx.compose.material:material:1.7.0-alpha01")
-    kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha01")
-    samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha01")
-    samples("androidx.compose.material:material-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha01")
-    docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha01")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha01")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha01")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha01")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha01")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha01")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha01")
-    samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha01")
+    samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0")
+    samples("androidx.compose.material3:material3-adaptive-samples:1.2.0")
+    samples("androidx.compose.material3:material3-samples:1.2.0")
+    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0")
+    kmpDocs("androidx.compose.material:material:1.7.0-alpha02")
+    kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha02")
+    samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha02")
+    samples("androidx.compose.material:material-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha02")
+    docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha02")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha02")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha02")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha02")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha02")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha02")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha02")
+    samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha02")
     docs("androidx.compose.runtime:runtime-tracing:1.0.0-beta01")
-    kmpDocs("androidx.compose.ui:ui:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha01")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha01")
-    kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha01")
-    docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha01")
-    samples("androidx.compose.ui:ui-samples:1.7.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha02")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha02")
+    docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha02")
+    samples("androidx.compose.ui:ui-samples:1.7.0-alpha02")
     docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
     docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
     docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
@@ -129,23 +129,23 @@
     docs("androidx.constraintlayout:constraintlayout-core:1.1.0-alpha13")
     docs("androidx.contentpager:contentpager:1.0.0")
     docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha02")
-    docs("androidx.core:core:1.13.0-alpha04")
+    docs("androidx.core:core:1.13.0-alpha05")
     // TODO(b/294531403): Turn on apiSince for core-animation when it releases as alpha
     docsWithoutApiSince("androidx.core:core-animation:1.0.0-rc01")
     docsWithoutApiSince("androidx.core:core-animation-testing:1.0.0-rc01")
     docs("androidx.core:core-google-shortcuts:1.2.0-alpha01")
     docs("androidx.core:core-i18n:1.0.0-alpha01")
-    docs("androidx.core:core-ktx:1.13.0-alpha04")
+    docs("androidx.core:core-ktx:1.13.0-alpha05")
     docs("androidx.core:core-location-altitude:1.0.0-alpha01")
     docs("androidx.core:core-performance:1.0.0")
     docs("androidx.core:core-performance-play-services:1.0.0")
     samples("androidx.core:core-performance-samples:1.0.0")
     docs("androidx.core:core-performance-testing:1.0.0")
-    docs("androidx.core:core-remoteviews:1.0.0-rc01")
+    docs("androidx.core:core-remoteviews:1.1.0-alpha01")
     docs("androidx.core:core-role:1.2.0-alpha01")
     docs("androidx.core:core-splashscreen:1.1.0-alpha02")
     docs("androidx.core:core-telecom:1.0.0-alpha02")
-    docs("androidx.core:core-testing:1.13.0-alpha04")
+    docs("androidx.core:core-testing:1.13.0-alpha05")
     docs("androidx.core.uwb:uwb:1.0.0-alpha08")
     docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha08")
     docs("androidx.credentials:credentials:1.3.0-alpha01")
@@ -181,35 +181,38 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.6")
-    docs("androidx.fragment:fragment:1.7.0-alpha09")
-    docs("androidx.fragment:fragment-compose:1.7.0-alpha09")
-    docs("androidx.fragment:fragment-ktx:1.7.0-alpha09")
-    docs("androidx.fragment:fragment-testing:1.7.0-alpha09")
-    docs("androidx.glance:glance:1.0.0")
-    docs("androidx.glance:glance-appwidget:1.0.0")
-    samples("androidx.glance:glance-appwidget-samples:1.0.0")
+    docs("androidx.fragment:fragment:1.7.0-alpha10")
+    docs("androidx.fragment:fragment-compose:1.7.0-alpha10")
+    docs("androidx.fragment:fragment-ktx:1.7.0-alpha10")
+    docs("androidx.fragment:fragment-testing:1.7.0-alpha10")
+    docs("androidx.glance:glance:1.1.0-alpha01")
+    docs("androidx.glance:glance-appwidget:1.1.0-alpha01")
+    samples("androidx.glance:glance-appwidget-samples:1.1.0-alpha01")
     docs("androidx.glance:glance-appwidget-preview:1.0.0-alpha06")
-    docs("androidx.glance:glance-material:1.0.0")
-    docs("androidx.glance:glance-material3:1.0.0")
+    docs("androidx.glance:glance-appwidget-testing:1.1.0-alpha01")
+    samples("androidx.glance:glance-appwidget-testing-samples:1.1.0-alpha01")
+    docs("androidx.glance:glance-material:1.1.0-alpha01")
+    docs("androidx.glance:glance-material3:1.1.0-alpha01")
     docs("androidx.glance:glance-preview:1.0.0-alpha06")
     docs("androidx.glance:glance-template:1.0.0-alpha06")
+    docs("androidx.glance:glance-testing:1.1.0-alpha01")
     docs("androidx.glance:glance-wear-tiles:1.0.0-alpha06")
     docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
     docs("androidx.graphics:graphics-core:1.0.0-beta01")
     samples("androidx.graphics:graphics-core-samples:1.0.0-beta01")
     docs("androidx.graphics:graphics-path:1.0.0-beta02")
-    docs("androidx.graphics:graphics-shapes:1.0.0-alpha04")
+    kmpDocs("androidx.graphics:graphics-shapes:1.0.0-alpha05")
     docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
     docs("androidx.health.connect:connect-client:1.1.0-alpha07")
     samples("androidx.health.connect:connect-client-samples:1.1.0-alpha07")
     docs("androidx.health:health-services-client:1.1.0-alpha02")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha02")
-    docs("androidx.hilt:hilt-common:1.2.0-beta01")
-    docs("androidx.hilt:hilt-navigation:1.2.0-beta01")
-    docs("androidx.hilt:hilt-navigation-compose:1.2.0-beta01")
-    samples("androidx.hilt:hilt-navigation-compose-samples:1.2.0-beta01")
-    docs("androidx.hilt:hilt-navigation-fragment:1.2.0-beta01")
-    docs("androidx.hilt:hilt-work:1.2.0-beta01")
+    docs("androidx.hilt:hilt-common:1.2.0-rc01")
+    docs("androidx.hilt:hilt-navigation:1.2.0-rc01")
+    docs("androidx.hilt:hilt-navigation-compose:1.2.0-rc01")
+    samples("androidx.hilt:hilt-navigation-compose-samples:1.2.0-rc01")
+    docs("androidx.hilt:hilt-navigation-fragment:1.2.0-rc01")
+    docs("androidx.hilt:hilt-work:1.2.0-rc01")
     docs("androidx.input:input-motionprediction:1.0.0-beta03")
     docs("androidx.interpolator:interpolator:1.0.0")
     docs("androidx.javascriptengine:javascriptengine:1.0.0-beta01")
@@ -273,34 +276,34 @@
     docsWithoutApiSince("androidx.media3:media3-transformer:1.3.0-beta01")
     docsWithoutApiSince("androidx.media3:media3-ui:1.3.0-beta01")
     docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.3.0-beta01")
-    docs("androidx.mediarouter:mediarouter:1.7.0-alpha01")
-    docs("androidx.mediarouter:mediarouter-testing:1.7.0-alpha01")
+    docs("androidx.mediarouter:mediarouter:1.7.0-alpha02")
+    docs("androidx.mediarouter:mediarouter-testing:1.7.0-alpha02")
     docs("androidx.metrics:metrics-performance:1.0.0-beta01")
-    docs("androidx.navigation:navigation-common:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-compose:2.8.0-alpha01")
-    samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-fragment:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-runtime:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-testing:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-ui:2.8.0-alpha01")
-    docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha01")
-    docs("androidx.paging:paging-common:3.3.0-alpha01")
-    docs("androidx.paging:paging-common-ktx:3.3.0-alpha01")
-    kmpDocs("androidx.paging:paging-compose:3.3.0-alpha01")
-    samples("androidx.paging:paging-compose-samples:3.3.0-alpha01")
-    docs("androidx.paging:paging-guava:3.3.0-alpha01")
-    docs("androidx.paging:paging-runtime:3.3.0-alpha01")
-    docs("androidx.paging:paging-runtime-ktx:3.3.0-alpha01")
-    docs("androidx.paging:paging-rxjava2:3.3.0-alpha01")
-    docs("androidx.paging:paging-rxjava2-ktx:3.3.0-alpha01")
-    docs("androidx.paging:paging-rxjava3:3.3.0-alpha01")
-    samples("androidx.paging:paging-samples:3.3.0-alpha01")
-    docs("androidx.paging:paging-testing:3.3.0-alpha01")
+    docs("androidx.navigation:navigation-common:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-compose:2.8.0-alpha02")
+    samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-fragment:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-runtime:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-testing:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-ui:2.8.0-alpha02")
+    docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha02")
+    kmpDocs("androidx.paging:paging-common:3.3.0-alpha03")
+    docs("androidx.paging:paging-common-ktx:3.3.0-alpha03")
+    kmpDocs("androidx.paging:paging-compose:3.3.0-alpha03")
+    samples("androidx.paging:paging-compose-samples:3.3.0-alpha03")
+    docs("androidx.paging:paging-guava:3.3.0-alpha03")
+    docs("androidx.paging:paging-runtime:3.3.0-alpha03")
+    docs("androidx.paging:paging-runtime-ktx:3.3.0-alpha03")
+    docs("androidx.paging:paging-rxjava2:3.3.0-alpha03")
+    docs("androidx.paging:paging-rxjava2-ktx:3.3.0-alpha03")
+    docs("androidx.paging:paging-rxjava3:3.3.0-alpha03")
+    samples("androidx.paging:paging-samples:3.3.0-alpha03")
+    kmpDocs("androidx.paging:paging-testing:3.3.0-alpha03")
     docs("androidx.palette:palette:1.0.0")
     docs("androidx.palette:palette-ktx:1.0.0")
     docs("androidx.percentlayout:percentlayout:1.0.1")
@@ -315,11 +318,11 @@
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha12")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha12")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-provider:1.0.0-alpha12")
-    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha06")
+    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha07")
     docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha07")
     docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha07")
     docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha07")
-    docs("androidx.profileinstaller:profileinstaller:1.3.1")
+    docs("androidx.profileinstaller:profileinstaller:1.4.0-alpha01")
     docs("androidx.recommendation:recommendation:1.0.0")
     docs("androidx.recyclerview:recyclerview:1.4.0-alpha01")
     docs("androidx.recyclerview:recyclerview-selection:2.0.0-alpha01")
@@ -355,7 +358,7 @@
     docs("androidx.sqlite:sqlite-ktx:2.4.0")
     docs("androidx.startup:startup-runtime:1.2.0-alpha02")
     docs("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
-    // androidx.test is not hosted in androidx\
+    // androidx.test is not hosted in androidx
     docsWithoutApiSince("androidx.test:core:1.6.0-alpha05")
     docsWithoutApiSince("androidx.test:core-ktx:1.6.0-alpha05")
     docsWithoutApiSince("androidx.test:monitor:1.7.0-alpha04")
@@ -375,7 +378,7 @@
     docsWithoutApiSince("androidx.test.ext:junit-ktx:1.2.0-alpha03")
     docsWithoutApiSince("androidx.test.ext:truth:1.6.0-alpha03")
     docsWithoutApiSince("androidx.test.services:storage:1.5.0-alpha03")
-    docsWithoutApiSince("androidx.test.uiautomator:uiautomator:2.3.0-beta01")
+    docsWithoutApiSince("androidx.test.uiautomator:uiautomator:2.3.0-rc01")
     // androidx.textclassifier is not hosted in androidx
     docsWithoutApiSince("androidx.textclassifier:textclassifier:1.0.0-alpha04")
     docs("androidx.tracing:tracing:1.3.0-alpha02")
@@ -396,44 +399,44 @@
     docs("androidx.versionedparcelable:versionedparcelable:1.2.0")
     docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
     docs("androidx.viewpager:viewpager:1.1.0-alpha01")
-    docs("androidx.wear.compose:compose-foundation:1.4.0-alpha01")
-    samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha01")
-    docs("androidx.wear.compose:compose-material:1.4.0-alpha01")
-    docs("androidx.wear.compose:compose-material-core:1.4.0-alpha01")
-    samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha01")
-    docs("androidx.wear.compose:compose-material3:1.0.0-alpha16")
-    samples("androidx.wear.compose:compose-material3-samples:1.3.0-rc01")
-    docs("androidx.wear.compose:compose-navigation:1.4.0-alpha01")
-    samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha01")
-    docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha01")
-    docs("androidx.wear.protolayout:protolayout:1.1.0-rc01")
-    docs("androidx.wear.protolayout:protolayout-expression:1.1.0-rc01")
-    docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.1.0-rc01")
-    docs("androidx.wear.protolayout:protolayout-material:1.1.0-rc01")
-    docs("androidx.wear.protolayout:protolayout-material-core:1.1.0-rc01")
-    docs("androidx.wear.protolayout:protolayout-renderer:1.1.0-rc01")
-    docs("androidx.wear.tiles:tiles:1.3.0-rc01")
-    docs("androidx.wear.tiles:tiles-material:1.3.0-rc01")
-    docs("androidx.wear.tiles:tiles-renderer:1.3.0-rc01")
-    docs("androidx.wear.tiles:tiles-testing:1.3.0-rc01")
-    docs("androidx.wear.tiles:tiles-tooling:1.3.0-rc01")
-    docs("androidx.wear.tiles:tiles-tooling-preview:1.3.0-rc01")
-    docs("androidx.wear.watchface:watchface:1.2.1")
-    docs("androidx.wear.watchface:watchface-client:1.2.1")
-    docs("androidx.wear.watchface:watchface-client-guava:1.2.1")
-    docs("androidx.wear.watchface:watchface-complications:1.2.1")
-    docs("androidx.wear.watchface:watchface-complications-data:1.2.1")
-    docs("androidx.wear.watchface:watchface-complications-data-source:1.2.1")
-    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.1")
-    samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.1")
-    docs("androidx.wear.watchface:watchface-complications-rendering:1.2.1")
-    docs("androidx.wear.watchface:watchface-data:1.2.1")
-    docs("androidx.wear.watchface:watchface-editor:1.2.1")
-    docs("androidx.wear.watchface:watchface-editor-guava:1.2.1")
-    samples("androidx.wear.watchface:watchface-editor-samples:1.2.1")
-    docs("androidx.wear.watchface:watchface-guava:1.2.1")
-    samples("androidx.wear.watchface:watchface-samples:1.2.1")
-    docs("androidx.wear.watchface:watchface-style:1.2.1")
+    docs("androidx.wear.compose:compose-foundation:1.4.0-alpha02")
+    samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha02")
+    docs("androidx.wear.compose:compose-material:1.4.0-alpha02")
+    docs("androidx.wear.compose:compose-material-core:1.4.0-alpha02")
+    samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha02")
+    docs("androidx.wear.compose:compose-material3:1.0.0-alpha17")
+    samples("androidx.wear.compose:compose-material3-samples:1.4.0-alpha02")
+    docs("androidx.wear.compose:compose-navigation:1.4.0-alpha02")
+    samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha02")
+    docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha02")
+    docs("androidx.wear.protolayout:protolayout:1.1.0")
+    docs("androidx.wear.protolayout:protolayout-expression:1.1.0")
+    docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.1.0")
+    docs("androidx.wear.protolayout:protolayout-material:1.1.0")
+    docs("androidx.wear.protolayout:protolayout-material-core:1.1.0")
+    docs("androidx.wear.protolayout:protolayout-renderer:1.1.0")
+    docs("androidx.wear.tiles:tiles:1.3.0")
+    docs("androidx.wear.tiles:tiles-material:1.3.0")
+    docs("androidx.wear.tiles:tiles-renderer:1.3.0")
+    docs("androidx.wear.tiles:tiles-testing:1.3.0")
+    docs("androidx.wear.tiles:tiles-tooling:1.3.0")
+    docs("androidx.wear.tiles:tiles-tooling-preview:1.3.0")
+    docs("androidx.wear.watchface:watchface:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-client:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-client-guava:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-complications:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-complications-data:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-complications-data-source:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.3.0-alpha01")
+    samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-complications-rendering:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-data:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-editor:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-editor-guava:1.3.0-alpha01")
+    samples("androidx.wear.watchface:watchface-editor-samples:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-guava:1.3.0-alpha01")
+    samples("androidx.wear.watchface:watchface-samples:1.3.0-alpha01")
+    docs("androidx.wear.watchface:watchface-style:1.3.0-alpha01")
     docs("androidx.wear:wear:1.4.0-alpha01")
     stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
     docs("androidx.wear:wear-input:1.2.0-alpha02")
@@ -445,17 +448,17 @@
     docs("androidx.wear:wear-remote-interactions:1.1.0-alpha02")
     samples("androidx.wear:wear-remote-interactions-samples:1.1.0-alpha02")
     docs("androidx.wear:wear-tooling-preview:1.0.0")
-    docs("androidx.webkit:webkit:1.10.0")
+    docs("androidx.webkit:webkit:1.11.0-alpha01")
     docs("androidx.window.extensions.core:core:1.0.0")
-    docs("androidx.window:window:1.3.0-alpha01")
+    docs("androidx.window:window:1.3.0-alpha02")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
-    docs("androidx.window:window-core:1.3.0-alpha01")
+    kmpDocs("androidx.window:window-core:1.3.0-alpha02")
     stubs("androidx.window:window-extensions:1.0.0-alpha01")
-    docs("androidx.window:window-java:1.3.0-alpha01")
-    docs("androidx.window:window-rxjava2:1.3.0-alpha01")
-    docs("androidx.window:window-rxjava3:1.3.0-alpha01")
-    samples("androidx.window:window-samples:1.3.0-alpha01")
-    docs("androidx.window:window-testing:1.3.0-alpha01")
+    docs("androidx.window:window-java:1.3.0-alpha02")
+    docs("androidx.window:window-rxjava2:1.3.0-alpha02")
+    docs("androidx.window:window-rxjava3:1.3.0-alpha02")
+    samples("androidx.window:window-samples:1.3.0-alpha02")
+    docs("androidx.window:window-testing:1.3.0-alpha02")
     docs("androidx.work:work-gcm:2.10.0-alpha01")
     docs("androidx.work:work-multiprocess:2.10.0-alpha01")
     docs("androidx.work:work-runtime:2.10.0-alpha01")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 8dfb4b7..ef4b029 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -58,7 +58,7 @@
     stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
     docs(project(":camera:camera-lifecycle"))
     docs(project(":camera:camera-mlkit-vision"))
-    docsWithoutApiSince(project(":camera:camera-testing"))
+    docs(project(":camera:camera-testing"))
     docs(project(":camera:camera-video"))
     docs(project(":camera:camera-view"))
     docs(project(":camera:camera-viewfinder"))
@@ -69,6 +69,7 @@
     docs(project(":car:app:app-automotive"))
     docs(project(":car:app:app-projected"))
     docs(project(":car:app:app-testing"))
+    samples(project(":car:app:app-samples:navigation-common"))
     docs(project(":cardview:cardview"))
     kmpDocs(project(":collection:collection"))
     docs(project(":collection:collection-ktx"))
@@ -82,12 +83,14 @@
     kmpDocs(project(":compose:foundation:foundation-layout"))
     samples(project(":compose:foundation:foundation-layout:foundation-layout-samples"))
     samples(project(":compose:foundation:foundation:foundation-samples"))
+    kmpDocs(project(":compose:material3:adaptive:adaptive"))
+    kmpDocs(project(":compose:material3:adaptive:adaptive-layout"))
+    kmpDocs(project(":compose:material3:adaptive:adaptive-navigation"))
+    samples(project(":compose:material3:adaptive:adaptive-samples"))
     kmpDocs(project(":compose:material3:material3"))
-    kmpDocs(project(":compose:material3:material3-adaptive"))
-    samples(project(":compose:material3:material3-adaptive:material3-adaptive-samples"))
     kmpDocs(project(":compose:material3:material3-adaptive-navigation-suite"))
     samples(project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples"))
-    docs(project(":compose:material3:material3-common"))
+    kmpDocs(project(":compose:material3:material3-common"))
     samples(project(":compose:material3:material3:material3-samples"))
     kmpDocs(project(":compose:material3:material3-window-size-class"))
     samples(project(":compose:material3:material3-window-size-class:material3-window-size-class-samples"))
@@ -208,7 +211,7 @@
     docs(project(":graphics:graphics-core"))
     samples(project(":graphics:graphics-core:graphics-core-samples"))
     docs(project(":graphics:graphics-path"))
-    docs(project(":graphics:graphics-shapes"))
+    kmpDocs(project(":graphics:graphics-shapes"))
     docs(project(":gridlayout:gridlayout"))
     docs(project(":health:connect:connect-client"))
     samples(project(":health:connect:connect-client-samples"))
@@ -223,13 +226,12 @@
     docs(project(":input:input-motionprediction"))
     docs(project(":interpolator:interpolator"))
     docs(project(":javascriptengine:javascriptengine"))
-    docs(project(":metrics:metrics-performance"))
     docs(project(":leanback:leanback"))
     docs(project(":leanback:leanback-grid"))
     docs(project(":leanback:leanback-paging"))
     docs(project(":leanback:leanback-preference"))
     docs(project(":leanback:leanback-tab"))
-    docs(project(":lifecycle:lifecycle-common"))
+    kmpDocs(project(":lifecycle:lifecycle-common"))
     docs(project(":lifecycle:lifecycle-common-java8"))
     docs(project(":lifecycle:lifecycle-extensions"))
     docs(project(":lifecycle:lifecycle-livedata"))
@@ -239,13 +241,13 @@
     docs(project(":lifecycle:lifecycle-process"))
     docs(project(":lifecycle:lifecycle-reactivestreams"))
     docs(project(":lifecycle:lifecycle-reactivestreams-ktx"))
-    docs(project(":lifecycle:lifecycle-runtime"))
+    kmpDocs(project(":lifecycle:lifecycle-runtime"))
     docs(project(":lifecycle:lifecycle-runtime-compose"))
     samples(project(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples"))
-    docs(project(":lifecycle:lifecycle-runtime-ktx"))
+    kmpDocs(project(":lifecycle:lifecycle-runtime-ktx"))
     docs(project(":lifecycle:lifecycle-runtime-testing"))
     docs(project(":lifecycle:lifecycle-service"))
-    docs(project(":lifecycle:lifecycle-viewmodel"))
+    kmpDocs(project(":lifecycle:lifecycle-viewmodel"))
     docs(project(":lifecycle:lifecycle-viewmodel-compose"))
     samples(project(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples"))
     docs(project(":lifecycle:lifecycle-viewmodel-ktx"))
@@ -253,11 +255,6 @@
     docs(project(":loader:loader"))
     docs(project(":loader:loader-ktx"))
     // localbroadcastmanager is deprecated
-    docs(project(":media2:media2-common"))
-    docs(project(":media2:media2-exoplayer"))
-    docs(project(":media2:media2-player"))
-    docs(project(":media2:media2-session"))
-    docs(project(":media2:media2-widget"))
     docs(project(":media:media"))
     // androidx.media3 is not hosted in androidx
     docs(project(":mediarouter:mediarouter"))
@@ -276,9 +273,9 @@
     docs(project(":navigation:navigation-testing"))
     docs(project(":navigation:navigation-ui"))
     docs(project(":navigation:navigation-ui-ktx"))
-    docs(project(":paging:paging-common"))
+    kmpDocs(project(":paging:paging-common"))
     docs(project(":paging:paging-common-ktx"))
-    docs(project(":paging:paging-compose"))
+    kmpDocs(project(":paging:paging-compose"))
     samples(project(":paging:paging-compose:paging-compose-samples"))
     docs(project(":paging:paging-guava"))
     docs(project(":paging:paging-runtime"))
@@ -287,7 +284,7 @@
     docs(project(":paging:paging-rxjava2-ktx"))
     docs(project(":paging:paging-rxjava3"))
     samples(project(":paging:paging-samples"))
-    docs(project(":paging:paging-testing"))
+    kmpDocs(project(":paging:paging-testing"))
     docs(project(":palette:palette"))
     docs(project(":palette:palette-ktx"))
     docs(project(":pdf:pdf-viewer"))
@@ -313,7 +310,7 @@
     docs(project(":recyclerview:recyclerview-selection"))
     docs(project(":remotecallback:remotecallback"))
     docs(project(":resourceinspection:resourceinspection-annotation"))
-    docs(project(":room:room-common"))
+    kmpDocs(project(":room:room-common"))
     docs(project(":room:room-guava"))
     docs(project(":room:room-ktx"))
     docs(project(":room:room-migration"))
@@ -321,7 +318,7 @@
     docs(project(":room:room-paging-guava"))
     docs(project(":room:room-paging-rxjava2"))
     docs(project(":room:room-paging-rxjava3"))
-    docs(project(":room:room-runtime"))
+    kmpDocs(project(":room:room-runtime"))
     docs(project(":room:room-rxjava2"))
     docs(project(":room:room-rxjava3"))
     docs(project(":room:room-testing"))
@@ -334,6 +331,7 @@
     docs(project(":security:security-crypto"))
     docs(project(":security:security-crypto-ktx"))
     docs(project(":security:security-identity-credential"))
+    docs(project(":security:security-mls"))
     docs(project(":sharetarget:sharetarget"))
     docs(project(":slice:slice-builders"))
     docs(project(":slice:slice-builders-ktx"))
@@ -341,9 +339,9 @@
     docs(project(":slice:slice-remotecallback"))
     docs(project(":slice:slice-view"))
     docs(project(":slidingpanelayout:slidingpanelayout"))
-    docs(project(":sqlite:sqlite"))
+    kmpDocs(project(":sqlite:sqlite"))
     kmpDocs(project(":sqlite:sqlite-bundled"))
-    docs(project(":sqlite:sqlite-framework"))
+    kmpDocs(project(":sqlite:sqlite-framework"))
     docs(project(":sqlite:sqlite-ktx"))
     docs(project(":startup:startup-runtime"))
     docs(project(":swiperefreshlayout:swiperefreshlayout"))
@@ -396,10 +394,12 @@
     docs(project(":wear:watchface:watchface-complications-data"))
     docs(project(":wear:watchface:watchface-complications-data-source"))
     docs(project(":wear:watchface:watchface-complications-data-source-ktx"))
+    samples(project(":wear:watchface:watchface-complications-permission-dialogs-sample"))
     docs(project(":wear:watchface:watchface-complications-rendering"))
     docs(project(":wear:watchface:watchface-data"))
     docs(project(":wear:watchface:watchface-editor"))
     docs(project(":wear:watchface:watchface-editor-guava"))
+    samples(project(":wear:watchface:watchface-editor-samples"))
     docs(project(":wear:watchface:watchface-guava"))
     samples(project(":wear:watchface:watchface-samples"))
     docs(project(":wear:watchface:watchface-style"))
@@ -417,10 +417,9 @@
     docs(project(":webkit:webkit"))
     docs(project(":window:window"))
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release.aar"]))
-    stubs(project(":window:extensions:core:core"))
-    stubs(project(":window:extensions:extensions"))
-    stubs(project(":window:sidecar:sidecar"))
-    docs(project(":window:window-core"))
+    docs(project(":window:extensions:core:core"))
+    docs(project(":window:extensions:extensions"))
+    kmpDocs(project(":window:window-core"))
     docs(project(":window:window-java"))
     docs(project(":window:window-rxjava2"))
     docs(project(":window:window-rxjava3"))
diff --git a/emoji2/emoji2-emojipicker/samples/build.gradle b/emoji2/emoji2-emojipicker/samples/build.gradle
index c438f60..5ef572c 100644
--- a/emoji2/emoji2-emojipicker/samples/build.gradle
+++ b/emoji2/emoji2-emojipicker/samples/build.gradle
@@ -27,10 +27,20 @@
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4")
     implementation("androidx.appcompat:appcompat:1.6.1")
     implementation(project(":emoji2:emoji2-emojipicker"))
+    implementation("androidx.compose.ui:ui-util:1.6.0")
+    implementation("androidx.compose.ui:ui:1.6.0")
+    implementation("androidx.activity:activity-compose:1.8.2")
+    implementation(project(":compose:foundation:foundation"))
 }
 android {
     defaultConfig {
         minSdkVersion 21
     }
+    buildFeatures {
+        compose = true
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion = '1.5.8'
+    }
     namespace "androidx.emoji2.emojipicker.samples"
 }
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml b/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
index 33d97f7..21e5119 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
@@ -28,5 +28,6 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".ComposeActivity" android:theme="@style/MyPinkTheme" />
     </application>
 </manifest>
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/ComposeActivity.kt b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/ComposeActivity.kt
new file mode 100644
index 0000000..bc74cb7
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/ComposeActivity.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.emoji2.emojipicker.samples
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ToggleButton
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.emoji2.emojipicker.EmojiPickerView
+import androidx.emoji2.emojipicker.EmojiViewItem
+
+class ComposeActivity : ComponentActivity() {
+    private lateinit var context: Context
+    private lateinit var mainLayout: View
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        context = this
+        setContent {
+            EmojiPicker()
+        }
+    }
+
+    @Composable
+    private fun EmojiPicker() {
+        AndroidView(
+            modifier = Modifier.fillMaxSize(),
+            factory = { context ->
+                val view = LayoutInflater.from(context).inflate(
+                    R.layout.main, /* root= */ null, /* attachToRoot= */ false)
+                val emojiPickerView = view.findViewById<EmojiPickerView>(R.id.emoji_picker)
+                emojiPickerView.setOnEmojiPickedListener(this::updateEditText)
+                view.findViewById<ToggleButton>(R.id.toggle_button)
+                    .setOnCheckedChangeListener { _, isChecked ->
+                        if (isChecked) {
+                            emojiPickerView.emojiGridColumns = 8
+                            emojiPickerView.emojiGridRows = 8.3f
+                        } else {
+                            emojiPickerView.emojiGridColumns = 9
+                            emojiPickerView.emojiGridRows = 15f
+                        }
+                    }
+                view.findViewById<Button>(R.id.button).visibility = View.GONE
+                val activityButton = view.findViewById<ToggleButton>(R.id.activity_button)
+                activityButton.isChecked = true
+                activityButton.setOnCheckedChangeListener { _, isChecked ->
+                    if (isChecked) {
+                        val intent = Intent(this, ComposeActivity::class.java)
+                        startActivity(intent)
+                    } else {
+                        val intent = Intent(this, MainActivity::class.java)
+                        startActivity(intent)
+                    }
+                }
+                view
+            },
+            update = { view -> mainLayout = view }
+        )
+    }
+
+    private fun updateEditText(emojiViewItem: EmojiViewItem) {
+        val editText = mainLayout.findViewById<EditText>(R.id.edit_text)
+        editText.append(emojiViewItem.emoji)
+    }
+}
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
index cd46f5d..532f03b 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
+++ b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
@@ -18,6 +18,7 @@
 
 import android.app.Activity
 import android.content.Context
+import android.content.Intent
 import android.os.Bundle
 import android.widget.Button
 import android.widget.EditText
@@ -53,6 +54,16 @@
                 RecentEmojiProviderAdapter(CustomRecentEmojiProvider(applicationContext))
             )
         }
+        findViewById<ToggleButton>(R.id.activity_button)
+            .setOnCheckedChangeListener { _, isChecked ->
+            if (isChecked) {
+                val intent = Intent(this, ComposeActivity::class.java)
+                startActivity(intent)
+            } else {
+                val intent = Intent(this, MainActivity::class.java)
+                startActivity(intent)
+            }
+        }
     }
 }
 
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml b/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
index f79a375..f90b99e 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
@@ -33,13 +33,19 @@
             android:id="@+id/toggle_button"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textOff="Display Larger Emojis"
-            android:textOn="Display Smaller Emojis" />
+            android:textOff="Larger Emojis"
+            android:textOn="Smaller Emojis" />
         <Button
             android:id="@+id/button"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="Freq Used As Recent" />
+        <ToggleButton
+            android:id="@+id/activity_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textOff="Compose"
+            android:textOn="Standard" />
     </LinearLayout>
 
     <androidx.emoji2.emojipicker.EmojiPickerView
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml b/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml
index 32ff475..b445700 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml
@@ -28,6 +28,8 @@
         <item name="android:textSize">12dp</item>
         <!-- The color for variant popup background -->
         <item name="colorButtonNormal">#FFC0CB</item>
+        <!-- The color for the selected item on the multi-skintone selection -->
+        <item name="android:colorControlHighlight">#FFA8C7FA</item>
     </style>
 
 </resources>
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 4381628..727f57b 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -100,6 +100,8 @@
     private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
     private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
     private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
+    private static final String INVALID_WEBP_WITH_JPEG_APP1_MARKER =
+            "invalid_webp_with_jpeg_app1_marker.webp";
     private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA =
             "webp_with_anim_without_exif.webp";
     private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp";
@@ -122,6 +124,7 @@
             R.raw.png_with_exif_byte_order_ii,
             R.raw.png_without_exif,
             R.raw.webp_with_exif,
+            R.raw.invalid_webp_with_jpeg_app1_marker,
             R.raw.webp_with_anim_without_exif,
             R.raw.webp_without_exif,
             R.raw.webp_lossless_without_exif,
@@ -139,6 +142,7 @@
             PNG_WITH_EXIF_BYTE_ORDER_II,
             PNG_WITHOUT_EXIF,
             WEBP_WITH_EXIF,
+            INVALID_WEBP_WITH_JPEG_APP1_MARKER,
             WEBP_WITHOUT_EXIF_WITH_ANIM_DATA,
             WEBP_WITHOUT_EXIF,
             WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING,
@@ -533,6 +537,15 @@
 
     @Test
     @LargeTest
+    public void testWebpWithExifApp1() throws Throwable {
+        readFromFilesWithExif(INVALID_WEBP_WITH_JPEG_APP1_MARKER,
+                R.array.invalid_webp_with_jpeg_app1_marker);
+        writeToFilesWithExif(INVALID_WEBP_WITH_JPEG_APP1_MARKER,
+                R.array.invalid_webp_with_jpeg_app1_marker);
+    }
+
+    @Test
+    @LargeTest
     public void testWebpWithoutExif() throws Throwable {
         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF);
     }
diff --git a/exifinterface/exifinterface/src/androidTest/res/raw/invalid_webp_with_jpeg_app1_marker.webp b/exifinterface/exifinterface/src/androidTest/res/raw/invalid_webp_with_jpeg_app1_marker.webp
new file mode 100644
index 0000000..58da0dc
--- /dev/null
+++ b/exifinterface/exifinterface/src/androidTest/res/raw/invalid_webp_with_jpeg_app1_marker.webp
Binary files differ
diff --git a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
index c175dbc..6e69297 100644
--- a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
+++ b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
@@ -411,6 +411,50 @@
         <item>0</item>
         <item>0</item>
     </array>
+    <array name="invalid_webp_with_jpeg_app1_marker">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>0</item>
+        <item>0</item>
+        <item />
+        <item>8</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
     <array name="heif_with_exif">
         <!--Whether thumbnail exists-->
         <item>false</item>
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index e7c8d08..97770e9 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -6219,6 +6219,16 @@
                     // TODO: Need to handle potential OutOfMemoryError
                     byte[] payload = new byte[chunkSize];
                     in.readFully(payload);
+
+                    // Skip a JPEG APP1 marker that some image libraries incorrectly include in the
+                    // Exif data in WebP images (e.g.
+                    // https://github.com/ImageMagick/ImageMagick/issues/3140)
+                    if (startsWith(payload, IDENTIFIER_EXIF_APP1)) {
+                        int adjustedChunkSize = chunkSize - IDENTIFIER_EXIF_APP1.length;
+                        payload = Arrays.copyOfRange(payload, IDENTIFIER_EXIF_APP1.length,
+                                adjustedChunkSize);
+                    }
+
                     // Save offset to EXIF data for handling thumbnail and attribute offsets.
                     mOffsetToExifData = bytesRead;
                     readExifSegment(payload, IFD_TYPE_PRIMARY);
diff --git a/external/paparazzi/paparazzi-agent/build.gradle b/external/paparazzi/paparazzi-agent/build.gradle
deleted file mode 100644
index 802d417..0000000
--- a/external/paparazzi/paparazzi-agent/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * 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
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-}
-
-dependencies {
-    api(libs.kotlinStdlib)
-    api(libs.junit)
-    implementation(libs.byteBuddy)
-    implementation(libs.byteBuddyAgent)
-    testImplementation(libs.assertj)
-}
-
-androidx {
-    name = "Paparazzi Agent - AndroidX Fork"
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
diff --git a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt b/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt
deleted file mode 100644
index b79b5d5..0000000
--- a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.agent.ByteBuddyAgent
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-class AgentTestRule : TestRule {
-  override fun apply(
-    base: Statement,
-    description: Description
-  ) = object : Statement() {
-    override fun evaluate() {
-      ByteBuddyAgent.install()
-      InterceptorRegistrar.registerMethodInterceptors()
-      // interceptors are statically retained until test process finishes, so no need to cleanup
-      base.evaluate()
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt b/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt
deleted file mode 100644
index b436835..0000000
--- a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.ByteBuddy
-import net.bytebuddy.dynamic.loading.ClassReloadingStrategy
-import net.bytebuddy.implementation.MethodDelegation
-import net.bytebuddy.matcher.ElementMatchers
-
-object InterceptorRegistrar {
-  private val byteBuddy = ByteBuddy()
-  private val methodInterceptors = mutableListOf<() -> Unit>()
-
-  fun addMethodInterceptor(
-    receiver: Class<*>,
-    methodName: String,
-    interceptor: Class<*>
-  ) = addMethodInterceptors(receiver, setOf(methodName to interceptor))
-
-  fun addMethodInterceptors(
-    receiver: Class<*>,
-    methodNamesToInterceptors: Set<Pair<String, Class<*>>>
-  ) {
-    methodInterceptors += {
-      var builder = byteBuddy
-        .redefine(receiver)
-
-      methodNamesToInterceptors.forEach {
-        builder = builder
-          .method(ElementMatchers.named(it.first))
-          .intercept(MethodDelegation.to(it.second))
-      }
-
-      builder
-        .make()
-        .load(receiver.classLoader, ClassReloadingStrategy.fromInstalledAgent())
-    }
-  }
-
-  fun registerMethodInterceptors() {
-    methodInterceptors.forEach { it.invoke() }
-  }
-
-  fun clearMethodInterceptors() {
-    methodInterceptors.clear()
-  }
-}
diff --git a/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt b/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt
deleted file mode 100644
index 1113073..0000000
--- a/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.agent.ByteBuddyAgent
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-
-class InterceptorRegistrarTest {
-  @Before
-  fun setup() {
-    InterceptorRegistrar.addMethodInterceptors(
-      Utils::class.java,
-      setOf(
-        "log1" to Interceptor1::class.java,
-        "log2" to Interceptor2::class.java
-      )
-    )
-
-    ByteBuddyAgent.install()
-    InterceptorRegistrar.registerMethodInterceptors()
-  }
-
-  @Test
-  fun test() {
-    Utils.log1()
-    Utils.log2()
-
-    assertThat(logs).containsExactly("intercept1", "intercept2")
-  }
-
-  @After
-  fun teardown() {
-    InterceptorRegistrar.clearMethodInterceptors()
-  }
-
-  object Utils {
-    fun log1() {
-      logs += "original1"
-    }
-
-    fun log2() {
-      logs += "original2"
-    }
-  }
-
-  object Interceptor1 {
-    @Suppress("unused")
-    @JvmStatic
-    fun intercept() {
-      logs += "intercept1"
-    }
-  }
-
-  object Interceptor2 {
-    @Suppress("unused")
-    @JvmStatic
-    fun intercept() {
-      logs += "intercept2"
-    }
-  }
-
-  companion object {
-    private val logs = mutableListOf<String>()
-  }
-}
diff --git a/external/paparazzi/paparazzi/build.gradle b/external/paparazzi/paparazzi/build.gradle
deleted file mode 100644
index bc018ac..0000000
--- a/external/paparazzi/paparazzi/build.gradle
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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 org.gradle.api.artifacts.transform.TransformParameters.None
-import java.util.zip.ZipInputStream
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-    id("com.google.devtools.ksp")
-    id("AndroidXComposePlugin")
-}
-
-androidx.configureAarAsJarForConfiguration("compileOnly")
-androidx.configureAarAsJarForConfiguration("testImplementation")
-
-dependencies {
-    api("androidx.annotation:annotation:1.3.0")
-    api("com.android.tools.layoutlib:layoutlib-api:27.2.2")
-    api("com.android.tools:common:27.1.2")
-    api(libs.androidToolsNinepatch)
-    api("com.android.tools:sdk-common:26.6.4")
-    api(libs.guava)
-    api(libs.junit)
-    api(libs.kotlinStdlib)
-    api(libs.kotlinCoroutinesCore)
-    api(libs.kxml2)
-    api(libs.okio)
-    api(libs.paparazziNativeJvm)
-    constraints {
-        implementation(libs.kotlinReflect) {
-            because("sdk-common depends on an old kotlin-reflect")
-        }
-    }
-
-    implementation(project(":external:paparazzi:paparazzi-agent"))
-    implementation(libs.jcodec)
-    implementation(libs.jcodecJavaSe)
-    implementation(libs.moshi)
-    implementation(libs.moshiAdapters)
-
-    compileOnlyAarAsJar("androidx.compose.runtime:runtime:1.2.1")
-    compileOnlyAarAsJar("androidx.compose.ui:ui:1.2.1")
-    compileOnly(project(":lifecycle:lifecycle-common"))
-    compileOnlyAarAsJar(project(":lifecycle:lifecycle-runtime"))
-    compileOnlyAarAsJar("androidx.savedstate:savedstate:1.2.0")
-
-    ksp(libs.moshiCodeGen)
-
-    testImplementation(libs.assertj)
-    testImplementationAarAsJar("androidx.compose.runtime:runtime:1.2.1")
-}
-
-androidx {
-    name = "Paparazzi - AndroidX Fork"
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
\ No newline at end of file
diff --git a/external/paparazzi/paparazzi/lint-baseline.xml b/external/paparazzi/paparazzi/lint-baseline.xml
deleted file mode 100644
index a53cf10..0000000
--- a/external/paparazzi/paparazzi/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="      Thread.sleep(100)"
-        errorLine2="             ~~~~~">
-        <location
-            file="src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt"/>
-    </issue>
-
-</issues>
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt
deleted file mode 100644
index 7345548..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2014 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 app.cash.paparazzi
-
-import com.android.ide.common.rendering.api.HardwareConfig
-import com.android.ide.common.resources.configuration.CountryCodeQualifier
-import com.android.ide.common.resources.configuration.DensityQualifier
-import com.android.ide.common.resources.configuration.FolderConfiguration
-import com.android.ide.common.resources.configuration.KeyboardStateQualifier
-import com.android.ide.common.resources.configuration.LayoutDirectionQualifier
-import com.android.ide.common.resources.configuration.LocaleQualifier
-import com.android.ide.common.resources.configuration.NavigationMethodQualifier
-import com.android.ide.common.resources.configuration.NetworkCodeQualifier
-import com.android.ide.common.resources.configuration.NightModeQualifier
-import com.android.ide.common.resources.configuration.ScreenDimensionQualifier
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier
-import com.android.ide.common.resources.configuration.ScreenRatioQualifier
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier
-import com.android.ide.common.resources.configuration.TextInputMethodQualifier
-import com.android.ide.common.resources.configuration.TouchScreenQualifier
-import com.android.ide.common.resources.configuration.UiModeQualifier
-import com.android.ide.common.resources.configuration.VersionQualifier
-import com.android.resources.Density
-import com.android.resources.Keyboard
-import com.android.resources.KeyboardState
-import com.android.resources.LayoutDirection
-import com.android.resources.Navigation
-import com.android.resources.NightMode
-import com.android.resources.NightMode.NOTNIGHT
-import com.android.resources.ScreenOrientation
-import com.android.resources.ScreenRatio
-import com.android.resources.ScreenSize
-import com.android.resources.TouchScreen
-import com.android.resources.UiMode
-import com.google.android.collect.Maps
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
-import java.util.Properties
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-import org.xmlpull.v1.XmlPullParserFactory
-
-/**
- * Provides [FolderConfiguration] and [HardwareConfig] for various devices. Also provides utility
- * methods to parse `build.prop` and `attrs.xml` to generate the appropriate maps.
- *
- * Defaults are for a Nexus 4 device.
- */
-data class DeviceConfig(
-  val screenHeight: Int = 1280,
-  val screenWidth: Int = 768,
-  val xdpi: Int = 320,
-  val ydpi: Int = 320,
-  val orientation: ScreenOrientation = ScreenOrientation.PORTRAIT,
-  val nightMode: NightMode = NOTNIGHT,
-  val density: Density = Density.XHIGH,
-  val fontScale: Float = 1f,
-  val layoutDirection: LayoutDirection = LayoutDirection.LTR,
-  val locale: String = "en",
-  val ratio: ScreenRatio = ScreenRatio.NOTLONG,
-  val size: ScreenSize = ScreenSize.NORMAL,
-  val keyboard: Keyboard = Keyboard.NOKEY,
-  val touchScreen: TouchScreen = TouchScreen.FINGER,
-  val keyboardState: KeyboardState = KeyboardState.SOFT,
-  val softButtons: Boolean = true,
-  val navigation: Navigation = Navigation.NONAV,
-  val released: String = "November 13, 2012"
-) {
-  val folderConfiguration: FolderConfiguration
-    get() = FolderConfiguration.createDefault()
-      .apply {
-        densityQualifier = DensityQualifier(density)
-        navigationMethodQualifier = NavigationMethodQualifier(navigation)
-        screenDimensionQualifier = when {
-          screenWidth > screenHeight -> ScreenDimensionQualifier(screenWidth, screenHeight)
-          else -> ScreenDimensionQualifier(screenHeight, screenWidth)
-        }
-        screenRatioQualifier = ScreenRatioQualifier(ratio)
-        screenSizeQualifier = ScreenSizeQualifier(size)
-        textInputMethodQualifier = TextInputMethodQualifier(keyboard)
-        touchTypeQualifier = TouchScreenQualifier(touchScreen)
-        keyboardStateQualifier = KeyboardStateQualifier(keyboardState)
-        screenOrientationQualifier = ScreenOrientationQualifier(orientation)
-
-        updateScreenWidthAndHeight()
-        uiModeQualifier = UiModeQualifier(UiMode.NORMAL)
-        nightModeQualifier = NightModeQualifier(nightMode)
-        countryCodeQualifier = CountryCodeQualifier()
-        layoutDirectionQualifier = LayoutDirectionQualifier(layoutDirection)
-        networkCodeQualifier = NetworkCodeQualifier()
-        localeQualifier = LocaleQualifier.getQualifier(locale)
-        versionQualifier = VersionQualifier()
-      }
-
-  val hardwareConfig: HardwareConfig
-    get() = HardwareConfig(
-      screenWidth, screenHeight, density, xdpi.toFloat(), ydpi.toFloat(), size,
-      orientation, null, softButtons
-    )
-
-  /**
-   * Device specs per:
-   * https://android.googlesource.com/platform/tools/base/+/mirror-goog-studio-master-dev/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
-   *
-   * Release dates obtained from Wikipedia.
-   */
-
-  companion object {
-    @JvmField
-    val NEXUS_4 = DeviceConfig()
-
-    @JvmField
-    val NEXUS_5 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 445,
-      ydpi = 445,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.XXHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 31, 2013"
-    )
-
-    @JvmField
-    val NEXUS_7 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1200,
-      xdpi = 323,
-      ydpi = 323,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.LARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "July 26, 2013"
-    )
-
-    @JvmField
-    val NEXUS_10 = DeviceConfig(
-      screenHeight = 1600,
-      screenWidth = 2560,
-      xdpi = 300,
-      ydpi = 300,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.XLARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "November 13, 2012"
-    )
-
-    @JvmField
-    val NEXUS_5_LAND = DeviceConfig(
-      screenHeight = 1080,
-      screenWidth = 1920,
-      xdpi = 445,
-      ydpi = 445,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XXHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 31, 2013"
-    )
-
-    @JvmField
-    val NEXUS_7_2012 = DeviceConfig(
-      screenHeight = 1280,
-      screenWidth = 800,
-      xdpi = 195,
-      ydpi = 200,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.TV,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.LARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "July 13, 2012"
-    )
-
-    @JvmField
-    val PIXEL_C = DeviceConfig(
-      screenHeight = 1800,
-      screenWidth = 2560,
-      xdpi = 308,
-      ydpi = 308,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.XLARGE,
-      keyboard = Keyboard.QWERTY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "December 8, 2015"
-    )
-
-    @JvmField
-    val PIXEL = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 440,
-      ydpi = 440,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 20, 2016"
-    )
-
-    @JvmField
-    val PIXEL_XL = DeviceConfig(
-      screenHeight = 2560,
-      screenWidth = 1440,
-      xdpi = 534,
-      ydpi = 534,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 20, 2016"
-    )
-
-    @JvmField
-    val PIXEL_2 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 443,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 19, 2017"
-    )
-
-    @JvmField
-    val PIXEL_2_XL = DeviceConfig(
-      screenHeight = 2880,
-      screenWidth = 1440,
-      xdpi = 537,
-      ydpi = 537,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 19, 2017"
-    )
-
-    @JvmField
-    val PIXEL_3 = DeviceConfig(
-      screenHeight = 2160,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 442,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 18, 2018"
-    )
-
-    @JvmField
-    val PIXEL_3_XL = DeviceConfig(
-      screenHeight = 2960,
-      screenWidth = 1440,
-      xdpi = 522,
-      ydpi = 522,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 18, 2018"
-    )
-
-    @JvmField
-    val PIXEL_3A = DeviceConfig(
-      screenHeight = 2220,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "May 7, 2019"
-    )
-
-    @JvmField
-    val PIXEL_3A_XL = DeviceConfig(
-      screenHeight = 2160,
-      screenWidth = 1080,
-      xdpi = 397,
-      ydpi = 400,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_400,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "May 7, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4 = DeviceConfig(
-      screenHeight = 2280,
-      screenWidth = 1080,
-      xdpi = 444,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 24, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4_XL = DeviceConfig(
-      screenHeight = 3040,
-      screenWidth = 1440,
-      xdpi = 537,
-      ydpi = 537,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 24, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4A = DeviceConfig(
-      screenHeight = 2340,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "August 20, 2020"
-    )
-
-    @JvmField
-    val PIXEL_5 = DeviceConfig(
-      screenHeight = 2340,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 15, 2020"
-    )
-
-    @JvmField
-    val PIXEL_6 = DeviceConfig(
-      screenHeight = 2400,
-      screenWidth = 1080,
-      xdpi = 406,
-      ydpi = 411,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 28, 2021"
-    )
-
-    @JvmField
-    val PIXEL_6_PRO = DeviceConfig(
-      screenHeight = 3120,
-      screenWidth = 1440,
-      xdpi = 512,
-      ydpi = 512,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 28, 2021"
-    )
-
-    private const val TAG_ATTR = "attr"
-    private const val TAG_ENUM = "enum"
-    private const val TAG_FLAG = "flag"
-    private const val ATTR_NAME = "name"
-    private const val ATTR_VALUE = "value"
-
-    @Throws(IOException::class)
-    fun loadProperties(path: File): Map<String, String> {
-      val p = Properties()
-      val map = Maps.newHashMap<String, String>()
-      p.load(FileInputStream(path))
-      for (key in p.stringPropertyNames()) {
-        map[key] = p.getProperty(key)
-      }
-      return map
-    }
-
-    @Throws(IOException::class, XmlPullParserException::class)
-    fun getEnumMap(path: File): Map<String, Map<String, Int>> {
-      val map = mutableMapOf<String, MutableMap<String, Int>>()
-
-      val xmlPullParser = XmlPullParserFactory.newInstance()
-        .newPullParser()
-      xmlPullParser.setInput(FileInputStream(path), null)
-      var eventType = xmlPullParser.eventType
-      var attr: String? = null
-      while (eventType != XmlPullParser.END_DOCUMENT) {
-        if (eventType == XmlPullParser.START_TAG) {
-          if (TAG_ATTR == xmlPullParser.name) {
-            attr = xmlPullParser.getAttributeValue(null, ATTR_NAME)
-          } else if (TAG_ENUM == xmlPullParser.name || TAG_FLAG == xmlPullParser.name) {
-            val name = xmlPullParser.getAttributeValue(null, ATTR_NAME)
-            val value = xmlPullParser.getAttributeValue(null, ATTR_VALUE)
-            // Integer.decode cannot handle "ffffffff", see JDK issue 6624867
-            val i = (java.lang.Long.decode(value) as Long).toInt()
-            require(attr != null)
-            var attributeMap: MutableMap<String, Int>? = map[attr]
-            if (attributeMap == null) {
-              attributeMap = Maps.newHashMap()
-              map[attr] = attributeMap
-            }
-            attributeMap!![name] = i
-          }
-        } else if (eventType == XmlPullParser.END_TAG) {
-          if (TAG_ATTR == xmlPullParser.name) {
-            attr = null
-          }
-        }
-        eventType = xmlPullParser.next()
-      }
-
-      return map
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt
deleted file mode 100644
index 8cfae19..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import java.io.File
-import java.io.FileNotFoundException
-import java.nio.file.Path
-import java.nio.file.Paths
-import java.util.Locale
-import kotlin.io.path.exists
-
-data class Environment(
-  val platformDir: String,
-  val appTestDir: String,
-  val resDir: String,
-  val assetsDir: String,
-  val compileSdkVersion: Int,
-  val resourcePackageNames: List<String>
-) {
-  init {
-    val platformDirPath = Path.of(platformDir)
-    if (!platformDirPath.exists()) {
-      val elements = platformDirPath.nameCount
-      val platform = platformDirPath.subpath(elements - 1, elements)
-      val platformVersion = platform.toString().split("-").last()
-      throw FileNotFoundException(
-        "Missing platform version $platformVersion. " +
-            "Install with sdkmanager --install \"platforms;$platform\""
-      )
-    }
-  }
-}
-
-@Suppress("unused")
-fun androidHome() = System.getenv("ANDROID_SDK_ROOT")
-  ?: System.getenv("ANDROID_HOME")
-  ?: androidSdkPath()
-
-fun detectEnvironment(): Environment {
-  checkInstalledJvm()
-
-  val resourcesFile = File(System.getProperty("paparazzi.test.resources"))
-  val configLines = resourcesFile.readLines()
-
-  val appTestDir = Paths.get(System.getProperty("user.dir"))
-  val androidHome = Paths.get(androidHome())
-  return Environment(
-    platformDir = androidHome.resolve(configLines[3]).toString(),
-    appTestDir = appTestDir.toString(),
-    resDir = appTestDir.resolve(configLines[1]).toString(),
-    assetsDir = appTestDir.resolve(configLines[4]).toString(),
-    compileSdkVersion = configLines[2].toInt(),
-    resourcePackageNames = configLines[5].split(",")
-  )
-}
-
-private fun androidSdkPath(): String {
-  val osName = System.getProperty("os.name").lowercase(Locale.US)
-  val sdkPathDir = if (osName.startsWith("windows")) {
-    "\\AppData\\Local\\Android\\Sdk"
-  } else if (osName.startsWith("mac")) {
-    "/Library/Android/sdk"
-  } else {
-    "/Android/Sdk"
-  }
-  val homeDir = System.getProperty("user.home")
-  return homeDir + sdkPathDir
-}
-
-private fun checkInstalledJvm() {
-  val feature = try {
-    // Runtime#version() only available as of Java 9.
-    val version = Runtime::class.java.getMethod("version").invoke(null)
-    // Runtime.Version#feature() only available as of Java 10.
-    version.javaClass.getMethod("feature").invoke(version) as Int
-  } catch (e: NoSuchMethodException) {
-    -1
-  }
-
-  if (feature < 11) {
-    throw IllegalStateException(
-      "Unsupported JRE detected! Please install and run Paparazzi test suites on JDK 11+."
-    )
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt
deleted file mode 100644
index 62c0b62..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package app.cash.paparazzi
-
-object Flags {
-  const val DEBUG_LINKED_OBJECTS = "app.cash.paparazzi.debug.linked.objects"
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt
deleted file mode 100644
index c43f4fb..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import app.cash.paparazzi.SnapshotHandler.FrameHandler
-import app.cash.paparazzi.internal.PaparazziJson
-import com.google.common.base.CharMatcher
-import java.awt.image.BufferedImage
-import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.UUID
-import javax.imageio.ImageIO
-import okio.BufferedSink
-import okio.HashingSink
-import okio.blackholeSink
-import okio.buffer
-import okio.sink
-import okio.source
-import org.jcodec.api.awt.AWTSequenceEncoder
-
-/**
- * Creates an HTML report that avoids writing files that have already been written.
- *
- * Images and videos are named by hashes of their contents. Paparazzi won't write two images or videos with the same
- * contents. Note that the images/ directory includes the individual frames of each video.
- *
- * Runs are named by their date.
- *
- * ```
- * images
- *   088c60580f06efa95c37fd8e754074729ee74a06.png
- *   93f9a81cb594280f4b3898d90dfad8c8ea969b01.png
- *   22d37abd0841ba2a8d0bd635954baf7cbfaa269b.png
- *   a4769e43cc5901ef28c0d46c46a44ea6429cbccc.png
- * videos
- *   d1cddc5da2224053f2af51f4e69a76de4e61fc41.mov
- * runs
- *   20190626002322_b9854e.js
- *   20190626002345_b1e882.js
- * index.html
- * index.js
- * paparazzi.js
- * ```
- */
-class HtmlReportWriter @JvmOverloads constructor(
-  private val runName: String = defaultRunName(),
-  private val rootDirectory: File = File("build/reports/paparazzi"),
-  snapshotRootDirectory: File = File("src/test/snapshots")
-) : SnapshotHandler {
-  private val runsDirectory: File = File(rootDirectory, "runs")
-  private val imagesDirectory: File = File(rootDirectory, "images")
-  private val videosDirectory: File = File(rootDirectory, "videos")
-
-  private val goldenImagesDirectory = File(snapshotRootDirectory, "images")
-  private val goldenVideosDirectory = File(snapshotRootDirectory, "videos")
-
-  private val shots = mutableListOf<Snapshot>()
-
-  private val isRecording: Boolean =
-    System.getProperty("paparazzi.test.record")?.toBoolean() == true
-
-  init {
-    runsDirectory.mkdirs()
-    imagesDirectory.mkdirs()
-    videosDirectory.mkdirs()
-    writeStaticFiles()
-    writeRunJs()
-    writeIndexJs()
-  }
-
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler {
-    return object : FrameHandler {
-      val hashes = mutableListOf<String>()
-
-      override fun handle(image: BufferedImage) {
-        hashes += writeImage(image)
-      }
-
-      override fun close() {
-        if (hashes.isEmpty()) return
-
-        val shot = if (hashes.size == 1) {
-          val original = File(imagesDirectory, "${hashes[0]}.png")
-          if (isRecording) {
-            val goldenFile = File(goldenImagesDirectory, snapshot.toFileName("_", "png"))
-            original.copyTo(goldenFile, overwrite = true)
-          }
-          snapshot.copy(file = original.toJsonPath())
-        } else {
-          val hash = writeVideo(hashes, fps)
-
-          if (isRecording) {
-            for ((index, frameHash) in hashes.withIndex()) {
-              val originalFrame = File(imagesDirectory, "$frameHash.png")
-              val frameSnapshot = snapshot.copy(name = "${snapshot.name} $index")
-              val goldenFile = File(goldenImagesDirectory, frameSnapshot.toFileName("_", "png"))
-              if (!goldenFile.exists()) {
-                originalFrame.copyTo(goldenFile)
-              }
-            }
-          }
-          val original = File(videosDirectory, "$hash.mov")
-          if (isRecording) {
-            val goldenFile = File(goldenVideosDirectory, snapshot.toFileName("_", "mov"))
-            if (!goldenFile.exists()) {
-              original.copyTo(goldenFile)
-            }
-          }
-          snapshot.copy(file = original.toJsonPath())
-        }
-
-        shots += shot
-      }
-    }
-  }
-
-  /** Returns the hash of the image. */
-  private fun writeImage(image: BufferedImage): String {
-    val hash = hash(image)
-    val file = File(imagesDirectory, "$hash.png")
-    if (!file.exists()) {
-      file.writeAtomically(image)
-    }
-    return hash
-  }
-
-  /** Returns a SHA-1 hash of the pixels of [image]. */
-  private fun hash(image: BufferedImage): String {
-    val hashingSink = HashingSink.sha1(blackholeSink())
-    hashingSink.buffer().use { sink ->
-      for (y in 0 until image.height) {
-        for (x in 0 until image.width) {
-          sink.writeInt(image.getRGB(x, y))
-        }
-      }
-    }
-    return hashingSink.hash.hex()
-  }
-
-  private fun writeVideo(
-    frameHashes: List<String>,
-    fps: Int
-  ): String {
-    val hash = hash(frameHashes)
-    val file = File(videosDirectory, "$hash.mov")
-    if (!file.exists()) {
-      val tmpFile = File(videosDirectory, "$hash.mov.tmp")
-      val encoder = AWTSequenceEncoder.createSequenceEncoder(tmpFile, fps)
-      for (frameHash in frameHashes) {
-        val frame = ImageIO.read(File(imagesDirectory, "$frameHash.png"))
-        encoder.encodeImage(frame)
-      }
-      encoder.finish()
-      tmpFile.renameTo(file)
-    }
-    return hash
-  }
-
-  /** Returns a SHA-1 hash of [lines]. */
-  private fun hash(lines: List<String>): String {
-    val hashingSink = HashingSink.sha1(blackholeSink())
-    hashingSink.buffer().use { sink ->
-      for (hash in lines) {
-        sink.writeUtf8(hash)
-        sink.writeUtf8("\n")
-      }
-    }
-    return hashingSink.hash.hex()
-  }
-
-  /** Release all resources and block until everything has been written to the file system. */
-  override fun close() {
-    writeRunJs()
-  }
-
-  /**
-   * Emits the all runs index, which reads like JSON with an executable header.
-   *
-   * ```
-   * window.all_runs = [
-   *   "20190319153912aaab",
-   *   "20190319153917bcfe"
-   * ];
-   * ```
-   */
-  private fun writeIndexJs() {
-    val runNames = mutableListOf<String>()
-    val runs = runsDirectory.list().sorted()
-    for (run in runs) {
-      if (run.endsWith(".js")) {
-        runNames += run.substring(0, run.length - 3)
-      }
-    }
-
-    File(rootDirectory, "index.js").writeAtomically {
-      writeUtf8("window.all_runs = ")
-      PaparazziJson.listOfStringsAdapter.toJson(this, runNames)
-      writeUtf8(";")
-    }
-  }
-
-  /**
-   * Emits a run index, which reads like JSON with an executable header.
-   *
-   * ```
-   * window.runs["20190319153912aaab"] = [
-   *   {
-   *     "name": "loading",
-   *     "testName": "app.cash.CelebrityTest#testSettings",
-   *     "timestamp": "2019-03-20T10:27:43Z",
-   *     "tags": ["redesign"],
-   *     "file": "loading.png"
-   *   },
-   *   {
-   *     "name": "error",
-   *     "testName": "app.cash.CelebrityTest#testSettings",
-   *     "timestamp": "2019-03-20T10:27:43Z",
-   *     "tags": ["redesign"],
-   *     "file": "error.png"
-   *   }
-   * ];
-   * ```
-   */
-  private fun writeRunJs() {
-    val runJs = File(runsDirectory, "${runName.sanitizeForFilename()}.js")
-    runJs.writeAtomically {
-      writeUtf8("window.runs[\"$runName\"] = ")
-      PaparazziJson.listOfShotsAdapter.toJson(this, shots)
-      writeUtf8(";")
-    }
-  }
-
-  private fun writeStaticFiles() {
-    for (staticFile in listOf("index.html", "paparazzi.js")) {
-      File(rootDirectory, staticFile).writeAtomically {
-        writeAll(HtmlReportWriter::class.java.classLoader.getResourceAsStream(staticFile).source())
-      }
-    }
-  }
-
-  private fun File.writeAtomically(bufferedImage: BufferedImage) {
-    val tmpFile = File(parentFile, "$name.tmp")
-    ImageIO.write(bufferedImage, "PNG", tmpFile)
-    delete()
-    tmpFile.renameTo(this)
-  }
-
-  private fun File.writeAtomically(writerAction: BufferedSink.() -> Unit) {
-    val tmpFile = File(parentFile, "$name.tmp")
-    tmpFile.sink()
-      .buffer()
-      .use { sink ->
-        sink.writerAction()
-      }
-    delete()
-    tmpFile.renameTo(this)
-  }
-
-  private fun File.toJsonPath(): String = relativeTo(rootDirectory).invariantSeparatorsPath
-}
-
-internal fun defaultRunName(): String {
-  val now = Date()
-  val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(now)
-  val token = UUID.randomUUID().toString().substring(0, 6)
-  return "${timestamp}_$token"
-}
-
-internal val filenameSafeChars = CharMatcher.inRange('a', 'z')
-  .or(CharMatcher.inRange('0', '9'))
-  .or(CharMatcher.anyOf("_-.~@^()[]{}:;,"))
-
-internal fun String.sanitizeForFilename(): String? {
-  return filenameSafeChars.negate().replaceFrom(lowercase(Locale.US), '_')
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
deleted file mode 100644
index 2c9dec41..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import android.animation.AnimationHandler
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Bitmap
-import android.os.Handler_Delegate
-import android.os.SystemClock_Delegate
-import android.util.AttributeSet
-import android.util.DisplayMetrics
-import android.view.BridgeInflater
-import android.view.Choreographer
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.widget.FrameLayout
-import androidx.annotation.LayoutRes
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ComposeView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.lifecycle.setViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
-import androidx.savedstate.SavedStateRegistryController
-import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.setViewTreeSavedStateRegistryOwner
-import app.cash.paparazzi.agent.AgentTestRule
-import app.cash.paparazzi.agent.InterceptorRegistrar
-import app.cash.paparazzi.internal.ChoreographerDelegateInterceptor
-import app.cash.paparazzi.internal.EditModeInterceptor
-import app.cash.paparazzi.internal.IInputMethodManagerInterceptor
-import app.cash.paparazzi.internal.ImageUtils
-import app.cash.paparazzi.internal.MatrixMatrixMultiplicationInterceptor
-import app.cash.paparazzi.internal.MatrixVectorMultiplicationInterceptor
-import app.cash.paparazzi.internal.PaparazziCallback
-import app.cash.paparazzi.internal.PaparazziLogger
-import app.cash.paparazzi.internal.Renderer
-import app.cash.paparazzi.internal.ResourcesInterceptor
-import app.cash.paparazzi.internal.ServiceManagerInterceptor
-import app.cash.paparazzi.internal.SessionParamsBuilder
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.ide.common.rendering.api.RenderSession
-import com.android.ide.common.rendering.api.Result
-import com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import com.android.internal.lang.System_Delegate
-import com.android.layoutlib.bridge.Bridge
-import com.android.layoutlib.bridge.Bridge.cleanupThread
-import com.android.layoutlib.bridge.Bridge.prepareThread
-import com.android.layoutlib.bridge.BridgeRenderSession
-import com.android.layoutlib.bridge.impl.RenderAction
-import com.android.layoutlib.bridge.impl.RenderSessionImpl
-import java.awt.image.BufferedImage
-import java.util.Date
-import java.util.concurrent.TimeUnit
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-class Paparazzi @JvmOverloads constructor(
-  private val environment: Environment = detectEnvironment(),
-  private val deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5,
-  private val theme: String = "android:Theme.Material.NoActionBar.Fullscreen",
-  private val renderingMode: RenderingMode = RenderingMode.NORMAL,
-  private val appCompatEnabled: Boolean = true,
-  private val maxPercentDifference: Double = 0.1,
-  private val snapshotHandler: SnapshotHandler = determineHandler(maxPercentDifference),
-  private val renderExtensions: Set<RenderExtension> = setOf()
-) : TestRule {
-  private val logger = PaparazziLogger()
-  private lateinit var renderSession: RenderSessionImpl
-  private lateinit var bridgeRenderSession: RenderSession
-  private var testName: TestName? = null
-
-  val layoutInflater: LayoutInflater
-    get() = RenderAction.getCurrentContext().getSystemService("layout_inflater") as BridgeInflater
-
-  val resources: Resources
-    get() = RenderAction.getCurrentContext().resources
-
-  val context: Context
-    get() = RenderAction.getCurrentContext()
-
-  /**
-   * The root layout that test views will be placed into. The FrameLayout is dynamically set to
-   * `wrap_content` if the `renderMode` is `RenderingMode.SizeAction.SHRINK` in the appropriate
-   * direction, otherwise it is set to `match_parent`.
-   */
-  private val contentRoot = """
-        |<?xml version="1.0" encoding="utf-8"?>
-        |<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        |              android:layout_width="${renderingMode.horizAction.toAttrValue()}"
-        |              android:layout_height="${renderingMode.vertAction.toAttrValue()}"/>
-  """.trimMargin()
-
-  private fun RenderingMode.SizeAction.toAttrValue() =
-    if (this == RenderingMode.SizeAction.SHRINK) "wrap_content" else "match_parent"
-
-  override fun apply(
-    base: Statement,
-    description: Description
-  ): Statement {
-    val statement = object : Statement() {
-      override fun evaluate() {
-        prepare(description)
-        try {
-          base.evaluate()
-        } finally {
-          close()
-          logger.assertNoErrors()
-        }
-      }
-    }
-
-    return if (!isInitialized) {
-      registerFontLookupInterceptionIfResourceCompatDetected()
-      registerViewEditModeInterception()
-      registerMatrixMultiplyInterception()
-      registerChoreographerDelegateInterception()
-      registerServiceManagerInterception()
-      registerIInputMethodManagerInterception()
-
-      val outerRule = AgentTestRule()
-      outerRule.apply(statement, description)
-    } else {
-      statement
-    }
-  }
-
-  fun prepare(description: Description) {
-    forcePlatformSdkVersion(environment.compileSdkVersion)
-
-    val layoutlibCallback = PaparazziCallback(logger, environment.resourcePackageNames)
-    layoutlibCallback.initResources()
-
-    testName = description.toTestName()
-
-    if (!isInitialized) {
-      renderer = Renderer(environment, layoutlibCallback, logger, maxPercentDifference)
-      sessionParamsBuilder = renderer.prepare()
-    }
-
-    sessionParamsBuilder = sessionParamsBuilder
-      .copy(
-        layoutPullParser = LayoutPullParser.createFromString(contentRoot),
-        deviceConfig = deviceConfig,
-        renderingMode = renderingMode
-      )
-      .withTheme(theme)
-
-    val sessionParams = sessionParamsBuilder.build()
-    renderSession = createRenderSession(sessionParams)
-    prepareThread()
-    renderSession.init(sessionParams.timeout)
-    Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEVICE_STABLE)
-
-    // requires LayoutInflater to be created, which is a side-effect of RenderSessionImpl.init()
-    if (appCompatEnabled) {
-      initializeAppCompatIfPresent()
-    }
-
-    bridgeRenderSession = createBridgeSession(renderSession, renderSession.inflate())
-  }
-
-  fun close() {
-    testName = null
-    renderSession.release()
-    bridgeRenderSession.dispose()
-    cleanupThread()
-    snapshotHandler.close()
-
-    renderer.dumpDelegates()
-  }
-
-  @Suppress("UNCHECKED_CAST")
-  fun <V : View> inflate(@LayoutRes layoutId: Int): V =
-    layoutInflater.inflate(layoutId, null) as V
-
-  fun snapshot(name: String? = null, composable: @Composable () -> Unit) {
-    val hostView = ComposeView(context)
-    // During onAttachedToWindow, AbstractComposeView will attempt to resolve its parent's
-    // CompositionContext, which requires first finding the "content view", then using that to
-    // find a root view with a ViewTreeLifecycleOwner
-    val parent = FrameLayout(context).apply { id = android.R.id.content }
-    parent.addView(
-      hostView,
-      renderingMode.horizAction.toLayoutParams(),
-      renderingMode.vertAction.toLayoutParams()
-    )
-    PaparazziComposeOwner.register(parent)
-    hostView.setContent(composable)
-
-    try {
-      snapshot(parent, name)
-    } finally {
-      forceReleaseComposeReferenceLeaks()
-    }
-  }
-
-  private fun RenderingMode.SizeAction.toLayoutParams() =
-    if (this == RenderingMode.SizeAction.SHRINK) {
-      LayoutParams.WRAP_CONTENT
-    } else {
-      LayoutParams.MATCH_PARENT
-    }
-
-  @JvmOverloads
-  fun snapshot(view: View, name: String? = null) {
-    takeSnapshots(view, name, 0, -1, 1)
-  }
-
-  @JvmOverloads
-  fun gif(
-    view: View,
-    name: String? = null,
-    start: Long = 0L,
-    end: Long = 500L,
-    fps: Int = 30
-  ) {
-    // Add one to the frame count so we get the last frame. Otherwise a 1 second, 60 FPS animation
-    // our 60th frame will be at time 983 ms, and we want our last frame to be 1,000 ms. This gets
-    // us 61 frames for a 1 second animation, 121 frames for a 2 second animation, etc.
-    val durationMillis = (end - start).toInt()
-    val frameCount = (durationMillis * fps) / 1000 + 1
-    val startNanos = TimeUnit.MILLISECONDS.toNanos(start)
-    takeSnapshots(view, name, startNanos, fps, frameCount)
-  }
-
-  fun unsafeUpdateConfig(
-    deviceConfig: DeviceConfig? = null,
-    theme: String? = null,
-    renderingMode: RenderingMode? = null
-  ) {
-    require(deviceConfig != null || theme != null || renderingMode != null) {
-      "Calling unsafeUpdateConfig requires at least one non-null argument."
-    }
-
-    renderSession.release()
-    bridgeRenderSession.dispose()
-    cleanupThread()
-
-    sessionParamsBuilder = sessionParamsBuilder
-      .copy(
-        // Required to reset underlying parser stream
-        layoutPullParser = LayoutPullParser.createFromString(contentRoot)
-      )
-
-    if (deviceConfig != null) {
-      sessionParamsBuilder = sessionParamsBuilder.copy(deviceConfig = deviceConfig)
-    }
-
-    if (theme != null) {
-      sessionParamsBuilder = sessionParamsBuilder.withTheme(theme)
-    }
-
-    if (renderingMode != null) {
-      sessionParamsBuilder = sessionParamsBuilder.copy(renderingMode = renderingMode)
-    }
-
-    val sessionParams = sessionParamsBuilder.build()
-    renderSession = createRenderSession(sessionParams)
-    prepareThread()
-    renderSession.init(sessionParams.timeout)
-    Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEVICE_STABLE)
-    bridgeRenderSession = createBridgeSession(renderSession, renderSession.inflate())
-  }
-
-  private fun takeSnapshots(
-    view: View,
-    name: String?,
-    startNanos: Long,
-    fps: Int,
-    frameCount: Int
-  ) {
-    val snapshot = Snapshot(name, testName!!, Date())
-
-    val frameHandler = snapshotHandler.newFrameHandler(snapshot, frameCount, fps)
-    frameHandler.use {
-      val viewGroup = bridgeRenderSession.rootViews[0].viewObject as ViewGroup
-      val modifiedView = renderExtensions.fold(view) { view, renderExtension ->
-        renderExtension.renderView(view)
-      }
-
-      System_Delegate.setBootTimeNanos(0L)
-      try {
-        withTime(0L) {
-          // Initialize the choreographer at time=0.
-        }
-
-        viewGroup.addView(modifiedView)
-        for (frame in 0 until frameCount) {
-          val nowNanos = (startNanos + (frame * 1_000_000_000.0 / fps)).toLong()
-          withTime(nowNanos) {
-            val result = renderSession.render(true)
-            if (result.status == ERROR_UNKNOWN) {
-              throw result.exception
-            }
-
-            val image = bridgeRenderSession.image
-            frameHandler.handle(scaleImage(image))
-          }
-        }
-      } finally {
-        viewGroup.removeView(modifiedView)
-        AnimationHandler.sAnimatorHandler.set(null)
-      }
-    }
-  }
-
-  private fun withTime(
-    timeNanos: Long,
-    block: () -> Unit
-  ) {
-    val frameNanos = TIME_OFFSET_NANOS + timeNanos
-
-    // Execute the block at the requested time.
-    System_Delegate.setNanosTime(frameNanos)
-
-    val choreographer = Choreographer.getInstance()
-    val areCallbacksRunningField = choreographer::class.java.getDeclaredField("mCallbacksRunning")
-    areCallbacksRunningField.isAccessible = true
-
-    try {
-      areCallbacksRunningField.setBoolean(choreographer, true)
-
-      // https://android.googlesource.com/platform/frameworks/layoutlib/+/d58aa4703369e109b24419548f38b422d5a44738/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java#171
-      // BridgeRenderSession.executeCallbacks aggressively tears down the main Looper and BridgeContext, so we call the static delegates ourselves.
-      Handler_Delegate.executeCallbacks()
-      val currentTimeMs = SystemClock_Delegate.uptimeMillis()
-      val choreographerCallbacks =
-        RenderAction.getCurrentContext().sessionInteractiveData.choreographerCallbacks
-      choreographerCallbacks.execute(currentTimeMs, Bridge.getLog())
-
-      block()
-    } catch (e: Throwable) {
-      Bridge.getLog().error("broken", "Failed executing Choreographer#doFrame", e, null, null)
-      throw e
-    } finally {
-      areCallbacksRunningField.setBoolean(choreographer, false)
-    }
-  }
-
-  private fun createRenderSession(sessionParams: SessionParams): RenderSessionImpl {
-    val renderSession = RenderSessionImpl(sessionParams)
-    renderSession.setElapsedFrameTimeNanos(0L)
-    RenderSessionImpl::class.java
-      .getDeclaredField("mFirstFrameExecuted")
-      .apply {
-        isAccessible = true
-        set(renderSession, true)
-      }
-    return renderSession
-  }
-
-  private fun createBridgeSession(
-    renderSession: RenderSessionImpl,
-    result: Result
-  ): BridgeRenderSession {
-    try {
-      val bridgeSessionClass = Class.forName("com.android.layoutlib.bridge.BridgeRenderSession")
-      val constructor =
-        bridgeSessionClass.getDeclaredConstructor(RenderSessionImpl::class.java, Result::class.java)
-      constructor.isAccessible = true
-      return constructor.newInstance(renderSession, result) as BridgeRenderSession
-    } catch (e: Exception) {
-      throw RuntimeException(e)
-    }
-  }
-
-  private fun scaleImage(image: BufferedImage): BufferedImage {
-    val scale = ImageUtils.getThumbnailScale(image)
-    // Only scale images down so we don't waste storage space enlarging smaller layouts.
-    return if (scale < 1f) ImageUtils.scale(image, scale, scale) else image
-  }
-
-  private fun Description.toTestName(): TestName {
-    val fullQualifiedName = className
-    val packageName = fullQualifiedName.substringBeforeLast('.', missingDelimiterValue = "")
-    val className = fullQualifiedName.substringAfterLast('.')
-    return TestName(packageName, className, methodName)
-  }
-
-  private fun forcePlatformSdkVersion(compileSdkVersion: Int) {
-    val buildVersionClass = try {
-      Paparazzi::class.java.classLoader.loadClass("android.os.Build\$VERSION")
-    } catch (e: ClassNotFoundException) {
-      // Project unit tests don't load Android platform code
-      return
-    }
-    buildVersionClass
-      .getFieldReflectively("SDK_INT")
-      .setStaticValue(compileSdkVersion)
-  }
-
-  private fun initializeAppCompatIfPresent() {
-    lateinit var appCompatDelegateClass: Class<*>
-    try {
-      // See androidx.appcompat.widget.AppCompatDrawableManager#preload()
-      val appCompatDrawableManagerClass =
-        Class.forName("androidx.appcompat.widget.AppCompatDrawableManager")
-      val preloadMethod = appCompatDrawableManagerClass.getMethod("preload")
-      preloadMethod.invoke(null)
-
-      appCompatDelegateClass = Class.forName("androidx.appcompat.app.AppCompatDelegate")
-    } catch (e: ClassNotFoundException) {
-      logger.verbose("AppCompat not found on classpath")
-      return
-    }
-
-    // See androidx.appcompat.app.AppCompatDelegateImpl#installViewFactory()
-    if (layoutInflater.factory == null) {
-      layoutInflater.factory2 = object : LayoutInflater.Factory2 {
-        override fun onCreateView(
-          parent: View?,
-          name: String,
-          context: Context,
-          attrs: AttributeSet
-        ): View? {
-          val appCompatViewInflaterClass =
-            Class.forName("androidx.appcompat.app.AppCompatViewInflater")
-
-          val createViewMethod = appCompatViewInflaterClass
-            .getDeclaredMethod(
-              "createView",
-              View::class.java,
-              String::class.java,
-              Context::class.java,
-              AttributeSet::class.java,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType
-            )
-            .apply { isAccessible = true }
-
-          val inheritContext = true
-          val readAndroidTheme = true
-          val readAppTheme = true
-          val wrapContext = true
-
-          val newAppCompatViewInflaterInstance = appCompatViewInflaterClass
-            .getConstructor()
-            .newInstance()
-
-          return createViewMethod.invoke(
-            newAppCompatViewInflaterInstance, parent, name, context, attrs,
-            inheritContext, readAndroidTheme, readAppTheme, wrapContext
-          ) as View?
-        }
-
-        override fun onCreateView(
-          name: String,
-          context: Context,
-          attrs: AttributeSet
-        ): View? = onCreateView(null, name, context, attrs)
-      }
-    } else {
-      if (!appCompatDelegateClass.isAssignableFrom(layoutInflater.factory2::class.java)) {
-        throw IllegalStateException(
-          "The LayoutInflater already has a Factory installed so we can not install AppCompat's"
-        )
-      }
-    }
-  }
-
-  /**
-   * Current workaround for supporting custom fonts when constructing views in code. This check
-   * may be used or expanded to support other cases requiring similar method interception
-   * techniques.
-   *
-   * See:
-   * https://github.com/cashapp/paparazzi/issues/119
-   * https://issuetracker.google.com/issues/156065472
-   */
-  private fun registerFontLookupInterceptionIfResourceCompatDetected() {
-    try {
-      val resourcesCompatClass = Class.forName("androidx.core.content.res.ResourcesCompat")
-      InterceptorRegistrar.addMethodInterceptor(
-        resourcesCompatClass,
-        "getFont",
-        ResourcesInterceptor::class.java
-      )
-    } catch (e: ClassNotFoundException) {
-      logger.verbose("ResourceCompat not found on classpath")
-    }
-  }
-
-  private fun registerServiceManagerInterception() {
-    val serviceManager = Class.forName("android.os.ServiceManager")
-    InterceptorRegistrar.addMethodInterceptor(
-      serviceManager,
-      "getServiceOrThrow",
-      ServiceManagerInterceptor::class.java
-    )
-  }
-
-  private fun registerIInputMethodManagerInterception() {
-    val iimm = Class.forName("com.android.internal.view.IInputMethodManager\$Stub")
-    InterceptorRegistrar.addMethodInterceptor(
-      iimm,
-      "asInterface",
-      IInputMethodManagerInterceptor::class.java
-    )
-  }
-
-  private fun registerViewEditModeInterception() {
-    val viewClass = Class.forName("android.view.View")
-    InterceptorRegistrar.addMethodInterceptor(
-      viewClass,
-      "isInEditMode",
-      EditModeInterceptor::class.java
-    )
-  }
-
-  private fun registerMatrixMultiplyInterception() {
-    val matrixClass = Class.forName("android.opengl.Matrix")
-    InterceptorRegistrar.addMethodInterceptors(
-      matrixClass,
-      setOf(
-        "multiplyMM" to MatrixMatrixMultiplicationInterceptor::class.java,
-        "multiplyMV" to MatrixVectorMultiplicationInterceptor::class.java
-      )
-    )
-  }
-
-  private fun registerChoreographerDelegateInterception() {
-    val choreographerDelegateClass = Class.forName("android.view.Choreographer_Delegate")
-    InterceptorRegistrar.addMethodInterceptor(
-      choreographerDelegateClass,
-      "getFrameTimeNanos",
-      ChoreographerDelegateInterceptor::class.java
-    )
-  }
-
-  private fun forceReleaseComposeReferenceLeaks() {
-    // AndroidUiDispatcher is backed by a Handler, by executing one last time
-    // we give the dispatcher the ability to clean-up / release its callbacks.
-    executeHandlerCallbacks()
-  }
-
-  private fun executeHandlerCallbacks() {
-    // Avoid ConcurrentModificationException in
-    // RenderAction.currentContext.sessionInteractiveData.handlerMessageQueue.runnablesMap which is a WeakHashMap
-    // https://android.googlesource.com/platform/tools/adt/idea/+/c331c9b2f4334748c55c29adec3ad1cd67e45df2/designer/src/com/android/tools/idea/uibuilder/scene/LayoutlibSceneManager.java#1558
-    synchronized(this) {
-      // https://android.googlesource.com/platform/frameworks/layoutlib/+/d58aa4703369e109b24419548f38b422d5a44738/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java#171
-      // BridgeRenderSession.executeCallbacks aggressively tears down the main Looper and BridgeContext, so we call the static delegates ourselves.
-      Handler_Delegate.executeCallbacks()
-    }
-  }
-
-  private class PaparazziComposeOwner private constructor() :
-    LifecycleOwner, SavedStateRegistryOwner {
-    private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
-    private val savedStateRegistryController = SavedStateRegistryController.create(this)
-
-    override val lifecycle: Lifecycle
-      get() = lifecycleRegistry
-    override val savedStateRegistry: SavedStateRegistry =
-      savedStateRegistryController.savedStateRegistry
-
-    companion object {
-      fun register(view: View) {
-        val owner = PaparazziComposeOwner()
-        owner.savedStateRegistryController.performRestore(null)
-        owner.lifecycleRegistry.currentState = Lifecycle.State.CREATED
-        view.setViewTreeLifecycleOwner(owner)
-        view.setViewTreeSavedStateRegistryOwner(owner)
-      }
-    }
-  }
-
-  companion object {
-    /** The choreographer doesn't like 0 as a frame time, so start an hour later. */
-    internal val TIME_OFFSET_NANOS = TimeUnit.HOURS.toNanos(1L)
-
-    internal lateinit var renderer: Renderer
-    internal val isInitialized get() = ::renderer.isInitialized
-
-    internal lateinit var sessionParamsBuilder: SessionParamsBuilder
-
-    private val isVerifying: Boolean =
-      System.getProperty("paparazzi.test.verify")?.toBoolean() == true
-
-    private fun determineHandler(maxPercentDifference: Double): SnapshotHandler =
-      if (isVerifying) {
-        SnapshotVerifier(maxPercentDifference)
-      } else {
-        HtmlReportWriter()
-      }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt
deleted file mode 100644
index 15617f0..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package app.cash.paparazzi
-
-import java.lang.reflect.Field
-import java.lang.reflect.Modifier
-import java.security.PrivilegedAction
-import sun.misc.Unsafe
-
-/**
- * Inspired by and ported from:
- * https://github.com/powermock/powermock/commit/fc092c5d7e339d01e079184a2a0e88b5c46fc0e8
- * https://github.com/powermock/powermock/commit/bd92bcc5329c4981cf09dece5c3eafcf92fe49ff
- */
-internal fun Class<*>.getFieldReflectively(fieldName: String): Field =
-  try {
-    this.getDeclaredField(fieldName).also { it.isAccessible = true }
-  } catch (e: NoSuchFieldException) {
-    throw RuntimeException("Field '$fieldName' was not found in class $name.")
-  }
-
-internal fun Field.setStaticValue(value: Any) {
-  try {
-    this.isAccessible = true
-    val isFinalModifierPresent = this.modifiers and Modifier.FINAL == Modifier.FINAL
-    if (isFinalModifierPresent) {
-      @Suppress("DEPRECATION")
-      java.security.AccessController.doPrivileged<Any?>(
-        PrivilegedAction {
-          try {
-            val unsafe = Unsafe::class.java.getFieldReflectively("theUnsafe").get(null) as Unsafe
-            val offset = unsafe.staticFieldOffset(this)
-            val base = unsafe.staticFieldBase(this)
-            unsafe.setFieldValue(this, base, offset, value)
-            null
-          } catch (t: Throwable) {
-            throw RuntimeException(t)
-          }
-        }
-      )
-    } else {
-      this.set(null, value)
-    }
-  } catch (ex: SecurityException) {
-    throw RuntimeException(ex)
-  } catch (ex: IllegalAccessException) {
-    throw RuntimeException(ex)
-  } catch (ex: IllegalArgumentException) {
-    throw RuntimeException(ex)
-  }
-}
-
-internal fun Unsafe.setFieldValue(field: Field, base: Any, offset: Long, value: Any) =
-  when (field.type) {
-    Integer.TYPE -> this.putInt(base, offset, (value as Int))
-    java.lang.Short.TYPE -> this.putShort(base, offset, (value as Short))
-    java.lang.Long.TYPE -> this.putLong(base, offset, (value as Long))
-    java.lang.Byte.TYPE -> this.putByte(base, offset, (value as Byte))
-    java.lang.Boolean.TYPE -> this.putBoolean(base, offset, (value as Boolean))
-    java.lang.Float.TYPE -> this.putFloat(base, offset, (value as Float))
-    java.lang.Double.TYPE -> this.putDouble(base, offset, (value as Double))
-    Character.TYPE -> this.putChar(base, offset, (value as Char))
-    else -> this.putObject(base, offset, value)
-  }
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt
deleted file mode 100644
index d0d12b2..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import android.view.View
-
-/**
- * An extension for overlaying additional information on top of each rendered frame.
- */
-interface RenderExtension {
-  /**
-   * Allows this extension to modify the view hierarchy represented by [contentView].
-   *
-   * Returns the root view of the modified hierarchy.
-   */
-  fun renderView(contentView: View): View
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
deleted file mode 100644
index 85501f7..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import com.squareup.moshi.JsonClass
-import java.util.Date
-import java.util.Locale
-
-@JsonClass(generateAdapter = true)
-data class Snapshot(
-  val name: String?,
-  val testName: TestName,
-  val timestamp: Date,
-  val tags: List<String> = listOf(),
-  val file: String? = null
-)
-
-internal fun Snapshot.toFileName(
-  delimiter: String = "_",
-  extension: String
-): String {
-  val formattedLabel = if (name != null) {
-    "$delimiter${name.lowercase(Locale.US).replace("\\s".toRegex(), delimiter)}"
-  } else {
-    ""
-  }
-  return "${testName.packageName}${delimiter}${testName.className}" +
-      "${delimiter}${testName.methodName}$formattedLabel.$extension"
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt
deleted file mode 100644
index 5067f25..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import java.awt.image.BufferedImage
-import java.io.Closeable
-
-interface SnapshotHandler : Closeable {
-  fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler
-
-  interface FrameHandler : Closeable {
-    fun handle(image: BufferedImage)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt
deleted file mode 100644
index 564cd2e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import app.cash.paparazzi.SnapshotHandler.FrameHandler
-import app.cash.paparazzi.internal.ImageUtils
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-
-class SnapshotVerifier @JvmOverloads constructor(
-  private val maxPercentDifference: Double,
-  rootDirectory: File = File("src/test/snapshots")
-) : SnapshotHandler {
-  private val imagesDirectory: File = File(rootDirectory, "images")
-  private val videosDirectory: File = File(rootDirectory, "videos")
-
-  init {
-    imagesDirectory.mkdirs()
-    videosDirectory.mkdirs()
-  }
-
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler {
-    return object : FrameHandler {
-      override fun handle(image: BufferedImage) {
-        // Note: does not handle videos or its frames at the moment
-        val expected = File(imagesDirectory, snapshot.toFileName(extension = "png"))
-        if (!expected.exists()) {
-          throw AssertionError("File $expected does not exist")
-        }
-
-        val goldenImage = ImageIO.read(expected)
-        ImageUtils.assertImageSimilar(
-          relativePath = expected.path,
-          image = image,
-          goldenImage = goldenImage,
-          maxPercentDifferent = maxPercentDifference
-        )
-      }
-
-      override fun close() = Unit
-    }
-  }
-
-  override fun close() = Unit
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt
deleted file mode 100644
index 2ab7963..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-data class TestName(
-  val packageName: String,
-  val className: String,
-  val methodName: String
-)
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt
deleted file mode 100644
index f6f16aa..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.accessibility
-
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.LayerDrawable
-import android.util.TypedValue
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.RenderExtension
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_DESCRIPTION_BACKGROUND_COLOR
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_RECT_SIZE
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_RENDER_ALPHA
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_TEXT_COLOR
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_TEXT_SIZE
-import app.cash.paparazzi.accessibility.RenderSettings.getColor
-import app.cash.paparazzi.accessibility.RenderSettings.toColorInt
-import app.cash.paparazzi.accessibility.RenderSettings.withAlpha
-
-class AccessibilityRenderExtension : RenderExtension {
-  override fun renderView(
-    contentView: View
-  ): View {
-    val accessibilityViews = contentView.findAccessibilityViews()
-    accessibilityViews.forEach { view ->
-      val color = getColor(view)
-      val colorInt = color.toColorInt()
-
-      val colorDrawable = GradientDrawable(
-        GradientDrawable.Orientation.TOP_BOTTOM,
-        intArrayOf(colorInt, colorInt)
-      ).apply {
-        setStroke(2, color.withAlpha(DEFAULT_RENDER_ALPHA * 2).toColorInt())
-      }
-
-      view.foreground = view.foreground?.let { drawable ->
-        // If there is an existing foreground layer the color on top of it.
-        LayerDrawable(arrayOf(drawable, colorDrawable))
-      } ?: colorDrawable
-    }
-
-    return LinearLayout(contentView.context).apply {
-      orientation = LinearLayout.HORIZONTAL
-      weightSum = 2f
-      layoutParams = ViewGroup.LayoutParams(
-        ViewGroup.LayoutParams.MATCH_PARENT,
-        ViewGroup.LayoutParams.MATCH_PARENT
-      )
-
-      val contentLayoutParams = contentView.layoutParams ?: generateLayoutParams(null)
-      addView(
-        contentView,
-        LinearLayout.LayoutParams(
-          contentLayoutParams.width,
-          contentLayoutParams.height,
-          1f
-        )
-      )
-      addView(
-        buildAccessibilityView(contentView),
-        LinearLayout.LayoutParams(
-          ViewGroup.LayoutParams.MATCH_PARENT,
-          ViewGroup.LayoutParams.MATCH_PARENT,
-          1f
-        )
-      )
-    }
-  }
-
-  private fun View.findAccessibilityViews(): List<View> {
-    val accessibilityViews = mutableListOf<View>()
-    if (isImportantForAccessibility && !iterableTextForAccessibility.isNullOrBlank()) {
-      accessibilityViews.add(this)
-    }
-
-    if (this is ViewGroup) {
-      (0 until childCount).forEach {
-        accessibilityViews += getChildAt(it).findAccessibilityViews()
-      }
-    }
-
-    return accessibilityViews
-  }
-
-  private fun buildAccessibilityView(contentView: View): View {
-    val linearLayout = LinearLayout(contentView.context).apply {
-      orientation = LinearLayout.VERTICAL
-      setBackgroundColor(DEFAULT_DESCRIPTION_BACKGROUND_COLOR.toColorInt())
-    }
-
-    fun renderAccessibility(view: View) {
-      if (view.isImportantForAccessibility && !view.iterableTextForAccessibility.isNullOrBlank()) {
-        linearLayout.addView(buildAccessibilityRow(view, view.iterableTextForAccessibility))
-      }
-
-      if (view is ViewGroup) {
-        (0 until view.childCount).forEach {
-          renderAccessibility(view.getChildAt(it))
-        }
-      }
-    }
-
-    renderAccessibility(contentView)
-    return linearLayout
-  }
-
-  private fun buildAccessibilityRow(view: View, iterableTextForAccessibility: CharSequence): View {
-    val context = view.context
-    val color = getColor(view).toColorInt()
-    val margin = view.dip(8)
-    val innerMargin = view.dip(4)
-
-    return LinearLayout(context).apply {
-      orientation = LinearLayout.HORIZONTAL
-      layoutParams = ViewGroup.LayoutParams(
-        ViewGroup.LayoutParams.MATCH_PARENT,
-        ViewGroup.LayoutParams.WRAP_CONTENT
-      )
-      setPaddingRelative(margin, innerMargin, margin, innerMargin)
-
-      addView(
-        View(context).apply {
-          layoutParams = ViewGroup.LayoutParams(dip(DEFAULT_RECT_SIZE), dip(DEFAULT_RECT_SIZE))
-          background = GradientDrawable(
-            GradientDrawable.Orientation.TOP_BOTTOM,
-            intArrayOf(color, color)
-          ).apply {
-            cornerRadius = dip(DEFAULT_RECT_SIZE / 4f)
-          }
-          setPaddingRelative(innerMargin, innerMargin, innerMargin, innerMargin)
-        }
-      )
-      addView(
-        TextView(context).apply {
-          layoutParams = ViewGroup.LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          )
-          text = iterableTextForAccessibility
-          textSize = DEFAULT_TEXT_SIZE
-          setTextColor(DEFAULT_TEXT_COLOR.toColorInt())
-          setPaddingRelative(innerMargin, 0, innerMargin, 0)
-        }
-      )
-    }
-  }
-}
-
-private fun View.dip(value: Float): Float =
-  TypedValue.applyDimension(
-    TypedValue.COMPLEX_UNIT_DIP,
-    value,
-    resources.displayMetrics
-  )
-
-private fun View.dip(value: Int): Int = dip(value.toFloat()).toInt()
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt
deleted file mode 100644
index 768fc6e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.accessibility
-
-import android.view.View
-import java.awt.Color
-
-internal object RenderSettings {
-  const val DEFAULT_RENDER_ALPHA = 40
-  val DEFAULT_RENDER_COLORS = listOf(
-    Color.RED,
-    Color.GREEN,
-    Color.BLUE,
-    Color.YELLOW,
-    Color.ORANGE,
-    Color.MAGENTA,
-    Color.CYAN,
-    Color.PINK
-  )
-  val DEFAULT_TEXT_COLOR: Color = Color.BLACK
-  val DEFAULT_DESCRIPTION_BACKGROUND_COLOR: Color = Color.WHITE
-  const val DEFAULT_TEXT_SIZE: Float = 10f
-  const val DEFAULT_RECT_SIZE: Int = 16
-
-  private val colorMap = mutableMapOf<Int, Color>()
-
-  fun getColor(view: View): Color {
-    val key = "${view::class.simpleName}(${view.iterableTextForAccessibility})"
-    return getColor(key)
-  }
-
-  private fun getColor(key: String): Color {
-    val hashCode = key.hashCode()
-    return colorMap.getOrPut(hashCode) {
-      nextColor(hashCode).withAlpha(DEFAULT_RENDER_ALPHA)
-    }
-  }
-
-  private fun nextColor(hashCode: Int): Color {
-    return DEFAULT_RENDER_COLORS[colorIndex(hashCode)]
-  }
-
-  private fun colorIndex(hashCode: Int): Int {
-    val size = DEFAULT_RENDER_COLORS.size
-    val i = hashCode % size
-    return if (i < 0) i + size else i
-  }
-
-  internal fun Color.toColorInt(): Int =
-    android.graphics.Color.argb(alpha, red, green, blue)
-
-  internal fun Color.withAlpha(alpha: Int): Color {
-    return Color(red, green, blue, alpha)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt
deleted file mode 100644
index 57b7bf1..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.view.Choreographer
-import com.android.internal.lang.System_Delegate
-
-object ChoreographerDelegateInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    @Suppress("UNUSED_PARAMETER") choreographer: Choreographer
-  ): Long = System_Delegate.nanoTime()
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt
deleted file mode 100644
index c464a04..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package app.cash.paparazzi.internal
-
-object EditModeInterceptor {
-  @JvmStatic
-  fun intercept(): Boolean = false
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt
deleted file mode 100644
index 5bbd25c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 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 app.cash.paparazzi.internal
-
-import java.lang.ref.WeakReference
-
-internal object Gc {
-  fun gc() {
-    // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
-    var obj: Any? = Any()
-    val ref = WeakReference<Any>(obj)
-
-    @Suppress("UNUSED_VAlUE")
-    obj = null
-    while (ref.get() != null) {
-      System.gc()
-      System.runFinalization()
-    }
-
-    System.gc()
-    System.runFinalization()
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt
deleted file mode 100644
index 1908949..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.os.IBinder
-import com.android.internal.view.IInputMethodManager
-
-/**
- * With [ServiceManagerInterceptor] returning null for the service, we must override the logic
- * in [com.android.internal.view.IInputMethodManager.Stub.asInterface] to return the default
- * implementation of [IInputMethodManager].
- */
-object IInputMethodManagerInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun interceptAsInterface(@Suppress("UNUSED_PARAMETER") obj: IBinder?): IInputMethodManager =
-    IInputMethodManager.Default()
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt
deleted file mode 100644
index 8e7111c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2016 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 app.cash.paparazzi.internal
-
-import java.awt.AlphaComposite
-import java.awt.Color
-import java.awt.Graphics2D
-import java.awt.RenderingHints.KEY_ANTIALIASING
-import java.awt.RenderingHints.KEY_INTERPOLATION
-import java.awt.RenderingHints.KEY_RENDERING
-import java.awt.RenderingHints.VALUE_ANTIALIAS_ON
-import java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR
-import java.awt.RenderingHints.VALUE_RENDER_QUALITY
-import java.awt.image.BufferedImage
-import java.awt.image.BufferedImage.TYPE_INT_ARGB
-import java.io.File
-import java.io.File.separatorChar
-import java.io.IOException
-import javax.imageio.ImageIO
-import kotlin.math.max
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-
-/**
- * Utilities related to image processing.
- */
-internal object ImageUtils {
-  /**
-   * Normally, this test will fail when there is a missing thumbnail. However, when
-   * you create creating a new test, it's useful to be able to turn this off such that
-   * you can generate all the missing thumbnails in one go, rather than having to run
-   * the test repeatedly to get to each new render assertion generating its thumbnail.
-   */
-  private val FAIL_ON_MISSING_THUMBNAIL = true
-
-  private const val THUMBNAIL_SIZE = 1000
-
-  /** Directory where to write the thumbnails and deltas. */
-  private val failureDir: File
-    get() {
-      val workingDirString = System.getProperty("user.dir")
-      val failureDir = File(workingDirString, "out/failures")
-      failureDir.mkdirs()
-      return failureDir
-    }
-
-  @Throws(IOException::class)
-  fun requireSimilar(
-    relativePath: String,
-    image: BufferedImage,
-    maxPercentDifference: Double
-  ) {
-    val scale = getThumbnailScale(image)
-    val thumbnail = scale(image, scale, scale)
-
-    val `is` = ImageUtils::class.java.classLoader.getResourceAsStream(relativePath)
-    if (`is` ==
-      null
-    ) {
-      var message = "Unable to load golden thumbnail: $relativePath\n"
-      message = saveImageAndAppendMessage(thumbnail, message, relativePath)
-      if (FAIL_ON_MISSING_THUMBNAIL) {
-        fail(message)
-      } else {
-        println(message)
-      }
-    } else {
-      try {
-        val goldenImage = ImageIO.read(`is`)
-        assertImageSimilar(
-          relativePath,
-          goldenImage,
-          thumbnail,
-          maxPercentDifference
-        )
-      } finally {
-        `is`.close()
-      }
-    }
-  }
-
-  @Throws(IOException::class)
-  fun assertImageSimilar(
-    relativePath: String,
-    goldenImage: BufferedImage,
-    image: BufferedImage,
-    maxPercentDifferent: Double
-  ) {
-    @Suppress("NAME_SHADOWING") var goldenImage = goldenImage
-    if (goldenImage.type != TYPE_INT_ARGB) {
-      val temp = BufferedImage(
-        goldenImage.width,
-        goldenImage.height,
-        TYPE_INT_ARGB
-      )
-      temp.graphics.drawImage(goldenImage, 0, 0, null)
-      goldenImage = temp
-    }
-    assertEquals(TYPE_INT_ARGB.toLong(), goldenImage.type.toLong())
-
-    val imageWidth = Math.min(goldenImage.width, image.width)
-    val imageHeight = Math.min(goldenImage.height, image.height)
-
-    // Blur the images to account for the scenarios where there are pixel
-    // differences
-    // in where a sharp edge occurs
-    // goldenImage = blur(goldenImage, 6);
-    // image = blur(image, 6);
-
-    val width = 3 * imageWidth
-    val deltaImage = BufferedImage(width, imageHeight, TYPE_INT_ARGB)
-    val g = deltaImage.graphics
-
-    // Compute delta map
-    var delta: Long = 0
-    for (y in 0 until imageHeight) {
-      for (x in 0 until imageWidth) {
-        val goldenRgb = goldenImage.getRGB(x, y)
-        val rgb = image.getRGB(x, y)
-        if (goldenRgb == rgb) {
-          deltaImage.setRGB(imageWidth + x, y, 0x00808080)
-          continue
-        }
-
-        // If the pixels have no opacity, don't delta colors at all
-        if (goldenRgb and -0x1000000 == 0 && rgb and -0x1000000 == 0) {
-          deltaImage.setRGB(imageWidth + x, y, 0x00808080)
-          continue
-        }
-
-        val deltaR = (rgb and 0xFF0000).ushr(16) - (goldenRgb and 0xFF0000).ushr(16)
-        val newR = 128 + deltaR and 0xFF
-        val deltaG = (rgb and 0x00FF00).ushr(8) - (goldenRgb and 0x00FF00).ushr(8)
-        val newG = 128 + deltaG and 0xFF
-        val deltaB = (rgb and 0x0000FF) - (goldenRgb and 0x0000FF)
-        val newB = 128 + deltaB and 0xFF
-
-        val avgAlpha =
-          ((goldenRgb and -0x1000000).ushr(24) + (rgb and -0x1000000).ushr(24)) / 2 shl 24
-
-        val newRGB = avgAlpha or (newR shl 16) or (newG shl 8) or newB
-        deltaImage.setRGB(imageWidth + x, y, newRGB)
-
-        delta += Math.abs(deltaR)
-          .toLong()
-        delta += Math.abs(deltaG)
-          .toLong()
-        delta += Math.abs(deltaB)
-          .toLong()
-      }
-    }
-
-    // 3 different colors, 256 color levels
-    val total = imageHeight.toLong() * imageWidth.toLong() * 3L * 256L
-    val percentDifference = (delta * 100 / total.toDouble()).toFloat()
-
-    var error: String? = null
-    val imageName = getName(relativePath)
-    if (percentDifference > maxPercentDifferent) {
-      error = String.format("Images differ (by %.1f%%)", percentDifference)
-    } else if (Math.abs(goldenImage.width - image.width) >= 2) {
-      error = "Widths differ too much for " + imageName + ": " +
-        goldenImage.width + "x" + goldenImage.height +
-        "vs" + image.width + "x" + image.height
-    } else if (Math.abs(goldenImage.height - image.height) >= 2) {
-      error = "Heights differ too much for " + imageName + ": " +
-        goldenImage.width + "x" + goldenImage.height +
-        "vs" + image.width + "x" + image.height
-    }
-
-    if (error != null) {
-      // Expected on the left
-      // Golden on the right
-      g.drawImage(goldenImage, 0, 0, null)
-      g.drawImage(image, 2 * imageWidth, 0, null)
-
-      // Labels
-      if (imageWidth > 80) {
-        g.color = Color.RED
-        g.drawString("Expected", 10, 20)
-        g.drawString("Actual", 2 * imageWidth + 10, 20)
-      }
-
-      val output = File(failureDir, "delta-$imageName")
-      if (output.exists()) {
-        val deleted = output.delete()
-        assertTrue(deleted)
-      }
-      ImageIO.write(deltaImage, "PNG", output)
-      error += " - see details in file://" + output.path + "\n"
-      error = saveImageAndAppendMessage(image, error, relativePath)
-      println(error)
-      fail(error)
-    }
-
-    g.dispose()
-  }
-
-  /**
-   * Resize the given image
-   *
-   * @param source the image to be scaled
-   * @param xScale x scale
-   * @param yScale y scale
-   * @return the scaled image
-   */
-  fun scale(
-    source: BufferedImage,
-    xScale: Double,
-    yScale: Double
-  ): BufferedImage {
-    @Suppress("NAME_SHADOWING") var source = source
-
-    var sourceWidth = source.width
-    var sourceHeight = source.height
-    val destWidth = Math.max(1, (xScale * sourceWidth).toInt())
-    val destHeight = Math.max(1, (yScale * sourceHeight).toInt())
-    var imageType = source.type
-    if (imageType == BufferedImage.TYPE_CUSTOM) {
-      imageType = BufferedImage.TYPE_INT_ARGB
-    }
-    if (xScale > 0.5 && yScale > 0.5) {
-      val scaled = BufferedImage(destWidth, destHeight, imageType)
-      val g2 = scaled.createGraphics()
-      g2.composite = AlphaComposite.Src
-      g2.color = Color(0, true)
-      g2.fillRect(0, 0, destWidth, destHeight)
-      if (xScale == 1.0 && yScale == 1.0) {
-        g2.drawImage(source, 0, 0, null)
-      } else {
-        setRenderingHints(g2)
-        g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight, null)
-      }
-      g2.dispose()
-      return scaled
-    } else {
-      // When creating a thumbnail, using the above code doesn't work very well;
-      // you get some visible artifacts, especially for text. Instead use the
-      // technique of repeatedly scaling the image into half; this will cause
-      // proper averaging of neighboring pixels, and will typically (for the kinds
-      // of screen sizes used by this utility method in the layout editor) take
-      // about 3-4 iterations to get the result since we are logarithmically reducing
-      // the size. Besides, each successive pass in operating on much fewer pixels
-      // (a reduction of 4 in each pass).
-      //
-      // However, we may not be resizing to a size that can be reached exactly by
-      // successively diving in half. Therefore, once we're within a factor of 2 of
-      // the final size, we can do a resize to the exact target size.
-      // However, we can get even better results if we perform this final resize
-      // up front. Let's say we're going from width 1000 to a destination width of 85.
-      // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
-      // then a resize from 125 to 85. That last resize can distort/blur a lot.
-      // Instead, we can start with the destination width, 85, and double it
-      // successfully until we're close to the initial size: 85, then 170,
-      // then 340, and finally 680. (The next one, 1360, is larger than 1000).
-      // So, now we *start* the thumbnail operation by resizing from width 1000 to
-      // width 680, which will preserve a lot of visual details such as text.
-      // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
-      // We end up with the expected final size, but we've been doing an exact
-      // divide-in-half resizing operation at the end so there is less distortion.
-
-      var iterations = 0 // Number of halving operations to perform after the initial resize
-      var nearestWidth = destWidth // Width closest to source width that = 2^x, x is integer
-      var nearestHeight = destHeight
-      while (nearestWidth < sourceWidth / 2) {
-        nearestWidth *= 2
-        nearestHeight *= 2
-        iterations++
-      }
-
-      var scaled = BufferedImage(nearestWidth, nearestHeight, imageType)
-
-      var g2 = scaled.createGraphics()
-      setRenderingHints(g2)
-      g2.drawImage(source, 0, 0, nearestWidth, nearestHeight, 0, 0, sourceWidth, sourceHeight, null)
-      g2.dispose()
-
-      sourceWidth = nearestWidth
-      sourceHeight = nearestHeight
-      source = scaled
-
-      for (iteration in iterations - 1 downTo 0) {
-        val halfWidth = sourceWidth / 2
-        val halfHeight = sourceHeight / 2
-        scaled = BufferedImage(halfWidth, halfHeight, imageType)
-        g2 = scaled.createGraphics()
-        setRenderingHints(g2)
-        g2.drawImage(source, 0, 0, halfWidth, halfHeight, 0, 0, sourceWidth, sourceHeight, null)
-        g2.dispose()
-
-        sourceWidth = halfWidth
-        sourceHeight = halfHeight
-        source = scaled
-        iterations--
-      }
-      return scaled
-    }
-  }
-
-  fun getThumbnailScale(image: BufferedImage): Double {
-    val maxDimension = max(image.width, image.height)
-    return THUMBNAIL_SIZE / maxDimension.toDouble()
-  }
-
-  private fun setRenderingHints(g2: Graphics2D) {
-    g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR)
-    g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY)
-    g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)
-  }
-
-  /**
-   * Saves the generated thumbnail image and appends the info message to an initial message
-   */
-  @Throws(IOException::class)
-  private fun saveImageAndAppendMessage(
-    image: BufferedImage,
-    initialMessage: String,
-    relativePath: String
-  ): String {
-    @Suppress("NAME_SHADOWING") var initialMessage = initialMessage
-    val output = File(
-      failureDir,
-      getName(relativePath)
-    )
-    if (output.exists()) {
-      val deleted = output.delete()
-      assertTrue(deleted)
-    }
-    ImageIO.write(image, "PNG", output)
-    initialMessage += "Thumbnail for current rendering stored at file://" + output.path
-    //        initialMessage += "\nRun the following command to accept the changes:\n";
-    //        initialMessage += String.format("mv %1$s %2$s", output.getPath(),
-    //                ImageUtils.class.getResource(relativePath).getPath());
-    // The above has been commented out, since the destination path returned is in out dir
-    // and it makes the tests pass without the code being actually checked in.
-    return initialMessage
-  }
-
-  private fun getName(relativePath: String): String {
-    return relativePath.substring(relativePath.lastIndexOf(separatorChar) + 1)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt
deleted file mode 100644
index 41994c2d..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package app.cash.paparazzi.internal
-
-// Sampled from https://cs.android.com/android/platform/superproject/+/master:external/robolectric-shadows/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOpenGLMatrix.java;l=10-67
-object MatrixMatrixMultiplicationInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    result: FloatArray,
-    resultOffset: Int,
-    lhs: FloatArray,
-    lhsOffset: Int,
-    rhs: FloatArray,
-    rhsOffset: Int
-  ) {
-    require(resultOffset + 16 <= result.size) { "resultOffset + 16 > result.length" }
-    require(lhsOffset + 16 <= lhs.size) { "lhsOffset + 16 > lhs.length" }
-    require(rhsOffset + 16 <= rhs.size) { "rhsOffset + 16 > rhs.length" }
-    for (i in 0..3) {
-      val rhs_i0 = rhs[I(i, 0, rhsOffset)]
-      var ri0 = lhs[I(0, 0, lhsOffset)] * rhs_i0
-      var ri1 = lhs[I(0, 1, lhsOffset)] * rhs_i0
-      var ri2 = lhs[I(0, 2, lhsOffset)] * rhs_i0
-      var ri3 = lhs[I(0, 3, lhsOffset)] * rhs_i0
-      for (j in 1..3) {
-        val rhs_ij = rhs[I(i, j, rhsOffset)]
-        ri0 += lhs[I(j, 0, lhsOffset)] * rhs_ij
-        ri1 += lhs[I(j, 1, lhsOffset)] * rhs_ij
-        ri2 += lhs[I(j, 2, lhsOffset)] * rhs_ij
-        ri3 += lhs[I(j, 3, lhsOffset)] * rhs_ij
-      }
-      result[I(i, 0, resultOffset)] = ri0
-      result[I(i, 1, resultOffset)] = ri1
-      result[I(i, 2, resultOffset)] = ri2
-      result[I(i, 3, resultOffset)] = ri3
-    }
-  }
-
-  @Suppress("FunctionName")
-  private fun I(i: Int, j: Int, offset: Int): Int {
-    // #define I(_i, _j) ((_j)+ 4*(_i))
-    return offset + j + 4 * i
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt
deleted file mode 100644
index 403413e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package app.cash.paparazzi.internal
-
-// Sampled from https://cs.android.com/android/platform/superproject/+/master:external/robolectric-shadows/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOpenGLMatrix.java;l=69-121
-object MatrixVectorMultiplicationInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    resultVec: FloatArray,
-    resultVecOffset: Int,
-    lhsMat: FloatArray,
-    lhsMatOffset: Int,
-    rhsVec: FloatArray,
-    rhsVecOffset: Int
-  ) {
-    require(resultVecOffset + 4 <= resultVec.size) { "resultOffset + 4 > result.length" }
-    require(lhsMatOffset + 16 <= lhsMat.size) { "lhsOffset + 16 > lhs.length" }
-    require(rhsVecOffset + 4 <= rhsVec.size) { "rhsOffset + 4 > rhs.length" }
-    val x = rhsVec[rhsVecOffset + 0]
-    val y = rhsVec[rhsVecOffset + 1]
-    val z = rhsVec[rhsVecOffset + 2]
-    val w = rhsVec[rhsVecOffset + 3]
-    resultVec[resultVecOffset + 0] =
-      lhsMat[I(0, 0, lhsMatOffset)] * x + lhsMat[I(1, 0, lhsMatOffset)] * y +
-          lhsMat[I(2, 0, lhsMatOffset)] * z + lhsMat[I(3, 0, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 1] =
-      lhsMat[I(0, 1, lhsMatOffset)] * x + lhsMat[I(1, 1, lhsMatOffset)] * y +
-          lhsMat[I(2, 1, lhsMatOffset)] * z + lhsMat[I(3, 1, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 2] =
-      lhsMat[I(0, 2, lhsMatOffset)] * x + lhsMat[I(1, 2, lhsMatOffset)] * y +
-          lhsMat[I(2, 2, lhsMatOffset)] * z + lhsMat[I(3, 2, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 3] =
-      lhsMat[I(0, 3, lhsMatOffset)] * x + lhsMat[I(1, 3, lhsMatOffset)] * y +
-          lhsMat[I(2, 3, lhsMatOffset)] * z + lhsMat[I(3, 3, lhsMatOffset)] * w
-  }
-
-  @Suppress("FunctionName")
-  private fun I(i: Int, j: Int, offset: Int): Int {
-    // #define I(_i, _j) ((_j)+ 4*(_i))
-    return offset + j + 4 * i
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt
deleted file mode 100644
index 72bcf85..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 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 app.cash.paparazzi.internal
-
-import com.android.ide.common.rendering.api.AssetRepository
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.io.InputStream
-
-internal class PaparazziAssetRepository(private val assetPath: String) : AssetRepository() {
-  @Throws(FileNotFoundException::class)
-  private fun open(path: String): InputStream? {
-    val asset = File(path)
-    return when {
-      asset.isFile -> FileInputStream(asset)
-      else -> null
-    }
-  }
-
-  override fun isSupported(): Boolean = true
-
-  @Throws(IOException::class)
-  override fun openAsset(
-    path: String,
-    mode: Int
-  ): InputStream? = open("$assetPath/$path")
-
-  @Throws(IOException::class)
-  override fun openNonAsset(
-    cookie: Int,
-    path: String,
-    mode: Int
-  ): InputStream? = open(path)
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt
deleted file mode 100644
index 6e2b62e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2014 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import app.cash.paparazzi.internal.parsers.TagSnapshot
-import com.android.ide.common.rendering.api.ActionBarCallback
-import com.android.ide.common.rendering.api.AdapterBinding
-import com.android.ide.common.rendering.api.ILayoutPullParser
-import com.android.ide.common.rendering.api.LayoutlibCallback
-import com.android.ide.common.rendering.api.ResourceNamespace.RES_AUTO
-import com.android.ide.common.rendering.api.ResourceReference
-import com.android.ide.common.rendering.api.ResourceValue
-import com.android.ide.common.rendering.api.SessionParams.Key
-import com.android.layoutlib.bridge.android.RenderParamsFlags
-import com.android.resources.ResourceType
-import com.android.resources.ResourceType.STYLE
-import com.google.common.io.ByteStreams
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.lang.reflect.Modifier
-import org.kxml2.io.KXmlParser
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-
-internal class PaparazziCallback(
-  private val logger: PaparazziLogger,
-  private val resourcePackageNames: List<String>
-) : LayoutlibCallback() {
-  private val projectResources = mutableMapOf<Int, ResourceReference>()
-  private val resources = mutableMapOf<ResourceReference, Int>()
-  private val actionBarCallback = ActionBarCallback()
-  private val aaptDeclaredResources = mutableMapOf<String, TagSnapshot>()
-
-  private var adaptiveIconMaskPath: String? = null
-  private val loadedClasses = mutableMapOf<String, Class<*>>()
-
-  @Throws(ClassNotFoundException::class)
-  fun initResources() {
-    for (rPackageName in resourcePackageNames) {
-      val rClass = Class.forName("$rPackageName.R")
-      for (resourceClass in rClass.declaredClasses) {
-        val resourceType = ResourceType.fromClassName(resourceClass.simpleName) ?: continue
-
-        for (field in resourceClass.declaredFields) {
-          if (!Modifier.isStatic(field.modifiers)) continue
-
-          // May not be final in library projects.
-          val type = field.type
-          try {
-            if (type == Int::class.javaPrimitiveType) {
-              val value = field.get(null) as Int
-              val reference = ResourceReference(RES_AUTO, resourceType, field.name)
-              projectResources[value] = reference
-              resources[reference] = value
-            } else if (type.isArray && type.componentType == Int::class.javaPrimitiveType) {
-              // Ignore.
-            } else {
-              logger.error(null, "Unknown field type in R class: $type")
-            }
-          } catch (e: IllegalAccessException) {
-            logger.error(e, "Malformed R class: %1\$s", "$rPackageName.R")
-          }
-        }
-      }
-    }
-  }
-
-  @Throws(Exception::class)
-  override fun loadView(
-    name: String,
-    constructorSignature: Array<Class<*>>,
-    constructorArgs: Array<Any>
-  ): Any? {
-    val viewClass = Class.forName(name)
-    val viewConstructor = viewClass.getConstructor(*constructorSignature)
-    viewConstructor.isAccessible = true
-    return viewConstructor.newInstance(*constructorArgs)
-  }
-
-  override fun resolveResourceId(id: Int): ResourceReference? = projectResources[id]
-
-  override fun getOrGenerateResourceId(resource: ResourceReference): Int {
-    // Workaround: We load our resource map from fields in R.class, which are named using Java
-    // class conventions.  Therefore, we need to similarly transform style naming conventions
-    // that contain periods (e.g., Widget.AppCompat.TextView) to avoid false lookup misses.
-    // Long-term: Perhaps parse and load resource names from file system directly?
-    val resourceKey =
-      if (resource.resourceType == STYLE) resource.transformStyleResource() else resource
-    return resources[resourceKey] ?: 0
-  }
-
-  override fun getParser(layoutResource: ResourceValue): ILayoutPullParser? {
-    try {
-      val value = layoutResource.value ?: return null
-      if (aaptDeclaredResources.isNotEmpty() && layoutResource.resourceType == ResourceType.AAPT) {
-        val aaptResource = aaptDeclaredResources.getValue(value)
-        return LayoutPullParser.createFromAaptResource(aaptResource)
-      }
-
-      return LayoutPullParser.createFromFile(File(layoutResource.value))
-        .also {
-          // For parser of elements included in this parser, publish any aapt declared values
-          aaptDeclaredResources.putAll(it.getAaptDeclaredAttrs())
-        }
-    } catch (e: FileNotFoundException) {
-      return null
-    }
-  }
-
-  override fun getAdapterItemValue(
-    adapterView: ResourceReference,
-    adapterCookie: Any,
-    itemRef: ResourceReference,
-    fullPosition: Int,
-    positionPerType: Int,
-    fullParentPosition: Int,
-    parentPositionPerType: Int,
-    viewRef: ResourceReference,
-    viewAttribute: ViewAttribute,
-    defaultValue: Any
-  ): Any? = null
-
-  override fun getAdapterBinding(
-    adapterViewRef: ResourceReference,
-    adapterCookie: Any,
-    viewObject: Any
-  ): AdapterBinding? = null
-
-  override fun getActionBarCallback(): ActionBarCallback = actionBarCallback
-
-  override fun createXmlParserForPsiFile(fileName: String): XmlPullParser? =
-    createXmlParserForFile(fileName)
-
-  override fun createXmlParserForFile(fileName: String): XmlPullParser? {
-    try {
-      FileInputStream(fileName).use { fileStream ->
-        // Read data fully to memory to be able to close the file stream.
-        val byteOutputStream = ByteArrayOutputStream()
-        ByteStreams.copy(fileStream, byteOutputStream)
-        val parser = KXmlParser()
-        parser.setInput(ByteArrayInputStream(byteOutputStream.toByteArray()), null)
-        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
-        return parser
-      }
-    } catch (e: IOException) {
-      return null
-    } catch (e: XmlPullParserException) {
-      return null
-    }
-  }
-
-  override fun createXmlParser(): XmlPullParser = KXmlParser()
-
-  @Suppress("UNCHECKED_CAST")
-  override fun <T> getFlag(key: Key<T>?): T? {
-    return when (key) {
-      RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH -> adaptiveIconMaskPath as T?
-      else -> null
-    }
-  }
-
-  fun setAdaptiveIconMaskPath(adaptiveIconMaskPath: String) {
-    this.adaptiveIconMaskPath = adaptiveIconMaskPath
-  }
-
-  override fun findClass(name: String): Class<*> {
-    val clazz = loadedClasses[name]
-    logger.verbose("loadClassA($name)")
-
-    try {
-      if (clazz != null) {
-        return clazz
-      }
-      val clazz2 = Class.forName(name)
-      logger.verbose("loadClassB($name)")
-      loadedClasses[name] = clazz2
-      return clazz2
-    } catch (e: LinkageError) {
-      throw ClassNotFoundException("error loading class $name", e)
-    } catch (e: ExceptionInInitializerError) {
-      throw ClassNotFoundException("error loading class $name", e)
-    } catch (e: ClassNotFoundException) {
-      throw ClassNotFoundException("error loading class $name", e)
-    }
-  }
-
-  private fun ResourceReference.transformStyleResource() =
-    ResourceReference.style(namespace, name.replace('.', '_'))
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt
deleted file mode 100644
index 6739e81..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.TestName
-import com.squareup.moshi.FromJson
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.ToJson
-import com.squareup.moshi.Types
-import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
-import java.util.Date
-
-internal object PaparazziJson {
-  val moshi = Moshi.Builder()
-    .add(Date::class.java, Rfc3339DateJsonAdapter())
-    .add(this)
-    .build()!!
-
-  val listOfShotsAdapter: JsonAdapter<List<Snapshot>> =
-    moshi
-      .adapter<List<Snapshot>>(
-        Types.newParameterizedType(List::class.java, Snapshot::class.java)
-      )
-      .indent("  ")
-
-  val listOfStringsAdapter: JsonAdapter<List<String>> =
-    moshi
-      .adapter<List<String>>(
-        Types.newParameterizedType(List::class.java, String::class.java)
-      )
-      .indent("  ")
-
-  @ToJson
-  fun testNameToJson(testName: TestName): String {
-    return "${testName.packageName}.${testName.className}#${testName.methodName}"
-  }
-
-  @FromJson
-  fun testNameFromJson(json: String): TestName {
-    val regex = Regex("(.*)\\.([^.]*)#([^.]*)")
-    val (packageName, className, methodName) = regex.matchEntire(json)!!.destructured
-    return TestName(packageName, className, methodName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt
deleted file mode 100644
index 01595f7..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.Paparazzi
-import com.android.ide.common.rendering.api.ILayoutLog
-import com.android.utils.ILogger
-import java.io.PrintStream
-import java.io.PrintWriter
-import java.util.logging.Level
-import java.util.logging.Logger
-import java.util.logging.Logger.getLogger
-
-/**
- * This logger delegates to java.util.Logging.
- */
-internal class PaparazziLogger : ILayoutLog, ILogger {
-  private val logger: Logger = getLogger(Paparazzi::class.java.name)
-  private val errors = mutableListOf<Throwable>()
-
-  override fun error(
-    throwable: Throwable?,
-    format: String?,
-    vararg args: Any
-  ) {
-    logger.log(Level.SEVERE, format?.format(args), throwable)
-    if (throwable != null) {
-      errors += throwable
-    }
-  }
-
-  override fun warning(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.WARNING, format, args)
-  }
-
-  override fun info(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.INFO, format, args)
-  }
-
-  override fun verbose(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.FINE, format, args)
-  }
-
-  override fun fidelityWarning(
-    tag: String?,
-    message: String?,
-    throwable: Throwable?,
-    cookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.WARNING, "$tag: $message", throwable)
-  }
-
-  override fun error(
-    tag: String?,
-    message: String?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.SEVERE, "$tag: $message")
-  }
-
-  override fun error(
-    tag: String?,
-    message: String?,
-    throwable: Throwable?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.SEVERE, "$tag: $message", throwable)
-    if (throwable != null) {
-      errors += throwable
-    }
-  }
-
-  override fun warning(
-    tag: String?,
-    message: String?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.WARNING, "$tag: $message")
-  }
-
-  override fun logAndroidFramework(priority: Int, tag: String?, message: String?) {
-    logger.log(Level.INFO, "$tag [$priority]: $message")
-  }
-
-  fun assertNoErrors() {
-    when (errors.size) {
-      0 -> return
-      1 -> throw errors[0]
-      else -> throw MultipleFailuresException(errors)
-    }
-  }
-
-  internal class MultipleFailuresException(private val causes: List<Throwable>) : Exception() {
-    init {
-      require(causes.isNotEmpty()) { "List of Throwables must not be empty" }
-    }
-
-    override val message: String
-      get() = buildString {
-        appendLine(String.format("There were %d errors:", causes.size))
-        causes.forEach { e ->
-          appendLine(String.format("%n  %s: %s", e.javaClass.name, e.message))
-          e.stackTrace.forEach { traceElement ->
-            appendLine("\tat $traceElement")
-          }
-        }
-      }
-
-    override fun printStackTrace() {
-      causes.forEach { e ->
-        e.printStackTrace()
-      }
-    }
-
-    override fun printStackTrace(s: PrintStream) {
-      causes.forEach { e ->
-        e.printStackTrace(s)
-      }
-    }
-
-    override fun printStackTrace(s: PrintWriter) {
-      causes.forEach { e ->
-        e.printStackTrace(s)
-      }
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt
deleted file mode 100644
index cbf05df..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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 app.cash.paparazzi.internal
-
-import com.android.ide.common.rendering.api.RenderSession
-import com.android.ide.common.rendering.api.Result
-import com.android.ide.common.rendering.api.ViewInfo
-import java.awt.image.BufferedImage
-
-internal data class RenderResult(
-  val result: Result,
-  val systemViews: List<ViewInfo>,
-  val rootViews: List<ViewInfo>,
-  val image: BufferedImage
-)
-
-internal fun RenderSession.toResult(): RenderResult {
-  return RenderResult(result, systemRootViews.toList(), rootViews.toList(), image)
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt
deleted file mode 100644
index 569d1cd..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2016 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Environment
-import app.cash.paparazzi.Flags
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.io.FolderWrapper
-import com.android.layoutlib.bridge.Bridge
-import com.android.layoutlib.bridge.android.RenderParamsFlags
-import com.android.layoutlib.bridge.impl.DelegateManager
-import java.awt.image.BufferedImage
-import java.io.Closeable
-import java.io.File
-import java.io.IOException
-import java.util.Locale
-
-/** View rendering. */
-internal class Renderer(
-  private val environment: Environment,
-  private val layoutlibCallback: PaparazziCallback,
-  private val logger: PaparazziLogger,
-  private val maxPercentDifference: Double
-) : Closeable {
-  private var bridge: Bridge? = null
-  private lateinit var sessionParamsBuilder: SessionParamsBuilder
-
-  /** Initialize the bridge and the resource maps. */
-  fun prepare(): SessionParamsBuilder {
-    val platformDataResDir = File("${environment.platformDir}/data/res")
-
-    @Suppress("DEPRECATION")
-    val frameworkResources = com.android.ide.common.resources.deprecated.FrameworkResources(
-      FolderWrapper(platformDataResDir)
-    ).apply {
-      loadResources()
-      loadPublicResources(logger)
-    }
-
-    @Suppress("DEPRECATION")
-    val projectResources = object : com.android.ide.common.resources.deprecated.ResourceRepository(
-      FolderWrapper(environment.resDir),
-      false
-    ) {
-      override fun createResourceItem(
-        name: String
-      ): com.android.ide.common.resources.deprecated.ResourceItem {
-        return com.android.ide.common.resources.deprecated.ResourceItem(name)
-      }
-    }
-    projectResources.loadResources()
-
-    sessionParamsBuilder = SessionParamsBuilder(
-      layoutlibCallback = layoutlibCallback,
-      logger = logger,
-      frameworkResources = frameworkResources,
-      projectResources = projectResources,
-      assetRepository = PaparazziAssetRepository(environment.assetsDir)
-    )
-      .plusFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true)
-      .withTheme("AppTheme", true)
-
-    val platformDataRoot = System.getProperty("paparazzi.platform.data.root")
-      ?: throw RuntimeException("Missing system property for 'paparazzi.platform.data.root'")
-    val platformDataDir = File(platformDataRoot, "data")
-    val fontLocation = File(platformDataDir, "fonts")
-    val nativeLibLocation = File(platformDataDir, getNativeLibDir())
-    val icuLocation = File(platformDataDir, "icu" + File.separator + "icudt70l.dat")
-    val buildProp = File(environment.platformDir, "build.prop")
-    val attrs = File(platformDataResDir, "values" + File.separator + "attrs.xml")
-    val systemProperties = DeviceConfig.loadProperties(buildProp) + mapOf(
-      // We want Choreographer.USE_FRAME_TIME to be false so it uses System_Delegate.nanoTime()
-      "debug.choreographer.frametime" to "false"
-    )
-    bridge = Bridge().apply {
-      check(
-        init(
-          systemProperties,
-          fontLocation,
-          nativeLibLocation.path,
-          icuLocation.path,
-          DeviceConfig.getEnumMap(attrs),
-          logger
-        )
-      ) { "Failed to init Bridge." }
-    }
-    Bridge.getLock()
-      .lock()
-    try {
-      Bridge.setLog(logger)
-    } finally {
-      Bridge.getLock()
-        .unlock()
-    }
-
-    return sessionParamsBuilder
-  }
-
-  private fun getNativeLibDir(): String {
-    val osName = System.getProperty("os.name").lowercase(Locale.US)
-    val osLabel = when {
-      osName.startsWith("windows") -> "win"
-      osName.startsWith("mac") -> {
-        val osArch = System.getProperty("os.arch").lowercase(Locale.US)
-        if (osArch.startsWith("x86")) "mac" else "mac-arm"
-      }
-      else -> "linux"
-    }
-    return "$osLabel/lib64"
-  }
-
-  override fun close() {
-    bridge = null
-
-    Gc.gc()
-
-    dumpDelegates()
-  }
-
-  fun dumpDelegates() {
-    if (System.getProperty(Flags.DEBUG_LINKED_OBJECTS) != null) {
-      println("Objects still linked from the DelegateManager:")
-      DelegateManager.dump(System.out)
-    }
-  }
-
-  fun render(
-    bridge: com.android.ide.common.rendering.api.Bridge,
-    params: SessionParams,
-    frameTimeNanos: Long
-  ): RenderResult {
-    val session = bridge.createSession(params)
-
-    try {
-      if (frameTimeNanos != -1L) {
-        session.setElapsedFrameTimeNanos(frameTimeNanos)
-      }
-
-      if (!session.result.isSuccess) {
-        logger.error(session.result.exception, session.result.errorMessage)
-      } else {
-        // Render the session with a timeout of 50s.
-        val renderResult = session.render(50000)
-        if (!renderResult.isSuccess) {
-          logger.error(session.result.exception, session.result.errorMessage)
-        }
-      }
-
-      return session.toResult()
-    } finally {
-      session.dispose()
-    }
-  }
-
-  /** Compares the golden image with the passed image. */
-  fun verify(
-    goldenImageName: String,
-    image: BufferedImage
-  ) {
-    try {
-      val goldenImagePath = environment.appTestDir + "/golden/" + goldenImageName
-      ImageUtils.requireSimilar(goldenImagePath, image, maxPercentDifference)
-    } catch (e: IOException) {
-      logger.error(e, e.message)
-    }
-  }
-
-  /**
-   * Create a new rendering session and test that rendering the given layout doesn't throw any
-   * exceptions and matches the provided image.
-   *
-   * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
-   * how far in the future is.
-   */
-  @JvmOverloads
-  fun renderAndVerify(
-    sessionParams: SessionParams,
-    goldenFileName: String,
-    frameTimeNanos: Long = -1
-  ): RenderResult {
-    val result = render(bridge!!, sessionParams, frameTimeNanos)
-    verify(goldenFileName, result.image)
-    return result
-  }
-
-  fun createParserFromPath(layoutPath: String): LayoutPullParser =
-    LayoutPullParser.createFromPath("${environment.resDir}/layout/$layoutPath")
-
-  /**
-   * Create a new rendering session and test that rendering the given layout on given device
-   * doesn't throw any exceptions and matches the provided image.
-   */
-  @JvmOverloads
-  fun renderAndVerify(
-    layoutFileName: String,
-    goldenFileName: String,
-    deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5
-  ): RenderResult {
-    val sessionParams = sessionParamsBuilder
-      .copy(
-        layoutPullParser = createParserFromPath(layoutFileName),
-        deviceConfig = deviceConfig
-      )
-      .build()
-    return renderAndVerify(sessionParams, goldenFileName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt
deleted file mode 100644
index a7f14c8..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.content.Context
-import android.graphics.Typeface
-
-object ResourcesInterceptor {
-  @JvmStatic
-  fun intercept(
-    context: Context,
-    resId: Int
-  ): Typeface? {
-    return context.resources.getFont(resId)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt
deleted file mode 100644
index 4726fad..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.os.IBinder
-
-/**
- * The ImeTracing class attempts to initialize its [mService field in its constructor](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/util/imetracing/ImeTracing.java;l=60).
- *
- * Unfortunately, [layoutlib's version of ServiceManager](https://cs.android.com/android/platform/superproject/+/master:frameworks/layoutlib/bridge/src/android/os/ServiceManager.java;l=37)
- * throws an exception immediately.
- *
- * This interceptor overrides ServiceManager.getServiceOrThrow to simply return null instead.
- */
-object ServiceManagerInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun interceptGetServiceOrThrow(@Suppress("UNUSED_PARAMETER") name: String): IBinder? = null
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt
deleted file mode 100644
index ea263b6..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.SdkConstants
-import com.android.ide.common.rendering.api.AssetRepository
-import com.android.ide.common.rendering.api.ResourceNamespace
-import com.android.ide.common.rendering.api.ResourceReference
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.ide.common.rendering.api.SessionParams.Key
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import com.android.ide.common.resources.ResourceResolver
-import com.android.ide.common.resources.ResourceValueMap
-import com.android.layoutlib.bridge.Bridge
-import com.android.resources.LayoutDirection
-import com.android.resources.ResourceType
-
-/** Creates [SessionParams] objects. */
-internal data class SessionParamsBuilder(
-  private val layoutlibCallback: PaparazziCallback,
-  private val logger: PaparazziLogger,
-  @Suppress("DEPRECATION")
-  private val frameworkResources: com.android.ide.common.resources.deprecated.ResourceRepository,
-  private val assetRepository: AssetRepository,
-  @Suppress("DEPRECATION")
-  private val projectResources: com.android.ide.common.resources.deprecated.ResourceRepository,
-  private val deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5,
-  private val renderingMode: RenderingMode = RenderingMode.NORMAL,
-  private val targetSdk: Int = 22,
-  private val flags: Map<Key<*>, Any> = mapOf(),
-  private val themeName: String? = null,
-  private val isProjectTheme: Boolean = false,
-  private val layoutPullParser: LayoutPullParser? = null,
-  private val projectKey: Any? = null,
-  private val minSdk: Int = 0,
-  private val decor: Boolean = true
-) {
-  fun withTheme(
-    themeName: String,
-    isProjectTheme: Boolean
-  ): SessionParamsBuilder {
-    return copy(themeName = themeName, isProjectTheme = isProjectTheme)
-  }
-
-  fun withTheme(themeName: String): SessionParamsBuilder {
-    return when {
-      themeName.startsWith(SdkConstants.PREFIX_ANDROID) -> {
-        withTheme(themeName.substring(SdkConstants.PREFIX_ANDROID.length), false)
-      }
-      else -> withTheme(themeName, true)
-    }
-  }
-
-  fun plusFlag(
-    flag: SessionParams.Key<*>,
-    value: Any
-  ) = copy(flags = flags + (flag to value))
-
-  fun build(): SessionParams {
-    require(themeName != null)
-
-    val folderConfiguration = deviceConfig.folderConfiguration
-
-    @Suppress("DEPRECATION")
-    val resourceResolver = ResourceResolver.create(
-      mapOf<ResourceNamespace, Map<ResourceType, ResourceValueMap>>(
-        ResourceNamespace.ANDROID to frameworkResources.getConfiguredResources(
-          folderConfiguration
-        ),
-        ResourceNamespace.TODO() to projectResources.getConfiguredResources(
-          folderConfiguration
-        )
-      ),
-      ResourceReference(
-        ResourceNamespace.fromBoolean(!isProjectTheme),
-        ResourceType.STYLE,
-        themeName
-      )
-    )
-
-    val result = SessionParams(
-      layoutPullParser, renderingMode, projectKey /* for caching */,
-      deviceConfig.hardwareConfig, resourceResolver, layoutlibCallback, minSdk, targetSdk, logger
-    )
-    result.fontScale = deviceConfig.fontScale
-
-    val localeQualifier = folderConfiguration.localeQualifier
-    val layoutDirectionQualifier = folderConfiguration.layoutDirectionQualifier
-    // https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-main:android/src/com/android/tools/idea/rendering/RenderTask.java;l=645
-    if (
-      LayoutDirection.RTL == layoutDirectionQualifier.value &&
-      !Bridge.isLocaleRtl(localeQualifier.tag)
-    ) {
-      result.locale = "ur"
-    } else {
-      result.locale = localeQualifier.tag
-    }
-    result.setRtlSupport(true)
-
-    for ((key, value) in flags) {
-      @Suppress("UNCHECKED_CAST")
-      result.setFlag(key as Key<Any>, value)
-    }
-    result.setAssetRepository(assetRepository)
-
-    if (!decor) {
-      result.setForceNoDecor()
-    }
-
-    return result
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt
deleted file mode 100644
index 3c62aa8..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-/**
- * Copied from https://cs.android.com/android-studio/platform/tools/adt/idea/+/858f81bb7c350bc7a05daad36edefd21f74c8cef:android/src/com/android/tools/idea/rendering/parsers/AaptAttrParser.java
- *
- * Interface for parsers that support declaration of inlined {@code aapt:attr} attributes
- */
-interface AaptAttrParser {
-  /**
-   * Returns a [Map] that contains all the `aapt:attr` elements declared in this or any
-   * children parsers. This list can be used to resolve `@aapt/_aapt` references into this parser.
-   */
-  fun getAaptDeclaredAttrs(): Map<String, TagSnapshot>
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt
deleted file mode 100644
index 4105eee..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.AAPT_ATTR_PREFIX
-import com.android.SdkConstants.AAPT_PREFIX
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/AaptAttrAttributeSnapshot.java
- *
- * Aapt attributes are attributes that instead of containing a reference, contain the inlined value
- * of the reference. This snapshot will generate a dynamic reference that will be used by the
- * resource resolution to be able to retrieve the inlined value.
- */
-class AaptAttrSnapshot(
-  override val namespace: String,
-  override val prefix: String,
-  override val name: String,
-  val id: String,
-  val bundledTag: TagSnapshot
-) : AttributeSnapshot(namespace, prefix, name, "${AAPT_ATTR_PREFIX}$AAPT_PREFIX$id")
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt
deleted file mode 100644
index 50b658c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/AttributeSnapshot.java
- *
- * A snapshot of an attribute value pulled from an XML resource.
- * Used in conjunction with [TagSnapshot].
- */
-open class AttributeSnapshot(
-  open val namespace: String,
-  open val prefix: String?,
-  open val name: String,
-  open val value: String
-) {
-  override fun toString() = "$name: $value"
-
-  // since data classes can't be subclassed
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (javaClass != other?.javaClass) return false
-
-    other as AttributeSnapshot
-
-    if (namespace != other.namespace) return false
-    if (prefix != other.prefix) return false
-    if (name != other.name) return false
-    if (value != other.value) return false
-
-    return true
-  }
-
-  override fun hashCode(): Int {
-    var result = namespace.hashCode()
-    result = 31 * result + prefix.hashCode()
-    result = 31 * result + name.hashCode()
-    result = 31 * result + value.hashCode()
-    return result
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt
deleted file mode 100644
index 5b709fb..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-import org.kxml2.io.KXmlParser
-import org.xmlpull.v1.XmlPullParserException
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/858f81bb7c350bc7a05daad36edefd21f74c8cef:android/src/com/android/tools/idea/rendering/parsers/LayoutPullParser.java;bpv=0;bpt=0
- *
- * A parser implementation that walks an in-memory XML DOM tree.
- */
-abstract class InMemoryParser : KXmlParser() {
-  abstract fun rootTag(): TagSnapshot
-
-  private val nodeStack = mutableListOf<TagSnapshot>()
-  private var parsingState = START_DOCUMENT
-
-  override fun getAttributeCount(): Int {
-    val tag = getCurrentNode() ?: return 0
-    return tag.attributes.size
-  }
-
-  override fun getAttributeName(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.name
-  }
-
-  override fun getAttributeNamespace(i: Int): String {
-    val attribute = getAttribute(i) ?: return ""
-    return attribute.namespace
-  }
-
-  override fun getAttributePrefix(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.prefix
-  }
-
-  override fun getAttributeValue(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.value
-  }
-
-  override fun getAttributeValue(
-    namespace: String?,
-    name: String?
-  ): String? {
-    val tag = getCurrentNode() ?: return null
-    return tag.attributes.find { it.name == name }?.value
-  }
-
-  override fun getDepth(): Int = nodeStack.size
-
-  override fun getName(): String? {
-    if (parsingState == START_TAG || parsingState == END_TAG) {
-      // Should only be called when START_TAG
-      val currentNode = getCurrentNode()!!
-      return currentNode.name
-    }
-    return null
-  }
-
-  @Throws(XmlPullParserException::class)
-  override fun next(): Int {
-    when (parsingState) {
-      END_DOCUMENT -> throw XmlPullParserException("Nothing after the end")
-      START_DOCUMENT -> onNextFromStartDocument()
-      START_TAG -> onNextFromStartTag()
-      END_TAG -> onNextFromEndTag()
-    }
-    return parsingState
-  }
-
-  private fun getCurrentNode(): TagSnapshot? = nodeStack.lastOrNull()
-
-  private fun getAttribute(i: Int): AttributeSnapshot? {
-    if (parsingState != START_TAG) {
-      throw IndexOutOfBoundsException()
-    }
-    val tag = getCurrentNode() ?: return null
-    return tag.attributes[i]
-  }
-
-  private fun push(node: TagSnapshot) {
-    nodeStack.add(node)
-  }
-
-  private fun pop(): TagSnapshot = nodeStack.removeLast()
-
-  private fun onNextFromStartDocument() {
-    val rootTag = rootTag()
-    @Suppress("SENSELESS_COMPARISON")
-    parsingState = if (rootTag != null) {
-      push(rootTag)
-      START_TAG
-    } else {
-      END_DOCUMENT
-    }
-  }
-
-  private fun onNextFromStartTag() {
-    // get the current node, and look for text or children (children first)
-    // Should only be called when START_TAG
-    val node = getCurrentNode()!!
-    val children = node.children
-    parsingState = if (children.isNotEmpty()) {
-      // move to the new child, and don't change the state.
-      push(children[0])
-
-      // in case the current state is CURRENT_DOC, we set the proper state.
-      START_TAG
-    } else {
-      if (parsingState == START_DOCUMENT) {
-        // this handles the case where there's no node.
-        END_DOCUMENT
-      } else {
-        END_TAG
-      }
-    }
-  }
-
-  private fun onNextFromEndTag() {
-    // look for a sibling. if no sibling, go back to the parent
-    // Should only be called when END_TAG
-    var node = getCurrentNode()!!
-    val sibling = node.next
-    if (sibling != null) {
-      node = sibling
-      // to go to the sibling, we need to remove the current node,
-      pop()
-      // and add its sibling.
-      push(node)
-      parsingState = START_TAG
-    } else {
-      // move back to the parent
-      pop()
-
-      // we have only one element left (myRoot), then we're done with the document.
-      parsingState = if (nodeStack.isEmpty()) {
-        END_DOCUMENT
-      } else {
-        END_TAG
-      }
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt
deleted file mode 100644
index 8f1a94a..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 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 app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.ATTR_IGNORE
-import com.android.SdkConstants.EXPANDABLE_LIST_VIEW
-import com.android.SdkConstants.GRID_VIEW
-import com.android.SdkConstants.LIST_VIEW
-import com.android.SdkConstants.SPINNER
-import com.android.SdkConstants.TOOLS_URI
-import com.android.ide.common.rendering.api.ILayoutPullParser
-import com.android.ide.common.rendering.api.ResourceNamespace
-import java.io.ByteArrayInputStream
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOError
-import java.io.InputStream
-import java.nio.charset.Charset
-import okio.buffer
-import okio.source
-import org.xmlpull.v1.XmlPullParserException
-
-/**
- * A layout parser that holds an in-memory tree of a given resource for subsequent traversal
- * during the inflation process
- */
-internal class LayoutPullParser : InMemoryParser, AaptAttrParser, ILayoutPullParser {
-  private constructor(inputStream: InputStream) : super() {
-    try {
-      val buffer = inputStream.source().buffer()
-
-      setFeature(FEATURE_PROCESS_NAMESPACES, true)
-      setInput(buffer.peek().inputStream(), null)
-
-      // IntelliJ uses XmlFile/PsiFile to parse tag snapshots,
-      // leaving XmlPullParser for Android to parse resources as usual
-      // Here, we use the same XmlPullParser approach for both, which means
-      // we need reinitialize the document stream between the two passes.
-      val resourceParser = ResourceParser(buffer.inputStream())
-      root = resourceParser.createTagSnapshot()
-
-      // Obtain a list of all the aapt declared attributes
-      declaredAaptAttrs = findDeclaredAaptAttrs(root)
-    } catch (e: XmlPullParserException) {
-      throw IOError(e)
-    }
-  }
-
-  private constructor(aaptResource: TagSnapshot) : super() {
-    root = aaptResource
-    declaredAaptAttrs = emptyMap()
-  }
-
-  private val root: TagSnapshot
-  private val declaredAaptAttrs: Map<String, TagSnapshot>
-
-  private var layoutNamespace = ResourceNamespace.RES_AUTO
-
-  override fun rootTag() = root
-
-  @Suppress("SENSELESS_COMPARISON")
-  override fun getViewCookie(): Any? {
-    // TODO: Implement this properly.
-    val name = super.getName() ?: return null
-
-    // Store tools attributes if this looks like a layout we'll need adapter view
-    // bindings for in the LayoutlibCallback.
-    if (LIST_VIEW == name || EXPANDABLE_LIST_VIEW == name || GRID_VIEW == name || SPINNER == name) {
-      var map: MutableMap<String, String>? = null
-      val count = attributeCount
-      for (i in 0 until count) {
-        val namespace = getAttributeNamespace(i)
-        if (namespace != null && namespace == TOOLS_URI) {
-          val attribute = getAttributeName(i)!!
-          if (attribute == ATTR_IGNORE) {
-            continue
-          }
-          if (map == null) {
-            map = HashMap(4)
-          }
-          map[attribute] = getAttributeValue(i)!!
-        }
-      }
-
-      return map
-    }
-
-    return null
-  }
-
-  override fun getLayoutNamespace(): ResourceNamespace = layoutNamespace
-
-  override fun getAaptDeclaredAttrs(): Map<String, TagSnapshot> = declaredAaptAttrs
-
-  fun setLayoutNamespace(layoutNamespace: ResourceNamespace) {
-    this.layoutNamespace = layoutNamespace
-  }
-
-  private fun findDeclaredAaptAttrs(tag: TagSnapshot): Map<String, TagSnapshot> {
-    if (!tag.hasDeclaredAaptAttrs) {
-      // Nor tag or any of the children has any aapt:attr declarations, we can stop here.
-      return emptyMap()
-    }
-
-    return buildMap {
-      tag.attributes
-        .filterIsInstance<AaptAttrSnapshot>()
-        .forEach { attr ->
-          val bundledTag = attr.bundledTag
-          put(attr.id, bundledTag)
-          for (child in bundledTag.children) {
-            putAll(findDeclaredAaptAttrs(child))
-          }
-        }
-      for (child in tag.children) {
-        putAll(findDeclaredAaptAttrs(child))
-      }
-    }
-  }
-
-  companion object {
-    @Throws(FileNotFoundException::class)
-    fun createFromFile(layoutFile: File) = LayoutPullParser(FileInputStream(layoutFile))
-
-    /**
-     * @param layoutPath Must start with '/' and be relative to test resources.
-     */
-    fun createFromPath(layoutPath: String): LayoutPullParser {
-      @Suppress("NAME_SHADOWING") var layoutPath = layoutPath
-      if (layoutPath.startsWith("/")) {
-        layoutPath = layoutPath.substring(1)
-      }
-
-      return LayoutPullParser(
-        LayoutPullParser::class.java.classLoader!!.getResourceAsStream(layoutPath)
-      )
-    }
-
-    fun createFromString(contents: String): LayoutPullParser {
-      return LayoutPullParser(
-        ByteArrayInputStream(contents.toByteArray(Charset.forName("UTF-8")))
-      )
-    }
-
-    fun createFromAaptResource(aaptResource: TagSnapshot): LayoutPullParser {
-      return LayoutPullParser(aaptResource)
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt
deleted file mode 100644
index f90475b..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.AAPT_URI
-import com.android.SdkConstants.TAG_ATTR
-import java.io.InputStream
-import org.kxml2.io.KXmlParser
-
-/**
- * An XML resource parser that creates a tree of [TagSnapshot]s
- */
-class ResourceParser(inputStream: InputStream) : KXmlParser() {
-  init {
-    setFeature(FEATURE_PROCESS_NAMESPACES, true)
-    setInput(inputStream, null)
-
-    require(START_DOCUMENT, null, null)
-    next()
-  }
-
-  fun createTagSnapshot(): TagSnapshot {
-    require(START_TAG, null, null)
-
-    // need to store now, since TagSnapshot is created on end tag after parser mark has moved
-    val tagName = name
-    val tagNamespace = namespace
-    val prefix = prefix
-
-    val attributes = createAttributesForTag()
-
-    var hasDeclaredAaptAttrs = false
-    var last: TagSnapshot? = null
-    val children = mutableListOf<TagSnapshot>()
-    while (eventType != END_DOCUMENT) {
-      when (next()) {
-        START_TAG -> {
-          if (AAPT_URI == namespace) {
-            if (TAG_ATTR == name) {
-              val attrAttribute = createAttrTagSnapshot()
-              if (attrAttribute != null) {
-                attributes += attrAttribute
-                hasDeclaredAaptAttrs = true
-              }
-            }
-            // Since we save the aapt:attr tags as an attribute, we do not save them as a child element. Skip.
-          } else {
-            val child = createTagSnapshot()
-            hasDeclaredAaptAttrs = hasDeclaredAaptAttrs || child.hasDeclaredAaptAttrs
-            children += child
-            if (last != null) {
-              last.next = child
-            }
-            last = child
-          }
-        }
-        END_TAG -> {
-          return TagSnapshot(
-            tagName,
-            tagNamespace,
-            prefix,
-            attributes,
-            children.toList(),
-            hasDeclaredAaptAttrs
-          )
-        }
-      }
-    }
-
-    throw IllegalStateException("We should never reach here")
-  }
-
-  private fun createAttrTagSnapshot(): AaptAttrSnapshot? {
-    require(START_TAG, null, "attr")
-
-    val name = getAttributeValue(null, "name") ?: return null
-    val prefix = findPrefixByQualifiedName(name)
-    val namespace = getNamespace(prefix)
-    val localName = findLocalNameByQualifiedName(name)
-    val id = (++uniqueId).toString()
-
-    var bundleTagSnapshot: TagSnapshot? = null
-    loop@ while (eventType != END_TAG) {
-      when (nextTag()) {
-        START_TAG -> {
-          bundleTagSnapshot = createTagSnapshot()
-        }
-        END_TAG -> {
-          break@loop
-        }
-      }
-    }
-
-    return if (bundleTagSnapshot != null) {
-      // swallow end tag
-      nextTag()
-      require(END_TAG, null, "attr")
-
-      AaptAttrSnapshot(namespace, prefix, localName, id, bundleTagSnapshot)
-    } else {
-      null
-    }
-  }
-
-  private fun findPrefixByQualifiedName(name: String): String {
-    val prefixEnd = name.indexOf(':')
-    return if (prefixEnd > 0) {
-      name.substring(0, prefixEnd)
-    } else ""
-  }
-
-  private fun findLocalNameByQualifiedName(name: String): String {
-    return name.substring(name.indexOf(':') + 1)
-  }
-
-  private fun createAttributesForTag(): MutableList<AttributeSnapshot> {
-    return buildList {
-      for (i in 0 until attributeCount) {
-        add(
-          AttributeSnapshot(
-            getAttributeNamespace(i),
-            getAttributePrefix(i),
-            getAttributeName(i),
-            getAttributeValue(i)
-          )
-        )
-      }
-    }.toMutableList()
-  }
-
-  companion object {
-    private var uniqueId = 0L
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt
deleted file mode 100644
index 19cab20..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/TagSnapshot.java
- *
- * A snapshot of the state of an xml tag.
- *
- * Used by the rendering architecture to be able to hold a consistent view of
- * the layout files across a long rendering operation without holding read locks,
- * as well as to for example let the property sheet evaluate and paint the values
- * of properties as they were at the time of rendering, not as they are at the current
- * instant.
- */
-data class TagSnapshot(
-  val name: String,
-  val namespace: String,
-  val prefix: String?,
-  val attributes: List<AttributeSnapshot>,
-  val children: List<TagSnapshot>,
-  val hasDeclaredAaptAttrs: Boolean = false
-) {
-  var next: TagSnapshot? = null
-
-  @Suppress("unused") // Used for debugging
-  fun printFormatted(): String {
-    indent++
-    val output = """
-      |$name:
-      |${pad(indent + 1)}attributes: ${print(attributes)}
-      |${pad(indent + 1)}children: ${print(children)} 
-    """.trimMargin()
-    indent--
-    return output
-  }
-
-  private fun print(children: List<*>): String {
-    if (children.isEmpty()) return children.toString()
-
-    indent++
-    val output = children.joinToString(
-      prefix = "[\n${pad(indent + 1)}",
-      separator = "\n${pad(indent + 1)}",
-      postfix = "\n${pad(indent)}]"
-    )
-    indent--
-    return output
-  }
-
-  private fun pad(length: Int): String = "  ".repeat(length)
-
-  companion object {
-    var indent = -1
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/resources/index.html b/external/paparazzi/paparazzi/src/main/resources/index.html
deleted file mode 100644
index 327f8b0..0000000
--- a/external/paparazzi/paparazzi/src/main/resources/index.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <style type="text/css">
-    body {
-      background: #212121;
-      text-align: center;
-    }
-
-    .screen {
-      margin: 0.2em;
-      display: inline-block;
-      position: relative;
-      overflow: hidden;
-    }
-
-    .screen img, .screen video {
-      width: 300px;
-    }
-
-    div.overlay__hovered {
-        display: block;
-    }
-
-    .overlay {
-      position: absolute;
-      bottom: 0;
-      left: 0;
-      right: 0;
-      background: rgba(25, 0, 237, .68);
-      color: #fff;
-      opacity: 0;
-      display: none;
-      transition: 150ms;
-
-       -webkit-animation-name: slideIn;
-       -webkit-animation-duration: 0.4s;
-      animation-name: slideIn;
-      animation-duration: 0.4s;
-      animation-fill-mode: forwards;
-    }
-
-    @-webkit-keyframes slideIn {
-      from {bottom: -300px; opacity: 0}
-      to {bottom: 0; opacity: 1}
-    }
-
-    @keyframes slideIn {
-      from {bottom: -300px; opacity: 0}
-      to {bottom: 0; opacity: 1}
-    }
-
-    .test__details {
-      text-align: left;
-      padding-left: 1em;
-    }
-
-    .test__details__name,
-    .test__details__class,
-    .test__details__package {
-      line-height: 0.7em;
-    }
-
-    .test__details__timestamp {
-      margin-top: 2em;
-    }
-
-    .test__details__selector {
-      width: 16px;
-      height: 16px;
-      border-radius: 50%;
-      display: inline-block;
-      border: 2px solid #fff;
-      margin: .1em;
-      margin-bottom: 1.3em;
-      margin-top: 1em;
-    }
-
-    .test__details__selector:hover {
-      background-color: white;
-    }
-    </style>
-</head>
-
-<script src="index.js"></script>
-<script src="paparazzi.js"></script>
-
-<body onload="bootstrap()">
-  <span id="rootContainer"/>
-</body>
-</html>
diff --git a/external/paparazzi/paparazzi/src/main/resources/paparazzi.js b/external/paparazzi/paparazzi/src/main/resources/paparazzi.js
deleted file mode 100644
index 5c12ab1..0000000
--- a/external/paparazzi/paparazzi/src/main/resources/paparazzi.js
+++ /dev/null
@@ -1,222 +0,0 @@
-window.runs = {};
-
-class Run {
-  constructor(id, data) {
-    this.id = id;
-    // TODO(oldergod) which entries are required/optional?
-    this.data = data;
-  }
-}
-
-class Shot {
-  constructor(name, test) {
-    this.name = name;
-    this.test = test;
-
-    [, this.package, this.clazz, this.method] = Shot.TestMethodRegex.exec(test);
-
-    this.runs = [];
-  }
-
-  static get TestMethodRegex() {
-    return /^(.*)\.(.*)#(.*)$/;
-  }
-
-  addRun(runId, file, timestamp) {
-    this.runs.push(
-      {
-        'id': runId,
-        'file': file,
-        'timestamp': timestamp
-      }
-    );
-
-    if (file.endsWith('.png')) {
-      this.img.src = file;
-      this.img.style.display = 'inline';
-      this.video.style.display = 'none';
-    } else {
-      this.video.src = file;
-      this.video.style.display = 'inline';
-      this.img.style.display = 'none';
-    }
-    this.timestampP.innerText = timestamp;
-
-    const circle = document.createElement('div');
-    circle.classList.add('test__details__selector', `run-${runId}`);
-    circle.onmouseover = function (e) {
-      if (file.endsWith('.png')) {
-        this.img.src = file;
-      } else {
-        this.video.src = file;
-      }
-
-      for (let shot of Object.values(paparazziRenderer.shots)) {
-        let found = false;
-        for (let run of shot.runs) {
-          if (runId == run.id) {
-            shot.img.src = run.file;
-            shot.timestampP.innerText = run.timestamp;
-
-            found = true;
-            break;
-          }
-        }
-        shot.img.style.opacity = found ? "1" : "0.3";
-      }
-    }.bind(this);
-    this.overlayDiv.appendChild(circle);
-  }
-
-  removeRun(runId) {
-    const index = this.runs.indexOf((run) => run.id == runId);
-    if (index == -1) return;
-
-    this.runs.splice(index, 1);
-  }
-
-  inflate() {
-    const screenDiv = document.createElement('div');
-    screenDiv.classList.add('screen');
-
-    document.rootContainer.appendChild(screenDiv);
-
-    const img = document.createElement('img');
-    const video = document.createElement('video');
-    video.autoplay = 'autoplay';
-    video.muted = 'muted';
-    video.loop = 'loop';
-
-    const overlayDiv = document.createElement('div');
-    overlayDiv.classList.add('overlay');
-
-    screenDiv.appendChild(img);
-    screenDiv.appendChild(video);
-    screenDiv.appendChild(overlayDiv);
-    screenDiv.onmouseover = function (e) {
-      overlayDiv.classList.add('overlay__hovered');
-    }.bind(this);
-    screenDiv.onmouseout = function (e) {
-      overlayDiv.classList.remove('overlay__hovered');
-    }.bind(this);
-
-    const nameP = document.createElement('p');
-    nameP.classList.add('test__details', 'test__details__name');
-
-    const classP = document.createElement('p');
-    classP.classList.add('test__details', 'test__details__class');
-
-    const packageP = document.createElement('p');
-    packageP.classList.add('test__details', 'test__details__package');
-
-    const timestampP = document.createElement('p');
-    timestampP.classList.add('test__details', 'test__details__timestamp');
-
-    overlayDiv.appendChild(nameP);
-    overlayDiv.appendChild(classP);
-    overlayDiv.appendChild(packageP);
-    overlayDiv.appendChild(timestampP);
-
-    nameP.innerText = this.method;
-    if (this.name !== undefined) {
-      nameP.innerText += ` ${this.name}`;
-    }
-    classP.innerText = this.clazz;
-    packageP.innerText = this.package;
-
-    // hold references to the DOM for later updates
-    this.img = img;
-    this.video = video;
-    this.timestampP = timestampP;
-    this.overlayDiv = overlayDiv;
-  }
-}
-
-class PaparazziRenderer {
-  constructor() {
-    // Used for content comparison for we only re-render the updated ones.
-    this.currentRuns = {};
-    // Used to store runs we know won't be updated anymore.
-    this.lockedRunIds = [];
-    this.shots = {}; // Key is `${test}${name}`, Value is a Shot.
-  }
-
-  start() {
-    this.loadRunScript('index.js');
-    for (let runId of window.all_runs) {
-      this.loadRunScript(`runs/${runId}.js`);
-    }
-    setInterval(this.refresh.bind(this), 100);
-  }
-
-  render(run) {
-    if (this.currentRuns[run.id]
-      && JSON.stringify(this.currentRuns[run.id]) == JSON.stringify(run)) {
-      // This run didn't change.
-      return;
-    }
-    this.currentRuns[run.id] = run;
-    console.log('rendering', run);
-
-    for (let datum of run.data) {
-      const key = `${datum.testName}${datum.name}`;
-      let shot = this.shots[key];
-      if (!shot) {
-        console.log('New shot detected', shot);
-        shot = new Shot(datum.name, datum.testName);
-        this.shots[key] = shot;
-        shot.inflate();
-      } else {
-        //shot.removeRun(run.id);
-      }
-
-      console.log('Adding run to shot', shot);
-      shot.addRun(run.id, datum.file, datum.timestamp);
-
-      // TODO setup listeners for filters/hovering, etc
-    }
-  }
-
-  renderAll() {
-    this.loadRunScript('index.js');
-    for (let runId of window.all_runs) {
-      if (this.lockedRunIds.includes(runId)) {
-        continue;
-      }
-      // The js loading is async so the rendering can happen in the next refresh
-      this.loadRunScript(`runs/${runId}.js`);
-
-      this.render(new Run(runId, window.runs[runId]));
-
-      const lastRunId = window.all_runs[window.all_runs.length - 1];
-      if (runId != lastRunId) {
-        // This run isn't the last run so we know it ain't gonna be updated.
-        this.lockedRunIds.push(runId);
-        delete this.currentRuns[runId];
-      }
-    }
-  }
-
-  refresh() {
-    if (window.all_runs.length == 0) return;
-
-    this.renderAll();
-  }
-
-  loadRunScript(js) {
-    const script = document.createElement('script');
-    script.src = js;
-    script.onload = function () {
-      this.remove();
-    }
-    document.head.appendChild(script);
-  }
-}
-
-const paparazziRenderer = new PaparazziRenderer();
-console.log(paparazziRenderer);
-
-function bootstrap() {
-  document.rootContainer = document.getElementById('rootContainer');
-  paparazziRenderer.start();
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt
deleted file mode 100644
index 80979c7..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import java.awt.image.BufferedImage
-import java.io.File
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.attribute.BasicFileAttributes
-import java.nio.file.attribute.FileTime
-import java.time.Instant
-import java.util.Date
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-
-class HtmlReportWriterTest {
-  @get:Rule
-  val reportRoot: TemporaryFolder = TemporaryFolder()
-
-  @get:Rule
-  val snapshotRoot: TemporaryFolder = TemporaryFolder()
-
-  private val anyImage = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
-  private val anyImageHash = "9069ca78e7450a285173431b3e52c5c25299e473"
-
-  @Test
-  fun happyPath() {
-    val htmlReportWriter = HtmlReportWriter("run_one", reportRoot.root)
-    htmlReportWriter.use {
-      val frameHandler = htmlReportWriter.newFrameHandler(
-        Snapshot(
-          name = "loading",
-          testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings"),
-          timestamp = Instant.parse("2019-03-20T10:27:43Z").toDate(),
-          tags = listOf("redesign")
-        ),
-        1,
-        -1
-      )
-      frameHandler.use {
-        frameHandler.handle(anyImage)
-      }
-    }
-
-    assertThat(File("${reportRoot.root}/index.js")).hasContent(
-      """
-        |window.all_runs = [
-        |  "run_one"
-        |];
-        |
-      """.trimMargin()
-    )
-
-    assertThat(File("${reportRoot.root}/runs/run_one.js")).hasContent(
-      """
-        |window.runs["run_one"] = [
-        |  {
-        |    "name": "loading",
-        |    "testName": "app.cash.paparazzi.CelebrityTest#testSettings",
-        |    "timestamp": "2019-03-20T10:27:43.000Z",
-        |    "tags": [
-        |      "redesign"
-        |    ],
-        |    "file": "images/$anyImageHash.png"
-        |  }
-        |];
-        |
-      """.trimMargin()
-    )
-  }
-
-  @Test
-  fun sanitizeForFilename() {
-    assertThat("0 Dollars".sanitizeForFilename()).isEqualTo("0_dollars")
-    assertThat("`!#$%&*+=|\\'\"<>?/".sanitizeForFilename()).isEqualTo("_________________")
-    assertThat("~@^()[]{}:;,.".sanitizeForFilename()).isEqualTo("~@^()[]{}:;,.")
-  }
-
-  @Test
-  fun noSnapshotOnFailure() {
-    val htmlReportWriter = HtmlReportWriter("run_one", reportRoot.root)
-    htmlReportWriter.use {
-      val frameHandler = htmlReportWriter.newFrameHandler(
-        snapshot = Snapshot(
-          name = "loading",
-          testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings"),
-          timestamp = Instant.parse("2019-03-20T10:27:43Z").toDate()
-        ),
-        frameCount = 4,
-        fps = -1
-      )
-      frameHandler.use {
-        // intentionally empty, to simulate no content written on exception
-      }
-    }
-
-    assertThat(File(reportRoot.root, "images")).isEmptyDirectory
-    assertThat(File(reportRoot.root, "videos")).isEmptyDirectory
-  }
-
-  @Test
-  fun alwaysOverwriteOnRecord() {
-    // set record mode
-    System.setProperty("paparazzi.test.record", "true")
-
-    val htmlReportWriter = HtmlReportWriter("record_run", reportRoot.root, snapshotRoot.root)
-    htmlReportWriter.use {
-      val now = Instant.parse("2021-02-23T10:27:43Z")
-      val snapshot = Snapshot(
-        name = "test",
-        testName = TestName("app.cash.paparazzi", "HomeView", "testSettings"),
-        timestamp = now.toDate()
-      )
-      val file =
-        File("${snapshotRoot.root}/images/app.cash.paparazzi_HomeView_testSettings_test.png")
-      val golden = file.toPath()
-
-      // precondition
-      assertThat(golden).doesNotExist()
-
-      // take 1
-      val frameHandler1 = htmlReportWriter.newFrameHandler(
-        snapshot = snapshot,
-        frameCount = 1,
-        fps = -1
-      )
-      frameHandler1.use { frameHandler1.handle(anyImage) }
-      assertThat(golden).exists()
-      val timeFirstWrite = golden.lastModifiedTime()
-
-      // I know....but guarantees writes won't happen in same tick
-      Thread.sleep(100)
-
-      // take 2
-      val frameHandler2 = htmlReportWriter.newFrameHandler(
-        snapshot = snapshot.copy(timestamp = now.plusSeconds(1).toDate()),
-        frameCount = 1,
-        fps = -1
-      )
-      frameHandler2.use { frameHandler2.handle(anyImage) }
-      assertThat(golden).exists()
-      val timeOverwrite = golden.lastModifiedTime()
-
-      // should always overwrite
-      assertThat(timeOverwrite).isGreaterThan(timeFirstWrite)
-    }
-  }
-
-  private fun Instant.toDate() = Date(toEpochMilli())
-
-  private fun Path.lastModifiedTime(): FileTime {
-    return Files.readAttributes(this, BasicFileAttributes::class.java).lastModifiedTime()
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt
deleted file mode 100644
index 356a6c8..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-import android.animation.AnimationHandler
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.graphics.Canvas
-import android.graphics.Color
-import android.view.Choreographer
-import android.view.Choreographer.CALLBACK_ANIMATION
-import android.view.View
-import android.view.animation.LinearInterpolator
-import android.widget.Button
-import com.android.internal.lang.System_Delegate
-import java.util.concurrent.TimeUnit
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-class PaparazziTest {
-  @get:Rule
-  val paparazzi = Paparazzi()
-
-  @Ignore("b/245941625")
-  @Test
-  fun drawCalls() {
-    val log = mutableListOf<String>()
-
-    val view = object : View(paparazzi.context) {
-      override fun onDraw(canvas: Canvas) {
-        log += "onDraw time=$time"
-      }
-    }
-
-    paparazzi.snapshot(view)
-
-    assertThat(log).containsExactly("onDraw time=0")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun resetsAnimationHandler() {
-    assertThat(AnimationHandler.sAnimatorHandler.get()).isNull()
-
-    // Why Button?  Because it sets a StateListAnimator on window attach
-    // See https://github.com/cashapp/paparazzi/pull/319
-    paparazzi.snapshot(Button(paparazzi.context))
-
-    assertThat(AnimationHandler.sAnimatorHandler.get()).isNull()
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun animationEvents() {
-    val log = mutableListOf<String>()
-
-    val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
-    animator.addListener(object : AnimatorListenerAdapter() {
-      override fun onAnimationStart(animation: Animator) {
-        log += "onAnimationStart time=$time animationElapsed=${animator.animatedValue}"
-      }
-
-      override fun onAnimationEnd(animation: Animator) {
-        log += "onAnimationEnd time=$time animationElapsed=${animator.animatedValue}"
-      }
-    })
-
-    val view = object : View(paparazzi.context) {
-      override fun onDraw(canvas: Canvas) {
-        log += "onDraw time=$time animationElapsed=${animator.animatedValue}"
-      }
-    }
-
-    animator.addUpdateListener {
-      log += "onAnimationUpdate time=$time animationElapsed=${animator.animatedValue}"
-
-      val colorComponent = it.animatedFraction
-      view.setBackgroundColor(Color.argb(1f, colorComponent, colorComponent, colorComponent))
-    }
-
-    animator.startDelay = 2_000L
-    animator.duration = 1_000L
-    animator.interpolator = LinearInterpolator()
-    animator.start()
-
-    paparazzi.gif(view, start = 1_000L, end = 4_000L, fps = 4)
-
-    assertThat(log).containsExactly(
-      "onDraw time=1000 animationElapsed=0.0",
-      "onAnimationStart time=2000 animationElapsed=0.0",
-      "onAnimationUpdate time=2000 animationElapsed=0.0",
-      "onDraw time=2000 animationElapsed=0.0",
-      "onAnimationUpdate time=2250 animationElapsed=0.25",
-      "onDraw time=2250 animationElapsed=0.25",
-      "onAnimationUpdate time=2500 animationElapsed=0.5",
-      "onDraw time=2500 animationElapsed=0.5",
-      "onAnimationUpdate time=2750 animationElapsed=0.75",
-      "onDraw time=2750 animationElapsed=0.75",
-      "onAnimationUpdate time=3000 animationElapsed=1.0",
-      "onAnimationEnd time=3000 animationElapsed=1.0",
-      "onDraw time=3000 animationElapsed=1.0"
-    )
-  }
-
-  @Test
-  @Ignore
-  fun frameCallbacksExecutedAfterLayout() {
-    val log = mutableListOf<String>()
-
-    val view = object : View(paparazzi.context) {
-      override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        Choreographer.getInstance()
-          .postCallback(
-            CALLBACK_ANIMATION,
-            { log += "view width=$width height=$height" },
-            false
-          )
-      }
-    }
-
-    paparazzi.snapshot(view)
-
-    assertThat(log).containsExactly("view width=1080 height=1776")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun throwsRenderingExceptions() {
-    val view = object : View(paparazzi.context) {
-      override fun onAttachedToWindow() {
-        throw Throwable("Oops")
-      }
-    }
-
-    val thrown = try {
-      paparazzi.snapshot(view)
-      false
-    } catch (exception: Throwable) {
-      true
-    }
-
-    assertThat(thrown).isTrue
-  }
-
-  private val time: Long
-    get() {
-      return TimeUnit.NANOSECONDS.toMillis(System_Delegate.nanoTime() - Paparazzi.TIME_OFFSET_NANOS)
-    }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt
deleted file mode 100644
index 5827e33..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi
-
-/** Simulate an empty R.java for this package. */
-class R
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt
deleted file mode 100644
index d774099..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-package app.cash.paparazzi
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.internal.ImageUtils
-import com.android.ide.common.rendering.api.SessionParams
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.Description
-
-class RenderingModeTest {
-
-  @Ignore("b/245941625")
-  @Test
-  fun `shrinks to wrap view`() {
-    Paparazzi(
-      snapshotHandler = TestSnapshotVerifier(),
-      deviceConfig = DeviceConfig.NEXUS_5.copy(
-        softButtons = false
-      ),
-      renderingMode = SessionParams.RenderingMode.SHRINK
-    ).runTest("shrinks to wrap view") {
-      val view = buildView(
-        context,
-        ViewGroup.LayoutParams(
-          ViewGroup.LayoutParams.WRAP_CONTENT,
-          ViewGroup.LayoutParams.WRAP_CONTENT
-        )
-      )
-      snapshot(view, "rendering-mode-shrink")
-    }
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `renders full device with RenderingMode NORMAL`() {
-    Paparazzi(
-      snapshotHandler = TestSnapshotVerifier(true),
-      deviceConfig = DeviceConfig.NEXUS_5.copy(
-        softButtons = false
-      ),
-      renderingMode = SessionParams.RenderingMode.NORMAL
-    ).runTest("renders full device with RenderingMode NORMAL") {
-      val view = buildView(
-        context,
-        ViewGroup.LayoutParams(
-          ViewGroup.LayoutParams.WRAP_CONTENT,
-          ViewGroup.LayoutParams.WRAP_CONTENT
-        )
-      )
-      snapshot(view, "rendering-mode-normal")
-    }
-  }
-
-  private fun Paparazzi.runTest(name: String, body: Paparazzi.() -> Unit) {
-    try {
-      prepare(Description.createTestDescription(this@RenderingModeTest::class.java, name))
-      body()
-    } finally {
-      close()
-    }
-  }
-
-  private fun buildView(
-    context: Context,
-    rootLayoutParams: ViewGroup.LayoutParams? = ViewGroup.LayoutParams(
-      ViewGroup.LayoutParams.MATCH_PARENT,
-      ViewGroup.LayoutParams.MATCH_PARENT
-    )
-  ) =
-    LinearLayout(context).apply {
-      orientation = LinearLayout.VERTICAL
-      rootLayoutParams?.let { layoutParams = it }
-      addView(
-        TextView(context).apply {
-          id = 1
-          text = "Text View Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 2
-          layoutParams = LinearLayout.LayoutParams(100, 100)
-          contentDescription = "Content Description Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 3
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          contentDescription = "Margin Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 4
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          foreground = GradientDrawable(
-            GradientDrawable.Orientation.TL_BR,
-            intArrayOf(Color.YELLOW, Color.BLUE)
-          ).apply {
-            shape = GradientDrawable.OVAL
-          }
-          contentDescription = "Foreground Drawable"
-        }
-      )
-
-      addView(
-        Button(context).apply {
-          id = 5
-          layoutParams = LinearLayout.LayoutParams(
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          ).apply {
-            gravity = Gravity.CENTER
-          }
-          text = "Button Sample"
-        }
-      )
-    }
-}
-
-private class TestSnapshotVerifier(val writeImages: Boolean = false) : SnapshotHandler {
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): SnapshotHandler.FrameHandler {
-    return object : SnapshotHandler.FrameHandler {
-      override fun handle(image: BufferedImage) {
-        val expected = File("src/test/resources/${snapshot.name}.png")
-        if (writeImages) {
-          ImageIO.write(image, "png", expected)
-        } else {
-          ImageUtils.assertImageSimilar(
-            relativePath = expected.path,
-            image = image,
-            goldenImage = ImageIO.read(expected),
-            maxPercentDifferent = 0.1
-          )
-        }
-      }
-
-      override fun close() = Unit
-    }
-  }
-
-  override fun close() = Unit
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt
deleted file mode 100644
index b9afe6e..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package app.cash.paparazzi.accessibility
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.GradientDrawable.OVAL
-import android.graphics.drawable.GradientDrawable.Orientation.TL_BR
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Paparazzi
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.SnapshotHandler
-import app.cash.paparazzi.internal.ImageUtils
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-class AccessibilityRenderExtensionTest {
-  private val snapshotHandler = TestSnapshotVerifier()
-
-  @get:Rule
-  val paparazzi = Paparazzi(
-    deviceConfig = DeviceConfig.NEXUS_5.copy(
-      // Needed to render accessibility content next to main content.
-      screenWidth = DeviceConfig.NEXUS_5.screenWidth * 2,
-      softButtons = false
-    ),
-    snapshotHandler = snapshotHandler,
-    renderExtensions = setOf(AccessibilityRenderExtension())
-  )
-
-  @Ignore("b/245941625")
-  @Test
-  fun `verify baseline`() {
-    val view = buildView(paparazzi.context)
-    paparazzi.snapshot(view, name = "accessibility")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `test without layout params set`() {
-    val view = buildView(paparazzi.context, null)
-    paparazzi.snapshot(view, name = "without-layout-params")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `verify changing view hierarchy order doesn't change accessibility colors`() {
-    val view = buildView(paparazzi.context).apply {
-      addView(
-        View(context).apply { contentDescription = "Empty View" },
-        0,
-        LinearLayout.LayoutParams(0, 0)
-      )
-    }
-    paparazzi.snapshot(view, name = "accessibility-new-view")
-  }
-
-  private fun buildView(
-    context: Context,
-    rootLayoutParams: ViewGroup.LayoutParams? = ViewGroup.LayoutParams(
-      ViewGroup.LayoutParams.MATCH_PARENT,
-      ViewGroup.LayoutParams.MATCH_PARENT
-    )
-  ) =
-    LinearLayout(context).apply {
-      orientation = LinearLayout.VERTICAL
-      rootLayoutParams?.let { layoutParams = it }
-      addView(
-        TextView(context).apply {
-          id = 1
-          text = "Text View Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 2
-          layoutParams = LinearLayout.LayoutParams(100, 100)
-          contentDescription = "Content Description Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 3
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          contentDescription = "Margin Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 4
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          foreground = GradientDrawable(TL_BR, intArrayOf(Color.YELLOW, Color.BLUE)).apply {
-            shape = OVAL
-          }
-          contentDescription = "Foreground Drawable"
-        }
-      )
-
-      addView(
-        Button(context).apply {
-          id = 5
-          layoutParams = LinearLayout.LayoutParams(
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          ).apply {
-            gravity = Gravity.CENTER
-          }
-          text = "Button Sample"
-        }
-      )
-    }
-
-  private class TestSnapshotVerifier : SnapshotHandler {
-    override fun newFrameHandler(
-      snapshot: Snapshot,
-      frameCount: Int,
-      fps: Int
-    ): SnapshotHandler.FrameHandler {
-      return object : SnapshotHandler.FrameHandler {
-        override fun handle(image: BufferedImage) {
-          val expected = File("src/test/resources/${snapshot.name}.png")
-          ImageUtils.assertImageSimilar(
-            relativePath = expected.path,
-            image = image,
-            goldenImage = ImageIO.read(expected),
-            maxPercentDifferent = 0.1
-          )
-        }
-
-        override fun close() = Unit
-      }
-    }
-
-    override fun close() = Unit
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt
deleted file mode 100644
index 9752037..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal
-
-import app.cash.paparazzi.TestName
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-
-class PaparazziJsonTest {
-  @Test
-  fun testName() {
-    val adapter = PaparazziJson.moshi.adapter(TestName::class.java)
-    val testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings")
-    val json = "\"app.cash.paparazzi.CelebrityTest#testSettings\""
-    assertThat(adapter.toJson(testName)).isEqualTo(json)
-    assertThat(adapter.fromJson(json)).isEqualTo(testName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt
deleted file mode 100644
index 89d7823..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.internal.PaparazziLogger.MultipleFailuresException
-import java.io.FileNotFoundException
-import org.assertj.core.api.Assertions.assertThat
-import org.assertj.core.api.Assertions.fail
-import org.junit.Test
-
-class PaparazziLoggerTest {
-  @Test
-  fun testNoErrors() {
-    val logger = PaparazziLogger()
-
-    try {
-      logger.assertNoErrors()
-    } catch (ignored: Exception) {
-      fail("Did not expect exception to be thrown: $ignored")
-    }
-  }
-
-  @Test
-  fun testSingleError() {
-    val logger = PaparazziLogger()
-    logger.error(FileNotFoundException("error1"), null)
-
-    try {
-      logger.assertNoErrors()
-      fail("Expected exception to be thrown")
-    } catch (ignored: Exception) {
-      assertThat(ignored).isInstanceOf(FileNotFoundException::class.java)
-    }
-  }
-
-  @Test
-  fun testMultipleErrors() {
-    val logger = PaparazziLogger()
-    logger.error(FileNotFoundException("error1"), null)
-    logger.error("tag", null, IllegalStateException("error2"), null, null)
-
-    try {
-      logger.assertNoErrors()
-      fail("Expected exceptions to be thrown")
-    } catch (ignored: Exception) {
-      assertThat(ignored).isInstanceOf(MultipleFailuresException::class.java)
-      assertThat(ignored.message).contains("There were 2 errors:")
-      assertThat(ignored.message).contains("java.io.FileNotFoundException: error1")
-      assertThat(ignored.message).contains("java.lang.IllegalStateException: error2")
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt
deleted file mode 100644
index 9a032e3..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package app.cash.paparazzi.internal.parsers
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-import org.xmlpull.v1.XmlPullParserException
-
-class InMemoryParserTest {
-  @Test
-  fun parse() {
-    val root = parseResourceTree("plus_sign.xml")
-    val parser = RealInMemoryParser(root)
-
-    assertThat(parser.name).isNull() // START_DOCUMENT
-    assertThat(parser.depth).isEqualTo(0)
-
-    parser.next() // START_TAG = "vector"
-
-    assertThat(parser.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(1)
-    assertThat(parser.attributeCount).isEqualTo(4)
-
-    assertThat(parser.getAttributeName(0)).isEqualTo("height")
-    assertThat(parser.getAttributeName(1)).isEqualTo("viewportHeight")
-    assertThat(parser.getAttributeName(2)).isEqualTo("viewportWidth")
-    assertThat(parser.getAttributeName(3)).isEqualTo("width")
-
-    assertThat(parser.getAttributeNamespace(0)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(1)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(2)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(3)).isEqualTo(ANDROID_NAMESPACE)
-
-    assertThat(parser.getAttributePrefix(0)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(1)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(2)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(3)).isEqualTo(ANDROID_PREFIX)
-
-    assertThat(parser.getAttributeValue(0)).isEqualTo("24dp")
-    assertThat(parser.getAttributeValue(1)).isEqualTo("40")
-    assertThat(parser.getAttributeValue(2)).isEqualTo("40")
-    assertThat(parser.getAttributeValue(3)).isEqualTo("24dp")
-
-    parser.next() // START_TAG = "path"
-
-    assertThat(parser.name).isEqualTo(PATH_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(2)
-    assertThat(parser.attributeCount).isEqualTo(2)
-
-    assertThat(parser.getAttributeName(0)).isEqualTo(FILL_COLOR_ATTR_NAME)
-    assertThat(parser.getAttributeName(1)).isEqualTo(PATH_DATA_ATTR_NAME)
-
-    assertThat(parser.getAttributeNamespace(0)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(1)).isEqualTo(ANDROID_NAMESPACE)
-
-    assertThat(parser.getAttributePrefix(0)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(1)).isEqualTo(ANDROID_PREFIX)
-
-    assertThat(parser.getAttributeValue(0)).isEqualTo("#999999")
-    assertThat(parser.getAttributeValue(1)).isNotNull // pathData
-
-    parser.next() // END_TAG = "path"
-
-    assertThat(parser.name).isEqualTo(PATH_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(2)
-
-    parser.next() // END_TAG = "vector"
-
-    assertThat(parser.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(1)
-
-    parser.next() // END_DOCUMENT
-    assertThat(parser.name).isNull() // START_DOCUMENT
-    assertThat(parser.depth).isEqualTo(0)
-
-    try {
-      parser.next()
-    } catch (expected: XmlPullParserException) {
-    }
-  }
-
-  private fun parseResourceTree(resourceId: String): TagSnapshot {
-    val resourceInputStream = javaClass.classLoader.getResourceAsStream(resourceId)!!
-    return ResourceParser(resourceInputStream).createTagSnapshot()
-  }
-
-  class RealInMemoryParser(private val root: TagSnapshot) : InMemoryParser() {
-    override fun rootTag(): TagSnapshot = root
-  }
-
-  companion object {
-    const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
-    const val ANDROID_PREFIX = "android"
-
-    const val VECTOR_TAG_NAME = "vector"
-    const val PATH_TAG_NAME = "path"
-    const val PATH_DATA_ATTR_NAME = "pathData"
-    const val FILL_COLOR_ATTR_NAME = "fillColor"
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt
deleted file mode 100644
index 67402f9..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * 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 app.cash.paparazzi.internal.parsers
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-
-class ResourceParserTest {
-  @Test
-  fun parseResource() {
-    val root = parseResourceTree("plus_sign.xml")
-    assertThat(root.namespace).isEmpty()
-    assertThat(root.prefix).isNull()
-    assertThat(root.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(root.hasDeclaredAaptAttrs).isEqualTo(false)
-    assertThat(root.next).isNull()
-    assertThat(root.attributes).containsExactly(
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "height", "24dp"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "viewportHeight", "40"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "viewportWidth", "40"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "width", "24dp")
-    )
-
-    val pathElement = root.children.single()
-    with(pathElement) {
-      assertThat(namespace).isEmpty()
-      assertThat(prefix).isNull()
-      assertThat(name).isEqualTo(PATH_TAG_NAME)
-      assertThat(hasDeclaredAaptAttrs).isEqualTo(false)
-      assertThat(next).isNull()
-    }
-
-    val pathAttributes = pathElement.attributes
-    assertThat(pathAttributes).hasSize(2)
-    assertThat(pathAttributes[0]).isEqualTo(
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, FILL_COLOR_ATTR_NAME, "#999999")
-    )
-
-    with(pathAttributes[1]) {
-      assertThat(namespace).isEqualTo(ANDROID_NAMESPACE)
-      assertThat(prefix).isEqualTo(ANDROID_PREFIX)
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(value).isNotEmpty // don't care about pathData precision
-    }
-  }
-
-  @Test
-  fun parseAaptAttrTags() {
-    // Since #parseResource covers the basics, this test can be more targeted
-
-    val root = parseResourceTree("card_chip.xml")
-    assertThat(root.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(root.hasDeclaredAaptAttrs).isEqualTo(true)
-    assertThat(root.next).isNull()
-
-    assertThat(root.children).hasSize(2)
-
-    val outerPathElement = root.children[0]
-    assertThat(outerPathElement.hasDeclaredAaptAttrs).isEqualTo(false)
-
-    val groupElement = root.children[1]
-    assertThat(groupElement.hasDeclaredAaptAttrs).isEqualTo(true)
-
-    assertThat(outerPathElement.next).isEqualTo(groupElement)
-
-    val clipPathElement = groupElement.children[0]
-    assertThat(clipPathElement.hasDeclaredAaptAttrs).isEqualTo(false)
-
-    val innerPathElement1 = groupElement.children[1]
-    assertThat(innerPathElement1.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement1.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement1.attributes[1] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("1")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt1")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-
-    val innerPathElement2 = groupElement.children[2]
-    assertThat(innerPathElement2.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement2.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement2.attributes[1] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("2")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt2")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-
-    val innerPathElement3 = groupElement.children[3]
-    assertThat(innerPathElement3.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement3.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement3.attributes[1]) {
-      assertThat(name).isEqualTo(FILL_TYPE_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement3.attributes[2] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("3")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt3")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-  }
-
-  private fun parseResourceTree(resourceId: String): TagSnapshot {
-    val resourceInputStream = javaClass.classLoader.getResourceAsStream(resourceId)!!
-    return ResourceParser(resourceInputStream).createTagSnapshot()
-  }
-
-  companion object {
-    const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
-    const val ANDROID_PREFIX = "android"
-
-    const val VECTOR_TAG_NAME = "vector"
-    const val PATH_TAG_NAME = "path"
-    const val PATH_DATA_ATTR_NAME = "pathData"
-    const val FILL_COLOR_ATTR_NAME = "fillColor"
-    const val FILL_TYPE_ATTR_NAME = "fillType"
-    const val GRADIENT_TAG_NAME = "gradient"
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png b/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png
deleted file mode 100644
index 57f25b5a..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png
+++ /dev/null
Binary files differ
diff --git a/external/paparazzi/paparazzi/src/test/resources/accessibility.png b/external/paparazzi/paparazzi/src/test/resources/accessibility.png
deleted file mode 100644
index d7f3a16..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/accessibility.png
+++ /dev/null
Binary files differ
diff --git a/external/paparazzi/paparazzi/src/test/resources/card_chip.xml b/external/paparazzi/paparazzi/src/test/resources/card_chip.xml
deleted file mode 100644
index a9cb398..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/card_chip.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--Copyright Square, Inc.-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="42dp" android:height="34dp" android:viewportWidth="42" android:viewportHeight="34">
-    <path android:pathData="M36.795 0H5.205C2.333 0 0 2.384 0 5.318v22.374c0 2.934 2.333 5.318 5.205 5.318h31.59c2.872 0 5.205-2.384 5.205-5.318V5.318C41.955 2.384 39.622 0 36.795 0z" android:fillColor="#666666"/>
-    <group>
-        <clip-path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z"/>
-        <path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="3.4928" android:startX="1.38832" android:endY="46.3417" android:endX="68.7929" android:type="linear">
-                    <item android:offset="0" android:color="#FFFFFFFF"/>
-                    <item android:offset="0.4081" android:color="#FFC7B299"/>
-                    <item android:offset="1" android:color="#FF998675"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-        <path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="31.3798" android:startX="42.1542" android:endY="-9.12049" android:endX="13.8976" android:type="linear">
-                    <item android:offset="0" android:color="#FFF1EAE2"/>
-                    <item android:offset="0.149949" android:color="#FFF3ECE3"/>
-                    <item android:offset="0.319891" android:color="#FFF4EDE4"/>
-                    <item android:offset="0.509302" android:color="#FFEFE6DB"/>
-                    <item android:offset="0.707905" android:color="#FFECE4DB"/>
-                    <item android:offset="1" android:color="#FFF1F1F1"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-        <path android:pathData="M1.25 0.5C1.08 0.775 1.166 1 1.441 1S2.08 0.775 2.25 0.5C2.42 0.225 2.334 0 2.059 0S1.42 0.225 1.25 0.5zM24 1.03c0 0.565 0.225 0.89 0.5 0.72 0.275-0.17 0.5-0.633 0.5-1.03C25 0.324 24.775 0 24.5 0S24 0.464 24 1.03zm4-0.78c0 0.138 0.113 0.25 0.25 0.25s0.25-0.112 0.25-0.25S28.387 0 28.25 0 28 0.113 28 0.25zm-8.181 2C19.661 2.663 19.749 3 20.016 3 20.282 3 20.5 2.663 20.5 2.25S20.412 1.5 20.303 1.5c-0.108 0-0.326 0.337-0.484 0.75zM24 2.75C24 2.888 24.113 3 24.25 3s0.25-0.112 0.25-0.25-0.113-0.25-0.25-0.25S24 2.612 24 2.75zm-4.76 1.78c-0.45 1.42 0.05 1.796 0.721 0.543 0.326-0.609 0.357-1.127 0.077-1.3-0.256-0.158-0.615 0.182-0.797 0.757zM22.83 4c-0.977 3.02-0.495 6.254 0.669 4.5 0.274-0.412 0.448-1.088 0.387-1.5-0.06-0.412 0.046-0.998 0.236-1.301 0.191-0.303 0.033-0.978-0.351-1.5C23.19 3.412 23.03 3.378 22.829 4zm2.895 1.875C24.304 9.252 23.56 14.5 24.5 14.5c0.275 0 0.5-0.575 0.5-1.278C25 11.64 26.04 7.34 26.504 7c0.188-0.138 0.493-0.869 0.678-1.625 0.501-2.046-0.536-1.69-1.457 0.5zM32 4.75C32 4.888 32.112 5 32.25 5s0.25-0.112 0.25-0.25-0.112-0.25-0.25-0.25S32 4.612 32 4.75zM0.508 5.875c0.004 0.206 0.217 0.706 0.473 1.11 0.576 0.911 1.251 0.341 0.846-0.714-0.298-0.779-1.333-1.09-1.32-0.396zM13 6.029c0 0.292 0.225 0.391 0.5 0.221S14 5.842 14 5.72c0-0.12-0.225-0.22-0.5-0.22S13 5.739 13 6.03zm20.54 0.173c-0.415 0.5-0.424 0.753-0.031 0.885C34.584 7.444 29.094 22 27.884 22c-0.223 0-1.383-0.9-2.576-2-3.16-2.913-3.467-2.449-0.421 0.636l2.603 2.635-1.295 1.398c-1.703 1.835-1.092 2.398 0.719 0.663 1.881-1.802 5.678-9.57 7.284-14.905C34.914 8.05 35.5 5.97 35.5 5.804c0-0.58-1.382-0.298-1.96 0.399zM2.876 6.33c0.344 0.139 0.906 0.139 1.25 0S4.188 6.079 3.5 6.079 2.531 6.193 2.875 6.331zM5 6.5C5 6.775 5.225 7 5.5 7S6 6.775 6 6.5 5.775 6 5.5 6 5 6.225 5 6.5zm10.5 0.25C15.5 6.888 15.613 7 15.75 7S16 6.888 16 6.75 15.887 6.5 15.75 6.5 15.5 6.612 15.5 6.75zM13 7.25c0 0.138 0.113 0.25 0.25 0.25s0.25-0.112 0.25-0.25S13.387 7 13.25 7 13 7.112 13 7.25zM8.5 8.146C6.987 8.373 5.02 8.447 4.128 8.311 3.098 8.155 2.245 8.327 1.79 8.783 1.395 9.177 0.83 9.5 0.535 9.5 0.241 9.5 0 10.063 0 10.75 0 11.943 0.136 12 2.966 12c2.761 0 3.044 0.11 4.096 1.587 0.656 0.922 1.216 1.329 1.336 0.971 0.112-0.338-0.191-1.053-0.674-1.587-0.484-0.534-0.788-0.983-0.676-0.998 0.111-0.015 1.954-0.21 4.096-0.436 2.142-0.224 3.995-0.51 4.12-0.634 0.124-0.124-0.05-0.81-0.387-1.524-0.723-1.532-2.324-1.841-6.377-1.233zM16 7.75C16 7.888 16.113 8 16.25 8s0.25-0.112 0.25-0.25-0.113-0.25-0.25-0.25S16 7.612 16 7.75zm12.278 2.778c-1.78 3.966-1.386 4.36 0.472 0.472 0.785-1.643 1.308-3.108 1.162-3.255-0.146-0.146-0.882 1.106-1.634 2.783zM13.334 8.834c-0.184 0.183-0.484 0.183-0.668 0C12.483 8.65 12.633 8.5 13 8.5s0.517 0.15 0.334 0.334zm4.968 0.541c-1.22 4.022-1.857 8.726-1.305 9.62 0.555 0.898 1.384-2.032 1.761-6.225 0.172-1.915 0.423-3.658 0.557-3.875 0.134-0.217 0.021-0.395-0.252-0.395-0.272 0-0.615 0.394-0.761 0.875zM4.5 9.25c0 0.137-0.112 0.25-0.25 0.25S4 9.387 4 9.25 4.112 9 4.25 9 4.5 9.113 4.5 9.25zm2 0c0 0.137-0.112 0.25-0.25 0.25S6 9.387 6 9.25 6.112 9 6.25 9 6.5 9.113 6.5 9.25zm4.375 0.108c-0.756 0.114-1.993 0.114-2.75 0C7.369 9.243 7.987 9.15 9.5 9.15c1.512 0 2.132 0.093 1.375 0.208zM22.08 11.93c-0.698 2.33-0.737 3.226-0.123 2.847 0.494-0.305 1.282-4.204 0.923-4.564-0.121-0.12-0.481 0.652-0.8 1.717zM3.5 13.5c0 0.367 0.15 0.517 0.333 0.334 0.183-0.184 0.183-0.484 0-0.668C3.65 12.983 3.5 13.133 3.5 13.5zm1-0.25c0 0.137 0.112 0.25 0.25 0.25S5 13.387 5 13.25 4.888 13 4.75 13 4.5 13.113 4.5 13.25zm28.334 2.584c-0.184 0.183-0.334 0.565-0.334 0.85 0 0.313 0.233 0.283 0.592-0.076 0.325-0.325 0.475-0.708 0.333-0.85-0.142-0.142-0.408-0.108-0.591 0.076zM15.5 16.25c0 0.137 0.113 0.25 0.25 0.25S16 16.387 16 16.25 15.887 16 15.75 16s-0.25 0.113-0.25 0.25zm3.959 2.375c-0.023 0.344-0.137 1.2-0.254 1.902-0.156 0.938 0.188 1.662 1.291 2.719 1.452 1.391 2.13 3.2 0.919 2.452-0.399-0.247-0.479-0.085-0.25 0.511 0.184 0.48 0.335 1.305 0.335 1.832 0 0.527 0.242 0.959 0.538 0.959 0.296 0 0.497-0.619 0.447-1.375-0.139-2.099-0.066-2.203 1.091-1.584 0.928 0.497 1.031 0.46 0.75-0.274C24.146 25.301 24 24.824 24 24.71c0-0.115-0.281-0.212-0.625-0.215-0.343-0.003-1.336-0.736-2.204-1.631-1.391-1.431-1.5-1.736-0.91-2.543 0.683-0.934 0.495-2.32-0.314-2.32-0.246 0-0.465 0.282-0.488 0.625zM6.75 21c-0.17 0.275-0.07 0.5 0.22 0.5 0.292 0 0.53-0.225 0.53-0.5s-0.099-0.5-0.22-0.5c-0.122 0-0.36 0.225-0.53 0.5zm9 0.09c-0.825 0.245-2.328 0.325-3.339 0.177-1.258-0.183-2.075-0.032-2.586 0.48-0.412 0.411-1.159 0.823-1.661 0.916C5.802 23.1 2.816 24.16 3.378 24.36c0.345 0.124 0.501 0.431 0.345 0.683C3.569 25.294 4.13 25.5 4.971 25.5 5.81 25.5 6.5 25.725 6.5 26s-0.956 0.503-2.125 0.508C2.819 26.514 2.45 26.645 3 27c0.413 0.267 1.699 0.488 2.86 0.492 1.7 0.006 2.05 0.158 1.81 0.788-0.165 0.428-0.6 0.663-0.968 0.521-0.369-0.14-0.803-0.041-0.965 0.222C5.574 29.285 5.932 29.5 6.532 29.5c0.856 0 0.97 0.145 0.534 0.671-0.715 0.861-0.279 1.291 0.977 0.963 0.696-0.183 0.898-0.534 0.688-1.196C8.528 29.304 8.676 29 9.186 29c0.415 0 0.892-0.223 1.06-0.494 0.175-0.284-0.026-0.367-0.471-0.197-1.216 0.467-0.908-0.593 0.349-1.199l1.125-0.543-1.125-0.033C8.85 26.496 8.62 25.753 9.75 25.319c0.412-0.158 0.75-0.621 0.75-1.03 0-0.658 2.703-1.778 4.31-1.786 0.585-0.003 1.464 4.422 1.266 6.372C16.014 29.494 16.197 30 16.482 30 16.791 30 17 28.576 17 26.47c0-2.225-0.184-3.415-0.5-3.22-0.786 0.485-0.585-0.328 0.33-1.338 1.061-1.173 0.792-1.377-1.08-0.822zm25.285 0.994c-0.019 0.459-0.171 1.19-0.338 1.625-0.2 0.519-0.028 0.791 0.5 0.791 0.875 0 0.97-0.698 0.303-2.25-0.343-0.799-0.437-0.832-0.465-0.166zM10.5 22.25c0 0.137-0.113 0.25-0.25 0.25S10 22.387 10 22.25 10.113 22 10.25 22s0.25 0.113 0.25 0.25zM39 23.5c0 0.366 0.15 0.517 0.334 0.334 0.182-0.184 0.182-0.484 0-0.668C39.15 22.983 39 23.134 39 23.5zM8.634 24.328c-0.338 0.137-1.013 0.146-1.5 0.018-0.486-0.127-0.209-0.238 0.616-0.248 0.825-0.01 1.223 0.093 0.884 0.23zM5 24.75C5 24.887 4.888 25 4.75 25S4.5 24.887 4.5 24.75s0.112-0.25 0.25-0.25S5 24.613 5 24.75zm26.5 0.75c0 0.275 0.238 0.5 0.53 0.5 0.29 0 0.39-0.225 0.22-0.5S31.842 25 31.72 25c-0.121 0-0.22 0.225-0.22 0.5zm6.822 0.54c-0.857 0.826-0.87 1.012-0.164 2.427C38.58 29.311 38.74 30 38.517 30c-0.223 0-0.375 0.394-0.337 0.875 0.048 0.605-0.355 0.924-1.305 1.033-0.86 0.099-1.375-0.079-1.375-0.475 0-0.465-0.148-0.485-0.556-0.076C33.995 32.303 34.96 33 37.225 33c1.465 0 2.26-0.243 2.455-0.75 0.158-0.413 0.547-0.75 0.864-0.75 0.416 0 0.404 0.21-0.045 0.75-0.52 0.627-0.448 0.75 0.439 0.75C41.717 33 42 32.689 42 31.834c0-1.512-0.486-1.968-1.341-1.258-0.852 0.706-1.284 0.331-0.867-0.755C39.965 29.37 40.532 29 41.053 29c1.19 0 1.244-0.85 0.072-1.155-0.834-0.218-0.834-0.232 0-0.287C42.063 27.496 42.429 25 41.5 25c-0.275 0-0.5 0.225-0.5 0.5 0 0.609-1.35 0.67-1.584 0.073-0.091-0.236-0.583-0.026-1.094 0.466zM8 25.72c0 0.122-0.225 0.36-0.5 0.53C7.225 26.42 7 26.32 7 26.03c0-0.292 0.225-0.53 0.5-0.53S8 25.599 8 25.72zm15 1.03c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25-0.113-0.25-0.25-0.25S23 26.613 23 26.75zM1.5 27.5c0 0.366 0.15 0.517 0.333 0.334 0.184-0.184 0.184-0.484 0-0.668C1.65 26.983 1.5 27.134 1.5 27.5zm23.532 0.29c0.325 0.39 0.913 0.71 1.308 0.71 1.006 0 0.39-0.823-0.861-1.15-0.85-0.223-0.93-0.144-0.447 0.44zm-10.28 1.335c-0.18 0.894-0.429 1.992-0.554 2.441-0.126 0.448 0.06 0.926 0.412 1.06 0.465 0.178 0.667-0.492 0.736-2.44 0.11-3.047-0.114-3.446-0.594-1.061zm8.341 0.282c-0.078 1.37 0.023 1.65 0.393 1.093 0.603-0.907 0.664-2.349 0.114-2.688-0.22-0.136-0.448 0.582-0.507 1.595zM24.5 29.25c0 0.137 0.113 0.25 0.25 0.25S25 29.387 25 29.25 24.887 29 24.75 29s-0.25 0.113-0.25 0.25zm1.166 0.084c0.184 0.183 0.484 0.183 0.668 0C26.517 29.15 26.366 29 26 29s-0.517 0.15-0.334 0.334zM28 29.75c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25-0.113-0.25-0.25-0.25S28 29.613 28 29.75zM1 30.5c0 0.366 0.15 0.517 0.333 0.334 0.183-0.184 0.183-0.484 0-0.668C1.15 29.983 1 30.134 1 30.5zm28-0.25c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25S29.387 30 29.25 30 29 30.113 29 30.25zm-7.5 1.5c0 0.137 0.113 0.25 0.25 0.25S22 31.887 22 31.75s-0.113-0.25-0.25-0.25-0.25 0.113-0.25 0.25zm-14 0.75c0.982 0.635 2.5 0.635 2.5 0 0-0.275-0.731-0.496-1.625-0.492C7.142 32.013 6.931 32.132 7.5 32.5zm17.5 0c0.97 0.627 1.5 0.627 1.5 0 0-0.275-0.506-0.496-1.125-0.492-0.91 0.006-0.982 0.1-0.375 0.492z" android:fillType="evenOdd">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="1.5" android:startX="32.5" android:endY="37.0292" android:endX="-0.969948" android:type="linear">
-                    <item android:offset="0" android:color="#FFECE3DA"/>
-                    <item android:offset="0.208782" android:color="#FFEBE2D6"/>
-                    <item android:offset="1" android:color="#FFEBE2D9"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-    </group>
-</vector>
diff --git a/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml b/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml
deleted file mode 100644
index 15567605..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="40" android:viewportWidth="40" android:width="24dp">
-    <path android:fillColor="#999999" android:pathData="M21.5 14c0-0.828-0.672-1.5-1.5-1.5s-1.5 0.672-1.5 1.5v4.5H14c-0.828 0-1.5 0.672-1.5 1.5s0.672 1.5 1.5 1.5h4.5V26c0 0.828 0.672 1.5 1.5 1.5s1.5-0.672 1.5-1.5v-4.5H26c0.828 0 1.5-0.672 1.5-1.5s-0.672-1.5-1.5-1.5h-4.5V14z"/>
-</vector>
diff --git a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png b/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png
deleted file mode 100644
index e69de29..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png
+++ /dev/null
diff --git a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png b/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png
deleted file mode 100644
index e69de29..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png
+++ /dev/null
diff --git a/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png b/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png
deleted file mode 100644
index e3da5cb..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png
+++ /dev/null
Binary files differ
diff --git a/fragment/fragment-compose/api/1.7.0-beta01.txt b/fragment/fragment-compose/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..6c501a4
--- /dev/null
+++ b/fragment/fragment-compose/api/1.7.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.fragment.compose {
+
+  public final class FragmentKt {
+    method public static androidx.compose.ui.platform.ComposeView content(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+}
+
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/fragment/fragment-compose/api/res-1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-common/api/res-1.0.0-beta01.txt
copy to fragment/fragment-compose/api/res-1.7.0-beta01.txt
diff --git a/fragment/fragment-compose/api/restricted_1.7.0-beta01.txt b/fragment/fragment-compose/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..6c501a4
--- /dev/null
+++ b/fragment/fragment-compose/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.fragment.compose {
+
+  public final class FragmentKt {
+    method public static androidx.compose.ui.platform.ComposeView content(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+}
+
diff --git a/fragment/fragment-ktx/api/1.7.0-beta01.txt b/fragment/fragment-ktx/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..94e8371
--- /dev/null
+++ b/fragment/fragment-ktx/api/1.7.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public final class FragmentKt {
+    method public static void clearFragmentResult(androidx.fragment.app.Fragment, String requestKey);
+    method public static void clearFragmentResultListener(androidx.fragment.app.Fragment, String requestKey);
+    method public static void setFragmentResult(androidx.fragment.app.Fragment, String requestKey, android.os.Bundle result);
+    method public static void setFragmentResultListener(androidx.fragment.app.Fragment, String requestKey, kotlin.jvm.functions.Function2<? super java.lang.String,? super android.os.Bundle,kotlin.Unit> listener);
+  }
+
+  public final class FragmentManagerKt {
+    method public static inline void commit(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @MainThread public static inline void commitNow(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, optional boolean now, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+  }
+
+  public final class FragmentTransactionKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
+  }
+
+  public final class FragmentViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras> extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+  public final class ViewKt {
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+  }
+
+}
+
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/fragment/fragment-ktx/api/res-1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-common/api/res-1.0.0-beta01.txt
copy to fragment/fragment-ktx/api/res-1.7.0-beta01.txt
diff --git a/fragment/fragment-ktx/api/restricted_1.7.0-beta01.txt b/fragment/fragment-ktx/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..94e8371
--- /dev/null
+++ b/fragment/fragment-ktx/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public final class FragmentKt {
+    method public static void clearFragmentResult(androidx.fragment.app.Fragment, String requestKey);
+    method public static void clearFragmentResultListener(androidx.fragment.app.Fragment, String requestKey);
+    method public static void setFragmentResult(androidx.fragment.app.Fragment, String requestKey, android.os.Bundle result);
+    method public static void setFragmentResultListener(androidx.fragment.app.Fragment, String requestKey, kotlin.jvm.functions.Function2<? super java.lang.String,? super android.os.Bundle,kotlin.Unit> listener);
+  }
+
+  public final class FragmentManagerKt {
+    method public static inline void commit(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @MainThread public static inline void commitNow(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, optional boolean now, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+  }
+
+  public final class FragmentTransactionKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
+  }
+
+  public final class FragmentViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras> extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+  public final class ViewKt {
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+  }
+
+}
+
diff --git a/media2/media2-exoplayer/api/1.1.0-beta01.txt b/fragment/fragment-testing-manifest/api/1.7.0-beta01.txt
similarity index 100%
rename from media2/media2-exoplayer/api/1.1.0-beta01.txt
rename to fragment/fragment-testing-manifest/api/1.7.0-beta01.txt
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/fragment/fragment-testing-manifest/api/res-1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-common/api/res-1.0.0-beta01.txt
copy to fragment/fragment-testing-manifest/api/res-1.7.0-beta01.txt
diff --git a/media2/media2-exoplayer/api/1.1.0-beta01.txt b/fragment/fragment-testing-manifest/api/restricted_1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-exoplayer/api/1.1.0-beta01.txt
copy to fragment/fragment-testing-manifest/api/restricted_1.7.0-beta01.txt
diff --git a/fragment/fragment-testing/api/1.7.0-beta01.txt b/fragment/fragment-testing/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..81734a0
--- /dev/null
+++ b/fragment/fragment-testing/api/1.7.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.fragment.app.testing {
+
+  public final class FragmentScenario<F extends androidx.fragment.app.Fragment> implements java.io.Closeable {
+    method public void close();
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State newState);
+    method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F> action);
+    method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+    field public static final androidx.fragment.app.testing.FragmentScenario.Companion Companion;
+  }
+
+  public static final class FragmentScenario.Companion {
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+  }
+
+  public static fun interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+    method public void perform(F fragment);
+  }
+
+  public final class FragmentScenarioKt {
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment, T> T withFragment(androidx.fragment.app.testing.FragmentScenario<F>, kotlin.jvm.functions.Function1<? super F,? extends T> block);
+  }
+
+}
+
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/fragment/fragment-testing/api/res-1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-common/api/res-1.0.0-beta01.txt
copy to fragment/fragment-testing/api/res-1.7.0-beta01.txt
diff --git a/fragment/fragment-testing/api/restricted_1.7.0-beta01.txt b/fragment/fragment-testing/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..81734a0
--- /dev/null
+++ b/fragment/fragment-testing/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.fragment.app.testing {
+
+  public final class FragmentScenario<F extends androidx.fragment.app.Fragment> implements java.io.Closeable {
+    method public void close();
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State newState);
+    method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F> action);
+    method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+    field public static final androidx.fragment.app.testing.FragmentScenario.Companion Companion;
+  }
+
+  public static final class FragmentScenario.Companion {
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+  }
+
+  public static fun interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+    method public void perform(F fragment);
+  }
+
+  public final class FragmentScenarioKt {
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method @Deprecated public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment, T> T withFragment(androidx.fragment.app.testing.FragmentScenario<F>, kotlin.jvm.functions.Function1<? super F,? extends T> block);
+  }
+
+}
+
diff --git a/fragment/fragment/api/1.7.0-beta01.txt b/fragment/fragment/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..3c4835a
--- /dev/null
+++ b/fragment/fragment/api/1.7.0-beta01.txt
@@ -0,0 +1,560 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+    ctor public DialogFragment();
+    ctor public DialogFragment(@LayoutRes int);
+    method public void dismiss();
+    method public void dismissAllowingStateLoss();
+    method @MainThread public void dismissNow();
+    method public android.app.Dialog? getDialog();
+    method public boolean getShowsDialog();
+    method @StyleRes public int getTheme();
+    method public boolean isCancelable();
+    method public void onCancel(android.content.DialogInterface);
+    method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
+    method @CallSuper public void onDismiss(android.content.DialogInterface);
+    method public final androidx.activity.ComponentDialog requireComponentDialog();
+    method public final android.app.Dialog requireDialog();
+    method public void setCancelable(boolean);
+    method public void setShowsDialog(boolean);
+    method public void setStyle(int, @StyleRes int);
+    method public void show(androidx.fragment.app.FragmentManager, String?);
+    method public int show(androidx.fragment.app.FragmentTransaction, String?);
+    method public void showNow(androidx.fragment.app.FragmentManager, String?);
+    field public static final int STYLE_NORMAL = 0; // 0x0
+    field public static final int STYLE_NO_FRAME = 2; // 0x2
+    field public static final int STYLE_NO_INPUT = 3; // 0x3
+    field public static final int STYLE_NO_TITLE = 1; // 0x1
+  }
+
+  public class Fragment implements androidx.activity.result.ActivityResultCaller android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+    ctor public Fragment();
+    ctor @ContentView public Fragment(@LayoutRes int);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public final boolean equals(Object?);
+    method public final androidx.fragment.app.FragmentActivity? getActivity();
+    method public boolean getAllowEnterTransitionOverlap();
+    method public boolean getAllowReturnTransitionOverlap();
+    method public final android.os.Bundle? getArguments();
+    method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+    method public android.content.Context? getContext();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public Object? getEnterTransition();
+    method public Object? getExitTransition();
+    method @Deprecated public final androidx.fragment.app.FragmentManager? getFragmentManager();
+    method public final Object? getHost();
+    method public final int getId();
+    method public final android.view.LayoutInflater getLayoutInflater();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+    method public final androidx.fragment.app.Fragment? getParentFragment();
+    method public final androidx.fragment.app.FragmentManager getParentFragmentManager();
+    method public Object? getReenterTransition();
+    method public final android.content.res.Resources getResources();
+    method @Deprecated public final boolean getRetainInstance();
+    method public Object? getReturnTransition();
+    method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public Object? getSharedElementEnterTransition();
+    method public Object? getSharedElementReturnTransition();
+    method public final String getString(@StringRes int);
+    method public final String getString(@StringRes int, java.lang.Object!...?);
+    method public final String? getTag();
+    method @Deprecated public final androidx.fragment.app.Fragment? getTargetFragment();
+    method @Deprecated public final int getTargetRequestCode();
+    method public final CharSequence getText(@StringRes int);
+    method @Deprecated public boolean getUserVisibleHint();
+    method public android.view.View? getView();
+    method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+    method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner!> getViewLifecycleOwnerLiveData();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    method public final int hashCode();
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public final boolean isAdded();
+    method public final boolean isDetached();
+    method public final boolean isHidden();
+    method public final boolean isInLayout();
+    method public final boolean isRemoving();
+    method public final boolean isResumed();
+    method public final boolean isStateSaved();
+    method public final boolean isVisible();
+    method @Deprecated @CallSuper @MainThread public void onActivityCreated(android.os.Bundle?);
+    method @Deprecated public void onActivityResult(int, int, android.content.Intent?);
+    method @Deprecated @CallSuper @MainThread public void onAttach(android.app.Activity);
+    method @CallSuper @MainThread public void onAttach(android.content.Context);
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+    method @MainThread public boolean onContextItemSelected(android.view.MenuItem);
+    method @CallSuper @MainThread public void onCreate(android.os.Bundle?);
+    method @MainThread public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+    method @MainThread public android.animation.Animator? onCreateAnimator(int, boolean, int);
+    method @MainThread public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+    method @Deprecated @MainThread public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method @MainThread public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+    method @CallSuper @MainThread public void onDestroy();
+    method @Deprecated @MainThread public void onDestroyOptionsMenu();
+    method @CallSuper @MainThread public void onDestroyView();
+    method @CallSuper @MainThread public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+    method @MainThread public void onHiddenChanged(boolean);
+    method @Deprecated @CallSuper @UiThread public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @UiThread public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @MainThread public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
+    method @Deprecated @MainThread public boolean onOptionsItemSelected(android.view.MenuItem);
+    method @Deprecated @MainThread public void onOptionsMenuClosed(android.view.Menu);
+    method @CallSuper @MainThread public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
+    method @Deprecated @MainThread public void onPrepareOptionsMenu(android.view.Menu);
+    method @MainThread public void onPrimaryNavigationFragmentChanged(boolean);
+    method @Deprecated public void onRequestPermissionsResult(int, String![], int[]);
+    method @CallSuper @MainThread public void onResume();
+    method @MainThread public void onSaveInstanceState(android.os.Bundle);
+    method @CallSuper @MainThread public void onStart();
+    method @CallSuper @MainThread public void onStop();
+    method @MainThread public void onViewCreated(android.view.View, android.os.Bundle?);
+    method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
+    method public void postponeEnterTransition();
+    method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method public void registerForContextMenu(android.view.View);
+    method @Deprecated public final void requestPermissions(String![], int);
+    method public final androidx.fragment.app.FragmentActivity requireActivity();
+    method public final android.os.Bundle requireArguments();
+    method public final android.content.Context requireContext();
+    method @Deprecated public final androidx.fragment.app.FragmentManager requireFragmentManager();
+    method public final Object requireHost();
+    method public final androidx.fragment.app.Fragment requireParentFragment();
+    method public final android.view.View requireView();
+    method public void setAllowEnterTransitionOverlap(boolean);
+    method public void setAllowReturnTransitionOverlap(boolean);
+    method public void setArguments(android.os.Bundle?);
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setEnterTransition(Object?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitTransition(Object?);
+    method @Deprecated public void setHasOptionsMenu(boolean);
+    method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+    method public void setMenuVisibility(boolean);
+    method public void setReenterTransition(Object?);
+    method @Deprecated public void setRetainInstance(boolean);
+    method public void setReturnTransition(Object?);
+    method public void setSharedElementEnterTransition(Object?);
+    method public void setSharedElementReturnTransition(Object?);
+    method @Deprecated public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+    method @Deprecated public void setUserVisibleHint(boolean);
+    method public boolean shouldShowRequestPermissionRationale(String);
+    method public void startActivity(android.content.Intent);
+    method public void startActivity(android.content.Intent, android.os.Bundle?);
+    method @Deprecated public void startActivityForResult(android.content.Intent, int);
+    method @Deprecated public void startActivityForResult(android.content.Intent, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
+    method public void unregisterForContextMenu(android.view.View);
+  }
+
+  public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+    ctor public Fragment.InstantiationException(String, Exception?);
+  }
+
+  public static class Fragment.SavedState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState!> CREATOR;
+  }
+
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.lifecycle.LifecycleOwner {
+    ctor public FragmentActivity();
+    ctor @ContentView public FragmentActivity(@LayoutRes int);
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method protected void onResumeFragments();
+    method public void onStateNotSaved();
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void supportFinishAfterTransition();
+    method @Deprecated public void supportInvalidateOptionsMenu();
+    method public void supportPostponeEnterTransition();
+    method public void supportStartPostponedEnterTransition();
+    method @Deprecated public final void validateRequestPermissionsRequestCode(int);
+  }
+
+  public abstract class FragmentContainer {
+    ctor public FragmentContainer();
+    method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public abstract android.view.View? onFindViewById(@IdRes int);
+    method public abstract boolean onHasView();
+  }
+
+  public final class FragmentContainerView extends android.widget.FrameLayout {
+    ctor public FragmentContainerView(android.content.Context context);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs, optional int defStyleAttr);
+    method public <F extends androidx.fragment.app.Fragment> F getFragment();
+  }
+
+  public class FragmentController {
+    method public void attachHost(androidx.fragment.app.Fragment?);
+    method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+    method public void dispatchActivityCreated();
+    method @Deprecated public void dispatchConfigurationChanged(android.content.res.Configuration);
+    method public boolean dispatchContextItemSelected(android.view.MenuItem);
+    method public void dispatchCreate();
+    method @Deprecated public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method public void dispatchDestroy();
+    method public void dispatchDestroyView();
+    method @Deprecated public void dispatchLowMemory();
+    method @Deprecated public void dispatchMultiWindowModeChanged(boolean);
+    method @Deprecated public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+    method @Deprecated public void dispatchOptionsMenuClosed(android.view.Menu);
+    method public void dispatchPause();
+    method @Deprecated public void dispatchPictureInPictureModeChanged(boolean);
+    method @Deprecated public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+    method @Deprecated public void dispatchReallyStop();
+    method public void dispatchResume();
+    method public void dispatchStart();
+    method public void dispatchStop();
+    method @Deprecated public void doLoaderDestroy();
+    method @Deprecated public void doLoaderRetain();
+    method @Deprecated public void doLoaderStart();
+    method @Deprecated public void doLoaderStop(boolean);
+    method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public boolean execPendingActions();
+    method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+    method public java.util.List<androidx.fragment.app.Fragment!> getActiveFragments(java.util.List<androidx.fragment.app.Fragment!>!);
+    method public int getActiveFragmentsCount();
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+    method public void noteStateNotSaved();
+    method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+    method @Deprecated public void reportLoaderStart();
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment!>?);
+    method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>!);
+    method @Deprecated public void restoreSaveState(android.os.Parcelable?);
+    method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>? retainLoaderNonConfig();
+    method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+    method @Deprecated public java.util.List<androidx.fragment.app.Fragment!>? retainNonConfig();
+    method @Deprecated public android.os.Parcelable? saveAllState();
+  }
+
+  public class FragmentFactory {
+    ctor public FragmentFactory();
+    method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+    method public static Class<? extends androidx.fragment.app.Fragment!> loadFragmentClass(ClassLoader, String);
+  }
+
+  public abstract class FragmentHostCallback<H> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context context, android.os.Handler handler, int windowAnimations);
+    method public void onDump(String prefix, java.io.FileDescriptor? fd, java.io.PrintWriter writer, String[]? args);
+    method public android.view.View? onFindViewById(int id);
+    method public abstract H onGetHost();
+    method public android.view.LayoutInflater onGetLayoutInflater();
+    method public int onGetWindowAnimations();
+    method public boolean onHasView();
+    method public boolean onHasWindowAnimations();
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment fragment, String[] permissions, int requestCode);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String permission);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode, android.os.Bundle? options);
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment fragment, android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
+    method public void onSupportInvalidateOptionsMenu();
+  }
+
+  public abstract class FragmentManager implements androidx.fragment.app.FragmentResultOwner {
+    ctor public FragmentManager();
+    method public void addFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public androidx.fragment.app.FragmentTransaction beginTransaction();
+    method public void clearBackStack(String);
+    method public final void clearFragmentResult(String);
+    method public final void clearFragmentResultListener(String);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method @Deprecated public static void enableDebugLogging(boolean);
+    method @SuppressCompatibility @androidx.fragment.app.PredictiveBackControl public static void enablePredictiveBack(boolean);
+    method @MainThread public boolean executePendingTransactions();
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+    method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+    method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
+    method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+    method public int getBackStackEntryCount();
+    method public androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+    method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+    method public java.util.List<androidx.fragment.app.Fragment!> getFragments();
+    method public androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy? getStrictModePolicy();
+    method public boolean isDestroyed();
+    method public boolean isStateSaved();
+    method public void popBackStack();
+    method public void popBackStack(int, int);
+    method public void popBackStack(String?, int);
+    method @MainThread public boolean popBackStackImmediate();
+    method public boolean popBackStackImmediate(int, int);
+    method @MainThread public boolean popBackStackImmediate(String?, int);
+    method public void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+    method public void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+    method public void removeFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public void restoreBackStack(String);
+    method public void saveBackStack(String);
+    method public androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+    method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+    method public final void setFragmentResult(String, android.os.Bundle);
+    method public final void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+    method public void setStrictModePolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy?);
+    method public void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+    field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+  }
+
+  public static interface FragmentManager.BackStackEntry {
+    method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+    method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+    method @Deprecated public CharSequence? getBreadCrumbTitle();
+    method @Deprecated @StringRes public int getBreadCrumbTitleRes();
+    method public int getId();
+    method public String? getName();
+  }
+
+  public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+    ctor public FragmentManager.FragmentLifecycleCallbacks();
+    method @Deprecated public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+    method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+    method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  public static interface FragmentManager.OnBackStackChangedListener {
+    method @MainThread public default void onBackStackChangeCancelled();
+    method @MainThread public default void onBackStackChangeCommitted(androidx.fragment.app.Fragment, boolean);
+    method @MainThread public default void onBackStackChangeProgressed(androidx.activity.BackEventCompat);
+    method @MainThread public default void onBackStackChangeStarted(androidx.fragment.app.Fragment, boolean);
+    method @MainThread public void onBackStackChanged();
+  }
+
+  @Deprecated public class FragmentManagerNonConfig {
+  }
+
+  public interface FragmentOnAttachListener {
+    method @MainThread public void onAttachFragment(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  @Deprecated public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public long getItemId(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  public interface FragmentResultListener {
+    method public void onFragmentResult(String, android.os.Bundle);
+  }
+
+  public interface FragmentResultOwner {
+    method public void clearFragmentResult(String);
+    method public void clearFragmentResultListener(String);
+    method public void setFragmentResult(String, android.os.Bundle);
+    method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+  }
+
+  @Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  }
+
+  public abstract class FragmentTransaction {
+    ctor @Deprecated public FragmentTransaction();
+    method public androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+    method public androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+    method public androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+    method public abstract int commit();
+    method public abstract int commitAllowingStateLoss();
+    method @MainThread public abstract void commitNow();
+    method @MainThread public abstract void commitNowAllowingStateLoss();
+    method public androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+    method public androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+    method public boolean isAddToBackStackAllowed();
+    method public boolean isEmpty();
+    method public androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
+    method public androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+    method public androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+    method public androidx.fragment.app.FragmentTransaction setTransition(int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+    method public androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+    field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+    field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+    field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+    field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE = 8197; // 0x2005
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN = 4100; // 0x1004
+    field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+    field public static final int TRANSIT_NONE = 0; // 0x0
+    field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+  }
+
+  public class ListFragment extends androidx.fragment.app.Fragment {
+    ctor public ListFragment();
+    method public android.widget.ListAdapter? getListAdapter();
+    method public android.widget.ListView getListView();
+    method public long getSelectedItemId();
+    method public int getSelectedItemPosition();
+    method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+    method public final android.widget.ListAdapter requireListAdapter();
+    method public void setEmptyText(CharSequence?);
+    method public void setListAdapter(android.widget.ListAdapter?);
+    method public void setListShown(boolean);
+    method public void setListShownNoAnimation(boolean);
+    method public void setSelection(int);
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public @interface PredictiveBackControl {
+  }
+
+}
+
+package androidx.fragment.app.strictmode {
+
+  public final class FragmentReuseViolation extends androidx.fragment.app.strictmode.Violation {
+    method public String getPreviousFragmentId();
+    property public final String previousFragmentId;
+  }
+
+  public final class FragmentStrictMode {
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @VisibleForTesting public void onPolicyViolation(androidx.fragment.app.strictmode.Violation violation);
+    method public void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy);
+    property public final androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy;
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode INSTANCE;
+  }
+
+  public static fun interface FragmentStrictMode.OnViolationListener {
+    method public void onViolation(androidx.fragment.app.strictmode.Violation violation);
+  }
+
+  public static final class FragmentStrictMode.Policy {
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode.Policy LAX;
+  }
+
+  public static final class FragmentStrictMode.Policy.Builder {
+    ctor public FragmentStrictMode.Policy.Builder();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(Class<? extends androidx.fragment.app.Fragment> fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(String fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentReuse();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentTagUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectRetainInstanceUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongNestedHierarchy();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
+  }
+
+  public final class FragmentTagUsageViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup? getParentContainer();
+    property public final android.view.ViewGroup? parentContainer;
+  }
+
+  public final class GetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class GetTargetFragmentRequestCodeUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public final class GetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public abstract class RetainInstanceUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public final class SetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class SetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+    method public int getRequestCode();
+    method public androidx.fragment.app.Fragment getTargetFragment();
+    property public final int requestCode;
+    property public final androidx.fragment.app.Fragment targetFragment;
+  }
+
+  public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    method public boolean isVisibleToUser();
+    property public final boolean isVisibleToUser;
+  }
+
+  public abstract class TargetFragmentUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.RuntimeException {
+    method public final androidx.fragment.app.Fragment getFragment();
+    property public final androidx.fragment.app.Fragment fragment;
+  }
+
+  public final class WrongFragmentContainerViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup getContainer();
+    property public final android.view.ViewGroup container;
+  }
+
+  public final class WrongNestedHierarchyViolation extends androidx.fragment.app.strictmode.Violation {
+    method public int getContainerId();
+    method public androidx.fragment.app.Fragment getExpectedParentFragment();
+    property public final int containerId;
+    property public final androidx.fragment.app.Fragment expectedParentFragment;
+  }
+
+}
+
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 7a1dab6..3c4835a 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -250,21 +250,21 @@
     method public static Class<? extends androidx.fragment.app.Fragment!> loadFragmentClass(ClassLoader, String);
   }
 
-  public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
-    ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
-    method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
-    method public android.view.View? onFindViewById(int);
-    method public abstract E? onGetHost();
+  public abstract class FragmentHostCallback<H> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context context, android.os.Handler handler, int windowAnimations);
+    method public void onDump(String prefix, java.io.FileDescriptor? fd, java.io.PrintWriter writer, String[]? args);
+    method public android.view.View? onFindViewById(int id);
+    method public abstract H onGetHost();
     method public android.view.LayoutInflater onGetLayoutInflater();
     method public int onGetWindowAnimations();
     method public boolean onHasView();
     method public boolean onHasWindowAnimations();
-    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String![], int);
-    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
-    method public boolean onShouldShowRequestPermissionRationale(String);
-    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int);
-    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int, android.os.Bundle?);
-    method @Deprecated public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment fragment, String[] permissions, int requestCode);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String permission);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode, android.os.Bundle? options);
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment fragment, android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
     method public void onSupportInvalidateOptionsMenu();
   }
 
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/fragment/fragment/api/res-1.7.0-beta01.txt
similarity index 100%
copy from media2/media2-common/api/res-1.0.0-beta01.txt
copy to fragment/fragment/api/res-1.7.0-beta01.txt
diff --git a/fragment/fragment/api/restricted_1.7.0-beta01.txt b/fragment/fragment/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..5472436
--- /dev/null
+++ b/fragment/fragment/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,596 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+    ctor public DialogFragment();
+    ctor public DialogFragment(@LayoutRes int);
+    method public void dismiss();
+    method public void dismissAllowingStateLoss();
+    method @MainThread public void dismissNow();
+    method public android.app.Dialog? getDialog();
+    method public boolean getShowsDialog();
+    method @StyleRes public int getTheme();
+    method public boolean isCancelable();
+    method public void onCancel(android.content.DialogInterface);
+    method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
+    method @CallSuper public void onDismiss(android.content.DialogInterface);
+    method public final androidx.activity.ComponentDialog requireComponentDialog();
+    method public final android.app.Dialog requireDialog();
+    method public void setCancelable(boolean);
+    method public void setShowsDialog(boolean);
+    method public void setStyle(int, @StyleRes int);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setupDialog(android.app.Dialog, int);
+    method public void show(androidx.fragment.app.FragmentManager, String?);
+    method public int show(androidx.fragment.app.FragmentTransaction, String?);
+    method public void showNow(androidx.fragment.app.FragmentManager, String?);
+    field public static final int STYLE_NORMAL = 0; // 0x0
+    field public static final int STYLE_NO_FRAME = 2; // 0x2
+    field public static final int STYLE_NO_INPUT = 3; // 0x3
+    field public static final int STYLE_NO_TITLE = 1; // 0x1
+  }
+
+  public class Fragment implements androidx.activity.result.ActivityResultCaller android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+    ctor public Fragment();
+    ctor @ContentView public Fragment(@LayoutRes int);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public final boolean equals(Object?);
+    method public final androidx.fragment.app.FragmentActivity? getActivity();
+    method public boolean getAllowEnterTransitionOverlap();
+    method public boolean getAllowReturnTransitionOverlap();
+    method public final android.os.Bundle? getArguments();
+    method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+    method public android.content.Context? getContext();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public Object? getEnterTransition();
+    method public Object? getExitTransition();
+    method @Deprecated public final androidx.fragment.app.FragmentManager? getFragmentManager();
+    method public final Object? getHost();
+    method public final int getId();
+    method public final android.view.LayoutInflater getLayoutInflater();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.view.LayoutInflater getLayoutInflater(android.os.Bundle?);
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+    method public final androidx.fragment.app.Fragment? getParentFragment();
+    method public final androidx.fragment.app.FragmentManager getParentFragmentManager();
+    method public Object? getReenterTransition();
+    method public final android.content.res.Resources getResources();
+    method @Deprecated public final boolean getRetainInstance();
+    method public Object? getReturnTransition();
+    method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public Object? getSharedElementEnterTransition();
+    method public Object? getSharedElementReturnTransition();
+    method public final String getString(@StringRes int);
+    method public final String getString(@StringRes int, java.lang.Object!...?);
+    method public final String? getTag();
+    method @Deprecated public final androidx.fragment.app.Fragment? getTargetFragment();
+    method @Deprecated public final int getTargetRequestCode();
+    method public final CharSequence getText(@StringRes int);
+    method @Deprecated public boolean getUserVisibleHint();
+    method public android.view.View? getView();
+    method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+    method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner!> getViewLifecycleOwnerLiveData();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean hasOptionsMenu();
+    method public final int hashCode();
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public final boolean isAdded();
+    method public final boolean isDetached();
+    method public final boolean isHidden();
+    method public final boolean isInLayout();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean isMenuVisible();
+    method public final boolean isRemoving();
+    method public final boolean isResumed();
+    method public final boolean isStateSaved();
+    method public final boolean isVisible();
+    method @Deprecated @CallSuper @MainThread public void onActivityCreated(android.os.Bundle?);
+    method @Deprecated public void onActivityResult(int, int, android.content.Intent?);
+    method @Deprecated @CallSuper @MainThread public void onAttach(android.app.Activity);
+    method @CallSuper @MainThread public void onAttach(android.content.Context);
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+    method @MainThread public boolean onContextItemSelected(android.view.MenuItem);
+    method @CallSuper @MainThread public void onCreate(android.os.Bundle?);
+    method @MainThread public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+    method @MainThread public android.animation.Animator? onCreateAnimator(int, boolean, int);
+    method @MainThread public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+    method @Deprecated @MainThread public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method @MainThread public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+    method @CallSuper @MainThread public void onDestroy();
+    method @Deprecated @MainThread public void onDestroyOptionsMenu();
+    method @CallSuper @MainThread public void onDestroyView();
+    method @CallSuper @MainThread public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+    method @MainThread public void onHiddenChanged(boolean);
+    method @Deprecated @CallSuper @UiThread public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @UiThread public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @MainThread public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
+    method @Deprecated @MainThread public boolean onOptionsItemSelected(android.view.MenuItem);
+    method @Deprecated @MainThread public void onOptionsMenuClosed(android.view.Menu);
+    method @CallSuper @MainThread public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
+    method @Deprecated @MainThread public void onPrepareOptionsMenu(android.view.Menu);
+    method @MainThread public void onPrimaryNavigationFragmentChanged(boolean);
+    method @Deprecated public void onRequestPermissionsResult(int, String![], int[]);
+    method @CallSuper @MainThread public void onResume();
+    method @MainThread public void onSaveInstanceState(android.os.Bundle);
+    method @CallSuper @MainThread public void onStart();
+    method @CallSuper @MainThread public void onStop();
+    method @MainThread public void onViewCreated(android.view.View, android.os.Bundle?);
+    method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
+    method public void postponeEnterTransition();
+    method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method public void registerForContextMenu(android.view.View);
+    method @Deprecated public final void requestPermissions(String![], int);
+    method public final androidx.fragment.app.FragmentActivity requireActivity();
+    method public final android.os.Bundle requireArguments();
+    method public final android.content.Context requireContext();
+    method @Deprecated public final androidx.fragment.app.FragmentManager requireFragmentManager();
+    method public final Object requireHost();
+    method public final androidx.fragment.app.Fragment requireParentFragment();
+    method public final android.view.View requireView();
+    method public void setAllowEnterTransitionOverlap(boolean);
+    method public void setAllowReturnTransitionOverlap(boolean);
+    method public void setArguments(android.os.Bundle?);
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setEnterTransition(Object?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitTransition(Object?);
+    method @Deprecated public void setHasOptionsMenu(boolean);
+    method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+    method public void setMenuVisibility(boolean);
+    method public void setReenterTransition(Object?);
+    method @Deprecated public void setRetainInstance(boolean);
+    method public void setReturnTransition(Object?);
+    method public void setSharedElementEnterTransition(Object?);
+    method public void setSharedElementReturnTransition(Object?);
+    method @Deprecated public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+    method @Deprecated public void setUserVisibleHint(boolean);
+    method public boolean shouldShowRequestPermissionRationale(String);
+    method public void startActivity(android.content.Intent);
+    method public void startActivity(android.content.Intent, android.os.Bundle?);
+    method @Deprecated public void startActivityForResult(android.content.Intent, int);
+    method @Deprecated public void startActivityForResult(android.content.Intent, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
+    method public void unregisterForContextMenu(android.view.View);
+  }
+
+  public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+    ctor public Fragment.InstantiationException(String, Exception?);
+  }
+
+  public static class Fragment.SavedState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState!> CREATOR;
+  }
+
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
+    ctor public FragmentActivity();
+    ctor @ContentView public FragmentActivity(@LayoutRes int);
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method protected void onResumeFragments();
+    method public void onStateNotSaved();
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void supportFinishAfterTransition();
+    method @Deprecated public void supportInvalidateOptionsMenu();
+    method public void supportPostponeEnterTransition();
+    method public void supportStartPostponedEnterTransition();
+    method @Deprecated public final void validateRequestPermissionsRequestCode(int);
+  }
+
+  public abstract class FragmentContainer {
+    ctor public FragmentContainer();
+    method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public abstract android.view.View? onFindViewById(@IdRes int);
+    method public abstract boolean onHasView();
+  }
+
+  public final class FragmentContainerView extends android.widget.FrameLayout {
+    ctor public FragmentContainerView(android.content.Context context);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs, optional int defStyleAttr);
+    method public <F extends androidx.fragment.app.Fragment> F getFragment();
+  }
+
+  public class FragmentController {
+    method public void attachHost(androidx.fragment.app.Fragment?);
+    method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+    method public void dispatchActivityCreated();
+    method @Deprecated public void dispatchConfigurationChanged(android.content.res.Configuration);
+    method public boolean dispatchContextItemSelected(android.view.MenuItem);
+    method public void dispatchCreate();
+    method @Deprecated public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method public void dispatchDestroy();
+    method public void dispatchDestroyView();
+    method @Deprecated public void dispatchLowMemory();
+    method @Deprecated public void dispatchMultiWindowModeChanged(boolean);
+    method @Deprecated public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+    method @Deprecated public void dispatchOptionsMenuClosed(android.view.Menu);
+    method public void dispatchPause();
+    method @Deprecated public void dispatchPictureInPictureModeChanged(boolean);
+    method @Deprecated public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+    method @Deprecated public void dispatchReallyStop();
+    method public void dispatchResume();
+    method public void dispatchStart();
+    method public void dispatchStop();
+    method @Deprecated public void doLoaderDestroy();
+    method @Deprecated public void doLoaderRetain();
+    method @Deprecated public void doLoaderStart();
+    method @Deprecated public void doLoaderStop(boolean);
+    method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public boolean execPendingActions();
+    method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+    method public java.util.List<androidx.fragment.app.Fragment!> getActiveFragments(java.util.List<androidx.fragment.app.Fragment!>!);
+    method public int getActiveFragmentsCount();
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+    method public void noteStateNotSaved();
+    method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+    method @Deprecated public void reportLoaderStart();
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment!>?);
+    method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>!);
+    method @Deprecated public void restoreSaveState(android.os.Parcelable?);
+    method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>? retainLoaderNonConfig();
+    method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+    method @Deprecated public java.util.List<androidx.fragment.app.Fragment!>? retainNonConfig();
+    method @Deprecated public android.os.Parcelable? saveAllState();
+  }
+
+  public class FragmentFactory {
+    ctor public FragmentFactory();
+    method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+    method public static Class<? extends androidx.fragment.app.Fragment!> loadFragmentClass(ClassLoader, String);
+  }
+
+  public abstract class FragmentHostCallback<H> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context context, android.os.Handler handler, int windowAnimations);
+    method public void onDump(String prefix, java.io.FileDescriptor? fd, java.io.PrintWriter writer, String[]? args);
+    method public android.view.View? onFindViewById(int id);
+    method public abstract H onGetHost();
+    method public android.view.LayoutInflater onGetLayoutInflater();
+    method public int onGetWindowAnimations();
+    method public boolean onHasView();
+    method public boolean onHasWindowAnimations();
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment fragment, String[] permissions, int requestCode);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String permission);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode, android.os.Bundle? options);
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment fragment, android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
+    method public void onSupportInvalidateOptionsMenu();
+  }
+
+  public abstract class FragmentManager implements androidx.fragment.app.FragmentResultOwner {
+    ctor public FragmentManager();
+    method public void addFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public androidx.fragment.app.FragmentTransaction beginTransaction();
+    method public void clearBackStack(String);
+    method public final void clearFragmentResult(String);
+    method public final void clearFragmentResultListener(String);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method @Deprecated public static void enableDebugLogging(boolean);
+    method @SuppressCompatibility @androidx.fragment.app.PredictiveBackControl public static void enablePredictiveBack(boolean);
+    method @MainThread public boolean executePendingTransactions();
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+    method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+    method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
+    method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+    method public int getBackStackEntryCount();
+    method public androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+    method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+    method public java.util.List<androidx.fragment.app.Fragment!> getFragments();
+    method public androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy? getStrictModePolicy();
+    method public boolean isDestroyed();
+    method public boolean isStateSaved();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.fragment.app.FragmentTransaction openTransaction();
+    method public void popBackStack();
+    method public void popBackStack(int, int);
+    method public void popBackStack(String?, int);
+    method @MainThread public boolean popBackStackImmediate();
+    method public boolean popBackStackImmediate(int, int);
+    method @MainThread public boolean popBackStackImmediate(String?, int);
+    method public void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+    method public void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+    method public void removeFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public void restoreBackStack(String);
+    method public void saveBackStack(String);
+    method public androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+    method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+    method public final void setFragmentResult(String, android.os.Bundle);
+    method public final void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+    method public void setStrictModePolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy?);
+    method public void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+    field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+  }
+
+  public static interface FragmentManager.BackStackEntry {
+    method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+    method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+    method @Deprecated public CharSequence? getBreadCrumbTitle();
+    method @Deprecated @StringRes public int getBreadCrumbTitleRes();
+    method public int getId();
+    method public String? getName();
+  }
+
+  public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+    ctor public FragmentManager.FragmentLifecycleCallbacks();
+    method @Deprecated public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+    method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+    method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  public static interface FragmentManager.OnBackStackChangedListener {
+    method @MainThread public default void onBackStackChangeCancelled();
+    method @MainThread public default void onBackStackChangeCommitted(androidx.fragment.app.Fragment, boolean);
+    method @MainThread public default void onBackStackChangeProgressed(androidx.activity.BackEventCompat);
+    method @MainThread public default void onBackStackChangeStarted(androidx.fragment.app.Fragment, boolean);
+    method @MainThread public void onBackStackChanged();
+  }
+
+  @Deprecated public class FragmentManagerNonConfig {
+  }
+
+  public interface FragmentOnAttachListener {
+    method @MainThread public void onAttachFragment(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  @Deprecated public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public long getItemId(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  public interface FragmentResultListener {
+    method public void onFragmentResult(String, android.os.Bundle);
+  }
+
+  public interface FragmentResultOwner {
+    method public void clearFragmentResult(String);
+    method public void clearFragmentResultListener(String);
+    method public void setFragmentResult(String, android.os.Bundle);
+    method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+  }
+
+  @Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  }
+
+  public abstract class FragmentTransaction {
+    ctor @Deprecated public FragmentTransaction();
+    method public androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+    method public androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+    method public androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+    method public abstract int commit();
+    method public abstract int commitAllowingStateLoss();
+    method @MainThread public abstract void commitNow();
+    method @MainThread public abstract void commitNowAllowingStateLoss();
+    method public androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+    method public androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+    method public boolean isAddToBackStackAllowed();
+    method public boolean isEmpty();
+    method public androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment!>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
+    method public androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+    method public androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+    method public androidx.fragment.app.FragmentTransaction setTransition(int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+    method public androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+    field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+    field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+    field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+    field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE = 8197; // 0x2005
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN = 4100; // 0x1004
+    field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+    field public static final int TRANSIT_NONE = 0; // 0x0
+    field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class FragmentTransitionImpl {
+    ctor public FragmentTransitionImpl();
+    method public abstract void addTarget(Object, android.view.View);
+    method public abstract void addTargets(Object, java.util.ArrayList<android.view.View!>);
+    method public void animateToEnd(Object);
+    method public void animateToStart(Object, Runnable);
+    method public abstract void beginDelayedTransition(android.view.ViewGroup, Object?);
+    method protected static void bfsAddViewChildren(java.util.List<android.view.View!>!, android.view.View!);
+    method public abstract boolean canHandle(Object);
+    method public abstract Object! cloneTransition(Object?);
+    method public Object? controlDelayedTransition(android.view.ViewGroup, Object);
+    method protected void getBoundsOnScreen(android.view.View!, android.graphics.Rect!);
+    method protected static boolean isNullOrEmpty(java.util.List!);
+    method public boolean isSeekingSupported();
+    method public boolean isSeekingSupported(Object);
+    method public abstract Object! mergeTransitionsInSequence(Object?, Object?, Object?);
+    method public abstract Object! mergeTransitionsTogether(Object?, Object?, Object?);
+    method public abstract void removeTarget(Object, android.view.View);
+    method public abstract void replaceTargets(Object, java.util.ArrayList<android.view.View!>!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void scheduleHideFragmentView(Object, android.view.View, java.util.ArrayList<android.view.View!>);
+    method public abstract void scheduleRemoveTargets(Object, Object?, java.util.ArrayList<android.view.View!>?, Object?, java.util.ArrayList<android.view.View!>?, Object?, java.util.ArrayList<android.view.View!>?);
+    method public void setCurrentPlayTime(Object, float);
+    method public abstract void setEpicenter(Object, android.graphics.Rect);
+    method public abstract void setEpicenter(Object, android.view.View?);
+    method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable);
+    method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable?, Runnable);
+    method public abstract void setSharedElementTargets(Object, android.view.View, java.util.ArrayList<android.view.View!>);
+    method public abstract void swapSharedElementTargets(Object?, java.util.ArrayList<android.view.View!>?, java.util.ArrayList<android.view.View!>?);
+    method public abstract Object! wrapTransitionInSet(Object?);
+  }
+
+  public class ListFragment extends androidx.fragment.app.Fragment {
+    ctor public ListFragment();
+    method public android.widget.ListAdapter? getListAdapter();
+    method public android.widget.ListView getListView();
+    method public long getSelectedItemId();
+    method public int getSelectedItemPosition();
+    method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+    method public final android.widget.ListAdapter requireListAdapter();
+    method public void setEmptyText(CharSequence?);
+    method public void setListAdapter(android.widget.ListAdapter?);
+    method public void setListShown(boolean);
+    method public void setListShownNoAnimation(boolean);
+    method public void setSelection(int);
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public @interface PredictiveBackControl {
+  }
+
+}
+
+package androidx.fragment.app.strictmode {
+
+  public final class FragmentReuseViolation extends androidx.fragment.app.strictmode.Violation {
+    method public String getPreviousFragmentId();
+    property public final String previousFragmentId;
+  }
+
+  public final class FragmentStrictMode {
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @VisibleForTesting public void onPolicyViolation(androidx.fragment.app.strictmode.Violation violation);
+    method public void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy);
+    property public final androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy;
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode INSTANCE;
+  }
+
+  public static fun interface FragmentStrictMode.OnViolationListener {
+    method public void onViolation(androidx.fragment.app.strictmode.Violation violation);
+  }
+
+  public static final class FragmentStrictMode.Policy {
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode.Policy LAX;
+  }
+
+  public static final class FragmentStrictMode.Policy.Builder {
+    ctor public FragmentStrictMode.Policy.Builder();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(Class<? extends androidx.fragment.app.Fragment> fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(String fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentReuse();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentTagUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectRetainInstanceUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongNestedHierarchy();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
+  }
+
+  public final class FragmentTagUsageViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup? getParentContainer();
+    property public final android.view.ViewGroup? parentContainer;
+  }
+
+  public final class GetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class GetTargetFragmentRequestCodeUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public final class GetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public abstract class RetainInstanceUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public final class SetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class SetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+    method public int getRequestCode();
+    method public androidx.fragment.app.Fragment getTargetFragment();
+    property public final int requestCode;
+    property public final androidx.fragment.app.Fragment targetFragment;
+  }
+
+  public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    method public boolean isVisibleToUser();
+    property public final boolean isVisibleToUser;
+  }
+
+  public abstract class TargetFragmentUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.RuntimeException {
+    method public final androidx.fragment.app.Fragment getFragment();
+    property public final androidx.fragment.app.Fragment fragment;
+  }
+
+  public final class WrongFragmentContainerViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup getContainer();
+    property public final android.view.ViewGroup container;
+  }
+
+  public final class WrongNestedHierarchyViolation extends androidx.fragment.app.strictmode.Violation {
+    method public int getContainerId();
+    method public androidx.fragment.app.Fragment getExpectedParentFragment();
+    property public final int containerId;
+    property public final androidx.fragment.app.Fragment expectedParentFragment;
+  }
+
+}
+
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 880c2f1..5472436 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -254,21 +254,21 @@
     method public static Class<? extends androidx.fragment.app.Fragment!> loadFragmentClass(ClassLoader, String);
   }
 
-  public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
-    ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
-    method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
-    method public android.view.View? onFindViewById(int);
-    method public abstract E? onGetHost();
+  public abstract class FragmentHostCallback<H> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context context, android.os.Handler handler, int windowAnimations);
+    method public void onDump(String prefix, java.io.FileDescriptor? fd, java.io.PrintWriter writer, String[]? args);
+    method public android.view.View? onFindViewById(int id);
+    method public abstract H onGetHost();
     method public android.view.LayoutInflater onGetLayoutInflater();
     method public int onGetWindowAnimations();
     method public boolean onHasView();
     method public boolean onHasWindowAnimations();
-    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String![], int);
-    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
-    method public boolean onShouldShowRequestPermissionRationale(String);
-    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int);
-    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent, int, android.os.Bundle?);
-    method @Deprecated public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment fragment, String[] permissions, int requestCode);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String permission);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment fragment, android.content.Intent intent, int requestCode, android.os.Bundle? options);
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment fragment, android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
     method public void onSupportInvalidateOptionsMenu();
   }
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ControllerHostCallbacks.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ControllerHostCallbacks.kt
index 93c0389..15a34b6 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ControllerHostCallbacks.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ControllerHostCallbacks.kt
@@ -80,9 +80,9 @@
 }
 
 class ControllerHostCallbacks(
-    private val activity: FragmentActivity,
+    private val fragmentActivity: FragmentActivity,
     private val vmStore: ViewModelStore
-) : FragmentHostCallback<FragmentActivity>(activity), ViewModelStoreOwner {
+) : FragmentHostCallback<FragmentActivity>(fragmentActivity), ViewModelStoreOwner {
 
     override val viewModelStore: ViewModelStore = vmStore
 
@@ -95,19 +95,19 @@
     }
 
     override fun onShouldSaveFragmentState(fragment: Fragment): Boolean {
-        return !activity.isFinishing
+        return !fragmentActivity.isFinishing
     }
 
     override fun onGetLayoutInflater(): LayoutInflater {
-        return activity.layoutInflater.cloneInContext(activity)
+        return fragmentActivity.layoutInflater.cloneInContext(fragmentActivity)
     }
 
-    override fun onGetHost(): FragmentActivity? {
-        return activity
+    override fun onGetHost(): FragmentActivity {
+        return fragmentActivity
     }
 
     override fun onSupportInvalidateOptionsMenu() {
-        activity.invalidateOptionsMenu()
+        fragmentActivity.invalidateOptionsMenu()
     }
 
     override fun onStartActivityFromFragment(
@@ -115,7 +115,7 @@
         intent: Intent,
         requestCode: Int
     ) {
-        activity.startActivityFromFragment(fragment, intent, requestCode)
+        fragmentActivity.startActivityFromFragment(fragment, intent, requestCode)
     }
 
     override fun onStartActivityFromFragment(
@@ -124,9 +124,17 @@
         requestCode: Int,
         options: Bundle?
     ) {
-        activity.startActivityFromFragment(fragment, intent, requestCode, options)
+        fragmentActivity.startActivityFromFragment(fragment, intent, requestCode, options)
     }
 
+    @Suppress("DeprecatedCallableAddReplaceWith")
+    @Deprecated(
+        """Have your FragmentHostCallback implement {@link ActivityResultRegistryOwner}
+      to allow Fragments to use
+      {@link Fragment#registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
+      with {@link RequestMultiplePermissions}. This method will still be called when Fragments
+      call the deprecated <code>requestPermissions()</code> method."""
+    )
     override fun onRequestPermissionsFromFragment(
         fragment: Fragment,
         permissions: Array<String>,
@@ -137,21 +145,21 @@
 
     override fun onShouldShowRequestPermissionRationale(permission: String): Boolean {
         return ActivityCompat.shouldShowRequestPermissionRationale(
-            activity, permission
+            fragmentActivity, permission
         )
     }
 
-    override fun onHasWindowAnimations() = activity.window != null
+    override fun onHasWindowAnimations() = fragmentActivity.window != null
 
     override fun onGetWindowAnimations() =
-        activity.window?.attributes?.windowAnimations ?: 0
+        fragmentActivity.window?.attributes?.windowAnimations ?: 0
 
     override fun onFindViewById(id: Int): View? {
-        return activity.findViewById(id)
+        return fragmentActivity.findViewById(id)
     }
 
     override fun onHasView(): Boolean {
-        val w = activity.window
+        val w = fragmentActivity.window
         return w?.peekDecorView() != null
     }
 }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
index 7d23bc2..1c832ad 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
@@ -67,7 +67,7 @@
      */
     @NonNull
     public FragmentManager getSupportFragmentManager() {
-        return mHost.mFragmentManager;
+        return mHost.getFragmentManager();
     }
 
     /**
@@ -90,14 +90,14 @@
      */
     @Nullable
     public Fragment findFragmentByWho(@NonNull String who) {
-        return mHost.mFragmentManager.findFragmentByWho(who);
+        return mHost.getFragmentManager().findFragmentByWho(who);
     }
 
     /**
      * Returns the number of active fragments.
      */
     public int getActiveFragmentsCount() {
-        return mHost.mFragmentManager.getActiveFragmentCount();
+        return mHost.getFragmentManager().getActiveFragmentCount();
     }
 
     /**
@@ -106,7 +106,7 @@
     @NonNull
     public List<Fragment> getActiveFragments(@SuppressLint("UnknownNullness")
             List<Fragment> actives) {
-        return mHost.mFragmentManager.getActiveFragments();
+        return mHost.getFragmentManager().getActiveFragments();
     }
 
     /**
@@ -114,7 +114,7 @@
      * attached before the FragmentManager can be used to manage Fragments.
      */
     public void attachHost(@Nullable Fragment parent) {
-        mHost.mFragmentManager.attachController(
+        mHost.getFragmentManager().attachController(
                 mHost, mHost /*container*/, parent);
     }
 
@@ -132,7 +132,7 @@
     @Nullable
     public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
             @NonNull AttributeSet attrs) {
-        return mHost.mFragmentManager.getLayoutInflaterFactory()
+        return mHost.getFragmentManager().getLayoutInflaterFactory()
                 .onCreateView(parent, name, context, attrs);
     }
 
@@ -140,7 +140,7 @@
      * Marks the fragment state as unsaved. This allows for "state loss" detection.
      */
     public void noteStateNotSaved() {
-        mHost.mFragmentManager.noteStateNotSaved();
+        mHost.getFragmentManager().noteStateNotSaved();
     }
 
     /**
@@ -156,7 +156,7 @@
     @Deprecated
     @Nullable
     public Parcelable saveAllState() {
-        return mHost.mFragmentManager.saveAllState();
+        return mHost.getFragmentManager().saveAllState();
     }
 
     /**
@@ -171,7 +171,7 @@
     @Deprecated
     public void restoreAllState(@Nullable Parcelable state,
             @Nullable List<Fragment> nonConfigList) {
-        mHost.mFragmentManager.restoreAllState(state,
+        mHost.getFragmentManager().restoreAllState(state,
                 new FragmentManagerNonConfig(nonConfigList, null, null));
     }
 
@@ -187,7 +187,7 @@
     @Deprecated
     public void restoreAllState(@Nullable Parcelable state,
             @Nullable FragmentManagerNonConfig nonConfig) {
-        mHost.mFragmentManager.restoreAllState(state, nonConfig);
+        mHost.getFragmentManager().restoreAllState(state, nonConfig);
     }
 
     /**
@@ -207,7 +207,7 @@
                     + "ViewModelStoreOwner to call restoreSaveState(). Call restoreAllState() "
                     + " if you're still using retainNestedNonConfig().");
         }
-        mHost.mFragmentManager.restoreSaveState(state);
+        mHost.getFragmentManager().restoreSaveState(state);
     }
 
     /**
@@ -221,7 +221,7 @@
     @Deprecated
     @Nullable
     public List<Fragment> retainNonConfig() {
-        FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
+        FragmentManagerNonConfig nonconf = mHost.getFragmentManager().retainNonConfig();
         return nonconf != null && nonconf.getFragments() != null
                 ? new ArrayList<>(nonconf.getFragments())
                 : null;
@@ -238,7 +238,7 @@
     @Deprecated
     @Nullable
     public FragmentManagerNonConfig retainNestedNonConfig() {
-        return mHost.mFragmentManager.retainNonConfig();
+        return mHost.getFragmentManager().retainNonConfig();
     }
 
     /**
@@ -249,7 +249,7 @@
      * @see Fragment#onCreate(Bundle)
      */
     public void dispatchCreate() {
-        mHost.mFragmentManager.dispatchCreate();
+        mHost.getFragmentManager().dispatchCreate();
     }
 
     /**
@@ -260,7 +260,7 @@
      * @see Fragment#onActivityCreated(Bundle)
      */
     public void dispatchActivityCreated() {
-        mHost.mFragmentManager.dispatchActivityCreated();
+        mHost.getFragmentManager().dispatchActivityCreated();
     }
 
     /**
@@ -271,7 +271,7 @@
      * @see Fragment#onStart()
      */
     public void dispatchStart() {
-        mHost.mFragmentManager.dispatchStart();
+        mHost.getFragmentManager().dispatchStart();
     }
 
     /**
@@ -282,7 +282,7 @@
      * @see Fragment#onResume()
      */
     public void dispatchResume() {
-        mHost.mFragmentManager.dispatchResume();
+        mHost.getFragmentManager().dispatchResume();
     }
 
     /**
@@ -293,7 +293,7 @@
      * @see Fragment#onPause()
      */
     public void dispatchPause() {
-        mHost.mFragmentManager.dispatchPause();
+        mHost.getFragmentManager().dispatchPause();
     }
 
     /**
@@ -304,7 +304,7 @@
      * @see Fragment#onStop()
      */
     public void dispatchStop() {
-        mHost.mFragmentManager.dispatchStop();
+        mHost.getFragmentManager().dispatchStop();
     }
 
     /**
@@ -322,7 +322,7 @@
      * @see Fragment#onDestroyView()
      */
     public void dispatchDestroyView() {
-        mHost.mFragmentManager.dispatchDestroyView();
+        mHost.getFragmentManager().dispatchDestroyView();
     }
 
     /**
@@ -343,7 +343,7 @@
      * @see Fragment#onDestroy()
      */
     public void dispatchDestroy() {
-        mHost.mFragmentManager.dispatchDestroy();
+        mHost.getFragmentManager().dispatchDestroy();
     }
 
     /**
@@ -358,7 +358,7 @@
      */
     @Deprecated
     public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
-        mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode, true);
+        mHost.getFragmentManager().dispatchMultiWindowModeChanged(isInMultiWindowMode, true);
     }
 
     /**
@@ -373,7 +373,8 @@
      */
     @Deprecated
     public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode, true);
+        mHost.getFragmentManager().dispatchPictureInPictureModeChanged(
+                isInPictureInPictureMode, true);
     }
 
     /**
@@ -388,7 +389,7 @@
      */
     @Deprecated
     public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
-        mHost.mFragmentManager.dispatchConfigurationChanged(newConfig, true);
+        mHost.getFragmentManager().dispatchConfigurationChanged(newConfig, true);
     }
 
     /**
@@ -404,7 +405,7 @@
      */
     @Deprecated
     public void dispatchLowMemory() {
-        mHost.mFragmentManager.dispatchLowMemory(true);
+        mHost.getFragmentManager().dispatchLowMemory(true);
     }
 
     /**
@@ -420,7 +421,7 @@
      */
     @Deprecated
     public boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
-        return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
+        return mHost.getFragmentManager().dispatchCreateOptionsMenu(menu, inflater);
     }
 
     /**
@@ -436,7 +437,7 @@
      */
     @Deprecated
     public boolean dispatchPrepareOptionsMenu(@NonNull Menu menu) {
-        return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu);
+        return mHost.getFragmentManager().dispatchPrepareOptionsMenu(menu);
     }
 
     /**
@@ -453,7 +454,7 @@
      */
     @Deprecated
     public boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
-        return mHost.mFragmentManager.dispatchOptionsItemSelected(item);
+        return mHost.getFragmentManager().dispatchOptionsItemSelected(item);
     }
 
     /**
@@ -466,7 +467,7 @@
      * @see Fragment#onContextItemSelected(MenuItem)
      */
     public boolean dispatchContextItemSelected(@NonNull MenuItem item) {
-        return mHost.mFragmentManager.dispatchContextItemSelected(item);
+        return mHost.getFragmentManager().dispatchContextItemSelected(item);
     }
 
     /**
@@ -481,7 +482,7 @@
      */
     @Deprecated
     public void dispatchOptionsMenuClosed(@NonNull Menu menu) {
-        mHost.mFragmentManager.dispatchOptionsMenuClosed(menu);
+        mHost.getFragmentManager().dispatchOptionsMenuClosed(menu);
     }
 
     /**
@@ -492,7 +493,7 @@
      * @return {@code true} if queued actions were performed
      */
     public boolean execPendingActions() {
-        return mHost.mFragmentManager.execPendingActions(true);
+        return mHost.getFragmentManager().execPendingActions(true);
     }
 
     /**
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.java
deleted file mode 100644
index 19d75d9..0000000
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright 2018 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.fragment.app;
-
-import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
-import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultRegistryOwner;
-import androidx.activity.result.contract.ActivityResultContract;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Integration points with the Fragment host.
- * <p>
- * Fragments may be hosted by any object; such as an {@link Activity}. In order to
- * host fragments, implement {@link FragmentHostCallback}, overriding the methods
- * applicable to the host.
- * <p>
- * FragmentManager changes its behavior based on what optional interfaces your
- * FragmentHostCallback implements. This includes the following:
- * <ul>
- *     <li><strong>{@link ActivityResultRegistryOwner}</strong>: Removes the need to
- *     override {@link #onStartIntentSenderFromFragment} or
- *     {@link #onRequestPermissionsFromFragment}.</li>
- *     <li><strong>{@link FragmentOnAttachListener}</strong>: Removes the need to
- *     manually call {@link FragmentManager#addFragmentOnAttachListener} from your
- *     host in order to receive {@link FragmentOnAttachListener#onAttachFragment} callbacks
- *     for the {@link FragmentController#getSupportFragmentManager()}.</li>
- *     <li><strong>{@link androidx.activity.OnBackPressedDispatcherOwner}</strong>: Removes
- *     the need to manually call
- *     {@link FragmentManager#popBackStackImmediate()} when handling the system
- *     back button.</li>
- *     <li><strong>{@link androidx.lifecycle.ViewModelStoreOwner}</strong>: Removes the need
- *     for your {@link FragmentController} to call
- *     {@link FragmentController#retainNestedNonConfig()} or
- *     {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</li>
- * </ul>
- *
- * @param <E> the type of object that's currently hosting the fragments. An instance of this
- *           class must be returned by {@link #onGetHost()}.
- */
-@SuppressWarnings("deprecation")
-public abstract class FragmentHostCallback<E> extends FragmentContainer {
-    @Nullable private final Activity mActivity;
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    private final int mWindowAnimations;
-    final FragmentManager mFragmentManager = new FragmentManagerImpl();
-
-    public FragmentHostCallback(@NonNull Context context, @NonNull Handler handler,
-            int windowAnimations) {
-        this(context instanceof Activity ? (Activity) context : null, context, handler,
-                windowAnimations);
-    }
-
-    @SuppressWarnings("deprecation")
-    FragmentHostCallback(@NonNull FragmentActivity activity) {
-        this(activity, activity /*context*/, new Handler(), 0 /*windowAnimations*/);
-    }
-
-    FragmentHostCallback(@Nullable Activity activity, @NonNull Context context,
-            @NonNull Handler handler, int windowAnimations) {
-        mActivity = activity;
-        mContext = Preconditions.checkNotNull(context, "context == null");
-        mHandler = Preconditions.checkNotNull(handler, "handler == null");
-        mWindowAnimations = windowAnimations;
-    }
-
-    /**
-     * Print internal state into the given stream.
-     *
-     * @param prefix Desired prefix to prepend at each line of output.
-     * @param fd The raw file descriptor that the dump is being sent to.
-     * @param writer The PrintWriter to which you should dump your state. This will be closed
-     *                  for you after you return.
-     * @param args additional arguments to the dump request.
-     */
-    public void onDump(@NonNull String prefix, @Nullable FileDescriptor fd,
-            @NonNull PrintWriter writer, @Nullable String[] args) {
-    }
-
-    /**
-     * Return {@code true} if the fragment's state needs to be saved.
-     */
-    public boolean onShouldSaveFragmentState(@NonNull Fragment fragment) {
-        return true;
-    }
-
-    /**
-     * Return a {@link LayoutInflater}.
-     * See {@link Activity#getLayoutInflater()}.
-     */
-    @NonNull
-    public LayoutInflater onGetLayoutInflater() {
-        return LayoutInflater.from(mContext);
-    }
-
-    /**
-     * Return the object that's currently hosting the fragment. If a {@link Fragment}
-     * is hosted by a {@link FragmentActivity}, the object returned here should be
-     * the same object returned from {@link Fragment#getActivity()}.
-     */
-    @Nullable
-    public abstract E onGetHost();
-
-    /**
-     * Invalidates the activity's options menu.
-     * See {@link FragmentActivity#supportInvalidateOptionsMenu()}
-     */
-    public void onSupportInvalidateOptionsMenu() {
-    }
-
-    /**
-     * Starts a new {@link Activity} from the given fragment.
-     * See {@link FragmentActivity#startActivityForResult(Intent, int)}.
-     */
-    public void onStartActivityFromFragment(@NonNull Fragment fragment,
-            @NonNull Intent intent, int requestCode) {
-        onStartActivityFromFragment(fragment, intent, requestCode, null);
-    }
-
-    /**
-     * Starts a new {@link Activity} from the given fragment.
-     * See {@link FragmentActivity#startActivityForResult(Intent, int, Bundle)}.
-     */
-    public void onStartActivityFromFragment(
-            @NonNull Fragment fragment, @NonNull Intent intent,
-            int requestCode, @Nullable Bundle options) {
-        if (requestCode != -1) {
-            throw new IllegalStateException(
-                    "Starting activity with a requestCode requires a FragmentActivity host");
-        }
-        ContextCompat.startActivity(mContext, intent, options);
-    }
-
-    /**
-     * Starts a new {@link IntentSender} from the given fragment.
-     * See {@link Activity#startIntentSender(IntentSender, Intent, int, int, int, Bundle)}.
-     *
-     * @deprecated Have your FragmentHostCallback implement {@link ActivityResultRegistryOwner}
-     * to allow Fragments to use
-     * {@link Fragment#registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
-     * with {@link StartIntentSenderForResult}. This method will still be called when Fragments
-     * call the deprecated <code>startIntentSenderForResult()</code> method.
-     */
-    @Deprecated
-    public void onStartIntentSenderFromFragment(@NonNull Fragment fragment,
-            @NonNull IntentSender intent, int requestCode,
-            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
-            @Nullable Bundle options) throws IntentSender.SendIntentException {
-        if (requestCode != -1) {
-            throw new IllegalStateException(
-                    "Starting intent sender with a requestCode requires a FragmentActivity host");
-        }
-        ActivityCompat.startIntentSenderForResult(mActivity, intent, requestCode, fillInIntent,
-                flagsMask, flagsValues, extraFlags, options);
-    }
-
-    /**
-     * Requests permissions from the given fragment.
-     * See {@link FragmentActivity#requestPermissions(String[], int)}
-     *
-     * @deprecated Have your FragmentHostCallback implement {@link ActivityResultRegistryOwner}
-     * to allow Fragments to use
-     * {@link Fragment#registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
-     * with {@link RequestMultiplePermissions}. This method will still be called when Fragments
-     * call the deprecated <code>requestPermissions()</code> method.
-     */
-    @Deprecated
-    public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
-            @NonNull String[] permissions, int requestCode) {
-    }
-
-    /**
-     * Checks whether to show permission rationale UI from a fragment.
-     * See {@link FragmentActivity#shouldShowRequestPermissionRationale(String)}
-     */
-    public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
-        return false;
-    }
-
-    /**
-     * Return {@code true} if there are window animations.
-     */
-    public boolean onHasWindowAnimations() {
-        return true;
-    }
-
-    /**
-     * Return the window animations.
-     */
-    public int onGetWindowAnimations() {
-        return mWindowAnimations;
-    }
-
-    @Nullable
-    @Override
-    public View onFindViewById(int id) {
-        return null;
-    }
-
-    @Override
-    public boolean onHasView() {
-        return true;
-    }
-
-    @Nullable
-    Activity getActivity() {
-        return mActivity;
-    }
-
-    @NonNull
-    Context getContext() {
-        return mContext;
-    }
-
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @NonNull
-    public Handler getHandler() {
-        return mHandler;
-    }
-}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.kt b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.kt
new file mode 100644
index 0000000..5d88bea
--- /dev/null
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentHostCallback.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2018 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.fragment.app
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.IntentSender
+import android.content.IntentSender.SendIntentException
+import android.os.Bundle
+import android.os.Handler
+import android.view.LayoutInflater
+import android.view.View
+import androidx.annotation.RestrictTo
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Integration points with the Fragment host.
+ *
+ * Fragments may be hosted by any object; such as an [Activity]. In order to
+ * host fragments, implement [FragmentHostCallback], overriding the methods
+ * applicable to the host.
+ *
+ * FragmentManager changes its behavior based on what optional interfaces your
+ * FragmentHostCallback implements. This includes the following:
+ *
+ * - **[androidx.activity.result.ActivityResultRegistryOwner]**: Removes the need to
+ * override [.onStartIntentSenderFromFragment] or
+ * [.onRequestPermissionsFromFragment].
+ * - **[FragmentOnAttachListener]**: Removes the need to
+ * manually call [FragmentManager.addFragmentOnAttachListener] from your
+ * host in order to receive [FragmentOnAttachListener.onAttachFragment] callbacks
+ * for the [FragmentController.getSupportFragmentManager].
+ * - **[androidx.activity.OnBackPressedDispatcherOwner]**: Removes
+ * the need to manually call
+ * [FragmentManager.popBackStackImmediate] when handling the system
+ * back button.
+ * - **[androidx.lifecycle.ViewModelStoreOwner]**: Removes the need
+ * for your [FragmentController] to call
+ * [FragmentController.retainNestedNonConfig] or
+ * [FragmentController.restoreAllState].
+ *
+ * @param H the type of object that's currently hosting the fragments. An instance of this
+ * class must be returned by [onGetHost].
+ */
+@Suppress("deprecation")
+abstract class FragmentHostCallback<H> internal constructor(
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    val activity: Activity?,
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    val context: Context,
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    val handler: Handler,
+    private val windowAnimations: Int
+) : FragmentContainer() {
+
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    val fragmentManager: FragmentManager = FragmentManagerImpl()
+
+    @Suppress("unused")
+    constructor(
+        context: Context,
+        handler: Handler,
+        windowAnimations: Int
+    ) : this(
+        if (context is Activity) context else null,
+        context,
+        handler,
+        windowAnimations
+    )
+
+    @Suppress("deprecation")
+    internal constructor(activity: FragmentActivity) : this(
+        activity,
+        context = activity,
+        Handler(),
+        windowAnimations = 0
+    )
+
+    /**
+     * Print internal state into the given stream.
+     *
+     * @param prefix Desired prefix to prepend at each line of output.
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state. This will be closed
+     * for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    open fun onDump(
+        prefix: String,
+        fd: FileDescriptor?,
+        writer: PrintWriter,
+        args: Array<String>?
+    ) {
+    }
+
+    /**
+     * Return `true` if the fragment's state needs to be saved.
+     */
+    open fun onShouldSaveFragmentState(fragment: Fragment): Boolean {
+        return true
+    }
+
+    /**
+     * Return a [LayoutInflater].
+     * See [Activity.getLayoutInflater].
+     */
+    open fun onGetLayoutInflater(): LayoutInflater {
+        return LayoutInflater.from(context)
+    }
+
+    /**
+     * Return the object that's currently hosting the fragment. If a [Fragment]
+     * is hosted by a [FragmentActivity], the object returned here should be
+     * the same object returned from [Fragment.getActivity].
+     */
+    abstract fun onGetHost(): H
+
+    /**
+     * Invalidates the activity's options menu.
+     * See [FragmentActivity.supportInvalidateOptionsMenu]
+     */
+    open fun onSupportInvalidateOptionsMenu() {}
+
+    /**
+     * Starts a new [Activity] from the given fragment.
+     * See [FragmentActivity.startActivityForResult].
+     */
+    open fun onStartActivityFromFragment(
+        fragment: Fragment,
+        intent: Intent,
+        requestCode: Int
+    ) {
+        onStartActivityFromFragment(fragment, intent, requestCode, null)
+    }
+
+    /**
+     * Starts a new [Activity] from the given fragment.
+     * See [FragmentActivity.startActivityForResult].
+     */
+    open fun onStartActivityFromFragment(
+        fragment: Fragment,
+        intent: Intent,
+        requestCode: Int,
+        options: Bundle?
+    ) {
+        check(requestCode == -1) {
+            "Starting activity with a requestCode requires a FragmentActivity host"
+        }
+        ContextCompat.startActivity(context, intent, options)
+    }
+
+    /**
+     * Starts a new [IntentSender] from the given fragment.
+     * See [Activity.startIntentSender].
+     */
+    @Deprecated(
+        """Have your FragmentHostCallback implement {@link ActivityResultRegistryOwner}
+      to allow Fragments to use
+      {@link Fragment#registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
+      with {@link StartIntentSenderForResult}. This method will still be called when Fragments
+      call the deprecated <code>startIntentSenderForResult()</code> method."""
+    )
+    @Throws(SendIntentException::class)
+    open fun onStartIntentSenderFromFragment(
+        fragment: Fragment,
+        intent: IntentSender,
+        requestCode: Int,
+        fillInIntent: Intent?,
+        flagsMask: Int,
+        flagsValues: Int,
+        extraFlags: Int,
+        options: Bundle?
+    ) {
+        check(requestCode == -1) {
+            "Starting intent sender with a requestCode requires a FragmentActivity host"
+        }
+        val activity = checkNotNull(activity) {
+            "Starting intent sender with a requestCode requires a FragmentActivity host"
+        }
+        ActivityCompat.startIntentSenderForResult(
+            activity, intent, requestCode, fillInIntent,
+            flagsMask, flagsValues, extraFlags, options
+        )
+    }
+
+    /**
+     * Requests permissions from the given fragment.
+     * See [FragmentActivity.requestPermissions]
+     */
+    @Deprecated(
+        """Have your FragmentHostCallback implement {@link ActivityResultRegistryOwner}
+      to allow Fragments to use
+      {@link Fragment#registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
+      with {@link RequestMultiplePermissions}. This method will still be called when Fragments
+      call the deprecated <code>requestPermissions()</code> method."""
+    )
+    open fun onRequestPermissionsFromFragment(
+        fragment: Fragment,
+        permissions: Array<String>,
+        requestCode: Int
+    ) {
+    }
+
+    /**
+     * Checks whether to show permission rationale UI from a fragment.
+     * See [FragmentActivity.shouldShowRequestPermissionRationale]
+     */
+    open fun onShouldShowRequestPermissionRationale(permission: String): Boolean {
+        return false
+    }
+
+    /**
+     * Return `true` if there are window animations.
+     */
+    open fun onHasWindowAnimations(): Boolean {
+        return true
+    }
+
+    /**
+     * Return the window animations.
+     */
+    open fun onGetWindowAnimations(): Int {
+        return windowAnimations
+    }
+
+    override fun onFindViewById(id: Int): View? {
+        return null
+    }
+
+    override fun onHasView(): Boolean {
+        return true
+    }
+}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
index 2430387..f2faf26 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
@@ -126,7 +126,7 @@
                 ?: (ReservedItemIdRangeEnd - index)
             check(id != LazyListScope.UnspecifiedItemId) { "Implicit list item ids exhausted." }
             LazyListItem(id, alignment) {
-                object : LazyItemScope { }.apply { composable() }
+                object : LazyItemScope { }.composable()
             }
         }
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
index 53a41ce..71d0724 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
@@ -135,7 +135,7 @@
                 "Implicit list item ids exhausted."
             }
             LazyVerticalGridItem(id, alignment) {
-                object : LazyItemScope { }.apply { composable() }
+                object : LazyItemScope { }.composable()
             }
         }
     }
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
index 58489b1..33afc1f 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
@@ -124,7 +124,7 @@
 
     return {
         curvedChildList.forEach { composable ->
-            object : CurvedChildScope {}.apply { composable() }
+            object : CurvedChildScope {}.composable()
         }
     }
 }
diff --git a/gradle.properties b/gradle.properties
index 7194ebd..4df9576 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,8 +16,7 @@
 
 # The following entries are workarounds
 # fullsdk-linux/**/package.xml -> b/291331139
-# androidx/compose/lint/common/build/libs/common.jar -> b/295395616
-org.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks=**/prebuilts/fullsdk-linux;**/prebuilts/fullsdk-linux/platforms/android-*/package.xml;**/androidx/compose/lint/common/build/libs/common.jar
+org.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks=**/prebuilts/fullsdk-linux;**/prebuilts/fullsdk-linux/platforms/android-*/package.xml
 
 android.lint.baselineOmitLineNumbers=true
 android.lint.printStackTrace=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index aa65f2a..aa82ccc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
 # -----------------------------------------------------------------------------
 # All of the following should be updated in sync.
 # -----------------------------------------------------------------------------
-androidGradlePlugin = "8.3.0-beta01"
+androidGradlePlugin = "8.4.0-alpha09"
 # NOTE: When updating the lint version we also need to update the `api` version
 # supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "31.3.0-beta01"
+androidLint = "31.4.0-alpha09"
 # Once you have a chosen version of AGP to upgrade to, go to
 # https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2023.2.1.19"
+androidStudio = "2023.3.1.9"
 # -----------------------------------------------------------------------------
 
 androidGradlePluginMin = "7.0.4"
@@ -27,7 +27,7 @@
 byteBuddy = "1.14.9"
 asm = "9.3"
 cmake = "3.22.1"
-composeCompilerPlugin = "1.5.8"
+composeCompilerPlugin = "1.5.9"
 dagger = "2.49"
 dexmaker = "2.28.3"
 dokka = "1.8.20-dev-214"
@@ -56,8 +56,6 @@
 mockito = "2.25.0"
 moshi = "1.13.0"
 protobuf = "3.22.3"
-paparazzi = "1.0.0"
-paparazziNative = "2022.1.1-canary-f5f9f71"
 skiko = "0.7.7"
 spdxGradlePlugin = "0.3.0"
 sqldelight = "1.3.0"
@@ -245,11 +243,6 @@
 playServicesDevicePerformance = { module = "com.google.android.gms:play-services-deviceperformance", version = "16.0.0" }
 playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.1.0"}
 playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
-paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
-paparazziNativeJvm = { module = "app.cash.paparazzi:layoutlib-native-jdk11", version.ref = "paparazziNative" }
-paparazziNativeLinuxX64 = { module = "app.cash.paparazzi:layoutlib-native-linux", version.ref = "paparazziNative" }
-paparazziNativeMacOsArm64 = { module = "app.cash.paparazzi:layoutlib-native-macarm", version.ref = "paparazziNative" }
-paparazziNativeMacOsX64 = { module = "app.cash.paparazzi:layoutlib-native-macosx", version.ref = "paparazziNative" }
 protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
 protobufCompiler = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
 protobufGradlePlugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.9.4" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 4061606..82e12f6 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -238,7 +238,6 @@
             <trusting group="com.google.auto.value"/>
             <trusting group="com.google.auto.value" name="auto-value"/>
          </trusted-key>
-         <trusted-key id="70731C2FFB496DC4E8105AA3604F437C1682DDE5" group="app.cash.paparazzi"/>
          <trusted-key id="70CD19BFD9F6C330027D6F260315BFB7970A144F">
             <trusting group="com.sun.istack"/>
             <trusting group="com.sun.xml.bind"/>
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 96d76c1..9323b06 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.6-rc-1-bin.zip
-distributionSha256Sum=a2da4ba435f6728b43554c5845f6f88f79589c3e0018c29ab33eb23bd781255b
+distributionUrl=../../../../tools/external/gradle/gradle-8.6-bin.zip
+distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 64c7da7..4019b0b 100755
--- a/gradlew
+++ b/gradlew
@@ -264,8 +264,7 @@
   fi
   if [ "$compact" == "--strict" ]; then
     expanded="-Pandroidx.validateNoUnrecognizedMessages\
-     -Pandroidx.verifyUpToDate\
-     --no-watch-fs"
+     -Pandroidx.verifyUpToDate"
     if [ "$USE_ANDROIDX_REMOTE_BUILD_CACHE" == "" ]; then
       expanded="$expanded --offline"
     fi
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
index f77ccb5..9d9228e 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
@@ -872,31 +872,34 @@
             )
         }
 
-        val sharedFdMonitor = CanvasBufferedRendererV34.obtainSharedFdMonitor()!!
-        val hbr1 = createHardwareBufferRenderer(sharedFdMonitor)
-        val hbr2 = createHardwareBufferRenderer(sharedFdMonitor)
-        val hbr3 = createHardwareBufferRenderer(sharedFdMonitor)
+        val sharedFdMonitor = CanvasBufferedRendererV34.obtainSharedFdMonitor()
+        // Monitor is only returned on devices running the vulkan hwui backend
+        if (sharedFdMonitor != null) {
+            val hbr1 = createHardwareBufferRenderer(sharedFdMonitor)
+            val hbr2 = createHardwareBufferRenderer(sharedFdMonitor)
+            val hbr3 = createHardwareBufferRenderer(sharedFdMonitor)
 
-        hbr1.close()
+            hbr1.close()
 
-        assertTrue(sharedFdMonitor.isMonitoring)
+            assertTrue(sharedFdMonitor.isMonitoring)
 
-        hbr2.close()
+            hbr2.close()
 
-        assertTrue(sharedFdMonitor.isMonitoring)
+            assertTrue(sharedFdMonitor.isMonitoring)
 
-        hbr3.close()
+            hbr3.close()
 
-        assertFalse(sharedFdMonitor.isMonitoring)
+            assertFalse(sharedFdMonitor.isMonitoring)
 
-        val sharedFdMonitor2 = CanvasBufferedRendererV34.obtainSharedFdMonitor()!!
-        val hbr4 = createHardwareBufferRenderer(sharedFdMonitor2)
+            val sharedFdMonitor2 = CanvasBufferedRendererV34.obtainSharedFdMonitor()!!
+            val hbr4 = createHardwareBufferRenderer(sharedFdMonitor2)
 
-        assertTrue(sharedFdMonitor2.isMonitoring)
+            assertTrue(sharedFdMonitor2.isMonitoring)
 
-        hbr4.close()
+            hbr4.close()
 
-        assertFalse(sharedFdMonitor2.isMonitoring)
+            assertFalse(sharedFdMonitor2.isMonitoring)
+        }
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
diff --git a/graphics/graphics-core/src/main/cpp/graphics-core.cpp b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
index 4603022..632d647 100644
--- a/graphics/graphics-core/src/main/cpp/graphics-core.cpp
+++ b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
@@ -460,6 +460,15 @@
     return (*env).NewStringUTF(name);
 }
 
+jboolean JniBindings_nIsHwuiUsingVulkanRenderer(JNIEnv*, jclass) {
+    char value[PROP_VALUE_MAX];
+    __system_property_get("ro.hwui.use_vulkan", value);
+    bool device_is_vulkan = strcmp(value, "true") == 0;
+    __system_property_get("debug.hwui.renderer", value);
+    bool is_debug_vulkan = strcmp(value, "skiavk") == 0;
+    return device_is_vulkan || is_debug_vulkan;
+}
+
 jint JniBindings_nGetPreviousReleaseFenceFd(JNIEnv *env, jclass,
                                             jlong surfaceControl,
                                             jlong transactionStats) {
@@ -648,6 +657,11 @@
             "nSetFrameRate",
                 "(JJFII)V",
                 (void *) JniBindings_nSetFrameRate
+        },
+        {
+            "nIsHwuiUsingVulkanRenderer",
+                "()Z",
+                (void *) JniBindings_nIsHwuiUsingVulkanRenderer
         }
 };
 
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV34.kt b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV34.kt
index bc20094..e491d16 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV34.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV34.kt
@@ -25,6 +25,7 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.core.util.Consumer
+import androidx.graphics.surface.JniBindings
 import androidx.hardware.BufferPool
 import androidx.hardware.FileDescriptorMonitor
 import androidx.hardware.SyncFenceCompat
@@ -179,7 +180,8 @@
         private var sharedFdMonitor: SharedFileDescriptorMonitor? = null
 
         fun obtainSharedFdMonitor(): SharedFileDescriptorMonitor? {
-            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            val isVulkan = JniBindings.nIsHwuiUsingVulkanRenderer()
+            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && isVulkan) {
                 // See b/295332012
                 monitorLock.withLock {
                     var monitor = sharedFdMonitor
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
index 6c6b44c..eb87d20 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
@@ -190,6 +190,10 @@
 
         @JvmStatic
         @JniVisible
+        external fun nIsHwuiUsingVulkanRenderer(): Boolean
+
+        @JvmStatic
+        @JniVisible
         external fun nGetPreviousReleaseFenceFd(surfaceControl: Long, transactionStats: Long): Int
 
         @JvmStatic
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/FileDescriptorMonitor.kt b/graphics/graphics-core/src/main/java/androidx/hardware/FileDescriptorMonitor.kt
index c31b7f6..ee22309 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/FileDescriptorMonitor.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/FileDescriptorMonitor.kt
@@ -39,27 +39,57 @@
     private val mIsManagingHandlerThread = AtomicBoolean(false)
     private var mExecutor: HandlerThreadExecutor
 
+    private data class FdSignalPair(val fd: Int, val signalTime: Long)
+    private val pendingFileDescriptors = ArrayList<FdSignalPair>()
+
     init {
         mExecutor = executor ?: HandlerThreadExecutor("fdcleanup")
         mIsManagingHandlerThread.set(manageExecutor)
     }
 
+    private fun closePendingFileDescriptors() {
+        pendingFileDescriptors.sortByDescending { fdSignalTimePair -> fdSignalTimePair.signalTime }
+        while (pendingFileDescriptors.size > MAX_FD) {
+            val fdSignalPair = pendingFileDescriptors.removeLast()
+            try {
+                val fd = fdSignalPair.fd
+                // Re-query the signal time in case the fd was re-used
+                val signalTime = SyncFenceBindings.nGetSignalTime(fd)
+                val diff = signalTime.signalTimeDiffMillis()
+                if (diff > SIGNAL_TIME_DELTA_MILLIS) {
+                    SyncFenceBindings.nForceClose(fd)
+                }
+            } catch (_: Throwable) {
+                // Just in case the owner actually does close the fd
+            }
+        }
+    }
+
+    private fun Long.signalTimeDiffMillis(): Long {
+        val now = System.nanoTime()
+        val signalled = this != SyncFenceCompat.SIGNAL_TIME_INVALID &&
+            this != SyncFenceCompat.SIGNAL_TIME_PENDING
+        return if (signalled && now > this) {
+            TimeUnit.NANOSECONDS.toMillis(now - this)
+        } else {
+            -1
+        }
+    }
+
     private val mCleanupRunnable = Runnable {
         mProcFd.listFiles()?.let { files ->
             for (file in files) {
                 try {
                     val fd = Integer.parseInt(file.name)
                     val signalTime = SyncFenceBindings.nGetSignalTime(fd)
-                    val hasSignaled = signalTime != SyncFenceCompat.SIGNAL_TIME_INVALID &&
-                        signalTime != SyncFenceCompat.SIGNAL_TIME_PENDING
-                    val now = System.nanoTime()
-                    val diff = if (hasSignaled && now > signalTime) {
-                        TimeUnit.NANOSECONDS.toMillis(now - signalTime)
-                    } else {
-                        -1
-                    }
+                    val diff = signalTime.signalTimeDiffMillis()
                     if (diff > SIGNAL_TIME_DELTA_MILLIS) {
-                        SyncFenceBindings.nForceClose(fd);
+                        // Store the signal time as it can potentially change in the middle of
+                        // executing the sorting algorithm and can throw exceptions
+                        pendingFileDescriptors.add(FdSignalPair(fd, signalTime))
+                        if (pendingFileDescriptors.size > MAX_FD) {
+                            closePendingFileDescriptors()
+                        }
                     }
                 } catch (formatException: NumberFormatException) {
                     Log.w(TAG, "Unable to parse fd value from name ${file.name}")
@@ -147,8 +177,10 @@
         /**
          * Delta in which if a fence has signalled it should be removed
          */
-        const val SIGNAL_TIME_DELTA_MILLIS = 1000
+        const val SIGNAL_TIME_DELTA_MILLIS = 3000
 
         const val MONITOR_DELAY = 1000L
+
+        const val MAX_FD = 100
     }
 }
diff --git a/graphics/graphics-path/src/main/cpp/pathway.cpp b/graphics/graphics-path/src/main/cpp/pathway.cpp
index 7a265b3..d3a7d75 100644
--- a/graphics/graphics-path/src/main/cpp/pathway.cpp
+++ b/graphics/graphics-path/src/main/cpp/pathway.cpp
@@ -151,47 +151,95 @@
         jclass pathsClass = env->FindClass(JNI_CLASS_NAME);
         if (pathsClass == nullptr) return JNI_ERR;
 
-        static const JNINativeMethod methods[] = {
-            {
-                (char*) "createInternalPathIterator",
-                (char*) "(Landroid/graphics/Path;IF)J",
-                reinterpret_cast<void*>(createPathIterator)
-            },
-            {
-                (char*) "destroyInternalPathIterator",
-                (char*) "(J)V",
-                reinterpret_cast<void*>(destroyPathIterator)
-            },
-            {
-                (char*) "internalPathIteratorHasNext",
-                (char*) "(J)Z",
-                reinterpret_cast<void*>(pathIteratorHasNext)
-            },
-            {
-                (char*) "internalPathIteratorNext",
-                (char*) "(J[FI)I",
-                reinterpret_cast<void*>(pathIteratorNext)
-            },
-            {
-                (char*) "internalPathIteratorPeek",
-                (char*) "(J)I",
-                reinterpret_cast<void*>(pathIteratorPeek)
-            },
-            {
-                (char*) "internalPathIteratorRawSize",
-                (char*) "(J)I",
-                reinterpret_cast<void*>(pathIteratorRawSize)
-            },
-            {
-                (char*) "internalPathIteratorSize",
-                (char*) "(J)I",
-                reinterpret_cast<void*>(pathIteratorSize)
-            },
-        };
+        jint result;
 
-        int result = env->RegisterNatives(
-                pathsClass, methods, sizeof(methods) / sizeof(JNINativeMethod)
-        );
+        const uint32_t apiLevel = android_get_device_api_level();
+        if (apiLevel >= 26) { // Android 8.0
+            static const JNINativeMethod methods[] = {
+                    {
+                            (char *) "createInternalPathIterator",
+                            (char *) "(Landroid/graphics/Path;IF)J",
+                            reinterpret_cast<void *>(createPathIterator)
+                    },
+                    {
+                            (char *) "destroyInternalPathIterator",
+                            (char *) "(J)V",
+                            reinterpret_cast<void *>(destroyPathIterator)
+                    },
+                    {
+                            (char *) "internalPathIteratorHasNext",
+                            (char *) "(J)Z",
+                            reinterpret_cast<void *>(pathIteratorHasNext)
+                    },
+                    {
+                            (char *) "internalPathIteratorNext",
+                            (char *) "(J[FI)I",
+                            reinterpret_cast<void *>(pathIteratorNext)
+                    },
+                    {
+                            (char *) "internalPathIteratorPeek",
+                            (char *) "(J)I",
+                            reinterpret_cast<void *>(pathIteratorPeek)
+                    },
+                    {
+                            (char *) "internalPathIteratorRawSize",
+                            (char *) "(J)I",
+                            reinterpret_cast<void *>(pathIteratorRawSize)
+                    },
+                    {
+                            (char *) "internalPathIteratorSize",
+                            (char *) "(J)I",
+                            reinterpret_cast<void *>(pathIteratorSize)
+                    },
+            };
+
+            result = env->RegisterNatives(
+                    pathsClass, methods, sizeof(methods) / sizeof(JNINativeMethod)
+            );
+        } else {
+            // Before Android 8, rely on the !bang JNI notation to speed up our JNI calls
+            static const JNINativeMethod methods[] = {
+                    {
+                            (char *) "createInternalPathIterator",
+                            (char *) "(Landroid/graphics/Path;IF)J",
+                            reinterpret_cast<void *>(createPathIterator)
+                    },
+                    {
+                            (char *) "destroyInternalPathIterator",
+                            (char *) "(J)V",
+                            reinterpret_cast<void *>(destroyPathIterator)
+                    },
+                    {
+                            (char *) "internalPathIteratorHasNext",
+                            (char *) "!(J)Z",
+                            reinterpret_cast<void *>(pathIteratorHasNext)
+                    },
+                    {
+                            (char *) "internalPathIteratorNext",
+                            (char *) "!(J[FI)I",
+                            reinterpret_cast<void *>(pathIteratorNext)
+                    },
+                    {
+                            (char *) "internalPathIteratorPeek",
+                            (char *) "!(J)I",
+                            reinterpret_cast<void *>(pathIteratorPeek)
+                    },
+                    {
+                            (char *) "internalPathIteratorRawSize",
+                            (char *) "!(J)I",
+                            reinterpret_cast<void *>(pathIteratorRawSize)
+                    },
+                    {
+                            (char *) "internalPathIteratorSize",
+                            (char *) "!(J)I",
+                            reinterpret_cast<void *>(pathIteratorSize)
+                    },
+            };
+
+            result = env->RegisterNatives(
+                    pathsClass, methods, sizeof(methods) / sizeof(JNINativeMethod)
+            );
+        }
         if (result != JNI_OK) return result;
 
         env->DeleteLocalRef(pathsClass);
diff --git a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
index 1fa733d..6dfff55 100644
--- a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
+++ b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
@@ -21,6 +21,7 @@
 import android.graphics.PointF
 import androidx.annotation.RequiresApi
 import androidx.graphics.path.PathIterator.ConicEvaluation
+import dalvik.annotation.optimization.FastNative
 
 /**
  * Base class for API-version-specific PathIterator implementation classes. All functionality
@@ -228,9 +229,11 @@
     private external fun destroyInternalPathIterator(internalPathIterator: Long)
 
     @Suppress("KotlinJniMissingFunction")
+    @FastNative
     private external fun internalPathIteratorHasNext(internalPathIterator: Long): Boolean
 
     @Suppress("KotlinJniMissingFunction")
+    @FastNative
     private external fun internalPathIteratorNext(
         internalPathIterator: Long,
         points: FloatArray,
@@ -238,12 +241,15 @@
     ): Int
 
     @Suppress("KotlinJniMissingFunction")
+    @FastNative
     private external fun internalPathIteratorPeek(internalPathIterator: Long): Int
 
     @Suppress("KotlinJniMissingFunction")
+    @FastNative
     private external fun internalPathIteratorRawSize(internalPathIterator: Long): Int
 
     @Suppress("KotlinJniMissingFunction")
+    @FastNative
     private external fun internalPathIteratorSize(internalPathIterator: Long): Int
 
     /**
diff --git a/graphics/graphics-shapes/build.gradle b/graphics/graphics-shapes/build.gradle
index bc98beb..e1e08ca 100644
--- a/graphics/graphics-shapes/build.gradle
+++ b/graphics/graphics-shapes/build.gradle
@@ -45,7 +45,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation("androidx.collection:collection:1.4.0-beta02")
+                implementation("androidx.collection:collection:1.4.0")
                 implementation("androidx.annotation:annotation:1.1.0")
             }
         }
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonMeasure.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonMeasure.kt
index d8636f1..4af009d 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonMeasure.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonMeasure.kt
@@ -114,34 +114,33 @@
          * Cut this MeasuredCubic into two MeasuredCubics at the given outline progress value.
          */
         fun cutAtProgress(cutOutlineProgress: Float): Pair<MeasuredCubic, MeasuredCubic> {
+            // Floating point errors further up can cause cutOutlineProgress to land just
+            // slightly outside of the start/end progress for this cubic, so we limit it
+            // to those bounds to avoid further errors later
+            val boundedCutOutlineProgress =
+                cutOutlineProgress.coerceIn(startOutlineProgress, endOutlineProgress)
             val outlineProgressSize = endOutlineProgress - startOutlineProgress
-            val progressFromStart = positiveModulo(cutOutlineProgress - startOutlineProgress, 1f)
-            // progressFromStart should be in the [0 .. outlineProgressSize] range.
-            // If it's not, cap to that range.
-            val mid = if (progressFromStart > (1 + outlineProgressSize) / 2)
-                0f
-            else
-                progressFromStart.coerceAtMost(outlineProgressSize)
+            val progressFromStart = boundedCutOutlineProgress - startOutlineProgress
 
             // Note that in earlier parts of the computation, we have empty MeasuredCubics (cubics
             // with progressSize == 0f), but those cubics are filtered out before this method is
             // called.
-            val relativeMidProgress = mid / outlineProgressSize
-            val t = measurer.findCubicCutPoint(cubic, relativeMidProgress * measuredSize)
+            val relativeProgress = progressFromStart / outlineProgressSize
+            val t = measurer.findCubicCutPoint(cubic, relativeProgress * measuredSize)
             require(t in 0f..1f) {
                 "Cubic cut point is expected to be between 0 and 1"
             }
 
             debugLog(LOG_TAG) {
-                "cutAtProgress: progress = $cutOutlineProgress / " +
+                "cutAtProgress: progress = $boundedCutOutlineProgress / " +
                     "this = [$startOutlineProgress .. $endOutlineProgress] / " +
-                    "pp = $mid / rp = $relativeMidProgress / t = $t"
+                    "ps = $progressFromStart / rp = $relativeProgress / t = $t"
             }
 
             // c1/c2 are the two new cubics, then we return MeasuredCubics created from them
             val (c1, c2) = cubic.split(t)
-            return MeasuredCubic(c1, startOutlineProgress, cutOutlineProgress) to
-                MeasuredCubic(c2, cutOutlineProgress, endOutlineProgress)
+            return MeasuredCubic(c1, startOutlineProgress, boundedCutOutlineProgress) to
+                MeasuredCubic(c2, boundedCutOutlineProgress, endOutlineProgress)
         }
 
         override fun toString(): String {
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
index 44eebdc..a7ebd90 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
@@ -189,14 +189,16 @@
         throw IllegalArgumentException("Pill shapes must have positive width and height")
     }
 
+    val wHalf = width / 2
+    val hHalf = height / 2
     return RoundedPolygon(
         vertices = floatArrayOf(
-            -width + centerX, -height + centerY,
-            width + centerX, -height + centerY,
-            width + centerX, height + centerY,
-            -width + centerX, height + centerY,
+            wHalf + centerX, hHalf + centerY,
+            -wHalf + centerX, hHalf + centerY,
+            -wHalf + centerX, -hHalf + centerY,
+            wHalf + centerX, -hHalf + centerY,
         ),
-        rounding = CornerRounding(min(width, height), smoothing),
+        rounding = CornerRounding(min(wHalf, hHalf), smoothing),
         centerX = centerX, centerY = centerY
     )
 }
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
index 86832b4..333d888 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
@@ -82,7 +82,8 @@
 
     /**
      * A class that stores unique active instances of [ExerciseUpdateCallback] to ensure same binder
-     * object is passed by framework to service side of the IPC.
+     * object is passed by framework to service side of the IPC. This is required because the same
+     * stub object that is set as the listener needs to be used to clear it.
      */
     public class ExerciseUpdateListenerCache private constructor() {
         private val listenerLock = Any()
@@ -91,15 +92,20 @@
         private val listeners: MutableMap<ExerciseUpdateCallback, ExerciseUpdateListenerStub> =
             HashMap()
 
-        public fun getOrCreate(
+        public fun create(
             listener: ExerciseUpdateCallback,
             executor: Executor,
             requestedDataTypesProvider: () -> Set<DataType<*, *>>
         ): ExerciseUpdateListenerStub {
             synchronized(listenerLock) {
-                return listeners.getOrPut(listener) {
-                    ExerciseUpdateListenerStub(listener, executor, requestedDataTypesProvider)
-                }
+                // Each client can only have one listener at a time, if a new
+                // listener is being registered we should clear out the old stub.
+                // It's okay if we end up re-registering an equivalent stub.
+                listeners.clear()
+                val stub = ExerciseUpdateListenerStub(
+                    listener, executor, requestedDataTypesProvider)
+                listeners.put(listener, stub)
+                return stub
             }
         }
 
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveListenerCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveListenerCallbackStub.kt
index 6042029..fb6b62b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveListenerCallbackStub.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveListenerCallbackStub.kt
@@ -72,30 +72,35 @@
     }
 
     /**
-     * Its important to use the same stub for registration and un-registration, to ensure same
-     * binder object is passed by framework to service side of the IPC.
+     * Clients can only have one {@link PassiveListenerCallbackStub} registered at a time. Hold onto
+     * the last one that is registered.
+     *
+     * Note: For PassiveListenerCallback, the {@link ListenerKey} held in the stub is the important
+     * bit that is used, not the stub object itself.
      */
     internal class PassiveListenerCallbackCache private constructor() {
         private val listenerLock = Any()
 
         @GuardedBy("listenerLock")
-        private val listeners: MutableMap<String, PassiveListenerCallbackStub> = HashMap()
+        private var listener: PassiveListenerCallbackStub? = null
 
-        public fun getOrCreate(
+        public fun create(
             packageName: String,
             executor: Executor,
             callback: PassiveListenerCallback
         ): PassiveListenerCallbackStub {
             synchronized(listenerLock) {
-                return listeners.getOrPut(packageName) {
-                    PassiveListenerCallbackStub(packageName, executor, callback)
-                }
+               val stub = PassiveListenerCallbackStub(packageName, executor, callback)
+               listener = stub
+               return stub
             }
         }
 
-        public fun remove(packageName: String): PassiveListenerCallbackStub? {
+        public fun clear(): PassiveListenerCallbackStub? {
             synchronized(listenerLock) {
-                return listeners.remove(packageName)
+                val prev: PassiveListenerCallbackStub? = listener
+                listener = null
+                return prev
             }
         }
 
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
index 3cd2656..0039740 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -144,7 +144,7 @@
         callback: ExerciseUpdateCallback
     ) {
         val listenerStub =
-            ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.getOrCreate(
+            ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.create(
                 callback,
                 executor,
                 requestedDataTypesProvider = {
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
index 606b00e..430153b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
@@ -115,7 +115,7 @@
             return
         }
         val callbackStub =
-            PassiveListenerCallbackCache.INSTANCE.getOrCreate(packageName, executor, callback)
+            PassiveListenerCallbackCache.INSTANCE.create(packageName, executor, callback)
         val future =
             registerListener(callbackStub.listenerKey) { service, result: SettableFuture<Void?> ->
                 service.registerPassiveListenerCallback(
@@ -148,19 +148,13 @@
         )
     }
 
+    @Suppress("UNCHECKED_CAST")
     override fun clearPassiveListenerCallbackAsync(): ListenableFuture<Void> {
-        val callbackStub = PassiveListenerCallbackCache.INSTANCE.remove(packageName)
-        if (callbackStub != null) {
-            return unregisterListener(callbackStub.listenerKey) { service, resultFuture ->
-                service.unregisterPassiveListenerCallback(packageName, StatusCallback(resultFuture))
-            }
+        val callbackStub = PassiveListenerCallbackCache.INSTANCE.clear()
+        ?: return Futures.immediateFuture(null) as ListenableFuture<Void>
+        return unregisterListener(callbackStub.listenerKey) { service, resultFuture ->
+            service.unregisterPassiveListenerCallback(packageName, StatusCallback(resultFuture))
         }
-        return executeWithVersionCheck(
-            { service, resultFuture ->
-                service.unregisterPassiveListenerCallback(packageName, StatusCallback(resultFuture))
-            },
-            /* minApiVersion= */ 4
-        )
     }
 
     override fun flushAsync(): ListenableFuture<Void> {
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
index 5a483e59..118b661 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
@@ -93,7 +93,7 @@
     }
 
     @Test
-    fun registeredCallbackShouldBeInvoked() {
+    fun setUpdateCallback_registeredCallbackShouldBeInvoked() {
         client.setUpdateCallback(callback)
         shadowOf(getMainLooper()).idle()
 
@@ -102,7 +102,7 @@
     }
 
     @Test
-    fun registrationFailedCallbackShouldBeInvoked() {
+    fun setUpdateCallback_registrationFailedCallbackShouldBeInvoked() {
         fakeService.statusCallbackAction = { it!!.onFailure("Terrible failure!") }
 
         client.setUpdateCallback(callback)
@@ -114,6 +114,31 @@
     }
 
     @Test
+    fun setUpdateCallback_secondCallbackReplacesFirst() {
+        client.setUpdateCallback(callback)
+        shadowOf(getMainLooper()).idle()
+        val callback2 = FakeExerciseUpdateCallback()
+        client.setUpdateCallback(callback2)
+        shadowOf(getMainLooper()).idle()
+
+        val resultFuture = client.clearUpdateCallbackAsync(callback)
+        shadowOf(getMainLooper()).idle()
+        resultFuture.get()
+        val resultFuture2 = client.clearUpdateCallbackAsync(callback2)
+        shadowOf(getMainLooper()).idle()
+        resultFuture2.get()
+
+        // Two registrations but only one clear request is sent to the
+        // FakeService since the previous listener was evicted and doesn't need
+        // to be cleared.
+        assertThat(fakeService.setListenerPackageNames).containsExactly(
+            "androidx.health.services.client.test",
+            "androidx.health.services.client.test")
+        assertThat(fakeService.clearListenerPackageNames).containsExactly(
+            "androidx.health.services.client.test")
+    }
+
+    @Test
     fun clearUpdateCallbackAsync_callbackNotRegistered_noOp() {
         val resultFuture = client.clearUpdateCallbackAsync(callback)
         shadowOf(getMainLooper()).idle()
@@ -339,6 +364,8 @@
         var listener: IExerciseUpdateListener? = null
         var statusCallbackAction: (IStatusCallback?) -> Unit = { it!!.onSuccess() }
         var exerciseConfig: ExerciseConfig? = null
+        val setListenerPackageNames = mutableListOf<String>()
+        val clearListenerPackageNames = mutableListOf<String>()
 
         override fun getApiVersion(): Int = 12
 
@@ -381,20 +408,25 @@
         }
 
         override fun setUpdateListener(
-            packageName: String?,
+            packageName: String,
             listener: IExerciseUpdateListener?,
             statusCallback: IStatusCallback?
         ) {
             this.listener = listener
+            setListenerPackageNames += packageName
             statusCallbackAction.invoke(statusCallback)
         }
 
         override fun clearUpdateListener(
-            packageName: String?,
+            packageName: String,
             listener: IExerciseUpdateListener?,
             statusCallback: IStatusCallback?
         ) {
-            throw NotImplementedError()
+            clearListenerPackageNames += packageName
+            if (this.listener == listener) {
+              this.listener = null
+            }
+            this.statusCallbackAction.invoke(statusCallback)
         }
 
         override fun addGoalToActiveExercise(
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
index c56d440e..2c6a3bc 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
@@ -144,7 +144,7 @@
     }
 
     @Test
-    fun registersPassiveListenerCallback() {
+    fun setPassiveListenerCallback_registersCallback() {
         val config = PassiveListenerConfig(
             dataTypes = setOf(STEPS_DAILY),
             shouldUserActivityInfoBeRequested = true,
@@ -165,7 +165,7 @@
     }
 
     @Test
-    fun registersPassiveListenerCallback_fail() {
+    fun setPassiveListenerCallback_fail() {
         val config = PassiveListenerConfig(
             dataTypes = setOf(CALORIES_DAILY),
             shouldUserActivityInfoBeRequested = true,
@@ -187,6 +187,31 @@
     }
 
     @Test
+    fun setPassiveListenerCallback_multipleCallbacksRegistered() {
+        val config = PassiveListenerConfig(
+            dataTypes = setOf(STEPS_DAILY),
+            shouldUserActivityInfoBeRequested = true,
+            dailyGoals = setOf(),
+            healthEventTypes = setOf()
+        )
+        val callback = FakeCallback()
+        client.setPassiveListenerCallback(config, callback)
+        shadowOf(Looper.getMainLooper()).idle()
+
+        val callback2 = FakeCallback()
+        client.setPassiveListenerCallback(config, callback2)
+        shadowOf(Looper.getMainLooper()).idle()
+
+        assertThat(fakeService.registerCallbackRequests).hasSize(2)
+        assertThat(callback.onRegisteredCalls).isEqualTo(1)
+        assertThat(callback2.onRegisteredCalls).isEqualTo(1)
+        assertThat(fakeService.registeredCallbacks).hasSize(2)
+        // Stub is not reused.
+        assertThat(fakeService.registeredCallbacks[0]).isNotSameInstanceAs(
+            fakeService.registeredCallbacks[1]);
+    }
+
+    @Test
     fun callbackReceivesDataPointsAndUserActivityInfo() {
         shadowOf(Looper.getMainLooper()).idle() // ?????
         val config = PassiveListenerConfig(
@@ -326,6 +351,37 @@
         assertThat(callback.onPermissionLostCalls).isEqualTo(1)
     }
 
+    @Test
+    fun clearPassiveListenerCallbackAsync_nothingRegistered_noOp() {
+        // Return value of future.get() is not used, but verifying no exceptions are thrown.
+        client.clearPassiveListenerCallbackAsync().get()
+        shadowOf(Looper.getMainLooper()).idle()
+
+        assertThat(fakeService.unregisterCallbackPackageNames).isEmpty()
+    }
+
+    @Test
+    fun clearPassiveListenerCallbackAsync_callbackRegistered_sendsRequest() {
+        val config = PassiveListenerConfig(
+            dataTypes = setOf(STEPS_DAILY),
+            shouldUserActivityInfoBeRequested = true,
+            dailyGoals = setOf(),
+            healthEventTypes = setOf()
+        )
+        val callback = FakeCallback()
+        client.setPassiveListenerCallback(config, callback)
+        shadowOf(Looper.getMainLooper()).idle()
+
+        // Return value of future.get() is not used, but verifying no exceptions are thrown.
+        val resultFuture = client.clearPassiveListenerCallbackAsync()
+        shadowOf(Looper.getMainLooper()).idle()
+        resultFuture.get()
+
+        assertThat(fakeService.unregisterCallbackPackageNames).hasSize(1)
+        assertThat(fakeService.unregisterCallbackPackageNames[0]).isEqualTo(
+            "androidx.health.services.client.test")
+    }
+
     class FakeListenerService : PassiveListenerService()
 
     internal class FakeCallback : PassiveListenerCallback {
diff --git a/inspection/inspection-gradle-plugin/lint-baseline.xml b/inspection/inspection-gradle-plugin/lint-baseline.xml
new file mode 100644
index 0000000..0be37bb
--- /dev/null
+++ b/inspection/inspection-gradle-plugin/lint-baseline.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha07)" variant="all" version="8.4.0-alpha07">
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        it.jars.from(jar.get().archiveFile)"
+        errorLine2="                         ~~~">
+        <location
+            file="src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        val fileTree = project.fileTree(zipTask.get().destinationDir)"
+        errorLine2="                                                ~~~">
+        <location
+            file="src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        it.from(versionTask.get().outputDir)"
+        errorLine2="                            ~~~">
+        <location
+            file="src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt"/>
+    </issue>
+
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="        it.from(versionTask.get().outputDir)"
+        errorLine2="                            ~~~">
+        <location
+            file="src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt"/>
+    </issue>
+
+</issues>
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
index 2b5bf0a..cded638 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
@@ -96,9 +96,11 @@
                     }
                 }
             }
-            libExtension.sourceSets.findByName("main")!!.resources.srcDirs(
-                File(project.rootDir, "src/main/proto")
-            )
+            libExtension.sourceSets.named("main").configure {
+                it.resources.srcDirs(
+                    File(project.rootDir, "src/main/proto")
+                )
+            }
         }
 
         project.apply(plugin = "com.google.protobuf")
diff --git a/kruth/kruth/api/api_lint.ignore b/kruth/kruth/api/api_lint.ignore
index 7ac8a1f..a92425e 100644
--- a/kruth/kruth/api/api_lint.ignore
+++ b/kruth/kruth/api/api_lint.ignore
@@ -7,17 +7,19 @@
     Method parameter should be Collection<Object> (or subclass) instead of raw array; was `java.lang.Object[]`
 ArrayReturn: androidx.kruth.IterableSubject#containsNoneIn(Object[]) parameter #0:
     Method parameter should be Collection<Object> (or subclass) instead of raw array; was `java.lang.Object[]`
-ArrayReturn: androidx.kruth.KruthKt#assertThat(T[]) parameter #0:
+ArrayReturn: androidx.kruth.Kruth#assertThat(T[]) parameter #0:
     Method parameter should be Collection<T> (or subclass) instead of raw array; was `T[]`
 ArrayReturn: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
     Method parameter should be Collection<T> (or subclass) instead of raw array; was `T[]`
 
 
-AutoBoxing: androidx.kruth.KruthKt#assertThat(Boolean) parameter #0:
+AutoBoxing: androidx.kruth.IntegerSubject#IntegerSubject(androidx.kruth.FailureMetadata, Integer) parameter #1:
+    Must avoid boxed primitives (`java.lang.Integer`)
+AutoBoxing: androidx.kruth.Kruth#assertThat(Boolean) parameter #0:
     Must avoid boxed primitives (`java.lang.Boolean`)
-AutoBoxing: androidx.kruth.KruthKt#assertThat(Double) parameter #0:
+AutoBoxing: androidx.kruth.Kruth#assertThat(Double) parameter #0:
     Must avoid boxed primitives (`java.lang.Double`)
-AutoBoxing: androidx.kruth.KruthKt#assertThat(Integer) parameter #0:
+AutoBoxing: androidx.kruth.Kruth#assertThat(Integer) parameter #0:
     Must avoid boxed primitives (`java.lang.Integer`)
 AutoBoxing: androidx.kruth.StandardSubjectBuilder#that(Boolean) parameter #0:
     Must avoid boxed primitives (`java.lang.Boolean`)
@@ -71,6 +73,12 @@
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.withMessage(String)
 
 
+InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #0:
+    Invalid nullability on parameter `base` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #1:
+    Invalid nullability on parameter `description` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+
+
 MissingBuildMethod: androidx.kruth.SimpleSubjectBuilder:
     androidx.kruth.SimpleSubjectBuilder does not declare a `build()` method, but builder classes are expected to
 MissingBuildMethod: androidx.kruth.StandardSubjectBuilder:
diff --git a/kruth/kruth/api/current.ignore b/kruth/kruth/api/current.ignore
index 61a67d4..96f1a33 100644
--- a/kruth/kruth/api/current.ignore
+++ b/kruth/kruth/api/current.ignore
@@ -1,44 +1,4 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.kruth.FailureStrategy#fail(Error):
-    Added method androidx.kruth.FailureStrategy.fail(Error)
-
-
-AddedFinal: androidx.kruth.IterableSubject#isInOrder():
-    Method androidx.kruth.IterableSubject.isInOrder has added 'final' qualifier
-AddedFinal: androidx.kruth.IterableSubject#isInStrictOrder():
-    Method androidx.kruth.IterableSubject.isInStrictOrder has added 'final' qualifier
-AddedFinal: androidx.kruth.MapSubject:
-    Class androidx.kruth.MapSubject added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject:
-    Class androidx.kruth.StringSubject added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#contains(CharSequence):
-    Method androidx.kruth.StringSubject.contains has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#containsMatch(String):
-    Method androidx.kruth.StringSubject.containsMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotContain(CharSequence):
-    Method androidx.kruth.StringSubject.doesNotContain has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotContainMatch(String):
-    Method androidx.kruth.StringSubject.doesNotContainMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotMatch(String):
-    Method androidx.kruth.StringSubject.doesNotMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#endsWith(String):
-    Method androidx.kruth.StringSubject.endsWith has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#hasLength(int):
-    Method androidx.kruth.StringSubject.hasLength has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#ignoringCase():
-    Method androidx.kruth.StringSubject.ignoringCase has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#isEmpty():
-    Method androidx.kruth.StringSubject.isEmpty has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#isNotEmpty():
-    Method androidx.kruth.StringSubject.isNotEmpty has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#matches(String):
-    Method androidx.kruth.StringSubject.matches has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#startsWith(String):
-    Method androidx.kruth.StringSubject.startsWith has added 'final' qualifier
-AddedFinal: androidx.kruth.ThrowableSubject:
-    Class androidx.kruth.ThrowableSubject added 'final' qualifier
-
-
 ChangedType: androidx.kruth.ObjectArraySubject#asList():
     Method androidx.kruth.ObjectArraySubject.asList has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<?>
 ChangedType: androidx.kruth.PrimitiveBooleanArraySubject#asList():
@@ -57,36 +17,18 @@
     Method androidx.kruth.SimpleSubjectBuilder.that has changed return type from SubjectT (extends androidx.kruth.Subject) to S (extends androidx.kruth.Subject<? extends T>)
 ChangedType: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.Subject.Factory<? extends S,T>):
     Method androidx.kruth.StandardSubjectBuilder.about has changed return type from androidx.kruth.SimpleSubjectBuilder<S,A> to androidx.kruth.SimpleSubjectBuilder<S,T>
-ChangedType: androidx.kruth.StandardSubjectBuilder#fail():
-    Method androidx.kruth.StandardSubjectBuilder.fail has changed return type from void to java.lang.Void
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(Iterable<? extends T>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
-    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ClassSubject to androidx.kruth.Subject<T>
-ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
-ChangedType: androidx.kruth.Subject#failWithActual(String, Object):
-    Method androidx.kruth.Subject.failWithActual has changed return type from void to java.lang.Void
 ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
     Method androidx.kruth.Subject.Factory.createSubject has changed return type from SubjectT (extends androidx.kruth.Subject) to SubjectT (extends androidx.kruth.Subject<? extends ActualT>)
 ChangedType: androidx.kruth.ThrowableSubject#hasCauseThat():
     Method androidx.kruth.ThrowableSubject.hasCauseThat has changed return type from androidx.kruth.ThrowableSubject to androidx.kruth.ThrowableSubject<java.lang.Throwable>
 
 
-InvalidNullConversion: androidx.kruth.MapSubject#containsEntry(K, V) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.kruth.MapSubject.containsEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#containsEntry(K, V) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.kruth.MapSubject.containsEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#doesNotContainEntry(K, V) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.kruth.MapSubject.doesNotContainEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#doesNotContainEntry(K, V) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.kruth.MapSubject.doesNotContainEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.SimpleSubjectBuilder#that(T) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.SimpleSubjectBuilder.that(T actual)
-InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.StandardSubjectBuilder.that(T actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
 InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
@@ -94,7 +36,7 @@
 InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in androidx.kruth.StringSubject.doesNotContain(CharSequence string)
+    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
@@ -105,6 +47,8 @@
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.matches(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in androidx.kruth.StringSubject.startsWith(String string)
+InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
+    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter expected in androidx.kruth.StringSubject.CaseInsensitiveStringComparison.doesNotContain(CharSequence expected)
 InvalidNullConversion: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT) parameter #1:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.Subject.Factory.createSubject(androidx.kruth.FailureMetadata metadata, ActualT actual)
 
@@ -113,8 +57,6 @@
     Removed class androidx.kruth.Correspondence
 RemovedClass: androidx.kruth.CustomSubjectBuilder:
     Removed class androidx.kruth.CustomSubjectBuilder
-RemovedClass: androidx.kruth.Expect:
-    Removed class androidx.kruth.Expect
 RemovedClass: androidx.kruth.ExpectFailure:
     Removed class androidx.kruth.ExpectFailure
 RemovedClass: androidx.kruth.FloatSubject:
@@ -147,16 +89,10 @@
     Class androidx.kruth.Fact no longer implements java.io.Serializable
 
 
-RemovedMethod: androidx.kruth.ComparableSubject#ComparableSubject(androidx.kruth.FailureMetadata, T):
-    Removed constructor androidx.kruth.ComparableSubject(androidx.kruth.FailureMetadata,T)
 RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
     Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
 RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
     Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.FailureStrategy#fail(AssertionError):
-    Removed method androidx.kruth.FailureStrategy.fail(AssertionError)
-RemovedMethod: androidx.kruth.IntegerSubject#IntegerSubject(androidx.kruth.FailureMetadata, Integer):
-    Removed constructor androidx.kruth.IntegerSubject(androidx.kruth.FailureMetadata,Integer)
 RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
     Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
 RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -203,8 +139,6 @@
     Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
     Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#StringSubject(androidx.kruth.FailureMetadata, String):
-    Removed constructor androidx.kruth.StringSubject(androidx.kruth.FailureMetadata,String)
 RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
     Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
 RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
@@ -217,14 +151,6 @@
     Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
 RemovedMethod: androidx.kruth.Subject#actualCustomStringRepresentation():
     Removed method androidx.kruth.Subject.actualCustomStringRepresentation()
-RemovedMethod: androidx.kruth.Subject#check(String, java.lang.Object...):
-    Removed method androidx.kruth.Subject.check(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.Subject#failWithActual(androidx.kruth.Fact, androidx.kruth.Fact...):
-    Removed method androidx.kruth.Subject.failWithActual(androidx.kruth.Fact,androidx.kruth.Fact...)
-RemovedMethod: androidx.kruth.Subject#failWithoutActual(androidx.kruth.Fact, androidx.kruth.Fact...):
-    Removed method androidx.kruth.Subject.failWithoutActual(androidx.kruth.Fact,androidx.kruth.Fact...)
-RemovedMethod: androidx.kruth.Subject#ignoreCheck():
-    Removed method androidx.kruth.Subject.ignoreCheck()
 RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
     Removed method androidx.kruth.Subject.isInstanceOf(Class<?>)
 RemovedMethod: androidx.kruth.Subject#isNotInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index 38b70aa..f559426 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -18,6 +18,7 @@
   }
 
   public class ComparableSubject<T extends java.lang.Comparable<? super T>> extends androidx.kruth.Subject<T> {
+    ctor protected ComparableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
     method public final void isAtLeast(T? other);
     method public final void isAtMost(T? other);
     method public void isEquivalentAccordingToCompareTo(T? other);
@@ -47,43 +48,33 @@
     method public abstract void of(double expected);
   }
 
+  public final class Expect extends androidx.kruth.StandardSubjectBuilder implements org.junit.rules.TestRule {
+    method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public static androidx.kruth.Expect create();
+    method public boolean hasFailures();
+    field public static final androidx.kruth.Expect.Companion Companion;
+  }
+
+  public static final class Expect.Companion {
+    method public androidx.kruth.Expect create();
+  }
+
   public final class Fact {
-    method public static androidx.kruth.Fact fact(String key);
-    method public static androidx.kruth.Fact fact(String key, optional Object? value);
-    method public String getKey();
-    method public String? getValue();
-    method public static String makeMessage(java.util.List<java.lang.String> messages, java.util.List<androidx.kruth.Fact> facts);
+    method public static androidx.kruth.Fact fact(String key, Object? value);
     method public static androidx.kruth.Fact simpleFact(String key);
-    property public final String key;
-    property public final String? value;
     field public static final androidx.kruth.Fact.Companion Companion;
   }
 
   public static final class Fact.Companion {
-    method public androidx.kruth.Fact fact(String key);
-    method public androidx.kruth.Fact fact(String key, optional Object? value);
-    method public String makeMessage(java.util.List<java.lang.String> messages, java.util.List<androidx.kruth.Fact> facts);
+    method public androidx.kruth.Fact fact(String key, Object? value);
     method public androidx.kruth.Fact simpleFact(String key);
   }
 
   public final class FailureMetadata {
-    method public androidx.kruth.FailureStrategy component1();
-    method public java.util.List<java.lang.String> component2();
-    method public androidx.kruth.FailureMetadata copy(androidx.kruth.FailureStrategy failureStrategy, java.util.List<java.lang.String> messagesToPrepend);
-    method public static androidx.kruth.FailureMetadata forFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
-    method public androidx.kruth.FailureStrategy getFailureStrategy();
-    method public java.util.List<java.lang.String> getMessagesToPrepend();
-    property public final androidx.kruth.FailureStrategy failureStrategy;
-    property public final java.util.List<java.lang.String> messagesToPrepend;
-    field public static final androidx.kruth.FailureMetadata.Companion Companion;
-  }
-
-  public static final class FailureMetadata.Companion {
-    method public androidx.kruth.FailureMetadata forFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
   }
 
   public fun interface FailureStrategy {
-    method public Void fail(Error failure);
+    method public void fail(AssertionError failure);
   }
 
   public final class GuavaOptionalSubject<T> extends androidx.kruth.Subject<com.google.common.base.Optional<? extends T>> {
@@ -93,10 +84,12 @@
   }
 
   public class IntegerSubject extends androidx.kruth.ComparableSubject<java.lang.Integer> {
+    ctor protected IntegerSubject(androidx.kruth.FailureMetadata metadata, Integer? actual);
     method @Deprecated public void isEquivalentAccordingToCompareTo(Integer? other);
   }
 
   public class IterableSubject<T> extends androidx.kruth.Subject<java.lang.Iterable<? extends T>> {
+    ctor protected IterableSubject(androidx.kruth.FailureMetadata metadata, Iterable<? extends T>? actual);
     method public final void contains(Object? element);
     method public final void containsAnyIn(Iterable<?>? expected);
     method public final void containsAnyIn(Object?[]? expected);
@@ -114,21 +107,16 @@
     method public final void doesNotContain(Object? element);
     method public final void hasSize(int expectedSize);
     method public final void isEmpty();
-    method public final void isInOrder();
+    method public void isInOrder();
     method public final void isInOrder(java.util.Comparator<?>? comparator);
-    method public final void isInStrictOrder();
+    method public void isInStrictOrder();
     method public final void isInStrictOrder(java.util.Comparator<?>? comparator);
     method @Deprecated public void isNoneOf(Object? first, Object? second, java.lang.Object?... rest);
     method public final void isNotEmpty();
     method @Deprecated public void isNotIn(Iterable<?>? iterable);
   }
 
-  public final class KruthExtKt {
-    method public static inline <reified T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method public static inline <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.reflect.KClass<T> exceptionClass, kotlin.jvm.functions.Function0<kotlin.Unit> block);
-  }
-
-  public final class KruthKt {
+  public final class Kruth {
     method public static <S extends androidx.kruth.Subject<? extends T>, T> androidx.kruth.SimpleSubjectBuilder<S,T> assertAbout(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
     method public static androidx.kruth.PrimitiveBooleanArraySubject assertThat(boolean[]? actual);
     method public static androidx.kruth.PrimitiveByteArraySubject assertThat(byte[]? actual);
@@ -149,6 +137,12 @@
     method public static <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThat(T? actual);
     method public static <T> androidx.kruth.ObjectArraySubject<T> assertThat(T[]? actual);
     method public static androidx.kruth.StandardSubjectBuilder assertWithMessage(String messageToPrepend);
+    method public static androidx.kruth.StandardSubjectBuilder assert_();
+  }
+
+  public final class KruthExtKt {
+    method public static inline <reified T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method public static inline <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.reflect.KClass<T> exceptionClass, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public final class Kruth_jvmKt {
@@ -157,20 +151,21 @@
     method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
   }
 
-  public final class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
-    method public androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
-    method public androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
-    method public void containsEntry(K key, V value);
-    method public void containsEntry(kotlin.Pair<? extends K,? extends V> entry);
-    method public androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
-    method public androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
-    method public void containsKey(Object? key);
-    method public void doesNotContainEntry(K key, V value);
-    method public void doesNotContainEntry(kotlin.Pair<? extends K,? extends V> entry);
-    method public void doesNotContainKey(Object? key);
-    method public void hasSize(int expectedSize);
-    method public void isEmpty();
-    method public void isNotEmpty();
+  public class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
+    ctor protected MapSubject(androidx.kruth.FailureMetadata metadata, java.util.Map<K,? extends V>? actual);
+    method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
+    method public final androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
+    method public final void containsEntry(K key, V value);
+    method public final void containsEntry(kotlin.Pair<? extends K,? extends V> entry);
+    method public final androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
+    method public final androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
+    method public final void containsKey(Object? key);
+    method public final void doesNotContainEntry(K key, V value);
+    method public final void doesNotContainEntry(kotlin.Pair<? extends K,? extends V> entry);
+    method public final void doesNotContainKey(Object? key);
+    method public final void hasSize(int expectedSize);
+    method public final void isEmpty();
+    method public final void isNotEmpty();
   }
 
   public final class ObjectArraySubject<T> extends androidx.kruth.Subject<T[]> {
@@ -244,29 +239,29 @@
     method public S that(T actual);
   }
 
-  public final class StandardSubjectBuilder {
-    method public <T, S extends androidx.kruth.Subject<? extends T>> androidx.kruth.SimpleSubjectBuilder<S,T> about(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
-    method public Void fail();
-    method public static androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
-    method public androidx.kruth.PrimitiveBooleanArraySubject that(boolean[]? actual);
-    method public androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
-    method public androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
-    method public androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
-    method public androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
-    method public androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
-    method public androidx.kruth.BooleanSubject that(Boolean? actual);
-    method public androidx.kruth.DoubleSubject that(Double? actual);
-    method public androidx.kruth.IntegerSubject that(Integer? actual);
-    method public <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
-    method public androidx.kruth.StringSubject that(String? actual);
-    method public <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
-    method public androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
-    method public androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
-    method public <T> androidx.kruth.Subject<T> that(T actual);
-    method public <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
-    method public <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
-    method public <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
-    method public androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
+  public class StandardSubjectBuilder {
+    method public final <T, S extends androidx.kruth.Subject<? extends T>> androidx.kruth.SimpleSubjectBuilder<S,T> about(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
+    method public final void fail();
+    method public static final androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
+    method public final androidx.kruth.PrimitiveBooleanArraySubject that(boolean[]? actual);
+    method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
+    method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+    method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
+    method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
+    method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
+    method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+    method public final androidx.kruth.DoubleSubject that(Double? actual);
+    method public final androidx.kruth.IntegerSubject that(Integer? actual);
+    method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
+    method public final androidx.kruth.StringSubject that(String? actual);
+    method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
+    method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
+    method public final androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
+    method public final <T> androidx.kruth.Subject<T> that(T? actual);
+    method public final <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
+    method public final <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
+    method public final <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
+    method public final androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
     field public static final androidx.kruth.StandardSubjectBuilder.Companion Companion;
   }
 
@@ -274,43 +269,43 @@
     method public androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
   }
 
-  public final class StringSubject extends androidx.kruth.ComparableSubject<java.lang.String> {
+  public class StringSubject extends androidx.kruth.ComparableSubject<java.lang.String> {
+    ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
     method public void contains(CharSequence charSequence);
     method public void containsMatch(String regex);
-    method public void containsMatch(kotlin.text.Regex regex);
-    method public void doesNotContain(CharSequence string);
+    method public final void containsMatch(kotlin.text.Regex regex);
+    method public void doesNotContain(CharSequence charSequence);
     method public void doesNotContainMatch(String regex);
-    method public void doesNotContainMatch(kotlin.text.Regex regex);
+    method public final void doesNotContainMatch(kotlin.text.Regex regex);
     method public void doesNotMatch(String regex);
-    method public void doesNotMatch(kotlin.text.Regex regex);
+    method public final void doesNotMatch(kotlin.text.Regex regex);
     method public void endsWith(String string);
     method public void hasLength(int expectedLength);
     method public androidx.kruth.StringSubject.CaseInsensitiveStringComparison ignoringCase();
     method public void isEmpty();
     method public void isNotEmpty();
     method public void matches(String regex);
-    method public void matches(kotlin.text.Regex regex);
+    method public final void matches(kotlin.text.Regex regex);
     method public void startsWith(String string);
   }
 
   public final class StringSubject.CaseInsensitiveStringComparison {
     method public void contains(CharSequence? expected);
-    method public void doesNotContain(CharSequence? expected);
+    method public void doesNotContain(CharSequence expected);
     method public void isEqualTo(String? expected);
     method public void isNotEqualTo(String? unexpected);
   }
 
   public class Subject<T> {
-    ctor public Subject(T? actual, optional androidx.kruth.FailureMetadata metadata);
+    ctor protected Subject(androidx.kruth.FailureMetadata metadata, T? actual);
     method protected final androidx.kruth.StandardSubjectBuilder check();
-    method protected final Void failWithActual(androidx.kruth.Fact... facts);
-    method protected final Void failWithActual(String key);
-    method protected final Void failWithActual(String key, optional Object? value);
-    method protected final Void failWithoutActual(androidx.kruth.Fact... facts);
-    method protected final Void failWithoutActual(String key);
-    method protected final Void failWithoutActual(String key, optional Object? value);
+    method protected final androidx.kruth.StandardSubjectBuilder check(String format, java.lang.Object... args);
+    method protected final void failWithActual(androidx.kruth.Fact first, androidx.kruth.Fact... rest);
+    method protected final void failWithActual(String key, Object? value);
+    method protected final void failWithoutActual(androidx.kruth.Fact first, androidx.kruth.Fact... rest);
     method public final T? getActual();
     method public final androidx.kruth.FailureMetadata getMetadata();
+    method protected final androidx.kruth.StandardSubjectBuilder ignoreCheck();
     method public void isAnyOf(Object? first, Object? second, java.lang.Object?... rest);
     method public void isEqualTo(Object? expected);
     method public void isIn(Iterable<?>? iterable);
@@ -331,9 +326,10 @@
     method public SubjectT createSubject(androidx.kruth.FailureMetadata metadata, ActualT actual);
   }
 
-  public final class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
-    method public androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
-    method public androidx.kruth.StringSubject hasMessageThat();
+  public class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
+    ctor protected ThrowableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
+    method public final androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
+    method public final androidx.kruth.StringSubject hasMessageThat();
   }
 
 }
diff --git a/kruth/kruth/api/restricted_current.ignore b/kruth/kruth/api/restricted_current.ignore
index 61a67d4..96f1a33 100644
--- a/kruth/kruth/api/restricted_current.ignore
+++ b/kruth/kruth/api/restricted_current.ignore
@@ -1,44 +1,4 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.kruth.FailureStrategy#fail(Error):
-    Added method androidx.kruth.FailureStrategy.fail(Error)
-
-
-AddedFinal: androidx.kruth.IterableSubject#isInOrder():
-    Method androidx.kruth.IterableSubject.isInOrder has added 'final' qualifier
-AddedFinal: androidx.kruth.IterableSubject#isInStrictOrder():
-    Method androidx.kruth.IterableSubject.isInStrictOrder has added 'final' qualifier
-AddedFinal: androidx.kruth.MapSubject:
-    Class androidx.kruth.MapSubject added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject:
-    Class androidx.kruth.StringSubject added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#contains(CharSequence):
-    Method androidx.kruth.StringSubject.contains has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#containsMatch(String):
-    Method androidx.kruth.StringSubject.containsMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotContain(CharSequence):
-    Method androidx.kruth.StringSubject.doesNotContain has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotContainMatch(String):
-    Method androidx.kruth.StringSubject.doesNotContainMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#doesNotMatch(String):
-    Method androidx.kruth.StringSubject.doesNotMatch has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#endsWith(String):
-    Method androidx.kruth.StringSubject.endsWith has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#hasLength(int):
-    Method androidx.kruth.StringSubject.hasLength has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#ignoringCase():
-    Method androidx.kruth.StringSubject.ignoringCase has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#isEmpty():
-    Method androidx.kruth.StringSubject.isEmpty has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#isNotEmpty():
-    Method androidx.kruth.StringSubject.isNotEmpty has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#matches(String):
-    Method androidx.kruth.StringSubject.matches has added 'final' qualifier
-AddedFinal: androidx.kruth.StringSubject#startsWith(String):
-    Method androidx.kruth.StringSubject.startsWith has added 'final' qualifier
-AddedFinal: androidx.kruth.ThrowableSubject:
-    Class androidx.kruth.ThrowableSubject added 'final' qualifier
-
-
 ChangedType: androidx.kruth.ObjectArraySubject#asList():
     Method androidx.kruth.ObjectArraySubject.asList has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<?>
 ChangedType: androidx.kruth.PrimitiveBooleanArraySubject#asList():
@@ -57,36 +17,18 @@
     Method androidx.kruth.SimpleSubjectBuilder.that has changed return type from SubjectT (extends androidx.kruth.Subject) to S (extends androidx.kruth.Subject<? extends T>)
 ChangedType: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.Subject.Factory<? extends S,T>):
     Method androidx.kruth.StandardSubjectBuilder.about has changed return type from androidx.kruth.SimpleSubjectBuilder<S,A> to androidx.kruth.SimpleSubjectBuilder<S,T>
-ChangedType: androidx.kruth.StandardSubjectBuilder#fail():
-    Method androidx.kruth.StandardSubjectBuilder.fail has changed return type from void to java.lang.Void
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(Iterable<? extends T>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
-    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ClassSubject to androidx.kruth.Subject<T>
-ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
-ChangedType: androidx.kruth.Subject#failWithActual(String, Object):
-    Method androidx.kruth.Subject.failWithActual has changed return type from void to java.lang.Void
 ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
     Method androidx.kruth.Subject.Factory.createSubject has changed return type from SubjectT (extends androidx.kruth.Subject) to SubjectT (extends androidx.kruth.Subject<? extends ActualT>)
 ChangedType: androidx.kruth.ThrowableSubject#hasCauseThat():
     Method androidx.kruth.ThrowableSubject.hasCauseThat has changed return type from androidx.kruth.ThrowableSubject to androidx.kruth.ThrowableSubject<java.lang.Throwable>
 
 
-InvalidNullConversion: androidx.kruth.MapSubject#containsEntry(K, V) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.kruth.MapSubject.containsEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#containsEntry(K, V) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.kruth.MapSubject.containsEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#doesNotContainEntry(K, V) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.kruth.MapSubject.doesNotContainEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.MapSubject#doesNotContainEntry(K, V) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.kruth.MapSubject.doesNotContainEntry(K key, V value)
-InvalidNullConversion: androidx.kruth.SimpleSubjectBuilder#that(T) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.SimpleSubjectBuilder.that(T actual)
-InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.StandardSubjectBuilder.that(T actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
 InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
@@ -94,7 +36,7 @@
 InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in androidx.kruth.StringSubject.doesNotContain(CharSequence string)
+    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
@@ -105,6 +47,8 @@
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter regex in androidx.kruth.StringSubject.matches(String regex)
 InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in androidx.kruth.StringSubject.startsWith(String string)
+InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
+    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter expected in androidx.kruth.StringSubject.CaseInsensitiveStringComparison.doesNotContain(CharSequence expected)
 InvalidNullConversion: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT) parameter #1:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter actual in androidx.kruth.Subject.Factory.createSubject(androidx.kruth.FailureMetadata metadata, ActualT actual)
 
@@ -113,8 +57,6 @@
     Removed class androidx.kruth.Correspondence
 RemovedClass: androidx.kruth.CustomSubjectBuilder:
     Removed class androidx.kruth.CustomSubjectBuilder
-RemovedClass: androidx.kruth.Expect:
-    Removed class androidx.kruth.Expect
 RemovedClass: androidx.kruth.ExpectFailure:
     Removed class androidx.kruth.ExpectFailure
 RemovedClass: androidx.kruth.FloatSubject:
@@ -147,16 +89,10 @@
     Class androidx.kruth.Fact no longer implements java.io.Serializable
 
 
-RemovedMethod: androidx.kruth.ComparableSubject#ComparableSubject(androidx.kruth.FailureMetadata, T):
-    Removed constructor androidx.kruth.ComparableSubject(androidx.kruth.FailureMetadata,T)
 RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
     Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
 RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
     Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.FailureStrategy#fail(AssertionError):
-    Removed method androidx.kruth.FailureStrategy.fail(AssertionError)
-RemovedMethod: androidx.kruth.IntegerSubject#IntegerSubject(androidx.kruth.FailureMetadata, Integer):
-    Removed constructor androidx.kruth.IntegerSubject(androidx.kruth.FailureMetadata,Integer)
 RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
     Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
 RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -203,8 +139,6 @@
     Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
     Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#StringSubject(androidx.kruth.FailureMetadata, String):
-    Removed constructor androidx.kruth.StringSubject(androidx.kruth.FailureMetadata,String)
 RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
     Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
 RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
@@ -217,14 +151,6 @@
     Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
 RemovedMethod: androidx.kruth.Subject#actualCustomStringRepresentation():
     Removed method androidx.kruth.Subject.actualCustomStringRepresentation()
-RemovedMethod: androidx.kruth.Subject#check(String, java.lang.Object...):
-    Removed method androidx.kruth.Subject.check(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.Subject#failWithActual(androidx.kruth.Fact, androidx.kruth.Fact...):
-    Removed method androidx.kruth.Subject.failWithActual(androidx.kruth.Fact,androidx.kruth.Fact...)
-RemovedMethod: androidx.kruth.Subject#failWithoutActual(androidx.kruth.Fact, androidx.kruth.Fact...):
-    Removed method androidx.kruth.Subject.failWithoutActual(androidx.kruth.Fact,androidx.kruth.Fact...)
-RemovedMethod: androidx.kruth.Subject#ignoreCheck():
-    Removed method androidx.kruth.Subject.ignoreCheck()
 RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
     Removed method androidx.kruth.Subject.isInstanceOf(Class<?>)
 RemovedMethod: androidx.kruth.Subject#isNotInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index de44dd6..b61c180 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -18,6 +18,7 @@
   }
 
   public class ComparableSubject<T extends java.lang.Comparable<? super T>> extends androidx.kruth.Subject<T> {
+    ctor protected ComparableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
     method public final void isAtLeast(T? other);
     method public final void isAtMost(T? other);
     method public void isEquivalentAccordingToCompareTo(T? other);
@@ -47,43 +48,33 @@
     method public abstract void of(double expected);
   }
 
+  public final class Expect extends androidx.kruth.StandardSubjectBuilder implements org.junit.rules.TestRule {
+    method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public static androidx.kruth.Expect create();
+    method public boolean hasFailures();
+    field public static final androidx.kruth.Expect.Companion Companion;
+  }
+
+  public static final class Expect.Companion {
+    method public androidx.kruth.Expect create();
+  }
+
   public final class Fact {
-    method public static androidx.kruth.Fact fact(String key);
-    method public static androidx.kruth.Fact fact(String key, optional Object? value);
-    method public String getKey();
-    method public String? getValue();
-    method public static String makeMessage(java.util.List<java.lang.String> messages, java.util.List<androidx.kruth.Fact> facts);
+    method public static androidx.kruth.Fact fact(String key, Object? value);
     method public static androidx.kruth.Fact simpleFact(String key);
-    property public final String key;
-    property public final String? value;
     field public static final androidx.kruth.Fact.Companion Companion;
   }
 
   public static final class Fact.Companion {
-    method public androidx.kruth.Fact fact(String key);
-    method public androidx.kruth.Fact fact(String key, optional Object? value);
-    method public String makeMessage(java.util.List<java.lang.String> messages, java.util.List<androidx.kruth.Fact> facts);
+    method public androidx.kruth.Fact fact(String key, Object? value);
     method public androidx.kruth.Fact simpleFact(String key);
   }
 
   public final class FailureMetadata {
-    method public androidx.kruth.FailureStrategy component1();
-    method public java.util.List<java.lang.String> component2();
-    method public androidx.kruth.FailureMetadata copy(androidx.kruth.FailureStrategy failureStrategy, java.util.List<java.lang.String> messagesToPrepend);
-    method public static androidx.kruth.FailureMetadata forFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
-    method public androidx.kruth.FailureStrategy getFailureStrategy();
-    method public java.util.List<java.lang.String> getMessagesToPrepend();
-    property public final androidx.kruth.FailureStrategy failureStrategy;
-    property public final java.util.List<java.lang.String> messagesToPrepend;
-    field public static final androidx.kruth.FailureMetadata.Companion Companion;
-  }
-
-  public static final class FailureMetadata.Companion {
-    method public androidx.kruth.FailureMetadata forFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
   }
 
   public fun interface FailureStrategy {
-    method public Void fail(Error failure);
+    method public void fail(AssertionError failure);
   }
 
   public final class GuavaOptionalSubject<T> extends androidx.kruth.Subject<com.google.common.base.Optional<? extends T>> {
@@ -93,10 +84,12 @@
   }
 
   public class IntegerSubject extends androidx.kruth.ComparableSubject<java.lang.Integer> {
+    ctor protected IntegerSubject(androidx.kruth.FailureMetadata metadata, Integer? actual);
     method @Deprecated public void isEquivalentAccordingToCompareTo(Integer? other);
   }
 
   public class IterableSubject<T> extends androidx.kruth.Subject<java.lang.Iterable<? extends T>> {
+    ctor protected IterableSubject(androidx.kruth.FailureMetadata metadata, Iterable<? extends T>? actual);
     method public final void contains(Object? element);
     method public final void containsAnyIn(Iterable<?>? expected);
     method public final void containsAnyIn(Object?[]? expected);
@@ -114,21 +107,16 @@
     method public final void doesNotContain(Object? element);
     method public final void hasSize(int expectedSize);
     method public final void isEmpty();
-    method public final void isInOrder();
+    method public void isInOrder();
     method public final void isInOrder(java.util.Comparator<?>? comparator);
-    method public final void isInStrictOrder();
+    method public void isInStrictOrder();
     method public final void isInStrictOrder(java.util.Comparator<?>? comparator);
     method @Deprecated public void isNoneOf(Object? first, Object? second, java.lang.Object?... rest);
     method public final void isNotEmpty();
     method @Deprecated public void isNotIn(Iterable<?>? iterable);
   }
 
-  public final class KruthExtKt {
-    method public static inline <reified T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method public static inline <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.reflect.KClass<T> exceptionClass, kotlin.jvm.functions.Function0<kotlin.Unit> block);
-  }
-
-  public final class KruthKt {
+  public final class Kruth {
     method public static <S extends androidx.kruth.Subject<? extends T>, T> androidx.kruth.SimpleSubjectBuilder<S,T> assertAbout(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
     method public static androidx.kruth.PrimitiveBooleanArraySubject assertThat(boolean[]? actual);
     method public static androidx.kruth.PrimitiveByteArraySubject assertThat(byte[]? actual);
@@ -149,6 +137,12 @@
     method public static <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThat(T? actual);
     method public static <T> androidx.kruth.ObjectArraySubject<T> assertThat(T[]? actual);
     method public static androidx.kruth.StandardSubjectBuilder assertWithMessage(String messageToPrepend);
+    method public static androidx.kruth.StandardSubjectBuilder assert_();
+  }
+
+  public final class KruthExtKt {
+    method public static inline <reified T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method public static inline <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThrows(kotlin.reflect.KClass<T> exceptionClass, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public final class Kruth_jvmKt {
@@ -157,20 +151,21 @@
     method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
   }
 
-  public final class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
-    method public androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
-    method public androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
-    method public void containsEntry(K key, V value);
-    method public void containsEntry(kotlin.Pair<? extends K,? extends V> entry);
-    method public androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
-    method public androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
-    method public void containsKey(Object? key);
-    method public void doesNotContainEntry(K key, V value);
-    method public void doesNotContainEntry(kotlin.Pair<? extends K,? extends V> entry);
-    method public void doesNotContainKey(Object? key);
-    method public void hasSize(int expectedSize);
-    method public void isEmpty();
-    method public void isNotEmpty();
+  public class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
+    ctor protected MapSubject(androidx.kruth.FailureMetadata metadata, java.util.Map<K,? extends V>? actual);
+    method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
+    method public final androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
+    method public final void containsEntry(K key, V value);
+    method public final void containsEntry(kotlin.Pair<? extends K,? extends V> entry);
+    method public final androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
+    method public final androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
+    method public final void containsKey(Object? key);
+    method public final void doesNotContainEntry(K key, V value);
+    method public final void doesNotContainEntry(kotlin.Pair<? extends K,? extends V> entry);
+    method public final void doesNotContainKey(Object? key);
+    method public final void hasSize(int expectedSize);
+    method public final void isEmpty();
+    method public final void isNotEmpty();
   }
 
   public final class ObjectArraySubject<T> extends androidx.kruth.Subject<T[]> {
@@ -244,29 +239,29 @@
     method public S that(T actual);
   }
 
-  public final class StandardSubjectBuilder {
-    method public <T, S extends androidx.kruth.Subject<? extends T>> androidx.kruth.SimpleSubjectBuilder<S,T> about(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
-    method public Void fail();
-    method public static androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
-    method public androidx.kruth.PrimitiveBooleanArraySubject that(boolean[]? actual);
-    method public androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
-    method public androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
-    method public androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
-    method public androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
-    method public androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
-    method public androidx.kruth.BooleanSubject that(Boolean? actual);
-    method public androidx.kruth.DoubleSubject that(Double? actual);
-    method public androidx.kruth.IntegerSubject that(Integer? actual);
-    method public <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
-    method public androidx.kruth.StringSubject that(String? actual);
-    method public <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
-    method public androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
-    method public androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
-    method public <T> androidx.kruth.Subject<T> that(T actual);
-    method public <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
-    method public <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
-    method public <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
-    method public androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
+  public class StandardSubjectBuilder {
+    method public final <T, S extends androidx.kruth.Subject<? extends T>> androidx.kruth.SimpleSubjectBuilder<S,T> about(androidx.kruth.Subject.Factory<? extends S,T> subjectFactory);
+    method public final void fail();
+    method public static final androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
+    method public final androidx.kruth.PrimitiveBooleanArraySubject that(boolean[]? actual);
+    method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
+    method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+    method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
+    method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
+    method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
+    method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+    method public final androidx.kruth.DoubleSubject that(Double? actual);
+    method public final androidx.kruth.IntegerSubject that(Integer? actual);
+    method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
+    method public final androidx.kruth.StringSubject that(String? actual);
+    method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
+    method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
+    method public final androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
+    method public final <T> androidx.kruth.Subject<T> that(T? actual);
+    method public final <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
+    method public final <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
+    method public final <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
+    method public final androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
     field public static final androidx.kruth.StandardSubjectBuilder.Companion Companion;
   }
 
@@ -274,44 +269,44 @@
     method public androidx.kruth.StandardSubjectBuilder forCustomFailureStrategy(androidx.kruth.FailureStrategy failureStrategy);
   }
 
-  public final class StringSubject extends androidx.kruth.ComparableSubject<java.lang.String> {
+  public class StringSubject extends androidx.kruth.ComparableSubject<java.lang.String> {
+    ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
     method public void contains(CharSequence charSequence);
     method public void containsMatch(String regex);
-    method public void containsMatch(kotlin.text.Regex regex);
-    method public void doesNotContain(CharSequence string);
+    method public final void containsMatch(kotlin.text.Regex regex);
+    method public void doesNotContain(CharSequence charSequence);
     method public void doesNotContainMatch(String regex);
-    method public void doesNotContainMatch(kotlin.text.Regex regex);
+    method public final void doesNotContainMatch(kotlin.text.Regex regex);
     method public void doesNotMatch(String regex);
-    method public void doesNotMatch(kotlin.text.Regex regex);
+    method public final void doesNotMatch(kotlin.text.Regex regex);
     method public void endsWith(String string);
     method public void hasLength(int expectedLength);
     method public androidx.kruth.StringSubject.CaseInsensitiveStringComparison ignoringCase();
     method public void isEmpty();
     method public void isNotEmpty();
     method public void matches(String regex);
-    method public void matches(kotlin.text.Regex regex);
+    method public final void matches(kotlin.text.Regex regex);
     method public void startsWith(String string);
   }
 
   public final class StringSubject.CaseInsensitiveStringComparison {
     method public void contains(CharSequence? expected);
-    method public void doesNotContain(CharSequence? expected);
+    method public void doesNotContain(CharSequence expected);
     method public void isEqualTo(String? expected);
     method public void isNotEqualTo(String? unexpected);
   }
 
   public class Subject<T> {
-    ctor public Subject(T? actual, optional androidx.kruth.FailureMetadata metadata);
+    ctor protected Subject(androidx.kruth.FailureMetadata metadata, T? actual);
     method protected final androidx.kruth.StandardSubjectBuilder check();
-    method @kotlin.PublishedApi internal final void doFail(String message);
-    method protected final Void failWithActual(androidx.kruth.Fact... facts);
-    method protected final Void failWithActual(String key);
-    method protected final Void failWithActual(String key, optional Object? value);
-    method protected final Void failWithoutActual(androidx.kruth.Fact... facts);
-    method protected final Void failWithoutActual(String key);
-    method protected final Void failWithoutActual(String key, optional Object? value);
+    method protected final androidx.kruth.StandardSubjectBuilder check(String format, java.lang.Object... args);
+    method @kotlin.PublishedApi internal final void doFail(androidx.kruth.Fact... facts);
+    method protected final void failWithActual(androidx.kruth.Fact first, androidx.kruth.Fact... rest);
+    method protected final void failWithActual(String key, Object? value);
+    method protected final void failWithoutActual(androidx.kruth.Fact first, androidx.kruth.Fact... rest);
     method public final T? getActual();
     method public final androidx.kruth.FailureMetadata getMetadata();
+    method protected final androidx.kruth.StandardSubjectBuilder ignoreCheck();
     method public void isAnyOf(Object? first, Object? second, java.lang.Object?... rest);
     method public void isEqualTo(Object? expected);
     method public void isIn(Iterable<?>? iterable);
@@ -332,9 +327,10 @@
     method public SubjectT createSubject(androidx.kruth.FailureMetadata metadata, ActualT actual);
   }
 
-  public final class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
-    method public androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
-    method public androidx.kruth.StringSubject hasMessageThat();
+  public class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
+    ctor protected ThrowableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
+    method public final androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
+    method public final androidx.kruth.StringSubject hasMessageThat();
   }
 
 }
diff --git a/kruth/kruth/build.gradle b/kruth/kruth/build.gradle
index 1695cf0..b6cafba 100644
--- a/kruth/kruth/build.gradle
+++ b/kruth/kruth/build.gradle
@@ -21,6 +21,7 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
 import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
@@ -64,6 +65,13 @@
                 api(libs.kotlinStdlib)
                 api(libs.kotlinTest)
                 implementation(libs.guavaAndroid)
+                implementation(libs.junit)
+            }
+        }
+
+        jvmTest {
+            dependencies {
+                api(libs.kotlinCoroutinesTest)
             }
         }
 
diff --git a/kruth/kruth/lint-baseline.xml b/kruth/kruth/lint-baseline.xml
new file mode 100644
index 0000000..7c9829d
--- /dev/null
+++ b/kruth/kruth/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+
+    <issue
+        id="BanUncheckedReflection"
+        message="Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API."
+        errorLine1="        requireNonNull(getSuppressed.invoke(throwable)) as Array&lt;Throwable>"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/jvmMain/kotlin/androidx/kruth/Platform.jvm.kt"/>
+    </issue>
+
+</issues>
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/AssertionErrorWithFacts.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/AssertionErrorWithFacts.kt
new file mode 100644
index 0000000..37f9652
--- /dev/null
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/AssertionErrorWithFacts.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.Fact.Companion.makeMessage
+
+/**
+ * An [AssertionError] composed of structured [Fact] instances and other string messages.
+ */
+// TODO(dustinlam): Split into platform-specific implementations so we can add a
+//  "createWithNoStack" constructor on JVM.
+internal class AssertionErrorWithFacts(
+    messagesToPrepend: List<String>,
+    val facts: List<Fact> = emptyList(),
+    // TODO: change to AssertionError that takes in a cause when upgraded to 1.9.20
+    @Suppress("UNUSED_PARAMETER") cause: Throwable? = null
+) : AssertionError(
+    makeMessage(messagesToPrepend, facts),
+    // TODO: change to AssertionError that takes in a cause when upgraded to 1.9.20
+    // cause
+) {
+
+    constructor(message: String? = null, cause: Throwable? = null) : this(
+        messagesToPrepend = listOfNotNull(message),
+        facts = emptyList(),
+        cause = cause,
+    )
+
+    override fun toString(): String {
+        // We intentionally hide the class name.
+        return requireNonNull(message)
+    }
+
+    internal companion object {
+        internal fun createWithNoStack(
+            message: String,
+            cause: Throwable? = null
+        ): AssertionErrorWithFacts {
+            return AssertionErrorWithFacts(message, cause)
+                .also(AssertionErrorWithFacts::clearStackTrace)
+        }
+    }
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/BooleanSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/BooleanSubject.kt
index 0bd11d2..e82c33b 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/BooleanSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/BooleanSubject.kt
@@ -22,7 +22,7 @@
 class BooleanSubject internal constructor(
     actual: Boolean?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Boolean>(actual = actual, metadata = metadata) {
+) : Subject<Boolean>(actual, metadata = metadata) {
 
     /**
      * Fails if the subject is false or `null`.
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
index fae5143..dd4b673 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
@@ -22,13 +22,18 @@
  * Propositions for [Comparable] typed subjects.
  *
  * @param T the type of the object being tested by this [ComparableSubject]
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-open class ComparableSubject<T : Comparable<T>> internal constructor(
+open class ComparableSubject<T : Comparable<T>> protected constructor(
+    metadata: FailureMetadata,
     actual: T?,
-    metadata: FailureMetadata = FailureMetadata(),
 ) : Subject<T>(actual, metadata),
     PlatformComparableSubject<T> by PlatformComparableSubjectImpl(actual, metadata) {
 
+    internal constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
     /**
      * Checks that the subject is equivalent to [other] according to [Comparable.compareTo],
      * (i.e., checks that `a.comparesTo(b) == 0`).
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/DoubleSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/DoubleSubject.kt
index e5fe1e56..6f2b9fb 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/DoubleSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/DoubleSubject.kt
@@ -42,7 +42,7 @@
 class DoubleSubject internal constructor(
     actual: Double?,
     metadata: FailureMetadata = FailureMetadata(),
-) : ComparableSubject<Double>(actual = actual, metadata = metadata) {
+) : ComparableSubject<Double>(actual, metadata = metadata) {
 
     abstract class TolerantDoubleComparison internal constructor() {
         /**
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Fact.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Fact.kt
index aa6b769..ace9d2e 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Fact.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Fact.kt
@@ -16,28 +16,45 @@
 
 package androidx.kruth
 
-import kotlin.jvm.JvmOverloads
 import kotlin.jvm.JvmStatic
 
-class Fact private constructor(val key: String, val value: String?) {
+// TODO(dustinlam): This needs to implement Serializable on JVM.
+class Fact private constructor(private val key: String, private val value: String?) {
     override fun toString(): String {
         return if (value == null) key else "$key: $value"
     }
 
+    /**
+     * Helper function to format fact messages with appropriate padding and indentations
+     * given the appearance of new line values.
+     */
+    private fun toMessageString(padKeyToLength: Int, seenNewLineInValue: Boolean) = when {
+        value == null -> key
+        seenNewLineInValue -> "$key:\n${value.prependIndent("    ")}"
+        else -> "${key.padEnd(padKeyToLength)}: $value"
+    }
+
     companion object {
         /**
          * Creates a fact with the given key and value, which will be printed in a format like "key:
          * value." The value is converted to a string by calling [toString] on it.
          */
         @JvmStatic
-        @JvmOverloads
-        fun fact(key: String, value: Any? = null): Fact {
+        fun fact(key: String, value: Any?): Fact {
             return Fact(key, value.toString())
         }
 
         /**
          * Creates a fact with no value, which will be printed in the format "key" (with no colon or
          * value).
+         *
+         * In most cases, prefer [fact], which give Truth more flexibility in how to format the fact
+         * for display. [simpleFact] is useful primarily for:
+         * * messages from no-arg assertions. For example, `isNotEmpty()` would generate the fact
+         *   "expected not to be empty"
+         * * prose that is part of a larger message. For example, `contains()` sometimes
+         *   displays facts like "expected to contain: ..." _"but did not"_ "though it did contain:
+         *   ..."
          */
         @JvmStatic
         fun simpleFact(key: String): Fact {
@@ -49,11 +66,12 @@
          * particular, this method horizontally aligns the beginning of fact values.
          */
         @JvmStatic
-        fun makeMessage(messages: List<String>, facts: List<Fact>): String {
+        internal fun makeMessage(messages: List<String>, facts: List<Fact>): String {
             val longestKeyLength = facts.filter { it.value != null }
                 .maxOfOrNull { it.key.length } ?: 0
             val seenNewlineInValue = facts.filter { it.value != null }
                 .any { it.value!!.contains("\n") }
+            // Using transform instead of separator ensures we end with a newline.
             val messagesToMessage = messages.joinToString("") { it + "\n" }
             val factsToMessage =
                 facts.joinToString(
@@ -63,14 +81,4 @@
             return messagesToMessage + factsToMessage
         }
     }
-
-    /**
-     * Helper function to format fact messages with appropriate padding and indentations
-     * given the appearance of new line values.
-     */
-    private fun toMessageString(padKeyToLength: Int, seenNewLineInValue: Boolean) = when {
-        value == null -> key
-        seenNewLineInValue -> "$key:\n${value.prependIndent("    ")}"
-        else -> "${key.padEnd(padKeyToLength)}: $value"
-    }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
index 593e3cb..3f7f94d 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
@@ -16,6 +16,8 @@
 
 package androidx.kruth
 
+import androidx.kruth.Fact.Companion.simpleFact
+
 /**
  * Always fails with the provided error message.
  */
@@ -25,6 +27,6 @@
 ) : Ordered {
 
     override fun inOrder() {
-        metadata.fail(message())
+        metadata.fail(simpleFact(message()))
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureMetadata.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureMetadata.kt
index 222b09b..8ac2deb 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureMetadata.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureMetadata.kt
@@ -16,45 +16,217 @@
 
 package androidx.kruth
 
+import androidx.kruth.Fact.Companion.fact
+import androidx.kruth.Fact.Companion.simpleFact
+import androidx.kruth.Step.CheckStep
+import androidx.kruth.Step.SubjectStep
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
-import kotlin.jvm.JvmStatic
 
+/**
+ * An opaque, immutable object containing state from the previous calls in the fluent assertion
+ * chain. It appears primarily as a parameter to [Subject] constructors (and [Subject.Factory]
+ * methods), which should pass it to the superclass constructor and not otherwise use or store it.
+ * In particular, users should not attempt to call [Subject] constructors or [Subject.Factory]
+ * methods directly. Instead, they should use the appropriate factory method:
+ * * If you're writing a test: Truth#assertAbout(Subject.Factory)}`.that(...)`
+ * * If you're creating a derived subject from within another subject:
+ *     `check(...).about(...).that(...)`
+ * * If you're testing your subject to verify that assertions fail when they should:
+ *     [ExpectFailure]
+ *
+ * (One exception: Implementations of [CustomSubjectBuilder] do directly call constructors, using
+ * their [CustomSubjectBuilder.metadata] method to get an instance to pass to the constructor.)
+ */
 @OptIn(ExperimentalContracts::class)
-data class FailureMetadata internal constructor(
-    val failureStrategy: FailureStrategy = FailureStrategy { failure -> throw failure },
-    val messagesToPrepend: List<String> = emptyList(),
+class FailureMetadata internal constructor(
+    private val failureStrategy: FailureStrategy = FailureStrategy { failure -> throw failure },
+    // TODO(dustinlam): In Google Truth, messages are lazily evaluated.
+    private val messagesToPrepend: List<String> = emptyList(),
+    private val steps: List<Step> = emptyList()
 ) {
-    companion object {
-        @JvmStatic
-        fun forFailureStrategy(failureStrategy: FailureStrategy): FailureMetadata {
-            return FailureMetadata(
-                failureStrategy
-            )
+
+    /**
+     * Returns a new instance that includes the given subject in its chain of values. Truth users do
+     * not need to call this method directly; Truth automatically accumulates context, starting from
+     * the initial that(...) call and continuing into any chained calls, like
+     * [ThrowableSubject.hasMessageThat].
+     */
+    internal fun updateForSubject(subject: Subject<*>): FailureMetadata {
+        return FailureMetadata(
+            failureStrategy = failureStrategy,
+            messagesToPrepend = messagesToPrepend,
+            steps = steps + SubjectStep(subject)
+        )
+    }
+
+    internal fun updateForCheckCall(
+        valuesAreSimilar: OldAndNewValuesAreSimilar,
+        descriptionUpdate: (String?) -> String
+    ): FailureMetadata {
+        return FailureMetadata(
+            failureStrategy = failureStrategy,
+            messagesToPrepend = messagesToPrepend,
+            steps = steps + CheckStep(valuesAreSimilar, descriptionUpdate)
+        )
+    }
+
+    internal fun fail(vararg facts: Fact) {
+        fail(facts.asList())
+    }
+
+    // TODO: change to AssertionError that takes in a cause when upgraded to 1.9.20
+    internal fun fail(facts: List<Fact> = emptyList()) {
+        failureStrategy.fail(
+            AssertionErrorWithFacts(
+                messagesToPrepend = messagesToPrepend,
+                facts = description() + facts + rootUnlessThrowable(),
+                cause = rootCause()
+            ).also(AssertionErrorWithFacts::cleanStackTrace)
+        )
+    }
+
+    /**
+     * @param message A message to append to a list of messages stored in this [FailureMetadata],
+     * which are prepended to the list of [Fact] when reporting a failure via [fail].
+     */
+    internal fun withMessage(message: String): FailureMetadata = FailureMetadata(
+        failureStrategy = failureStrategy,
+        messagesToPrepend = messagesToPrepend + message,
+        steps = steps
+    )
+
+    /**
+     * Returns a description of the final actual value, if it appears "interesting" enough to show.
+     * The description is considered interesting if the chain of derived subjects ends with at least
+     * one derivation that we have a name for. It's also considered interesting in the absence of
+     * derived subjects if we inferred a name for the root actual value from the bytecode.
+     *
+     * We don't want to say: "value of string: expected \[foo\] but was \[bar\]" (OK, we might still
+     * decide to say this, but for now, we don't.)
+     *
+     * We do want to say: "value of throwable.getMessage(): expected \[foo\] but was \[bar\]"
+     *
+     * We also want to say: "value of getLogMessages(): expected not to be empty"
+     *
+     * To support that, `descriptionIsInteresting` tracks whether we've been given context
+     * through `check` calls _that include names_ or, initially, whether we inferred a name
+     * for the root actual value from the bytecode.
+     *
+     * If we're missing a naming function halfway through, we have to reset: We don't want to claim
+     * that the value is "foo.bar.baz" when it's "foo.bar.somethingelse.baz." We have to go back to
+     * "object.baz." (But note that [rootUnlessThrowable] will still provide the value of the
+     * root foo to the user as long as we had at least one naming function: We might not know the
+     * root's exact relationship to the final object, but we know it's some object "different
+     * enough" to be worth displaying.)
+     */
+    private fun description(): List<Fact> {
+        var description: String? = null
+        var descriptionIsInteresting = false
+        for (step in steps) {
+            if (step is CheckStep) {
+                if (step.descriptionUpdate == null) {
+                    description = null
+                    descriptionIsInteresting = false
+                } else {
+                    description = step.descriptionUpdate.invoke(description)
+                    descriptionIsInteresting = true
+                }
+                continue
+            }
+
+            if (description == null) {
+                require(step is SubjectStep)
+                description = step.subject.typeName()
+            }
+        }
+
+        return if (descriptionIsInteresting) {
+            listOf(fact("value of", description))
+        } else {
+            emptyList()
         }
     }
 
-    internal fun fail(message: String? = null): Nothing {
-        // TODO: change to AssertionError that takes in a cause when upgraded to 1.9.20
-        failureStrategy.fail(AssertionError(formatMessage(message)))
+    /**
+     * Returns the root actual value, if we know it's "different enough" from the final actual
+     * value.
+     *
+     * We don't want to say: "expected \[foo\] but was \[bar\]. string: \[bar\]"
+     *
+     * We do want to say: "expected \[foo\] but was \[bar\]. myObject: MyObject\[string=bar, i=0\]"
+     *
+     * To support that, `seenDerivation` tracks whether we've seen multiple actual values,
+     * which is equivalent to whether we've seen multiple Subject instances or, more informally,
+     * whether the user is making a chained assertion.
+     *
+     * There's one wrinkle: Sometimes chaining doesn't add information. This is often true with
+     * "internal" chaining, like when StreamSubject internally creates an IterableSubject to
+     * delegate to. The two subjects' string representations will be identical (or, in some cases,
+     * _almost_ identical), so there is no value in showing both. In such cases, implementations can
+     * call the no-arg `checkNoNeedToDisplayBothValues()`, which sets `valuesAreSimilar`,
+     * instructing this method that that particular chain link "doesn't count." (Note also that
+     * there are some edge cases that we're not sure how to handle yet, for which we might introduce
+     * additional `check`-like methods someday.)
+     */
+    // TODO(b/134505914): Consider returning multiple facts in some cases.
+    private fun rootUnlessThrowable(): List<Fact> {
+        var rootSubject: Step? = null
+        var seenDerivation = false
+        for (step in steps) {
+            if (step is CheckStep) {
+                // If we don't have a description update, don't trigger display of a root object.
+                // (If we did, we'd change the messages of a bunch of existing subjects, and we
+                // don't want to bite that off yet.)
+                //
+                // If we do have a description update, then trigger display of a root object but
+                // only if the old and new values are "different enough" to be worth both
+                // displaying.
+                seenDerivation =
+                    seenDerivation || (step.descriptionUpdate != null &&
+                        step.valuesAreSimilar == OldAndNewValuesAreSimilar.DIFFERENT)
+                continue
+            }
+
+            if (rootSubject == null) {
+                require(step is SubjectStep)
+                if (step.subject.actual is Throwable) {
+                    // We'll already include the Throwable as a cause of the AssertionError
+                    // (see rootCause()), so we don't need to include it again in the message.
+                    return emptyList()
+                }
+                rootSubject = step
+            }
+        }
+
+        /*
+         * TODO(cpovirk): Maybe say "root foo was: ..." instead of just "foo was: ..." if there's
+         *  more than one foo in the chain, if the description string doesn't start with "foo,"
+         *  and/or if the name we have is just "object?"
+         */
+        return if (seenDerivation) {
+            requireNonNull(rootSubject)
+            require(rootSubject is SubjectStep)
+            listOf(
+                fact(
+                    "${rootSubject.subject.typeName()} was ", rootSubject.subject.actual
+                )
+            )
+        } else {
+            emptyList()
+        }
     }
 
-    internal fun withMessage(messageToPrepend: String): FailureMetadata =
-        copy(messagesToPrepend = messagesToPrepend + messageToPrepend)
-
-    internal fun formatMessage(vararg messages: String?): String =
-        (messagesToPrepend + messages.filterNotNull()).joinToString(separator = "\n")
-
     /**
      * Asserts that the specified value is `true`.
      *
      * @param message the message to report if the assertion fails.
      */
-    internal inline fun assertTrue(actual: Boolean, message: () -> String? = { null }) {
+    internal inline fun assertTrue(actual: Boolean, message: () -> String) {
         contract { returns() implies actual }
 
         if (!actual) {
-            fail(message())
+            fail(simpleFact(message()))
         }
     }
 
@@ -63,7 +235,7 @@
      *
      * @param message the message to report if the assertion fails.
      */
-    internal inline fun assertFalse(actual: Boolean, message: () -> String? = { null }) {
+    internal inline fun assertFalse(actual: Boolean, message: () -> Fact) {
         contract { returns() implies !actual }
 
         if (actual) {
@@ -79,7 +251,7 @@
     internal inline fun assertEquals(
         expected: Any?,
         actual: Any?,
-        message: () -> String? = { null },
+        message: () -> String,
     ) {
         assertTrue(expected == actual, message)
     }
@@ -89,7 +261,7 @@
      *
      * @param message the message to report if the assertion fails.
      */
-    internal fun assertNotEquals(illegal: Any?, actual: Any?, message: () -> String? = { null }) {
+    internal fun assertNotEquals(illegal: Any?, actual: Any?, message: () -> Fact) {
         assertFalse(illegal == actual, message)
     }
 
@@ -98,7 +270,7 @@
      *
      * @param message the message to report if the assertion fails.
      */
-    internal fun assertNull(actual: Any?, message: () -> String? = { null }) {
+    internal fun assertNull(actual: Any?, message: () -> String) {
         contract { returns() implies (actual == null) }
         assertTrue(actual == null, message)
     }
@@ -108,10 +280,53 @@
      *
      * @param message the message to report if the assertion fails.
      */
-    internal fun <T : Any> assertNotNull(actual: T?, message: () -> String? = { null }): T {
+    internal fun <T : Any> assertNotNull(actual: T?, message: () -> Fact): T {
         contract { returns() implies (actual != null) }
         assertFalse(actual == null, message)
 
         return actual
     }
+
+    /**
+     * Returns the first [Throwable] in the chain of actual values. Typically, we'll have a root
+     * cause only if the assertion chain contains a [ThrowableSubject].
+     */
+    private fun rootCause(): Throwable? {
+        return steps.firstNotNullOfOrNull { step ->
+            (step as? SubjectStep)?.subject?.actual as? Throwable
+        }
+    }
+}
+
+/**
+ * Whether the value of the original subject and the value of the derived subject are "similar
+ * enough" that we don't need to display both. For example, if we're printing a message about the
+ * value of optional.get(), there's no need to print the optional itself because it adds no
+ * information. Similarly, if we're printing a message about the asList() view of an array,
+ * there's no need to also print the array.
+ */
+internal enum class OldAndNewValuesAreSimilar {
+    SIMILAR, DIFFERENT
+}
+
+/**
+ * The data from a call to either (a) a [Subject] constructor or (b) [Subject.check].
+ */
+internal sealed class Step {
+
+    internal class SubjectStep(
+        /**
+         * We store Subject, rather than the actual value itself, so that we can call
+         * actualCustomStringRepresentation(). Why not call actualCustomStringRepresentation()
+         * immediately? First, it might be expensive, and second, the Subject isn't initialized at the
+         * time we receive it. We *might* be able to make it safe to call if it looks only at actual(),
+         * but it might try to look at facts initialized by a subclass, which aren't ready yet.
+         */
+        val subject: Subject<*>
+    ) : Step()
+
+    internal class CheckStep(
+        val valuesAreSimilar: OldAndNewValuesAreSimilar?,
+        val descriptionUpdate: ((String?) -> String)?
+    ) : Step()
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureStrategy.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureStrategy.kt
index 0c5d4af..6d9179b 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureStrategy.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/FailureStrategy.kt
@@ -46,5 +46,5 @@
      * We encourage implementations to record as much of this information as practical in the
      * exceptions they may throw or the other records they may make.
      */
-    fun fail(failure: Error): Nothing
+    fun fail(failure: AssertionError)
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/HelperArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/HelperArraySubject.kt
index 029e460..a3440df 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/HelperArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/HelperArraySubject.kt
@@ -16,27 +16,27 @@
 
 package androidx.kruth
 
+import androidx.kruth.Fact.Companion.simpleFact
+
 internal class HelperArraySubject<out T>(
     actual: T?,
     private val size: (T) -> Int,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<T>(actual = actual, metadata = metadata) {
+) : Subject<T>(actual, metadata = metadata) {
 
     /** Fails if the array is not empty (i.e. `array.size > 0`). */
     fun isEmpty() {
-        metadata.assertNotNull(actual) { "Expected array to be empty, but was null" }
-
+        requireNonNull(actual)
         if (size(actual) > 0) {
-            failWithActual(Fact.simpleFact("Expected to be empty"))
+            failWithActual(simpleFact("expected to be empty"))
         }
     }
 
     /** Fails if the array is empty (i.e. `array.size == 0`). */
     fun isNotEmpty() {
-        metadata.assertNotNull(actual) { "Expected array not to be empty, but was null" }
-
+        requireNonNull(actual)
         if (size(actual) == 0) {
-            failWithoutActual(Fact.simpleFact("Expected not to be empty"))
+            failWithoutActual(simpleFact("expected not to be empty"))
         }
     }
 
@@ -46,13 +46,8 @@
      * @throws IllegalArgumentException if [length] < 0
      */
     fun hasLength(length: Int) {
-        require(length >= 0) { "length (%d) must be >= 0" }
-
-        metadata.assertNotNull(actual) { "Expected length to be equal to $length, but was null" }
-
-        val actualSize = size(actual)
-        metadata.assertEquals(length, actualSize) {
-            "Expected length to be equal to $length, but was $actualSize"
-        }
+        require(length >= 0) { "length ($length) must be >= 0" }
+        requireNonNull(actual)
+        check("length").that(size(actual)).isEqualTo(length)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IntegerSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IntegerSubject.kt
index 4fb9236..dc0cc1d 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IntegerSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IntegerSubject.kt
@@ -16,10 +16,19 @@
 
 package androidx.kruth
 
-open class IntegerSubject internal constructor(
+/**
+ * Propositions for [Int] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+open class IntegerSubject protected constructor(
+    metadata: FailureMetadata,
     actual: Int?,
-    metadata: FailureMetadata = FailureMetadata(),
-) : ComparableSubject<Int>(actual = actual, metadata = metadata) {
+) : ComparableSubject<Int>(metadata, actual) {
+
+    internal constructor(actual: Int?, metadata: FailureMetadata) : this(metadata, actual)
+
     @Deprecated(
         "Use .isEqualTo instead. Long comparison is consistent with equality.",
         ReplaceWith("this.isEqualTo(other)")
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
index 10e4d51..34673f9 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
@@ -16,6 +16,9 @@
 
 package androidx.kruth
 
+import androidx.kruth.Fact.Companion.fact
+import androidx.kruth.Fact.Companion.simpleFact
+
 /**
  * Propositions for [Iterable] subjects.
  *
@@ -27,11 +30,17 @@
  * which does (e.g. `iterable.toList()`). If you don't, you may see surprising failures.
  * - Assertions may also require that the elements in the given [Iterable] implement
  * [Any.hashCode] correctly.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-open class IterableSubject<T> internal constructor(
+// Can't be final since MultisetSubject and SortedSetSubject extend it
+open class IterableSubject<T> protected constructor(
+    metadata: FailureMetadata,
     actual: Iterable<T>?,
-    metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Iterable<T>>(actual = actual, metadata = metadata) {
+) : Subject<Iterable<T>>(actual, metadata = metadata) {
+
+    internal constructor(actual: Iterable<T>?, metadata: FailureMetadata) : this(metadata, actual)
 
     override fun isEqualTo(expected: Any?) {
         // method contract requires testing iterables for equality
@@ -57,7 +66,7 @@
         requireNonNull(actual) { "Expected to be empty, but was null" }
 
         if (!actual.isEmpty()) {
-            failWithoutActual("Expected to be empty")
+            failWithoutActual(simpleFact("Expected to be empty"))
         }
     }
 
@@ -66,16 +75,16 @@
         requireNonNull(actual) { "Expected not to be empty, but was null" }
 
         if (actual.isEmpty()) {
-            failWithoutActual("Expected to be not empty")
+            failWithoutActual(simpleFact("Expected to be not empty"))
         }
     }
 
     /** Fails if the subject does not have the given size. */
     fun hasSize(expectedSize: Int) {
-        require(expectedSize >= 0) { "expectedSize must be >= 0, but was $expectedSize" }
+        require(expectedSize >= 0) { "expectedSize($expectedSize) must be >= 0" }
         requireNonNull(actual) { "Expected to have size $expectedSize, but was null" }
 
-        metadata.assertEquals(expectedSize, actual.count())
+        check("count()").that(actual.count()).isEqualTo(expectedSize)
     }
 
     /** Checks (with a side-effect failure) that the subject contains the supplied item. */
@@ -86,11 +95,13 @@
             val matchingItems = actual.retainMatchingToString(listOf(element))
             if (matchingItems.isNotEmpty()) {
                 failWithoutActual(
-                    "Expected to contain $element, but did not. " +
-                        "Though it did contain $matchingItems"
+                    fact("expected to contain", element),
+                    fact("an instance of", element.typeName()),
+                    simpleFact("but did not"),
+                    fact("though it did contain", matchingItems)
                 )
             } else {
-                failWithoutActual("Expected to contain $element, but did not")
+                failWithActual("expected to contain", element)
             }
         }
     }
@@ -100,7 +111,7 @@
         requireNonNull(actual) { "Expected not to contain $element, but was null" }
 
         if (element in actual) {
-            failWithoutActual("Expected not to contain $element")
+            failWithoutActual(simpleFact("Expected not to contain $element"))
         }
     }
 
@@ -111,7 +122,9 @@
         val duplicates = actual.groupBy { it }.values.filter { it.size > 1 }
 
         if (duplicates.isNotEmpty()) {
-            failWithoutActual("Expected not to contain duplicates, but contained $duplicates")
+            failWithoutActual(
+                simpleFact("Expected not to contain duplicates, but contained $duplicates")
+            )
         }
     }
 
@@ -135,11 +148,12 @@
         val matchingItems = actual.retainMatchingToString(expected)
         if (matchingItems.isNotEmpty()) {
             failWithoutActual(
-                "Expected to contain any of $expected, but did not. " +
-                    "Though it did contain $matchingItems"
+                fact("expected to contain any of", expected),
+                simpleFact("but did not"),
+                fact("though it did contain", matchingItems)
             )
         } else {
-            failWithoutActual("Expected to contain any of $expected, but did not")
+            failWithActual("expected to contain any of", expected)
         }
     }
 
@@ -192,11 +206,13 @@
         if (missing.isNotEmpty()) {
             val nearMissing = actualList.retainMatchingToString(missing)
 
-            failWithoutActual(
-                """
-                    Expected to contain at least $expected, but did not.
-                    Missing $missing, though it did contain $nearMissing.
-                """.trimIndent()
+            // TODO(dustinlam): Message is still a bit different from Truth.
+            failWithActual(
+                fact("missing", missing),
+                simpleFact(""),
+                fact("though it did contain", nearMissing),
+                simpleFact("---"),
+                fact("expected to contain at least", expected)
             )
         }
 
@@ -206,8 +222,8 @@
 
         return FailingOrdered(metadata) {
             buildString {
-                append("Required elements were all found, but order was wrong.")
-                append("Expected order: $expected.")
+                append("required elements were all found, but order was wrong")
+                append("expected order for required elements $expected.")
 
                 if (actualList.any { it !in expected }) {
                     append("Actual order: $actualList.")
@@ -288,15 +304,17 @@
                      * already made. So we expose a special method for this and call it from here.
                      *
                      * TODO(b/135918662): Consider always throwing ComparisonFailure if there is exactly one
-                     * missing and exactly one extra element, even if there were additional (matching)
-                     * elements. However, this will probably be useful less often, and it will be tricky to
-                     * explain. First, what would we say, "value of: iterable.onlyElementThatDidNotMatch()?"
-                     * And second, it feels weirder to call out a single element when the expected and actual
-                     * values had multiple elements. Granted, Fuzzy Truth already does this, so maybe it's OK?
-                     * But Fuzzy Truth doesn't (yet) make the mismatched value so prominent.
+                     *  missing and exactly one extra element, even if there were additional (matching)
+                     *  elements. However, this will probably be useful less often, and it will be tricky to
+                     *  explain. First, what would we say, "value of: iterable.onlyElementThatDidNotMatch()?"
+                     *  And second, it feels weirder to call out a single element when the expected and actual
+                     *  values had multiple elements. Granted, Fuzzy Truth already does this, so maybe it's OK?
+                     *  But Fuzzy Truth doesn't (yet) make the mismatched value so prominent.
                      */
                     failWithoutActual(
-                        "Expected $actualElement to be equal to $requiredElement, but was not"
+                        simpleFact(
+                            "Expected $actualElement to be equal to $requiredElement, but was not"
+                        )
                     )
                 }
 
@@ -329,7 +347,6 @@
                      * This containsExactly() call is a success. But the iterables were not in the same order,
                      * so return an object that will fail the test if the user calls inOrder().
                      */
-
                     return FailingOrdered(metadata) {
                         """
                              Contents match. Expected the order to also match, but was not.
@@ -340,12 +357,12 @@
                 }
 
                 failWithActual(
-                    """
-                        Contents do not match.
-                        Expected: $required.
-                        Missing: $missing.
-                        Unexpected: $extra.
-                    """.trimIndent()
+                    fact("missing", missing),
+                    simpleFact(""),
+                    fact("unexpected", extra),
+                    simpleFact("---"),
+                    fact("expected", required),
+                    fact("but was", actual)
                 )
             }
 
@@ -355,24 +372,21 @@
         // Here, we must have reached the end of one of the iterators without finding any
         // pairs of elements that differ. If the actual iterator still has elements, they're
         // extras. If the required iterator has elements, they're missing elements.
-
         if (actualIter.hasNext()) {
-            failWithActual(
-                """
-                    Contents do not match.
-                    Expected: $required.
-                    Unexpected: ${actualIter.asSequence().toList()}.
-                """.trimIndent()
+            failWithoutActual(
+                fact("unexpected", actualIter.asSequence().toList()),
+                simpleFact("---"),
+                fact("expected", required),
+                fact("but was", actual)
             )
         }
 
         if (requiredIter.hasNext()) {
-            failWithActual(
-                """
-                    Contents do not match.
-                    Expected: $required.
-                    Missing: ${requiredIter.asSequence().toList()}.
-                """.trimIndent()
+            failWithoutActual(
+                fact("missing", requiredIter.asSequence().toList()),
+                simpleFact("---"),
+                fact("expected", required),
+                fact("but was", actual)
             )
         }
 
@@ -418,7 +432,9 @@
         val present = excluded.intersect(actual)
 
         if (present.isNotEmpty()) {
-            failWithActual("Expected not to contain any of $excluded but contained $present.")
+            failWithActual(
+                simpleFact("Expected not to contain any of $excluded but contained $present.")
+            )
         }
     }
 
@@ -439,7 +455,7 @@
      * @throws ClassCastException if any pair of elements is not mutually Comparable
      * @throws NullPointerException if any element is null
      */
-    fun isInStrictOrder() {
+    open fun isInStrictOrder() {
         isInStrictOrder(compareBy<Comparable<Any>> { it })
     }
 
@@ -470,7 +486,7 @@
      * @throws ClassCastException if any pair of elements is not mutually Comparable
      * @throws NullPointerException if any element is null
      */
-    fun isInOrder() {
+    open fun isInOrder() {
         isInOrder(compareBy<Comparable<Any>> { it })
     }
 
@@ -499,7 +515,7 @@
             .zipWithNext(::Pair)
             .forEach { (a, b) ->
                 if (!predicate(a, b)) {
-                    failWithActual(message(a, b))
+                    failWithActual(simpleFact(message(a, b)))
                 }
             }
     }
@@ -527,11 +543,13 @@
         val nonIterables = iterable.filterNot { it is Iterable<*> }
         if (nonIterables.isNotEmpty()) {
             failWithoutActual(
-                "The actual value is an Iterable, and you've written a test that compares it to " +
-                    "some objects that are not Iterables. Did you instead mean to check " +
-                    "whether its *contents* match any of the *contents* of the given values? " +
-                    "If so, call containsNoneOf(...)/containsNoneIn(...) instead. " +
-                    "Non-iterables: $nonIterables"
+                simpleFact(
+                    "The actual value is an Iterable, and you've written a test that compares it " +
+                        "to some objects that are not Iterables. Did you instead mean to check " +
+                        "whether its *contents* match any of the *contents* of the given values? " +
+                        "If so, call containsNoneOf(...)/containsNoneIn(...) instead. " +
+                        "Non-iterables: $nonIterables"
+                )
             )
         }
     }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
index 9e6da3f..ba73410 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
@@ -14,72 +14,60 @@
  * limitations under the License.
  */
 
+@file:JvmName("Kruth")
+
 package androidx.kruth
 
+import kotlin.jvm.JvmName
+
+private val ASSERT = StandardSubjectBuilder.forCustomFailureStrategy { throw it }
+
+/**
+ * Begins a call chain with the fluent Truth API. If the check made by the chain fails, it will
+ * throw [AssertionError].
+ */
+@Suppress("FunctionName") // The underscore is a weird but intentional choice.
+fun assert_() = ASSERT
+
 // The order of these declarations follows those defined in Truth, which maintains their special
 // ordering of which Subject type to prioritize from the general `assertThat` factory method. See:
 // https://github.com/google/truth/blob/master/core/src/main/java/com/google/common/truth/Truth.java
 
-fun <T : Comparable<T>> assertThat(actual: T?): ComparableSubject<T> {
-    return ComparableSubject(actual)
-}
+fun <T : Comparable<T>> assertThat(actual: T?): ComparableSubject<T> = assert_().that(actual)
 
-fun <T> assertThat(actual: T?): Subject<T> {
-    return Subject(actual)
-}
+fun <T> assertThat(actual: T?): Subject<T> = assert_().that(actual)
 
-fun <T : Throwable> assertThat(actual: T?): ThrowableSubject<T> {
-    return ThrowableSubject(actual)
-}
+fun <T : Throwable> assertThat(actual: T?): ThrowableSubject<T> = assert_().that(actual)
 
-fun assertThat(actual: Boolean?): BooleanSubject {
-    return BooleanSubject(actual)
-}
+fun assertThat(actual: Boolean?): BooleanSubject = assert_().that(actual)
 
-fun assertThat(actual: Double?): DoubleSubject {
-    return DoubleSubject(actual)
-}
+fun assertThat(actual: Double?): DoubleSubject = assert_().that(actual)
 
-fun assertThat(actual: Int?): IntegerSubject {
-    return IntegerSubject(actual)
-}
+fun assertThat(actual: Int?): IntegerSubject = assert_().that(actual)
 
-fun assertThat(actual: String?): StringSubject {
-    return StringSubject(actual)
-}
+fun assertThat(actual: String?): StringSubject = assert_().that(actual)
 
-fun <T> assertThat(actual: Iterable<T>?): IterableSubject<T> =
-    IterableSubject(actual)
+fun <T> assertThat(actual: Iterable<T>?): IterableSubject<T> = assert_().that(actual)
 
-fun <T> assertThat(actual: Array<out T>?): ObjectArraySubject<T> =
-    ObjectArraySubject(actual)
+fun <T> assertThat(actual: Array<out T>?): ObjectArraySubject<T> = assert_().that(actual)
 
-fun assertThat(actual: BooleanArray?): PrimitiveBooleanArraySubject =
-    PrimitiveBooleanArraySubject(actual)
+fun assertThat(actual: BooleanArray?): PrimitiveBooleanArraySubject = assert_().that(actual)
 
-fun assertThat(actual: ShortArray?): PrimitiveShortArraySubject =
-    PrimitiveShortArraySubject(actual)
+fun assertThat(actual: ShortArray?): PrimitiveShortArraySubject = assert_().that(actual)
 
-fun assertThat(actual: IntArray?): PrimitiveIntArraySubject =
-    PrimitiveIntArraySubject(actual)
+fun assertThat(actual: IntArray?): PrimitiveIntArraySubject = assert_().that(actual)
 
-fun assertThat(actual: LongArray?): PrimitiveLongArraySubject =
-    PrimitiveLongArraySubject(actual)
+fun assertThat(actual: LongArray?): PrimitiveLongArraySubject = assert_().that(actual)
 
-fun assertThat(actual: ByteArray?): PrimitiveByteArraySubject =
-    PrimitiveByteArraySubject(actual)
+fun assertThat(actual: ByteArray?): PrimitiveByteArraySubject = assert_().that(actual)
 
-fun assertThat(actual: CharArray?): PrimitiveCharArraySubject =
-    PrimitiveCharArraySubject(actual)
+fun assertThat(actual: CharArray?): PrimitiveCharArraySubject = assert_().that(actual)
 
-fun assertThat(actual: FloatArray?): PrimitiveFloatArraySubject =
-    PrimitiveFloatArraySubject(actual)
+fun assertThat(actual: FloatArray?): PrimitiveFloatArraySubject = assert_().that(actual)
 
-fun assertThat(actual: DoubleArray?): PrimitiveDoubleArraySubject =
-    PrimitiveDoubleArraySubject(actual)
+fun assertThat(actual: DoubleArray?): PrimitiveDoubleArraySubject = assert_().that(actual)
 
-fun <K, V> assertThat(actual: Map<K, V>?): MapSubject<K, V> =
-    MapSubject(actual)
+fun <K, V> assertThat(actual: Map<K, V>?): MapSubject<K, V> = assert_().that(actual)
 
 /**
  * Begins an assertion that, if it fails, will prepend the given message to the failure message.
@@ -94,6 +82,7 @@
  * [that][SimpleSubjectBuilder.that] method creates instances of that class.
  */
 fun <S : Subject<T>, T> assertAbout(
-    subjectFactory: Subject.Factory<S, T>,
-): SimpleSubjectBuilder<S, T> =
-    SimpleSubjectBuilder(subjectFactory = subjectFactory)
+    subjectFactory: Subject.Factory<S, T>
+): SimpleSubjectBuilder<S, T> {
+    return assert_().about(subjectFactory)
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/KruthAsserter.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/KruthAsserter.kt
deleted file mode 100644
index 6d0c4bb..0000000
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/KruthAsserter.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2023 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.kruth
-
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
-@OptIn(ExperimentalContracts::class)
-internal class KruthAsserter(
-    private val formatMessage: (String?) -> String?,
-) {
-
-    /**
-     * Fails the current test with the specified message and cause exception.
-     *
-     * @param message the message to report.
-     * @param cause the exception to set as the root cause of the reported failure.
-     */
-    fun fail(message: String? = null, cause: Throwable? = null): Nothing {
-        kotlin.test.fail(message = formatMessage(message), cause = cause)
-    }
-
-    /**
-     * Asserts that the specified value is `true`.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun assertTrue(actual: Boolean, message: String? = null) {
-        contract { returns() implies actual }
-
-        if (!actual) {
-            fail(message)
-        }
-    }
-
-    /**
-     * Asserts that the specified value is `false`.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun assertFalse(actual: Boolean, message: String? = null) {
-        contract { returns() implies !actual }
-
-        if (actual) {
-            fail(message)
-        }
-    }
-
-    /**
-     * Asserts that the specified values are equal.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun assertEquals(expected: Any?, actual: Any?, message: String? = null) {
-        assertTrue(expected == actual, message)
-    }
-
-    /**
-     * Asserts that the specified values are not equal.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun assertNotEquals(illegal: Any?, actual: Any?, message: String? = null) {
-        assertFalse(illegal == actual, message)
-    }
-
-    /**
-     * Asserts that the specified value is `null`.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun assertNull(actual: Any?, message: String? = null) {
-        contract { returns() implies (actual == null) }
-        assertTrue(actual == null, message)
-    }
-
-    /**
-     * Asserts that the specified value is not `null`.
-     *
-     * @param message the message to report if the assertion fails.
-     */
-    fun <T : Any> assertNotNull(actual: T?, message: String? = null): T {
-        contract { returns() implies (actual != null) }
-        assertFalse(actual == null, message)
-
-        return actual
-    }
-}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
index e839570..9f96669 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
@@ -19,58 +19,56 @@
 import androidx.kruth.Fact.Companion.fact
 import androidx.kruth.Fact.Companion.simpleFact
 
-class MapSubject<K, V> internal constructor(
+/**
+ * Propositions for [Map] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+open class MapSubject<K, V> protected constructor(
+    metadata: FailureMetadata,
     actual: Map<K, V>?,
-    metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Map<K, V>>(actual = actual, metadata = metadata) {
+) : Subject<Map<K, V>>(actual, metadata = metadata) {
+
+    internal constructor(actual: Map<K, V>?, metadata: FailureMetadata) : this(metadata, actual)
 
     /** Fails if the map is not empty. */
     fun isEmpty() {
-        requireNonNull(actual) { "Expected to be empty, but was null" }
-
+        requireNonNull(actual)
         if (actual.isNotEmpty()) {
-            metadata.fail("Expected to be empty, but was $actual")
+            failWithActual(simpleFact("expected to be empty"))
         }
     }
 
     /** Fails if the map is empty. */
     fun isNotEmpty() {
-        requireNonNull(actual) { "Expected to be not empty, but was null" }
-
+        requireNonNull(actual)
         if (actual.isEmpty()) {
-            metadata.fail("Expected to be not empty, but was $actual")
+            failWithoutActual(simpleFact("expected not to be empty"))
         }
     }
 
     /** Fails if expected size of map is not equal to actual. */
     fun hasSize(expectedSize: Int) {
-        require(expectedSize >= 0) { "expectedSize must be >= 0, but was $expectedSize" }
-        requireNonNull(actual) { "Expected to be empty, but was null" }
-        metadata.assertEquals(expectedSize, actual.size)
+        require(expectedSize >= 0) { "expectedSize ($expectedSize) must be >= 0" }
+        check("size").that(requireNonNull(actual).size).isEqualTo(expectedSize)
     }
 
     /** Fails if the map does not contain the given key. */
     fun containsKey(key: Any?) {
-        requireNonNull(actual) { "Expected to contain $key, but was null" }
-
-        if (!actual.containsKey(key)) {
-            metadata.fail("Expected to contain $key, but was ${actual.keys}")
-        }
+        check("keys").that(requireNonNull(actual).keys).contains(key)
     }
 
     /** Fails if the map contains the given key.  */
     fun doesNotContainKey(key: Any?) {
-        requireNonNull(actual) { "Expected not to contain $key, but was null" }
-        if (key in actual) {
-            failWithoutActual(fact("Expected not to contain", key), fact("but was", actual.keys))
-        }
+        check("keys").that(requireNonNull(actual).keys).doesNotContain(key)
     }
 
     /** Fails if the map does not contain the given entry.  */
     fun containsEntry(key: K, value: V) {
         val entry = key to value
 
-        requireNonNull(actual) { "Expected to contain $entry, but was null" }
+        requireNonNull(actual)
 
         if (actual.entries.any { (k, v) -> (k == key) && (v == value) }) {
             return
@@ -90,7 +88,7 @@
             if ((value == null) || (actualValue == null)) {
                 failWithActual(
                     fact("Expected to contain entry", entry),
-                    fact("key is present but with a different value"),
+                    simpleFact("key is present but with a different value"),
                 )
             } else {
                 failWithActual(fact("Expected to contain entry", entry))
@@ -137,7 +135,7 @@
     fun doesNotContainEntry(key: K, value: V) {
         val entry = key to value
 
-        requireNonNull(actual) { "Expected not to contain $entry, but was null" }
+        requireNonNull(actual)
 
         if (actual.entries.any { (k, v) -> (k == key) && (v == value) }) {
             failWithActual(fact("Expected not to contain", entry))
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ObjectArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ObjectArraySubject.kt
index fb4125a..eb56cb9 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ObjectArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ObjectArraySubject.kt
@@ -22,7 +22,7 @@
 class ObjectArraySubject<T> internal constructor(
     actual: Array<out T>?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Array<out T>>(actual = actual, metadata = metadata) {
+) : Subject<Array<out T>>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [ObjectArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<*> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.toList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Platform.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Platform.kt
new file mode 100644
index 0000000..5a2e72f
--- /dev/null
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Platform.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+/**
+ * Clear the stacktrace of a [Throwable]. Unlike [cleanStackTrace], which only cleans "redundant"
+ * information, this completely empties the entire stack trace.
+ */
+internal expect fun Throwable.clearStackTrace()
+
+/**
+ *  Cleans the stack trace on the given [Throwable], replacing the original stack trace
+ *  stored on the instance (see [Throwable.stackTrace]).
+ *
+ *  Removes Truth stack frames from the top and JUnit framework and reflective call frames from
+ *  the bottom. Collapses the frames for various frameworks in the middle of the trace as well.
+ */
+internal expect fun Throwable.cleanStackTrace()
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveBooleanArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveBooleanArraySubject.kt
index e260fb7..9666284 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveBooleanArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveBooleanArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveBooleanArraySubject internal constructor(
     actual: BooleanArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<BooleanArray?>(actual = actual, metadata = metadata) {
+) : Subject<BooleanArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Boolean> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveByteArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveByteArraySubject.kt
index 98ff764..f28de61 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveByteArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveByteArraySubject.kt
@@ -19,7 +19,7 @@
 class PrimitiveByteArraySubject internal constructor(
     actual: ByteArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<ByteArray?>(actual = actual, metadata = metadata) {
+) : Subject<ByteArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -49,8 +49,7 @@
 
     /** Converts this [PrimitiveByteArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Byte> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveCharArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveCharArraySubject.kt
index d11c068..8ffd9e3 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveCharArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveCharArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveCharArraySubject internal constructor(
     actual: CharArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<CharArray?>(actual = actual, metadata = metadata) {
+) : Subject<CharArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Char> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveDoubleArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveDoubleArraySubject.kt
index 8ffcce7..ce1afb2 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveDoubleArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveDoubleArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveDoubleArraySubject internal constructor(
     actual: DoubleArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<DoubleArray?>(actual = actual, metadata = metadata) {
+) : Subject<DoubleArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -93,8 +93,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Double> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveFloatArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveFloatArraySubject.kt
index c2b7deaa..f8582d6 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveFloatArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveFloatArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveFloatArraySubject internal constructor(
     actual: FloatArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<FloatArray?>(actual = actual, metadata = metadata) {
+) : Subject<FloatArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -93,8 +93,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Float> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveIntArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveIntArraySubject.kt
index b4b1a4f..6b9e942 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveIntArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveIntArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveIntArraySubject internal constructor(
     actual: IntArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<IntArray?>(actual = actual, metadata = metadata) {
+) : Subject<IntArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Int> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveLongArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveLongArraySubject.kt
index 934506b..ab8f5d7 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveLongArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveLongArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveLongArraySubject internal constructor(
     actual: LongArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<LongArray?>(actual = actual, metadata = metadata) {
+) : Subject<LongArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Long> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveShortArraySubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveShortArraySubject.kt
index d0231d7..7d1a12d 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveShortArraySubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/PrimitiveShortArraySubject.kt
@@ -22,7 +22,7 @@
 class PrimitiveShortArraySubject internal constructor(
     actual: ShortArray?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<ShortArray?>(actual = actual, metadata = metadata) {
+) : Subject<ShortArray?>(actual, metadata = metadata) {
 
     private val helper =
         HelperArraySubject(
@@ -52,8 +52,7 @@
 
     /** Converts this [PrimitiveBooleanArraySubject] to [IterableSubject].*/
     fun asList(): IterableSubject<Short> {
-        metadata.assertNotNull(actual)
-
+        requireNonNull(actual)
         return IterableSubject(actual = actual.asList(), metadata = metadata)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/SimpleSubjectBuilder.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/SimpleSubjectBuilder.kt
index 6f817d2..902d7ec 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/SimpleSubjectBuilder.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/SimpleSubjectBuilder.kt
@@ -30,7 +30,7 @@
  * into the process.
  */
 class SimpleSubjectBuilder<out S : Subject<T>, T> internal constructor(
-    private val metadata: FailureMetadata = FailureMetadata(),
+    private val metadata: FailureMetadata,
     private val subjectFactory: Subject.Factory<S, T>,
 ) {
 
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
index 1f8eab7..c4d8a3b 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
@@ -25,16 +25,23 @@
  * - For the types of [Subject] built into Kruth, directly specify the value under test
  * with [withMessage].
  */
-class StandardSubjectBuilder internal constructor(
-    internal val metadata: FailureMetadata = FailureMetadata(),
+@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
+open class StandardSubjectBuilder internal constructor(
+    metadata: FailureMetadata,
 ) : PlatformStandardSubjectBuilder by PlatformStandardSubjectBuilderImpl(metadata) {
+    internal val metadata = metadata
+        get() {
+            checkStatePreconditions()
+            return field
+        }
+
     companion object {
         /**
          * Returns a new instance that invokes the given [FailureStrategy] when a check fails.
          */
         @JvmStatic
         fun forCustomFailureStrategy(failureStrategy: FailureStrategy): StandardSubjectBuilder {
-            return StandardSubjectBuilder(FailureMetadata.forFailureStrategy(failureStrategy))
+            return StandardSubjectBuilder(FailureMetadata(failureStrategy = failureStrategy))
         }
     }
 
@@ -44,9 +51,9 @@
      * specified.
      */
     fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
-        StandardSubjectBuilder(metadata = metadata.withMessage(messageToPrepend = messageToPrepend))
+        StandardSubjectBuilder(metadata = metadata.withMessage(message = messageToPrepend))
 
-    fun <T> that(actual: T): Subject<T> =
+    fun <T> that(actual: T?): Subject<T> =
         Subject(actual = actual, metadata = metadata)
 
     fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> =
@@ -116,9 +123,11 @@
      * To set a message, first call [withMessage] (or, more commonly, use the shortcut
      * [assertWithMessage].
      */
-    fun fail(): Nothing {
-        kotlin.test.fail(metadata.formatMessage())
+    fun fail() {
+        metadata.fail()
     }
+
+    internal open fun checkStatePreconditions() {}
 }
 
 /** Platform-specific additions for [StandardSubjectBuilder]. */
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
index 014626e..dde803d 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
@@ -20,99 +20,83 @@
 import androidx.kruth.Fact.Companion.simpleFact
 
 /**
- * Propositions for string subjects.
+ * Propositions for [String] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-class StringSubject internal constructor(
+open class StringSubject protected constructor(
+    metadata: FailureMetadata,
     actual: String?,
-    metadata: FailureMetadata = FailureMetadata(),
-) : ComparableSubject<String>(actual = actual, metadata = metadata),
+) : ComparableSubject<String>(actual, metadata),
     PlatformStringSubject by PlatformStringSubjectImpl(actual, metadata) {
 
+    internal constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+
     /**
      * Fails if the string does not contain the given sequence.
      */
-    fun contains(charSequence: CharSequence) {
-        metadata.assertNotNull(actual)
-
-        metadata.assertTrue(actual.contains(charSequence)) {
-            "Expected to contain \"$charSequence\", but was: \"$actual\""
+    open fun contains(charSequence: CharSequence) {
+        if (actual == null) {
+            failWithActual("expected a string that contains", charSequence)
+        } else if (!actual.contains(charSequence)) {
+            failWithActual("expected to contain", charSequence)
         }
     }
 
     /** Fails if the string does not have the given length.  */
-    fun hasLength(expectedLength: Int) {
-        metadata.assertNotNull(actual)
-
-        metadata.assertTrue(actual.length == expectedLength) {
-            "Expected to have length $expectedLength, but was: \"$actual\""
-        }
+    open fun hasLength(expectedLength: Int) {
+        require(expectedLength >= 0) { "expectedLength($expectedLength) must be >= 0" }
+        check("length").that(requireNonNull(actual).length).isEqualTo(expectedLength)
     }
 
     /** Fails if the string is not equal to the zero-length "empty string."  */
-    fun isEmpty() {
-        metadata.assertNotNull(actual)
-        if (actual.isNotEmpty()) {
-            metadata.fail(
-                """
-                    expected to be empty
-                    | but was $actual
-                """.trimMargin()
-            )
+    open fun isEmpty() {
+        if (actual == null) {
+            failWithActual(simpleFact("expected an empty string"))
+        } else if (actual.isNotEmpty()) {
+            failWithActual(simpleFact("expected to be string"))
         }
     }
 
     /** Fails if the string is equal to the zero-length "empty string."  */
-    fun isNotEmpty() {
-        metadata.assertNotNull(actual)
-        if (actual.isEmpty()) {
-            metadata.fail("expected not to be empty")
+    open fun isNotEmpty() {
+        if (actual == null) {
+            failWithActual(simpleFact("expected a non-empty string"))
+        } else if (actual.isEmpty()) {
+            failWithoutActual(simpleFact("expected not to be empty"))
         }
     }
 
     /** Fails if the string contains the given sequence.  */
-    fun doesNotContain(string: CharSequence) {
-        metadata.assertNotNull(actual) { "expected a string that does not contain $string" }
-
-        if (actual.contains(string)) {
-            metadata.fail(
-                """
-                    expected not to contain $string
-                    | but was $actual
-                """.trimMargin()
-            )
+    open fun doesNotContain(charSequence: CharSequence) {
+        if (actual == null) {
+            failWithActual("expected a string that does not contain", charSequence)
+        } else if (actual.contains(charSequence)) {
+            failWithActual("expected not to contain", charSequence)
         }
     }
 
     /** Fails if the string does not start with the given string.  */
-    fun startsWith(string: String) {
-        metadata.assertNotNull(actual) { "expected a string that starts with $string" }
-
-        if (!actual.startsWith(string)) {
-            metadata.fail(
-                """
-                    expected to start with $string
-                    | but was $actual
-                """.trimMargin()
-            )
+    open fun startsWith(string: String) {
+        if (actual == null) {
+            failWithActual("expected a string that starts with", string)
+        } else if (!actual.startsWith(string)) {
+            failWithActual("expected to start with", string)
         }
     }
 
     /** Fails if the string does not end with the given string.  */
-    fun endsWith(string: String) {
-        metadata.assertNotNull(actual) { "expected a string that ends with $string" }
-
-        if (!actual.endsWith(string)) {
-            metadata.fail(
-                """
-                    expected to end with $string
-                    | but was $actual
-                """.trimMargin()
-            )
+    open fun endsWith(string: String) {
+        if (actual == null) {
+            failWithActual("expected a string that ends with", string)
+        } else if (!actual.endsWith(string)) {
+            failWithActual("expected to end with", string)
         }
     }
 
     /** Fails if the string does not match the given [regex]. */
-    fun matches(regex: String) {
+    open fun matches(regex: String) {
         matchesImpl(regex.toRegex()) {
             "Looks like you want to use .isEqualTo() for an exact equality assertion."
         }
@@ -126,7 +110,7 @@
     }
 
     /** Fails if the string matches the given regex.  */
-    fun doesNotMatch(regex: String) {
+    open fun doesNotMatch(regex: String) {
         doesNotMatchImpl(regex.toRegex())
     }
 
@@ -136,13 +120,22 @@
     }
 
     /** Fails if the string does not contain a match on the given regex.  */
+    open fun containsMatch(regex: String) {
+        containsMatchImpl(regex.toRegex())
+    }
+
+    /** Fails if the string does not contain a match on the given regex.  */
     fun containsMatch(regex: Regex) {
         containsMatchImpl(regex)
     }
 
-    /** Fails if the string does not contain a match on the given regex.  */
-    fun containsMatch(regex: String) {
-        containsMatchImpl(regex.toRegex())
+    /** Fails if the string contains a match on the given regex.  */
+    open fun doesNotContainMatch(regex: String) {
+        if (actual == null) {
+            failWithActual("expected a string that does not contain a match for", regex)
+        } else if (regex.toRegex().containsMatchIn(actual)) {
+            failWithActual("expected not to contain a match for", regex)
+        }
     }
 
     /** Fails if the string contains a match on the given regex.  */
@@ -150,17 +143,6 @@
         doesNotContainMatchImpl(regex)
     }
 
-    /** Fails if the string contains a match on the given regex.  */
-    fun doesNotContainMatch(regex: String) {
-        if (actual == null) {
-            failWithActual("expected a string that does not contain a match for", regex)
-        }
-
-        if (regex.toRegex().containsMatchIn(actual)) {
-            failWithActual("expected not to contain a match for", regex)
-        }
-    }
-
     /**
      * Returns a [StringSubject]-like instance that will ignore the case of the characters.
      *
@@ -168,7 +150,7 @@
      * after calling [Char.lowercaseChar] or after calling [Char.uppercaseChar].
      * Note that this is independent of any locale.
      */
-    fun ignoringCase(): CaseInsensitiveStringComparison =
+    open fun ignoringCase(): CaseInsensitiveStringComparison =
         CaseInsensitiveStringComparison()
 
     inner class CaseInsensitiveStringComparison internal constructor() {
@@ -182,22 +164,24 @@
          * Example: "abc" is equal to "ABC", but not to "abcd".
          */
         fun isEqualTo(expected: String?) {
-            when {
-                (actual == null) && (expected != null) ->
-                    metadata.fail(
-                        "Expected a string equal to \"$expected\" (case is ignored), but was null"
-                    )
-
-                (expected == null) && (actual != null) ->
-                    metadata.fail(
-                        "Expected a string that is null (null reference), but was \"$actual\""
-                    )
-
-                !actual.equals(expected, ignoreCase = true) ->
-                    metadata.fail(
-                        "Expected a string equal to \"$expected\" (case is ignored), " +
-                            "but was \"$actual\""
-                    )
+            if ((actual == null) && (expected != null)) {
+                failWithoutActual(
+                    fact("expected a string that is equal to", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
+            } else if ((expected == null) && (actual != null)) {
+                failWithoutActual(
+                    fact("expected", "null (null reference)"),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
+            } else if (!actual.equals(expected, ignoreCase = true)) {
+                failWithoutActual(
+                    fact("expected", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
             }
         }
 
@@ -206,17 +190,17 @@
          * equality is the same as for the [isEqualTo] method.
          */
         fun isNotEqualTo(unexpected: String?) {
-            when {
-                (actual == null) && (unexpected == null) ->
-                    metadata.fail(
-                        "Expected a string not equal to null (null reference), but it was null"
-                    )
-
-                actual.equals(unexpected, ignoreCase = true) ->
-                    metadata.fail(
-                        "Expected a string not equal to \"$unexpected\" (case is ignored), " +
-                            "but it was equal. Actual string: \"$actual\"."
-                    )
+            if ((actual == null) && (unexpected == null)) {
+                failWithoutActual(
+                    fact("expected a string that is not equal to", "null (null reference)"),
+                    simpleFact("(case is ignored)")
+                )
+            } else if (actual.equals(unexpected, ignoreCase = true)) {
+                failWithoutActual(
+                    fact("expected not to be", unexpected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
             }
         }
 
@@ -224,36 +208,35 @@
         fun contains(expected: CharSequence?) {
             requireNonNull(expected)
 
-            when {
-                actual == null ->
-                    metadata.fail(
-                        "Expected a string that contains \"$expected\" (case is ignored), " +
-                            "but was null"
-                    )
-
-                !actual.contains(expected, ignoreCase = true) ->
-                    metadata.fail(
-                        "Expected to contain \"$expected\" (case is ignored), but was \"$actual\""
-                    )
+            if (actual == null) {
+                failWithoutActual(
+                    fact("expected a string that contains", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
+            } else if (!actual.contains(expected, ignoreCase = true)) {
+                failWithoutActual(
+                    fact("expected to contain", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
             }
         }
 
         /** Fails if the string contains the given sequence (while ignoring case).  */
-        fun doesNotContain(expected: CharSequence?) {
-            requireNonNull(expected)
-
-            when {
-                actual == null ->
-                    metadata.fail(
-                        "Expected a string that does not contain \"$expected\" " +
-                            "(case is ignored), but was null"
-                    )
-
-                actual.contains(expected, ignoreCase = true) ->
-                    metadata.fail(
-                        "Expected a string that does not contain \"$expected\" " +
-                            "(case is ignored), but it was. Actual string: \"$actual\"."
-                    )
+        fun doesNotContain(expected: CharSequence) {
+            if (actual == null) {
+                failWithoutActual(
+                    fact("expected a string that does not contain", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
+            } else if (actual.contains(expected, ignoreCase = true)) {
+                failWithoutActual(
+                    fact("expected not to contain", expected),
+                    fact("but was", actual),
+                    simpleFact("(case is ignored)")
+                )
             }
         }
     }
@@ -262,9 +245,7 @@
 internal inline fun Subject<String>.matchesImpl(regex: Regex, equalToStringErrorMsg: () -> String) {
     if (actual == null) {
         failWithActualInternal("Expected a string that matches", regex)
-    }
-
-    if (actual.matches(regex)) {
+    } else if (actual.matches(regex)) {
         return
     }
 
@@ -275,16 +256,14 @@
             simpleFact(equalToStringErrorMsg()),
         )
     } else {
-        failWithActualInternal("Expected to match", regex);
+        failWithActualInternal("Expected to match", regex)
     }
 }
 
 internal fun Subject<String>.doesNotMatchImpl(regex: Regex) {
     if (actual == null) {
         failWithActualInternal("Expected a string that does not match", regex)
-    }
-
-    if (actual.matches(regex)) {
+    } else if (actual.matches(regex)) {
         failWithActualInternal("Expected not to match", regex)
     }
 }
@@ -292,9 +271,7 @@
 internal fun Subject<String>.containsMatchImpl(regex: Regex) {
     if (actual == null) {
         failWithActualInternal("Expected a string that contains a match for", regex)
-    }
-
-    if (!regex.containsMatchIn(actual)) {
+    } else if (!regex.containsMatchIn(actual)) {
         failWithActualInternal("Expected to contain a match for", regex)
     }
 }
@@ -302,14 +279,15 @@
 internal fun Subject<String>.doesNotContainMatchImpl(regex: Regex) {
     if (actual == null) {
         failWithActualInternal("expected a string that does not contain a match for", regex)
+        return
     }
 
     val result = regex.find(actual)
     if (result != null) {
         failWithoutActualInternal(
-            fact("Expected not to contain a match for", regex),
+            fact("expected not to contain a match for", regex),
             fact("but contained", result.value),
-            fact("Full string", actual)
+            fact("full string", actual)
         )
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
index 023b5ea..43e3ff0 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
@@ -17,7 +17,9 @@
 package androidx.kruth
 
 import androidx.kruth.Fact.Companion.fact
-import kotlin.jvm.JvmOverloads
+import androidx.kruth.Fact.Companion.simpleFact
+import androidx.kruth.OldAndNewValuesAreSimilar.DIFFERENT
+import androidx.kruth.OldAndNewValuesAreSimilar.SIMILAR
 import kotlin.reflect.typeOf
 
 // As opposed to Truth, which limits visibility on `actual` and the generic type, we purposely make
@@ -29,11 +31,17 @@
  * contains [isEqualTo] and [isInstanceOf], and [StringSubject] contains [StringSubject.contains]
  *
  * To create a [Subject] instance, most users will call an [assertThat] method.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-open class Subject<out T>(
+open class Subject<out T> protected constructor(
+    metadata: FailureMetadata,
     val actual: T?,
-    val metadata: FailureMetadata = FailureMetadata(),
 ) {
+    internal constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
+    val metadata: FailureMetadata by lazy { metadata.updateForSubject(this) }
 
     protected fun check(): StandardSubjectBuilder = StandardSubjectBuilder(metadata = metadata)
 
@@ -87,8 +95,13 @@
     open fun isSameInstanceAs(expected: Any?) {
         if (actual !== expected) {
             metadata.fail(
-                "Expected ${actual.toStringForAssert()} to be the same instance as " +
-                    "${expected.toStringForAssert()}, but was not"
+                listOf(
+                    // TODO(dustinlam): This error string does not match the one from Truth.
+                    simpleFact(
+                        "Expected ${actual.toStringForAssert()} to be the same instance  as " +
+                            "${expected.toStringForAssert()}, but was not"
+                    )
+                )
             )
         }
     }
@@ -96,8 +109,8 @@
     /** Fails if the subject is the same instance as the given object.  */
     open fun isNotSameInstanceAs(unexpected: Any?) {
         if (actual === unexpected) {
-            metadata.fail(
-                "Expected ${actual.toStringForAssert()} not to be specific instance, but it was"
+            failWithoutActual(
+                fact("expected not to be specific instance", actual)
             )
         }
     }
@@ -105,76 +118,138 @@
     /**
      * Fails if the subject is not an instance of the given class.
      */
+    // TODO(dustinlam): Add a JVM-only non inline version for compatibility and java users.
     inline fun <reified V> isInstanceOf() {
         if (actual !is V) {
-            doFail("Expected $actual to be an instance of ${typeOf<V>()} but it was not")
+            doFail(
+                fact("expected instance of", typeOf<V>()),
+                fact("but was", actual.toString())
+            )
         }
     }
 
     /**
      * Fails if the subject is an instance of the given class.
      */
+    // TODO(dustinlam): Add a JVM-only non inline version for compatibility and java users.
     inline fun <reified V> isNotInstanceOf() {
         if (actual is V) {
-            doFail("Expected $actual to be not an instance of ${typeOf<V>()} but it was")
+            doFail(
+                fact("expected not to be an instance of", typeOf<V>()),
+                fact("but was", actual.toString())
+            )
         }
     }
 
     // TODO(KT-20427): Only needed to enable extensions in internal sources.
     @Suppress("NOTHING_TO_INLINE")
-    internal inline fun failWithActualInternal(key: String, value: Any? = null): Nothing {
+    internal inline fun failWithActualInternal(key: String, value: Any? = null) {
         failWithActual(key = key, value = value)
     }
 
     // TODO(KT-20427): Only needed to enable extensions in internal sources.
     @Suppress("NOTHING_TO_INLINE")
-    internal inline fun failWithActualInternal(vararg facts: Fact): Nothing {
-        failWithActual(*facts)
+    internal inline fun failWithActualInternal(fact: Fact, vararg facts: Fact) {
+        failWithActual(fact, *facts)
     }
 
-    @JvmOverloads
-    protected fun failWithActual(key: String, value: Any? = null): Nothing {
+    /**
+     * Fails, reporting a message with two "[facts][Fact]":
+     *  * _key_: _value_
+     *  * but was: _actual value_.
+     *
+     * This is the simplest failure API. For more advanced needs, see
+     * `failWithActual(Fact, Fact...)` the other overload, and `failWithoutActual(Fact, Fact...)`.
+     *
+     * Example usage: The check `contains(String)` calls
+     * `failWithActual("expected to contain", string)`.
+     */
+    protected fun failWithActual(key: String, value: Any?) {
         failWithActual(fact(key, value))
     }
 
-    protected fun failWithActual(vararg facts: Fact): Nothing {
+    /**
+     * Fails, reporting a message with the given facts, followed by an automatically added fact of
+     * the form:
+     *  * but was: _actual value_.
+     *
+     * If you have only one fact to report (and it's a key-value [Fact]), prefer
+     * `failWithActual(String, Any?)`, the simpler overload).
+     *
+     * Example usage: The check `isEmpty()` calls
+     * `failWithActual(simpleFact("expected to be empty"))`.
+     */
+    protected fun failWithActual(first: Fact, vararg rest: Fact) {
         metadata.fail(
-            Fact.makeMessage(
-                emptyList(),
-                facts.asList() + fact("but was", actual.toString()),
+            listOf(
+                first,
+                *rest,
+                fact("but was", actual)
             )
         )
     }
 
     // TODO(KT-20427): Only needed to enable extensions in internal sources.
     @Suppress("NOTHING_TO_INLINE")
-    internal inline fun failWithoutActualInternal(vararg facts: Fact): Nothing {
-        failWithoutActual(*facts)
+    internal inline fun failWithoutActualInternal(first: Fact, vararg rest: Fact) {
+        failWithoutActual(first, *rest)
     }
 
-    @JvmOverloads
-    protected fun failWithoutActual(key: String, value: Any? = null): Nothing {
-        failWithoutActual(fact(key, value))
+    /**
+     * Assembles a failure message without a given subject and passes it to the FailureStrategy
+     *
+     * @param check the check being asserted
+     */
+    @Deprecated(
+        "Prefer to construct Fact-style methods, typically by using " +
+            "failWithoutActual(Fact, Fact...). However, if you want to preserve your exact " +
+            "failure message as a migration aid, you can inline this method (and then inline the " +
+            "resulting method call, as well).",
+        ReplaceWith(
+            "failWithoutActual(simpleFact(\"Not true that the subject \$check\"))",
+            "androidx.kruth.Fact.Companion.simpleFact"
+        )
+    )
+    internal fun failWithoutActual(check: String) {
+        failWithoutActual(simpleFact("Not true that the subject $check"))
     }
 
-    protected fun failWithoutActual(vararg facts: Fact): Nothing {
+    /**
+     * Fails, reporting a message with the given facts, _without automatically adding the actual
+     * value._
+     *
+     * Most failure messages should report the actual value, so most checks should call
+     * `failWithActual(Fact, Fact...)` instead. However, [failWithoutActual] is useful in some
+     * cases:
+     *  * when the actual value is obvious from the rest of the message. For example, `isNotEmpty()`
+     *    calls `failWithoutActual(simpleFact("expected not to be empty")`.
+     *  * when the actual value shouldn't come last or should have a different key than the default
+     *    of "but was." For example, `isNotWithin(...).of(...)` calls `failWithoutActual` so that it
+     *    can put the expected and actual values together, followed by the tolerance.
+     *
+     * Example usage: The check `isEmpty()` calls
+     * `failWithActual(simpleFact("expected to be empty"))`.
+     */
+    protected fun failWithoutActual(first: Fact, vararg rest: Fact) {
         metadata.fail(
-            Fact.makeMessage(
-                emptyList(),
-                facts.asList(),
-            )
+            buildList {
+                add(first)
+                addAll(rest)
+            }
         )
     }
 
-    @PublishedApi
-    internal fun doFail(message: String) {
-        metadata.fail(message = message)
+    @PublishedApi // Required to allow isInstanceOf to be implemented via inline reified type.
+    internal fun doFail(vararg facts: Fact) {
+        metadata.fail(facts.asList())
     }
 
     /** Fails unless the subject is equal to any element in the given [iterable]. */
     open fun isIn(iterable: Iterable<*>?) {
         if (actual !in requireNonNull(iterable)) {
-            metadata.fail("Expected $actual to be in $iterable, but was not")
+            metadata.fail(
+                listOf(simpleFact("Expected $actual to be in $iterable, but was not"))
+            )
         }
     }
 
@@ -186,7 +261,7 @@
     /** Fails if the subject is equal to any element in the given [iterable]. */
     open fun isNotIn(iterable: Iterable<*>?) {
         if (actual in requireNonNull(iterable)) {
-            metadata.fail("Expected $actual not to be in $iterable, but it was")
+            failWithActual(fact("expected not to be any of", iterable))
         }
     }
 
@@ -202,9 +277,11 @@
     }
 
     private fun Any?.standardIsNotEqualTo(unexpected: Any?) {
-        metadata.assertFalse(compareForEquality(unexpected)) {
-            "expected ${toStringForAssert()} not be equal to ${unexpected.toStringForAssert()}, " +
-                "but it was"
+        if (compareForEquality(unexpected)) {
+            failWithoutActual(
+                fact("expected not to be", unexpected),
+                fact("but was; string representation of actual value", actual)
+            )
         }
     }
 
@@ -249,7 +326,9 @@
     private fun Any?.integralValue(): Long = when (this) {
         is Char -> code.toLong()
         is Number -> toLong()
-        else -> metadata.fail("$this must be either a Char or a Number.")
+        // This is intentionally AssertionError and not AssertionErrorWithFacts to stay behaviour
+        // compatible with Truth.
+        else -> throw AssertionError("$this must be either a Char or a Number.")
     }
 
     private fun Any?.toStringForAssert(): String = when {
@@ -259,6 +338,62 @@
     }
 
     /**
+     * Returns a builder for creating a derived subject.
+     *
+     * Derived subjects retain the [FailureStrategy] and [StandardSubjectBuilder.withMessage] of the
+     * current subject, and in some cases, they automatically supplement their failure message with
+     * information about the original subject.
+     *
+     * For example, [ThrowableSubject.hasMessageThat], which returns a [StringSubject],
+     * is implemented with `check("getMessage()").that(actual.getMessage())`.
+     *
+     * The arguments to [check] describe how the new subject was derived from the old,
+     * formatted like a chained method call. This allows Truth to include that information in its
+     * failure messages. For example, `assertThat(caught).hasCauseThat().hasMessageThat()` will
+     * produce a failure message that includes the string "throwable.getCause().getMessage()," thanks
+     * to internal [check] calls that supplied "getCause()" and "getMessage()" as arguments.
+     *
+     * If the method you're delegating to accepts parameters, you can pass [check] a format
+     * string. For example, [MultimapSubject.valuesForKey] calls `check("valuesForKey(%s)", key)`.
+     *
+     * If you aren't really delegating to an instance method on the actual value -- maybe you're
+     * calling a static method, or you're calling a chain of several methods -- you can supply
+     * whatever string will be most useful to users. For example, if you're delegating to
+     * `getOnlyElement(actual.colors())`, you might call `check("onlyColor()")`.
+     *
+     * @param format a template with `%s` placeholders
+     * @param args the arguments to be inserted into those placeholders
+     */
+    protected fun check(format: String, vararg args: Any): StandardSubjectBuilder {
+        return doCheck(DIFFERENT, format, args)
+    }
+
+    // TODO(b/134064106): Figure out a public API for this.
+    internal fun checkNoNeedToDisplayBothValues(
+        format: String,
+        vararg args: Any
+    ): StandardSubjectBuilder {
+        return doCheck(SIMILAR, format, args)
+    }
+
+    private fun doCheck(
+        valuesAreSimilar: OldAndNewValuesAreSimilar,
+        format: String,
+        args: Array<out Any>?
+    ): StandardSubjectBuilder {
+        val message: String by lazy { lenientFormat(format, args) }
+        return StandardSubjectBuilder(
+            metadata.updateForCheckCall(valuesAreSimilar) { input: String? ->
+                "$input.$message"
+            }
+        )
+    }
+
+    protected fun ignoreCheck(): StandardSubjectBuilder {
+        return StandardSubjectBuilder.forCustomFailureStrategy {}
+    }
+
+    /**
      * In a fluent assertion chain, the argument to the common overload of
      * [StandardSubjectBuilder.about], the method that specifies what kind of [Subject] to create.
      *
@@ -275,3 +410,36 @@
         fun createSubject(metadata: FailureMetadata, actual: ActualT): SubjectT
     }
 }
+
+private fun lenientFormat(template: String, vararg args: Any?): String {
+    val argsToLenientStrings = args.map {
+        if (it == null) {
+            return@map "null"
+        }
+
+        try {
+            it.toString()
+        } catch (e: Exception) {
+            // Default toString() behavior - see Object.toString()
+            val className = it::class.simpleName
+            val exceptionClassName = e::class.simpleName
+            val hashCodeHexString = it.hashCode().toUInt().toString(16)
+            "<$$className@$hashCodeHexString threw $exceptionClassName>"
+        }
+    }
+
+    var i = 0
+    val formattedString = template.replace(Regex("%s")) { matchResult ->
+        val result = when {
+            i <= argsToLenientStrings.lastIndex -> argsToLenientStrings[i]
+            else -> matchResult.value
+        }
+        i++
+        return@replace result
+    }
+
+    return when {
+        i >= argsToLenientStrings.size -> formattedString
+        else -> "$formattedString [${argsToLenientStrings.subList(i, argsToLenientStrings.size)}]"
+    }
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
index c0d1a05..e16191c 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
@@ -18,11 +18,16 @@
 
 /**
  * Propositions for [Throwable] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-class ThrowableSubject<out T : Throwable> internal constructor(
+open class ThrowableSubject<out T : Throwable> protected constructor(
+    metadata: FailureMetadata,
     actual: T?,
-    metadata: FailureMetadata = FailureMetadata(),
-) : Subject<T>(actual = actual, metadata = metadata) {
+) : Subject<T>(actual, metadata) {
+
+    internal constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
 
     /**
      * Returns a [StringSubject] to make assertions about the throwable's message.
@@ -36,11 +41,20 @@
      * cause. This method can be invoked repeatedly (e.g.
      * `assertThat(e).hasCauseThat().hasCauseThat()....` to assert on a particular indirect cause.
      */
+    // Any Throwable is fine, and we use plain Throwable to emphasize that it's not used "for real."
     fun hasCauseThat(): ThrowableSubject<Throwable> {
+        // provides a more helpful error message if hasCauseThat() methods are chained too deep
+        // e.g. assertThat(new Exception()).hCT().hCT()....
+        // TODO(diamondm) in keeping with other subjects' behavior this should still NPE if the subject
+        //  *itself* is null, since there's no context to lose. See also b/37645583
         if (actual == null) {
-            metadata.fail("Causal chain is not deep enough - add a .isNotNull() check?")
+            check("cause")
+                .withMessage("Causal chain is not deep enough - add a .isNotNull() check?")
+                .fail()
+
+            return ignoreCheck().that(Throwable())
         }
 
-        return ThrowableSubject(actual = actual.cause, metadata = metadata)
+        return check("cause").that(actual.cause)
     }
 }
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
index 5ffb9fa..fbf3b79 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
@@ -21,7 +21,8 @@
 
 /**
  * Same as [requireNotNull] but throws [NullPointerException] instead of [IllegalArgumentException].
- * Used for better behaviour compatibility with Truth.
+ *
+ * Used for better behaviour compatibility with Truth, which uses Guava's checkNotNull.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T : Any> requireNonNull(
@@ -65,6 +66,9 @@
         retainMatchingToString(items).isNotEmpty()
     }
 
+// TODO(b/317811086): Truth does some extra String processing here for nested classes and Subjects
+//  for j2cl that we do not yet have implemented. It is possible we don't need anything, but we need
+//  to double check and add a test.
 internal fun Any?.typeName(): String =
     when (this) {
         null -> {
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ChainingTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ChainingTest.kt
index a7971fd..f9acaa5 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ChainingTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ChainingTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.kruth
 
+import androidx.kruth.Subject.Factory
 import kotlin.test.Test
 import kotlin.test.assertFailsWith
 import kotlin.test.asserter
@@ -51,11 +52,12 @@
     private class ObjectSubject(
         metadata: FailureMetadata = FailureMetadata(),
         actual: Any?,
-    ) : Subject<Any>(actual = actual, metadata = metadata) {
+    ) : Subject<Any>(actual, metadata = metadata) {
         companion object {
-            val FACTORY: Factory<ObjectSubject, Any> = Factory<ObjectSubject, Any> {
-                    metadata, actual -> ObjectSubject(metadata, actual)
-            }
+            val FACTORY: Factory<ObjectSubject, Any> =
+                Factory<ObjectSubject, Any> { metadata, actual ->
+                    ObjectSubject(metadata, actual)
+                }
 
             // entry point
             fun assertThat(obj: Any): ObjectSubject {
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/FactTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/FactTest.kt
index 72d0aa7..e510683 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/FactTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/FactTest.kt
@@ -104,7 +104,7 @@
 
     @Test
     fun failWithActual_simpleFact() {
-        val subject = object : Subject<Int>(actual = 0) {
+        val subject = object : Subject<Int>(0, metadata = FailureMetadata()) {
             fun fail() {
                 failWithActual(simpleFact("Expected something else"))
             }
@@ -120,7 +120,7 @@
 
     @Test
     fun failWithActual_multipleFacts() {
-        val subject = object : Subject<Int>(actual = 0) {
+        val subject = object : Subject<Int>(0, metadata = FailureMetadata()) {
             fun fail() {
                 failWithActual(
                     simpleFact("Expected something else"),
@@ -140,7 +140,7 @@
 
     @Test
     fun failWithoutActual_simpleFact() {
-        val subject = object : Subject<Int>(actual = 0) {
+        val subject = object : Subject<Int>(0, metadata = FailureMetadata()) {
             fun fail() {
                 failWithoutActual(simpleFact("Expected something else"))
             }
@@ -156,7 +156,7 @@
     @Test
     fun failWithoutActual_multipleFacts() {
         val subject =
-            object : Subject<Int>(actual = 0) {
+            object : Subject<Int>(0, metadata = FailureMetadata()) {
                 fun fail() {
                     failWithoutActual(
                         simpleFact("Expected something else"),
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/KruthAsserterTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/KruthAsserterTest.kt
deleted file mode 100644
index 0adf072..0000000
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/KruthAsserterTest.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2023 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.kruth
-
-import kotlin.test.Test
-
-class KruthAsserterTest {
-
-    private val asserter = KruthAsserter(formatMessage = { "$it." })
-
-    @Test
-    fun fail() {
-        assertFailsWithMessage("Msg.") {
-            asserter.fail(message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertTrue_success() {
-        asserter.assertTrue(actual = true)
-    }
-
-    @Test
-    fun assertTrue_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertTrue(actual = false, message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertFalse_success() {
-        asserter.assertFalse(actual = false)
-    }
-
-    @Test
-    fun assertFalse_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertFalse(actual = true, message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertEquals_success() {
-        asserter.assertEquals(expected = 0, actual = 0)
-    }
-
-    @Test
-    fun assertEquals_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertEquals(expected = 0, actual = 1, message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertNotEquals_success() {
-        asserter.assertNotEquals(illegal = 0, actual = 1)
-    }
-
-    @Test
-    fun assertNotEquals_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertNotEquals(illegal = 0, actual = 0, message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertNull_success() {
-        asserter.assertNull(actual = null)
-    }
-
-    @Test
-    fun assertNull_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertNull(actual = 0, message = "Msg")
-        }
-    }
-
-    @Test
-    fun assertNotNull_success() {
-        asserter.assertNotNull(actual = 0)
-    }
-
-    @Test
-    fun assertNotNull_failsWithMessage() {
-        assertFailsWithMessage("Msg.") {
-            asserter.assertNotNull(actual = null, message = "Msg")
-        }
-    }
-}
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ObjectArraySubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ObjectArraySubjectTest.kt
index 4ece17a..b3c1ce1 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ObjectArraySubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ObjectArraySubjectTest.kt
@@ -18,7 +18,6 @@
 
 import kotlin.test.Test
 import kotlin.test.assertFailsWith
-import kotlin.test.fail
 
 class ObjectArraySubjectTest {
 
@@ -62,11 +61,8 @@
 
     @Test
     fun hasLengthNegative() {
-        try {
+        assertFailsWith<IllegalArgumentException> {
             assertThat(arrayOf(2, 5)).hasLength(-1)
-            fail("Should have failed")
-        } catch (expected: IllegalArgumentException) {
-            // no-op
         }
     }
 
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/PrimitiveIntArraySubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/PrimitiveIntArraySubjectTest.kt
index 8a7b1a5..ecb71db 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/PrimitiveIntArraySubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/PrimitiveIntArraySubjectTest.kt
@@ -18,7 +18,6 @@
 
 import kotlin.test.Test
 import kotlin.test.assertFailsWith
-import kotlin.test.fail
 
 class PrimitiveIntArraySubjectTest {
 
@@ -53,11 +52,8 @@
 
     @Test
     fun hasLengthNegative() {
-        try {
+        assertFailsWith<IllegalArgumentException> {
             assertThat(intArrayOf(2, 5)).hasLength(-1)
-            fail("Should have failed.")
-        } catch (expected: IllegalArgumentException) {
-            // no-op
         }
     }
 
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/StringSubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/StringSubjectTest.kt
index c0df0a0..c15a389 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/StringSubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/StringSubjectTest.kt
@@ -67,7 +67,7 @@
 
     @Test
     fun hasLengthNegative() {
-        assertFailsWith<AssertionError> {
+        assertFailsWith<IllegalArgumentException> {
             assertThat("kurt").hasLength(-1)
         }
     }
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/SubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/SubjectTest.kt
index 569d382..6ea55e4 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/SubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/SubjectTest.kt
@@ -702,7 +702,7 @@
     fun failWithActual_printsAllMessagesPlusActualValue() {
         val subject =
             object : Subject<Int>(
-                actual = 0,
+                0,
                 metadata = FailureMetadata(messagesToPrepend = listOf("msg1", "msg2")),
             ) {
                 fun fail() {
@@ -725,7 +725,7 @@
     fun failWithActual_printsAllMessagesPlusMultilineActualValue() {
         val subject =
             object : Subject<String>(
-                actual = "a\nb",
+                "a\nb",
                 metadata = FailureMetadata(messagesToPrepend = listOf("msg1", "msg2")),
             ) {
                 fun fail() {
@@ -750,7 +750,7 @@
     fun failWithoutActual_printsAllMessagesPlusActualValue() {
         val subject =
             object : Subject<Int>(
-                actual = 0,
+                0,
                 metadata = FailureMetadata(messagesToPrepend = listOf("msg1", "msg2")),
             ) {
                 fun fail() {
@@ -772,7 +772,7 @@
     fun failWithoutActual_printsAllMessagesPlusMultilineActualValue() {
         val subject =
             object : Subject<String>(
-                actual = "a\nb",
+                "a\nb",
                 metadata = FailureMetadata(messagesToPrepend = listOf("msg1", "msg2")),
             ) {
                 fun fail() {
@@ -818,7 +818,10 @@
 /**
  * Copied from Truth.
  */
-private class ForbidsEqualityChecksSubject(actual: Any?) : Subject<Any>(actual) {
+private class ForbidsEqualityChecksSubject(
+    actual: Any?
+) : Subject<Any>(actual, metadata = FailureMetadata()) {
+
     // Not sure how to feel about this, but people do it:
     override fun isEqualTo(expected: Any?) {
         throw UnsupportedOperationException()
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/extension/EmployeeSubject.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/extension/EmployeeSubject.kt
index f09d55a..ad59d55 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/extension/EmployeeSubject.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/extension/EmployeeSubject.kt
@@ -27,7 +27,7 @@
 class EmployeeSubject(
     metadata: FailureMetadata = FailureMetadata(),
     actual: Employee?,
-) : Subject<Employee>(actual = actual, metadata = metadata) {
+) : Subject<Employee>(actual, metadata = metadata) {
 
     companion object {
         // User-defined entry point
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/BigDecimalSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/BigDecimalSubject.jvm.kt
index fe03ab5..ae0884a 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/BigDecimalSubject.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/BigDecimalSubject.jvm.kt
@@ -26,7 +26,7 @@
 class BigDecimalSubject internal constructor(
     actual: BigDecimal?,
     metadata: FailureMetadata = FailureMetadata(),
-) : ComparableSubject<BigDecimal>(actual = actual, metadata = metadata) {
+) : ComparableSubject<BigDecimal>(actual, metadata = metadata) {
 
     /**
      * Fails if the subject's value is not equal to the value of the given [BigDecimal]. (i.e.,
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ClassSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ClassSubject.jvm.kt
index 278ac09..c29944d 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ClassSubject.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ClassSubject.jvm.kt
@@ -22,7 +22,7 @@
 class ClassSubject internal constructor(
     actual: Class<*>?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Class<*>>(actual = actual, metadata = metadata) {
+) : Subject<Class<*>>(actual, metadata = metadata) {
 
     /**
      * Fails if this class or interface is not the same as or a subclass or subinterface of, the
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Expect.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Expect.jvm.kt
new file mode 100644
index 0000000..8010c63
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Expect.jvm.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.TestPhase.AFTER
+import androidx.kruth.TestPhase.BEFORE
+import androidx.kruth.TestPhase.DURING
+import com.google.common.base.Preconditions.checkState
+import com.google.common.base.Throwables.getStackTraceAsString
+import org.junit.AssumptionViolatedException
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A [TestRule] that batches up all failures encountered during a test, and reports them all
+ * together at the end (similar to [ErrorCollector][org.junit.rules.ErrorCollector]). It is also
+ * useful for making assertions from other threads or from within callbacks whose exceptions would
+ * be swallowed or logged, rather than propagated out to fail the test.
+ * ([AssertJ](https://joel-costigliola.github.io/assertj) has a similar feature called
+ * "soft assertions"; however, soft assertions are not safe for concurrent use.)
+ *
+ * Usage:
+ *
+ * ```
+ * @get:Rule
+ * val expect: Expect = Expect.create()
+ *
+ * ...
+ *
+ *    expect.that(results).containsExactly(...)
+ *    expect.that(errors).isEmpty()
+ * ```
+ *
+ * If both of the assertions above fail, the test will fail with an exception that contains
+ * information about both.
+ *
+ * `Expect` may be used concurrently from multiple threads. However, multithreaded tests still
+ * require care:
+ *
+ * * `Expect` has no way of knowing when all your other test threads are done. It simply
+ *       checks for failures when the main thread finishes executing the test method. Thus, you must
+ *       ensure that any background threads complete their assertions before then, or your test may
+ *       ignore their results.
+ * * Assertion failures are not the only exceptions that may occur in other threads. For maximum
+ *       safety, multithreaded tests should check for such exceptions regardless of whether they use
+ *       `Expect`. (Typically, this means calling `get()` on any `Future` returned
+ *       by a method like `executor.submit(...)`. It might also include checking for
+ *       unexpected log messages or reading metrics that count failures.) If your tests already
+ *       check for exceptions from a thread, then that will any cover exception from plain
+ *       `assertThat`.
+ *
+ * To record failures for the purpose of testing that an assertion fails when it should, see
+ * [ExpectFailure].
+ *
+ * For more on this class, see [the documentation page](https://truth.dev/expect).
+ */
+// TODO(dustinlam): This class needs to be made thread-safe as Truth's version is synchronized.
+class Expect private constructor(
+    private val gatherer: ExpectationGatherer,
+) : StandardSubjectBuilder(FailureMetadata(failureStrategy = gatherer)), TestRule {
+
+    companion object {
+        /** Creates a new instance. */
+        @JvmStatic
+        fun create(): Expect = Expect(ExpectationGatherer())
+    }
+
+    fun hasFailures(): Boolean {
+        return gatherer.hasFailures()
+    }
+
+    override fun checkStatePreconditions() {
+        gatherer.checkInRuleContext()
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                gatherer.enterRuleContext()
+                var caught: Throwable? = null
+                try {
+                    base.evaluate()
+                } catch (t: Throwable) {
+                    caught = t
+                } finally {
+                    gatherer.leaveRuleContext(caught)
+                }
+            }
+        }
+    }
+}
+
+private class ExpectationGatherer : FailureStrategy {
+    private val failures: MutableList<AssertionError> = ArrayList()
+    private var inRuleContext: TestPhase = BEFORE
+
+    override fun fail(failure: AssertionError) {
+        record(failure)
+    }
+
+    fun enterRuleContext() {
+        checkState(inRuleContext == BEFORE)
+        inRuleContext = DURING
+    }
+
+    fun leaveRuleContext(caught: Throwable?) {
+        try {
+            if (caught == null) {
+                doLeaveRuleContext()
+            } else {
+                doLeaveRuleContext(caught)
+            }
+            /*
+             * We'd like to check this even if an exception was thrown, but we don't want to override
+             * the "real" failure. TODO(cpovirk): Maybe attach as a suppressed exception once we require
+             * a newer version of Android.
+             */
+            checkState(inRuleContext == DURING)
+        } finally {
+            inRuleContext = AFTER
+        }
+    }
+
+    fun checkInRuleContext() {
+        doCheckInRuleContext(null)
+    }
+
+    fun hasFailures(): Boolean {
+        return failures.isNotEmpty()
+    }
+
+    override fun toString(): String {
+        if (failures.isEmpty()) {
+            return "No expectation failed."
+        }
+        val numFailures = failures.size
+        val message = buildString {
+            append(numFailures)
+            append(if (numFailures > 1) " expectations" else " expectation")
+            append(" failed:\n")
+            val countLength = (failures.size + 1).toString().length
+            var count = 0
+            for (failure: AssertionError in failures) {
+                count++
+                append("  ")
+                append(count.toString().padStart(length = countLength, padChar = ' '))
+                append(". ")
+                if (count == 1) {
+                    appendIndented(countLength, getStackTraceAsString(failure))
+                } else {
+                    appendIndented(
+                        countLength,
+                        printSubsequentFailure(failures[0].getStackTrace(), failure)
+                    )
+                }
+                append("\n")
+            }
+        }
+        return message
+    }
+
+    private fun printSubsequentFailure(
+        baseTraceFrames: Array<StackTraceElement>,
+        toPrint: AssertionError
+    ): String {
+        val e = RuntimeException("__EXCEPTION_MARKER__", toPrint)
+        e.setStackTrace(baseTraceFrames)
+        val s = getStackTraceAsString(e)
+        // Force single line reluctant matching
+        return s.replaceFirst(Regex("(?s)^.*?__EXCEPTION_MARKER__.*?Caused by:\\s+"), "")
+    }
+
+    private fun doCheckInRuleContext(failure: AssertionError?) {
+        when (inRuleContext) {
+            BEFORE -> throw IllegalStateException(
+                "assertion made on Expect instance, but it's not enabled as a @Rule.", failure
+            )
+
+            DURING -> return
+            AFTER -> throw IllegalStateException(
+                "assertion made on Expect instance, but its @Rule has already completed. Maybe " +
+                    "you're making assertions from a background thread and not waiting for them " +
+                    "to complete, or maybe you've shared an Expect instance across multiple" +
+                    " tests? We're throwing this exception to warn you that your assertion would " +
+                    "have been ignored. However, this exception might not cause any test to " +
+                    "fail, or it might cause some subsequent test to fail rather than the test " +
+                    "that caused the problem.",
+                failure
+            )
+        }
+    }
+
+    private fun doLeaveRuleContext() {
+        if (hasFailures()) {
+            throw AssertionErrorWithFacts.createWithNoStack(toString())
+        }
+    }
+
+    /**
+     * @throws Throwable
+     */
+    private fun doLeaveRuleContext(caught: Throwable) {
+        if (hasFailures()) {
+            val message = when (caught) {
+                is AssumptionViolatedException -> {
+                    "Also, after those failures, an assumption was violated:"
+                }
+
+                else -> "Also, after those failures, an exception was thrown:"
+            }
+            caught.stackTrace = emptyArray()
+            record(AssertionErrorWithFacts.createWithNoStack(message, caught))
+            throw AssertionErrorWithFacts.createWithNoStack(toString())
+        } else {
+            throw caught
+        }
+    }
+
+    private fun record(failure: AssertionError) {
+        doCheckInRuleContext(failure)
+        failures.add(failure)
+    }
+}
+
+private enum class TestPhase {
+    BEFORE, DURING, AFTER
+}
+
+private fun StringBuilder.appendIndented(countLength: Int, toAppend: String) {
+    val indent = countLength + 4 // "  " and ". "
+    append(toAppend.replace("\n", "\n" + " ".repeat(indent)))
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/GuavaOptionalSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/GuavaOptionalSubject.jvm.kt
index dbb531d..949147e 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/GuavaOptionalSubject.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/GuavaOptionalSubject.jvm.kt
@@ -26,15 +26,13 @@
 class GuavaOptionalSubject<T : Any> internal constructor(
     actual: Optional<out T>?,
     metadata: FailureMetadata = FailureMetadata(),
-) : Subject<Optional<out T>>(actual = actual, metadata = metadata) {
+) : Subject<Optional<out T>>(actual, metadata = metadata) {
 
     /** Fails if the [Optional]`<T>` is absent or the subject is null. */
     fun isPresent() {
         if (actual == null) {
             failWithActual(simpleFact("expected present optional"))
-        }
-
-        if (!actual.isPresent) {
+        } else if (!actual.isPresent) {
             failWithoutActual(simpleFact("expected to be present"))
         }
     }
@@ -43,9 +41,7 @@
     fun isAbsent() {
         if (actual == null) {
             failWithActual(simpleFact("expected absent optional"))
-        }
-
-        if (actual.isPresent) {
+        } else if (actual.isPresent) {
             failWithoutActual(
                 simpleFact("expected to be absent"),
                 fact("but was present with value", actual.get()),
@@ -68,15 +64,13 @@
 
         if (actual == null) {
             failWithActual("expected an optional with value", expected)
-        }
-
-        if (!actual.isPresent) {
+        } else if (!actual.isPresent) {
             failWithoutActual(
                 fact("expected to have value", expected),
                 simpleFact("but was absent")
             )
+        } else {
+            checkNoNeedToDisplayBothValues("get()").that(actual.get()).isEqualTo(expected)
         }
-
-        check().that(actual.get()).isEqualTo(expected)
     }
 }
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Platform.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Platform.jvm.kt
new file mode 100644
index 0000000..b3f1319
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Platform.jvm.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import com.google.common.base.Throwables.throwIfUnchecked
+import com.google.common.collect.Sets
+import java.lang.reflect.InvocationTargetException
+
+/**
+ * Clear the stacktrace of a [Throwable]. Unlike [cleanStackTrace], which only cleans "redundant"
+ * information, this completely empties the entire stack trace.
+ */
+internal actual fun Throwable.clearStackTrace() {
+    stackTrace = emptyArray()
+}
+
+/**
+ *  Cleans the stack trace on the given [Throwable], replacing the original stack trace
+ *  stored on the instance (see [Throwable.stackTrace]).
+ *
+ *  Removes Truth stack frames from the top and JUnit framework and reflective call frames from
+ *  the bottom. Collapses the frames for various frameworks in the middle of the trace as well.
+ */
+internal actual fun Throwable.cleanStackTrace() {
+    StackTraceCleaner(this).clean(Sets.newIdentityHashSet<Throwable>())
+}
+
+/**
+ * Returns an array containing all of the exceptions that were suppressed to deliver the given
+ * exception. If suppressed exceptions are not supported (pre-Java 1.7), an empty array will be
+ * returned.
+ */
+internal fun getSuppressed(throwable: Throwable): Array<Throwable> {
+    return try {
+        val getSuppressed = throwable::class.java.getMethod("getSuppressed")
+        @Suppress("UNCHECKED_CAST")
+        requireNonNull(getSuppressed.invoke(throwable)) as Array<Throwable>
+    } catch (e: NoSuchMethodException) {
+        emptyArray<Throwable>()
+    } catch (e: IllegalAccessException) {
+        // We're calling a public method on a public class.
+        throw newLinkageError(e)
+    } catch (e: InvocationTargetException) {
+        // Intentionally run into NPE if e.cause is null as this is Truth's behavior.
+        @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+        throwIfUnchecked(e.cause)
+        // getSuppressed has no `throws` clause.
+        throw newLinkageError(e)
+    }
+}
+
+private fun newLinkageError(cause: Throwable): LinkageError {
+    val error = LinkageError(cause.toString())
+    error.initCause(cause)
+    return error
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StackTraceCleaner.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StackTraceCleaner.jvm.kt
new file mode 100644
index 0000000..d018676
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StackTraceCleaner.jvm.kt
@@ -0,0 +1,419 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.StackFrameType.Companion.createStreakReplacementFrame
+import com.google.common.base.MoreObjects.firstNonNull
+import java.lang.Thread.currentThread
+
+private const val CLEANER_LINK: String = "https://goo.gl/aH3UyP"
+
+/**
+ * Returns true if stack trace cleaning is explicitly disabled in a system property. This switch
+ * is intended to be used when attempting to debug the frameworks which are collapsed or filtered
+ * out of stack traces by the cleaner.
+ */
+private fun isStackTraceCleaningDisabled(): Boolean {
+    // Reading system properties might be forbidden.
+    return try {
+        System.getProperty("com.google.common.truth.disable_stack_trace_cleaning").toBoolean()
+    } catch (e: SecurityException) {
+        // Hope for the best.
+        false
+        // TODO(cpovirk): Log a warning? Or is that likely to trigger other violations?
+    }
+}
+
+private val SUBJECT_CLASS: Set<String> = setOf(Subject::class.java.canonicalName!!)
+private val STANDARD_SUBJECT_BUILDER_CLASS: Set<String> = setOf(
+    StandardSubjectBuilder::class.java.canonicalName!!
+)
+private val JUNIT_INFRASTRUCTURE_CLASSES: Set<String> = setOf(
+    "org.junit.runner.Runner",
+    "org.junit.runners.model.Statement"
+)
+
+/**
+ * Utility that cleans stack traces to remove noise from common frameworks.
+ *
+ * @constructor A new instance is instantiated for each throwable to be cleaned. This is so that
+ * helper methods can make use of instance variables describing the state of the cleaning process.
+ */
+internal class StackTraceCleaner(private val throwable: Throwable) {
+    private val cleanedStackTrace = mutableListOf<StackTraceElementWrapper>()
+    private var lastStackFrameElementWrapper: StackTraceElementWrapper? = null
+    private var currentStreakType: StackFrameType? = null
+    private var currentStreakLength = 0
+
+    // TODO(b/135924708): Add this to the test runners so that we clean all stack traces, not just
+    //  those of exceptions originating in Truth.
+    /** Cleans the stack trace on [throwable], replacing the trace that was originally on it. */
+    fun clean(seenThrowables: MutableSet<Throwable>) {
+        // Stack trace cleaning can be disabled using a system property.
+        if (isStackTraceCleaningDisabled()) {
+            return
+        }
+
+        // TODO(cpovirk): Consider wrapping this whole method in a try-catch in case there are any
+        //  bugs. It would be a shame for us to fail to report the "real" assertion failure because
+        //  we're instead reporting a bug in Truth's cosmetic stack cleaning.
+
+        // Prevent infinite recursion if there is a reference cycle between Throwables.
+        if (seenThrowables.contains(throwable)) {
+            return
+        }
+        seenThrowables.add(throwable)
+
+        val stackFrames: Array<StackTraceElement> = throwable.stackTrace
+
+        var stackIndex: Int = stackFrames.lastIndex
+        while (stackIndex >= 0 && !stackFrames[stackIndex].isTruthEntrance()) {
+            // Find first frame that enters Truth's world, and remove all above.
+            stackIndex--
+        }
+        stackIndex += 1
+
+        var endIndex = 0
+        while (endIndex <= stackFrames.lastIndex &&
+            !stackFrames[endIndex].isJUnitInfrastructure()
+        ) {
+            // Find last frame of setup frames, and remove from there down.
+            endIndex++
+        }
+
+        /*
+         * If the stack trace would be empty, the error was probably thrown from "JUnit infrastructure"
+         * frames. Keep those frames around (though much of JUnit itself and related startup frames will
+         * still be removed by the remainder of this method) so that the user sees a useful stack.
+         */
+        if (stackIndex >= endIndex) {
+            endIndex = stackFrames.size
+        }
+
+        while (stackIndex < endIndex) {
+            val stackTraceElementWrapper = StackTraceElementWrapper(stackFrames[stackIndex])
+            stackIndex++
+
+            // Always keep frames that might be useful.
+            if (stackTraceElementWrapper.stackFrameType == StackFrameType.NEVER_REMOVE) {
+                endStreak()
+                cleanedStackTrace.add(stackTraceElementWrapper)
+                continue
+            }
+
+            // Otherwise, process the current frame for collapsing
+            addToStreak(stackTraceElementWrapper)
+
+            lastStackFrameElementWrapper = stackTraceElementWrapper
+        }
+
+        // Close out the streak on the bottom of the stack.
+        endStreak()
+
+        // Filter out testing framework and reflective calls from the bottom of the stack
+        val iterator = cleanedStackTrace.listIterator(cleanedStackTrace.size)
+        while (iterator.hasPrevious()) {
+            val stackTraceElementWrapper = iterator.previous()
+            if (stackTraceElementWrapper.stackFrameType == StackFrameType.TEST_FRAMEWORK ||
+                stackTraceElementWrapper.stackFrameType == StackFrameType.REFLECTION
+            ) {
+                iterator.remove()
+            } else {
+                break
+            }
+        }
+
+        // Replace the stack trace on the Throwable with the cleaned one.
+        val result = Array<StackTraceElement>(cleanedStackTrace.size) { i ->
+            cleanedStackTrace[i].stackTraceElement
+        }
+        throwable.setStackTrace(result)
+
+        // Recurse on any related Throwables that are attached to this one
+        throwable.cause?.also {
+            StackTraceCleaner(it).clean(seenThrowables)
+        }
+        for (suppressed in getSuppressed(throwable)) {
+            StackTraceCleaner(suppressed).clean(seenThrowables)
+        }
+    }
+
+    /**
+     * Either adds the given frame to the running streak or closes out the running streak and starts a
+     * new one.
+     */
+    private fun addToStreak(stackTraceElementWrapper: StackTraceElementWrapper) {
+        if (stackTraceElementWrapper.stackFrameType != currentStreakType) {
+            endStreak()
+            currentStreakType = stackTraceElementWrapper.stackFrameType
+            currentStreakLength = 1
+        } else {
+            currentStreakLength++
+        }
+    }
+
+    /** Ends the current streak, adding a summary frame to the result. Resets the streak counter. */
+    private fun endStreak() {
+        if (currentStreakLength == 0) {
+            return
+        }
+
+        if (currentStreakLength == 1) {
+            // A single frame isn't a streak. Just include the frame as-is in the result.
+            cleanedStackTrace.add(checkNotNull(lastStackFrameElementWrapper))
+        } else {
+            // Add a single frame to the result summarizing the streak of framework frames
+            cleanedStackTrace.add(
+                createStreakReplacementFrame(checkNotNull(currentStreakType), currentStreakLength)
+            )
+        }
+
+        clearStreak()
+    }
+
+    /** Resets the streak counter. */
+    private fun clearStreak() {
+        currentStreakType = null
+        currentStreakLength = 0
+    }
+
+    private fun StackTraceElement.isTruthEntrance(): Boolean {
+        return isFromClassOrClassNestedInside(SUBJECT_CLASS) ||
+            /*
+             * Don't match classes _nested inside_ StandardSubjectBuilder because that would match
+             * Expect's Statement implementation. While we want to strip everything from there
+             * _down_, we don't want to strip everything from there _up_ (which would strip the test
+             * class itself!).
+             *
+             * (StandardSubjectBuilder is listed here only for its fail() methods, anyway, so we
+             * don't have to worry about nested classes like we do with Subject.)
+             */
+            isFromClassDirectly(STANDARD_SUBJECT_BUILDER_CLASS)
+    }
+
+    private fun StackTraceElement.isJUnitInfrastructure(): Boolean {
+        // It's not clear whether looking at nested classes here is useful, harmful, or neutral.
+        return isFromClassOrClassNestedInside(JUNIT_INFRASTRUCTURE_CLASSES)
+    }
+
+    private fun StackTraceElement.isFromClassOrClassNestedInside(
+        recognizedClasses: Set<String>
+    ): Boolean {
+        var stackClass: Class<*>? = try {
+            loadClass(className)
+        } catch (e: ClassNotFoundException) {
+            return false
+        }
+
+        try {
+            while (stackClass != null) {
+                for (recognizedClass in recognizedClasses) {
+                    if (isSubtypeOf(stackClass, recognizedClass)) {
+                        return true
+                    }
+                }
+                stackClass = stackClass.enclosingClass
+            }
+        } catch (e: Error) {
+            if (e::class.java.name.equals("com.google.j2objc.ReflectionStrippedError")) {
+                /*
+                 * We're running under j2objc without reflection. Skip testing the enclosing classes. At
+                 * least we tested the class itself against all the recognized classes.
+                 *
+                 * TODO(cpovirk): The smarter thing might be to guess the name of the enclosing classes by
+                 * removing "$Foo" from the end of the name. But this should be good enough for now.
+                 */
+                return false
+            }
+            if (e is IncompatibleClassChangeError) {
+                // OEM class-loading bug? https://issuetracker.google.com/issues/37045084
+                return false
+            }
+            throw e
+        }
+        return false
+    }
+
+    private fun isSubtypeOf(subclass: Class<*>?, superclass: String): Boolean {
+        var clazz = subclass
+        while (clazz != null) {
+            if (clazz.canonicalName != null && clazz.canonicalName.equals(superclass)) {
+                return true
+            }
+
+            clazz = clazz.superclass
+        }
+        return false
+    }
+
+    private fun StackTraceElement.isFromClassDirectly(
+        recognizedClasses: Set<String>
+    ): Boolean {
+        val stackClass = try {
+            loadClass(className)
+        } catch (e: ClassNotFoundException) {
+            return false
+        }
+        for (recognizedClass in recognizedClasses) {
+            if (isSubtypeOf(stackClass, recognizedClass)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * @throws ClassNotFoundException
+     */
+    // Using plain Class.forName can cause problems.
+    /*
+     * TODO(cpovirk): Consider avoiding classloading entirely by reading classes with ASM. But that
+     * won't work on Android, so we might ultimately need classloading as a fallback. Another
+     * possibility is to load classes in a fresh, isolated classloader. However, that requires
+     * creating a list of jars to load from, which is fragile and would also require special handling
+     * under Android. If we're lucky, this new solution will just work: The classes should already be
+     * loaded, anyway, since they appear on the stack, so we just have to hope that we have the right
+     * classloader.
+     */
+    private fun loadClass(name: String): Class<*> {
+        val loader: ClassLoader = firstNonNull(
+            currentThread().getContextClassLoader(), StackTraceCleaner::class.java.classLoader!!
+        )
+        return loader.loadClass(name)
+    }
+}
+
+/**
+ * Wrapper around a [StackTraceElement] for calculating and holding the metadata used to clean
+ * the stack trace.
+ *
+ * @constructor Creates a wrapper with the given frame and the given frame type.
+ */
+internal class StackTraceElementWrapper(
+    /** The wrapped [StackTraceElement]. */
+    internal val stackTraceElement: StackTraceElement,
+    /** The type of this frame. */
+    internal val stackFrameType: StackFrameType
+) {
+
+    /**
+     *  Creates a wrapper with the given frame with frame type inferred from frame's class name.
+     */
+    constructor(stackTraceElement: StackTraceElement) : this(
+        stackTraceElement,
+        StackFrameType.forClassName(stackTraceElement.className)
+    )
+}
+
+/**
+ * Enum of the package or class-name based categories of stack frames that might be removed or
+ * collapsed by the cleaner.
+ *
+ * @constructor Each type of stack frame has a name of the summary displayed in the cleaned
+ *  trace.
+ *
+ *  Most also have a set of fully qualified class name prefixes that identify when a
+ *  frame belongs to this type.
+ */
+internal enum class StackFrameType(
+    /** Returns the name of this frame type to display in the cleaned trace */
+    private val stackFrameName: String,
+    private val prefixes: List<String> = emptyList()
+) {
+    NEVER_REMOVE("N/A"),
+    TEST_FRAMEWORK(
+        "Testing framework",
+        listOf(
+            "junit",
+            "org.junit",
+            "androidx.test.internal.runner",
+            "com.github.bazel_contrib.contrib_rules_jvm.junit5",
+            "com.google.testing.junit",
+            "com.google.testing.testsize",
+            "com.google.testing.util"
+        )
+    ),
+    REFLECTION(
+        "Reflective call",
+        listOf("java.lang.reflect", "jdk.internal.reflect", "sun.reflect")
+    ),
+    CONCURRENT_FRAMEWORK(
+        "Concurrent framework",
+        listOf(
+            "com.google.tracing.CurrentContext",
+            "com.google.common.util.concurrent",
+            "java.util.concurrent.ForkJoin"
+        )
+    );
+
+    /**
+     * Returns true if the given frame belongs to this frame type based on the package and/or class
+     * name of the frame.
+     */
+    fun belongsToType(fullyQualifiedClassName: String): Boolean {
+        for (prefix in prefixes) {
+            // TODO(cpovirk): Should we also check prefix + "$"?
+            if (fullyQualifiedClassName == prefix ||
+                fullyQualifiedClassName.startsWith("$prefix.")
+            ) {
+                return true
+            }
+        }
+        return false
+    }
+
+    companion object {
+        private const val NON_LEAKY_TEST_FQN =
+            "androidx.test.internal.runner.junit3.NonLeakyTestSuite\$NonLeakyTest"
+
+        /** Helper method to determine the frame type from the fully qualified class name. */
+        internal fun forClassName(fullyQualifiedClassName: String): StackFrameType {
+            // Never remove the frames from a test class. These will probably be the frame of a failing
+            // assertion.
+            // TODO(cpovirk): This is really only for tests in Truth itself, so this doesn't matter
+            //  yet, but.... If the Truth tests someday start calling into nested classes, we may
+            //  want to add:
+            //  || fullyQualifiedClassName.contains("Test$")
+            if (fullyQualifiedClassName.endsWith("Test") &&
+                fullyQualifiedClassName != NON_LEAKY_TEST_FQN
+            ) {
+                return NEVER_REMOVE
+            }
+
+            for (stackFrameType in StackFrameType.values()) {
+                if (stackFrameType.belongsToType(fullyQualifiedClassName)) {
+                    return stackFrameType
+                }
+            }
+
+            return NEVER_REMOVE
+        }
+
+        internal fun createStreakReplacementFrame(
+            stackFrameType: StackFrameType,
+            length: Int
+        ) = StackTraceElementWrapper(
+            StackTraceElement(
+                "[[${stackFrameType.stackFrameName}: $length frames collapsed ($CLEANER_LINK)]]",
+                "",
+                "",
+                0
+            ),
+            stackFrameType
+        )
+    }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectFailureWithStackTraceTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectFailureWithStackTraceTest.kt
new file mode 100644
index 0000000..5204f4d
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectFailureWithStackTraceTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+private const val METHOD_NAME = "ExpectFailureWithStackTraceTest.expectTwoFailures"
+
+/** Test that stack traces are included in the error message created by Expect. */
+class ExpectFailureWithStackTraceTest {
+
+    @get:Rule
+    internal val failToExpect: FailingExpect = FailingExpect()
+
+    @Test
+    fun expectTwoFailures() {
+        failToExpect.delegate.that(4).isNotEqualTo(4)
+        failToExpect.delegate.that("abc").contains("x")
+    }
+}
+
+/** Expect class that can examine the error message */
+internal class FailingExpect : TestRule {
+    val delegate: Expect = Expect.create()
+
+    override fun apply(base: Statement, description: Description): Statement {
+        val s = delegate.apply(base, description)
+        return object : Statement() {
+            override fun evaluate() {
+                var failureMessage = ""
+                try {
+                    s.evaluate()
+                } catch (e: AssertionError) {
+                    failureMessage = e.message!!
+                }
+                // Check that error message contains stack traces. Method name should appear twice,
+                // once for each expect error.
+                val firstIndex = failureMessage.indexOf(METHOD_NAME)
+                assertThat(firstIndex).isGreaterThan(0)
+                val secondIndex = failureMessage.indexOf(METHOD_NAME, firstIndex + 1)
+                assertThat(secondIndex).isGreaterThan(firstIndex)
+            }
+        }
+    }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectTest.kt
new file mode 100644
index 0000000..59e6cf8
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import com.google.common.util.concurrent.Futures.immediateFuture
+import com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors.newSingleThreadExecutor
+import java.util.concurrent.Future
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.rules.ExpectedException
+import org.junit.rules.TestRule
+import org.junit.runners.model.Statement
+
+/**
+ * Tests (and effectively sample code) for the Expect verb (implemented as a rule)
+ */
+class ExpectTest {
+    private val oopsNotARule: Expect = Expect.create()
+    private val expect: Expect = Expect.create()
+
+    // We use ExpectedException so that we can test our code that runs after the test method completes.
+    @Suppress("DEPRECATION")
+    private val thrown: ExpectedException = ExpectedException.none()
+    private val postTestWait: TestRule = TestRule { base, _ ->
+        object : Statement() {
+            override fun evaluate() {
+                base.evaluate()
+                testMethodComplete.countDown()
+                taskToAwait.get()
+            }
+        }
+    }
+    private val testMethodComplete: CountDownLatch = CountDownLatch(1)
+
+    /**
+     * A task that the main thread will await, to be provided by tests that do work in other threads.
+     */
+    private var taskToAwait: Future<*> = immediateFuture(null)
+
+    @get:Rule
+    val wrapper: TestRule = TestRule { statement, description ->
+        var nextStatement = expect.apply(statement, description)
+        nextStatement = postTestWait.apply(nextStatement, description)
+        nextStatement = thrown.apply(nextStatement, description)
+        nextStatement
+    }
+
+    @Test
+    fun expectTrue() {
+        expect.that(4).isEqualTo(4)
+    }
+
+    @Test
+    fun singleExpectationFails() {
+        thrown.expectMessage("1 expectation failed:")
+        thrown.expectMessage("1. x")
+        expect.withMessage("x").fail()
+    }
+
+    @Test
+    fun expectFail() {
+        thrown.expectMessage("3 expectations failed:")
+        thrown.expectMessage("1. x")
+        thrown.expectMessage("2. y")
+        thrown.expectMessage("3. z")
+        expect.withMessage("x").fail()
+        expect.withMessage("y").fail()
+        expect.withMessage("z").fail()
+    }
+
+    @Test
+    fun expectFail10Aligned() {
+        thrown.expectMessage("10 expectations failed:")
+        thrown.expectMessage(" 1. x")
+        thrown.expectMessage("10. x")
+        repeat(10) {
+            expect.withMessage("x").fail()
+        }
+    }
+
+    @Test
+    fun expectFail10WrappedAligned() {
+        thrown.expectMessage("10 expectations failed:")
+        thrown.expectMessage(" 1. abc\n      xyz")
+        thrown.expectMessage("10. abc\n      xyz")
+        repeat(10) {
+            expect.withMessage("abc\nxyz").fail()
+        }
+    }
+
+    @Test
+    fun expectFailWithExceptionNoMessage() {
+        thrown.expectMessage("3 expectations failed:")
+        thrown.expectMessage("1. x")
+        thrown.expectMessage("2. y")
+        thrown.expectMessage("3. Also, after those failures, an exception was thrown:")
+        expect.withMessage("x").fail()
+        expect.withMessage("y").fail()
+        throw IllegalStateException()
+    }
+
+    @Test
+    fun expectFailWithExceptionWithMessage() {
+        thrown.expectMessage("3 expectations failed:")
+        thrown.expectMessage("1. x")
+        thrown.expectMessage("2. y")
+        thrown.expectMessage("3. Also, after those failures, an exception was thrown:")
+        expect.withMessage("x").fail()
+        expect.withMessage("y").fail()
+        throw IllegalStateException("testing")
+    }
+
+    @Test
+    fun expectFailWithExceptionBeforeExpectFailures() {
+        thrown.expect(IllegalStateException::class.java)
+        thrown.expectMessage("testing")
+        throwException()
+        expect.withMessage("x").fail()
+        expect.withMessage("y").fail()
+    }
+
+    private fun throwException() {
+        throw IllegalStateException("testing")
+    }
+
+    @Test
+    fun warnWhenExpectIsNotRule() {
+        val message = "assertion made on Expect instance, but it's not enabled as a @Rule."
+        thrown.expectMessage(message)
+        oopsNotARule.that(true).isEqualTo(true)
+    }
+
+    @Test
+    fun bash() = runTest {
+        val results = mutableListOf<Deferred<*>>()
+        repeat(1000) {
+            results.add(async { expect.that(3).isEqualTo(4) })
+        }
+        results.forEach { it.await() }
+        thrown.expectMessage("1000 expectations failed:")
+    }
+
+    @Test
+    fun failWhenCallingThatAfterTest() {
+        val executor = newSingleThreadExecutor()
+        taskToAwait = executor.submit {
+            awaitUninterruptibly(testMethodComplete)
+            assertFailsWith<IllegalStateException> {
+                expect.that(3)
+            }
+        }
+        executor.shutdown()
+    }
+
+    @Test
+    fun failWhenCallingFailingAssertionMethodAfterTest() {
+        val executor = newSingleThreadExecutor()
+        /*
+         * We wouldn't expect people to do this exactly. The point is that, if someone were to call
+         * expect.that(3).isEqualTo(4), we would always either fail the test or throw an
+         * IllegalStateException, not record a "failure" that we never read.
+         */
+        val expectThat3 = expect.that(3)
+        taskToAwait = executor.submit {
+            awaitUninterruptibly(testMethodComplete)
+            val expectedException = assertFailsWith<IllegalStateException> {
+                expectThat3.isEqualTo(4)
+            }
+            assertThat(expectedException).hasCauseThat().isInstanceOf<AssertionError>()
+        }
+        executor.shutdown()
+    }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectWithStackTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectWithStackTest.kt
new file mode 100644
index 0000000..22a364e
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/ExpectWithStackTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class ExpectWithStackTest {
+    private val expectWithTrace: Expect = Expect.create()
+
+    @get:Rule
+    internal val verifyAssertionError: TestRuleVerifier = TestRuleVerifier(expectWithTrace)
+
+    @Test
+    fun testExpectTrace_simpleCase() {
+        verifyAssertionError.errorVerifier = ErrorVerifier { expected ->
+            assertThat(expected.getStackTrace()).hasLength(0)
+            assertThat(expected).hasMessageThat().startsWith("3 expectations failed:")
+        }
+
+        expectWithTrace.that(true).isFalse()
+        expectWithTrace.that("Hello").isNull()
+        expectWithTrace.that(1).isEqualTo(2)
+    }
+
+    @Test
+    fun testExpectTrace_loop() {
+        verifyAssertionError.errorVerifier = ErrorVerifier { expected ->
+            assertThat(expected.getStackTrace()).hasLength(0)
+            assertThat(expected).hasMessageThat().startsWith("4 expectations failed:")
+            assertWithMessage("test method name should only show up once with following omitted")
+                .that(expected.message!!.split("testExpectTrace_loop").toTypedArray())
+                .hasLength(2)
+        }
+
+        repeat(4) { expectWithTrace.that(true).isFalse() }
+    }
+
+    @Test
+    fun testExpectTrace_callerException() {
+        verifyAssertionError.errorVerifier = ErrorVerifier { expected ->
+            assertThat(expected.getStackTrace()).hasLength(0)
+            assertThat(expected).hasMessageThat().startsWith("2 expectations failed:")
+        }
+
+        expectWithTrace.that(true).isFalse()
+        expectWithTrace
+            .that(alwaysFailWithCause())
+            .isEqualTo(5)
+    }
+
+    @Test
+    fun testExpectTrace_onlyCallerException() {
+        verifyAssertionError.errorVerifier = ErrorVerifier { expected ->
+            assertWithMessage("Should throw exception as it is if only caller exception")
+                .that(expected.getStackTrace().size)
+                .isAtLeast(2)
+        }
+
+        expectWithTrace
+            .that(alwaysFailWithCause())
+            .isEqualTo(5)
+    }
+}
+
+internal fun interface ErrorVerifier {
+    fun verify(error: AssertionError)
+}
+
+internal class TestRuleVerifier(private val ruleToVerify: TestRule) : TestRule {
+    var errorVerifier: ErrorVerifier = ErrorVerifier {}
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                try {
+                    ruleToVerify.apply(base, description).evaluate()
+                } catch (caught: AssertionError) {
+                    errorVerifier.verify(caught)
+                }
+            }
+        }
+    }
+}
+
+private fun alwaysFailWithCause() {
+    throw AssertionError("Always fail", RuntimeException("First", RuntimeException("Second", null)))
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/Platform.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/Platform.native.kt
new file mode 100644
index 0000000..a2faf4b
--- /dev/null
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/Platform.native.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.kruth
+
+internal actual fun Throwable.clearStackTrace() {}
+
+internal actual fun Throwable.cleanStackTrace() {}
diff --git a/libraryversions.toml b/libraryversions.toml
index c5b4fa8..52a51c0 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -12,7 +12,7 @@
 BENCHMARK = "1.3.0-alpha01"
 BIOMETRIC = "1.2.0-alpha06"
 BLUETOOTH = "1.0.0-alpha02"
-BROWSER = "1.8.0-beta02"
+BROWSER = "1.9.0-alpha01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
 CAMERA = "1.4.0-alpha04"
 CAMERA_PIPE = "1.0.0-alpha01"
@@ -22,11 +22,11 @@
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-alpha01"
 COLLECTION = "1.5.0-alpha01"
-COMPOSE = "1.7.0-alpha02"
+COMPOSE = "1.7.0-alpha03"
 COMPOSE_COMPILER = "1.5.9"
 COMPOSE_MATERIAL3 = "1.3.0-alpha01"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha06"
-COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha03"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha07"
+COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha04"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
 CONSTRAINTLAYOUT = "2.2.0-alpha13"
@@ -63,7 +63,7 @@
 EMOJI2 = "1.5.0-alpha01"
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
-FRAGMENT = "1.7.0-alpha10"
+FRAGMENT = "1.7.0-beta01"
 FUTURES = "1.2.0-alpha02"
 GLANCE = "1.1.0-alpha01"
 GLANCE_PREVIEW = "1.0.0-alpha06"
@@ -92,15 +92,14 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.8.0-alpha01"
+LIFECYCLE = "2.8.0-alpha02"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LINT = "1.0.0-alpha01"
 LOADER = "1.2.0-alpha01"
 MEDIA = "1.7.0-rc01"
-MEDIA2 = "1.3.0-rc01"
-MEDIAROUTER = "1.7.0-alpha02"
+MEDIAROUTER = "1.7.0-beta01"
 METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-alpha02"
+NAVIGATION = "2.8.0-alpha03"
 PAGING = "3.3.0-alpha03"
 PALETTE = "1.1.0-alpha01"
 PDF = "1.0.0-alpha01"
@@ -127,6 +126,7 @@
 SECURITY_APP_AUTHENTICATOR_TESTING = "1.0.0-beta01"
 SECURITY_BIOMETRIC = "1.0.0-alpha01"
 SECURITY_IDENTITY_CREDENTIAL = "1.0.0-alpha04"
+SECURITY_MLS = "1.0.0-alpha01"
 SHARETARGET = "1.3.0-alpha01"
 SLICE = "1.1.0-alpha03"
 SLICE_BENCHMARK = "1.1.0-alpha03"
@@ -154,8 +154,8 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.1.0-beta03"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.4.0-alpha02"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha17"
+WEAR_COMPOSE = "1.4.0-alpha03"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha18"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha02"
@@ -166,7 +166,6 @@
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
 WEAR_WATCHFACE = "1.3.0-alpha01"
 WEBKIT = "1.11.0-alpha01"
-# Adding a comment to prevent merge conflicts for Window artifact
 WINDOW = "1.3.0-alpha02"
 WINDOW_EXTENSIONS = "1.3.0-alpha01"
 WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
@@ -203,6 +202,7 @@
 COMPOSE_FOUNDATION = { group = "androidx.compose.foundation", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_MATERIAL = { group = "androidx.compose.material", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_MATERIAL3 = { group = "androidx.compose.material3", atomicGroupVersion = "versions.COMPOSE_MATERIAL3" }
+COMPOSE_MATERIAL3_ADAPTIVE = { group = "androidx.compose.material3.adaptive", atomicGroupVersion = "versions.COMPOSE_MATERIAL3_ADAPTIVE" }
 COMPOSE_RUNTIME = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_RUNTIME_TRACING = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE_RUNTIME_TRACING", overrideInclude = [ ":compose:runtime:runtime-tracing" ] }
 COMPOSE_UI = { group = "androidx.compose.ui", atomicGroupVersion = "versions.COMPOSE" }
@@ -248,7 +248,6 @@
 LINT = { group = "androidx.lint", atomicGroupVersion = "versions.LINT" }
 LOADER = { group = "androidx.loader", atomicGroupVersion = "versions.LOADER" }
 MEDIA = { group = "androidx.media" }
-MEDIA2 = { group = "androidx.media2", atomicGroupVersion = "versions.MEDIA2" }
 MEDIAROUTER = { group = "androidx.mediarouter", atomicGroupVersion = "versions.MEDIAROUTER" }
 METRICS = { group = "androidx.metrics", atomicGroupVersion = "versions.METRICS" }
 NAVIGATION = { group = "androidx.navigation", atomicGroupVersion = "versions.NAVIGATION" }
diff --git a/lifecycle/lifecycle-runtime-compose/api/current.ignore b/lifecycle/lifecycle-runtime-compose/api/current.ignore
deleted file mode 100644
index bfd5f84..0000000
--- a/lifecycle/lifecycle-runtime-compose/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.compose.FlowExtKt#collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T, androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.coroutines.CoroutineContext) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.compose.FlowExtKt.collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T> arg1, T initialValue, androidx.lifecycle.Lifecycle lifecycle, androidx.lifecycle.Lifecycle.State minActiveState, kotlin.coroutines.CoroutineContext context)
-InvalidNullConversion: androidx.lifecycle.compose.FlowExtKt#collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.coroutines.CoroutineContext) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.compose.FlowExtKt.collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T> arg1, T initialValue, androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.lifecycle.Lifecycle.State minActiveState, kotlin.coroutines.CoroutineContext context)
diff --git a/lifecycle/lifecycle-runtime-compose/api/current.txt b/lifecycle/lifecycle-runtime-compose/api/current.txt
index 0346f51..bacd585 100644
--- a/lifecycle/lifecycle-runtime-compose/api/current.txt
+++ b/lifecycle/lifecycle-runtime-compose/api/current.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.lifecycle.compose {
 
+  public final class DropUnlessLifecycleKt {
+    method @CheckResult @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> dropUnlessResumed(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @CheckResult @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> dropUnlessStarted(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  }
+
   public final class FlowExtKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T initialValue, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState, optional kotlin.coroutines.CoroutineContext context);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T initialValue, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, optional androidx.lifecycle.Lifecycle.State minActiveState, optional kotlin.coroutines.CoroutineContext context);
@@ -10,10 +15,12 @@
 
   public final class LifecycleEffectKt {
     method @androidx.compose.runtime.Composable public static void LifecycleEventEffect(androidx.lifecycle.Lifecycle.Event event, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> onEvent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, Object? key2, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, Object? key2, Object? key3, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object?[]? keys, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
+    method @Deprecated @androidx.compose.runtime.Composable public static void LifecycleStartEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, Object? key2, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, Object? key2, Object? key3, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
diff --git a/lifecycle/lifecycle-runtime-compose/api/restricted_current.ignore b/lifecycle/lifecycle-runtime-compose/api/restricted_current.ignore
deleted file mode 100644
index bfd5f84..0000000
--- a/lifecycle/lifecycle-runtime-compose/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.compose.FlowExtKt#collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T, androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.coroutines.CoroutineContext) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.compose.FlowExtKt.collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T> arg1, T initialValue, androidx.lifecycle.Lifecycle lifecycle, androidx.lifecycle.Lifecycle.State minActiveState, kotlin.coroutines.CoroutineContext context)
-InvalidNullConversion: androidx.lifecycle.compose.FlowExtKt#collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.coroutines.CoroutineContext) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.compose.FlowExtKt.collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T> arg1, T initialValue, androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.lifecycle.Lifecycle.State minActiveState, kotlin.coroutines.CoroutineContext context)
diff --git a/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt b/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
index 0346f51..bacd585 100644
--- a/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.lifecycle.compose {
 
+  public final class DropUnlessLifecycleKt {
+    method @CheckResult @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> dropUnlessResumed(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @CheckResult @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> dropUnlessStarted(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  }
+
   public final class FlowExtKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T initialValue, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState, optional kotlin.coroutines.CoroutineContext context);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> collectAsStateWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, T initialValue, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, optional androidx.lifecycle.Lifecycle.State minActiveState, optional kotlin.coroutines.CoroutineContext context);
@@ -10,10 +15,12 @@
 
   public final class LifecycleEffectKt {
     method @androidx.compose.runtime.Composable public static void LifecycleEventEffect(androidx.lifecycle.Lifecycle.Event event, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> onEvent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, Object? key2, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object? key1, Object? key2, Object? key3, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(Object?[]? keys, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseOrDisposeEffectResult> effects);
+    method @Deprecated @androidx.compose.runtime.Composable public static void LifecycleStartEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, Object? key2, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
     method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(Object? key1, Object? key2, Object? key3, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopOrDisposeEffectResult> effects);
diff --git a/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/DropUnlessLifecycleSamples.kt b/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/DropUnlessLifecycleSamples.kt
new file mode 100644
index 0000000..9f95d00
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/DropUnlessLifecycleSamples.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.lifecycle.compose.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.lifecycle.compose.dropUnlessResumed
+import androidx.lifecycle.compose.dropUnlessStarted
+
+@Sampled
+@Composable
+fun DropUnlessStarted() {
+    Button(
+        onClick = dropUnlessStarted {
+            // Run on clicks only when the lifecycle is at least STARTED.
+            // Example: navController.navigate("next_screen")
+        },
+    ) {
+        Text(text = "Navigate to next screen")
+    }
+}
+
+@Sampled
+@Composable
+fun DropUnlessResumed() {
+    Button(
+        onClick = dropUnlessResumed {
+            // Run on clicks only when the lifecycle is at least RESUMED.
+            // Example: navController.navigate("next_screen")
+        },
+    ) {
+        Text(text = "Navigate to next screen")
+    }
+}
diff --git a/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/LifecycleComposeSamples.kt b/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/LifecycleComposeSamples.kt
index 1cf0ff6..3c047a1 100644
--- a/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/LifecycleComposeSamples.kt
+++ b/lifecycle/lifecycle-runtime-compose/samples/src/main/java/androidx/lifecycle/compose/samples/LifecycleComposeSamples.kt
@@ -92,7 +92,7 @@
 fun lifecycleStartEffectSample() {
     @Composable
     fun Analytics(dataAnalytics: DataAnalytics) {
-        LifecycleStartEffect {
+        LifecycleStartEffect(dataAnalytics) {
             val timeTracker = dataAnalytics.startTimeTracking()
 
             onStopOrDispose {
@@ -109,7 +109,7 @@
 fun lifecycleResumeEffectSample() {
     @Composable
     fun Analytics(dataAnalytics: DataAnalytics) {
-        LifecycleResumeEffect {
+        LifecycleResumeEffect(dataAnalytics) {
             val timeTracker = dataAnalytics.startTimeTracking()
 
             onPauseOrDispose {
diff --git a/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/DropUnlessLifecycleTest.kt b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/DropUnlessLifecycleTest.kt
new file mode 100644
index 0000000..0eeca8c
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/DropUnlessLifecycleTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.lifecycle.compose
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.Lifecycle.State
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class DropUnlessLifecycleTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    //region dropUnlessStarted
+    @Test
+    fun dropUnlessStarted_lifecycleInitialized_doNothing() {
+        testDropUnlessStarted(currentLifecycleState = State.INITIALIZED, shouldInvoke = false)
+    }
+
+    @Test
+    fun dropUnlessStarted_lifecycleCreated_doNothing() {
+        testDropUnlessStarted(currentLifecycleState = State.CREATED, shouldInvoke = false)
+    }
+
+    @Test
+    fun dropUnlessStarted_lifecycleStarted_invoke() {
+        testDropUnlessStarted(currentLifecycleState = State.STARTED, shouldInvoke = true)
+    }
+
+    @Test
+    fun dropUnlessStarted_lifecycleResumed_invoke() {
+        testDropUnlessStarted(currentLifecycleState = State.RESUMED, shouldInvoke = true)
+    }
+
+    @Test
+    fun dropUnlessStarted_lifecycleDestroyed_doNothing() {
+        testDropUnlessStarted(currentLifecycleState = State.DESTROYED, shouldInvoke = false)
+    }
+
+    private fun testDropUnlessStarted(currentLifecycleState: State, shouldInvoke: Boolean) {
+        val lifecycleOwner = TestLifecycleOwner(State.CREATED).apply {
+            currentState = currentLifecycleState
+        }
+        var hasBeenInvoked = false
+
+        composeTestRule.waitForIdle()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+                val underTest = dropUnlessStarted {
+                    hasBeenInvoked = true
+                }
+                underTest.invoke()
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            assertThat(hasBeenInvoked).isEqualTo(shouldInvoke)
+        }
+    }
+    //endregion
+
+    //region dropUnlessResumed
+    @Test
+    fun dropUnlessResumed_lifecycleInitialized_doNothing() {
+        testDropUnlessResumed(currentLifecycleState = State.INITIALIZED, shouldInvoke = false)
+    }
+
+    @Test
+    fun dropUnlessResumed_lifecycleCreated_doNothing() {
+        testDropUnlessResumed(currentLifecycleState = State.CREATED, shouldInvoke = false)
+    }
+
+    @Test
+    fun dropUnlessResumed_lifecycleStarted_invokes() {
+        testDropUnlessResumed(currentLifecycleState = State.STARTED, shouldInvoke = false)
+    }
+
+    @Test
+    fun dropUnlessResumed_lifecycleResumed_invoke() {
+        testDropUnlessResumed(currentLifecycleState = State.RESUMED, shouldInvoke = true)
+    }
+
+    @Test
+    fun dropUnlessResumed_lifecycleDestroyed_doNothing() {
+        testDropUnlessResumed(currentLifecycleState = State.DESTROYED, shouldInvoke = false)
+    }
+
+    private fun testDropUnlessResumed(currentLifecycleState: State, shouldInvoke: Boolean) {
+        val lifecycleOwner = TestLifecycleOwner(State.CREATED).apply {
+            currentState = currentLifecycleState
+        }
+        var hasBeenInvoked = false
+
+        composeTestRule.waitForIdle()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+                val underTest = dropUnlessResumed {
+                    hasBeenInvoked = true
+                }
+                underTest.invoke()
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            assertThat(hasBeenInvoked).isEqualTo(shouldInvoke)
+        }
+    }
+    //endregion
+}
diff --git a/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
index 6674a00..9eca3f2 100644
--- a/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
@@ -373,7 +373,7 @@
         composeTestRule.waitForIdle()
         composeTestRule.setContent {
             CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
-                LifecycleResumeEffect {
+                LifecycleResumeEffect(key1 = null) {
                     resumeCount++
 
                     onPauseOrDispose {
@@ -413,7 +413,7 @@
             CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
                 state = remember { mutableStateOf(true) }
                 if (state.value) {
-                    LifecycleResumeEffect {
+                    LifecycleResumeEffect(key1 = null) {
                         resumeCount++
 
                         onPauseOrDispose {
diff --git a/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/DropUnlessLifecycle.kt b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/DropUnlessLifecycle.kt
new file mode 100644
index 0000000..0bb34f1
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/DropUnlessLifecycle.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.lifecycle.compose
+
+import androidx.annotation.CheckResult
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.Lifecycle.State
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * Returns a new decorated function that will invoke the given [block] if the [lifecycleOwner]'s
+ * lifecycle state is at least [state]. Otherwise, [block] is not invoked.
+ *
+ * Note this function should **not** be used to target the [State.DESTROYED] because
+ * Compose stops recomposing after receiving a [Lifecycle.Event.ON_STOP] and will never be
+ * aware of an [State.DESTROYED] state.
+ *
+ * @param state The target [Lifecycle.State] to match.
+ * @param lifecycleOwner The [LifecycleOwner] whose lifecycle will be observed. Defaults to the
+ *  current [LocalLifecycleOwner].
+ * @param block The callback to be executed when the observed lifecycle state is at least [state].
+ *
+ * @return A decorated function that invoke [block] only if the lifecycle state is at least [state].
+ *
+ * @throws IllegalArgumentException if attempting to target state [State.DESTROYED].
+ *
+ * @see dropUnlessStarted
+ * @see dropUnlessResumed
+ */
+@CheckResult
+@Composable
+private fun dropUnlessStateIsAtLeast(
+    state: State,
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    block: () -> Unit,
+): () -> Unit {
+    require(state != State.DESTROYED) {
+        "Target state is not allowed to be `Lifecycle.State.DESTROYED` because Compose disposes " +
+            "of the composition before `Lifecycle.Event.ON_DESTROY` observers are invoked."
+    }
+
+    return {
+        if (lifecycleOwner.lifecycle.currentState.isAtLeast(state)) {
+            block()
+        }
+    }
+}
+
+/**
+ * Returns a new decorated function that will invoke the given [block] if the [lifecycleOwner]'s
+ * lifecycle state is at least [State.STARTED]. Otherwise, [block] is not invoked.
+ *
+ * @param lifecycleOwner The [LifecycleOwner] whose lifecycle will be observed. Defaults to the
+ *  current [LocalLifecycleOwner].
+ * @param block The callback to be executed when the observed lifecycle state is at least
+ *  [State.STARTED].
+ *
+ * @return A decorated function that invoke [block] only if the lifecycle state is at least
+ *  [State.STARTED].
+ *
+ * @sample androidx.lifecycle.compose.samples.DropUnlessStarted
+ */
+@CheckResult
+@Composable
+fun dropUnlessStarted(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    block: () -> Unit,
+): () -> Unit = dropUnlessStateIsAtLeast(State.STARTED, lifecycleOwner, block)
+
+/**
+ * Returns a new decorated function that will invoke the given [block] if the [lifecycleOwner]'s
+ * lifecycle state is at least [State.RESUMED]. Otherwise, [block] is not invoked.
+ *
+ * For Navigation users, it's recommended to safeguard navigate methods when using them while a
+ * composable is in transition as a result of navigation.
+ *
+ * @param lifecycleOwner The [LifecycleOwner] whose lifecycle will be observed. Defaults to the
+ *  current [LocalLifecycleOwner].
+ * @param block The callback to be executed when the observed lifecycle state is at least
+ *  [State.RESUMED].
+ *
+ * @return A decorated function that invoke [block] only if the lifecycle state is at least
+ *  [State.RESUMED].
+ *
+ * @sample androidx.lifecycle.compose.samples.DropUnlessResumed
+ */
+@CheckResult
+@Composable
+fun dropUnlessResumed(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    block: () -> Unit,
+): () -> Unit = dropUnlessStateIsAtLeast(State.RESUMED, lifecycleOwner, block)
diff --git a/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
index 80cadae..6865535 100644
--- a/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
@@ -330,6 +330,27 @@
     LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
 }
 
+private const val LifecycleStartEffectNoParamError =
+    "LifecycleStartEffect must provide one or more 'key' parameters that define the identity of " +
+        "the LifecycleStartEffect and determine when its previous effect coroutine should be " +
+        "cancelled and a new effect launched for the new key."
+
+/**
+ * It is an error to call [LifecycleStartEffect] without at least one `key` parameter.
+ *
+ * This deprecated-error function shadows the varargs overload so that the varargs version is not
+ * used without key parameters.
+ *
+ * @see LifecycleStartEffect
+ */
+@Deprecated(LifecycleStartEffectNoParamError, level = DeprecationLevel.ERROR)
+@Composable
+@Suppress("UNUSED_PARAMETER")
+fun LifecycleStartEffect(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
+): Unit = error(LifecycleStartEffectNoParamError)
+
 @Composable
 private fun LifecycleStartEffectImpl(
     lifecycleOwner: LifecycleOwner,
@@ -642,6 +663,27 @@
     LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
 }
 
+private const val LifecycleResumeEffectNoParamError =
+    "LifecycleResumeEffect must provide one or more 'key' parameters that define the identity of " +
+        "the LifecycleResumeEffect and determine when its previous effect coroutine should be " +
+        "cancelled and a new effect launched for the new key."
+
+/**
+ * It is an error to call [LifecycleStartEffect] without at least one `key` parameter.
+ *
+ * This deprecated-error function shadows the varargs overload so that the varargs version is not
+ * used without key parameters.
+ *
+ * @see LifecycleResumeEffect
+ */
+@Deprecated(LifecycleResumeEffectNoParamError, level = DeprecationLevel.ERROR)
+@Composable
+@Suppress("UNUSED_PARAMETER")
+fun LifecycleResumeEffect(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
+): Unit = error(LifecycleResumeEffectNoParamError)
+
 @Composable
 private fun LifecycleResumeEffectImpl(
     lifecycleOwner: LifecycleOwner,
diff --git a/lifecycle/lifecycle-runtime-ktx/api/current.ignore b/lifecycle/lifecycle-runtime-ktx/api/current.ignore
new file mode 100644
index 0000000..01ef7e3
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.lifecycle:
+    Removed package androidx.lifecycle
diff --git a/lifecycle/lifecycle-runtime-ktx/api/current.txt b/lifecycle/lifecycle-runtime-ktx/api/current.txt
index 2ee0d85..e6f50d0 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/current.txt
@@ -1,33 +1 @@
 // Signature format: 4.0
-package androidx.lifecycle {
-
-  public final class FlowExtKt {
-    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
-  }
-
-  public final class LifecycleDestroyedException extends java.util.concurrent.CancellationException {
-    ctor public LifecycleDestroyedException();
-  }
-
-  public final class RepeatOnLifecycleKt {
-    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-  }
-
-  public final class ViewKt {
-    method @Deprecated public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View);
-  }
-
-  public final class WithLifecycleStateKt {
-    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-  }
-
-}
-
diff --git a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore
new file mode 100644
index 0000000..01ef7e3
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.lifecycle:
+    Removed package androidx.lifecycle
diff --git a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
index a998f6e..e6f50d0 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
@@ -1,35 +1 @@
 // Signature format: 4.0
-package androidx.lifecycle {
-
-  public final class FlowExtKt {
-    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
-  }
-
-  public final class LifecycleDestroyedException extends java.util.concurrent.CancellationException {
-    ctor public LifecycleDestroyedException();
-  }
-
-  public final class RepeatOnLifecycleKt {
-    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-  }
-
-  public final class ViewKt {
-    method @Deprecated public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View);
-  }
-
-  public final class WithLifecycleStateKt {
-    method @kotlin.PublishedApi internal static suspend <R> Object? suspendWithStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, boolean dispatchNeeded, kotlinx.coroutines.CoroutineDispatcher lifecycleDispatcher, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-    method @kotlin.PublishedApi internal static suspend inline <R> Object? withStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
-  }
-
-}
-
diff --git a/lifecycle/lifecycle-runtime-ktx/build.gradle b/lifecycle/lifecycle-runtime-ktx/build.gradle
index 1f60592..7e4683b 100644
--- a/lifecycle/lifecycle-runtime-ktx/build.gradle
+++ b/lifecycle/lifecycle-runtime-ktx/build.gradle
@@ -21,31 +21,42 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KmpPlatformsKt
+import androidx.build.PlatformIdentifier
 import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
-    id("org.jetbrains.kotlin.android")
+}
+
+androidXMultiplatform {
+    android()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+                api(project(":lifecycle:lifecycle-runtime"))
+                api(project(":annotation:annotation"))
+            }
+        }
+
+        androidMain {
+            dependsOn(commonMain)
+            dependencies {
+                api(libs.kotlinCoroutinesAndroid)
+            }
+        }
+    }
 }
 
 dependencies {
-    api(project(":lifecycle:lifecycle-runtime"))
-    api(libs.kotlinStdlib)
-    api(libs.kotlinCoroutinesAndroid)
-    api("androidx.annotation:annotation:1.0.0")
-
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-
-    androidTestImplementation(project(":lifecycle:lifecycle-runtime"))
-    androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.kotlinCoroutinesTest)
-
     lintPublish(project(":lifecycle:lifecycle-runtime-ktx-lint"))
 }
 
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
deleted file mode 100644
index e69de29..0000000
--- a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
+++ /dev/null
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/View.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/View.kt
deleted file mode 100644
index 9d17f28..0000000
--- a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/View.kt
+++ /dev/null
@@ -1,34 +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.lifecycle
-
-import android.view.View
-
-/**
- * Locates the [LifecycleOwner] responsible for managing this [View], if present.
- * This may be used to scope work or heavyweight resources associated with the view
- * that may span cycles of the view becoming detached and reattached from a window.
- */
-@Deprecated(
-    message = "Replaced by View.findViewTreeLifecycleOwner() from lifecycle module",
-    replaceWith = ReplaceWith(
-        "findViewTreeLifecycleOwner()",
-        "androidx.lifecycle.findViewTreeLifecycleOwner"
-    ),
-    level = DeprecationLevel.HIDDEN
-)
-public fun View.findViewTreeLifecycleOwner(): LifecycleOwner? = findViewTreeLifecycleOwner()
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/WithLifecycleState.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/WithLifecycleState.kt
deleted file mode 100644
index e8f63d7..0000000
--- a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/WithLifecycleState.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2020 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.lifecycle
-
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.coroutines.resumeWithException
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.suspendCancellableCoroutine
-
-/**
- * A [CancellationException] that indicates that the [Lifecycle] associated with an operation
- * reached the [Lifecycle.State.DESTROYED] state before the operation could complete.
- */
-public class LifecycleDestroyedException : CancellationException()
-
-/**
- * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [state] and
- * resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
- * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
- * [block] is able to run.
- */
-public suspend inline fun <R> Lifecycle.withStateAtLeast(
-    state: Lifecycle.State,
-    crossinline block: () -> R
-): R {
-    require(state >= Lifecycle.State.CREATED) {
-        "target state must be CREATED or greater, found $state"
-    }
-
-    return withStateAtLeastUnchecked(state, block)
-}
-
-/**
- * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.CREATED]
- * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
- * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
- * [block] is able to run.
- */
-public suspend inline fun <R> Lifecycle.withCreated(
-    crossinline block: () -> R
-): R = withStateAtLeastUnchecked(
-    state = Lifecycle.State.CREATED,
-    block = block
-)
-
-/**
- * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.STARTED]
- * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
- * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
- * [block] is able to run.
- */
-public suspend inline fun <R> Lifecycle.withStarted(
-    crossinline block: () -> R
-): R = withStateAtLeastUnchecked(
-    state = Lifecycle.State.STARTED,
-    block = block
-)
-
-/**
- * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.RESUMED]
- * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
- * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
- * [block] is able to run.
- */
-public suspend inline fun <R> Lifecycle.withResumed(
-    crossinline block: () -> R
-): R = withStateAtLeastUnchecked(
-    state = Lifecycle.State.RESUMED,
-    block = block
-)
-
-/**
- * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least [state]
- * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
- * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
- * [block] is able to run.
- */
-public suspend inline fun <R> LifecycleOwner.withStateAtLeast(
-    state: Lifecycle.State,
-    crossinline block: () -> R
-): R = lifecycle.withStateAtLeast(
-    state = state,
-    block = block
-)
-
-/**
- * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
- * [Lifecycle.State.CREATED] and resume with the result.
- * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
- * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
- */
-public suspend inline fun <R> LifecycleOwner.withCreated(
-    crossinline block: () -> R
-): R = lifecycle.withStateAtLeastUnchecked(
-    state = Lifecycle.State.CREATED,
-    block = block
-)
-
-/**
- * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
- * [Lifecycle.State.STARTED] and resume with the result.
- * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
- * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
- */
-public suspend inline fun <R> LifecycleOwner.withStarted(
-    crossinline block: () -> R
-): R = lifecycle.withStateAtLeastUnchecked(
-    state = Lifecycle.State.STARTED,
-    block = block
-)
-
-/**
- * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
- * [Lifecycle.State.RESUMED] and resume with the result.
- * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
- * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
- */
-public suspend inline fun <R> LifecycleOwner.withResumed(
-    crossinline block: () -> R
-): R = lifecycle.withStateAtLeastUnchecked(
-    state = Lifecycle.State.RESUMED,
-    block = block
-)
-
-/**
- * The inlined check for whether dispatch is necessary to perform [Lifecycle.withStateAtLeast]
- * operations that does not bounds-check [state]. Used internally when we know the target state
- * is already in bounds. Runs [block] inline without allocating if possible.
- */
-@PublishedApi
-internal suspend inline fun <R> Lifecycle.withStateAtLeastUnchecked(
-    state: Lifecycle.State,
-    crossinline block: () -> R
-): R {
-    // Fast path: if our lifecycle dispatcher doesn't require dispatch we can check
-    // the current lifecycle state and decide if we can run synchronously
-    val lifecycleDispatcher = Dispatchers.Main.immediate
-    val dispatchNeeded = lifecycleDispatcher.isDispatchNeeded(coroutineContext)
-    if (!dispatchNeeded) {
-        if (currentState == Lifecycle.State.DESTROYED) throw LifecycleDestroyedException()
-        if (currentState >= state) return block()
-    }
-
-    return suspendWithStateAtLeastUnchecked(state, dispatchNeeded, lifecycleDispatcher) {
-        block()
-    }
-}
-
-/**
- * The "slow" code path for [Lifecycle.withStateAtLeast] operations that requires allocating
- * and suspending, factored into a non-inlined function to avoid inflating code size at call sites
- * or exposing too many implementation details as inlined code.
- */
-@PublishedApi
-internal suspend fun <R> Lifecycle.suspendWithStateAtLeastUnchecked(
-    state: Lifecycle.State,
-    dispatchNeeded: Boolean,
-    lifecycleDispatcher: CoroutineDispatcher,
-    block: () -> R
-): R = suspendCancellableCoroutine { co ->
-    val observer = object : LifecycleEventObserver {
-        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
-            if (event == Lifecycle.Event.upTo(state)) {
-                removeObserver(this)
-                co.resumeWith(runCatching(block))
-            } else if (event == Lifecycle.Event.ON_DESTROY) {
-                removeObserver(this)
-                co.resumeWithException(LifecycleDestroyedException())
-            }
-        }
-    }
-
-    if (dispatchNeeded) {
-        lifecycleDispatcher.dispatch(
-            EmptyCoroutineContext,
-            Runnable { addObserver(observer) }
-        )
-    } else addObserver(observer)
-
-    co.invokeOnCancellation {
-        if (lifecycleDispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
-            lifecycleDispatcher.dispatch(
-                EmptyCoroutineContext,
-                Runnable { removeObserver(observer) }
-            )
-        } else removeObserver(observer)
-    }
-}
diff --git a/lifecycle/lifecycle-runtime/api/current.txt b/lifecycle/lifecycle-runtime/api/current.txt
index b3a4ef0..abca56a 100644
--- a/lifecycle/lifecycle-runtime/api/current.txt
+++ b/lifecycle/lifecycle-runtime/api/current.txt
@@ -1,6 +1,14 @@
 // Signature format: 4.0
 package androidx.lifecycle {
 
+  public final class FlowExtKt {
+    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
+  }
+
+  public final class LifecycleDestroyedException extends java.util.concurrent.CancellationException {
+    ctor public LifecycleDestroyedException();
+  }
+
   public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
     ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner provider);
     method public void addObserver(androidx.lifecycle.LifecycleObserver observer);
@@ -25,10 +33,30 @@
     method @Deprecated public androidx.lifecycle.LifecycleRegistry getLifecycle();
   }
 
+  public final class RepeatOnLifecycleKt {
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public final class ViewKt {
+    method @Deprecated public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View view);
+  }
+
   public final class ViewTreeLifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner? get(android.view.View);
     method public static void set(android.view.View, androidx.lifecycle.LifecycleOwner? lifecycleOwner);
   }
 
+  public final class WithLifecycleStateKt {
+    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+  }
+
 }
 
diff --git a/lifecycle/lifecycle-runtime/api/restricted_current.txt b/lifecycle/lifecycle-runtime/api/restricted_current.txt
index 8cfc8dc..781d190 100644
--- a/lifecycle/lifecycle-runtime/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime/api/restricted_current.txt
@@ -1,6 +1,14 @@
 // Signature format: 4.0
 package androidx.lifecycle {
 
+  public final class FlowExtKt {
+    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
+  }
+
+  public final class LifecycleDestroyedException extends java.util.concurrent.CancellationException {
+    ctor public LifecycleDestroyedException();
+  }
+
   public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
     ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner provider);
     method public void addObserver(androidx.lifecycle.LifecycleObserver observer);
@@ -25,6 +33,11 @@
     method @Deprecated public androidx.lifecycle.LifecycleRegistry getLifecycle();
   }
 
+  public final class RepeatOnLifecycleKt {
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ReportFragment extends android.app.Fragment {
     ctor public ReportFragment();
     method public static final androidx.lifecycle.ReportFragment get(android.app.Activity);
@@ -50,10 +63,27 @@
     method public void injectIfNeededIn(android.app.Activity activity);
   }
 
+  public final class ViewKt {
+    method @Deprecated public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View view);
+  }
+
   public final class ViewTreeLifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner? get(android.view.View);
     method public static void set(android.view.View, androidx.lifecycle.LifecycleOwner? lifecycleOwner);
   }
 
+  public final class WithLifecycleStateKt {
+    method @kotlin.PublishedApi internal static suspend <R> Object? suspendWithStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, boolean dispatchNeeded, kotlinx.coroutines.CoroutineDispatcher lifecycleDispatcher, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend inline <R> Object? withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+    method @kotlin.PublishedApi internal static suspend inline <R> Object? withStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function0<? extends R> block, kotlin.coroutines.Continuation<? super R>);
+  }
+
 }
 
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index ca005ac..1bc8f7c 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -60,6 +60,7 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
+                api(libs.kotlinCoroutinesAndroid)
                 implementation("androidx.arch.core:core-runtime:2.2.0")
                 implementation("androidx.profileinstaller:profileinstaller:1.3.0")
             }
@@ -77,9 +78,11 @@
             dependsOn(commonTest)
             dependencies {
                 implementation(libs.junit)
+                implementation(libs.truth)
                 implementation(libs.testExtJunit)
                 implementation(libs.testCore)
                 implementation(libs.testRunner)
+                implementation(libs.kotlinCoroutinesTest)
             }
         }
 
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/Expectations.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/Expectations.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/Expectations.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/Expectations.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/FakeLifecycleOwner.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/FakeLifecycleOwner.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FlowWithLifecycleTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FlowWithLifecycleTest.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/TaskTracker.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/TaskTracker.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/TaskTracker.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/TaskTracker.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/TrackedExecutor.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/TrackedExecutor.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/WithLifecycleStateTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/WithLifecycleStateTest.kt
rename to lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/AndroidManifest.xml b/lifecycle/lifecycle-runtime/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/main/AndroidManifest.xml
rename to lifecycle/lifecycle-runtime/src/androidMain/AndroidManifest.xml
diff --git a/lifecycle/lifecycle-runtime/src/androidMain/kotlin/androidx/lifecycle/View.android.kt b/lifecycle/lifecycle-runtime/src/androidMain/kotlin/androidx/lifecycle/View.android.kt
new file mode 100644
index 0000000..4181808
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/androidMain/kotlin/androidx/lifecycle/View.android.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("ViewKt")
+
+package androidx.lifecycle
+
+import android.view.View
+
+/**
+ * Locates the [LifecycleOwner] responsible for managing this [View], if present.
+ * This may be used to scope work or heavyweight resources associated with the view
+ * that may span cycles of the view becoming detached and reattached from a window.
+ */
+@Deprecated(
+    message = "Replaced by View.findViewTreeLifecycleOwner() from lifecycle module",
+    replaceWith = ReplaceWith(
+        "findViewTreeLifecycleOwner()",
+        "androidx.lifecycle.findViewTreeLifecycleOwner"
+    ),
+    level = DeprecationLevel.HIDDEN
+)
+public fun findViewTreeLifecycleOwner(view: View): LifecycleOwner? =
+    view.findViewTreeLifecycleOwner()
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/FlowExt.kt b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/FlowExt.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/FlowExt.kt
rename to lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/FlowExt.kt
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/RepeatOnLifecycle.kt
similarity index 100%
rename from lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
rename to lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/RepeatOnLifecycle.kt
diff --git a/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/WithLifecycleState.kt b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/WithLifecycleState.kt
new file mode 100644
index 0000000..6f25167
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/WithLifecycleState.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2020 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.lifecycle
+
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * A [CancellationException] that indicates that the [Lifecycle] associated with an operation
+ * reached the [Lifecycle.State.DESTROYED] state before the operation could complete.
+ */
+public class LifecycleDestroyedException : CancellationException(null as String?)
+
+/**
+ * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [state] and
+ * resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
+ * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
+ * [block] is able to run.
+ */
+public suspend inline fun <R> Lifecycle.withStateAtLeast(
+    state: Lifecycle.State,
+    crossinline block: () -> R
+): R {
+    require(state >= Lifecycle.State.CREATED) {
+        "target state must be CREATED or greater, found $state"
+    }
+
+    return withStateAtLeastUnchecked(state, block)
+}
+
+/**
+ * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.CREATED]
+ * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
+ * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
+ * [block] is able to run.
+ */
+public suspend inline fun <R> Lifecycle.withCreated(
+    crossinline block: () -> R
+): R = withStateAtLeastUnchecked(
+    state = Lifecycle.State.CREATED,
+    block = block
+)
+
+/**
+ * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.STARTED]
+ * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
+ * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
+ * [block] is able to run.
+ */
+public suspend inline fun <R> Lifecycle.withStarted(
+    crossinline block: () -> R
+): R = withStateAtLeastUnchecked(
+    state = Lifecycle.State.STARTED,
+    block = block
+)
+
+/**
+ * Run [block] with this [Lifecycle] in a [Lifecycle.State] of at least [Lifecycle.State.RESUMED]
+ * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
+ * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
+ * [block] is able to run.
+ */
+public suspend inline fun <R> Lifecycle.withResumed(
+    crossinline block: () -> R
+): R = withStateAtLeastUnchecked(
+    state = Lifecycle.State.RESUMED,
+    block = block
+)
+
+/**
+ * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least [state]
+ * and resume with the result. Throws the [CancellationException] [LifecycleDestroyedException]
+ * if the lifecycle has reached [Lifecycle.State.DESTROYED] by the time of the call or before
+ * [block] is able to run.
+ */
+public suspend inline fun <R> LifecycleOwner.withStateAtLeast(
+    state: Lifecycle.State,
+    crossinline block: () -> R
+): R = lifecycle.withStateAtLeast(
+    state = state,
+    block = block
+)
+
+/**
+ * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
+ * [Lifecycle.State.CREATED] and resume with the result.
+ * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
+ * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
+ */
+public suspend inline fun <R> LifecycleOwner.withCreated(
+    crossinline block: () -> R
+): R = lifecycle.withStateAtLeastUnchecked(
+    state = Lifecycle.State.CREATED,
+    block = block
+)
+
+/**
+ * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
+ * [Lifecycle.State.STARTED] and resume with the result.
+ * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
+ * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
+ */
+public suspend inline fun <R> LifecycleOwner.withStarted(
+    crossinline block: () -> R
+): R = lifecycle.withStateAtLeastUnchecked(
+    state = Lifecycle.State.STARTED,
+    block = block
+)
+
+/**
+ * Run [block] with this [LifecycleOwner]'s [Lifecycle] in a [Lifecycle.State] of at least
+ * [Lifecycle.State.RESUMED] and resume with the result.
+ * Throws the [CancellationException] [LifecycleDestroyedException] if the lifecycle has reached
+ * [Lifecycle.State.DESTROYED] by the time of the call or before [block] is able to run.
+ */
+public suspend inline fun <R> LifecycleOwner.withResumed(
+    crossinline block: () -> R
+): R = lifecycle.withStateAtLeastUnchecked(
+    state = Lifecycle.State.RESUMED,
+    block = block
+)
+
+/**
+ * The inlined check for whether dispatch is necessary to perform [Lifecycle.withStateAtLeast]
+ * operations that does not bounds-check [state]. Used internally when we know the target state
+ * is already in bounds. Runs [block] inline without allocating if possible.
+ */
+@PublishedApi
+internal suspend inline fun <R> Lifecycle.withStateAtLeastUnchecked(
+    state: Lifecycle.State,
+    crossinline block: () -> R
+): R {
+    // Fast path: if our lifecycle dispatcher doesn't require dispatch we can check
+    // the current lifecycle state and decide if we can run synchronously
+    val lifecycleDispatcher = Dispatchers.Main.immediate
+    val dispatchNeeded = lifecycleDispatcher.isDispatchNeeded(coroutineContext)
+    if (!dispatchNeeded) {
+        if (currentState == Lifecycle.State.DESTROYED) throw LifecycleDestroyedException()
+        if (currentState >= state) return block()
+    }
+
+    return suspendWithStateAtLeastUnchecked(state, dispatchNeeded, lifecycleDispatcher) {
+        block()
+    }
+}
+
+/**
+ * The "slow" code path for [Lifecycle.withStateAtLeast] operations that requires allocating
+ * and suspending, factored into a non-inlined function to avoid inflating code size at call sites
+ * or exposing too many implementation details as inlined code.
+ */
+@PublishedApi
+internal suspend fun <R> Lifecycle.suspendWithStateAtLeastUnchecked(
+    state: Lifecycle.State,
+    dispatchNeeded: Boolean,
+    lifecycleDispatcher: CoroutineDispatcher,
+    block: () -> R
+): R = suspendCancellableCoroutine { co ->
+    val observer = object : LifecycleEventObserver {
+        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+            if (event == Lifecycle.Event.upTo(state)) {
+                removeObserver(this)
+                co.resumeWith(runCatching(block))
+            } else if (event == Lifecycle.Event.ON_DESTROY) {
+                removeObserver(this)
+                co.resumeWithException(LifecycleDestroyedException())
+            }
+        }
+    }
+
+    if (dispatchNeeded) {
+        lifecycleDispatcher.dispatch(
+            EmptyCoroutineContext,
+            Runnable { addObserver(observer) }
+        )
+    } else addObserver(observer)
+
+    co.invokeOnCancellation {
+        if (lifecycleDispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
+            lifecycleDispatcher.dispatch(
+                EmptyCoroutineContext,
+                Runnable { removeObserver(observer) }
+            )
+        } else removeObserver(observer)
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/build.gradle b/lifecycle/lifecycle-viewmodel/build.gradle
index fa9c73e..13d4549 100644
--- a/lifecycle/lifecycle-viewmodel/build.gradle
+++ b/lifecycle/lifecycle-viewmodel/build.gradle
@@ -21,13 +21,68 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+import androidx.build.PlatformIdentifier
 import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
-    id("kotlin-android")
+}
+
+androidXMultiplatform {
+    android()
+    kotlin {
+        explicitApi = ExplicitApiMode.Strict
+    }
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                // TODO(b/214568825): migrate from `androidMain` to here.
+            }
+        }
+
+        commonTest {
+            dependencies {
+                // TODO(b/214568825): migrate from `androidUnitTest` to here.
+            }
+        }
+
+        androidMain {
+            dependsOn(commonMain)
+            dependencies {
+                api(project(":annotation:annotation"))
+                api(libs.kotlinStdlib)
+                api(libs.kotlinCoroutinesAndroid)
+            }
+        }
+
+        androidUnitTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation(libs.junit)
+                implementation(libs.mockitoCore4)
+                implementation(libs.kotlinTest)
+                implementation(libs.truth)
+            }
+        }
+
+        androidInstrumentedTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation("androidx.core:core-ktx:1.2.0")
+                implementation(libs.truth)
+                implementation(libs.kotlinStdlib)
+                implementation(libs.junit)
+                implementation(libs.testExtJunit)
+                implementation(libs.testCore)
+                implementation(libs.testRunner)
+            }
+        }
+    }
 }
 
 android {
@@ -37,23 +92,6 @@
     namespace "androidx.lifecycle.viewmodel"
 }
 
-dependencies {
-    api("androidx.annotation:annotation:1.1.0")
-    api(libs.kotlinStdlib)
-    api(libs.kotlinCoroutinesAndroid)
-    testImplementation(libs.junit)
-    testImplementation(libs.mockitoCore4)
-    testImplementation(libs.truth)
-
-    androidTestImplementation("androidx.core:core-ktx:1.2.0")
-    androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.kotlinStdlib)
-    androidTestImplementation(libs.junit)
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-}
-
 androidx {
     name = "Lifecycle ViewModel"
     publish = Publish.SNAPSHOT_AND_RELEASE
diff --git a/lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/AndroidViewModelFactoryTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/AndroidViewModelFactoryTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/CreationExtrasTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/CreationExtrasTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidTest/java/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/main/AndroidManifest.xml b/lifecycle/lifecycle-viewmodel/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/main/AndroidManifest.xml
rename to lifecycle/lifecycle-viewmodel/src/androidMain/AndroidManifest.xml
diff --git a/lifecycle/lifecycle-viewmodel/src/main/baseline-prof.txt b/lifecycle/lifecycle-viewmodel/src/androidMain/baseline-prof.txt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/main/baseline-prof.txt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/baseline-prof.txt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.kt
new file mode 100644
index 0000000..17a5e71
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import android.app.Application
+
+/**
+ * Application context aware [ViewModel].
+ *
+ * Subclasses must have a constructor which accepts [Application] as the only parameter.
+ */
+public open class AndroidViewModel(private val application: Application) : ViewModel() {
+
+    /**
+     * Return the application.
+     */
+    @Suppress("UNCHECKED_CAST")
+    public open fun <T : Application> getApplication(): T {
+        return application as T
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt
new file mode 100644
index 0000000..80648f8
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.lifecycle
+
+import androidx.lifecycle.viewmodel.CreationExtras
+
+/**
+ * Interface that marks a [ViewModelStoreOwner] as having a default
+ * [ViewModelProvider.Factory] for use with [ViewModelProvider].
+ */
+public interface HasDefaultViewModelProviderFactory {
+    /**
+     * Returns the default [ViewModelProvider.Factory] that should be
+     * used when no custom `Factory` is provided to the
+     * [ViewModelProvider] constructors.
+     */
+    public val defaultViewModelProviderFactory: ViewModelProvider.Factory
+
+    /**
+     * Returns the default [CreationExtras] that should be passed into
+     * [ViewModelProvider.Factory.create] when no overriding
+     * [CreationExtras] were passed to the [ViewModelProvider] constructors.
+     */
+    public val defaultViewModelCreationExtras: CreationExtras
+        get() = CreationExtras.Empty
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt
new file mode 100644
index 0000000..da940078
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import androidx.annotation.MainThread
+import java.io.Closeable
+import java.io.IOException
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+
+/**
+ * ViewModel is a class that is responsible for preparing and managing the data for
+ * an [Activity][android.app.Activity] or a [Fragment][androidx.fragment.app.Fragment].
+ * It also handles the communication of the Activity / Fragment with the rest of the application
+ * (e.g. calling the business logic classes).
+ *
+ * A ViewModel is always created in association with a scope (a fragment or an activity) and will
+ * be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.
+ *
+ * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
+ * configuration change (e.g. rotation). The new owner instance just re-connects to the existing
+ * model.
+ *
+ * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
+ * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
+ * ViewModel. ViewModels usually expose this information via [Lifecycle][androidx.lifecycle.LiveData] or
+ * Android Data Binding. You can also use any observability construct from your favorite framework.
+ *
+ * ViewModel's only responsibility is to manage the data for the UI. It **should never** access
+ * your view hierarchy or hold a reference back to the Activity or the Fragment.
+ *
+ * Typical usage from an Activity standpoint would be:
+ *
+ * ```
+ * class UserActivity : ComponentActivity {
+ *     private val viewModel by viewModels<UserViewModel>()
+ *
+ *     override fun onCreate(savedInstanceState: Bundle) {
+ *         super.onCreate(savedInstanceState)
+ *         setContentView(R.layout.user_activity_layout)
+ *         viewModel.user.observe(this) { user: User ->
+ *             // update ui.
+ *         }
+ *         requireViewById(R.id.button).setOnClickListener {
+ *             viewModel.doAction()
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * ViewModel would be:
+ *
+ * ```
+ * class UserViewModel : ViewModel {
+ *     private val userLiveData = MutableLiveData<User>()
+ *     val user: LiveData<User> get() = userLiveData
+ *
+ *     init {
+ *         // trigger user load.
+ *     }
+ *
+ *     fun doAction() {
+ *         // depending on the action, do necessary business logic calls and update the
+ *         // userLiveData.
+ *     }
+ * }
+ * ```
+ *
+ * ViewModels can also be used as a communication layer between different Fragments of an Activity.
+ * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
+ * communication between Fragments in a de-coupled fashion such that they never need to talk to
+ * the other Fragment directly.
+ *
+ * ```
+ * class MyFragment : Fragment {
+ *   val viewModel by activityViewModels<UserViewModel>()
+ * }
+ *```
+ */
+public abstract class ViewModel {
+
+    // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
+    private val bagOfTags = mutableMapOf<String, Any>()
+    private val closeables = mutableSetOf<Closeable>()
+
+    @Volatile
+    private var isCleared = false
+
+    /**
+     * Construct a new ViewModel instance.
+     *
+     * You should **never** manually construct a ViewModel outside of a
+     * [ViewModelProvider.Factory].
+     */
+    public constructor()
+
+    /**
+     * Construct a new ViewModel instance. Any [Closeable] objects provided here
+     * will be closed directly before [ViewModel.onCleared] is called.
+     *
+     * You should **never** manually construct a ViewModel outside of a
+     * [ViewModelProvider.Factory].
+     */
+    public constructor(vararg closeables: Closeable) {
+        this.closeables += closeables
+    }
+
+    /**
+     * This method will be called when this ViewModel is no longer used and will be destroyed.
+     *
+     * It is useful when ViewModel observes some data and you need to clear this subscription to
+     * prevent a leak of this ViewModel.
+     */
+    protected open fun onCleared() {}
+
+    @MainThread
+    internal fun clear() {
+        isCleared = true
+        // Since `clear()` is final, this method is still called on mock objects
+        // and in those cases, `bagOfTags` is `null`. It'll always be empty though
+        // because `setTagIfAbsent` and `getTag` are not final so we can skip
+        // clearing it
+        @Suppress("SENSELESS_COMPARISON")
+        if (bagOfTags != null) {
+            synchronized(bagOfTags) {
+                for (value in bagOfTags.values) {
+                    // see comment for the similar call in `setTagIfAbsent`
+                    closeWithRuntimeException(value)
+                }
+            }
+        }
+        // We need the same null check here
+        @Suppress("SENSELESS_COMPARISON")
+        if (closeables != null) {
+            synchronized(closeables) {
+                for (closeable in closeables) {
+                    closeWithRuntimeException(closeable)
+                }
+            }
+            closeables.clear()
+        }
+        onCleared()
+    }
+
+    /**
+     * Add a new [Closeable] object that will be closed directly before
+     * [ViewModel.onCleared] is called.
+     *
+     * If `onCleared()` has already been called, the closeable will not be added,
+     * and will instead be closed immediately.
+     *
+     * @param key A key that allows you to retrieve the closeable passed in by using the same
+     *            key with [ViewModel.getCloseable]
+     * @param closeable The object that should be [Closeable.close] directly before
+     *                  [ViewModel.onCleared] is called.
+     */
+    public fun addCloseable(key: String, closeable: Closeable) {
+        // Although no logic should be done after user calls onCleared(), we will
+        // ensure that if it has already been called, the closeable attempting to
+        // be added will be closed immediately to ensure there will be no leaks.
+        if (isCleared) {
+            closeWithRuntimeException(closeable)
+            return
+        }
+
+        // As this method is final, it will still be called on mock objects even
+        // though `closeables` won't actually be created...we'll just not do anything
+        // in that case.
+        @Suppress("SENSELESS_COMPARISON")
+        if (bagOfTags != null) {
+            synchronized(bagOfTags) { bagOfTags.put(key, closeable) }
+        }
+    }
+
+    /**
+     * Add a new [Closeable] object that will be closed directly before
+     * [ViewModel.onCleared] is called.
+     *
+     * If `onCleared()` has already been called, the closeable will not be added,
+     * and will instead be closed immediately.
+     *
+     * @param closeable The object that should be [closed][Closeable.close] directly before
+     *                  [ViewModel.onCleared] is called.
+     */
+    public open fun addCloseable(closeable: Closeable) {
+        // Although no logic should be done after user calls onCleared(), we will
+        // ensure that if it has already been called, the closeable attempting to
+        // be added will be closed immediately to ensure there will be no leaks.
+        if (isCleared) {
+            closeWithRuntimeException(closeable)
+            return
+        }
+
+        // As this method is final, it will still be called on mock objects even
+        // though `closeables` won't actually be created...we'll just not do anything
+        // in that case.
+        @Suppress("SENSELESS_COMPARISON")
+        if (this.closeables != null) {
+            synchronized(this.closeables) {
+                this.closeables.add(closeable)
+            }
+        }
+    }
+
+    /**
+     * Returns the closeable previously added with [ViewModel.addCloseable] with the given key.
+     *
+     * @param key The key that was used to add the Closeable.
+     */
+    public fun <T : Closeable> getCloseable(key: String): T? {
+        @Suppress("SENSELESS_COMPARISON")
+        if (bagOfTags == null) {
+            return null
+        }
+        synchronized(bagOfTags) {
+            @Suppress("UNCHECKED_CAST")
+            return bagOfTags[key] as T?
+        }
+    }
+
+    private fun closeWithRuntimeException(instance: Any) {
+        if (instance is Closeable) {
+            try {
+                instance.close()
+            } catch (e: IOException) {
+                throw RuntimeException(e)
+            }
+        }
+    }
+}
+
+private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
+
+/**
+ * [CoroutineScope] tied to this [ViewModel].
+ * This scope will be canceled when ViewModel will be cleared, i.e. [ViewModel.onCleared] is called
+ *
+ * This scope is bound to
+ * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
+ */
+public val ViewModel.viewModelScope: CoroutineScope
+    get() {
+        return getCloseable<CloseableCoroutineScope>(JOB_KEY) ?: CloseableCoroutineScope(
+            SupervisorJob() + Dispatchers.Main.immediate
+        ).also { newClosableScope ->
+            addCloseable(JOB_KEY, newClosableScope)
+        }
+    }
+
+private class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
+    override val coroutineContext: CoroutineContext = context
+
+    override fun close() {
+        coroutineContext.cancel()
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelLazy.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelLazy.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
new file mode 100644
index 0000000..4c4761b
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+@file:JvmName("ViewModelProviderGetKt")
+
+package androidx.lifecycle
+
+import android.app.Application
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.DEFAULT_KEY
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.defaultFactory
+import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.VIEW_MODEL_KEY
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.CreationExtras.Key
+import androidx.lifecycle.viewmodel.InitializerViewModelFactory
+import androidx.lifecycle.viewmodel.MutableCreationExtras
+import androidx.lifecycle.viewmodel.ViewModelInitializer
+import java.lang.IllegalArgumentException
+import java.lang.RuntimeException
+import java.lang.reflect.InvocationTargetException
+import kotlin.UnsupportedOperationException
+
+/**
+ * A utility class that provides `ViewModels` for a scope.
+ *
+ * Default `ViewModelProvider` for an `Activity` or a `Fragment` can be obtained
+ * by passing it to the constructor: `ViewModelProvider(myFragment)`
+ */
+public open class ViewModelProvider
+/**
+ * Creates a ViewModelProvider
+ *
+ * @param store `ViewModelStore` where ViewModels will be stored.
+ * @param factory factory a `Factory` which will be used to instantiate new `ViewModels`
+ * @param defaultCreationExtras extras to pass to a factory
+ */
+@JvmOverloads
+constructor(
+    private val store: ViewModelStore,
+    private val factory: Factory,
+    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
+) {
+    /**
+     * Implementations of `Factory` interface are responsible to instantiate ViewModels.
+     */
+    public interface Factory {
+        /**
+         * Creates a new instance of the given `Class`.
+         *
+         * Default implementation throws [UnsupportedOperationException].
+         *
+         * @param modelClass a `Class` whose instance is requested
+         * @return a newly created ViewModel
+         */
+        public fun <T : ViewModel> create(modelClass: Class<T>): T {
+            throw UnsupportedOperationException(
+                "Factory.create(String) is unsupported.  This Factory requires " +
+                    "`CreationExtras` to be passed into `create` method."
+            )
+        }
+
+        /**
+         * Creates a new instance of the given `Class`.
+         *
+         * @param modelClass a `Class` whose instance is requested
+         * @param extras an additional information for this creation request
+         * @return a newly created ViewModel
+         */
+        public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
+            create(modelClass)
+
+        public companion object {
+            /**
+             * Creates an [InitializerViewModelFactory] using the given initializers.
+             *
+             * @param initializers the class initializer pairs used for the factory to create
+             * simple view models
+             */
+            @JvmStatic
+            public fun from(vararg initializers: ViewModelInitializer<*>): Factory =
+                InitializerViewModelFactory(*initializers)
+        }
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public open class OnRequeryFactory {
+        public open fun onRequery(viewModel: ViewModel) {}
+    }
+
+    /**
+     * Creates `ViewModelProvider`. This will create `ViewModels`
+     * and retain them in a store of the given `ViewModelStoreOwner`.
+     *
+     *
+     * This method will use the
+     * [default factory][HasDefaultViewModelProviderFactory.defaultViewModelProviderFactory]
+     * if the owner implements [HasDefaultViewModelProviderFactory]. Otherwise, a
+     * [NewInstanceFactory] will be used.
+     */
+    public constructor(
+        owner: ViewModelStoreOwner
+    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
+
+    /**
+     * Creates `ViewModelProvider`, which will create `ViewModels` via the given
+     * `Factory` and retain them in a store of the given `ViewModelStoreOwner`.
+     *
+     * @param owner   a `ViewModelStoreOwner` whose [ViewModelStore] will be used to
+     * retain `ViewModels`
+     * @param factory a `Factory` which will be used to instantiate
+     * new `ViewModels`
+     */
+    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
+        owner.viewModelStore,
+        factory,
+        defaultCreationExtras(owner)
+    )
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this `ViewModelProvider`.
+     *
+     *
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     * present.
+     * @return A ViewModel that is an instance of the given type `T`.
+     * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
+     */
+    @MainThread
+    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
+        val canonicalName = modelClass.canonicalName
+            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
+        return get("$DEFAULT_KEY:$canonicalName", modelClass)
+    }
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this `ViewModelProvider`.
+     *
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param key        The key to use to identify the ViewModel.
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     * present.
+     * @return A ViewModel that is an instance of the given type `T`.
+     */
+    @Suppress("UNCHECKED_CAST")
+    @MainThread
+    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
+        val viewModel = store[key]
+        if (modelClass.isInstance(viewModel)) {
+            (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
+            return viewModel as T
+        } else {
+            @Suppress("ControlFlowWithEmptyBody")
+            if (viewModel != null) {
+                // TODO: log a warning.
+            }
+        }
+        val extras = MutableCreationExtras(defaultCreationExtras)
+        extras[VIEW_MODEL_KEY] = key
+        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
+        // fall back to the other create method to keep from crashing.
+        return try {
+            factory.create(modelClass, extras)
+        } catch (e: AbstractMethodError) {
+            factory.create(modelClass)
+        }.also { store.put(key, it) }
+    }
+
+    /**
+     * Simple factory, which calls empty constructor on the give class.
+     */
+    // actually there is getInstance()
+    @Suppress("SingletonConstructor")
+    public open class NewInstanceFactory : Factory {
+        @Suppress("DocumentExceptions")
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            return try {
+                modelClass.getDeclaredConstructor().newInstance()
+            } catch (e: NoSuchMethodException) {
+                throw RuntimeException("Cannot create an instance of $modelClass", e)
+            } catch (e: InstantiationException) {
+                throw RuntimeException("Cannot create an instance of $modelClass", e)
+            } catch (e: IllegalAccessException) {
+                throw RuntimeException("Cannot create an instance of $modelClass", e)
+            }
+        }
+
+        public companion object {
+            private var sInstance: NewInstanceFactory? = null
+
+            /**
+             * Retrieve a singleton instance of NewInstanceFactory.
+             *
+             * @return A valid [NewInstanceFactory]
+             */
+            @JvmStatic
+            public val instance: NewInstanceFactory
+                @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+                get() {
+                    if (sInstance == null) {
+                        sInstance = NewInstanceFactory()
+                    }
+                    return sInstance!!
+                }
+
+            private object ViewModelKeyImpl : Key<String>
+            /**
+             * A [CreationExtras.Key] to get a key associated with a requested
+             * `ViewModel` from [CreationExtras]
+             *
+             *  `ViewModelProvider` automatically puts a key that was passed to
+             *  `ViewModelProvider.get(key, MyViewModel::class.java)`
+             *  or generated in `ViewModelProvider.get(MyViewModel::class.java)` to the `CreationExtras` that
+             *  are passed to [ViewModelProvider.Factory].
+             */
+            @JvmField
+            public val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl
+        }
+    }
+
+    /**
+     * [Factory] which may create [AndroidViewModel] and
+     * [ViewModel], which have an empty constructor.
+     *
+     * @param application an application to pass in [AndroidViewModel]
+     */
+    public open class AndroidViewModelFactory
+    private constructor(
+        private val application: Application?,
+        // parameter to avoid clash between constructors with nullable and non-nullable
+        // Application
+        @Suppress("UNUSED_PARAMETER") unused: Int,
+    ) : NewInstanceFactory() {
+
+        /**
+         * Constructs this factory.
+         * When a factory is constructed this way, a component for which [ViewModel] is created
+         * must provide an [Application] by [APPLICATION_KEY] in [CreationExtras], otherwise
+         *  [IllegalArgumentException] will be thrown from [create] method.
+         */
+        @Suppress("SingletonConstructor")
+        public constructor() : this(null, 0)
+
+        /**
+         * Constructs this factory.
+         *
+         * @param application an application to pass in [AndroidViewModel]
+         */
+        @Suppress("SingletonConstructor")
+        public constructor(application: Application) : this(application, 0)
+
+        @Suppress("DocumentExceptions")
+        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
+            return if (application != null) {
+                create(modelClass)
+            } else {
+                val application = extras[APPLICATION_KEY]
+                if (application != null) {
+                    create(modelClass, application)
+                } else {
+                    // For AndroidViewModels, CreationExtras must have an application set
+                    if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
+                        throw IllegalArgumentException(
+                            "CreationExtras must have an application by `APPLICATION_KEY`"
+                        )
+                    }
+                    super.create(modelClass)
+                }
+            }
+        }
+
+        @Suppress("DocumentExceptions")
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            return if (application == null) {
+                throw UnsupportedOperationException(
+                    "AndroidViewModelFactory constructed " +
+                        "with empty constructor works only with " +
+                        "create(modelClass: Class<T>, extras: CreationExtras)."
+                )
+            } else {
+                create(modelClass, application)
+            }
+        }
+
+        @Suppress("DocumentExceptions")
+        private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
+            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
+                try {
+                    modelClass.getConstructor(Application::class.java).newInstance(app)
+                } catch (e: NoSuchMethodException) {
+                    throw RuntimeException("Cannot create an instance of $modelClass", e)
+                } catch (e: IllegalAccessException) {
+                    throw RuntimeException("Cannot create an instance of $modelClass", e)
+                } catch (e: InstantiationException) {
+                    throw RuntimeException("Cannot create an instance of $modelClass", e)
+                } catch (e: InvocationTargetException) {
+                    throw RuntimeException("Cannot create an instance of $modelClass", e)
+                }
+            } else super.create(modelClass)
+        }
+
+        public companion object {
+            internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
+                if (owner is HasDefaultViewModelProviderFactory)
+                    owner.defaultViewModelProviderFactory else instance
+
+            internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
+
+            private var sInstance: AndroidViewModelFactory? = null
+
+            /**
+             * Retrieve a singleton instance of AndroidViewModelFactory.
+             *
+             * @param application an application to pass in [AndroidViewModel]
+             * @return A valid [AndroidViewModelFactory]
+             */
+            @JvmStatic
+            public fun getInstance(application: Application): AndroidViewModelFactory {
+                if (sInstance == null) {
+                    sInstance = AndroidViewModelFactory(application)
+                }
+                return sInstance!!
+            }
+
+            private object ApplicationKeyImpl : Key<Application>
+
+            /**
+             * A [CreationExtras.Key] to query an application in which ViewModel is being created.
+             */
+            @JvmField
+            public val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
+        }
+    }
+}
+
+internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras {
+    return if (owner is HasDefaultViewModelProviderFactory) {
+        owner.defaultViewModelCreationExtras
+    } else CreationExtras.Empty
+}
+
+/**
+ * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+ * an activity), associated with this `ViewModelProvider`.
+ *
+ * @see ViewModelProvider.get(Class)
+ */
+@MainThread
+public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class.java)
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStore.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStore.kt
new file mode 100644
index 0000000..7e91609
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStore.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import androidx.annotation.RestrictTo
+
+/**
+ * Class to store `ViewModel`s.
+ *
+ * An instance of `ViewModelStore` must be retained through configuration changes:
+ * if an owner of this `ViewModelStore` is destroyed and recreated due to configuration
+ * changes, new instance of an owner should still have the same old instance of
+ * `ViewModelStore`.
+ *
+ * If an owner of this `ViewModelStore` is destroyed and is not going to be recreated,
+ * then it should call [clear] on this `ViewModelStore`, so `ViewModel`s would
+ * be notified that they are no longer used.
+ *
+ * Use [ViewModelStoreOwner.getViewModelStore] to retrieve a `ViewModelStore` for
+ * activities and fragments.
+ */
+public open class ViewModelStore {
+
+    private val map = mutableMapOf<String, ViewModel>()
+
+    /**
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun put(key: String, viewModel: ViewModel) {
+        val oldViewModel = map.put(key, viewModel)
+        oldViewModel?.clear()
+    }
+
+    /**
+     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
+     */
+    /**
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public operator fun get(key: String): ViewModel? {
+        return map[key]
+    }
+
+    /**
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun keys(): Set<String> {
+        return HashSet(map.keys)
+    }
+
+    /**
+     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
+     */
+    public fun clear() {
+        for (vm in map.values) {
+            vm.clear()
+        }
+        map.clear()
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt
new file mode 100644
index 0000000..1f33fd1
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+/**
+ * A scope that owns [ViewModelStore].
+ *
+ * A responsibility of an implementation of this interface is to retain owned ViewModelStore
+ * during the configuration changes and call [ViewModelStore.clear], when this scope is
+ * going to be destroyed.
+ *
+ * @see ViewTreeViewModelStoreOwner
+ */
+public interface ViewModelStoreOwner {
+
+    /**
+     * The owned [ViewModelStore]
+     */
+    public val viewModelStore: ViewModelStore
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewTreeViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewTreeViewModel.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt
new file mode 100644
index 0000000..e196613
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 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.
+ */
+@file:JvmName("ViewTreeViewModelStoreOwner")
+
+package androidx.lifecycle
+
+import android.view.View
+import androidx.lifecycle.viewmodel.R
+
+/**
+ * Set the [ViewModelStoreOwner] associated with the given [View].
+ * Calls to [get] from this view or descendants will return
+ * `viewModelStoreOwner`.
+ *
+ * This should only be called by constructs such as activities or fragments that manage
+ * a view tree and retain state through a [ViewModelStoreOwner]. Callers
+ * should only set a [ViewModelStoreOwner] that will be *stable.* The associated
+ * [ViewModelStore] should be cleared if the view tree is removed and is not
+ * guaranteed to later become reattached to a window.
+ *
+ * @param viewModelStoreOwner ViewModelStoreOwner associated with the given view
+ */
+@JvmName("set")
+public fun View.setViewTreeViewModelStoreOwner(viewModelStoreOwner: ViewModelStoreOwner?) {
+    setTag(R.id.view_tree_view_model_store_owner, viewModelStoreOwner)
+}
+
+/**
+ * Retrieve the [ViewModelStoreOwner] associated with the given [View].
+ * This may be used to retain state associated with this view across configuration changes.
+ *
+ * @return The [ViewModelStoreOwner] associated with this view and/or some subset
+ * of its ancestors
+ */
+@JvmName("get")
+public fun View.findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? {
+    return generateSequence(this) { view ->
+        view.parent as? View
+    }.mapNotNull { view ->
+        view.getTag(R.id.view_tree_view_model_store_owner) as? ViewModelStoreOwner
+    }.firstOrNull()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
new file mode 100644
index 0000000..3d5d587
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 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.lifecycle.viewmodel
+
+/**
+ * Simple map-like object that passed in [ViewModelProvider.Factory.create]
+ * to provide an additional information to a factory.
+ *
+ * It allows making `Factory` implementations stateless, which makes an injection of factories
+ * easier because  don't require all information be available at construction time.
+ */
+public abstract class CreationExtras internal constructor() {
+    internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()
+
+    /**
+     * Key for the elements of [CreationExtras]. [T] is a type of an element with this key.
+     */
+    public interface Key<T>
+
+    /**
+     * Returns an element associated with the given [key]
+     */
+    public abstract operator fun <T> get(key: Key<T>): T?
+
+    /**
+     * Empty [CreationExtras]
+     */
+    public object Empty : CreationExtras() {
+        override fun <T> get(key: Key<T>): T? = null
+    }
+}
+
+/**
+ * Mutable implementation of [CreationExtras]
+ *
+ * @param initialExtras extras that will be filled into the resulting MutableCreationExtras
+ */
+public class MutableCreationExtras(initialExtras: CreationExtras = Empty) : CreationExtras() {
+
+    init {
+        map.putAll(initialExtras.map)
+    }
+    /**
+     * Associates the given [key] with [t]
+     */
+    public operator fun <T> set(key: Key<T>, t: T) {
+        map[key] = t
+    }
+
+    public override fun <T> get(key: Key<T>): T? {
+        @Suppress("UNCHECKED_CAST")
+        return map[key] as T?
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
new file mode 100644
index 0000000..38ec91c
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2022 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.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlin.reflect.KClass
+
+@DslMarker
+public annotation class ViewModelFactoryDsl
+
+/**
+ * Creates an [InitializerViewModelFactory] with the initializers provided in the builder.
+ */
+public inline fun viewModelFactory(
+    builder: InitializerViewModelFactoryBuilder.() -> Unit
+): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()
+
+/**
+ * DSL for constructing a new [ViewModelProvider.Factory]
+ */
+@ViewModelFactoryDsl
+public class InitializerViewModelFactoryBuilder {
+    private val initializers = mutableListOf<ViewModelInitializer<*>>()
+
+    /**
+     * Add the initializer for the given ViewModel class.
+     *
+     * @param clazz the class the initializer is associated with.
+     * @param initializer lambda used to create an instance of the ViewModel class
+     */
+    public fun <T : ViewModel> addInitializer(
+        clazz: KClass<T>,
+        initializer: CreationExtras.() -> T,
+    ) {
+        initializers.add(ViewModelInitializer(clazz.java, initializer))
+    }
+
+    /**
+     * Build the InitializerViewModelFactory.
+     */
+    public fun build(): ViewModelProvider.Factory =
+        InitializerViewModelFactory(*initializers.toTypedArray())
+}
+
+/**
+ * Add an initializer to the [InitializerViewModelFactoryBuilder]
+ */
+public inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
+    noinline initializer: CreationExtras.() -> VM
+) {
+    addInitializer(VM::class, initializer)
+}
+
+/**
+ * Holds a [ViewModel] class and initializer for that class
+ */
+public class ViewModelInitializer<T : ViewModel>(
+    internal val clazz: Class<T>,
+    internal val initializer: CreationExtras.() -> T,
+)
+
+/**
+ * A [ViewModelProvider.Factory] that allows you to add lambda initializers for handling
+ * particular ViewModel classes using [CreationExtras], while using the default behavior for any
+ * other classes.
+ *
+ * ```
+ * val factory = viewModelFactory {
+ *   initializer { TestViewModel(this[key]) }
+ * }
+ * val viewModel: TestViewModel = factory.create(TestViewModel::class.java, extras)
+ * ```
+ */
+internal class InitializerViewModelFactory(
+    private vararg val initializers: ViewModelInitializer<*>
+) : ViewModelProvider.Factory {
+
+    /**
+     * Creates a new instance of the given `Class`.
+     *
+     * This will use the initializer if one has been set for the class, otherwise it throw an
+     * [IllegalArgumentException].
+     *
+     * @param modelClass a `Class` whose instance is requested
+     * @param extras an additional information for this creation request
+     * @return a newly created ViewModel
+     *
+     * @throws IllegalArgumentException if no initializer has been set for the given class.
+     */
+    override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
+        var viewModel: T? = null
+        @Suppress("UNCHECKED_CAST")
+        initializers.forEach {
+            if (it.clazz == modelClass) {
+                viewModel = it.initializer.invoke(extras) as? T
+            }
+        }
+        return viewModel ?: throw IllegalArgumentException(
+            "No initializer set for given class ${modelClass.name}"
+        )
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/res/values/ids.xml b/lifecycle/lifecycle-viewmodel/src/androidMain/res/values/ids.xml
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/main/res/values/ids.xml
rename to lifecycle/lifecycle-viewmodel/src/androidMain/res/values/ids.xml
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderReifiedTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderReifiedTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelStoreTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelStoreTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt
deleted file mode 100644
index 60bb467..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle
-
-import android.app.Application
-
-/**
- * Application context aware [ViewModel].
- *
- * Subclasses must have a constructor which accepts [Application] as the only parameter.
- */
-open class AndroidViewModel(private val application: Application) : ViewModel() {
-
-    /**
-     * Return the application.
-     */
-    @Suppress("UNCHECKED_CAST")
-    open fun <T : Application> getApplication(): T {
-        return application as T
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt
deleted file mode 100644
index 18039b69..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt
+++ /dev/null
@@ -1,39 +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.lifecycle
-
-import androidx.lifecycle.viewmodel.CreationExtras
-
-/**
- * Interface that marks a [ViewModelStoreOwner] as having a default
- * [ViewModelProvider.Factory] for use with [ViewModelProvider].
- */
-interface HasDefaultViewModelProviderFactory {
-    /**
-     * Returns the default [ViewModelProvider.Factory] that should be
-     * used when no custom `Factory` is provided to the
-     * [ViewModelProvider] constructors.
-     */
-    val defaultViewModelProviderFactory: ViewModelProvider.Factory
-
-    /**
-     * Returns the default [CreationExtras] that should be passed into
-     * [ViewModelProvider.Factory.create] when no overriding
-     * [CreationExtras] were passed to the [ViewModelProvider] constructors.
-     */
-    val defaultViewModelCreationExtras: CreationExtras
-        get() = CreationExtras.Empty
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.kt
deleted file mode 100644
index 927ccbb..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.kt
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle
-
-import androidx.annotation.MainThread
-import java.io.Closeable
-import java.io.IOException
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-
-/**
- * ViewModel is a class that is responsible for preparing and managing the data for
- * an [Activity][android.app.Activity] or a [Fragment][androidx.fragment.app.Fragment].
- * It also handles the communication of the Activity / Fragment with the rest of the application
- * (e.g. calling the business logic classes).
- *
- * A ViewModel is always created in association with a scope (a fragment or an activity) and will
- * be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.
- *
- * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
- * configuration change (e.g. rotation). The new owner instance just re-connects to the existing
- * model.
- *
- * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
- * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
- * ViewModel. ViewModels usually expose this information via [Lifecycle][androidx.lifecycle.LiveData] or
- * Android Data Binding. You can also use any observability construct from your favorite framework.
- *
- * ViewModel's only responsibility is to manage the data for the UI. It **should never** access
- * your view hierarchy or hold a reference back to the Activity or the Fragment.
- *
- * Typical usage from an Activity standpoint would be:
- *
- * ```
- * class UserActivity : ComponentActivity {
- *     private val viewModel by viewModels<UserViewModel>()
- *
- *     override fun onCreate(savedInstanceState: Bundle) {
- *         super.onCreate(savedInstanceState)
- *         setContentView(R.layout.user_activity_layout)
- *         viewModel.user.observe(this) { user: User ->
- *             // update ui.
- *         }
- *         requireViewById(R.id.button).setOnClickListener {
- *             viewModel.doAction()
- *         }
- *     }
- * }
- * ```
- *
- * ViewModel would be:
- *
- * ```
- * class UserViewModel : ViewModel {
- *     private val userLiveData = MutableLiveData<User>()
- *     val user: LiveData<User> get() = userLiveData
- *
- *     init {
- *         // trigger user load.
- *     }
- *
- *     fun doAction() {
- *         // depending on the action, do necessary business logic calls and update the
- *         // userLiveData.
- *     }
- * }
- * ```
- *
- * ViewModels can also be used as a communication layer between different Fragments of an Activity.
- * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
- * communication between Fragments in a de-coupled fashion such that they never need to talk to
- * the other Fragment directly.
- *
- * ```
- * class MyFragment : Fragment {
- *   val viewModel by activityViewModels<UserViewModel>()
- * }
- *```
- */
-abstract class ViewModel {
-
-    // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
-    private val bagOfTags = mutableMapOf<String, Any>()
-    private val closeables = mutableSetOf<Closeable>()
-
-    @Volatile
-    private var isCleared = false
-
-    /**
-     * Construct a new ViewModel instance.
-     *
-     * You should **never** manually construct a ViewModel outside of a
-     * [ViewModelProvider.Factory].
-     */
-    constructor()
-
-    /**
-     * Construct a new ViewModel instance. Any [Closeable] objects provided here
-     * will be closed directly before [ViewModel.onCleared] is called.
-     *
-     * You should **never** manually construct a ViewModel outside of a
-     * [ViewModelProvider.Factory].
-     */
-    constructor(vararg closeables: Closeable) {
-        this.closeables += closeables
-    }
-
-    /**
-     * This method will be called when this ViewModel is no longer used and will be destroyed.
-     *
-     * It is useful when ViewModel observes some data and you need to clear this subscription to
-     * prevent a leak of this ViewModel.
-     */
-    protected open fun onCleared() {}
-
-    @MainThread
-    internal fun clear() {
-        isCleared = true
-        // Since `clear()` is final, this method is still called on mock objects
-        // and in those cases, `bagOfTags` is `null`. It'll always be empty though
-        // because `setTagIfAbsent` and `getTag` are not final so we can skip
-        // clearing it
-        @Suppress("SENSELESS_COMPARISON")
-        if (bagOfTags != null) {
-            synchronized(bagOfTags) {
-                for (value in bagOfTags.values) {
-                    // see comment for the similar call in `setTagIfAbsent`
-                    closeWithRuntimeException(value)
-                }
-            }
-        }
-        // We need the same null check here
-        @Suppress("SENSELESS_COMPARISON")
-        if (closeables != null) {
-            synchronized(closeables) {
-                for (closeable in closeables) {
-                    closeWithRuntimeException(closeable)
-                }
-            }
-            closeables.clear()
-        }
-        onCleared()
-    }
-
-    /**
-     * Add a new [Closeable] object that will be closed directly before
-     * [ViewModel.onCleared] is called.
-     *
-     * If `onCleared()` has already been called, the closeable will not be added,
-     * and will instead be closed immediately.
-     *
-     * @param key A key that allows you to retrieve the closeable passed in by using the same
-     *            key with [ViewModel.getCloseable]
-     * @param closeable The object that should be [Closeable.close] directly before
-     *                  [ViewModel.onCleared] is called.
-     */
-    fun addCloseable(key: String, closeable: Closeable) {
-        // Although no logic should be done after user calls onCleared(), we will
-        // ensure that if it has already been called, the closeable attempting to
-        // be added will be closed immediately to ensure there will be no leaks.
-        if (isCleared) {
-            closeWithRuntimeException(closeable)
-            return
-        }
-
-        // As this method is final, it will still be called on mock objects even
-        // though `closeables` won't actually be created...we'll just not do anything
-        // in that case.
-        @Suppress("SENSELESS_COMPARISON")
-        if (bagOfTags != null) {
-            synchronized(bagOfTags) { bagOfTags.put(key, closeable) }
-        }
-    }
-
-    /**
-     * Add a new [Closeable] object that will be closed directly before
-     * [ViewModel.onCleared] is called.
-     *
-     * If `onCleared()` has already been called, the closeable will not be added,
-     * and will instead be closed immediately.
-     *
-     * @param closeable The object that should be [closed][Closeable.close] directly before
-     *                  [ViewModel.onCleared] is called.
-     */
-    open fun addCloseable(closeable: Closeable) {
-        // Although no logic should be done after user calls onCleared(), we will
-        // ensure that if it has already been called, the closeable attempting to
-        // be added will be closed immediately to ensure there will be no leaks.
-        if (isCleared) {
-            closeWithRuntimeException(closeable)
-            return
-        }
-
-        // As this method is final, it will still be called on mock objects even
-        // though `closeables` won't actually be created...we'll just not do anything
-        // in that case.
-        @Suppress("SENSELESS_COMPARISON")
-        if (this.closeables != null) {
-            synchronized(this.closeables) {
-                this.closeables.add(closeable)
-            }
-        }
-    }
-
-    /**
-     * Returns the closeable previously added with [ViewModel.addCloseable] with the given key.
-     *
-     * @param key The key that was used to add the Closeable.
-     */
-    fun <T : Closeable> getCloseable(key: String): T? {
-        @Suppress("SENSELESS_COMPARISON")
-        if (bagOfTags == null) {
-            return null
-        }
-        synchronized(bagOfTags) {
-            @Suppress("UNCHECKED_CAST")
-            return bagOfTags[key] as T?
-        }
-    }
-
-    private fun closeWithRuntimeException(instance: Any) {
-        if (instance is Closeable) {
-            try {
-                instance.close()
-            } catch (e: IOException) {
-                throw RuntimeException(e)
-            }
-        }
-    }
-}
-
-private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
-
-/**
- * [CoroutineScope] tied to this [ViewModel].
- * This scope will be canceled when ViewModel will be cleared, i.e. [ViewModel.onCleared] is called
- *
- * This scope is bound to
- * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
- */
-public val ViewModel.viewModelScope: CoroutineScope
-    get() {
-        return getCloseable<CloseableCoroutineScope>(JOB_KEY) ?: CloseableCoroutineScope(
-            SupervisorJob() + Dispatchers.Main.immediate
-        ).also { newClosableScope ->
-            addCloseable(JOB_KEY, newClosableScope)
-        }
-    }
-
-private class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
-    override val coroutineContext: CoroutineContext = context
-
-    override fun close() {
-        coroutineContext.cancel()
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
deleted file mode 100644
index 53ffa1b..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-@file:JvmName("ViewModelProviderGetKt")
-
-package androidx.lifecycle
-
-import android.app.Application
-import androidx.annotation.MainThread
-import androidx.annotation.RestrictTo
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.DEFAULT_KEY
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.defaultFactory
-import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.VIEW_MODEL_KEY
-import androidx.lifecycle.viewmodel.CreationExtras
-import androidx.lifecycle.viewmodel.CreationExtras.Key
-import androidx.lifecycle.viewmodel.InitializerViewModelFactory
-import androidx.lifecycle.viewmodel.MutableCreationExtras
-import androidx.lifecycle.viewmodel.ViewModelInitializer
-import java.lang.IllegalArgumentException
-import java.lang.RuntimeException
-import java.lang.reflect.InvocationTargetException
-import kotlin.UnsupportedOperationException
-
-/**
- * A utility class that provides `ViewModels` for a scope.
- *
- * Default `ViewModelProvider` for an `Activity` or a `Fragment` can be obtained
- * by passing it to the constructor: `ViewModelProvider(myFragment)`
- */
-public open class ViewModelProvider
-/**
- * Creates a ViewModelProvider
- *
- * @param store `ViewModelStore` where ViewModels will be stored.
- * @param factory factory a `Factory` which will be used to instantiate new `ViewModels`
- * @param defaultCreationExtras extras to pass to a factory
- */
-@JvmOverloads
-constructor(
-    private val store: ViewModelStore,
-    private val factory: Factory,
-    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
-) {
-    /**
-     * Implementations of `Factory` interface are responsible to instantiate ViewModels.
-     */
-    public interface Factory {
-        /**
-         * Creates a new instance of the given `Class`.
-         *
-         * Default implementation throws [UnsupportedOperationException].
-         *
-         * @param modelClass a `Class` whose instance is requested
-         * @return a newly created ViewModel
-         */
-        public fun <T : ViewModel> create(modelClass: Class<T>): T {
-            throw UnsupportedOperationException(
-                "Factory.create(String) is unsupported.  This Factory requires " +
-                    "`CreationExtras` to be passed into `create` method."
-            )
-        }
-
-        /**
-         * Creates a new instance of the given `Class`.
-         *
-         * @param modelClass a `Class` whose instance is requested
-         * @param extras an additional information for this creation request
-         * @return a newly created ViewModel
-         */
-        public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
-            create(modelClass)
-
-        companion object {
-            /**
-             * Creates an [InitializerViewModelFactory] using the given initializers.
-             *
-             * @param initializers the class initializer pairs used for the factory to create
-             * simple view models
-             */
-            @JvmStatic
-            fun from(vararg initializers: ViewModelInitializer<*>): Factory =
-                InitializerViewModelFactory(*initializers)
-        }
-    }
-
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public open class OnRequeryFactory {
-        public open fun onRequery(viewModel: ViewModel) {}
-    }
-
-    /**
-     * Creates `ViewModelProvider`. This will create `ViewModels`
-     * and retain them in a store of the given `ViewModelStoreOwner`.
-     *
-     *
-     * This method will use the
-     * [default factory][HasDefaultViewModelProviderFactory.defaultViewModelProviderFactory]
-     * if the owner implements [HasDefaultViewModelProviderFactory]. Otherwise, a
-     * [NewInstanceFactory] will be used.
-     */
-    public constructor(
-        owner: ViewModelStoreOwner
-    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
-
-    /**
-     * Creates `ViewModelProvider`, which will create `ViewModels` via the given
-     * `Factory` and retain them in a store of the given `ViewModelStoreOwner`.
-     *
-     * @param owner   a `ViewModelStoreOwner` whose [ViewModelStore] will be used to
-     * retain `ViewModels`
-     * @param factory a `Factory` which will be used to instantiate
-     * new `ViewModels`
-     */
-    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
-        owner.viewModelStore,
-        factory,
-        defaultCreationExtras(owner)
-    )
-
-    /**
-     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
-     * an activity), associated with this `ViewModelProvider`.
-     *
-     *
-     * The created ViewModel is associated with the given scope and will be retained
-     * as long as the scope is alive (e.g. if it is an activity, until it is
-     * finished or process is killed).
-     *
-     * @param modelClass The class of the ViewModel to create an instance of it if it is not
-     * present.
-     * @return A ViewModel that is an instance of the given type `T`.
-     * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
-     */
-    @MainThread
-    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
-        val canonicalName = modelClass.canonicalName
-            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
-        return get("$DEFAULT_KEY:$canonicalName", modelClass)
-    }
-
-    /**
-     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
-     * an activity), associated with this `ViewModelProvider`.
-     *
-     * The created ViewModel is associated with the given scope and will be retained
-     * as long as the scope is alive (e.g. if it is an activity, until it is
-     * finished or process is killed).
-     *
-     * @param key        The key to use to identify the ViewModel.
-     * @param modelClass The class of the ViewModel to create an instance of it if it is not
-     * present.
-     * @return A ViewModel that is an instance of the given type `T`.
-     */
-    @Suppress("UNCHECKED_CAST")
-    @MainThread
-    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
-        val viewModel = store[key]
-        if (modelClass.isInstance(viewModel)) {
-            (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
-            return viewModel as T
-        } else {
-            @Suppress("ControlFlowWithEmptyBody")
-            if (viewModel != null) {
-                // TODO: log a warning.
-            }
-        }
-        val extras = MutableCreationExtras(defaultCreationExtras)
-        extras[VIEW_MODEL_KEY] = key
-        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
-        // fall back to the other create method to keep from crashing.
-        return try {
-            factory.create(modelClass, extras)
-        } catch (e: AbstractMethodError) {
-            factory.create(modelClass)
-        }.also { store.put(key, it) }
-    }
-
-    /**
-     * Simple factory, which calls empty constructor on the give class.
-     */
-    // actually there is getInstance()
-    @Suppress("SingletonConstructor")
-    public open class NewInstanceFactory : Factory {
-        @Suppress("DocumentExceptions")
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            return try {
-                modelClass.getDeclaredConstructor().newInstance()
-            } catch (e: NoSuchMethodException) {
-                throw RuntimeException("Cannot create an instance of $modelClass", e)
-            } catch (e: InstantiationException) {
-                throw RuntimeException("Cannot create an instance of $modelClass", e)
-            } catch (e: IllegalAccessException) {
-                throw RuntimeException("Cannot create an instance of $modelClass", e)
-            }
-        }
-
-        public companion object {
-            private var sInstance: NewInstanceFactory? = null
-
-            /**
-             * Retrieve a singleton instance of NewInstanceFactory.
-             *
-             * @return A valid [NewInstanceFactory]
-             */
-            @JvmStatic
-            public val instance: NewInstanceFactory
-                @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-                get() {
-                    if (sInstance == null) {
-                        sInstance = NewInstanceFactory()
-                    }
-                    return sInstance!!
-                }
-
-            private object ViewModelKeyImpl : Key<String>
-            /**
-             * A [CreationExtras.Key] to get a key associated with a requested
-             * `ViewModel` from [CreationExtras]
-             *
-             *  `ViewModelProvider` automatically puts a key that was passed to
-             *  `ViewModelProvider.get(key, MyViewModel::class.java)`
-             *  or generated in `ViewModelProvider.get(MyViewModel::class.java)` to the `CreationExtras` that
-             *  are passed to [ViewModelProvider.Factory].
-             */
-            @JvmField
-            val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl
-        }
-    }
-
-    /**
-     * [Factory] which may create [AndroidViewModel] and
-     * [ViewModel], which have an empty constructor.
-     *
-     * @param application an application to pass in [AndroidViewModel]
-     */
-    public open class AndroidViewModelFactory
-    private constructor(
-        private val application: Application?,
-        // parameter to avoid clash between constructors with nullable and non-nullable
-        // Application
-        @Suppress("UNUSED_PARAMETER") unused: Int,
-    ) : NewInstanceFactory() {
-
-        /**
-         * Constructs this factory.
-         * When a factory is constructed this way, a component for which [ViewModel] is created
-         * must provide an [Application] by [APPLICATION_KEY] in [CreationExtras], otherwise
-         *  [IllegalArgumentException] will be thrown from [create] method.
-         */
-        @Suppress("SingletonConstructor")
-        public constructor() : this(null, 0)
-
-        /**
-         * Constructs this factory.
-         *
-         * @param application an application to pass in [AndroidViewModel]
-         */
-        @Suppress("SingletonConstructor")
-        public constructor(application: Application) : this(application, 0)
-
-        @Suppress("DocumentExceptions")
-        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
-            return if (application != null) {
-                create(modelClass)
-            } else {
-                val application = extras[APPLICATION_KEY]
-                if (application != null) {
-                    create(modelClass, application)
-                } else {
-                    // For AndroidViewModels, CreationExtras must have an application set
-                    if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
-                        throw IllegalArgumentException(
-                            "CreationExtras must have an application by `APPLICATION_KEY`"
-                        )
-                    }
-                    super.create(modelClass)
-                }
-            }
-        }
-
-        @Suppress("DocumentExceptions")
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            return if (application == null) {
-                throw UnsupportedOperationException(
-                    "AndroidViewModelFactory constructed " +
-                        "with empty constructor works only with " +
-                        "create(modelClass: Class<T>, extras: CreationExtras)."
-                )
-            } else {
-                create(modelClass, application)
-            }
-        }
-
-        @Suppress("DocumentExceptions")
-        private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
-            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
-                try {
-                    modelClass.getConstructor(Application::class.java).newInstance(app)
-                } catch (e: NoSuchMethodException) {
-                    throw RuntimeException("Cannot create an instance of $modelClass", e)
-                } catch (e: IllegalAccessException) {
-                    throw RuntimeException("Cannot create an instance of $modelClass", e)
-                } catch (e: InstantiationException) {
-                    throw RuntimeException("Cannot create an instance of $modelClass", e)
-                } catch (e: InvocationTargetException) {
-                    throw RuntimeException("Cannot create an instance of $modelClass", e)
-                }
-            } else super.create(modelClass)
-        }
-
-        public companion object {
-            internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
-                if (owner is HasDefaultViewModelProviderFactory)
-                    owner.defaultViewModelProviderFactory else instance
-
-            internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
-
-            private var sInstance: AndroidViewModelFactory? = null
-
-            /**
-             * Retrieve a singleton instance of AndroidViewModelFactory.
-             *
-             * @param application an application to pass in [AndroidViewModel]
-             * @return A valid [AndroidViewModelFactory]
-             */
-            @JvmStatic
-            public fun getInstance(application: Application): AndroidViewModelFactory {
-                if (sInstance == null) {
-                    sInstance = AndroidViewModelFactory(application)
-                }
-                return sInstance!!
-            }
-
-            private object ApplicationKeyImpl : Key<Application>
-
-            /**
-             * A [CreationExtras.Key] to query an application in which ViewModel is being created.
-             */
-            @JvmField
-            val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
-        }
-    }
-}
-
-internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras {
-    return if (owner is HasDefaultViewModelProviderFactory) {
-        owner.defaultViewModelCreationExtras
-    } else CreationExtras.Empty
-}
-
-/**
- * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
- * an activity), associated with this `ViewModelProvider`.
- *
- * @see ViewModelProvider.get(Class)
- */
-@MainThread
-public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class.java)
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.kt
deleted file mode 100644
index 9b47687..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle
-
-import androidx.annotation.RestrictTo
-
-/**
- * Class to store `ViewModel`s.
- *
- * An instance of `ViewModelStore` must be retained through configuration changes:
- * if an owner of this `ViewModelStore` is destroyed and recreated due to configuration
- * changes, new instance of an owner should still have the same old instance of
- * `ViewModelStore`.
- *
- * If an owner of this `ViewModelStore` is destroyed and is not going to be recreated,
- * then it should call [clear] on this `ViewModelStore`, so `ViewModel`s would
- * be notified that they are no longer used.
- *
- * Use [ViewModelStoreOwner.getViewModelStore] to retrieve a `ViewModelStore` for
- * activities and fragments.
- */
-open class ViewModelStore {
-
-    private val map = mutableMapOf<String, ViewModel>()
-
-    /**
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun put(key: String, viewModel: ViewModel) {
-        val oldViewModel = map.put(key, viewModel)
-        oldViewModel?.clear()
-    }
-
-    /**
-     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
-     */
-    /**
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    operator fun get(key: String): ViewModel? {
-        return map[key]
-    }
-
-    /**
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun keys(): Set<String> {
-        return HashSet(map.keys)
-    }
-
-    /**
-     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
-     */
-    fun clear() {
-        for (vm in map.values) {
-            vm.clear()
-        }
-        map.clear()
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.kt
deleted file mode 100644
index dac1275..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle
-
-/**
- * A scope that owns [ViewModelStore].
- *
- * A responsibility of an implementation of this interface is to retain owned ViewModelStore
- * during the configuration changes and call [ViewModelStore.clear], when this scope is
- * going to be destroyed.
- *
- * @see ViewTreeViewModelStoreOwner
- */
-interface ViewModelStoreOwner {
-
-    /**
-     * The owned [ViewModelStore]
-     */
-    val viewModelStore: ViewModelStore
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt
deleted file mode 100644
index 1cdd93f..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-@file:JvmName("ViewTreeViewModelStoreOwner")
-
-package androidx.lifecycle
-
-import android.view.View
-import androidx.lifecycle.viewmodel.R
-
-/**
- * Set the [ViewModelStoreOwner] associated with the given [View].
- * Calls to [get] from this view or descendants will return
- * `viewModelStoreOwner`.
- *
- * This should only be called by constructs such as activities or fragments that manage
- * a view tree and retain state through a [ViewModelStoreOwner]. Callers
- * should only set a [ViewModelStoreOwner] that will be *stable.* The associated
- * [ViewModelStore] should be cleared if the view tree is removed and is not
- * guaranteed to later become reattached to a window.
- *
- * @param viewModelStoreOwner ViewModelStoreOwner associated with the given view
- */
-@JvmName("set")
-fun View.setViewTreeViewModelStoreOwner(viewModelStoreOwner: ViewModelStoreOwner?) {
-    setTag(R.id.view_tree_view_model_store_owner, viewModelStoreOwner)
-}
-
-/**
- * Retrieve the [ViewModelStoreOwner] associated with the given [View].
- * This may be used to retain state associated with this view across configuration changes.
- *
- * @return The [ViewModelStoreOwner] associated with this view and/or some subset
- * of its ancestors
- */
-@JvmName("get")
-fun View.findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? {
-    return generateSequence(this) { view ->
-        view.parent as? View
-    }.mapNotNull { view ->
-        view.getTag(R.id.view_tree_view_model_store_owner) as? ViewModelStoreOwner
-    }.firstOrNull()
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt
deleted file mode 100644
index 579e308..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2021 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.lifecycle.viewmodel
-
-/**
- * Simple map-like object that passed in [ViewModelProvider.Factory.create]
- * to provide an additional information to a factory.
- *
- * It allows making `Factory` implementations stateless, which makes an injection of factories
- * easier because  don't require all information be available at construction time.
- */
-public abstract class CreationExtras internal constructor() {
-    internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()
-
-    /**
-     * Key for the elements of [CreationExtras]. [T] is a type of an element with this key.
-     */
-    public interface Key<T>
-
-    /**
-     * Returns an element associated with the given [key]
-     */
-    public abstract operator fun <T> get(key: Key<T>): T?
-
-    /**
-     * Empty [CreationExtras]
-     */
-    object Empty : CreationExtras() {
-        override fun <T> get(key: Key<T>): T? = null
-    }
-}
-
-/**
- * Mutable implementation of [CreationExtras]
- *
- * @param initialExtras extras that will be filled into the resulting MutableCreationExtras
- */
-public class MutableCreationExtras(initialExtras: CreationExtras = Empty) : CreationExtras() {
-
-    init {
-        map.putAll(initialExtras.map)
-    }
-    /**
-     * Associates the given [key] with [t]
-     */
-    public operator fun <T> set(key: Key<T>, t: T) {
-        map[key] = t
-    }
-
-    public override fun <T> get(key: Key<T>): T? {
-        @Suppress("UNCHECKED_CAST")
-        return map[key] as T?
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
deleted file mode 100644
index 2c005d8..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2022 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.lifecycle.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import kotlin.reflect.KClass
-
-@DslMarker
-public annotation class ViewModelFactoryDsl
-
-/**
- * Creates an [InitializerViewModelFactory] with the initializers provided in the builder.
- */
-public inline fun viewModelFactory(
-    builder: InitializerViewModelFactoryBuilder.() -> Unit
-): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()
-
-/**
- * DSL for constructing a new [ViewModelProvider.Factory]
- */
-@ViewModelFactoryDsl
-public class InitializerViewModelFactoryBuilder {
-    private val initializers = mutableListOf<ViewModelInitializer<*>>()
-
-    /**
-     * Add the initializer for the given ViewModel class.
-     *
-     * @param clazz the class the initializer is associated with.
-     * @param initializer lambda used to create an instance of the ViewModel class
-     */
-    fun <T : ViewModel> addInitializer(clazz: KClass<T>, initializer: CreationExtras.() -> T) {
-        initializers.add(ViewModelInitializer(clazz.java, initializer))
-    }
-
-    /**
-     * Build the InitializerViewModelFactory.
-     */
-    fun build(): ViewModelProvider.Factory =
-        InitializerViewModelFactory(*initializers.toTypedArray())
-}
-
-/**
- * Add an initializer to the [InitializerViewModelFactoryBuilder]
- */
-inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
-    noinline initializer: CreationExtras.() -> VM
-) {
-    addInitializer(VM::class, initializer)
-}
-
-/**
- * Holds a [ViewModel] class and initializer for that class
- */
-class ViewModelInitializer<T : ViewModel>(
-    internal val clazz: Class<T>,
-    internal val initializer: CreationExtras.() -> T,
-)
-
-/**
- * A [ViewModelProvider.Factory] that allows you to add lambda initializers for handling
- * particular ViewModel classes using [CreationExtras], while using the default behavior for any
- * other classes.
- *
- * ```
- * val factory = viewModelFactory {
- *   initializer { TestViewModel(this[key]) }
- * }
- * val viewModel: TestViewModel = factory.create(TestViewModel::class.java, extras)
- * ```
- */
-internal class InitializerViewModelFactory(
-    private vararg val initializers: ViewModelInitializer<*>
-) : ViewModelProvider.Factory {
-
-    /**
-     * Creates a new instance of the given `Class`.
-     *
-     * This will use the initializer if one has been set for the class, otherwise it throw an
-     * [IllegalArgumentException].
-     *
-     * @param modelClass a `Class` whose instance is requested
-     * @param extras an additional information for this creation request
-     * @return a newly created ViewModel
-     *
-     * @throws IllegalArgumentException if no initializer has been set for the given class.
-     */
-    override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
-        var viewModel: T? = null
-        @Suppress("UNCHECKED_CAST")
-        initializers.forEach {
-            if (it.clazz == modelClass) {
-                viewModel = it.initializer.invoke(extras) as? T
-            }
-        }
-        return viewModel ?: throw IllegalArgumentException(
-            "No initializer set for given class ${modelClass.name}"
-        )
-    }
-}
diff --git a/lint-checks/src/main/java/androidx/build/lint/ObsoleteCompatDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ObsoleteCompatDetector.kt
index 68ad1afd..ef7fbcf 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ObsoleteCompatDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ObsoleteCompatDetector.kt
@@ -96,7 +96,7 @@
             ?.unwrapReceiver()
         if (firstParameter != receiver) return
 
-        val lintFix = LintFix.create().composite()
+        val lintFix = LintFix.create().composite().name("Replace obsolete compat method")
 
         if (!hasDeprecatedDoc) {
             val docLink = when (expression.selector) {
diff --git a/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
index 3322734..9987d06 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
@@ -167,12 +167,13 @@
         expression: String,
         imports: List<String>
     ): LintFix {
-        val lintFixBuilder = fix().composite()
+        val name = "Replace with `$expression`"
+        val lintFixBuilder = fix().composite().name(name)
         lintFixBuilder.add(
             fix()
                 .replace()
                 .range(location)
-                .name("Replace with `$expression`")
+                .name(name)
                 .with(expression)
                 .build()
         )
diff --git a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
index 03d83b5..2d6ad74 100644
--- a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
@@ -248,7 +248,7 @@
         if ((source as? KtModifierListOwner)?.hasActualModifier() == true) {
             analyze(source) {
                 val member = (source as? KtDeclaration)?.getSymbol() ?: return
-                val expect = member.getExpectForActual() ?: return
+                val expect = member.getExpectsForActual().singleOrNull() ?: return
                 (expect.psi as? KtDeclaration)?.docComment?.let {
                     handleSampleLink(it)
                 }
diff --git a/lint-checks/src/test/java/androidx/build/lint/CameraXQuirksClassDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/CameraXQuirksClassDetectorTest.kt
index 89ff847..77438db 100644
--- a/lint-checks/src/test/java/androidx/build/lint/CameraXQuirksClassDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/CameraXQuirksClassDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.build.lint
 
+import com.android.tools.lint.checks.infrastructure.TestMode.Companion.PARTIAL
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -42,7 +43,6 @@
             *     Bug Id:
             *     Description:
             *     Device(s):
-
              [CameraXQuirksClassDetector]
             public class CameraXMissingQuirkSummaryJava implements Quirk {
                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -50,6 +50,13 @@
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
-        check(*input).expect(expected)
+        lint()
+            .files(
+                *stubs,
+                *input
+            )
+            .allowDuplicates()
+            .skipTestModes(PARTIAL) // b/324629808
+            .run().expect(expected)
     }
 }
diff --git a/lint-checks/src/test/java/androidx/build/lint/IdeaSuppressionDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/IdeaSuppressionDetectorTest.kt
index 702c6088..1a2f2c0 100644
--- a/lint-checks/src/test/java/androidx/build/lint/IdeaSuppressionDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/IdeaSuppressionDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.build.lint
 
+import com.android.tools.lint.checks.infrastructure.TestMode
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -60,7 +61,15 @@
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
-        check(input).expect(expected)
+        lint()
+            .files(
+                *stubs,
+                input
+            )
+            .allowDuplicates()
+            .skipTestModes(TestMode.SUPPRESSIBLE)
+            .run()
+            .expect(expected)
     }
 
     @Test
@@ -94,7 +103,14 @@
 1 errors, 0 warnings
         """.trimIndent()
         /* ktlint-enable max-line-length */
-
-        check(input).expect(expected)
+        lint()
+            .files(
+                *stubs,
+                input
+            )
+            .allowDuplicates()
+            .skipTestModes(TestMode.SUPPRESSIBLE)
+            .run()
+            .expect(expected)
     }
 }
diff --git a/lint-checks/src/test/java/androidx/build/lint/ObsoleteCompatDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/ObsoleteCompatDetectorTest.kt
index 71d7768..2143e46b 100644
--- a/lint-checks/src/test/java/androidx/build/lint/ObsoleteCompatDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/ObsoleteCompatDetectorTest.kt
@@ -60,15 +60,20 @@
         """.trimIndent()
 
         val expectedAutoFix = """
-Fix for src/androidx/ObsoleteCompatMethod.java line 33: Add @deprecated Javadoc annotation:
-@@ -32 +32
+Fix for src/androidx/ObsoleteCompatMethod.java line 33: Replace obsolete compat method:
+@@ -20 +20
++ import androidx.annotation.ReplaceWith;
+@@ -32 +33
 +      * @deprecated Call {@link Object#hashCode()} directly.
+@@ -33 +35
 +     @Deprecated
-+     @androidx.annotation.ReplaceWith(expression = "obj.hashCode()")
-Fix for src/androidx/ObsoleteCompatMethod.java line 38: Add @deprecated Javadoc annotation:
-@@ -38 +38
++     @ReplaceWith(expression = "obj.hashCode()")
+Fix for src/androidx/ObsoleteCompatMethod.java line 38: Replace obsolete compat method:
+@@ -20 +20
++ import androidx.annotation.ReplaceWith;
+@@ -38 +39
 +     @Deprecated
-+     @androidx.annotation.ReplaceWith(expression = "obj.hashCode()")
++     @ReplaceWith(expression = "obj.hashCode()")
 +     /** @deprecated Call {@link Object#hashCode()} directly. */
         """.trimIndent()
         /* ktlint-enable max-line-length */
@@ -91,9 +96,11 @@
         """.trimIndent()
 
         val expectedAutoFix = """
-Fix for src/androidx/ObsoleteCompatMethodMissingReplaceWith.java line 32: Annotate with @ReplaceWith:
-@@ -31 +31
-+     @androidx.annotation.ReplaceWith(expression = "obj.hashCode()")
+Autofix for src/androidx/ObsoleteCompatMethodMissingReplaceWith.java line 32: Replace obsolete compat method:
+@@ -18 +18
++ import androidx.annotation.ReplaceWith;
+@@ -31 +32
++     @ReplaceWith(expression = "obj.hashCode()")
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
@@ -115,7 +122,7 @@
         """.trimIndent()
 
         val expectedAutoFix = """
-Fix for src/androidx/ObsoleteCompatMethodMissingDeprecated.java line 37: Annotate with @Deprecated:
+Autofix for src/androidx/ObsoleteCompatMethodMissingDeprecated.java line 37: Replace obsolete compat method:
 @@ -36 +36
 +     @Deprecated
         """.trimIndent()
@@ -142,10 +149,10 @@
         """.trimIndent()
 
         val expectedAutoFix = """
-Fix for src/androidx/ObsoleteCompatMethodMissingJavadoc.java line 37: Add @deprecated Javadoc annotation:
+Autofix for src/androidx/ObsoleteCompatMethodMissingJavadoc.java line 37: Replace obsolete compat method:
 @@ -34 +34
 +      * @deprecated Call {@link Object#hashCode()} directly.
-Fix for src/androidx/ObsoleteCompatMethodMissingJavadoc.java line 44: Add @deprecated Javadoc annotation:
+Autofix for src/androidx/ObsoleteCompatMethodMissingJavadoc.java line 44: Replace obsolete compat method:
 @@ -42 +42
 +     /** @deprecated Call {@link Object#hashCode()} directly. */
         """.trimIndent()
@@ -172,11 +179,12 @@
         """.trimIndent()
 
         val expectedAutoFix = """
-Fix for src/androidx/ObsoleteCompatMethodMissingDeprecatedAndJavadoc.java line 36: Add @deprecated Javadoc annotation:
+Fix for src/androidx/ObsoleteCompatMethodMissingDeprecatedAndJavadoc.java line 36: Replace obsolete compat method:
 @@ -34 +34
 +      * @deprecated Call {@link Object#hashCode()} directly.
+@@ -35 +36
 +     @Deprecated
-Fix for src/androidx/ObsoleteCompatMethodMissingDeprecatedAndJavadoc.java line 42: Add @deprecated Javadoc annotation:
+Fix for src/androidx/ObsoleteCompatMethodMissingDeprecatedAndJavadoc.java line 42: Replace obsolete compat method:
 @@ -41 +41
 +     @Deprecated
 +     /** @deprecated Call {@link Object#hashCode()} directly. */
diff --git a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt
index 6788668..b705402 100644
--- a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt
@@ -196,18 +196,18 @@
 
         val expectedFixDiffs = """
 Fix for src/MethodWithNoImportsOrPackage.java line 35: Replace with `newMethod(null)`:
-@@ -1 +1
-+ import androidx.annotation.Deprecated;
-@@ -35 +36
+@@ -35 +35
 -         oldMethodSingleImport(null);
 +         newMethod(null);
-Fix for src/MethodWithNoImportsOrPackage.java line 39: Replace with `newMethod(null)`:
-@@ -1 +1
+@@ -42 +42
 + import androidx.annotation.Deprecated;
-+ import androidx.annotation.NonNull;
-@@ -39 +41
+Fix for src/MethodWithNoImportsOrPackage.java line 39: Replace with `newMethod(null)`:
+@@ -39 +39
 -         oldMethodMultiImport(null);
 +         newMethod(null);
+@@ -42 +42
++ import androidx.annotation.Deprecated;
++ import androidx.annotation.NonNull;
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
diff --git a/lint/lint-gradle/build.gradle b/lint/lint-gradle/build.gradle
index 808e484..0793c790 100644
--- a/lint/lint-gradle/build.gradle
+++ b/lint/lint-gradle/build.gradle
@@ -22,6 +22,7 @@
  * modifying its settings.
  */
 import androidx.build.LibraryType
+import androidx.build.Publish
 
 plugins {
     id("AndroidXPlugin")
@@ -42,7 +43,7 @@
 androidx {
     name = "Gradle lint checks"
     type = LibraryType.LINT
+    publish = Publish.SNAPSHOT_AND_RELEASE
     inceptionYear = "2024"
     description = "Lint checks to verify usage of Gradle APIs."
 }
-
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerConfigurationDetector.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerConfigurationDetector.kt
new file mode 100644
index 0000000..603d470
--- /dev/null
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerConfigurationDetector.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.lint.gradle
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+
+/**
+ * Checks for usages of [eager APIs](https://docs.gradle.org/current/userguide/task_configuration_avoidance.html).
+ */
+class EagerConfigurationDetector : Detector(), Detector.UastScanner {
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(
+        UCallExpression::class.java
+    )
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = object :
+        UElementHandler() {
+        override fun visitCallExpression(node: UCallExpression) {
+            val methodName = node.methodName
+            val (containingClassName, replacementMethod) = REPLACEMENTS[methodName] ?: return
+            val containingClass = (node.receiverType as? PsiClassType)?.resolve() ?: return
+            // Check that the called method is from the expected class (or a child class) and not an
+            // unrelated method with the same name).
+            if (!containingClass.isInstanceOf(containingClassName)) return
+
+            val fix = replacementMethod?.let {
+                fix()
+                    .replace()
+                    .with(it)
+                    .reformat(true)
+                    // Don't auto-fix from the command line because the replacement methods don't
+                    // have the same return types, so the fixed code likely won't compile.
+                    .autoFix(robot = false, independent = false)
+                    .build()
+            }
+            val message = replacementMethod?.let { "Use $it instead of $methodName" }
+                ?: "Avoid using eager method $methodName"
+
+            val incident = Incident(context)
+                .issue(ISSUE)
+                .location(context.getNameLocation(node))
+                .message(message)
+                .fix(fix)
+                .scope(node)
+            context.report(incident)
+        }
+    }
+
+    /** Checks if the class is [qualifiedName] or has [qualifiedName] as a super type. */
+    fun PsiClass.isInstanceOf(qualifiedName: String): Boolean =
+        // Recursion will stop when this hits Object, which has no [supers]
+        qualifiedName == this.qualifiedName || supers.any { it.isInstanceOf(qualifiedName) }
+
+    companion object {
+        private const val TASK_CONTAINER = "org.gradle.api.tasks.TaskContainer"
+        private const val TASK_PROVIDER = "org.gradle.api.tasks.TaskProvider"
+        private const val DOMAIN_OBJECT_COLLECTION = "org.gradle.api.DomainObjectCollection"
+        private const val TASK_COLLECTION = "org.gradle.api.tasks.TaskCollection"
+        private const val NAMED_DOMAIN_OBJECT_COLLECTION =
+            "org.gradle.api.NamedDomainObjectCollection"
+
+        // A map from eager method name to the containing class of the method and the name of the
+        // replacement method, if there is a direct equivalent.
+        private val REPLACEMENTS = mapOf(
+            "create" to Pair(TASK_CONTAINER, "register"),
+            "getByName" to Pair(TASK_CONTAINER, "named"),
+            "all" to Pair(DOMAIN_OBJECT_COLLECTION, "configureEach"),
+            "whenTaskAdded" to Pair(TASK_CONTAINER, "configureEach"),
+            "whenObjectAdded" to Pair(DOMAIN_OBJECT_COLLECTION, "configureEach"),
+            "getAt" to Pair(TASK_COLLECTION, "named"),
+            "getByPath" to Pair(TASK_CONTAINER, null),
+            "findByName" to Pair(TASK_CONTAINER, null),
+            "findByPath" to Pair(TASK_CONTAINER, null),
+            "replace" to Pair(TASK_CONTAINER, null),
+            "remove" to Pair(TASK_CONTAINER, null),
+            "iterator" to Pair(TASK_CONTAINER, null),
+            "findAll" to Pair(NAMED_DOMAIN_OBJECT_COLLECTION, null),
+            "matching" to Pair(TASK_COLLECTION, null),
+            "get" to Pair(TASK_PROVIDER, null),
+        )
+
+        val ISSUE = Issue.create(
+            "EagerGradleConfiguration",
+            "Avoid using eager task APIs",
+            """
+                Lazy APIs defer creating and configuring objects until they are needed instead of
+                doing unnecessary work in the configuration phase.
+                See https://docs.gradle.org/current/userguide/task_configuration_avoidance.html for
+                more details.
+            """,
+            Category.CORRECTNESS, 5, Severity.ERROR,
+            Implementation(
+                EagerConfigurationDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerTaskConfigurationDetector.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerTaskConfigurationDetector.kt
deleted file mode 100644
index b904f17..0000000
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/EagerTaskConfigurationDetector.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.lint.gradle
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Incident
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-
-/**
- * Checks for usages of [eager APIs](https://docs.gradle.org/current/userguide/task_configuration_avoidance.html).
- */
-class EagerTaskConfigurationDetector : Detector(), Detector.UastScanner {
-
-    override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(
-        UCallExpression::class.java
-    )
-
-    override fun createUastHandler(context: JavaContext): UElementHandler = object :
-        UElementHandler() {
-        override fun visitCallExpression(node: UCallExpression) {
-            val (containingClassName, replacementMethod) = REPLACEMENTS[node.methodName] ?: return
-            val method = node.resolve() ?: return
-            val containingClass = method.containingClass ?: return
-            // Check that the called method is from the expected class (or a child class) and not an
-            // unrelated method with the same name).
-            if (
-                containingClass.qualifiedName != containingClassName &&
-                    containingClass.supers.none { it.qualifiedName == containingClassName }
-            ) return
-
-            val fix = fix()
-                .replace()
-                .with(replacementMethod)
-                .reformat(true)
-                // Don't auto-fix from the command line because the replacement methods don't have
-                // the same return types, so the fixed code likely won't compile.
-                .autoFix(robot = false, independent = false)
-                .build()
-            val incident = Incident(context)
-                .issue(ISSUE)
-                .location(context.getNameLocation(node))
-                .message("Use $replacementMethod instead of ${method.name}")
-                .fix(fix)
-                .scope(node)
-            context.report(incident)
-        }
-    }
-
-    companion object {
-        private const val TASK_CONTAINER = "org.gradle.api.tasks.TaskContainer"
-        private const val DOMAIN_OBJECT_COLLECTION = "org.gradle.api.DomainObjectCollection"
-        private const val TASK_COLLECTION = "org.gradle.api.tasks.TaskCollection"
-
-        // A map from eager method name to the containing class of the method and the name of the
-        // replacement method.
-        private val REPLACEMENTS = mapOf(
-            "create" to Pair(TASK_CONTAINER, "register"),
-            "getByName" to Pair(TASK_CONTAINER, "named"),
-            "all" to Pair(DOMAIN_OBJECT_COLLECTION, "configureEach"),
-            "whenTaskAdded" to Pair(TASK_CONTAINER, "configureEach"),
-            "whenObjectAdded" to Pair(DOMAIN_OBJECT_COLLECTION, "configureEach"),
-            "getAt" to Pair(TASK_COLLECTION, "named"),
-        )
-
-        val ISSUE = Issue.create(
-            "EagerGradleTaskConfiguration",
-            "Avoid using eager task APIs",
-            """
-                Lazy APIs defer task configuration until the task is needed instead of doing
-                unnecessary work in the configuration phase.
-                See https://docs.gradle.org/current/userguide/task_configuration_avoidance.html for
-                more details.
-            """,
-            Category.CORRECTNESS, 5, Severity.ERROR,
-            Implementation(
-                EagerTaskConfigurationDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            )
-        )
-    }
-}
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
index ea34e6f..bdc7136 100644
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
@@ -27,13 +27,13 @@
     override val api = CURRENT_API
 
     override val issues = listOf(
-        EagerTaskConfigurationDetector.ISSUE,
+        EagerConfigurationDetector.ISSUE,
     )
 
     override val vendor = Vendor(
         // TODO: Update component (or the issue template)
         feedbackUrl = "https://issuetracker.google.com/issues/new?component=1147525",
-        identifier = "androidx.gradle-lint:gradle-lint-checks",
+        identifier = "androidx.lint:lint-gradle",
         vendorName = "Android Open Source Project",
     )
 }
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerConfigurationDetectorTest.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerConfigurationDetectorTest.kt
new file mode 100644
index 0000000..8e8e434
--- /dev/null
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerConfigurationDetectorTest.kt
@@ -0,0 +1,417 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.lint.gradle
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EagerConfigurationDetectorTest : GradleLintDetectorTest(
+    detector = EagerConfigurationDetector(),
+    issues = listOf(EagerConfigurationDetector.ISSUE)
+) {
+    @Test
+    fun `Test usage of TaskContainer#create`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.create("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Use register instead of create [EagerGradleConfiguration]
+                project.tasks.create("example")
+                              ~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 4: Replace with register:
+            @@ -4 +4
+            -     project.tasks.create("example")
+            +     project.tasks.register("example")
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of unrelated create method`() {
+        val input = kotlin(
+            """
+                interface Bar
+                class Foo : Bar {
+                    fun create() = Unit
+                }
+                fun foo() {
+                    Foo().create()
+                }
+            """.trimIndent()
+        )
+        check(input).expectClean()
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#getByName`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.getByName("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Use named instead of getByName [EagerGradleConfiguration]
+                project.tasks.getByName("example")
+                              ~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 4: Replace with named:
+            @@ -4 +4
+            -     project.tasks.getByName("example")
+            +     project.tasks.named("example")
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of DomainObjectCollection#all`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Action
+                import org.gradle.api.Project
+                import org.gradle.api.Task
+
+                fun configure(project: Project, action: Action<Task>) {
+                    project.tasks.all(action)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:6: Error: Use configureEach instead of all [EagerGradleConfiguration]
+                project.tasks.all(action)
+                              ~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 6: Replace with configureEach:
+            @@ -6 +6
+            -     project.tasks.all(action)
+            +     project.tasks.configureEach(action)
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#whenTaskAdded`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Action
+                import org.gradle.api.Project
+                import org.gradle.api.Task
+
+                fun configure(project: Project, action: Action<Task>) {
+                    project.tasks.whenTaskAdded(action)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:6: Error: Use configureEach instead of whenTaskAdded [EagerGradleConfiguration]
+                project.tasks.whenTaskAdded(action)
+                              ~~~~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 6: Replace with configureEach:
+            @@ -6 +6
+            -     project.tasks.whenTaskAdded(action)
+            +     project.tasks.configureEach(action)
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of DomainObjectCollection#whenObjectAdded`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Action
+                import org.gradle.api.Project
+                import org.gradle.api.Task
+
+                fun configure(project: Project, action: Action<Task>) {
+                    project.tasks.whenObjectAdded(action)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:6: Error: Use configureEach instead of whenObjectAdded [EagerGradleConfiguration]
+                project.tasks.whenObjectAdded(action)
+                              ~~~~~~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 6: Replace with configureEach:
+            @@ -6 +6
+            -     project.tasks.whenObjectAdded(action)
+            +     project.tasks.configureEach(action)
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of TaskCollection#getAt`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.getAt("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Use named instead of getAt [EagerGradleConfiguration]
+                project.tasks.getAt("example")
+                              ~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        val expectedFixDiffs = """
+            Fix for src/test.kt line 4: Replace with named:
+            @@ -4 +4
+            -     project.tasks.getAt("example")
+            +     project.tasks.named("example")
+        """.trimIndent()
+
+        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#getByPath`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.getByPath("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method getByPath [EagerGradleConfiguration]
+                project.tasks.getByPath("example")
+                              ~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#findByName`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.findByName("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method findByName [EagerGradleConfiguration]
+                project.tasks.findByName("example")
+                              ~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#findByPath`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.findByPath("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method findByPath [EagerGradleConfiguration]
+                project.tasks.findByPath("example")
+                              ~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#replace`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.replace("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method replace [EagerGradleConfiguration]
+                project.tasks.replace("example")
+                              ~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#remove`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project, task: Task) {
+                    project.tasks.remove(task)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method remove [EagerGradleConfiguration]
+                project.tasks.remove(task)
+                              ~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskContainer#iterator`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.findByPath("example")
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method findByPath [EagerGradleConfiguration]
+                project.tasks.findByPath("example")
+                              ~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of NamedDomainObjectCollection#findAll`() {
+        val input = kotlin(
+            """
+                import groovy.lang.Closure
+                import org.gradle.api.Project
+
+                fun configure(project: Project, closure: Closure) {
+                    project.tasks.findAll(closure)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:5: Error: Avoid using eager method findAll [EagerGradleConfiguration]
+                project.tasks.findAll(closure)
+                              ~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskCollection#matching`() {
+        val input = kotlin(
+            """
+                import groovy.lang.Closure
+                import org.gradle.api.Project
+
+                fun configure(project: Project, closure: Closure) {
+                    project.tasks.matching(closure)
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:5: Error: Avoid using eager method matching [EagerGradleConfiguration]
+                project.tasks.matching(closure)
+                              ~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun `Test usage of TaskProvider#get`() {
+        val input = kotlin(
+            """
+                import org.gradle.api.Project
+
+                fun configure(project: Project) {
+                    project.tasks.register("example").get()
+                }
+            """.trimIndent()
+        )
+
+        val expected = """
+            src/test.kt:4: Error: Avoid using eager method get [EagerGradleConfiguration]
+                project.tasks.register("example").get()
+                                                  ~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+
+        check(input).expect(expected)
+    }
+}
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerTaskConfigurationDetectorTest.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerTaskConfigurationDetectorTest.kt
deleted file mode 100644
index d52b3d8..0000000
--- a/lint/lint-gradle/src/test/java/androidx/lint/gradle/EagerTaskConfigurationDetectorTest.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.lint.gradle
-
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class EagerTaskConfigurationDetectorTest : GradleLintDetectorTest(
-    detector = EagerTaskConfigurationDetector(),
-    issues = listOf(EagerTaskConfigurationDetector.ISSUE)
-) {
-    @Test
-    fun `Test usage of TaskContainer#create`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Project
-
-                fun configure(project: Project) {
-                    project.tasks.create("example")
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:4: Error: Use register instead of create [EagerGradleTaskConfiguration]
-                project.tasks.create("example")
-                              ~~~~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 4: Replace with register:
-            @@ -4 +4
-            -     project.tasks.create("example")
-            +     project.tasks.register("example")
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-
-    @Test
-    fun `Test usage of TaskContainer#getByName`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Project
-
-                fun configure(project: Project) {
-                    project.tasks.getByName("example")
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:4: Error: Use named instead of getByName [EagerGradleTaskConfiguration]
-                project.tasks.getByName("example")
-                              ~~~~~~~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 4: Replace with named:
-            @@ -4 +4
-            -     project.tasks.getByName("example")
-            +     project.tasks.named("example")
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-
-    @Test
-    fun `Test usage of DomainObjectCollection#all`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Action
-                import org.gradle.api.Project
-                import org.gradle.api.Task
-
-                fun configure(project: Project, action: Action<Task>) {
-                    project.tasks.all(action)
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:6: Error: Use configureEach instead of all [EagerGradleTaskConfiguration]
-                project.tasks.all(action)
-                              ~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 6: Replace with configureEach:
-            @@ -6 +6
-            -     project.tasks.all(action)
-            +     project.tasks.configureEach(action)
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-
-    @Test
-    fun `Test usage of TaskContainer#whenTaskAdded`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Action
-                import org.gradle.api.Project
-                import org.gradle.api.Task
-
-                fun configure(project: Project, action: Action<Task>) {
-                    project.tasks.whenTaskAdded(action)
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:6: Error: Use configureEach instead of whenTaskAdded [EagerGradleTaskConfiguration]
-                project.tasks.whenTaskAdded(action)
-                              ~~~~~~~~~~~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 6: Replace with configureEach:
-            @@ -6 +6
-            -     project.tasks.whenTaskAdded(action)
-            +     project.tasks.configureEach(action)
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-
-    @Test
-    fun `Test usage of DomainObjectCollection#whenObjectAdded`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Action
-                import org.gradle.api.Project
-                import org.gradle.api.Task
-
-                fun configure(project: Project, action: Action<Task>) {
-                    project.tasks.whenObjectAdded(action)
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:6: Error: Use configureEach instead of whenObjectAdded [EagerGradleTaskConfiguration]
-                project.tasks.whenObjectAdded(action)
-                              ~~~~~~~~~~~~~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 6: Replace with configureEach:
-            @@ -6 +6
-            -     project.tasks.whenObjectAdded(action)
-            +     project.tasks.configureEach(action)
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-
-    @Test
-    fun `Test usage of TaskCollection#getAt`() {
-        val input = kotlin(
-            """
-                import org.gradle.api.Project
-
-                fun configure(project: Project) {
-                    project.tasks.getAt("example")
-                }
-            """.trimIndent()
-        )
-
-        val expected = """
-            src/test.kt:4: Error: Use named instead of getAt [EagerGradleTaskConfiguration]
-                project.tasks.getAt("example")
-                              ~~~~~
-            1 errors, 0 warnings
-        """.trimIndent()
-        val expectedFixDiffs = """
-            Fix for src/test.kt line 4: Replace with named:
-            @@ -4 +4
-            -     project.tasks.getAt("example")
-            +     project.tasks.named("example")
-        """.trimIndent()
-
-        check(input).expect(expected).expectFixDiffs(expectedFixDiffs)
-    }
-}
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
index 245e6c6..eb8807d 100644
--- a/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
@@ -28,19 +28,38 @@
             """
                 package org.gradle.api.tasks
 
+                import groovy.lang.Closure
                 import org.gradle.api.DomainObjectCollection
+                import org.gradle.api.NamedDomainObjectCollection
+                import org.gradle.api.provider.Provider
                 import org.gradle.api.Task
+                import org.gradle.api.tasks.TaskProvider
 
-                class TaskContainer : DomainObjectCollection<Task>, TaskCollection<Task> {
+                class TaskContainer : DomainObjectCollection<Task>, TaskCollection<Task>, NamedDomainObjectCollection<Task> {
                     fun create(name: String) = Unit
-                    fun register(name: String) = Unit
+                    fun register(name: String): TaskProvider<Task> = TODO()
                     fun getByName(name: String) = Unit
                     fun named(name: String) = Unit
                     fun whenTaskAdded(action: Action<in T>)
+                    fun getByPath(path: String) = Unit
+                    fun findByPath(path: String) = Unit
+                    fun replace(name: String) = Unit
+                    fun remove(task: Task) = Unit
                 }
 
                 interface TaskCollection<T : Task> {
                     fun getAt(name: String) = Unit
+                    fun matching(closure: Closure) = Unit
+                }
+                interface TaskProvider<T : Task> : Provider<T>
+
+            """.trimIndent()
+        ),
+        kotlin(
+            """
+                package org.gradle.api.provider
+                interface Provider<T> {
+                    fun get() : T
                 }
             """.trimIndent()
         ),
@@ -48,21 +67,36 @@
             """
                 package org.gradle.api
 
+                import groovy.lang.Closure
                 import org.gradle.api.tasks.TaskContainer
 
                 class Project {
                     val tasks: TaskContainer
                 }
 
+                interface NamedDomainObjectCollection<T> : Collection<T>, DomainObjectCollection<T>, Iterable<T> {
+                    fun findByName(name: String) = Unit
+                    fun findAll(closure: Closure) = Unit
+                }
+
                 interface DomainObjectCollection<T> {
                     fun all(action: Action<in T>)
                     fun configureEach(action: Action<in T>)
                     fun whenObjectAdded(action: Action<in T>)
+                    fun withType(cls: Class)
+                    fun withType(cls: Class, action: Action)
                 }
 
                 interface Action<T>
 
                 interface Task
             """.trimIndent()
+        ),
+        kotlin(
+            """
+                package groovy.lang
+
+                class Closure
+            """.trimIndent()
         )
     )
diff --git a/media2/OWNERS b/media2/OWNERS
deleted file mode 100644
index 5fa0461..0000000
--- a/media2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 188488
-file: ../media/OWNERS
diff --git a/media2/constants.gradle b/media2/constants.gradle
deleted file mode 100644
index 4f347a9..0000000
--- a/media2/constants.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-project.ext {
-    ANNOTATION_VERSION = "1.2.0"
-    COLLECTION_VERSION = "1.1.0"
-    CONCURRENT_FUTURE_VERSION = "1.0.0"
-}
diff --git a/media2/integration-tests/testapp/OWNERS b/media2/integration-tests/testapp/OWNERS
deleted file mode 100644
index 8050f58..0000000
--- a/media2/integration-tests/testapp/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# Bug component: 188488
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/media2/integration-tests/testapp/build.gradle b/media2/integration-tests/testapp/build.gradle
deleted file mode 100644
index ef3049b..0000000
--- a/media2/integration-tests/testapp/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2018 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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.application")
-}
-
-dependencies {
-    implementation(project(":media2:media2-player"))
-    implementation(project(":media2:media2-widget"))
-    implementation("androidx.appcompat:appcompat:1.0.2")
-    implementation("androidx.core:core:1.1.0")
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 19
-    }
-    namespace "androidx.media2.integration.testapp"
-}
diff --git a/media2/integration-tests/testapp/lint-baseline.xml b/media2/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index 22825866..0000000
--- a/media2/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 23; however, the containing class androidx.media2.integration.testapp.VideoSelectorActivity is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 23; however, the containing class androidx.media2.integration.testapp.VideoSelectorActivity is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="MediaItem.getMediaId can only be called from within the same library group (referenced groupId=`androidx.media2` from groupId=`androidx.media2.integration-tests`)"
-        errorLine1="                    &amp;&amp; TextUtils.equals(currentItem.getMediaId(), mUri.toString())"
-        errorLine2="                                                    ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="BaseResult.RESULT_SUCCESS can only be accessed from within the same library group (referenced groupId=`androidx.media2` from groupId=`androidx.media2.integration-tests`)"
-        errorLine1="                if (playerResult.getResultCode() != SessionPlayer.PlayerResult.RESULT_SUCCESS) {"
-        errorLine2="                                                                               ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSessionService.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onCreate(Bundle savedInstanceState) {"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onCreate(Bundle savedInstanceState) {"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {"
-        errorLine2="                                                            ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {"
-        errorLine2="                                                                                  ~~~~~">
-        <location
-            file="src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/integration-tests/testapp/src/main/AndroidManifest.xml b/media2/integration-tests/testapp/src/main/AndroidManifest.xml
deleted file mode 100644
index 0d20cac..0000000
--- a/media2/integration-tests/testapp/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-
-    <uses-sdk tools:overrideLibrary="androidx.media2.widget" />
-
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-
-    <application android:label="Video View Test"
-                 android:supportsRtl="true"
-                 android:requestLegacyExternalStorage="true">
-
-        <activity android:name=".VideoSelectorActivity"
-            android:theme="@style/Theme.AppCompat"
-            android:exported="true"
-            android:configChanges="orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".VideoPlayerActivity"
-            android:theme="@style/Theme.AppCompat"
-            android:exported="true"
-            android:configChanges="orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW"/>
-            </intent-filter>
-        </activity>
-
-        <service
-            android:name=".VideoSessionService"
-            android:exported="false"
-            android:process=":VideoSessionService"
-            tools:ignore="MissingServiceExportedEqualsTrue">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaSessionService" />
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java
deleted file mode 100644
index 41d75e1..0000000
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java
+++ /dev/null
@@ -1,408 +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.media2.integration.testapp;
-
-import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.FragmentActivity;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.widget.MediaControlView;
-import androidx.media2.widget.VideoView;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Locale;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test application for VideoView/MediaControlView
- */
-public class VideoPlayerActivity extends FragmentActivity {
-    public static final String LOOPING_EXTRA_NAME =
-            "com.example.androidx.media.VideoPlayerActivity.IsLooping";
-    public static final String MEDIA_TYPE_ADVERTISEMENT =
-            "com.example.androidx.media.VideoPlayerActivity.MediaTypeAdvertisement";
-    private static final String TAG = "VideoPlayerActivity";
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MyVideoView mVideoView;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaController mMediaController;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    Uri mUri;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    float mSpeed = 1.0f;
-
-    private View mResizeHandle;
-
-    private int mVideoViewDX;
-    private int mVideoViewDY;
-    private int mResizeHandleDX;
-    private int mResizeHandleDY;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        //Remove title bar
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
-
-        setContentView(R.layout.activity_video_player);
-
-        mVideoView = findViewById(R.id.video_view);
-        mVideoView.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                return onTouchVideoView(event);
-            }
-        });
-        SessionToken token = new SessionToken(this,
-                new ComponentName(this, VideoSessionService.class));
-        Executor executor = ContextCompat.getMainExecutor(this);
-        mMediaController = new MediaController.Builder(this)
-                .setControllerCallback(executor, new ControllerCallback())
-                .setSessionToken(token)
-                .build();
-        mVideoView.setMediaController(mMediaController);
-
-        mResizeHandle = findViewById(R.id.resize_handle);
-        mResizeHandle.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                return onTouchResizeHandle(event);
-            }
-        });
-
-        mVideoView.setOnViewTypeChangedListener(new VideoView.OnViewTypeChangedListener() {
-            @Override
-            public void onViewTypeChanged(@NonNull View view, int viewType) {
-                String type = getViewTypeString(viewType);
-                Toast.makeText(VideoPlayerActivity.this, "switched to " + type, Toast.LENGTH_SHORT)
-                        .show();
-                setTitle(type);
-            }
-        });
-
-        CheckBox useTextureView = findViewById(R.id.use_textureview_checkbox);
-        useTextureView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-            @Override
-            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-                if (isChecked) {
-                    mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-                } else {
-                    mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW);
-                }
-            }
-        });
-
-        CheckBox transformable = findViewById(R.id.transformable_checkbox);
-        transformable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-            @Override
-            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-                applyTransformability(isChecked);
-            }
-        });
-
-        String errorString = null;
-        Intent intent = getIntent();
-        Uri videoUri;
-        if (intent == null || (videoUri = intent.getData()) == null || !videoUri.isAbsolute()) {
-            errorString = "Invalid intent";
-        } else {
-            MediaControlView mediaControlView = new MediaControlView(this);
-            mVideoView.setMediaControlView(mediaControlView, 2000);
-            mediaControlView.setOnFullScreenListener(new FullScreenListener());
-
-            mUri = videoUri;
-        }
-        if (errorString != null) {
-            showErrorDialog(errorString);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.d(TAG, "onDestroy");
-        super.onDestroy();
-        mMediaController.close();
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            int screenWidth = getResources().getDisplayMetrics().widthPixels;
-            mSpeed = mMediaController.getPlaybackSpeed();
-            if (ev.getRawX() < (screenWidth / 2.0f)) {
-                mSpeed -= 0.1f;
-            } else {
-                mSpeed += 0.1f;
-            }
-            final String speedString = String.format(Locale.US, "%.2f", mSpeed);
-            showErrorDialogIfFailed(mMediaController.setPlaybackSpeed(mSpeed),
-                    "failed to adjust speed rate to " + speedString);
-            Toast.makeText(this, "speed rate: " + speedString,
-                    Toast.LENGTH_SHORT).show();
-        }
-        return super.onTouchEvent(ev);
-    }
-
-    @Override
-    public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean onTouchVideoView(MotionEvent ev) {
-        int rawX = (int) ev.getRawX();
-        int rawY = (int) ev.getRawY();
-
-        // Move VideoView
-        ViewGroup.MarginLayoutParams vvParams = (ViewGroup.MarginLayoutParams)
-                mVideoView.getLayoutParams();
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mVideoViewDX = rawX - vvParams.leftMargin;
-                mVideoViewDY = rawY - vvParams.topMargin;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                vvParams.leftMargin = rawX - mVideoViewDX;
-                vvParams.topMargin = rawY - mVideoViewDY;
-                mVideoView.setLayoutParams(vvParams);
-                break;
-        }
-
-        // Move ResizeHandle as well
-        ViewGroup.MarginLayoutParams rhParams = (ViewGroup.MarginLayoutParams)
-                mResizeHandle.getLayoutParams();
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mResizeHandleDX = rawX - rhParams.leftMargin;
-                mResizeHandleDY = rawY - rhParams.topMargin;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                rhParams.leftMargin = rawX - mResizeHandleDX;
-                rhParams.topMargin = rawY - mResizeHandleDY;
-                mResizeHandle.setLayoutParams(rhParams);
-                break;
-        }
-
-        return true;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean onTouchResizeHandle(MotionEvent ev) {
-        int rawX = (int) ev.getRawX();
-        int rawY = (int) ev.getRawY();
-
-        // Resize VideoView
-        ViewGroup.MarginLayoutParams vvParams = (ViewGroup.MarginLayoutParams)
-                mVideoView.getLayoutParams();
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mVideoViewDX = rawX - vvParams.width;
-                mVideoViewDY = rawY - vvParams.height;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                vvParams.width = rawX - mVideoViewDX;
-                vvParams.height = rawY - mVideoViewDY;
-                mVideoView.setLayoutParams(vvParams);
-                break;
-        }
-
-        // Move ResizeHandle
-        ViewGroup.MarginLayoutParams rhParams = (ViewGroup.MarginLayoutParams)
-                mResizeHandle.getLayoutParams();
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mResizeHandleDX = rawX - rhParams.leftMargin;
-                mResizeHandleDY = rawY - rhParams.topMargin;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                rhParams.leftMargin = rawX - mResizeHandleDX;
-                rhParams.topMargin = rawY - mResizeHandleDY;
-                mResizeHandle.setLayoutParams(rhParams);
-                break;
-        }
-
-        return true;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void applyTransformability(boolean transformable) {
-        mVideoView.setTransformable(transformable);
-        mResizeHandle.setVisibility(transformable ? View.VISIBLE : View.GONE);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    String getViewTypeString(int viewType) {
-        if (viewType == VideoView.VIEW_TYPE_SURFACEVIEW) {
-            return "SurfaceView";
-        } else if (viewType == VideoView.VIEW_TYPE_TEXTUREVIEW) {
-            return "TextureView";
-        }
-        return "Unknown";
-    }
-
-    private void showErrorDialog(String errorMessage) {
-        AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        builder.setTitle("Playback error")
-                .setMessage(errorMessage)
-                .setCancelable(false)
-                .setPositiveButton("OK",
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialogInterface, int i) {
-                                finish();
-                            }
-                        }).show();
-    }
-
-    private void showErrorDialogIfFailed(ListenableFuture<SessionResult> result,
-            String errorMessage) {
-        result.addListener(() -> {
-            try {
-                SessionResult sessionResult = result.get(0, TimeUnit.MILLISECONDS);
-                if (sessionResult.getResultCode() != SessionResult.RESULT_SUCCESS) {
-                    showErrorDialog(errorMessage);
-                }
-            } catch (Exception e) {
-                showErrorDialog(errorMessage);
-            }
-        }, ContextCompat.getMainExecutor(this));
-    }
-
-    class ControllerCallback extends MediaController.ControllerCallback {
-        @Override
-        public void onPlaybackSpeedChanged(
-                @NonNull MediaController controller, float speed) {
-            mSpeed = speed;
-        }
-
-        @Override
-        public void onConnected(@NonNull MediaController controller,
-                @NonNull SessionCommandGroup allowedCommands) {
-            MediaItem currentItem = controller.getCurrentMediaItem();
-            // Return if current media item exists and it is the same as the one that is selected
-            // to play.
-            if (currentItem != null
-                    && TextUtils.equals(currentItem.getMediaId(), mUri.toString())
-                    && controller.getPlayerState() != SessionPlayer.PLAYER_STATE_IDLE
-                    && controller.getPlayerState() != SessionPlayer.PLAYER_STATE_ERROR) {
-                return;
-            }
-
-            showErrorDialogIfFailed(controller.setMediaItem(mUri.toString()),
-                    "failed to set " + mUri);
-            showErrorDialogIfFailed(controller.prepare(), "failed to prepare " + mUri);
-        }
-    }
-
-    class FullScreenListener implements MediaControlView.OnFullScreenListener {
-        private int mPrevWidth;
-        private int mPrevHeight;
-        private int mPrevLeft;
-        private int mPrevTop;
-
-        @Override
-        public void onFullScreen(@NonNull View view, boolean fullScreen) {
-            // TODO: Remove bottom controls after adding back button functionality.
-            ViewGroup.MarginLayoutParams params =
-                    (ViewGroup.MarginLayoutParams) mVideoView.getLayoutParams();
-            if (fullScreen) {
-                mPrevWidth = params.width;
-                mPrevHeight = params.height;
-                mPrevLeft = params.leftMargin;
-                mPrevTop = params.topMargin;
-
-                // Remove notification bar
-                getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
-                        WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
-                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
-                params.leftMargin = 0;
-                params.topMargin = 0;
-                mVideoView.setLayoutParams(params);
-            } else {
-                // Restore notification bar
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
-                params.width = mPrevWidth;
-                params.height = mPrevHeight;
-                params.leftMargin = mPrevLeft;
-                params.topMargin = mPrevTop;
-                mVideoView.setLayoutParams(params);
-            }
-            mVideoView.requestLayout();
-        }
-    }
-
-    // To intercept touch event when transformable is checked
-    static class MyVideoView extends VideoView {
-        private boolean mTransformable;
-
-        public MyVideoView(Context context) {
-            super(context);
-        }
-
-        public MyVideoView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
-            super(context, attrs, defStyle);
-        }
-
-        void setTransformable(boolean transformable) {
-            mTransformable = transformable;
-        }
-
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent ev) {
-            if (!mTransformable) {
-                return super.onInterceptTouchEvent(ev);
-            }
-            return true;
-        }
-    }
-}
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java
deleted file mode 100644
index f8c2542..0000000
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java
+++ /dev/null
@@ -1,317 +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.media2.integration.testapp;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ListView;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.TreeMap;
-
-/**
- * Start activity for the VideoViewTest application.  This class manages the UI
- * which allows a user to select a video to play back.
- */
-public class VideoSelectorActivity extends Activity {
-    private ListView      mSelectList;
-    private VideoItemList mSelectItems;
-    private EditText      mUrlText;
-    private CheckBox      mLoopingCheckbox;
-    private CheckBox      mAdvertisementCheckBox;
-
-    private static final String TEST_VID_STASH = "/sdcard";
-    private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 100;
-
-    private Intent createLaunchIntent(Context ctx, String url) {
-        Intent ret_val = new Intent(ctx, VideoPlayerActivity.class);
-        ret_val.setData(Uri.parse(url));
-        ret_val.putExtra(
-                VideoPlayerActivity.LOOPING_EXTRA_NAME, mLoopingCheckbox.isChecked());
-        ret_val.putExtra(
-                VideoPlayerActivity.MEDIA_TYPE_ADVERTISEMENT, mAdvertisementCheckBox.isChecked());
-        return ret_val;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                .detectAll()
-                .penaltyDeath()
-                .build());
-
-        setContentView(R.layout.activity_video_selector);
-
-        mSelectList  = (ListView) findViewById(R.id.select_list);
-        final Button playButton = (Button) findViewById(R.id.play_button);
-        mUrlText = (EditText) findViewById(R.id.video_selection_input);
-        mSelectItems = null;
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
-                    == PackageManager.PERMISSION_GRANTED) {
-                setUpInitialItemList();
-            } else {
-                requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
-                        EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
-            }
-        } else {
-            setUpInitialItemList();
-        }
-
-        playButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent launch = createLaunchIntent(
-                        VideoSelectorActivity.this,
-                        mUrlText.getText().toString());
-                startActivity(launch);
-            }
-        });
-        mLoopingCheckbox = findViewById(R.id.looping_checkbox);
-        mLoopingCheckbox.setChecked(false);
-        mAdvertisementCheckBox = findViewById(R.id.media_type_advertisement);
-        mAdvertisementCheckBox.setChecked(false);
-    }
-
-    @Override
-    public void onBackPressed() {
-        if ((null != mSelectItems) && (null != mSelectHandler) && !mSelectItems.getIsRoot()) {
-            mSelectHandler.onItemClick(null, null, 0, 0);
-        } else {
-            finish();
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
-        if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
-            if (results.length > 0 && results[0] == PackageManager.PERMISSION_GRANTED) {
-                setUpInitialItemList();
-            }
-        }
-    }
-
-    private void setUpInitialItemList() {
-        new VideoItemListTask(TEST_VID_STASH).execute();
-        mSelectList.setOnItemClickListener(mSelectHandler);
-    }
-
-    /**
-     * VideoItem is a class used to represent a selectable item on the listbox
-     * used to select videos to playback.
-     */
-    private static class VideoItem {
-        private final String mToStringName;
-        private final String mName;
-        private final String mUrl;
-        private final boolean mIsDir;
-
-        VideoItem(String name, String url, boolean isDir) {
-            mName = name;
-            mUrl  = url;
-            mIsDir = isDir;
-
-            if (isDir) {
-                mToStringName = String.format("[dir] %s", name);
-            } else {
-                int ndx = url.indexOf(':');
-                if (ndx > 0) {
-                    mToStringName = String.format("[%s] %s", url.substring(0, ndx), name);
-                } else {
-                    mToStringName = name;
-                }
-            }
-        }
-
-        public static VideoItem createFromLinkFile(File f) {
-            VideoItem retVal = null;
-
-            try {
-                BufferedReader rd = new BufferedReader(
-                        new InputStreamReader(new FileInputStream(f), "UTF-8"));
-                String name = rd.readLine();
-                String url  = rd.readLine();
-                if ((null != name) && (null != url)) {
-                    retVal = new VideoItem(name, url, false);
-                }
-            } catch (FileNotFoundException e) {
-            } catch (IOException e) {
-            }
-
-            return retVal;
-        }
-
-        @Override
-        public String toString() {
-            return mToStringName;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public String getUrl() {
-            return mUrl;
-        }
-
-        public boolean getIsDir() {
-            return mIsDir;
-        }
-    }
-
-    private OnItemClickListener mSelectHandler = new OnItemClickListener() {
-        @Override
-        public void onItemClick(AdapterView parent,
-                                View v,
-                                int position,
-                                long id) {
-            if ((position >= 0) && (position < mSelectItems.getCount())) {
-                VideoItem item = mSelectItems.getItem(position);
-                if (item.getIsDir()) {
-                    new VideoItemListTask(item.getUrl()).execute();
-                } else {
-                    Intent launch = createLaunchIntent(
-                            VideoSelectorActivity.this,
-                            item.getUrl());
-                    startActivity(launch);
-                }
-            }
-        }
-    };
-
-    /**
-     * VideoItemList is an array adapter of video items used by the android
-     * framework to populate the list of videos to select.
-     */
-    private class VideoItemList extends ArrayAdapter<VideoItem> {
-        private final boolean mIsRoot;
-        private VideoItemList(boolean isRoot) {
-            super(VideoSelectorActivity.this,
-                  R.layout.video_list_item,
-                  R.id.video_list_item);
-            mIsRoot = isRoot;
-        }
-        public boolean getIsRoot() {
-            return mIsRoot;
-        }
-    };
-
-    private VideoItemList createVil(String p) {
-        boolean is_root = TEST_VID_STASH.equals(p);
-
-        File dir = new File(p);
-        if (!dir.isDirectory() || !dir.canRead()) {
-            return null;
-        }
-
-        VideoItemList retVal = new VideoItemList(is_root);
-
-        // If this is not the root directory, go ahead and add the back link to
-        // our parent.
-        if (!is_root) {
-            retVal.add(new VideoItem("..", dir.getParentFile().getAbsolutePath(), true));
-        }
-
-        // Make a sorted list of directories and files contained in this
-        // directory.
-        TreeMap<String, VideoItem> dirs  = new TreeMap<String, VideoItem>();
-        TreeMap<String, VideoItem> files = new TreeMap<String, VideoItem>();
-
-        File search_dir = new File(p);
-        File[] flist = search_dir.listFiles();
-        if (null == flist) {
-            return retVal;
-        }
-
-        for (File f : flist) {
-            if (f.canRead()) {
-                if (f.isFile()) {
-                    String fname = f.getName();
-                    VideoItem newItem = null;
-
-                    if (fname.endsWith(".url")) {
-                        newItem = VideoItem.createFromLinkFile(f);
-                    } else {
-                        String url = "file://" + f.getAbsolutePath();
-                        newItem = new VideoItem(fname, url, false);
-                    }
-
-                    if (null != newItem) {
-                        files.put(newItem.getName(), newItem);
-                    }
-                } else if (f.isDirectory()) {
-                    VideoItem newItem = new VideoItem(f.getName(), f.getAbsolutePath(), true);
-                    dirs.put(newItem.getName(), newItem);
-                }
-            }
-        }
-
-        // now add the sorted directories to the result set.
-        for (VideoItem vi : dirs.values()) {
-            retVal.add(vi);
-        }
-
-        // finally add the sorted files to the result set.
-        for (VideoItem vi : files.values()) {
-            retVal.add(vi);
-        }
-
-        return retVal;
-    }
-
-    private final class VideoItemListTask extends android.os.AsyncTask<Void, Void, VideoItemList> {
-        private String mPath;
-
-        VideoItemListTask(String path) {
-            mPath = path;
-        }
-
-        @Override
-        protected VideoItemList doInBackground(Void... params) {
-            return createVil(mPath);
-        }
-
-        @Override
-        protected void onPostExecute(VideoItemList items) {
-            if (items != null) {
-                mSelectItems = items;
-                mSelectList.setAdapter(mSelectItems);
-            }
-        }
-    }
-}
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java
deleted file mode 100644
index f2df815..0000000
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java
+++ /dev/null
@@ -1,232 +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.media2.integration.testapp;
-
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.player.MediaPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSessionService;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test service for VideoPlayerActivity
- */
-public class VideoSessionService extends MediaSessionService {
-    private final String mSessionId = "VideoSessionService";
-
-    private MediaPlayer mMediaPlayer;
-    private MediaSession mMediaSession;
-    private AudioAttributesCompat mAudioAttributes;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        Executor executor = ContextCompat.getMainExecutor(this);
-        if (mMediaPlayer == null) {
-            mAudioAttributes = new AudioAttributesCompat.Builder()
-                    .setUsage(AudioAttributesCompat.USAGE_MEDIA)
-                    .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE).build();
-            mMediaPlayer = new MediaPlayer(this);
-            showToastIfFailed(mMediaPlayer.setAudioAttributes(mAudioAttributes),
-                    "Failed to set audio attribute.");
-        }
-
-        List<MediaSession> sessions = getSessions();
-        for (int i = 0; i < sessions.size(); i++) {
-            if (sessions.get(i).getId().equals(mSessionId)) {
-                mMediaSession = sessions.get(i);
-            }
-        }
-        if (mMediaSession == null) {
-            mMediaSession = new MediaSession.Builder(this, mMediaPlayer)
-                    .setSessionCallback(executor, new SessionCallback())
-                    .setId(mSessionId)
-                    .build();
-            mMediaSession.updatePlayer(mMediaPlayer);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        try {
-            if (mMediaPlayer != null) {
-                mMediaPlayer.close();
-                mMediaPlayer = null;
-            }
-        } catch (Exception e) {
-        }
-
-        if (mMediaSession != null) {
-            mMediaSession.close();
-            mMediaSession = null;
-        }
-    }
-
-    @Nullable
-    @Override
-    public MediaSession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
-        return mMediaSession;
-    }
-
-    void showToastIfFailed(ListenableFuture<SessionPlayer.PlayerResult> result,
-            String errorMessage) {
-        result.addListener(() -> {
-            boolean showToastMessage = false;
-            try {
-                SessionPlayer.PlayerResult playerResult = result.get(0, TimeUnit.MILLISECONDS);
-                if (playerResult.getResultCode() != SessionPlayer.PlayerResult.RESULT_SUCCESS) {
-                    showToastMessage = true;
-                }
-            } catch (Exception e) {
-                showToastMessage = true;
-            }
-            if (showToastMessage) {
-                Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT);
-            }
-        }, ContextCompat.getMainExecutor(this));
-    }
-
-    class SessionCallback extends MediaSession.SessionCallback {
-        @Nullable
-        @Override
-        public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                @NonNull MediaSession.ControllerInfo controller, @NonNull String mediaId) {
-            MediaMetadata metadata = new MediaMetadata.Builder()
-                    .putString(METADATA_KEY_MEDIA_ID, mediaId)
-                    .build();
-            UriMediaItem currentItem = new UriMediaItem.Builder(Uri.parse(mediaId))
-                    .setMetadata(metadata)
-                    .build();
-            MetadataExtractTask task = new MetadataExtractTask(currentItem,
-                    VideoSessionService.this);
-            task.execute();
-            // TODO: Temporary fix for multiple calls of setMediaItem not working properly.
-            //  (b/135728285)
-            mMediaPlayer.reset();
-            showToastIfFailed(mMediaPlayer.setAudioAttributes(mAudioAttributes),
-                    "Failed to set audio attribute.");
-            return currentItem;
-        }
-    }
-
-    private static class MetadataExtractTask
-            extends android.os.AsyncTask<Void, Void, MediaMetadata> {
-        private MediaItem mItem;
-        private WeakReference<Context> mRefContext;
-
-        MetadataExtractTask(MediaItem mediaItem, Context context) {
-            mItem = mediaItem;
-            mRefContext = new WeakReference<>(context);
-        }
-
-        @Override
-        protected MediaMetadata doInBackground(Void... params) {
-            return extractMetadata(mItem);
-        }
-
-        @Override
-        protected void onPostExecute(MediaMetadata metadata) {
-            if (metadata != null) {
-                mItem.setMetadata(metadata);
-            }
-        }
-
-        MediaMetadata extractMetadata(MediaItem mediaItem) {
-            MediaMetadataRetriever retriever = null;
-            Context context = mRefContext.get();
-            try {
-                if (mediaItem == null) {
-                    return null;
-                } else if (mediaItem instanceof UriMediaItem && context != null) {
-                    Uri uri = ((UriMediaItem) mediaItem).getUri();
-                    retriever = new MediaMetadataRetriever();
-                    retriever.setDataSource(mRefContext.get(), uri);
-                }
-            } catch (IllegalArgumentException e) {
-                return mediaItem.getMetadata();
-            }
-
-            if (retriever == null) {
-                return mediaItem.getMetadata();
-            }
-
-            String title = extractString(retriever, MediaMetadataRetriever.METADATA_KEY_TITLE);
-            String musicArtistText = extractString(retriever,
-                    MediaMetadataRetriever.METADATA_KEY_ARTIST);
-            Bitmap musicAlbumBitmap = extractAlbumArt(retriever);
-
-            if (retriever != null) {
-                try {
-                    retriever.release();
-                } catch (IOException e) {
-                    // Nothing we can do about that...
-                }
-            }
-
-            MediaMetadata metadata = mediaItem.getMetadata();
-            MediaMetadata.Builder builder = metadata == null
-                    ? new MediaMetadata.Builder() : new MediaMetadata.Builder(metadata);
-            builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
-            builder.putString(MediaMetadata.METADATA_KEY_ARTIST, musicArtistText);
-            builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, musicAlbumBitmap);
-            return builder.build();
-        }
-
-        private String extractString(MediaMetadataRetriever retriever, int intKey) {
-            if (retriever != null) {
-                return retriever.extractMetadata(intKey);
-            }
-            return null;
-        }
-
-        private Bitmap extractAlbumArt(MediaMetadataRetriever retriever) {
-            if (retriever != null) {
-                byte[] album = retriever.getEmbeddedPicture();
-                if (album != null) {
-                    return BitmapFactory.decodeByteArray(album, 0, album.length);
-                }
-            }
-            return null;
-        }
-    }
-}
diff --git a/media2/integration-tests/testapp/src/main/res/layout/activity_video_player.xml b/media2/integration-tests/testapp/src/main/res/layout/activity_video_player.xml
deleted file mode 100644
index fa449d9..0000000
--- a/media2/integration-tests/testapp/src/main/res/layout/activity_video_player.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:widget="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/video_player_main"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layoutDirection="ltr"
-    android:keepScreenOn="true"
-    android:background="#3F51B5">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layoutDirection="locale"
-        android:orientation="horizontal">
-
-        <CheckBox
-            android:id="@+id/use_textureview_checkbox"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/texture_view_text"
-            android:checked="true"
-        />
-
-        <CheckBox
-            android:id="@+id/transformable_checkbox"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/transformable_text"
-        />
-
-    </LinearLayout>
-
-    <!-- MediaControlView will be created manually in code -->
-    <view
-        android:id="@+id/video_view"
-        class="androidx.media2.integration.testapp.VideoPlayerActivity$MyVideoView"
-        android:layout_width="300dp"
-        android:layout_height="300dp"
-        android:layout_marginLeft="40dp"
-        android:layout_marginTop="40dp"
-        android:layoutDirection="locale"
-        android:background="#000000"
-        widget:enableControlView="false"
-        widget:viewType="textureView"
-    />
-
-    <View
-        android:id="@+id/resize_handle"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
-        android:layout_marginLeft="340dp"
-        android:layout_marginTop="340dp"
-        android:visibility="gone"
-        android:background="#FF0000"
-    />
-
-</FrameLayout>
diff --git a/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml b/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml
deleted file mode 100644
index 99323ec..0000000
--- a/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/video_selector_main"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical"
-    android:padding="16sp">
-
-    <TextView
-        android:id="@+id/title_text"
-        style="@style/title_text"
-        android:layout_alignParentTop="true"
-        android:text="@string/select_video_prompt" />
-
-    <CheckBox
-        android:id="@+id/looping_checkbox"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentRight="true"
-        android:layout_below="@id/title_text"
-        android:text="@string/looping_text" />
-
-    <CheckBox
-        android:id="@+id/media_type_advertisement"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/title_text"
-        android:layout_toLeftOf="@id/looping_checkbox"
-        android:text="@string/advertisement_text" />
-
-    <Button
-        android:id="@+id/play_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentRight="true"
-        android:text="@string/play_button_text" />
-
-    <EditText
-        android:id="@+id/video_selection_input"
-        android:layout_width="400sp"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@id/play_button" />
-
-    <ListView
-        android:id="@+id/select_list"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_above="@id/video_selection_input"
-        android:layout_below="@id/looping_checkbox" />
-</RelativeLayout>
diff --git a/media2/integration-tests/testapp/src/main/res/layout/video_list_item.xml b/media2/integration-tests/testapp/src/main/res/layout/video_list_item.xml
deleted file mode 100644
index c4e6045..0000000
--- a/media2/integration-tests/testapp/src/main/res/layout/video_list_item.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/video_list_item"
-    style="@style/video_list_item_text"
-    android:padding="12sp"
-/>
diff --git a/media2/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/media2/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
deleted file mode 100644
index 4b245c5..0000000
--- a/media2/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<resources>
-    <string name="select_video_prompt">Select Video</string>
-    <string name="play_button_text">Play</string>
-    <string name="looping_text">Looping</string>
-    <string name="texture_view_text">Use TextureView</string>
-    <string name="advertisement_text">Advertisement</string>
-    <string name="transformable_text">Transform</string>
-</resources>
diff --git a/media2/integration-tests/testapp/src/main/res/values/style.xml b/media2/integration-tests/testapp/src/main/res/values/style.xml
deleted file mode 100644
index 78638ed..0000000
--- a/media2/integration-tests/testapp/src/main/res/values/style.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<resources>
-    <style name="basic_text">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-    </style>
-
-    <style name="title_text" parent="@style/basic_text">
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="video_list_item_text" parent="@style/basic_text">
-        <item name="android:textSize">24sp</item>
-    </style>
-</resources>
diff --git a/media2/media2-common/api/1.0.0-beta01.txt b/media2/media2-common/api/1.0.0-beta01.txt
deleted file mode 100644
index 06f8c67..0000000
--- a/media2/media2-common/api/1.0.0-beta01.txt
+++ /dev/null
@@ -1,232 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public abstract int getShuffleMode();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/1.0.0-beta02.txt b/media2/media2-common/api/1.0.0-beta02.txt
deleted file mode 100644
index 13beb45..0000000
--- a/media2/media2-common/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,232 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public abstract int getShuffleMode();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/1.0.0-rc01.txt b/media2/media2-common/api/1.0.0-rc01.txt
deleted file mode 100644
index 06f8c67..0000000
--- a/media2/media2-common/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,232 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public abstract int getShuffleMode();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/1.1.0-beta01.txt b/media2/media2-common/api/1.1.0-beta01.txt
deleted file mode 100644
index 33d825f..0000000
--- a/media2/media2-common/api/1.1.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.io.Closeable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @CallSuper public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public abstract int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method public android.media.MediaFormat? getFormat();
-    method public int getId();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    method public boolean isSelectable();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SubtitleData(long, long, byte[]);
-    method public byte[] getData();
-    method public long getDurationUs();
-    method public long getStartTimeUs();
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @IntRange(from=0) public int getHeight();
-    method @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/1.2.0-beta01.txt b/media2/media2-common/api/1.2.0-beta01.txt
deleted file mode 100644
index 7992d7b..0000000
--- a/media2/media2-common/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.io.Closeable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @CallSuper public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public abstract int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method public android.media.MediaFormat? getFormat();
-    method public int getId();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    method public boolean isSelectable();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SubtitleData(long, long, byte[]);
-    method public byte[] getData();
-    method public long getDurationUs();
-    method public long getStartTimeUs();
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @IntRange(from=0) public int getHeight();
-    method @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/1.3.0-beta01.txt b/media2/media2-common/api/1.3.0-beta01.txt
deleted file mode 100644
index 3ba4f06..0000000
--- a/media2/media2-common/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  @Deprecated public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  @Deprecated public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem build();
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor @Deprecated public DataSourceCallback();
-    method @Deprecated public abstract long getSize() throws java.io.IOException;
-    method @Deprecated public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @Deprecated public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public long getFileDescriptorLength();
-    method @Deprecated public long getFileDescriptorOffset();
-    method @Deprecated public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field @Deprecated public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method @Deprecated public androidx.media2.common.FileMediaItem build();
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public long getEndPosition();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getMetadata();
-    method @Deprecated public long getStartPosition();
-    method @Deprecated public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static class MediaItem.Builder {
-    ctor @Deprecated public MediaItem.Builder();
-    method @Deprecated public androidx.media2.common.MediaItem build();
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean containsKey(String);
-    method @Deprecated public android.graphics.Bitmap? getBitmap(String);
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public float getFloat(String);
-    method @Deprecated public long getLong(String);
-    method @Deprecated public String? getMediaId();
-    method @Deprecated public androidx.media2.common.Rating? getRating(String);
-    method @Deprecated public String? getString(String);
-    method @Deprecated public CharSequence? getText(String);
-    method @Deprecated public java.util.Set<java.lang.String!> keySet();
-    method @Deprecated public int size();
-    field @Deprecated public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field @Deprecated public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field @Deprecated public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field @Deprecated public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field @Deprecated public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field @Deprecated public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field @Deprecated public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field @Deprecated public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field @Deprecated public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field @Deprecated public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field @Deprecated public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field @Deprecated public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field @Deprecated public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field @Deprecated public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field @Deprecated public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field @Deprecated public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field @Deprecated public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field @Deprecated public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field @Deprecated public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field @Deprecated public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field @Deprecated public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field @Deprecated public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field @Deprecated public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field @Deprecated public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @Deprecated public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field @Deprecated public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field @Deprecated public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field @Deprecated public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field @Deprecated public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field @Deprecated public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field @Deprecated public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field @Deprecated public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @Deprecated public static final class MediaMetadata.Builder {
-    ctor @Deprecated public MediaMetadata.Builder();
-    ctor @Deprecated public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method @Deprecated public androidx.media2.common.MediaMetadata build();
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @Deprecated public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class SessionPlayer implements java.io.Closeable {
-    ctor @Deprecated public SessionPlayer();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated @CallSuper public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public abstract long getBufferedPosition();
-    method @Deprecated public abstract int getBufferingState();
-    method @Deprecated protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method @Deprecated public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method @Deprecated public abstract long getCurrentPosition();
-    method @Deprecated public abstract long getDuration();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method @Deprecated public abstract float getPlaybackSpeed();
-    method @Deprecated public abstract int getPlayerState();
-    method @Deprecated public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @Deprecated public abstract int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public abstract int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field @Deprecated public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field @Deprecated public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field @Deprecated public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field @Deprecated public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field @Deprecated public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field @Deprecated public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field @Deprecated public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field @Deprecated public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @Deprecated public abstract static class SessionPlayer.PlayerCallback {
-    ctor @Deprecated public SessionPlayer.PlayerCallback();
-    method @Deprecated public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static class SessionPlayer.PlayerResult {
-    ctor @Deprecated public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method @Deprecated public android.media.MediaFormat? getFormat();
-    method @Deprecated public int getId();
-    method @Deprecated public java.util.Locale getLanguage();
-    method @Deprecated public int getTrackType();
-    method @Deprecated public boolean isSelectable();
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @Deprecated public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SubtitleData(long, long, byte[]);
-    method @Deprecated public byte[] getData();
-    method @Deprecated public long getDurationUs();
-    method @Deprecated public long getStartTimeUs();
-  }
-
-  @Deprecated public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.net.Uri getUri();
-    method @Deprecated public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method @Deprecated public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  @Deprecated public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri);
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method @Deprecated public androidx.media2.common.UriMediaItem build();
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated @IntRange(from=0) public int getHeight();
-    method @Deprecated @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/aidlRelease/current/androidx/media2/common/ParcelImplListSlice.aidl b/media2/media2-common/api/aidlRelease/current/androidx/media2/common/ParcelImplListSlice.aidl
deleted file mode 100644
index f4e5890..0000000
--- a/media2/media2-common/api/aidlRelease/current/androidx/media2/common/ParcelImplListSlice.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright 2023, 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 IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package androidx.media2.common;
-@JavaOnlyStableParcelable
-parcelable ParcelImplListSlice;
diff --git a/media2/media2-common/api/api_lint.ignore b/media2/media2-common/api/api_lint.ignore
deleted file mode 100644
index 051bfc9..0000000
--- a/media2/media2-common/api/api_lint.ignore
+++ /dev/null
@@ -1,87 +0,0 @@
-// Baseline format: 1.0
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#addPlaylistItem(int, androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#movePlaylistItem(int, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#pause():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#play():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#prepare():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#removePlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#replacePlaylistItem(int, androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#seekTo(long):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#selectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setAudioAttributes(androidx.media.AudioAttributesCompat):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setMediaItem(androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setPlaybackSpeed(float):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setPlaylist(java.util.List<androidx.media2.common.MediaItem>, androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setRepeatMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setShuffleMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#setSurface(android.view.Surface):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#skipToNextPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#skipToPlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#skipToPreviousPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.common.SessionPlayer#updatePlaylistMetadata(androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-
-
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putBitmap(String, android.graphics.Bitmap):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putBitmap(String,android.graphics.Bitmap)
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putFloat(String, float):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putFloat(String,float)
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putLong(String, long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putLong(String,long)
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putRating(String, androidx.media2.common.Rating):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putRating(String,androidx.media2.common.Rating)
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putString(String, String):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putString(String,String)
-BuilderSetStyle: androidx.media2.common.MediaMetadata.Builder#putText(String, CharSequence):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putText(String,CharSequence)
-
-
-CallbackMethodName: androidx.media2.common.DataSourceCallback#getSize():
-    Callback method names must follow the on<Something> style: getSize
-CallbackMethodName: androidx.media2.common.DataSourceCallback#readAt(long, byte[], int, int):
-    Callback method names must follow the on<Something> style: readAt
-
-
-ExecutorRegistration: androidx.media2.common.CallbackMediaItem.Builder#Builder(androidx.media2.common.DataSourceCallback):
-    Registration methods should have overload that accepts delivery Executor: `Builder`
-
-
-IntentName: androidx.media2.common.MediaMetadata#METADATA_KEY_EXTRAS:
-    Intent extra constant name must be EXTRA_FOO: METADATA_KEY_EXTRAS
-
-
-NullableCollection: androidx.media2.common.MediaMetadata#getExtras():
-    Return type of method androidx.media2.common.MediaMetadata.getExtras() is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.common.SessionPlayer#getPlaylist():
-    Return type of method androidx.media2.common.SessionPlayer.getPlaylist() is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.common.SessionPlayer.PlayerCallback#onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem>, androidx.media2.common.MediaMetadata) parameter #1:
-    Type of parameter list in androidx.media2.common.SessionPlayer.PlayerCallback.onPlaylistChanged(androidx.media2.common.SessionPlayer player, java.util.List<androidx.media2.common.MediaItem> list, androidx.media2.common.MediaMetadata metadata) is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.common.UriMediaItem#getUriCookies():
-    Return type of method androidx.media2.common.UriMediaItem.getUriCookies() is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.common.UriMediaItem#getUriHeaders():
-    Return type of method androidx.media2.common.UriMediaItem.getUriHeaders() is a nullable collection (`java.util.Map`); must be non-null
-
-
-StaticFinalBuilder: androidx.media2.common.MediaItem.Builder:
-    Builder must be final: androidx.media2.common.MediaItem.Builder
diff --git a/media2/media2-common/api/current.txt b/media2/media2-common/api/current.txt
deleted file mode 100644
index 3ba4f06..0000000
--- a/media2/media2-common/api/current.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  @Deprecated public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  @Deprecated public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem build();
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor @Deprecated public DataSourceCallback();
-    method @Deprecated public abstract long getSize() throws java.io.IOException;
-    method @Deprecated public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @Deprecated public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public long getFileDescriptorLength();
-    method @Deprecated public long getFileDescriptorOffset();
-    method @Deprecated public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field @Deprecated public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method @Deprecated public androidx.media2.common.FileMediaItem build();
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public long getEndPosition();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getMetadata();
-    method @Deprecated public long getStartPosition();
-    method @Deprecated public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static class MediaItem.Builder {
-    ctor @Deprecated public MediaItem.Builder();
-    method @Deprecated public androidx.media2.common.MediaItem build();
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean containsKey(String);
-    method @Deprecated public android.graphics.Bitmap? getBitmap(String);
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public float getFloat(String);
-    method @Deprecated public long getLong(String);
-    method @Deprecated public String? getMediaId();
-    method @Deprecated public androidx.media2.common.Rating? getRating(String);
-    method @Deprecated public String? getString(String);
-    method @Deprecated public CharSequence? getText(String);
-    method @Deprecated public java.util.Set<java.lang.String!> keySet();
-    method @Deprecated public int size();
-    field @Deprecated public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field @Deprecated public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field @Deprecated public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field @Deprecated public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field @Deprecated public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field @Deprecated public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field @Deprecated public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field @Deprecated public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field @Deprecated public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field @Deprecated public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field @Deprecated public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field @Deprecated public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field @Deprecated public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field @Deprecated public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field @Deprecated public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field @Deprecated public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field @Deprecated public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field @Deprecated public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field @Deprecated public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field @Deprecated public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field @Deprecated public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field @Deprecated public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field @Deprecated public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field @Deprecated public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @Deprecated public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field @Deprecated public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field @Deprecated public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field @Deprecated public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field @Deprecated public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field @Deprecated public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field @Deprecated public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field @Deprecated public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @Deprecated public static final class MediaMetadata.Builder {
-    ctor @Deprecated public MediaMetadata.Builder();
-    ctor @Deprecated public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method @Deprecated public androidx.media2.common.MediaMetadata build();
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @Deprecated public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class SessionPlayer implements java.io.Closeable {
-    ctor @Deprecated public SessionPlayer();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated @CallSuper public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public abstract long getBufferedPosition();
-    method @Deprecated public abstract int getBufferingState();
-    method @Deprecated protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method @Deprecated public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method @Deprecated public abstract long getCurrentPosition();
-    method @Deprecated public abstract long getDuration();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method @Deprecated public abstract float getPlaybackSpeed();
-    method @Deprecated public abstract int getPlayerState();
-    method @Deprecated public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @Deprecated public abstract int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public abstract int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field @Deprecated public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field @Deprecated public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field @Deprecated public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field @Deprecated public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field @Deprecated public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field @Deprecated public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field @Deprecated public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field @Deprecated public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @Deprecated public abstract static class SessionPlayer.PlayerCallback {
-    ctor @Deprecated public SessionPlayer.PlayerCallback();
-    method @Deprecated public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static class SessionPlayer.PlayerResult {
-    ctor @Deprecated public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method @Deprecated public android.media.MediaFormat? getFormat();
-    method @Deprecated public int getId();
-    method @Deprecated public java.util.Locale getLanguage();
-    method @Deprecated public int getTrackType();
-    method @Deprecated public boolean isSelectable();
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @Deprecated public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SubtitleData(long, long, byte[]);
-    method @Deprecated public byte[] getData();
-    method @Deprecated public long getDurationUs();
-    method @Deprecated public long getStartTimeUs();
-  }
-
-  @Deprecated public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.net.Uri getUri();
-    method @Deprecated public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method @Deprecated public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  @Deprecated public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri);
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method @Deprecated public androidx.media2.common.UriMediaItem build();
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated @IntRange(from=0) public int getHeight();
-    method @Deprecated @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/res-1.0.0-beta02.txt b/media2/media2-common/api/res-1.0.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-common/api/res-1.0.0-beta02.txt
+++ /dev/null
diff --git a/media2/media2-common/api/res-1.0.0-rc01.txt b/media2/media2-common/api/res-1.0.0-rc01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-common/api/res-1.0.0-rc01.txt
+++ /dev/null
diff --git a/media2/media2-common/api/res-1.1.0-beta01.txt b/media2/media2-common/api/res-1.1.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-common/api/res-1.1.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-common/api/res-1.2.0-beta01.txt b/media2/media2-common/api/res-1.2.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-common/api/res-1.2.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-common/api/res-1.3.0-beta01.txt b/media2/media2-common/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-common/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-common/api/restricted_1.0.0-beta01.txt b/media2/media2-common/api/restricted_1.0.0-beta01.txt
deleted file mode 100644
index e3735b8..0000000
--- a/media2/media2-common/api/restricted_1.0.0-beta01.txt
+++ /dev/null
@@ -1,278 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void decreaseRefCount();
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void increaseRefCount();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(@androidx.media2.common.MediaMetadata.FloatKey String);
-    method public long getLong(@androidx.media2.common.MediaMetadata.LongKey String);
-    method public String? getMediaId();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object? getObject(String);
-    method public androidx.media2.common.Rating? getRating(@androidx.media2.common.MediaMetadata.RatingKey String);
-    method public String? getString(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public CharSequence? getText(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_FREQUENCY = "androidx.media2.metadata.RADIO_FREQUENCY";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_PROGRAM_NAME = "androidx.media2.metadata.RADIO_PROGRAM_NAME";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BitmapKey {
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(@androidx.media2.common.MediaMetadata.LongKey String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(@androidx.media2.common.MediaMetadata.LongKey String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(@androidx.media2.common.MediaMetadata.RatingKey String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(@androidx.media2.common.MediaMetadata.TextKey String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(@androidx.media2.common.MediaMetadata.TextKey String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_EXTRAS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BundleKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_FREQUENCY}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.FloatKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION, androidx.media2.common.MediaMetadata.METADATA_KEY_YEAR, androidx.media2.common.MediaMetadata.METADATA_KEY_TRACK_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_NUM_TRACKS, androidx.media2.common.MediaMetadata.METADATA_KEY_DISC_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ADVERTISEMENT, androidx.media2.common.MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.LongKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING, androidx.media2.common.MediaMetadata.METADATA_KEY_RATING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.RatingKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM, androidx.media2.common.MediaMetadata.METADATA_KEY_AUTHOR, androidx.media2.common.MediaMetadata.METADATA_KEY_WRITER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPOSER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPILATION, androidx.media2.common.MediaMetadata.METADATA_KEY_DATE, androidx.media2.common.MediaMetadata.METADATA_KEY_GENRE, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_PROGRAM_NAME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.TextKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ParcelImplListSlice implements android.os.Parcelable {
-    ctor public ParcelImplListSlice(java.util.List<androidx.versionedparcelable.ParcelImpl!>);
-    method public int describeContents();
-    method public java.util.List<androidx.versionedparcelable.ParcelImpl!> getList();
-    method public void writeToParcel(android.os.Parcel!, int);
-    field public static final android.os.Parcelable.Creator<androidx.media2.common.ParcelImplListSlice!>! CREATOR;
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public abstract int getRepeatMode();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public abstract int getShuffleMode();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED, androidx.media2.common.SessionPlayer.BUFFERING_STATE_COMPLETE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.BuffState {
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.common.SessionPlayer.PlayerResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerResult.ResultCode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE, androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED, androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING, androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerState {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.REPEAT_MODE_NONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ALL, androidx.media2.common.SessionPlayer.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.RepeatMode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.SHUFFLE_MODE_NONE, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_ALL, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.ShuffleMode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_1.0.0-beta02.txt b/media2/media2-common/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index 20b5a56..0000000
--- a/media2/media2-common/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,278 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void decreaseRefCount();
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void increaseRefCount();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(@androidx.media2.common.MediaMetadata.FloatKey String);
-    method public long getLong(@androidx.media2.common.MediaMetadata.LongKey String);
-    method public String? getMediaId();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object? getObject(String);
-    method public androidx.media2.common.Rating? getRating(@androidx.media2.common.MediaMetadata.RatingKey String);
-    method public String? getString(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public CharSequence? getText(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_FREQUENCY = "androidx.media2.metadata.RADIO_FREQUENCY";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_PROGRAM_NAME = "androidx.media2.metadata.RADIO_PROGRAM_NAME";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BitmapKey {
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(@androidx.media2.common.MediaMetadata.LongKey String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(@androidx.media2.common.MediaMetadata.LongKey String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(@androidx.media2.common.MediaMetadata.RatingKey String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(@androidx.media2.common.MediaMetadata.TextKey String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(@androidx.media2.common.MediaMetadata.TextKey String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_EXTRAS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BundleKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_FREQUENCY}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.FloatKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION, androidx.media2.common.MediaMetadata.METADATA_KEY_YEAR, androidx.media2.common.MediaMetadata.METADATA_KEY_TRACK_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_NUM_TRACKS, androidx.media2.common.MediaMetadata.METADATA_KEY_DISC_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ADVERTISEMENT, androidx.media2.common.MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.LongKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING, androidx.media2.common.MediaMetadata.METADATA_KEY_RATING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.RatingKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM, androidx.media2.common.MediaMetadata.METADATA_KEY_AUTHOR, androidx.media2.common.MediaMetadata.METADATA_KEY_WRITER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPOSER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPILATION, androidx.media2.common.MediaMetadata.METADATA_KEY_DATE, androidx.media2.common.MediaMetadata.METADATA_KEY_GENRE, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_PROGRAM_NAME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.TextKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ParcelImplListSlice implements android.os.Parcelable {
-    ctor public ParcelImplListSlice(java.util.List<androidx.versionedparcelable.ParcelImpl!>);
-    method public int describeContents();
-    method public java.util.List<androidx.versionedparcelable.ParcelImpl!> getList();
-    method public void writeToParcel(android.os.Parcel!, int);
-    field public static final android.os.Parcelable.Creator<androidx.media2.common.ParcelImplListSlice!>! CREATOR;
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public abstract int getRepeatMode();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public abstract int getShuffleMode();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED, androidx.media2.common.SessionPlayer.BUFFERING_STATE_COMPLETE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.BuffState {
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.common.SessionPlayer.PlayerResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerResult.ResultCode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE, androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED, androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING, androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerState {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.REPEAT_MODE_NONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ALL, androidx.media2.common.SessionPlayer.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.RepeatMode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.SHUFFLE_MODE_NONE, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_ALL, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.ShuffleMode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_1.0.0-rc01.txt b/media2/media2-common/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index 0e57207..0000000
--- a/media2/media2-common/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1,278 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.common {
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void decreaseRefCount();
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void increaseRefCount();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(@androidx.media2.common.MediaMetadata.FloatKey String);
-    method public long getLong(@androidx.media2.common.MediaMetadata.LongKey String);
-    method public String? getMediaId();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object? getObject(String);
-    method public androidx.media2.common.Rating? getRating(@androidx.media2.common.MediaMetadata.RatingKey String);
-    method public String? getString(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public CharSequence? getText(@androidx.media2.common.MediaMetadata.TextKey String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_FREQUENCY = "androidx.media2.metadata.RADIO_FREQUENCY";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String METADATA_KEY_RADIO_PROGRAM_NAME = "androidx.media2.metadata.RADIO_PROGRAM_NAME";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BitmapKey {
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(@androidx.media2.common.MediaMetadata.BitmapKey String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(@androidx.media2.common.MediaMetadata.LongKey String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(@androidx.media2.common.MediaMetadata.LongKey String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(@androidx.media2.common.MediaMetadata.RatingKey String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(@androidx.media2.common.MediaMetadata.TextKey String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(@androidx.media2.common.MediaMetadata.TextKey String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_EXTRAS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.BundleKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_FREQUENCY}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.FloatKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION, androidx.media2.common.MediaMetadata.METADATA_KEY_YEAR, androidx.media2.common.MediaMetadata.METADATA_KEY_TRACK_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_NUM_TRACKS, androidx.media2.common.MediaMetadata.METADATA_KEY_DISC_NUMBER, androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ADVERTISEMENT, androidx.media2.common.MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.LongKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING, androidx.media2.common.MediaMetadata.METADATA_KEY_RATING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.RatingKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @StringDef({androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM, androidx.media2.common.MediaMetadata.METADATA_KEY_AUTHOR, androidx.media2.common.MediaMetadata.METADATA_KEY_WRITER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPOSER, androidx.media2.common.MediaMetadata.METADATA_KEY_COMPILATION, androidx.media2.common.MediaMetadata.METADATA_KEY_DATE, androidx.media2.common.MediaMetadata.METADATA_KEY_GENRE, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ARTIST, androidx.media2.common.MediaMetadata.METADATA_KEY_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID, androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI, androidx.media2.common.MediaMetadata.METADATA_KEY_RADIO_PROGRAM_NAME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaMetadata.TextKey {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ParcelImplListSlice implements android.os.Parcelable {
-    ctor public ParcelImplListSlice(java.util.List<androidx.versionedparcelable.ParcelImpl!>);
-    method public int describeContents();
-    method public java.util.List<androidx.versionedparcelable.ParcelImpl!> getList();
-    method public void writeToParcel(android.os.Parcel!, int);
-    field public static final android.os.Parcelable.Creator<androidx.media2.common.ParcelImplListSlice!>! CREATOR;
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.lang.AutoCloseable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public abstract int getRepeatMode();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public abstract int getShuffleMode();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE, androidx.media2.common.SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED, androidx.media2.common.SessionPlayer.BUFFERING_STATE_COMPLETE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.BuffState {
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.common.SessionPlayer.PlayerResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerResult.ResultCode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE, androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED, androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING, androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.PlayerState {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.REPEAT_MODE_NONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ONE, androidx.media2.common.SessionPlayer.REPEAT_MODE_ALL, androidx.media2.common.SessionPlayer.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.RepeatMode {
-  }
-
-  @IntDef({androidx.media2.common.SessionPlayer.SHUFFLE_MODE_NONE, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_ALL, androidx.media2.common.SessionPlayer.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionPlayer.ShuffleMode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_1.1.0-beta01.txt b/media2/media2-common/api/restricted_1.1.0-beta01.txt
deleted file mode 100644
index 775d1fe..0000000
--- a/media2/media2-common/api/restricted_1.1.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.io.Closeable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @CallSuper public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public abstract int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public static class SessionPlayer.TrackInfo extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method public android.media.MediaFormat? getFormat();
-    method public int getId();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    method public boolean isSelectable();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SubtitleData(long, long, byte[]);
-    method public byte[] getData();
-    method public long getDurationUs();
-    method public long getStartTimeUs();
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @IntRange(from=0) public int getHeight();
-    method @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_1.2.0-beta01.txt b/media2/media2-common/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index 097aa96..0000000
--- a/media2/media2-common/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method public androidx.media2.common.CallbackMediaItem build();
-    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor public DataSourceCallback();
-    method public abstract long getSize() throws java.io.IOException;
-    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method public long getFileDescriptorLength();
-    method public long getFileDescriptorOffset();
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.media2.common.FileMediaItem build();
-    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public long getEndPosition();
-    method public androidx.media2.common.MediaMetadata? getMetadata();
-    method public long getStartPosition();
-    method public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  public static class MediaItem.Builder {
-    ctor public MediaItem.Builder();
-    method public androidx.media2.common.MediaItem build();
-    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method public boolean containsKey(String);
-    method public android.graphics.Bitmap? getBitmap(String);
-    method public android.os.Bundle? getExtras();
-    method public float getFloat(String);
-    method public long getLong(String);
-    method public String? getMediaId();
-    method public androidx.media2.common.Rating? getRating(String);
-    method public String? getString(String);
-    method public CharSequence? getText(String);
-    method public java.util.Set<java.lang.String!> keySet();
-    method public int size();
-    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  public static final class MediaMetadata.Builder {
-    ctor public MediaMetadata.Builder();
-    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method public androidx.media2.common.MediaMetadata build();
-    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method public boolean isRated();
-  }
-
-  public abstract class SessionPlayer implements java.io.Closeable {
-    ctor public SessionPlayer();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @CallSuper public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract long getBufferedPosition();
-    method public abstract int getBufferingState();
-    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method public abstract long getCurrentPosition();
-    method public abstract long getDuration();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method public abstract float getPlaybackSpeed();
-    method public abstract int getPlayerState();
-    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method public abstract int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public abstract int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  public abstract static class SessionPlayer.PlayerCallback {
-    ctor public SessionPlayer.PlayerCallback();
-    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  public static class SessionPlayer.PlayerResult {
-    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method public long getCompletionTime();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public static class SessionPlayer.TrackInfo extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method public android.media.MediaFormat? getFormat();
-    method public int getId();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    method public boolean isSelectable();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SubtitleData(long, long, byte[]);
-    method public byte[] getData();
-    method public long getDurationUs();
-    method public long getStartTimeUs();
-  }
-
-  public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method public android.net.Uri getUri();
-    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.net.Uri);
-    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method public androidx.media2.common.UriMediaItem build();
-    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @IntRange(from=0) public int getHeight();
-    method @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_1.3.0-beta01.txt b/media2/media2-common/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index b3a7b21..0000000
--- a/media2/media2-common/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  @Deprecated public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  @Deprecated public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem build();
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor @Deprecated public DataSourceCallback();
-    method @Deprecated public abstract long getSize() throws java.io.IOException;
-    method @Deprecated public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @Deprecated public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public long getFileDescriptorLength();
-    method @Deprecated public long getFileDescriptorOffset();
-    method @Deprecated public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field @Deprecated public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method @Deprecated public androidx.media2.common.FileMediaItem build();
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method @Deprecated public long getEndPosition();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getMetadata();
-    method @Deprecated public long getStartPosition();
-    method @Deprecated public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static class MediaItem.Builder {
-    ctor @Deprecated public MediaItem.Builder();
-    method @Deprecated public androidx.media2.common.MediaItem build();
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method @Deprecated public boolean containsKey(String);
-    method @Deprecated public android.graphics.Bitmap? getBitmap(String);
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public float getFloat(String);
-    method @Deprecated public long getLong(String);
-    method @Deprecated public String? getMediaId();
-    method @Deprecated public androidx.media2.common.Rating? getRating(String);
-    method @Deprecated public String? getString(String);
-    method @Deprecated public CharSequence? getText(String);
-    method @Deprecated public java.util.Set<java.lang.String!> keySet();
-    method @Deprecated public int size();
-    field @Deprecated public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field @Deprecated public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field @Deprecated public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field @Deprecated public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field @Deprecated public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field @Deprecated public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field @Deprecated public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field @Deprecated public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field @Deprecated public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field @Deprecated public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field @Deprecated public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field @Deprecated public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field @Deprecated public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field @Deprecated public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field @Deprecated public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field @Deprecated public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field @Deprecated public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field @Deprecated public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field @Deprecated public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field @Deprecated public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field @Deprecated public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field @Deprecated public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field @Deprecated public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field @Deprecated public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @Deprecated public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field @Deprecated public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field @Deprecated public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field @Deprecated public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field @Deprecated public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field @Deprecated public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field @Deprecated public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field @Deprecated public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @Deprecated public static final class MediaMetadata.Builder {
-    ctor @Deprecated public MediaMetadata.Builder();
-    ctor @Deprecated public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method @Deprecated public androidx.media2.common.MediaMetadata build();
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @Deprecated public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class SessionPlayer implements java.io.Closeable {
-    ctor @Deprecated public SessionPlayer();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated @CallSuper public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public abstract long getBufferedPosition();
-    method @Deprecated public abstract int getBufferingState();
-    method @Deprecated protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method @Deprecated public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method @Deprecated public abstract long getCurrentPosition();
-    method @Deprecated public abstract long getDuration();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method @Deprecated public abstract float getPlaybackSpeed();
-    method @Deprecated public abstract int getPlayerState();
-    method @Deprecated public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @Deprecated public abstract int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public abstract int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field @Deprecated public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field @Deprecated public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field @Deprecated public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field @Deprecated public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field @Deprecated public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field @Deprecated public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field @Deprecated public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field @Deprecated public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @Deprecated public abstract static class SessionPlayer.PlayerCallback {
-    ctor @Deprecated public SessionPlayer.PlayerCallback();
-    method @Deprecated public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static class SessionPlayer.PlayerResult {
-    ctor @Deprecated public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public static class SessionPlayer.TrackInfo extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method @Deprecated public android.media.MediaFormat? getFormat();
-    method @Deprecated public int getId();
-    method @Deprecated public java.util.Locale getLanguage();
-    method @Deprecated public int getTrackType();
-    method @Deprecated public boolean isSelectable();
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SubtitleData(long, long, byte[]);
-    method @Deprecated public byte[] getData();
-    method @Deprecated public long getDurationUs();
-    method @Deprecated public long getStartTimeUs();
-  }
-
-  @Deprecated public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public android.net.Uri getUri();
-    method @Deprecated public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method @Deprecated public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  @Deprecated public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri);
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method @Deprecated public androidx.media2.common.UriMediaItem build();
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated @IntRange(from=0) public int getHeight();
-    method @Deprecated @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/api/restricted_current.txt b/media2/media2-common/api/restricted_current.txt
deleted file mode 100644
index b3a7b21..0000000
--- a/media2/media2-common/api/restricted_current.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.common {
-
-  @Deprecated public class CallbackMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public androidx.media2.common.DataSourceCallback getDataSourceCallback();
-  }
-
-  @Deprecated public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem build();
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated public abstract class DataSourceCallback implements java.io.Closeable {
-    ctor @Deprecated public DataSourceCallback();
-    method @Deprecated public abstract long getSize() throws java.io.IOException;
-    method @Deprecated public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
-  }
-
-  @Deprecated public class FileMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public long getFileDescriptorLength();
-    method @Deprecated public long getFileDescriptorOffset();
-    method @Deprecated public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-    field @Deprecated public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
-    method @Deprecated public androidx.media2.common.FileMediaItem build();
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method @Deprecated public long getEndPosition();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getMetadata();
-    method @Deprecated public long getStartPosition();
-    method @Deprecated public void setMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
-  }
-
-  @Deprecated public static class MediaItem.Builder {
-    ctor @Deprecated public MediaItem.Builder();
-    method @Deprecated public androidx.media2.common.MediaItem build();
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.MediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
-    method @Deprecated public boolean containsKey(String);
-    method @Deprecated public android.graphics.Bitmap? getBitmap(String);
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public float getFloat(String);
-    method @Deprecated public long getLong(String);
-    method @Deprecated public String? getMediaId();
-    method @Deprecated public androidx.media2.common.Rating? getRating(String);
-    method @Deprecated public String? getString(String);
-    method @Deprecated public CharSequence? getText(String);
-    method @Deprecated public java.util.Set<java.lang.String!> keySet();
-    method @Deprecated public int size();
-    field @Deprecated public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
-    field @Deprecated public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
-    field @Deprecated public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
-    field @Deprecated public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
-    field @Deprecated public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
-    field @Deprecated public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
-    field @Deprecated public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
-    field @Deprecated public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
-    field @Deprecated public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
-    field @Deprecated public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
-    field @Deprecated public static final String METADATA_KEY_ART = "android.media.metadata.ART";
-    field @Deprecated public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
-    field @Deprecated public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
-    field @Deprecated public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
-    field @Deprecated public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
-    field @Deprecated public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
-    field @Deprecated public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
-    field @Deprecated public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
-    field @Deprecated public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
-    field @Deprecated public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
-    field @Deprecated public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
-    field @Deprecated public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
-    field @Deprecated public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-    field @Deprecated public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
-    field @Deprecated public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
-    field @Deprecated public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
-    field @Deprecated public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-    field @Deprecated public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
-    field @Deprecated public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
-    field @Deprecated public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
-    field @Deprecated public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
-    field @Deprecated public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
-    field @Deprecated public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
-    field @Deprecated public static final long STATUS_DOWNLOADED = 2L; // 0x2L
-    field @Deprecated public static final long STATUS_DOWNLOADING = 1L; // 0x1L
-    field @Deprecated public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
-  }
-
-  @Deprecated public static final class MediaMetadata.Builder {
-    ctor @Deprecated public MediaMetadata.Builder();
-    ctor @Deprecated public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
-    method @Deprecated public androidx.media2.common.MediaMetadata build();
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
-    method @Deprecated public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
-  }
-
-  @Deprecated public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class SessionPlayer implements java.io.Closeable {
-    ctor @Deprecated public SessionPlayer();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated @CallSuper public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public abstract long getBufferedPosition();
-    method @Deprecated public abstract int getBufferingState();
-    method @Deprecated protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
-    method @Deprecated public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
-    method @Deprecated public abstract long getCurrentPosition();
-    method @Deprecated public abstract long getDuration();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
-    method @Deprecated public abstract float getPlaybackSpeed();
-    method @Deprecated public abstract int getPlayerState();
-    method @Deprecated public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
-    method @Deprecated public abstract int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public abstract int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
-    method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
-    field @Deprecated public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
-    field @Deprecated public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
-    field @Deprecated public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
-    field @Deprecated public static final int PLAYER_STATE_ERROR = 3; // 0x3
-    field @Deprecated public static final int PLAYER_STATE_IDLE = 0; // 0x0
-    field @Deprecated public static final int PLAYER_STATE_PAUSED = 1; // 0x1
-    field @Deprecated public static final int PLAYER_STATE_PLAYING = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_ALL = 2; // 0x2
-    field @Deprecated public static final int REPEAT_MODE_GROUP = 3; // 0x3
-    field @Deprecated public static final int REPEAT_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final int REPEAT_MODE_ONE = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_ALL = 1; // 0x1
-    field @Deprecated public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
-    field @Deprecated public static final int SHUFFLE_MODE_NONE = 0; // 0x0
-    field @Deprecated public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
-  }
-
-  @Deprecated public abstract static class SessionPlayer.PlayerCallback {
-    ctor @Deprecated public SessionPlayer.PlayerCallback();
-    method @Deprecated public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static class SessionPlayer.PlayerResult {
-    ctor @Deprecated public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public static class SessionPlayer.TrackInfo extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
-    ctor @Deprecated public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
-    method @Deprecated public android.media.MediaFormat? getFormat();
-    method @Deprecated public int getId();
-    method @Deprecated public java.util.Locale getLanguage();
-    method @Deprecated public int getTrackType();
-    method @Deprecated public boolean isSelectable();
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field @Deprecated public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SubtitleData(long, long, byte[]);
-    method @Deprecated public byte[] getData();
-    method @Deprecated public long getDurationUs();
-    method @Deprecated public long getStartTimeUs();
-  }
-
-  @Deprecated public class UriMediaItem extends androidx.media2.common.MediaItem {
-    method @Deprecated public android.net.Uri getUri();
-    method @Deprecated public java.util.List<java.net.HttpCookie!>? getUriCookies();
-    method @Deprecated public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
-  }
-
-  @Deprecated public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri);
-    ctor @Deprecated public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
-    method @Deprecated public androidx.media2.common.UriMediaItem build();
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
-    method @Deprecated public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated @IntRange(from=0) public int getHeight();
-    method @Deprecated @IntRange(from=0) public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-common/build.gradle b/media2/media2-common/build.gradle
deleted file mode 100644
index 29efd41..0000000
--- a/media2/media2-common/build.gradle
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2018 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.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("androidx.stableaidl")
-}
-
-apply(from: "../constants.gradle")
-
-dependencies {
-    api("androidx.core:core:1.6.0")
-    api("androidx.media:media:1.4.1")
-    // TODO(b/177296655): Remove versionedparcelable dependency after the transitive dependency
-    //  through media which depends on core is updated.
-    api(project(":versionedparcelable:versionedparcelable"))
-    api(libs.guavaListenableFuture)
-    implementation("androidx.annotation:annotation:" + ANNOTATION_VERSION)
-    implementation("androidx.collection:collection:" + COLLECTION_VERSION)
-    implementation("androidx.concurrent:concurrent-futures:" + CONCURRENT_FUTURE_VERSION)
-    compileOnly(libs.checkerframework)
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(project(":internal-testutils-runtime"))
-    annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
-}
-
-android {
-    buildFeatures {
-        aidl = true
-    }
-    buildTypes.all {
-        consumerProguardFiles "proguard-rules.pro"
-
-        stableAidl {
-            version 1
-        }
-    }
-    namespace "androidx.media2.common"
-}
-
-androidx {
-    name = "Media2 Common"
-    publish = Publish.SNAPSHOT_AND_RELEASE
-    inceptionYear = "2018"
-    description = "Media2 Common"
-    failOnDeprecationWarnings = false
-    metalavaK2UastEnabled = true
-}
diff --git a/media2/media2-common/lint-baseline.xml b/media2/media2-common/lint-baseline.xml
deleted file mode 100644
index 524d60d..0000000
--- a/media2/media2-common/lint-baseline.xml
+++ /dev/null
@@ -1,193 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="                List&lt;ParcelImpl> parcelImplList = new ArrayList&lt;>();"
-        errorLine2="                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaMetadata.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            List&lt;ParcelImpl> parcelImplList = mBitmapListSlice.getList();"
-        errorLine2="                 ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaMetadata.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public static ParcelImpl toParcelable(@Nullable VersionedParcelable item) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        return (ParcelImpl) ParcelUtils.toParcelable(item);"
-        errorLine2="                ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public static List&lt;ParcelImpl> toParcelableList("
-        errorLine2="                       ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        List&lt;ParcelImpl> list = new ArrayList&lt;>();"
-        errorLine2="             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public static &lt;T extends VersionedParcelable> T fromParcelable(@NonNull ParcelImpl p) {"
-        errorLine2="                                                                            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            @NonNull List&lt;ParcelImpl> parcelList) {"
-        errorLine2="                          ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    private static class MediaItemParcelImpl extends ParcelImpl {"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be called from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            super(new MediaItem(item));"
-        errorLine2="            ~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be called from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            super(new MediaItem(item));"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl.getVersionedParcel can only be called from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        public MediaItem getVersionedParcel() {"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/MediaParcelUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    final List&lt;ParcelImpl> mList;"
-        errorLine2="               ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public ParcelImplListSlice(@NonNull List&lt;ParcelImpl> list) {"
-        errorLine2="                                             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl parcelImpl = p.readParcelable(ParcelImpl.class.getClassLoader());"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="                    final ParcelImpl parcelImpl = reply.readParcelable("
-        errorLine2="                          ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public @NonNull List&lt;ParcelImpl> getList() {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="                final ParcelImpl parcelable = mList.get(i);"
-        errorLine2="                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="                            final ParcelImpl parcelable = mList.get(i);"
-        errorLine2="                                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-    <issue
-        id="KotlinPropertyAccess"
-        message="This method should be called `getMediaItem` such that `mediaItem` can be accessed as a property from Kotlin; see https://android.github.io/kotlin-guides/interop.html#property-prefixes"
-        errorLine1="    public abstract MediaItem getCurrentMediaItem();"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/SessionPlayer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void writeToParcel(Parcel dest, int flags) {"
-        errorLine2="                              ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/common/ParcelImplListSlice.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-common/proguard-rules.pro b/media2/media2-common/proguard-rules.pro
deleted file mode 100644
index cd25c1e..0000000
--- a/media2/media2-common/proguard-rules.pro
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2020 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.
-
-# Prevent Parcelable objects from being removed or renamed.
--keep class androidx.media2.** implements android.os.Parcelable {
-    public static final android.os.Parcelable$Creator *;
-}
diff --git a/media2/media2-common/src/androidTest/AndroidManifest.xml b/media2/media2-common/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index f8c9e96..0000000
--- a/media2/media2-common/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-</manifest>
diff --git a/media2/media2-common/src/androidTest/java/androidx/media2/common/SubtitleDataTest.java b/media2/media2-common/src/androidTest/java/androidx/media2/common/SubtitleDataTest.java
deleted file mode 100644
index eb3dd9b..0000000
--- a/media2/media2-common/src/androidTest/java/androidx/media2/common/SubtitleDataTest.java
+++ /dev/null
@@ -1,45 +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.media2.common;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.media.MediaFormat;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link SubtitleData}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SubtitleDataTest {
-
-    @Test
-    public void constructor() {
-        byte[] testData = {4, 3, 2, 1};
-        final MediaFormat format = new MediaFormat();
-        SubtitleData data = new SubtitleData(123, 456, testData);
-        assertEquals(123, data.getStartTimeUs());
-        assertEquals(456, data.getDurationUs());
-        assertEquals(testData, data.getData());
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/BaseResult.java b/media2/media2-common/src/main/java/androidx/media2/common/BaseResult.java
deleted file mode 100644
index c446363..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/BaseResult.java
+++ /dev/null
@@ -1,112 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-/**
- * Base interface for all result classes in {@link androidx.media2.session.MediaSession}, {@link
- * androidx.media2.session.MediaController}, and {@link SessionPlayer}, for defining result codes in
- * one place with documentation.
- *
- * <ul>
- *   <li>Error code: Negative integer
- *   <li>Success code: 0
- *   <li>Info code: Positive integer
- * </ul>
- *
- * <ul>
- *   <li>0 < |code| < 100: Session player specific code.
- *   <li>100 <= |code| < 500: Session/Controller specific code.
- *   <li>500 <= |code| < 1000: Browser/Library session specific code.
- *   <li>1000 <= |code| : Custom session player result code.
- * </ul>
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@RestrictTo(LIBRARY_GROUP)
-public interface BaseResult {
-    /**
-     * Result code representing that the command is successfully completed.
-     */
-    int RESULT_SUCCESS = 0;
-
-    /**
-     * Result code represents that call is ended with an unknown error.
-     */
-    int RESULT_ERROR_UNKNOWN = -1;
-
-    /**
-     * Result code representing that the command cannot be completed because the current state is
-     * not valid for the command.
-     */
-    int RESULT_ERROR_INVALID_STATE = -2;
-
-    /**
-     * Result code representing that an argument is illegal.
-     */
-    int RESULT_ERROR_BAD_VALUE = -3;
-
-    /**
-     * Result code representing that the command is not allowed.
-     */
-    int RESULT_ERROR_PERMISSION_DENIED = -4;
-
-    /**
-     * Result code representing a file or network related command error.
-     */
-    int RESULT_ERROR_IO = -5;
-
-    /**
-     * Result code representing that the command is not supported nor implemented.
-     */
-    int RESULT_ERROR_NOT_SUPPORTED = -6;
-
-    /**
-     * Result code representing that the command is skipped or canceled. For an example, a seek
-     * command can be skipped if it is followed by another seek command.
-     */
-    int RESULT_INFO_SKIPPED = 1;
-
-    /**
-     * Returns result code.
-     */
-    // Subclasses should write its own documentation with @IntDef annotation at the return type.
-    int getResultCode();
-
-    /**
-     * Returns elapsed time.
-     */
-    // Subclasses should write its own documentation.
-    // Should use SystemClock#elapsedRealtime() instead of System#currentTimeMillis() because
-    //    1. System#currentTimeMillis() can be unexpectedly set by System#setCurrentTimeMillis() or
-    //       changes in the timezone. So receiver cannot know when the command is finished.
-    //    2. For matching the timestamp with the PlaybackState(Compat) which uses
-    //       SystemClock#elapsedRealtime().
-    long getCompletionTime();
-
-    /**
-     * Returns MediaItem.
-     */
-    // Subclasses should write its own documentation.
-    @Nullable MediaItem getMediaItem();
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/CallbackMediaItem.java b/media2/media2-common/src/main/java/androidx/media2/common/CallbackMediaItem.java
deleted file mode 100644
index 825f2fe..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/CallbackMediaItem.java
+++ /dev/null
@@ -1,107 +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.media2.common;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.versionedparcelable.ParcelUtils;
-
-/**
- * Structure for media item descriptor for {@link DataSourceCallback}.
- *
- * <p>Users should use {@link Builder} to create {@link CallbackMediaItem}.
- *
- * <p>You cannot directly send this object across the process through {@link ParcelUtils}. See
- * {@link MediaItem} for detail.
- *
- * @see MediaItem
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class CallbackMediaItem extends MediaItem {
-    private final DataSourceCallback mDataSourceCallback;
-
-    CallbackMediaItem(Builder builder) {
-        super(builder);
-        mDataSourceCallback = builder.mDataSourceCallback;
-    }
-
-    /**
-     * Return the DataSourceCallback that implements the callback for the data source of this media
-     * item.
-     *
-     * @return the DataSourceCallback that implements the callback for the data source of this
-     *         media item,
-     */
-    public @NonNull DataSourceCallback getDataSourceCallback() {
-        return mDataSourceCallback;
-    }
-
-    /**
-     * This Builder class simplifies the creation of a {@link CallbackMediaItem} object.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder extends MediaItem.Builder {
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        DataSourceCallback mDataSourceCallback;
-
-        /**
-         * Creates a new Builder object.
-         * @param dsc2 the DataSourceCallback for the media you want to play
-         */
-        public Builder(@NonNull DataSourceCallback dsc2) {
-            Preconditions.checkNotNull(dsc2);
-            mDataSourceCallback = dsc2;
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setMetadata(@Nullable MediaMetadata metadata) {
-            return (Builder) super.setMetadata(metadata);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setStartPosition(long position) {
-            return (Builder) super.setStartPosition(position);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setEndPosition(long position) {
-            return (Builder) super.setEndPosition(position);
-        }
-
-        /**
-         * @return A new CallbackMediaItem with values supplied by the Builder.
-         */
-        @Override
-        @NonNull
-        public CallbackMediaItem build() {
-            return new CallbackMediaItem(this);
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java b/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java
deleted file mode 100644
index 5fc397b..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2021 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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-
-/**
- * Helper classes to avoid ClassVerificationFailure.
- *
- */
-@RestrictTo(LIBRARY_GROUP)
-public final class ClassVerificationHelper {
-
-    /** Helper class for {@link android.media.AudioManager}. */
-    public static final class AudioManager {
-
-        /** Helper methods for {@link android.media.AudioManager} APIs added in API level 21. */
-        @RequiresApi(21)
-        public static final class Api21 {
-
-            // TODO(b/194239360): Replace it with AudioManagerCompat#isVolumeFixed.
-            /** Helper method to call {@link android.media.AudioManager#isVolumeFixed()}. */
-            @DoNotInline
-            public static boolean isVolumeFixed(@NonNull android.media.AudioManager manager) {
-                return manager.isVolumeFixed();
-            }
-
-            private Api21() {}
-        }
-
-        private AudioManager() {}
-    }
-
-    /** Helper class for {@link android.app.PendingIntent}. */
-    public static final class PendingIntent {
-
-        /** Helper method for {@link android.app.PendingIntent} APIs added in API level 26. */
-        @RequiresApi(26)
-        public static final class Api26 {
-
-            /** Helper method to call {@link android.app.PendingIntent#getForegroundService}. */
-            @DoNotInline
-            @Nullable
-            public static android.app.PendingIntent getForegroundService(
-                    @NonNull android.content.Context context, int requestCode,
-                    @NonNull android.content.Intent intent, int flags) {
-                return android.app.PendingIntent.getForegroundService(context, requestCode, intent,
-                        flags);
-            }
-
-            private Api26() {}
-        }
-
-        private PendingIntent() {}
-    }
-
-    private ClassVerificationHelper() {}
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/DataSourceCallback.java b/media2/media2-common/src/main/java/androidx/media2/common/DataSourceCallback.java
deleted file mode 100644
index 2f84969..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/DataSourceCallback.java
+++ /dev/null
@@ -1,67 +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.media2.common;
-
-import androidx.annotation.NonNull;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-/**
- * For supplying media data, implement this if your app has special requirements for the way media
- * data is obtained.
- *
- * <p class="note">Methods of this interface may be called on multiple different threads. There will
- * be a thread synchronization point between each call to ensure that modifications to the state of
- * your DataSourceCallback are visible to future calls. This means you don't need to do your own
- * synchronization unless you're modifying the DataSourceCallback from another thread while it's
- * being used by the media library.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public abstract class DataSourceCallback implements Closeable {
-    /**
-     * Called to request data from the given position.
-     *
-     * Implementations should should write up to {@code size} bytes into
-     * {@code buffer}, and return the number of bytes written.
-     *
-     * Return {@code 0} if size is zero (thus no bytes are read).
-     *
-     * Return {@code -1} to indicate that end of stream is reached.
-     *
-     * @param position the position in the media item to read from.
-     * @param buffer the buffer to read the data into.
-     * @param offset the offset within buffer to read the data into.
-     * @param size the number of bytes to read.
-     * @throws IOException on fatal errors.
-     * @return the number of bytes read, or -1 if the end of stream was reached.
-     */
-    public abstract int readAt(long position, @NonNull byte[] buffer, int offset, int size)
-            throws IOException;
-
-    /**
-     * Called to get the size of the data source.
-     *
-     * @throws IOException on fatal errors
-     * @return the size of data source in bytes, or -1 if the size is unknown.
-     */
-    public abstract long getSize() throws IOException;
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/FileMediaItem.java b/media2/media2-common/src/main/java/androidx/media2/common/FileMediaItem.java
deleted file mode 100644
index 8d20a31..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/FileMediaItem.java
+++ /dev/null
@@ -1,258 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.util.Preconditions;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.io.IOException;
-
-/**
- * Structure for media item for a file.
- *
- * <p>Users should use {@link Builder} to create {@link FileMediaItem}.
- *
- * <p>You cannot directly send this object across the process through {@link ParcelUtils}. See
- * {@link MediaItem} for detail.
- *
- * @see MediaItem
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class FileMediaItem extends MediaItem {
-    private static final String TAG = "FileMediaItem";
-    /**
-     * Used when the length of file descriptor is unknown.
-     *
-     * @see #getFileDescriptorLength()
-     */
-    public static final long FD_LENGTH_UNKNOWN = LONG_MAX;
-
-    private final ParcelFileDescriptor mPFD;
-    private final long mFDOffset;
-    private final long mFDLength;
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private int mRefCount;
-
-    @GuardedBy("mLock")
-    private boolean mClosed;
-
-    FileMediaItem(Builder builder) {
-        super(builder);
-        mPFD = builder.mPFD;
-        mFDOffset = builder.mFDOffset;
-        mFDLength = builder.mFDLength;
-    }
-
-    /**
-     * Returns the ParcelFileDescriptor of this media item.
-     * @return the ParcelFileDescriptor of this media item
-     */
-    @NonNull
-    public ParcelFileDescriptor getParcelFileDescriptor() {
-        return mPFD;
-    }
-
-    /**
-     * Returns the offset associated with the ParcelFileDescriptor of this media item.
-     * It's meaningful only when it has been set by the {@link MediaItem.Builder}.
-     * @return the offset associated with the ParcelFileDescriptor of this media item
-     */
-    public long getFileDescriptorOffset() {
-        return mFDOffset;
-    }
-
-    /**
-     * Returns the content length associated with the ParcelFileDescriptor of this media item.
-     * {@link #FD_LENGTH_UNKNOWN} means same as the length of source content.
-     * @return the content length associated with the ParcelFileDescriptor of this media item
-     */
-    public long getFileDescriptorLength() {
-        return mFDLength;
-    }
-
-    /**
-     * Increases reference count for underlying ParcelFileDescriptor.
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void increaseRefCount() {
-        synchronized (mLock) {
-            if (mClosed) {
-                Log.w(TAG, "ParcelFileDescriptorClient is already closed.");
-                return;
-            }
-            mRefCount++;
-        }
-    }
-
-    /**
-     * Increases reference count for underlying ParcelFileDescriptor. The ParcelFileDescriptor will
-     * be closed when the count becomes zero.
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void decreaseRefCount() {
-        synchronized (mLock) {
-            if (mClosed) {
-                Log.w(TAG, "ParcelFileDescriptorClient is already closed.");
-                return;
-            }
-            if (--mRefCount <= 0) {
-                try {
-                    if (mPFD != null) {
-                        mPFD.close();
-                    }
-                } catch (IOException e) {
-                    Log.e(TAG, "Failed to close the ParcelFileDescriptor " + mPFD, e);
-                } finally {
-                    mClosed = true;
-                }
-            }
-        }
-    }
-
-    /**
-     * @return whether the underlying {@link ParcelFileDescriptor} is closed or not.
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public boolean isClosed() {
-        synchronized (mLock) {
-            return mClosed;
-        }
-    }
-
-    /**
-     * Close the {@link ParcelFileDescriptor} of this {@link FileMediaItem}.
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void close() throws IOException {
-        synchronized (mLock) {
-            if (mPFD != null) {
-                mPFD.close();
-            }
-            mClosed = true;
-        }
-    }
-
-    /**
-     * This Builder class simplifies the creation of a {@link FileMediaItem} object.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder extends MediaItem.Builder {
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        ParcelFileDescriptor mPFD;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        long mFDOffset = 0;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        long mFDLength = FD_LENGTH_UNKNOWN;
-
-        /**
-         * Creates a new Builder object with a media item (ParcelFileDescriptor) to use. The
-         * ParcelFileDescriptor must be seekable (N.B. a LocalSocket is not seekable).
-         * <p>
-         * If {@link FileMediaItem} is passed to {@link androidx.media2.player.MediaPlayer},
-         * {@link androidx.media2.player.MediaPlayer} will
-         * close the ParcelFileDescriptor.
-         *
-         * @param pfd the ParcelFileDescriptor for the file you want to play
-         */
-        public Builder(@NonNull ParcelFileDescriptor pfd) {
-            Preconditions.checkNotNull(pfd);
-            mPFD = pfd;
-            mFDOffset = 0;
-            mFDLength = FD_LENGTH_UNKNOWN;
-        }
-
-        /**
-         * Sets the start offset of the file where the data to be played in bytes.
-         * Any negative number for offset is treated as 0.
-         *
-         * @param offset the start offset of the file where the data to be played in bytes
-         * @return this instance for chaining
-         */
-        @NonNull
-        public Builder setFileDescriptorOffset(long offset) {
-            if (offset < 0) {
-                offset = 0;
-            }
-            mFDOffset = offset;
-            return this;
-        }
-
-        /**
-         * Sets the length of the data to be played in bytes.
-         * Any negative number for length is treated as maximum length of the media item.
-         *
-         * @param length the length of the data to be played in bytes
-         * @return this instance for chaining
-         */
-        @NonNull
-        public Builder setFileDescriptorLength(long length) {
-            if (length < 0) {
-                length = FD_LENGTH_UNKNOWN;
-            }
-            mFDLength = length;
-            return this;
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setMetadata(@Nullable MediaMetadata metadata) {
-            return (Builder) super.setMetadata(metadata);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setStartPosition(long position) {
-            return (Builder) super.setStartPosition(position);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setEndPosition(long position) {
-            return (Builder) super.setEndPosition(position);
-        }
-
-        /**
-         * @return A new FileMediaItem with values supplied by the Builder.
-         */
-        @Override
-        @NonNull
-        public FileMediaItem build() {
-            return new FileMediaItem(this);
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/MediaItem.java b/media2/media2-common/src/main/java/androidx/media2/common/MediaItem.java
deleted file mode 100644
index c445435..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/MediaItem.java
+++ /dev/null
@@ -1,355 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.util.Pair;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * A class with information on a single media item with the metadata information. Here are use
- * cases.
- *
- * <ul>
- *   <li>Specify media items to {@link SessionPlayer} for playback.
- *   <li>Share media items across the processes.
- * </ul>
- *
- * <p>Subclasses of the session player may only accept certain subclasses of the media items. Check
- * the player documentation that you're interested in.
- *
- * <p>When it's shared across the processes, we cannot guarantee that they contain the right values
- * because media items are application dependent especially for the metadata.
- *
- * <p>When an object of the {@link MediaItem}'s subclass is sent across the process between {@link
- * androidx.media2.session.MediaSession}/{@link androidx.media2.session.MediaController} or {@link
- * androidx.media2.session.MediaLibraryService.MediaLibrarySession}/ {@link
- * androidx.media2.session.MediaBrowser}, the object will sent as if it's {@link MediaItem}. The
- * recipient cannot get the object with the subclasses' type. This will sanitize process specific
- * information (e.g. {@link java.io.FileDescriptor}, {@link android.content.Context}, etc).
- *
- * <p>This object is thread safe.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize(isCustom = true)
-public class MediaItem extends CustomVersionedParcelable {
-    private static final String TAG = "MediaItem";
-
-    // intentionally less than long.MAX_VALUE.
-    // Declare this first to avoid 'illegal forward reference'.
-    static final long LONG_MAX = 0x7ffffffffffffffL;
-
-    /**
-     * Used when a position is unknown.
-     *
-     * @see #getEndPosition()
-     */
-    public static final long POSITION_UNKNOWN = LONG_MAX;
-
-    @NonParcelField
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    @ParcelField(1)
-    MediaMetadata mMetadata;
-    @ParcelField(2)
-    long mStartPositionMs = 0;
-    @ParcelField(3)
-    long mEndPositionMs = POSITION_UNKNOWN;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    @GuardedBy("mLock")
-    @NonParcelField
-    private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>();
-
-    /**
-     * Used for VersionedParcelable
-     */
-    MediaItem() {
-    }
-
-    /**
-     * Used by {@link MediaItem.Builder} and its subclasses
-     */
-    // Note: Needs to be protected when we want to allow 3rd party player to define customized
-    //       MediaItem.
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem(Builder builder) {
-        this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs);
-    }
-
-    MediaItem(MediaItem item) {
-        this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem(@Nullable MediaMetadata metadata, long startPositionMs, long endPositionMs) {
-        if (startPositionMs > endPositionMs) {
-            throw new IllegalStateException("Illegal start/end position: "
-                    + startPositionMs + " : " + endPositionMs);
-        }
-        if (metadata != null && metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
-            long durationMs = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            if (durationMs != SessionPlayer.UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN
-                    && endPositionMs > durationMs) {
-                throw new IllegalStateException("endPositionMs shouldn't be greater than"
-                        + " duration in the metdata, endPositionMs=" + endPositionMs
-                        + ", durationMs=" + durationMs);
-            }
-        }
-        mMetadata = metadata;
-        mStartPositionMs = startPositionMs;
-        mEndPositionMs = endPositionMs;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
-        synchronized (mLock) {
-            sb.append("{Media Id=").append(getMediaId());
-            sb.append(", mMetadata=").append(mMetadata);
-            sb.append(", mStartPositionMs=").append(mStartPositionMs);
-            sb.append(", mEndPositionMs=").append(mEndPositionMs);
-            sb.append('}');
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Sets metadata. If the metadata is not {@code null}, its id should be matched with this
-     * instance's media id.
-     *
-     * @param metadata metadata to update
-     * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-     */
-    public void setMetadata(@Nullable MediaMetadata metadata) {
-        List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>();
-        synchronized (mLock) {
-            if (mMetadata == metadata) {
-                return;
-            }
-            if (mMetadata != null && metadata != null
-                    && !TextUtils.equals(getMediaId(), metadata.getMediaId())) {
-                Log.w(TAG, "MediaItem's media ID shouldn't be changed");
-                return;
-            }
-            mMetadata = metadata;
-            listeners.addAll(mListeners);
-        }
-
-        for (Pair<OnMetadataChangedListener, Executor> pair : listeners) {
-            final OnMetadataChangedListener listener = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    listener.onMetadataChanged(MediaItem.this, metadata);
-                }
-            });
-        }
-    }
-
-    /**
-     * Gets the metadata of the media.
-     *
-     * @return metadata from the session
-     */
-    @Nullable
-    public MediaMetadata getMetadata() {
-        synchronized (mLock) {
-            return mMetadata;
-        }
-    }
-
-    /**
-     * Return the position in milliseconds at which the playback will start.
-     * @return the position in milliseconds at which the playback will start
-     */
-    public long getStartPosition() {
-        return mStartPositionMs;
-    }
-
-    /**
-     * Return the position in milliseconds at which the playback will end.
-     * {@link #POSITION_UNKNOWN} means ending at the end of source content.
-     * @return the position in milliseconds at which the playback will end
-     */
-    public long getEndPosition() {
-        return mEndPositionMs;
-    }
-
-    /**
-     * Gets the media id for this item. If it's not {@code null}, it's a persistent unique key
-     * for the underlying media content.
-     *
-     * @return media Id from the session
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @Nullable
-    public String getMediaId() {
-        synchronized (mLock) {
-            return mMetadata != null
-                    ? mMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) : null;
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void addOnMetadataChangedListener(
-            Executor executor, OnMetadataChangedListener listener) {
-        synchronized (mLock) {
-            for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) {
-                if (pair.first == listener) {
-                    return;
-                }
-            }
-            mListeners.add(new Pair<>(listener, executor));
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void removeOnMetadataChangedListener(OnMetadataChangedListener listener) {
-        synchronized (mLock) {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                if (mListeners.get(i).first == listener) {
-                    mListeners.remove(i);
-                    return;
-                }
-            }
-        }
-    }
-
-    /**
-     * Builder for {@link MediaItem}.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static class Builder {
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-                MediaMetadata mMetadata;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        long mStartPositionMs = 0;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        long mEndPositionMs = POSITION_UNKNOWN;
-
-        /**
-         * Default constructor
-         */
-        public Builder() {
-        }
-
-        /**
-         * Set the metadata of this instance. {@code null} for unset.
-         *
-         * @param metadata metadata
-         * @return this instance for chaining
-         */
-        @NonNull
-        public Builder setMetadata(@Nullable MediaMetadata metadata) {
-            mMetadata = metadata;
-            return this;
-        }
-
-        /**
-         * Sets the start position in milliseconds at which the playback will start.
-         * Any negative number is treated as 0.
-         *
-         * @param position the start position in milliseconds at which the playback will start
-         * @return this instance for chaining
-         */
-        @NonNull
-        public Builder setStartPosition(long position) {
-            if (position < 0) {
-                position = 0;
-            }
-            mStartPositionMs = position;
-            return this;
-        }
-
-        /**
-         * Sets the end position in milliseconds at which the playback will end.
-         * Any negative number is treated as maximum length of the media item.
-         *
-         * @param position the end position in milliseconds at which the playback will end
-         * @return this instance for chaining
-         */
-        @NonNull
-        public Builder setEndPosition(long position) {
-            if (position < 0) {
-                position = POSITION_UNKNOWN;
-            }
-            mEndPositionMs = position;
-            return this;
-        }
-
-        /**
-         * Build {@link MediaItem}.
-         *
-         * @return a new {@link MediaItem}.
-         */
-        @NonNull
-        public MediaItem build() {
-            return new MediaItem(this);
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public interface OnMetadataChangedListener {
-        /**
-         * Called when a media item's metadata is changed.
-         */
-        void onMetadataChanged(@NonNull MediaItem item,
-                @Nullable MediaMetadata metadata);
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    public void onPreParceling(boolean isStream) {
-        if (getClass() != MediaItem.class) {
-            throw new RuntimeException("MediaItem's subclasses shouldn't be parcelized.");
-        }
-        super.onPreParceling(isStream);
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/MediaMetadata.java b/media2/media2-common/src/main/java/androidx/media2/common/MediaMetadata.java
deleted file mode 100644
index 1959dc1..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/MediaMetadata.java
+++ /dev/null
@@ -1,1311 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringDef;
-import androidx.collection.ArrayMap;
-import androidx.core.graphics.BitmapCompat;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Contains metadata about an item, such as the title, artist, etc. This is optional, but you'd
- * better to provide this as much as possible when you're using media widget and/or session APIs.
- *
- * <p>The media widget components build its UI based on the metadata here. For an example, {@link
- * androidx.media2.widget.MediaControlView} will show title from the metadata.
- *
- * <p>The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require some
- * metadata values when it provides {@link MediaItem}s to {@link
- * androidx.media2.session.MediaBrowser}.
- *
- * <p>Topics covered here:
- *
- * <ol>
- *   <li><a href="#MediaId">Media ID</a>
- *   <li><a href="#Browsable">Browsable type</a>
- *   <li><a href="#Playable">Playable</a>
- *   <li><a href="#Duration">Duration</a>
- *   <li><a href="#UserRating">User rating</a>
- * </ol>
- *
- * <h3 id="MediaId">{@link MediaMetadata#METADATA_KEY_MEDIA_ID Media ID}</h3>
- *
- * <p>If set, the media ID must be the persistent key for the underlying media contents, so {@link
- * androidx.media2.session.MediaController} and {@link androidx.media2.session.MediaBrowser} can
- * store the information and reuse it later. Some APIs requires a media ID (e.g. {@link
- * androidx.media2.session.MediaController#setRating}, so you'd better specify one.
- *
- * <p>Typical example of using media ID is the URI of the contents, but use it with the caution
- * because the metadata is shared across the process in plain text.
- *
- * <p>The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require it
- * for the library root, so {@link androidx.media2.session.MediaBrowser} can call subsequent {@link
- * androidx.media2.session.MediaBrowser#getChildren} with the ID.
- *
- * <h3 id="Browsable">{@link MediaMetadata#METADATA_KEY_BROWSABLE Browsable type}</h3>
- *
- * <p>Browsable defines whether the media item has children and type of children if any. With this,
- * {@link androidx.media2.session.MediaBrowser} can know whether the subsequent {@link
- * androidx.media2.session.MediaBrowser#getChildren} would successfully run.
- *
- * <p>The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require the
- * explicit browsable type for the media items returned by the {@link
- * androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback}.
- *
- * <h3 id="Playable">{@link MediaMetadata#METADATA_KEY_PLAYABLE Playable type}</h3>
- *
- * <p>Playable defines whether the media item can be played or not. It may be possible for a
- * playlist to contain a media item which isn't playable in order to show a disabled media item.
- *
- * <p>The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require the
- * explicit playable value for the media items returned by the {@link
- * androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback}.
- *
- * <h3 id="Duration">{@link MediaMetadata#METADATA_KEY_DURATION Duration}</h3>
- *
- * The duration is the length of the contents. The {@link androidx.media2.session.MediaController}
- * can only get the duration through the metadata. This tells when would the playback ends, and also
- * tells about the allowed range of {@link androidx.media2.session.MediaController#seekTo(long)}.
- *
- * <p>If it's not set by developer, {@link androidx.media2.session.MediaSession} would update the
- * duration in the metadata with the {@link SessionPlayer#getDuration()}.
- *
- * <h3 id="UserRating">{@link MediaMetadata#METADATA_KEY_USER_RATING User rating}</h3>
- *
- * <p>Prefer to have unrated user rating instead of {@code null}, so {@link
- * androidx.media2.session.MediaController} can know the possible user rating type for calling
- * {@link androidx.media2.session.MediaController#setRating(String, Rating)}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-// New version of MediaMetadata with following changes
-//   - Don't implement Parcelable for updatable support.
-//   - Also support MediaDescription features. MediaDescription is deprecated instead because
-//     it was insufficient for controller to display media contents. (e.g. duration is missing)
-@Deprecated
-@VersionedParcelize(isCustom = true)
-public final class MediaMetadata extends CustomVersionedParcelable {
-    private static final String TAG = "MediaMetadata";
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the title of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_TITLE = android.media.MediaMetadata.METADATA_KEY_TITLE;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the artist of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_ARTIST =
-            android.media.MediaMetadata.METADATA_KEY_ARTIST;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the
-     * duration of the media in ms. A negative duration indicates that the duration is unknown
-     * (or infinite).
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_DURATION =
-            android.media.MediaMetadata.METADATA_KEY_DURATION;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the album title for the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_ALBUM =
-            android.media.MediaMetadata.METADATA_KEY_ALBUM;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the author of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_AUTHOR =
-            android.media.MediaMetadata.METADATA_KEY_AUTHOR;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the writer of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_WRITER =
-            android.media.MediaMetadata.METADATA_KEY_WRITER;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the composer of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_COMPOSER =
-            android.media.MediaMetadata.METADATA_KEY_COMPOSER;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the compilation status of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_COMPILATION =
-            android.media.MediaMetadata.METADATA_KEY_COMPILATION;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the date the media was created or published.
-     * The format is unspecified but RFC 3339 is recommended.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_DATE = android.media.MediaMetadata.METADATA_KEY_DATE;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the year
-     * the media was created or published.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_YEAR = android.media.MediaMetadata.METADATA_KEY_YEAR;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the genre of the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_GENRE = android.media.MediaMetadata.METADATA_KEY_GENRE;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the
-     * track number for the media.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_TRACK_NUMBER =
-            android.media.MediaMetadata.METADATA_KEY_TRACK_NUMBER;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the
-     * number of tracks in the media's original source.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_NUM_TRACKS =
-            android.media.MediaMetadata.METADATA_KEY_NUM_TRACKS;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the
-     * disc number for the media's original source.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_DISC_NUMBER =
-            android.media.MediaMetadata.METADATA_KEY_DISC_NUMBER;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the artist for the album of the media's original source.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_ALBUM_ARTIST =
-            android.media.MediaMetadata.METADATA_KEY_ALBUM_ARTIST;
-
-    /**
-     * The metadata key for a {@link Bitmap} typed value to retrieve the information about the
-     * artwork for the media.
-     * The artwork should be relatively small and may be scaled down if it is too large.
-     * For higher resolution artwork, {@link #METADATA_KEY_ART_URI} should be used instead.
-     *
-     * @see Builder#putBitmap(String, Bitmap)
-     * @see #getBitmap(String)
-     */
-    public static final String METADATA_KEY_ART = android.media.MediaMetadata.METADATA_KEY_ART;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about Uri of the artwork for the media.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_ART_URI =
-            android.media.MediaMetadata.METADATA_KEY_ART_URI;
-
-    /**
-     * The metadata key for a {@link Bitmap} typed value to retrieve the information about the
-     * artwork for the album of the media's original source.
-     * The artwork should be relatively small and may be scaled down if it is too large.
-     * For higher resolution artwork, {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
-     *
-     * @see Builder#putBitmap(String, Bitmap)
-     * @see #getBitmap(String)
-     */
-    public static final String METADATA_KEY_ALBUM_ART =
-            android.media.MediaMetadata.METADATA_KEY_ALBUM_ART;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the Uri of the artwork for the album of the media's original source.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_ALBUM_ART_URI =
-            android.media.MediaMetadata.METADATA_KEY_ALBUM_ART_URI;
-
-    /**
-     * The metadata key for a {@link Rating} typed value to retrieve the information about the
-     * user's rating for the media. Prefer to have unrated user rating instead of {@code null}, so
-     * {@link androidx.media2.session.MediaController} can know the possible user rating type.
-     *
-     * @see Builder#putRating(String, Rating)
-     * @see #getRating(String)
-     * @see <a href="#UserRating">User rating</a>
-     */
-    public static final String METADATA_KEY_USER_RATING =
-            android.media.MediaMetadata.METADATA_KEY_USER_RATING;
-
-    /**
-     * The metadata key for a {@link Rating} typed value to retrieve the information about the
-     * overall rating for the media.
-     *
-     * @see Builder#putRating(String, Rating)
-     * @see #getRating(String)
-     */
-    public static final String METADATA_KEY_RATING =
-            android.media.MediaMetadata.METADATA_KEY_RATING;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the title that is suitable for display to the user.
-     * It will generally be the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
-     * When displaying media described by this metadata, this should be preferred if present.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_DISPLAY_TITLE =
-            android.media.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the subtitle that is suitable for display to the user.
-     * When displaying a second line for media described by this metadata, this should be preferred
-     * to other fields if present.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_DISPLAY_SUBTITLE =
-            android.media.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the description that is suitable for display to the user.
-     * When displaying more information for media described by this metadata,
-     * this should be preferred to other fields if present.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
-            android.media.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION;
-
-    /**
-     * The metadata key for a {@link Bitmap} typed value to retrieve the information about the icon
-     * or thumbnail that is suitable for display to the user.
-     * When displaying an icon for media described by this metadata, this should be preferred to
-     * other fields if present.
-     * <p>
-     * The icon should be relatively small and may be scaled down if it is too large.
-     * For higher resolution artwork, {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
-     *
-     * @see Builder#putBitmap(String, Bitmap)
-     * @see #getBitmap(String)
-     */
-    public static final String METADATA_KEY_DISPLAY_ICON =
-            android.media.MediaMetadata.METADATA_KEY_DISPLAY_ICON;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the Uri of icon or thumbnail that is suitable for display to the user.
-     * When displaying more information for media described by this metadata, the
-     * display description should be preferred to other fields when present.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_DISPLAY_ICON_URI =
-            android.media.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the media ID of the content. This value is specific to the
-     * service providing the content. If used, this should be a persistent key for the underlying
-     * content. This ID is used by {@link androidx.media2.session.MediaController} and
-     * {@link androidx.media2.session.MediaBrowser}.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     * @see <a href="#MediaID">Media ID</a>
-     */
-    public static final String METADATA_KEY_MEDIA_ID =
-            android.media.MediaMetadata.METADATA_KEY_MEDIA_ID;
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the Uri of the content. This value is specific to the service providing the
-     * content.
-     *
-     * @see Builder#putText(String, CharSequence)
-     * @see Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    public static final String METADATA_KEY_MEDIA_URI =
-            android.media.MediaMetadata.METADATA_KEY_MEDIA_URI;
-
-    /**
-     * The metadata key for a {@link Float} typed value to retrieve the information about the
-     * radio frequency if this metadata represents radio content.
-     *
-     * @see Builder#putFloat(String, float)
-     * @see #getFloat(String)
-     */
-    @RestrictTo(LIBRARY)
-    public static final String METADATA_KEY_RADIO_FREQUENCY =
-            "androidx.media2.metadata.RADIO_FREQUENCY";
-
-    /**
-     * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
-     * information about the radio program name if this metadata represents radio content.
-     *
-     * @see MediaMetadata.Builder#putText(String, CharSequence)
-     * @see MediaMetadata.Builder#putString(String, String)
-     * @see #getText(String)
-     * @see #getString(String)
-     */
-    @RestrictTo(LIBRARY)
-    public static final String METADATA_KEY_RADIO_PROGRAM_NAME =
-            "androidx.media2.metadata.RADIO_PROGRAM_NAME";
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the type
-     * of browsable. It should be one of the following:
-     * <ul>
-     * <li>{@link #BROWSABLE_TYPE_NONE}</li>
-     * <li>{@link #BROWSABLE_TYPE_MIXED}</li>
-     * <li>{@link #BROWSABLE_TYPE_TITLES}</li>
-     * <li>{@link #BROWSABLE_TYPE_ALBUMS}</li>
-     * <li>{@link #BROWSABLE_TYPE_ARTISTS}</li>
-     * <li>{@link #BROWSABLE_TYPE_GENRES}</li>
-     * <li>{@link #BROWSABLE_TYPE_PLAYLISTS}</li>
-     * <li>{@link #BROWSABLE_TYPE_YEARS}</li>
-     * </ul>
-     * <p>
-     * The values other than {@link #BROWSABLE_TYPE_NONE} mean that the media item has children.[
-     * <p>
-     * This matches with the bluetooth folder type of the media specified in the section 6.10.2.2 of
-     * the Bluetooth AVRCP 1.5.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     * @see <a href="#Browsable">Browsable</a>
-     */
-    public static final String METADATA_KEY_BROWSABLE =
-            "androidx.media2.metadata.BROWSABLE";
-
-    /**
-     * The type of browsable for non-browsable media item.
-     */
-    public static final long BROWSABLE_TYPE_NONE = -1;
-
-    /**
-     * The type of browsable that is unknown or contains media items of mixed types.
-     * <p>
-     * This value matches with the folder type 'Mixed' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_MIXED = 0;
-
-    /**
-     * The type of browsable that only contains playable media items.
-     * <p>
-     * This value matches with the folder type 'Titles' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_TITLES = 1;
-
-    /**
-     * The type of browsable that contains browsable items categorized by album.
-     * <p>
-     * This value matches with the folder type 'Albums' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_ALBUMS = 2;
-
-    /**
-     * The type of browsable that contains browsable items categorized by artist.
-     * <p>
-     * This value matches with the folder type 'Artists' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_ARTISTS = 3;
-
-    /**
-     * The type of browsable that contains browsable items categorized by genre.
-     * <p>
-     * This value matches with the folder type 'Genres' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_GENRES = 4;
-
-    /**
-     * The type of browsable that contains browsable items categorized by playlist.
-     * <p>
-     * This value matches with the folder type 'Playlists' as specified in the section 6.10.2.2 of
-     * the Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_PLAYLISTS = 5;
-
-    /**
-     * The type of browsable that contains browsable items categorized by year.
-     * <p>
-     * This value matches with the folder type 'Years' as specified in the section 6.10.2.2 of the
-     * Bluetooth AVRCP 1.5.
-     */
-    public static final long BROWSABLE_TYPE_YEARS = 6;
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about whether
-     * the media is playable. A value of 0 indicates it is not a playable item.
-     * A value of 1 or non-zero indicates it is playable.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     * @see <a href="#Playable">Playable</a>
-     */
-    public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about whether
-     * the media is an advertisement. A value of 0 indicates it is not an advertisement.
-     * A value of 1 or non-zero indicates it is an advertisement.
-     * If not specified, this value is set to 0 by default.
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_ADVERTISEMENT =
-            "androidx.media2.metadata.ADVERTISEMENT";
-
-    /**
-     * The metadata key for a {@link Long} typed value to retrieve the information about the
-     * download status of the media which will be used for later offline playback. It should be
-     * one of the following:
-     *
-     * <ul>
-     * <li>{@link #STATUS_NOT_DOWNLOADED}</li>
-     * <li>{@link #STATUS_DOWNLOADING}</li>
-     * <li>{@link #STATUS_DOWNLOADED}</li>
-     * </ul>
-     *
-     * @see Builder#putLong(String, long)
-     * @see #getLong(String)
-     */
-    public static final String METADATA_KEY_DOWNLOAD_STATUS =
-            "androidx.media2.metadata.DOWNLOAD_STATUS";
-
-    /**
-     * The status value to indicate the media item is not downloaded.
-     *
-     * @see #METADATA_KEY_DOWNLOAD_STATUS
-     */
-    public static final long STATUS_NOT_DOWNLOADED = 0;
-
-    /**
-     * The status value to indicate the media item is being downloaded.
-     *
-     * @see #METADATA_KEY_DOWNLOAD_STATUS
-     */
-    public static final long STATUS_DOWNLOADING = 1;
-
-    /**
-     * The status value to indicate the media item is downloaded for later offline playback.
-     *
-     * @see #METADATA_KEY_DOWNLOAD_STATUS
-     */
-    public static final long STATUS_DOWNLOADED = 2;
-
-    /**
-     * A {@link Bundle} extra.
-     */
-    public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
-            METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
-            METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
-            METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
-            METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
-            METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI, METADATA_KEY_RADIO_PROGRAM_NAME})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TextKey {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
-            METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BROWSABLE,
-            METADATA_KEY_PLAYABLE, METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LongKey {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BitmapKey {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RatingKey {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_RADIO_FREQUENCY})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FloatKey {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @StringDef({METADATA_KEY_EXTRAS})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BundleKey {}
-
-    static final int METADATA_TYPE_LONG = 0;
-    static final int METADATA_TYPE_TEXT = 1;
-    static final int METADATA_TYPE_BITMAP = 2;
-    static final int METADATA_TYPE_RATING = 3;
-    static final int METADATA_TYPE_FLOAT = 4;
-    static final int METADATA_TYPE_BUNDLE = 5;
-    static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
-
-    static {
-        METADATA_KEYS_TYPE = new ArrayMap<>();
-        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_FREQUENCY, METADATA_TYPE_FLOAT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_PROGRAM_NAME, METADATA_TYPE_TEXT);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_BROWSABLE, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_PLAYABLE, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_EXTRAS, METADATA_TYPE_BUNDLE);
-    }
-
-    // Parceled via mParcelableNoBitmapBundle for non-Bitmap values, and mBitmapListSlice for Bitmap
-    // values.
-    @NonParcelField
-    Bundle mBundle;
-    // For parceling mBundle's non-Bitmap values. Should be only used by onPreParceling() and
-    // onPostParceling().
-    @ParcelField(1)
-    Bundle mParcelableWithoutBitmapBundle;
-
-    // For parceling mBundle's Bitmap values. Should be only used by onPreParceling() and
-    // onPostParceling().
-    @ParcelField(2)
-    ParcelImplListSlice mBitmapListSlice;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Used for VersionedParcelable
-     */
-    MediaMetadata() {
-    }
-
-    MediaMetadata(Bundle bundle) {
-        mBundle = new Bundle(bundle);
-        mBundle.setClassLoader(MediaMetadata.class.getClassLoader());
-    }
-
-    /**
-     * Returns true if the given key is contained in the metadata
-     *
-     * @param key a String key
-     * @return true if the key exists in this metadata, false otherwise
-     */
-    public boolean containsKey(@NonNull String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        return mBundle.containsKey(key);
-    }
-
-    /**
-     * Returns the value associated with the given key, or null if no mapping of
-     * the desired type exists for the given key or a null value is explicitly
-     * associated with the key.
-     *
-     * @param key The key the value is stored under
-     * @return a CharSequence value, or null
-     */
-    public @Nullable CharSequence getText(@NonNull @TextKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        return mBundle.getCharSequence(key);
-    }
-
-    /**
-     * Returns the media id, or {@code null} if the id doesn't exist.
-     *<p>
-     * This is equivalent to the {@link #getString(String)} with the {@link #METADATA_KEY_MEDIA_ID}.
-     *
-     * @return media id. Can be {@code null}
-     * @see #METADATA_KEY_MEDIA_ID
-     */
-    // TODO(jaewan): Hide -- no setMediaId()
-    public @Nullable String getMediaId() {
-        return getString(METADATA_KEY_MEDIA_ID);
-    }
-
-    /**
-     * Returns the value associated with the given key, or null if no mapping of
-     * the desired type exists for the given key or a null value is explicitly
-     * associated with the key.
-     *
-     * @param key The key the value is stored under
-     * @return a String value, or null
-     */
-    public @Nullable String getString(@NonNull @TextKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        CharSequence text = mBundle.getCharSequence(key);
-        if (text != null) {
-            return text.toString();
-        }
-        return null;
-    }
-
-    /**
-     * Returns the value associated with the given key, or 0L if no long exists
-     * for the given key.
-     *
-     * @param key The key the value is stored under
-     * @return a long value
-     */
-    public long getLong(@NonNull @LongKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        return mBundle.getLong(key, 0);
-    }
-
-    /**
-     * Return a {@link Rating} for the given key or null if no rating exists for
-     * the given key.
-     * <p>
-     * For the {@link #METADATA_KEY_USER_RATING}, A {@code null} return value means that user rating
-     * cannot be set by {@link androidx.media2.session.MediaController}.
-     *
-     * @param key The key the value is stored under
-     * @return A {@link Rating} or {@code null}
-     */
-    public @Nullable Rating getRating(@NonNull @RatingKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        Rating rating = null;
-        try {
-            rating = ParcelUtils.getVersionedParcelable(mBundle, key);
-        } catch (Exception e) {
-            // ignore, value was not a rating
-            Log.w(TAG, "Failed to retrieve a key as Rating.", e);
-        }
-        return rating;
-    }
-
-    /**
-     * Return the value associated with the given key, or 0.0f if no long exists
-     * for the given key.
-     *
-     * @param key The key the value is stored under
-     * @return a float value
-     */
-    public float getFloat(@NonNull @FloatKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        return mBundle.getFloat(key);
-    }
-
-    /**
-     * Return a {@link Bitmap} for the given key or null if no bitmap exists for
-     * the given key.
-     *
-     * @param key The key the value is stored under
-     * @return A {@link Bitmap} or null
-     */
-    @SuppressWarnings("deprecation")
-    public @Nullable Bitmap getBitmap(@NonNull @BitmapKey String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        Bitmap bmp = null;
-        try {
-            bmp = mBundle.getParcelable(key);
-        } catch (Exception e) {
-            // ignore, value was not a bitmap
-            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
-        }
-        return bmp;
-    }
-
-    /**
-     * Get the extra {@link Bundle} from the metadata object.
-     *
-     * @return A {@link Bundle} or {@code null}
-     */
-    public @Nullable Bundle getExtras() {
-        try {
-            return mBundle.getBundle(METADATA_KEY_EXTRAS);
-        } catch (Exception e) {
-            // ignore, value was not an bundle
-            Log.w(TAG, "Failed to retrieve an extra");
-        }
-        return null;
-    }
-
-    /**
-     * Get the number of fields in this metadata.
-     *
-     * @return The number of fields in the metadata.
-     */
-    public int size() {
-        return mBundle.size();
-    }
-
-    /**
-     * Returns a Set containing the Strings used as keys in this metadata.
-     *
-     * @return a Set of String keys
-     */
-    public @NonNull Set<String> keySet() {
-        return mBundle.keySet();
-    }
-
-    @Override
-    public String toString() {
-        return mBundle.toString();
-    }
-
-    /**
-     * Gets the object which matches the given key in the backing bundle.
-     *
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @SuppressWarnings("deprecation")
-    public @Nullable Object getObject(@NonNull String key) {
-        if (key == null) {
-            throw new NullPointerException("key shouldn't be null");
-        }
-        return mBundle.get(key);
-    }
-
-    /**
-     */
-    @Override
-    @RestrictTo(LIBRARY)
-    // mBundle is effectively final.
-    @SuppressWarnings({"SynchronizeOnNonFinalField", "deprecation"})
-    public void onPreParceling(boolean isStream) {
-        synchronized (mBundle) {
-            if (mParcelableWithoutBitmapBundle == null) {
-                mParcelableWithoutBitmapBundle = new Bundle(mBundle);
-                List<ParcelImpl> parcelImplList = new ArrayList<>();
-                for (String key : mBundle.keySet()) {
-                    Object value = mBundle.get(key);
-                    if (!(value instanceof Bitmap)) {
-                        // Note: Null bitmap is sent through mParcelableNoBitmapBundle.
-                        continue;
-                    }
-                    Bitmap bitmap = (Bitmap) value;
-                    parcelImplList.add(MediaParcelUtils.toParcelable(new BitmapEntry(key, bitmap)));
-                    mParcelableWithoutBitmapBundle.remove(key);
-                }
-                mBitmapListSlice = new ParcelImplListSlice(parcelImplList);
-            }
-        }
-    }
-
-    /**
-     */
-    @Override
-    @RestrictTo(LIBRARY)
-    public void onPostParceling() {
-        mBundle = (mParcelableWithoutBitmapBundle != null)
-                ? mParcelableWithoutBitmapBundle : new Bundle();
-        if (mBitmapListSlice != null) {
-            List<ParcelImpl> parcelImplList = mBitmapListSlice.getList();
-            for (ParcelImpl parcelImpl : parcelImplList) {
-                BitmapEntry entry = MediaParcelUtils.fromParcelable(parcelImpl);
-                mBundle.putParcelable(entry.getKey(), entry.getBitmap());
-            }
-        }
-    }
-
-    /**
-     * Use to build MediaMetadatax objects. The system defined metadata keys must use the
-     * appropriate data type.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder {
-        final Bundle mBundle;
-
-        /**
-         * Create an empty Builder. Any field that should be included in the
-         * {@link MediaMetadata} must be added.
-         */
-        public Builder() {
-            mBundle = new Bundle();
-        }
-
-        /**
-         * Create a Builder using a {@link MediaMetadata} instance to set the
-         * initial values. All fields in the source metadata will be included in
-         * the new metadata. Fields can be overwritten by adding the same key.
-         *
-         * @param source
-         */
-        public Builder(@NonNull MediaMetadata source) {
-            mBundle = new Bundle(source.mBundle);
-        }
-
-        /**
-         * Only for the backward compatibility.
-         *
-         * @param bundle
-         */
-        Builder(Bundle bundle) {
-            mBundle = new Bundle(bundle);
-        }
-
-        /**
-         * Put a CharSequence value into the metadata. Custom keys may be used,
-         * but if the METADATA_KEYs defined in this class are used they may only
-         * be one of the following:
-         * <ul>
-         * <li>{@link #METADATA_KEY_TITLE}</li>
-         * <li>{@link #METADATA_KEY_ARTIST}</li>
-         * <li>{@link #METADATA_KEY_ALBUM}</li>
-         * <li>{@link #METADATA_KEY_AUTHOR}</li>
-         * <li>{@link #METADATA_KEY_WRITER}</li>
-         * <li>{@link #METADATA_KEY_COMPOSER}</li>
-         * <li>{@link #METADATA_KEY_COMPILATION}</li>
-         * <li>{@link #METADATA_KEY_DATE}</li>
-         * <li>{@link #METADATA_KEY_GENRE}</li>
-         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
-         * <li>{@link #METADATA_KEY_ART_URI}</li>
-         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
-         * <li>{@link #METADATA_KEY_MEDIA_ID}</li>
-         * <li>{@link #METADATA_KEY_MEDIA_URI}</li>
-         * </ul>
-         *
-         * @param key The key for referencing this value
-         * @param value The CharSequence value to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putText(@NonNull @TextKey String key,
-                @Nullable CharSequence value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a CharSequence");
-                }
-            }
-            mBundle.putCharSequence(key, value);
-            return this;
-        }
-
-        /**
-         * Put a String value into the metadata. Custom keys may be used, but if
-         * the METADATA_KEYs defined in this class are used they may only be one
-         * of the following:
-         * <ul>
-         * <li>{@link #METADATA_KEY_TITLE}</li>
-         * <li>{@link #METADATA_KEY_ARTIST}</li>
-         * <li>{@link #METADATA_KEY_ALBUM}</li>
-         * <li>{@link #METADATA_KEY_AUTHOR}</li>
-         * <li>{@link #METADATA_KEY_WRITER}</li>
-         * <li>{@link #METADATA_KEY_COMPOSER}</li>
-         * <li>{@link #METADATA_KEY_COMPILATION}</li>
-         * <li>{@link #METADATA_KEY_DATE}</li>
-         * <li>{@link #METADATA_KEY_GENRE}</li>
-         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
-         * <li>{@link #METADATA_KEY_ART_URI}</li>
-         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
-         * <li>{@link #METADATA_KEY_MEDIA_ID}</li>
-         * <li>{@link #METADATA_KEY_MEDIA_URI}</li>
-         * </ul>
-         *
-         * @param key The key for referencing this value
-         * @param value The String value to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putString(@NonNull @TextKey String key,
-                @Nullable String value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a String");
-                }
-            }
-            mBundle.putCharSequence(key, value);
-            return this;
-        }
-
-        /**
-         * Put a long value into the metadata. Custom keys may be used, but if
-         * the METADATA_KEYs defined in this class are used they may only be one
-         * of the following:
-         * <ul>
-         * <li>{@link #METADATA_KEY_DURATION}</li>
-         * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
-         * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
-         * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
-         * <li>{@link #METADATA_KEY_YEAR}</li>
-         * <li>{@link #METADATA_KEY_BROWSABLE}</li>
-         * <li>{@link #METADATA_KEY_PLAYABLE}</li>
-         * <li>{@link #METADATA_KEY_ADVERTISEMENT}</li>
-         * <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}</li>
-         * </ul>
-         *
-         * @param key The key for referencing this value
-         * @param value The String value to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putLong(@NonNull @LongKey String key, long value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a long");
-                }
-            }
-            mBundle.putLong(key, value);
-            return this;
-        }
-
-        /**
-         * Put a {@link Rating} into the metadata. Custom keys may be used, but
-         * if the METADATA_KEYs defined in this class are used they may only be
-         * one of the following:
-         * <ul>
-         * <li>{@link #METADATA_KEY_RATING}</li>
-         * <li>{@link #METADATA_KEY_USER_RATING}</li>
-         * </ul>
-         *
-         * @param key The key for referencing this value
-         * @param value The String value to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putRating(@NonNull @RatingKey String key,
-                @Nullable Rating value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a Rating");
-                }
-            }
-            ParcelUtils.putVersionedParcelable(mBundle, key, value);
-            return this;
-        }
-
-        /**
-         * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
-         * if the METADATA_KEYs defined in this class are used they may only be
-         * one of the following:
-         * <ul>
-         * <li>{@link #METADATA_KEY_ART}</li>
-         * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
-         * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
-         * </ul>
-         * Large bitmaps may be scaled down when it is passed to the other process.
-         * To pass full resolution images {@link Uri Uris} should be used with
-         * {@link #putString}.
-         *
-         * @param key The key for referencing this value
-         * @param value The Bitmap to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putBitmap(@NonNull @BitmapKey String key,
-                @Nullable Bitmap value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a Bitmap");
-                }
-            }
-            mBundle.putParcelable(key, value);
-            return this;
-        }
-
-        /**
-         * Put a float value into the metadata. Custom keys may be used.
-         *
-         * @param key The key for referencing this value
-         * @param value The float value to store
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder putFloat(@NonNull @LongKey String key, float value) {
-            if (key == null) {
-                throw new NullPointerException("key shouldn't be null");
-            }
-            if (METADATA_KEYS_TYPE.containsKey(key)) {
-                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_FLOAT) {
-                    throw new IllegalArgumentException("The " + key
-                            + " key cannot be used to put a float");
-                }
-            }
-            mBundle.putFloat(key, value);
-            return this;
-        }
-
-        /**
-         * Set a bundle of extras.
-         *
-         * @param extras The extras to include with this description or null.
-         * @return The Builder to allow chaining
-         */
-        public @NonNull Builder setExtras(@Nullable Bundle extras) {
-            mBundle.putBundle(METADATA_KEY_EXTRAS, extras);
-            return this;
-        }
-
-        /**
-         * Creates a {@link MediaMetadata} instance with the specified fields.
-         *
-         * @return The new MediaMetadatax instance
-         */
-        public @NonNull MediaMetadata build() {
-            return new MediaMetadata(mBundle);
-        }
-    }
-
-    /**
-     * Stores a bitmap and the matching key. Used for sending bitmaps to other process one-by-one.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    @VersionedParcelize
-    static final class BitmapEntry implements VersionedParcelable {
-        static final int BITMAP_SIZE_LIMIT_IN_BYTES = 256 * 1024; // 256 KB
-
-        @ParcelField(1)
-        String mKey;
-
-        @ParcelField(2)
-        Bitmap mBitmap;
-
-        // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-        /**
-         * Used for VersionedParcelable
-         */
-        BitmapEntry() {
-        }
-
-        BitmapEntry(@NonNull String key, @NonNull Bitmap bitmap) {
-            mKey = key;
-            mBitmap = bitmap;
-
-            // Scale bitmap if it is large.
-            final int sizeInBytes = getBitmapSizeInBytes(mBitmap);
-            if (sizeInBytes > BITMAP_SIZE_LIMIT_IN_BYTES) {
-                int oldWidth = bitmap.getWidth();
-                int oldHeight = bitmap.getHeight();
-
-                double scaleFactor = Math.sqrt(BITMAP_SIZE_LIMIT_IN_BYTES / (double) sizeInBytes);
-                int newWidth = (int) (oldWidth * scaleFactor);
-                int newHeight = (int) (oldHeight * scaleFactor);
-                Log.i(TAG, "Scaling large bitmap of " + oldWidth + "x" + oldHeight + " into "
-                        + newWidth + "x" + newHeight);
-                mBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
-            }
-        }
-
-        String getKey() {
-            return mKey;
-        }
-
-        Bitmap getBitmap() {
-            return mBitmap;
-        }
-
-        private int getBitmapSizeInBytes(Bitmap bitmap) {
-            return BitmapCompat.getAllocationByteCount(bitmap);
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/MediaParcelUtils.java b/media2/media2-common/src/main/java/androidx/media2/common/MediaParcelUtils.java
deleted file mode 100644
index 5e4d2ee..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/MediaParcelUtils.java
+++ /dev/null
@@ -1,119 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-import androidx.versionedparcelable.VersionedParcelable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- */
-@RestrictTo(LIBRARY_GROUP)
-public class MediaParcelUtils {
-    public static final String TAG = "MediaParcelUtils";
-
-    /**
-     * Media2 version of {@link ParcelUtils#toParcelable(VersionedParcelable)}.
-     * <p>
-     * This sanitizes {@link MediaItem}'s subclass information.
-     *
-     * @param item
-     * @return
-     */
-    @NonNull
-    public static ParcelImpl toParcelable(@Nullable VersionedParcelable item) {
-        if (item instanceof MediaItem) {
-            return new MediaItemParcelImpl((MediaItem) item);
-        }
-        return (ParcelImpl) ParcelUtils.toParcelable(item);
-    }
-
-    /**
-     * Helper method for converting a list of VersionedParcelable items into ParcelImpl items.
-     *
-     * @param items
-     * @return
-     */
-    @NonNull
-    public static List<ParcelImpl> toParcelableList(
-            @NonNull List<? extends VersionedParcelable> items) {
-        List<ParcelImpl> list = new ArrayList<>();
-        for (int i = 0; i < items.size(); i++) {
-            list.add(toParcelable(items.get(i)));
-        }
-        return list;
-    }
-
-    /**
-     * Media2 version of {@link ParcelUtils#fromParcelable(Parcelable)}.
-     */
-    @SuppressWarnings("TypeParameterUnusedInFormals")
-    @Nullable
-    public static <T extends VersionedParcelable> T fromParcelable(@NonNull ParcelImpl p) {
-        return ParcelUtils.fromParcelable(p);
-    }
-
-    /**
-     * Helper method for converting a list of ParcelImpl items into VersionedParcelable items
-     *
-     * @param parcelList
-     * @return
-     */
-    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
-    @NonNull
-    public static <T extends VersionedParcelable> List<T> fromParcelableList(
-            @NonNull List<ParcelImpl> parcelList) {
-        List<T> list = new ArrayList<>();
-        for (int i = 0; i < parcelList.size(); i++) {
-            list.add((T) fromParcelable(parcelList.get(i)));
-        }
-        return list;
-    }
-
-    private MediaParcelUtils() {
-    }
-
-    private static class MediaItemParcelImpl extends ParcelImpl {
-        private final MediaItem mItem;
-        MediaItemParcelImpl(MediaItem item) {
-            // Up-cast (possibly MediaItem's subclass object) item to MediaItem for the
-            // writeToParcel(). The copied media item will be only used when it's sent across the
-            // process.
-            super(new MediaItem(item));
-
-            // Keeps the original copy for local binder to send the original item.
-            // When local binder is used (i.e. binder call happens in a single process),
-            // writeToParcel() wouldn't happen for the Parcelable object and the same object will
-            // be sent through the binder call.
-            mItem = item;
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public MediaItem getVersionedParcel() {
-            return mItem;
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/ParcelImplListSlice.java b/media2/media2-common/src/main/java/androidx/media2/common/ParcelImplListSlice.java
deleted file mode 100644
index fd465dd..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/ParcelImplListSlice.java
+++ /dev/null
@@ -1,210 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.annotation.SuppressLint;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Transfer a large list of {@link ParcelImpl} objects across an IPC. Splits into
- * multiple transactions if needed.
- *
- * Note: Using this class causes synchronous binder calls in the opposite direction regardless of
- * "oneway" property.
- *
- */
-@RestrictTo(LIBRARY_GROUP)
-@SuppressLint("BanParcelableUsage")
-public class ParcelImplListSlice implements Parcelable {
-    private static final String TAG = "ParcelImplListSlice";
-    private static final boolean DEBUG = false;
-
-    private static final int MAX_IPC_SIZE = 64 * 1024; // IBinder.MAX_IPC_SIZE
-    private static final int INLINE_COUNT_LIMIT = 1;
-
-    final List<ParcelImpl> mList;
-
-    public ParcelImplListSlice(@NonNull List<ParcelImpl> list) {
-        if (list == null) {
-            throw new NullPointerException("list shouldn't be null");
-        }
-        mList = list;
-    }
-
-    @SuppressWarnings("deprecation")
-    ParcelImplListSlice(Parcel p) {
-        final int itemCount = p.readInt();
-        mList = new ArrayList<>(itemCount);
-        if (DEBUG) {
-            Log.d(TAG, "Retrieving " + itemCount + " items");
-        }
-        if (itemCount <= 0) {
-            return;
-        }
-
-        int i = 0;
-        while (i < itemCount) {
-            if (p.readInt() == 0) {
-                break;
-            }
-
-            final ParcelImpl parcelImpl = p.readParcelable(ParcelImpl.class.getClassLoader());
-            mList.add(parcelImpl);
-
-            if (DEBUG) {
-                Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
-            }
-            i++;
-        }
-        if (i >= itemCount) {
-            return;
-        }
-        final IBinder retriever = p.readStrongBinder();
-        while (i < itemCount) {
-            if (DEBUG) {
-                Log.d(TAG, "Reading more @" + i + " of " + itemCount + ": retriever=" + retriever);
-            }
-            Parcel data = Parcel.obtain();
-            Parcel reply = Parcel.obtain();
-            try {
-                data.writeInt(i);
-                try {
-                    retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failure retrieving array; only received " + i + " of " + itemCount,
-                            e);
-                    return;
-                }
-                while (i < itemCount && reply.readInt() != 0) {
-                    final ParcelImpl parcelImpl = reply.readParcelable(
-                            ParcelImpl.class.getClassLoader());
-                    mList.add(parcelImpl);
-
-                    if (DEBUG) {
-                        Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
-                    }
-                    i++;
-                }
-            } finally {
-                reply.recycle();
-                data.recycle();
-            }
-        }
-    }
-
-    public @NonNull List<ParcelImpl> getList() {
-        return mList;
-    }
-
-    /**
-     * Write this to another Parcel. Note that this discards the internal Parcel
-     * and should not be used anymore. This is so we can pass this to a Binder
-     * where we won't have a chance to call recycle on this.
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        final int itemCount = mList.size();
-        dest.writeInt(itemCount);
-        if (DEBUG) {
-            Log.d(TAG, "Writing " + itemCount + " items");
-        }
-        if (itemCount > 0) {
-            int i = 0;
-            while (i < itemCount && i < INLINE_COUNT_LIMIT && dest.dataSize() < MAX_IPC_SIZE) {
-                dest.writeInt(1);
-
-                final ParcelImpl parcelable = mList.get(i);
-                dest.writeParcelable(parcelable, flags);
-
-                if (DEBUG) {
-                    Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
-                }
-                i++;
-            }
-            if (i < itemCount) {
-                dest.writeInt(0);
-                Binder retriever = new Binder() {
-                    @Override
-                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-                            throws RemoteException {
-                        if (code != FIRST_CALL_TRANSACTION) {
-                            return super.onTransact(code, data, reply, flags);
-                        }
-                        int i = data.readInt();
-                        if (DEBUG) {
-                            Log.d(TAG, "Writing more @" + i + " of " + itemCount);
-                        }
-                        while (i < itemCount && reply.dataSize() < MAX_IPC_SIZE) {
-                            reply.writeInt(1);
-
-                            final ParcelImpl parcelable = mList.get(i);
-                            reply.writeParcelable(parcelable, flags);
-
-                            if (DEBUG) {
-                                Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
-                            }
-                            i++;
-                        }
-                        if (i < itemCount) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Breaking @" + i + " of " + itemCount);
-                            }
-                            reply.writeInt(0);
-                        }
-                        return true;
-                    }
-                };
-                if (DEBUG) {
-                    Log.d(TAG, "Breaking @" + i + " of " + itemCount + ": retriever=" + retriever);
-                }
-                dest.writeStrongBinder(retriever);
-            }
-        }
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final Parcelable.Creator<ParcelImplListSlice> CREATOR =
-            new Parcelable.Creator<ParcelImplListSlice>() {
-        @Override
-        public ParcelImplListSlice createFromParcel(Parcel in) {
-            return new ParcelImplListSlice(in);
-        }
-
-        @Override
-        public ParcelImplListSlice[] newArray(int size) {
-            return new ParcelImplListSlice[size];
-        }
-    };
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/Rating.java b/media2/media2-common/src/main/java/androidx/media2/common/Rating.java
deleted file mode 100644
index f13f75d..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/Rating.java
+++ /dev/null
@@ -1,34 +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.media2.common;
-
-import androidx.versionedparcelable.VersionedParcelable;
-
-/**
- * An interface to encapsulate rating information used as content metadata.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public interface Rating extends VersionedParcelable {
-    /**
-     * Returns whether there is a rating value available.
-     * @return {@code true} if there is an available rating value.
-     */
-    boolean isRated();
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/SessionPlayer.java b/media2/media2-common/src/main/java/androidx/media2/common/SessionPlayer.java
deleted file mode 100644
index 4e75658..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/SessionPlayer.java
+++ /dev/null
@@ -1,1634 +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.media2.common;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntDef;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.Closeable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.Executor;
-
-/**
- * Base interface for all media players that want media session.
- *
- * <p>APIs that return {@link ListenableFuture} should be the asynchronous calls and shouldn't block
- * the calling thread. This guarantees the APIs are safe to be called on the main thread.
- *
- * <p>Topics covered here are:
- *
- * <ol>
- *   <li><a href="#BestPractices">Best practices</a>
- *   <li><a href="#PlayerStates">Player states</a>
- *   <li><a href="#InvalidStates">Invalid method calls</a>
- * </ol>
- *
- * <h3 id="BestPractices">Best practices</h3>
- *
- * Here are best practices when implementing/using SessionPlayer:
- *
- * <ul>
- *   <li>When updating UI, you should respond to {@link PlayerCallback} invocations instead of
- *       {@link PlayerResult} objects since the player can be controlled by others.
- *   <li>When a SessionPlayer object is no longer being used, call {@link #close()} as soon as
- *       possible to release the resources used by the internal player engine associated with the
- *       SessionPlayer. For example, if a player uses hardware decoder, other player instances may
- *       fallback to software decoders or fail to play. You cannot use SessionPlayer instance after
- *       you call {@link #close()}. There is no way to reuse the instance.
- *   <li>The current playback position can be retrieved with a call to {@link
- *       #getCurrentPosition()}, which is helpful for applications such as a music player that need
- *       to keep track of the playback progress.
- *   <li>The playback position can be adjusted with a call to {@link #seekTo(long)}. Although the
- *       asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
- *       while to finish, especially for audio/video being streamed.
- *   <li>You can call {@link #seekTo(long)} from the {@link #PLAYER_STATE_PAUSED}. In these cases,
- *       if you are playing a video stream and the requested position is valid, one video frame may
- *       be displayed.
- * </ul>
- *
- * <h3 id="PlayerStates">Player states</h3>
- *
- * The playback control of audio/video files is managed as a state machine. The SessionPlayer
- * defines four states:
- *
- * <ol>
- *   <li>{@link #PLAYER_STATE_IDLE}: Initial state after the instantiation.
- *       <p>While in this state, you should call {@link #setMediaItem(MediaItem)} or {@link
- *       #setPlaylist(List, MediaMetadata)}. Check returned {@link ListenableFuture} for potential
- *       error.
- *       <p>Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}.
- *   <li>{@link #PLAYER_STATE_PAUSED}: State when the audio/video playback is paused.
- *       <p>Call {@link #play()} to resume or start playback from the position where it paused.
- *   <li>{@link #PLAYER_STATE_PLAYING}: State when the player plays the media item.
- *       <p>In this state, {@link PlayerCallback#onBufferingStateChanged( SessionPlayer, MediaItem,
- *       int)} will be called regularly to tell the buffering status.
- *       <p>Playback state would remain {@link #PLAYER_STATE_PLAYING} when the currently playing
- *       media item is changed.
- *       <p>When the playback reaches the end of stream, the behavior depends on repeat mode, set by
- *       {@link #setRepeatMode(int)}. If the repeat mode was set to {@link #REPEAT_MODE_NONE}, the
- *       player will transfer to the {@link #PLAYER_STATE_PAUSED}. Otherwise, the SessionPlayer
- *       object remains in the {@link #PLAYER_STATE_PLAYING} and playback will be ongoing.
- *   <li>{@link #PLAYER_STATE_ERROR}: State when the playback failed and player cannot be recovered
- *       by itself.
- *       <p>In general, playback might fail due to various reasons such as unsupported audio/video
- *       format, poorly interleaved audio/video, resolution too high, streaming timeout, and others.
- *       In addition, due to programming errors, a playback control operation might be performed
- *       from an <a href="#InvalidStates">invalid state</a>. In these cases the player may
- *       transition to this state.
- * </ol>
- *
- * Subclasses may have extra methods to reset the player state to {@link #PLAYER_STATE_IDLE} from
- * other states. Take a look at documentations of specific subclass that you're interested in.
- *
- * <p>
- *
- * <h3 id="InvalidStates">Invalid method calls</h3>
- *
- * The only method you safely call from the {@link #PLAYER_STATE_ERROR} is {@link #close()}. Any
- * other methods might return meaningless data.
- *
- * <p>Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error
- * state and/or provide a method to recover from the error state. Take a look at documentations of
- * specific subclass that you're interested in.
- *
- * <p>Most methods can be called from any non-Error state. In case they're called in invalid state,
- * the implementation should ignore and would return {@link PlayerResult} with {@link
- * PlayerResult#RESULT_ERROR_INVALID_STATE}. The following table lists the methods that aren't
- * guaranteed to successfully running if they're called from the associated invalid states.
- *
- * <p>
- *
- * <table>
- * <tr><th>Method Name</th> <th>Invalid States</th></tr>
- * <tr><td>setAudioAttributes</td> <td>{Paused, Playing}</td></tr>
- * <tr><td>prepare</td> <td>{Paused, Playing}</td></tr>
- * <tr><td>play</td> <td>{Idle}</td></tr>
- * <tr><td>pause</td> <td>{Idle}</td></tr>
- * <tr><td>seekTo</td> <td>{Idle}</td></tr>
- * </table>
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-// Previously MediaSessionCompat.Callback.
-// Players can extend this directly (e.g. MediaPlayer) or create wrapper and control underlying
-// player.
-// Preferably it can be interface, but API guideline requires to use abstract class.
-@Deprecated
-public abstract class SessionPlayer implements Closeable {
-    private static final String TAG = "SessionPlayer";
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @IntDef({
-            PLAYER_STATE_IDLE,
-            PLAYER_STATE_PAUSED,
-            PLAYER_STATE_PLAYING,
-            PLAYER_STATE_ERROR})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PlayerState {
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @IntDef({
-            BUFFERING_STATE_UNKNOWN,
-            BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
-            BUFFERING_STATE_BUFFERING_AND_STARVED,
-            BUFFERING_STATE_COMPLETE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BuffState {
-    }
-
-    /**
-     * State when the player is idle, and needs configuration to start playback.
-     */
-    public static final int PLAYER_STATE_IDLE = 0;
-
-    /**
-     * State when the player's playback is paused
-     */
-    public static final int PLAYER_STATE_PAUSED = 1;
-
-    /**
-     * State when the player's playback is ongoing
-     */
-    public static final int PLAYER_STATE_PLAYING = 2;
-
-    /**
-     * State when the player is in error state and cannot be recovered self.
-     */
-    public static final int PLAYER_STATE_ERROR = 3;
-
-    /**
-     * Buffering state is unknown.
-     */
-    public static final int BUFFERING_STATE_UNKNOWN = 0;
-
-    /**
-     * Buffering state indicating the player is buffering but enough has been buffered
-     * for this player to be able to play the content.
-     * See {@link #getBufferedPosition()} for how far is buffered already.
-     */
-    public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1;
-
-    /**
-     * Buffering state indicating the player is buffering, but the player is currently starved
-     * for data, and cannot play.
-     */
-    public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2;
-
-    /**
-     * Buffering state indicating the player is done buffering, and the remainder of the content is
-     * available for playback.
-     */
-    public static final int BUFFERING_STATE_COMPLETE = 3;
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
-            REPEAT_MODE_GROUP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RepeatMode {
-    }
-
-    /**
-     * Playback will be stopped at the end of the playing media list.
-     */
-    public static final int REPEAT_MODE_NONE = 0;
-
-    /**
-     * Playback of the current playing media item will be repeated.
-     */
-    public static final int REPEAT_MODE_ONE = 1;
-
-    /**
-     * Playing media list will be repeated.
-     */
-    public static final int REPEAT_MODE_ALL = 2;
-
-    /**
-     * Playback of the playing media group will be repeated.
-     * A group is a logical block of media items which is specified in the section 5.7 of the
-     * Bluetooth AVRCP 1.6. An example of a group is the playlist.
-     */
-    public static final int REPEAT_MODE_GROUP = 3;
-
-    /**
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ShuffleMode {
-    }
-
-    /**
-     * Media list will be played in order.
-     */
-    public static final int SHUFFLE_MODE_NONE = 0;
-
-    /**
-     * Media list will be played in shuffled order.
-     */
-    public static final int SHUFFLE_MODE_ALL = 1;
-
-    /**
-     * Media group will be played in shuffled order.
-     * A group is a logical block of media items which is specified in the section 5.7 of the
-     * Bluetooth AVRCP 1.6. An example of a group is the playlist.
-     */
-    public static final int SHUFFLE_MODE_GROUP = 2;
-
-    /**
-     * Value indicating the time is unknown
-     */
-    public static final long UNKNOWN_TIME = Long.MIN_VALUE;
-
-    /**
-     * Media item index is invalid. This value will be returned when the corresponding media item
-     * does not exist.
-     */
-    public static final int INVALID_ITEM_INDEX = -1;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private final List<Pair<PlayerCallback, Executor>> mCallbacks = new ArrayList<>();
-
-    /**
-     * Starts or resumes playback.
-     * <p>
-     * On success, this transfers the player state to {@link #PLAYER_STATE_PLAYING} and
-     * a {@link PlayerResult} should be returned with the current media item when the command
-     * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
-     * it should be ignored and a {@link PlayerResult} should be returned with
-     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> play();
-
-    /**
-     * Pauses playback.
-     * <p>
-     * On success, this transfers the player state to {@link #PLAYER_STATE_PAUSED} and
-     * a {@link PlayerResult} should be returned with the current media item when the command
-     * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
-     * it should be ignored and a {@link PlayerResult} should be returned with
-     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> pause();
-
-    /**
-     * Prepares the media items for playback. During this time, the player may allocate resources
-     * required to play, such as audio and video decoders. Before calling this API, set media
-     * item(s) through either {@link #setMediaItem} or {@link #setPlaylist}.
-     * <p>
-     * On success, this transfers the player state from {@link #PLAYER_STATE_IDLE} to
-     * {@link #PLAYER_STATE_PAUSED} and a {@link PlayerResult} should be returned with the prepared
-     * media item when the command completed. If it's not called in {@link #PLAYER_STATE_IDLE},
-     * it should be ignored and {@link PlayerResult} should be returned with
-     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> prepare();
-
-    /**
-     * Seeks to the specified position.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
-     * calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored and
-     * a {@link PlayerResult} should be returned with
-     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @param position the new playback position in ms. The value should be in the range of start
-     * and end positions defined in {@link MediaItem}.
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> seekTo(long position);
-
-    /**
-     * Sets the playback speed. The default playback speed is {@code 1.0f}, and negative values
-     * indicate reverse playback and {@code 0.0f} is not allowed.
-     * <p>
-     * The supported playback speed range depends on the underlying player implementation, so it is
-     * recommended to query the actual speed of the player via {@link #getPlaybackSpeed()} after the
-     * operation completes. In particular, please note that player implementations may not support
-     * reverse playback.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param playbackSpeed the requested playback speed
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #getPlaybackSpeed()
-     * @see PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed);
-
-    /**
-     * Sets the {@link AudioAttributesCompat} to be used during the playback of the media.
-     * <p>
-     * You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
-     * become effective thereafter. Otherwise, the call would be ignored and {@link PlayerResult}
-     * should be returned with {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param attributes non-null <code>AudioAttributes</code>.
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes);
-
-    /**
-     * Gets the current player state.
-     *
-     * @return the current player state
-     * @see PlayerCallback#onPlayerStateChanged(SessionPlayer, int)
-     * @see #PLAYER_STATE_IDLE
-     * @see #PLAYER_STATE_PAUSED
-     * @see #PLAYER_STATE_PLAYING
-     * @see #PLAYER_STATE_ERROR
-     */
-    @PlayerState
-    public abstract int getPlayerState();
-
-    /**
-     * Gets the current playback position.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
-     * So the position {@code 0} means the start position of the {@link MediaItem}.
-     *
-     * @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown
-     */
-    public abstract long getCurrentPosition();
-
-    /**
-     * Gets the duration of the current media item, or {@link #UNKNOWN_TIME} if unknown. If the
-     * current {@link MediaItem} has either start or end position, then duration would be adjusted
-     * accordingly instead of returning the whole size of the {@link MediaItem}.
-     *
-     * @return the duration in ms, or {@link #UNKNOWN_TIME} if unknown
-     */
-    public abstract long getDuration();
-
-    /**
-     * Gets the position for how much has been buffered, or {@link #UNKNOWN_TIME} if unknown.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
-     * So the position {@code 0} means the start position of the {@link MediaItem}.
-     *
-     * @return the buffered position in ms, or {@link #UNKNOWN_TIME} if unknown
-     */
-    public abstract long getBufferedPosition();
-
-    /**
-     * Returns the current buffering state of the player.
-     * <p>
-     * During the buffering, see {@link #getBufferedPosition()} for the quantifying the amount
-     * already buffered.
-     *
-     * @return the buffering state, or {@link #BUFFERING_STATE_UNKNOWN} if unknown
-     * @see #getBufferedPosition()
-     */
-    @BuffState
-    public abstract int getBufferingState();
-
-    /**
-     * Gets the actual playback speed to be used by the player when playing. A value of {@code 1.0f}
-     * is the default playback value, and a negative value indicates reverse playback.
-     * <p>
-     * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
-     *
-     * @return the actual playback speed
-     */
-    public abstract float getPlaybackSpeed();
-
-    /**
-     * Gets the size of the video.
-     *
-     * @return the size of the video. The width and height of size could be 0 if there is no video
-     *         or the size has not been determined yet.
-     * @see PlayerCallback#onVideoSizeChanged(SessionPlayer, VideoSize)
-     */
-    @NonNull
-    public VideoSize getVideoSize() {
-        throw new UnsupportedOperationException("getVideoSize is not implemented");
-    }
-
-    /**
-     * Sets the {@link Surface} to be used as the sink for the video portion of the media.
-     * <p>
-     * A null surface will reset any Surface and result in only the audio track being played.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param surface the {@link Surface} to be used for the video portion of the media
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> setSurface(@Nullable Surface surface) {
-        throw new UnsupportedOperationException("setSurface is not implemented");
-    }
-
-    /**
-     * Sets a list of {@link MediaItem} with metadata. Use this or {@link #setMediaItem} to specify
-     * which items to play.
-     * <p>
-     * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
-     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
-     * <p>
-     * Ensure uniqueness of each {@link MediaItem} in the playlist so the session can uniquely
-     * identity individual items. All {@link MediaItem}s shouldn't be {@code null} as well.
-     * <p>
-     * It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
-     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
-     * duration information in the metadata, session will do extra work to get the duration and send
-     * it to the controller.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
-     * when it's completed. The current media item would be the first item in the playlist.
-     * <p>
-     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
-     * when a media item in the playlist is a {@link FileMediaItem}.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the first media item of the
-     * playlist when the command completed.
-     *
-     * @param list a list of {@link MediaItem} objects to set as a play list
-     * @throws IllegalArgumentException if the given list is {@code null} or empty, or has
-     *         duplicated media items.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see #setMediaItem
-     * @see PlayerCallback#onPlaylistChanged
-     * @see PlayerCallback#onCurrentMediaItemChanged
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setPlaylist(
-            @NonNull List<MediaItem> list, @Nullable MediaMetadata metadata);
-
-    /**
-     * Gets the {@link AudioAttributesCompat} that media player has.
-     */
-    @Nullable
-    public abstract AudioAttributesCompat getAudioAttributes();
-
-    /**
-     * Sets a {@link MediaItem} for playback. Use this or {@link #setPlaylist} to specify which
-     * items to play. If you want to change current item in the playlist, use one of
-     * {@link #skipToPlaylistItem}, {@link #skipToNextPlaylistItem}, or
-     * {@link #skipToPreviousPlaylistItem} instead of this method.
-     * <p>
-     * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
-     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
-     * <p>
-     * It's recommended to fill {@link MediaMetadata} in {@link MediaItem} especially for the
-     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
-     * duration information in the metadata, session will do extra work to get the duration and send
-     * it to the controller.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
-     * when it's completed. The current item would be the item given here.
-     * <p>
-     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
-     * if the given media item is a {@link FileMediaItem}.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with {@code item} set.
-     *
-     * @param item the descriptor of media item you want to play
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see #setPlaylist
-     * @see PlayerCallback#onPlaylistChanged
-     * @see PlayerCallback#onCurrentMediaItemChanged
-     * @throws IllegalArgumentException if the given item is {@code null}.
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setMediaItem(
-            @NonNull MediaItem item);
-
-    /**
-     * Adds the media item to the playlist at the index. Index equals to or greater than
-     * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
-     * the playlist.
-     * <p>
-     * If index is less than or equal to the current index of the playlist,
-     * the current index of the playlist should be increased correspondingly.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
-     * completed.
-     * <p>
-     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
-     * if the given media item is a {@link FileMediaItem}.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with {@code item} added.
-     *
-     * @param index the index of the item you want to add in the playlist
-     * @param item the media item you want to add
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> addPlaylistItem(int index,
-            @NonNull MediaItem item);
-
-    /**
-     * Removes the media item from the playlist
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with {@code item} removed.
-     * <p>
-     * If the last item is removed, the player should be moved to {@link #PLAYER_STATE_IDLE}.
-     *
-     * @param index the index of the item you want to remove in the playlist
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> removePlaylistItem(
-            @IntRange(from = 0) int index);
-
-    /**
-     * Replaces the media item at index in the playlist. This can be also used to update metadata of
-     * an item.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
-     * completed.
-     * <p>
-     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
-     * if the given media item is a {@link FileMediaItem}.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with {@code item} set.
-     *
-     * @param index the index of the item to replace in the playlist
-     * @param item the new item
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> replacePlaylistItem(int index,
-            @NonNull MediaItem item);
-
-    /**
-     * Moves the media item at {@code fromIdx} to {@code toIdx} in the playlist.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with {@code item} set.
-     *
-     * @param fromIndex the media item's initial index in the playlist
-     * @param toIndex the media item's target index in the playlist
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> movePlaylistItem(
-            @IntRange(from = 0) int fromIndex, @IntRange(from = 0) int toIndex) {
-        throw new UnsupportedOperationException("movePlaylistItem is not implemented");
-    }
-
-    /**
-     * Skips to the previous item in the playlist.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> skipToPreviousPlaylistItem();
-
-    /**
-     * Skips to the next item in the playlist.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> skipToNextPlaylistItem();
-
-    /**
-     * Skips to the item in the playlist at the index.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param index the index of the item you want to play in the playlist
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> skipToPlaylistItem(
-            @IntRange(from = 0) int index);
-
-    /**
-     * Updates the playlist metadata while keeping the playlist as-is.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)} when it's
-     * completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param metadata metadata of the playlist
-     * @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> updatePlaylistMetadata(
-            @Nullable MediaMetadata metadata);
-
-    /**
-     * Sets the repeat mode.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onRepeatModeChanged(SessionPlayer, int)} when it's completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param repeatMode repeat mode
-     * @see #REPEAT_MODE_NONE
-     * @see #REPEAT_MODE_ONE
-     * @see #REPEAT_MODE_ALL
-     * @see #REPEAT_MODE_GROUP
-     * @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setRepeatMode(
-            @RepeatMode int repeatMode);
-
-    /**
-     * Sets the shuffle mode.
-     * <p>
-     * The implementation must notify registered callbacks with
-     * {@link PlayerCallback#onShuffleModeChanged(SessionPlayer, int)} when it's completed.
-     * <p>
-     * On success, a {@link PlayerResult} should be returned with the current media item when the
-     * command completed.
-     *
-     * @param shuffleMode the shuffle mode
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #SHUFFLE_MODE_NONE
-     * @see #SHUFFLE_MODE_ALL
-     * @see #SHUFFLE_MODE_GROUP
-     * @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
-     */
-    @NonNull
-    public abstract ListenableFuture<PlayerResult> setShuffleMode(
-            @ShuffleMode int shuffleMode);
-
-    /**
-     * Gets the playlist. It can be {@code null} if the playlist hasn't been set or it's reset by
-     * {@link #setMediaItem}.
-     *
-     * @return playlist, or {@code null}
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @Nullable
-    public abstract List<MediaItem> getPlaylist();
-
-    /**
-     * Gets the playlist metadata.
-     *
-     * @return metadata metadata of the playlist, or null if none is set
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     * @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
-     */
-    @Nullable
-    public abstract MediaMetadata getPlaylistMetadata();
-
-    /**
-     * Gets the repeat mode.
-     *
-     * @return repeat mode
-     * @see #REPEAT_MODE_NONE
-     * @see #REPEAT_MODE_ONE
-     * @see #REPEAT_MODE_ALL
-     * @see #REPEAT_MODE_GROUP
-     * @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
-     */
-    @RepeatMode
-    public abstract int getRepeatMode();
-
-    /**
-     * Gets the shuffle mode.
-     *
-     * @return the shuffle mode
-     * @see #SHUFFLE_MODE_NONE
-     * @see #SHUFFLE_MODE_ALL
-     * @see #SHUFFLE_MODE_GROUP
-     * @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
-     */
-    @ShuffleMode
-    public abstract int getShuffleMode();
-
-    /**
-     * Gets the current media item, which is currently playing or would be played with later
-     * {@link #play}. This value may be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is
-     * called.
-     *
-     * @return the current media item. Can be {@code null} only when the player is in
-     * {@link #PLAYER_STATE_IDLE} and a media item or playlist hasn't been set.
-     * @see #setMediaItem
-     * @see #setPlaylist
-     */
-    @Nullable
-    public abstract MediaItem getCurrentMediaItem();
-
-    /**
-     * Gets the index of current media item in playlist. This value should be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of current media item. Can be {@link #INVALID_ITEM_INDEX} when current
-     *         media item is null or not in the playlist, and when the playlist hasn't been set.
-     */
-    @IntRange(from = INVALID_ITEM_INDEX)
-    public abstract int getCurrentMediaItemIndex();
-
-    /**
-     * Gets the previous item index in the playlist. This value should be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of previous media item. Can be {@link #INVALID_ITEM_INDEX} only when
-     *         previous media item does not exist or playlist hasn't been set.
-     */
-    @IntRange(from = INVALID_ITEM_INDEX)
-    public abstract int getPreviousMediaItemIndex();
-
-    /**
-     * Gets the next item index in the playlist. This value should be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of next media item. Can be {@link #INVALID_ITEM_INDEX} only when next media
-     *         item does not exist or playlist hasn't been set.
-     */
-    @IntRange(from = INVALID_ITEM_INDEX)
-    public abstract int getNextMediaItemIndex();
-
-    // Listeners / Callback related
-    // Intentionally final not to allow developers to change the behavior
-    /**
-     * Register {@link PlayerCallback} to listen changes.
-     *
-     * @param executor a callback Executor
-     * @param callback a PlayerCallback
-     * @throws IllegalArgumentException if executor or callback is {@code null}.
-     */
-    public final void registerPlayerCallback(
-            @NonNull /*@CallbackExecutor*/ Executor executor,
-            @NonNull PlayerCallback callback) {
-        if (executor == null) {
-            throw new NullPointerException("executor shouldn't be null");
-        }
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
-        }
-
-        synchronized (mLock) {
-            for (Pair<PlayerCallback, Executor> pair : mCallbacks) {
-                if (pair.first == callback && pair.second != null) {
-                    Log.w(TAG, "callback is already added. Ignoring.");
-                    return;
-                }
-            }
-            mCallbacks.add(new Pair<>(callback, executor));
-        }
-    }
-
-    /**
-     * Unregister the previously registered {@link PlayerCallback}.
-     *
-     * @param callback the callback to be removed
-     * @throws IllegalArgumentException if the callback is {@code null}.
-     */
-    public final void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
-        }
-        synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                if (mCallbacks.get(i).first == callback) {
-                    mCallbacks.remove(i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Gets the callbacks with executors for subclasses to notify player events.
-     *
-     * @return map of callbacks and its executors
-     */
-    @NonNull
-    protected final List<Pair<PlayerCallback, Executor>> getCallbacks() {
-        List<Pair<PlayerCallback, Executor>> list = new ArrayList<>();
-        synchronized (mLock) {
-            list.addAll(mCallbacks);
-        }
-        return list;
-    }
-
-    /**
-     * Gets the full list of selected and unselected tracks that the media contains. The order of
-     * the list is irrelevant as different players expose tracks in different ways, but the tracks
-     * will generally be ordered based on track type.
-     * <p>
-     * The types of tracks supported may vary based on player implementation.
-     *
-     * @return list of tracks. The total number of tracks is the size of the list. If empty,
-     *         the implementation should return an empty list instead of {@code null}.
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     */
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        throw new UnsupportedOperationException("getTracks is not implemented");
-    }
-
-    /**
-     * Selects the {@link TrackInfo} for the current media item.
-     * <p>
-     * Generally one track will be selected for each track type.
-     * <p>
-     * The types of tracks supported may vary based on player implementation.
-     * <p>
-     * Note: {@link #getTracks()} returns the list of tracks that can be selected, but the
-     * list may be invalidated when {@link PlayerCallback#onTracksChanged(SessionPlayer, List)}
-     * is called.
-     *
-     * @param trackInfo track to be selected
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     * @see PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> selectTrack(@NonNull TrackInfo trackInfo) {
-        throw new UnsupportedOperationException("selectTrack is not implemented");
-    }
-
-    /**
-     * Deselects the {@link TrackInfo} for the current media item.
-     * <p>
-     * Generally, a track should already be selected in order to be deselected, and audio and video
-     * tracks should not be deselected.
-     * <p>
-     * The types of tracks supported may vary based on player implementation.
-     * <p>
-     * Note: {@link #getSelectedTrack(int)} returns the currently selected track per track type that
-     * can be deselected, but the list may be invalidated when
-     * {@link PlayerCallback#onTracksChanged(SessionPlayer, List)} is called.
-     *
-     * @param trackInfo track to be deselected
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     * @see PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> deselectTrack(@NonNull TrackInfo trackInfo) {
-        throw new UnsupportedOperationException("deselectTrack is not implemented");
-    }
-
-    /**
-     * Gets currently selected track's {@link TrackInfo} for the given track type.
-     * <p>
-     * The returned value can be outdated after
-     * {@link PlayerCallback#onTracksChanged(SessionPlayer, List)},
-     * {@link PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)},
-     * or {@link PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)} is called.
-     *
-     * @param trackType type of selected track
-     * @return selected track info
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     */
-    @Nullable
-    public TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
-        throw new UnsupportedOperationException(
-                "getSelectedTrack is not implemented");
-    }
-
-    /**
-     * Removes all existing references to callbacks and executors.
-     *
-     * Note: Sub classes of {@link SessionPlayer} that override this API should call this super
-     * method.
-     */
-    @CallSuper
-    @Override
-    public void close() {
-        synchronized (mLock) {
-            mCallbacks.clear();
-        }
-    }
-
-    /**
-     * Class for the player to return each audio/video/subtitle track's metadata.
-     *
-     * <p>Note: TrackInfo holds a MediaFormat instance, but only the following key-values will be
-     * supported when sending it over different processes:
-     *
-     * <ul>
-     *   <li>{@link MediaFormat#KEY_LANGUAGE}
-     *   <li>{@link MediaFormat#KEY_MIME}
-     *   <li>{@link MediaFormat#KEY_IS_FORCED_SUBTITLE}
-     *   <li>{@link MediaFormat#KEY_IS_AUTOSELECT}
-     *   <li>{@link MediaFormat#KEY_IS_DEFAULT}
-     * </ul>
-     *
-     * @see #getTracks
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    @VersionedParcelize(isCustom = true)
-    public static class TrackInfo extends CustomVersionedParcelable {
-        public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
-        public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
-        public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
-        public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
-        public static final int MEDIA_TRACK_TYPE_METADATA = 5;
-
-        private static final String KEY_IS_FORMAT_NULL =
-                "androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_FORMAT_NULL";
-        private static final String KEY_IS_SELECTABLE =
-                "androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_SELECTABLE";
-
-        /**
-         */
-        @IntDef(flag = false, /*prefix = "MEDIA_TRACK_TYPE",*/ value = {
-                MEDIA_TRACK_TYPE_UNKNOWN,
-                MEDIA_TRACK_TYPE_VIDEO,
-                MEDIA_TRACK_TYPE_AUDIO,
-                MEDIA_TRACK_TYPE_SUBTITLE,
-                MEDIA_TRACK_TYPE_METADATA,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface MediaTrackType {}
-
-        @ParcelField(1)
-        int mId;
-
-        // Removed @ParcelField(2)
-
-        @ParcelField(3)
-        int mTrackType;
-
-        // Parceled via mParcelableExtras.
-        @NonParcelField
-        @Nullable
-        MediaFormat mFormat;
-        // Parceled via mParcelableExtras.
-        @NonParcelField
-        boolean mIsSelectable;
-        // For extra information containing MediaFormat and isSelectable data. Should only be used
-        // by onPreParceling() and onPostParceling().
-        @ParcelField(4)
-        Bundle mParcelableExtras;
-        @NonParcelField
-        private final Object mLock = new Object();
-
-        // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-        /**
-         * Used for VersionedParcelable
-         */
-        TrackInfo() {
-            // no-op
-        }
-
-        /**
-         * Constructor to create a TrackInfo instance.
-         *
-         * Note: The default value for {@link #isSelectable()} is false.
-         *
-         * @param id id of track unique across {@link MediaItem}s
-         * @param type type of track. Can be video, audio or subtitle
-         * @param format format of track
-         */
-        public TrackInfo(int id, int type, @Nullable MediaFormat format) {
-            this(id, type, format, /* isSelectable= */ false);
-        }
-
-        /**
-         * Constructor to create a TrackInfo instance.
-         *
-         * @param id id of track unique across {@link MediaItem}s
-         * @param type type of track. Can be video, audio or subtitle
-         * @param format format of track
-         * @param isSelectable whether track can be selected via
-         * {@link SessionPlayer#selectTrack(TrackInfo)}.
-         */
-        public TrackInfo(int id, int type, @Nullable MediaFormat format, boolean isSelectable) {
-            mId = id;
-            mTrackType = type;
-            mFormat = format;
-            mIsSelectable = isSelectable;
-        }
-
-        /**
-         * Gets the track type.
-         * @return MediaTrackType which indicates if the track is video, audio or subtitle
-         */
-        @MediaTrackType
-        public int getTrackType() {
-            return mTrackType;
-        }
-
-        /**
-         * Gets the language code of the track.
-         * @return {@link Locale} which includes the language information
-         */
-        @NonNull
-        public Locale getLanguage() {
-            String language = mFormat != null ? mFormat.getString(MediaFormat.KEY_LANGUAGE) : null;
-            if (language == null) {
-                language = "und";
-            }
-            return new Locale(language);
-        }
-
-        /**
-         * Gets the {@link MediaFormat} of the track.  If the format is
-         * unknown or could not be determined, null is returned.
-         */
-        @Nullable
-        public MediaFormat getFormat() {
-            return mFormat;
-        }
-
-        /**
-         * Gets the id of the track.
-         * The id is used by {@link #selectTrack(TrackInfo)} and {@link #deselectTrack(TrackInfo)}
-         * to identify the track to be (de)selected.
-         * So, it's highly recommended to ensure that the id of each track is unique across
-         * {@link MediaItem}s to avoid potential mis-selection when a stale {@link TrackInfo} is
-         * used.
-         *
-         * @return id of the track
-         */
-        public int getId() {
-            return mId;
-        }
-
-        /**
-         * Whether the current track can be selected via {@link #selectTrack(TrackInfo)} or not.
-         *
-         * @return true if the current track can be selected; false if otherwise.
-         */
-        public boolean isSelectable() {
-            return mIsSelectable;
-        }
-
-        @Override
-        @NonNull
-        public String toString() {
-            StringBuilder out = new StringBuilder(128);
-            out.append(getClass().getName());
-            out.append('#').append(mId);
-            out.append('{');
-            switch (mTrackType) {
-                case MEDIA_TRACK_TYPE_VIDEO:
-                    out.append("VIDEO");
-                    break;
-                case MEDIA_TRACK_TYPE_AUDIO:
-                    out.append("AUDIO");
-                    break;
-                case MEDIA_TRACK_TYPE_SUBTITLE:
-                    out.append("SUBTITLE");
-                    break;
-                case MEDIA_TRACK_TYPE_METADATA:
-                    out.append("METADATA");
-                    break;
-                default:
-                    out.append("UNKNOWN");
-                    break;
-            }
-            out.append(", ").append(mFormat);
-            out.append(", isSelectable=").append(mIsSelectable);
-            out.append("}");
-            return out.toString();
-        }
-
-        @Override
-        public int hashCode() {
-            return mId;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (!(obj instanceof TrackInfo)) {
-                return false;
-            }
-            TrackInfo other = (TrackInfo) obj;
-            return mId == other.mId;
-        }
-
-        /**
-         * @param isStream
-         */
-        @RestrictTo(LIBRARY)
-        @Override
-        public void onPreParceling(boolean isStream) {
-            synchronized (mLock) {
-                mParcelableExtras = new Bundle();
-
-                mParcelableExtras.putBoolean(KEY_IS_FORMAT_NULL, mFormat == null);
-                if (mFormat != null) {
-                    putStringValueToBundle(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
-                    putStringValueToBundle(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
-                    putIntValueToBundle(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
-                            mParcelableExtras);
-                    putIntValueToBundle(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
-                    putIntValueToBundle(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
-                }
-
-                mParcelableExtras.putBoolean(KEY_IS_SELECTABLE, mIsSelectable);
-            }
-        }
-
-        /**
-         */
-        @RestrictTo(LIBRARY)
-        @Override
-        public void onPostParceling() {
-            if (mParcelableExtras != null && !mParcelableExtras.getBoolean(KEY_IS_FORMAT_NULL)) {
-                mFormat = new MediaFormat();
-                setStringValueToMediaFormat(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
-                setStringValueToMediaFormat(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
-                setIntValueToMediaFormat(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
-                        mParcelableExtras);
-                setIntValueToMediaFormat(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
-                setIntValueToMediaFormat(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
-            }
-
-            if (mParcelableExtras == null || !mParcelableExtras.containsKey(KEY_IS_SELECTABLE)) {
-                mIsSelectable = mTrackType != MEDIA_TRACK_TYPE_VIDEO;
-            } else {
-                mIsSelectable = mParcelableExtras.getBoolean(KEY_IS_SELECTABLE);
-            }
-        }
-
-        private static void putIntValueToBundle(
-                String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
-            if (mediaFormat.containsKey(intValueKey)) {
-                bundle.putInt(intValueKey, mediaFormat.getInteger(intValueKey));
-            }
-        }
-
-        private static void putStringValueToBundle(
-                String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
-            if (mediaFormat.containsKey(stringValueKey)) {
-                bundle.putString(stringValueKey, mediaFormat.getString(stringValueKey));
-            }
-        }
-
-        private static void setIntValueToMediaFormat(
-                String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
-            if (bundle.containsKey(intValueKey)) {
-                mediaFormat.setInteger(intValueKey, bundle.getInt(intValueKey));
-            }
-        }
-
-        private static void setStringValueToMediaFormat(
-                String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
-            if (bundle.containsKey(stringValueKey)) {
-                mediaFormat.setString(stringValueKey, bundle.getString(stringValueKey));
-            }
-        }
-    }
-
-    /**
-     * A callback class to receive notifications for events on the session player. See {@link
-     * #registerPlayerCallback(Executor, PlayerCallback)} to register this callback.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public abstract static class PlayerCallback {
-        /**
-         * Called when the state of the player has changed.
-         *
-         * @param player the player whose state has changed
-         * @param playerState the new state of the player
-         * @see #getPlayerState()
-         */
-        public void onPlayerStateChanged(@NonNull SessionPlayer player,
-                @PlayerState int playerState) {
-        }
-
-        /**
-         * Called when a buffering events for a media item happened.
-         *
-         * @param player the player that is buffering
-         * @param item the media item for which buffering is happening
-         * @param buffState the new buffering state
-         * @see #getBufferingState()
-         */
-        public void onBufferingStateChanged(@NonNull SessionPlayer player,
-                @Nullable MediaItem item, @BuffState int buffState) {
-        }
-
-        /**
-         * Called when the playback speed has changed.
-         *
-         * @param player the player that has changed the playback speed
-         * @param playbackSpeed the new playback speed
-         * @see #getPlaybackSpeed()
-         */
-        public void onPlaybackSpeedChanged(@NonNull SessionPlayer player,
-                float playbackSpeed) {
-        }
-
-        /**
-         * Called when {@link #seekTo(long)} is completed.
-         *
-         * @param player the player that has completed seeking
-         * @param position the previous seeking request
-         * @see #getCurrentPosition()
-         */
-        public void onSeekCompleted(@NonNull SessionPlayer player, long position) {
-        }
-
-        /**
-         * Called when a playlist is changed. It's also called after {@link #setPlaylist} or
-         * {@link #setMediaItem}.
-         *
-         * @param player the player that has changed the playlist and playlist metadata
-         * @param list new playlist
-         * @param metadata new metadata
-         * @see #getPlaylist()
-         * @see #getPlaylistMetadata()
-         */
-        public void onPlaylistChanged(@NonNull SessionPlayer player,
-                @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
-        }
-
-        /**
-         * Called when a playlist metadata is changed.
-         *
-         * @param player the player that has changed the playlist metadata
-         * @param metadata new metadata
-         * @see #getPlaylistMetadata()
-         */
-        public void onPlaylistMetadataChanged(@NonNull SessionPlayer player,
-                @Nullable MediaMetadata metadata) {
-        }
-
-        /**
-         * Called when the shuffle mode is changed.
-         * <p>
-         * {@link SessionPlayer#getPreviousMediaItemIndex()} and
-         * {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
-         * is called if the current media item is the first or last item in the playlist.
-         *
-         * @param player playlist agent for this event
-         * @param shuffleMode shuffle mode
-         * @see #SHUFFLE_MODE_NONE
-         * @see #SHUFFLE_MODE_ALL
-         * @see #SHUFFLE_MODE_GROUP
-         * @see #getShuffleMode()
-         */
-        public void onShuffleModeChanged(@NonNull SessionPlayer player,
-                @ShuffleMode int shuffleMode) {
-        }
-
-        /**
-         * Called when the repeat mode is changed.
-         * <p>
-         * {@link SessionPlayer#getPreviousMediaItemIndex()} and
-         * {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
-         * is called if the current media item is the first or last item in the playlist.
-         *
-         * @param player player for this event
-         * @param repeatMode repeat mode
-         * @see #REPEAT_MODE_NONE
-         * @see #REPEAT_MODE_ONE
-         * @see #REPEAT_MODE_ALL
-         * @see #REPEAT_MODE_GROUP
-         * @see #getRepeatMode()
-         */
-        public void onRepeatModeChanged(@NonNull SessionPlayer player,
-                @RepeatMode int repeatMode) {
-        }
-
-        /**
-         * Called when the player's current media item has changed. Generally called after a new
-         * media item is set through {@link #setPlaylist} or {@link #setMediaItem}, or after
-         * skipping to a different item in a given playlist.
-         *
-         * @param player the player whose media item changed
-         * @param item the new current media item. This can be {@code null} when the state of
-         * the player becomes {@link #PLAYER_STATE_IDLE}.
-         * @see #getCurrentMediaItem()
-         */
-        public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                @Nullable MediaItem item) {
-        }
-
-        /**
-         * Called when the player finished playing. Playback state would be also set
-         * {@link #PLAYER_STATE_PAUSED} with it.
-         * <p>
-         * This will be called only when the repeat mode is set to {@link #REPEAT_MODE_NONE}.
-         *
-         * @param player the player whose playback is completed
-         * @see #REPEAT_MODE_NONE
-         */
-        public void onPlaybackCompleted(@NonNull SessionPlayer player) {
-        }
-
-        /**
-         * Called when the player's current audio attributes are changed.
-         *
-         * @param player the player whose audio attributes are changed
-         * @param attributes the new current audio attributes
-         * @see #getAudioAttributes()
-         */
-        public void onAudioAttributesChanged(@NonNull SessionPlayer player,
-                @Nullable AudioAttributesCompat attributes) {
-        }
-
-        /**
-         * Called to indicate the video size
-         * <p>
-         * The video size (width and height) could be 0 if there was no video,
-         * no display surface was set, or the value was not determined yet.
-         * <p>
-         * This callback is generally called when player updates video size, but will also be
-         * called when {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)}
-         * is called.
-         *
-         * @param player the player associated with this callback
-         * @param size the size of the video
-         * @see #getVideoSize()
-         */
-        public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
-        }
-
-        /**
-         * Called when the player's subtitle track has new subtitle data available.
-         * @param player the player that reports the new subtitle data
-         * @param item the MediaItem of this media item
-         * @param track the track that has the subtitle data
-         * @param data the subtitle data
-         */
-        public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) {
-        }
-
-        /**
-         * Called when the tracks of the current media item is changed such as
-         * 1) when tracks of a media item become available,
-         * 2) when new tracks are found during playback, or
-         * 3) when the current media item is changed.
-         * <p>
-         * When it's called, you should invalidate previous track information and use the new
-         * tracks to call {@link #selectTrack(TrackInfo)} or
-         * {@link #deselectTrack(TrackInfo)}.
-         *
-         * @param player the player associated with this callback
-         * @param tracks the list of tracks. It can be empty
-         * @see #getTracks()
-         */
-        public void onTracksChanged(@NonNull SessionPlayer player,
-                @NonNull List<TrackInfo> tracks) {
-        }
-
-        /**
-         * Called when a track is selected.
-         *
-         * @param player the player associated with this callback
-         * @param trackInfo the selected track
-         * @see #selectTrack(TrackInfo)
-         */
-        public void onTrackSelected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
-        }
-
-        /**
-         * Called when a track is deselected.
-         * <p>
-         * This callback will generally be called only after calling
-         * {@link #deselectTrack(TrackInfo)}.
-         *
-         * @param player the player associated with this callback
-         * @param trackInfo the deselected track
-         * @see #deselectTrack(TrackInfo)
-         */
-        public void onTrackDeselected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
-        }
-    }
-
-    /**
-     * Result class of the asynchronous APIs.
-     *
-     * <p>Subclass may extend this class for providing more result and/or custom result code. For
-     * the custom result code, follow the convention below to avoid potential code duplication.
-     *
-     * <p>
-     *
-     * <ul>
-     *   <li>Predefined error code: Negative integers greater than -100. (i.e. -100 < code < 0)
-     *   <li>Custom error code: Negative integers equal to or less than -1000. (i.e. code < -1000)
-     *   <li>Predefined info code: Positive integers less than 100. (i.e. 0 < code < 100)
-     *   <li>Custom Info code: Positive integers equal to or greater than 1000. (i.e. code > +1000)
-     * </ul>
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    @SuppressWarnings("HiddenSuperclass")
-    public static class PlayerResult implements BaseResult {
-        /**
-         */
-        @IntDef(flag = false, /*prefix = "RESULT",*/ value = {
-                RESULT_SUCCESS,
-                RESULT_ERROR_UNKNOWN,
-                RESULT_ERROR_INVALID_STATE,
-                RESULT_ERROR_BAD_VALUE,
-                RESULT_ERROR_PERMISSION_DENIED,
-                RESULT_ERROR_IO,
-                RESULT_ERROR_NOT_SUPPORTED,
-                RESULT_INFO_SKIPPED})
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY)
-        public @interface ResultCode {}
-
-        private final int mResultCode;
-        private final long mCompletionTime;
-        private final MediaItem mItem;
-
-        /**
-         * Constructor that uses the current system clock as the completion time.
-         *
-         * @param resultCode result code. Recommends to use the standard code defined here.
-         * @param item media item when the command completed
-         */
-        // Note: resultCode is intentionally not annotated for subclass to return extra error codes.
-        public PlayerResult(int resultCode, @Nullable MediaItem item) {
-            this(resultCode, item, SystemClock.elapsedRealtime());
-        }
-
-        // Note: resultCode is intentionally not annotated for subclass to return extra error codes.
-        private PlayerResult(int resultCode, @Nullable MediaItem item, long completionTime) {
-            mResultCode = resultCode;
-            mItem = item;
-            mCompletionTime = completionTime;
-        }
-
-
-        /**
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        @NonNull
-        public static ListenableFuture<PlayerResult> createFuture(int resultCode) {
-            ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
-            result.set(new PlayerResult(resultCode, null));
-            return result;
-        }
-
-        /**
-         * Gets the result code.
-         * <p>
-         * Subclass of the {@link SessionPlayer} may have defined customized extra code other than
-         * codes defined here. Check the documentation of the class that you're interested in.
-         *
-         * @return result code
-         * @see #RESULT_ERROR_UNKNOWN
-         * @see #RESULT_ERROR_INVALID_STATE
-         * @see #RESULT_ERROR_BAD_VALUE
-         * @see #RESULT_ERROR_PERMISSION_DENIED
-         * @see #RESULT_ERROR_IO
-         * @see #RESULT_ERROR_NOT_SUPPORTED
-         * @see #RESULT_INFO_SKIPPED
-         */
-        @Override
-        @ResultCode
-        public int getResultCode() {
-            return mResultCode;
-        }
-
-        /**
-         * Gets the completion time of the command. Being more specific, it's the same as
-         * {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
-         *
-         * @return completion time of the command
-         */
-        @Override
-        public long getCompletionTime() {
-            return mCompletionTime;
-        }
-
-        /**
-         * Gets the {@link MediaItem} for which the command was executed. In other words, this is
-         * the item sent as an argument of the command if any, otherwise the current media item when
-         * the command completed.
-         *
-         * @return media item when the command completed. Can be {@code null} for an error, or
-         *         the current media item was {@code null}
-         */
-        @Override
-        @Nullable
-        public MediaItem getMediaItem() {
-            return mItem;
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/SubtitleData.java b/media2/media2-common/src/main/java/androidx/media2/common/SubtitleData.java
deleted file mode 100644
index e6c71b5..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/SubtitleData.java
+++ /dev/null
@@ -1,144 +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.media2.common;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-
-/**
- * Class encapsulating subtitle data, as received through the {@link
- * SessionPlayer.PlayerCallback#onSubtitleData} interface. The subtitle data includes:
- *
- * <ul>
- *   <li>the start time (in microseconds) of the data
- *   <li>the duration (in microseconds) of the data
- *   <li>the actual data.
- * </ul>
- *
- * The data is stored in a byte-array, and is encoded in one of the supported in-band subtitle
- * formats. The subtitle encoding is determined by the MIME type of the {@link
- * SessionPlayer.TrackInfo} of the subtitle track, one of {@link
- * android.media.MediaFormat#MIMETYPE_TEXT_CEA_608} or {@link
- * android.media.MediaFormat#MIMETYPE_TEXT_CEA_708}.
- *
- * <p>Here is an example of iterating over the tracks of a {@link SessionPlayer}, and checking which
- * encoding is used for the subtitle tracks:
- *
- * <p>
- *
- * <pre class="prettyprint">
- * // Initialize instance of player that extends SessionPlayer
- * SessionPlayerExtension player = new SessionPlayerExtension();
- *
- * final TrackInfo[] trackInfos = player.getTrackInfo();
- * for (TrackInfo info : trackInfo) {
- *     if (info.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
- *         final String mime = info.getFormat().getString(MediaFormat.KEY_MIME);
- *         if ("text/cea-608".equals(mime) {
- *             // subtitle encoding is CEA 608
- *         } else if ("text/cea-708".equals(mime) {
- *             // subtitle encoding is CEA 708
- *         }
- *     }
- * }
- * </pre>
- *
- * <p>
- *
- * @see SessionPlayer#registerPlayerCallback(Executor, SessionPlayer.PlayerCallback)
- * @see SessionPlayer.PlayerCallback#onSubtitleData(SessionPlayer, MediaItem,
- *     SessionPlayer.TrackInfo, SubtitleData)
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class SubtitleData implements VersionedParcelable {
-    private static final String TAG = "SubtitleData";
-
-    @ParcelField(1)
-    long mStartTimeUs;
-    @ParcelField(2)
-    long mDurationUs;
-    @ParcelField(3)
-    byte[] mData;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Used for VersionedParcelable
-     */
-    SubtitleData() {
-    }
-
-    public SubtitleData(long startTimeUs, long durationUs, @NonNull byte[] data) {
-        mStartTimeUs = startTimeUs;
-        mDurationUs = durationUs;
-        mData = data;
-    }
-
-    /**
-     * Returns the media time at which the subtitle should be displayed, expressed in microseconds.
-     * @return the display start time for the subtitle
-     */
-    public long getStartTimeUs() {
-        return mStartTimeUs;
-    }
-
-    /**
-     * Returns the duration in microsecond during which the subtitle should be displayed.
-     * @return the display duration for the subtitle
-     */
-    public long getDurationUs() {
-        return mDurationUs;
-    }
-
-    /**
-     * Returns the encoded data for the subtitle content.
-     * Encoding format depends on the subtitle type, refer to
-     * <a href="https://en.wikipedia.org/wiki/CEA-708">CEA 708</a>, and
-     * <a href="https://en.wikipedia.org/wiki/EIA-608">CEA/EIA 608</a> defined by the MIME type
-     * of the subtitle track.
-     * @return the encoded subtitle data
-     */
-    @NonNull
-    public byte[] getData() {
-        return mData;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        SubtitleData that = (SubtitleData) o;
-        return mStartTimeUs == that.mStartTimeUs
-                && mDurationUs == that.mDurationUs
-                && Arrays.equals(mData, that.mData);
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mStartTimeUs, mDurationUs, Arrays.hashCode(mData));
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/UriMediaItem.java b/media2/media2-common/src/main/java/androidx/media2/common/UriMediaItem.java
deleted file mode 100644
index 5e2273c..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/UriMediaItem.java
+++ /dev/null
@@ -1,187 +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.media2.common;
-
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.net.HttpCookie;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Structure for media item descriptor for {@link Uri}.
- *
- * <p>Users should use {@link Builder} to create {@link UriMediaItem}.
- *
- * <p>You cannot directly send this object across the process through {@link ParcelUtils}. See
- * {@link MediaItem} for detail.
- *
- * @see MediaItem
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class UriMediaItem extends MediaItem {
-    private final Uri mUri;
-    private final Map<String, String> mUriHeader;
-    private final List<HttpCookie> mUriCookies;
-
-    UriMediaItem(Builder builder) {
-        super(builder);
-        mUri = builder.mUri;
-        mUriHeader = builder.mUriHeader;
-        mUriCookies = builder.mUriCookies;
-    }
-
-    /**
-     * Return the Uri of this media item.
-     * @return the Uri of this media item
-     */
-    @NonNull
-    public Uri getUri() {
-        return mUri;
-    }
-
-    /**
-     * Return the Uri headers of this media item.
-     * @return the Uri headers of this media item
-     */
-    @Nullable
-    public Map<String, String> getUriHeaders() {
-        if (mUriHeader == null) {
-            return null;
-        }
-        return new HashMap<String, String>(mUriHeader);
-    }
-
-    /**
-     * Return the Uri cookies of this media item.
-     * @return the Uri cookies of this media item
-     */
-    @Nullable
-    public List<HttpCookie> getUriCookies() {
-        if (mUriCookies == null) {
-            return null;
-        }
-        return new ArrayList<HttpCookie>(mUriCookies);
-    }
-
-    /**
-     * This Builder class simplifies the creation of a {@link UriMediaItem} object.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder extends MediaItem.Builder {
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        Uri mUri;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        Map<String, String> mUriHeader;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        List<HttpCookie> mUriCookies;
-
-        /**
-         * Creates a new Builder object with a content Uri.
-         *
-         * @param uri the Content URI of the data you want to play
-         */
-        public Builder(@NonNull Uri uri) {
-            this(uri, null, null);
-        }
-
-        /**
-         * Creates a new Builder object with a content Uri.
-         *
-         * To provide cookies for the subsequent HTTP requests, you can install your own default
-         * cookie handler and use other variants of setMediaItem APIs instead.
-         *
-         * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
-         * but that can be changed with key/value pairs through the headers parameter with
-         * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
-         * disallow or allow cross domain redirection.
-         *
-         * @param uri the Content URI of the data you want to play
-         * @param headers the headers to be sent together with the request for the data
-         *                The headers must not include cookies. Instead, use the cookies param.
-         * @param cookies the cookies to be sent together with the request
-         * @throws IllegalArgumentException if the cookie handler is not of CookieManager type
-         *                                  when cookies are provided.
-         */
-        public Builder(@NonNull Uri uri, @Nullable Map<String, String> headers,
-                @Nullable List<HttpCookie> cookies) {
-            Preconditions.checkNotNull(uri, "uri cannot be null");
-            mUri = uri;
-            if (cookies != null) {
-                CookieHandler cookieHandler = CookieHandler.getDefault();
-                if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
-                    throw new IllegalArgumentException(
-                            "The cookie handler has to be of CookieManager type "
-                                    + "when cookies are provided");
-                }
-            }
-
-            mUri = uri;
-            if (headers != null) {
-                mUriHeader = new HashMap<String, String>(headers);
-            }
-            if (cookies != null) {
-                mUriCookies = new ArrayList<HttpCookie>(cookies);
-            }
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setMetadata(@Nullable MediaMetadata metadata) {
-            return (Builder) super.setMetadata(metadata);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setStartPosition(long position) {
-            return (Builder) super.setStartPosition(position);
-        }
-
-        // Override just to change return type.
-        @Override
-        @NonNull
-        public Builder setEndPosition(long position) {
-            return (Builder) super.setEndPosition(position);
-        }
-
-        /**
-         * @return A new UriMediaItem with values supplied by the Builder.
-         */
-        @Override
-        @NonNull
-        public UriMediaItem build() {
-            return new UriMediaItem(this);
-        }
-    }
-}
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/VideoSize.java b/media2/media2-common/src/main/java/androidx/media2/common/VideoSize.java
deleted file mode 100644
index b14546f..0000000
--- a/media2/media2-common/src/main/java/androidx/media2/common/VideoSize.java
+++ /dev/null
@@ -1,122 +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.media2.common;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * Immutable class for describing video size.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public class VideoSize implements VersionedParcelable {
-    @ParcelField(1)
-    int mWidth;
-    @ParcelField(2)
-    int mHeight;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Used for VersionedParcelable
-     */
-    VideoSize() {
-    }
-
-    /**
-     * Creates a new immutable VideoSize instance.
-     *
-     * @param width The width of the video
-     * @param height The height of the video
-     */
-    public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) {
-        if (width < 0) {
-            throw new IllegalArgumentException("width can not be negative");
-        }
-        if (height < 0) {
-            throw new IllegalArgumentException("height can not be negative");
-        }
-        mWidth = width;
-        mHeight = height;
-    }
-
-    /**
-     * Returns the width of the video.
-     */
-    @IntRange(from = 0)
-    public int getWidth() {
-        return mWidth;
-    }
-
-    /**
-     * Returns the height of the video.
-     */
-    @IntRange(from = 0)
-    public int getHeight() {
-        return mHeight;
-    }
-
-    /**
-     * Checks if this video size is equal to another video size.
-     * <p>
-     * Two video sizes are equal if and only if both their widths and heights are
-     * equal.
-     * <p>
-     * A video size object is never equal to any other type of object.
-     *
-     * @return {@code true} if the objects were equal, {@code false} otherwise
-     */
-    @Override
-    public boolean equals(@Nullable final Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof VideoSize) {
-            VideoSize other = (VideoSize) obj;
-            return mWidth == other.mWidth && mHeight == other.mHeight;
-        }
-        return false;
-    }
-
-    /**
-     * Return the video size represented as a string with the format {@code "WxH"}
-     *
-     * @return string representation of the video size
-     */
-    @NonNull
-    @Override
-    public String toString() {
-        return mWidth + "x" + mHeight;
-    }
-
-    @Override
-    public int hashCode() {
-        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing
-        return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
-    }
-}
diff --git a/media2/media2-common/src/main/stableAidl/androidx/media2/common/ParcelImplListSlice.aidl b/media2/media2-common/src/main/stableAidl/androidx/media2/common/ParcelImplListSlice.aidl
deleted file mode 100644
index 220dda4..0000000
--- a/media2/media2-common/src/main/stableAidl/androidx/media2/common/ParcelImplListSlice.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright 2023, 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.media2.common;
-
-@JavaOnlyStableParcelable parcelable ParcelImplListSlice;
diff --git a/media2/media2-exoplayer/api/1.0.0-beta01.txt b/media2/media2-exoplayer/api/1.0.0-beta01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/1.0.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/1.0.0-beta02.txt b/media2/media2-exoplayer/api/1.0.0-beta02.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/1.0.0-beta03.txt b/media2/media2-exoplayer/api/1.0.0-beta03.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/1.0.0-beta03.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/1.0.0-rc01.txt b/media2/media2-exoplayer/api/1.0.0-rc01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/1.2.0-beta01.txt b/media2/media2-exoplayer/api/1.2.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/media2/media2-exoplayer/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/1.3.0-beta01.txt b/media2/media2-exoplayer/api/1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/media2/media2-exoplayer/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/res-1.0.0-beta01.txt b/media2/media2-exoplayer/api/res-1.0.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.0.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.0.0-beta02.txt b/media2/media2-exoplayer/api/res-1.0.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.0.0-beta02.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.0.0-beta03.txt b/media2/media2-exoplayer/api/res-1.0.0-beta03.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.0.0-beta03.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.0.0-rc01.txt b/media2/media2-exoplayer/api/res-1.0.0-rc01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.0.0-rc01.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.1.0-beta01.txt b/media2/media2-exoplayer/api/res-1.1.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.1.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.2.0-beta01.txt b/media2/media2-exoplayer/api/res-1.2.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.2.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-1.3.0-beta01.txt b/media2/media2-exoplayer/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/res-current.txt b/media2/media2-exoplayer/api/res-current.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-exoplayer/api/res-current.txt
+++ /dev/null
diff --git a/media2/media2-exoplayer/api/restricted_1.0.0-beta01.txt b/media2/media2-exoplayer/api/restricted_1.0.0-beta01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/restricted_1.0.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/restricted_1.0.0-beta02.txt b/media2/media2-exoplayer/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/restricted_1.0.0-beta03.txt b/media2/media2-exoplayer/api/restricted_1.0.0-beta03.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/restricted_1.0.0-beta03.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/restricted_1.0.0-rc01.txt b/media2/media2-exoplayer/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/media2/media2-exoplayer/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/media2/media2-exoplayer/api/restricted_1.1.0-beta01.txt b/media2/media2-exoplayer/api/restricted_1.1.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/media2/media2-exoplayer/api/restricted_1.1.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt b/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/restricted_1.3.0-beta01.txt b/media2/media2-exoplayer/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/media2/media2-exoplayer/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/media2/media2-exoplayer/build.gradle b/media2/media2-exoplayer/build.gradle
deleted file mode 100644
index 1defb33..0000000
--- a/media2/media2-exoplayer/build.gradle
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2018 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.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-android {
-    buildTypes.all {
-        consumerProguardFiles "proguard-rules.pro"
-    }
-    namespace "androidx.media2.exoplayer.external"
-}
-
-dependencies {
-    compileOnly(libs.checkerframework)
-
-    api files("src/main/libs/exoplayer-media2.jar")
-}
-
-androidx {
-    name = "Media2 ExoPlayer"
-    publish = Publish.SNAPSHOT_AND_RELEASE
-    inceptionYear = "2018"
-    description = "Repackaged ExoPlayer for 'media2' artifact"
-    metalavaK2UastEnabled = true
-}
diff --git a/media2/media2-exoplayer/proguard-rules.pro b/media2/media2-exoplayer/proguard-rules.pro
deleted file mode 120000
index 607cd95..0000000
--- a/media2/media2-exoplayer/proguard-rules.pro
+++ /dev/null
@@ -1 +0,0 @@
-../../../../prebuilts/androidx/exoplayer/proguard-rules.pro
\ No newline at end of file
diff --git a/media2/media2-exoplayer/src/main/libs/LICENSE b/media2/media2-exoplayer/src/main/libs/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/media2/media2-exoplayer/src/main/libs/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
diff --git a/media2/media2-exoplayer/src/main/libs/README.md b/media2/media2-exoplayer/src/main/libs/README.md
deleted file mode 120000
index 59fecee..0000000
--- a/media2/media2-exoplayer/src/main/libs/README.md
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../prebuilts/androidx/exoplayer/README.md
\ No newline at end of file
diff --git a/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar b/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar
deleted file mode 120000
index 29a5cfb..0000000
--- a/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../prebuilts/androidx/exoplayer/exoplayer-media2.jar
\ No newline at end of file
diff --git a/media2/media2-player/api/1.0.0-beta01.txt b/media2/media2-player/api/1.0.0-beta01.txt
deleted file mode 100644
index a292cc3..0000000
--- a/media2/media2-player/api/1.0.0-beta01.txt
+++ /dev/null
@@ -1,135 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-player/api/1.0.0-beta02.txt b/media2/media2-player/api/1.0.0-beta02.txt
deleted file mode 100644
index 5c94240..0000000
--- a/media2/media2-player/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,135 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-player/api/1.0.0-rc01.txt b/media2/media2-player/api/1.0.0-rc01.txt
deleted file mode 100644
index a292cc3..0000000
--- a/media2/media2-player/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,135 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
diff --git a/media2/media2-player/api/1.1.0-beta01.txt b/media2/media2-player/api/1.1.0-beta01.txt
deleted file mode 100644
index c5e968d..0000000
--- a/media2/media2-player/api/1.1.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/1.2.0-beta01.txt b/media2/media2-player/api/1.2.0-beta01.txt
deleted file mode 100644
index c5e968d..0000000
--- a/media2/media2-player/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/1.3.0-beta01.txt b/media2/media2-player/api/1.3.0-beta01.txt
deleted file mode 100644
index 0b2be00..0000000
--- a/media2/media2-player/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  @Deprecated public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public MediaPlayer(android.content.Context);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getAudioSessionId();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public float getMaxPlayerVolume();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @Deprecated @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public float getPlayerVolume();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method @Deprecated public androidx.media2.player.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public void reset();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field @Deprecated public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @Deprecated public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @Deprecated public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @Deprecated public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field @Deprecated public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field @Deprecated public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field @Deprecated public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field @Deprecated public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field @Deprecated public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field @Deprecated public static final int SEEK_CLOSEST = 3; // 0x3
-    field @Deprecated public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field @Deprecated public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field @Deprecated public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @Deprecated public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public MediaPlayer.PlayerCallback();
-    method @Deprecated public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method @Deprecated public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @Deprecated public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-  }
-
-  @Deprecated public final class MediaTimestamp {
-    method @Deprecated public long getAnchorMediaTimeUs();
-    method @Deprecated public long getAnchorSystemNanoTime();
-    method @Deprecated public float getMediaClockRate();
-    field @Deprecated public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  @Deprecated public final class PlaybackParams {
-    method @Deprecated public Integer? getAudioFallbackMode();
-    method @Deprecated public Float? getPitch();
-    method @Deprecated public Float? getSpeed();
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @Deprecated public static final class PlaybackParams.Builder {
-    ctor @Deprecated public PlaybackParams.Builder();
-    ctor @Deprecated public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method @Deprecated public androidx.media2.player.PlaybackParams build();
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  @Deprecated public class TimedMetaData {
-    method @Deprecated public byte[]! getMetaData();
-    method @Deprecated public long getTimestamp();
-  }
-
-  @Deprecated public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor @Deprecated public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/api_lint.ignore b/media2/media2-player/api/api_lint.ignore
deleted file mode 100644
index 1e12661..0000000
--- a/media2/media2-player/api/api_lint.ignore
+++ /dev/null
@@ -1,67 +0,0 @@
-// Baseline format: 1.0
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#addPlaylistItem(int, androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#attachAuxEffect(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#movePlaylistItem(int, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#pause():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#play():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#prepare():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#removePlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#replacePlaylistItem(int, androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#seekTo(long):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#seekTo(long, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#selectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setAudioAttributes(androidx.media.AudioAttributesCompat):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setAudioSessionId(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setAuxEffectSendLevel(float):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setMediaItem(androidx.media2.common.MediaItem):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setPlaybackParams(androidx.media2.player.PlaybackParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setPlaybackSpeed(float):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setPlayerVolume(float):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setPlaylist(java.util.List<androidx.media2.common.MediaItem>, androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setRepeatMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setShuffleMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#setSurface(android.view.Surface):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#skipToNextPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#skipToPlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#skipToPreviousPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.player.MediaPlayer#updatePlaylistMetadata(androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-
-
-AutoBoxing: androidx.media2.player.PlaybackParams#getAudioFallbackMode():
-    Must avoid boxed primitives (`java.lang.Integer`)
-AutoBoxing: androidx.media2.player.PlaybackParams#getPitch():
-    Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.media2.player.PlaybackParams#getSpeed():
-    Must avoid boxed primitives (`java.lang.Float`)
-
-
-MissingNullability: androidx.media2.player.TimedMetaData#getMetaData():
-    Missing nullability on method `getMetaData` return
diff --git a/media2/media2-player/api/current.txt b/media2/media2-player/api/current.txt
deleted file mode 100644
index 0b2be00..0000000
--- a/media2/media2-player/api/current.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  @Deprecated public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public MediaPlayer(android.content.Context);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getAudioSessionId();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public float getMaxPlayerVolume();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @Deprecated @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public float getPlayerVolume();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method @Deprecated public androidx.media2.player.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public void reset();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field @Deprecated public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @Deprecated public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @Deprecated public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @Deprecated public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field @Deprecated public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field @Deprecated public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field @Deprecated public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field @Deprecated public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field @Deprecated public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field @Deprecated public static final int SEEK_CLOSEST = 3; // 0x3
-    field @Deprecated public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field @Deprecated public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field @Deprecated public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @Deprecated public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public MediaPlayer.PlayerCallback();
-    method @Deprecated public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method @Deprecated public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @Deprecated public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
-  }
-
-  @Deprecated public final class MediaTimestamp {
-    method @Deprecated public long getAnchorMediaTimeUs();
-    method @Deprecated public long getAnchorSystemNanoTime();
-    method @Deprecated public float getMediaClockRate();
-    field @Deprecated public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  @Deprecated public final class PlaybackParams {
-    method @Deprecated public Integer? getAudioFallbackMode();
-    method @Deprecated public Float? getPitch();
-    method @Deprecated public Float? getSpeed();
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @Deprecated public static final class PlaybackParams.Builder {
-    ctor @Deprecated public PlaybackParams.Builder();
-    ctor @Deprecated public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method @Deprecated public androidx.media2.player.PlaybackParams build();
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  @Deprecated public class TimedMetaData {
-    method @Deprecated public byte[]! getMetaData();
-    method @Deprecated public long getTimestamp();
-  }
-
-  @Deprecated public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor @Deprecated public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/res-1.0.0-beta01.txt b/media2/media2-player/api/res-1.0.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.0.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-1.0.0-beta02.txt b/media2/media2-player/api/res-1.0.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.0.0-beta02.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-1.0.0-rc01.txt b/media2/media2-player/api/res-1.0.0-rc01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.0.0-rc01.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-1.1.0-beta01.txt b/media2/media2-player/api/res-1.1.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.1.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-1.2.0-beta01.txt b/media2/media2-player/api/res-1.2.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.2.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-1.3.0-beta01.txt b/media2/media2-player/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-player/api/res-current.txt b/media2/media2-player/api/res-current.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-player/api/res-current.txt
+++ /dev/null
diff --git a/media2/media2-player/api/restricted_1.0.0-beta01.txt b/media2/media2-player/api/restricted_1.0.0-beta01.txt
deleted file mode 100644
index 4a59b22..0000000
--- a/media2/media2-player/api/restricted_1.0.0-beta01.txt
+++ /dev/null
@@ -1,712 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media2.player.MediaPlayer.DrmInfo? getDrmInfo();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.PersistableBundle! getMetrics();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(@androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.player.MediaPlayer.DrmResult!> prepareDrm(java.util.UUID);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public byte[]? provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void releaseDrm() throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, @androidx.media2.player.MediaPlayer.SeekMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer.OnDrmConfigHelper?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_END = 5; // 0x5
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_LIST_END = 6; // 0x6
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_REPEAT = 7; // 0x7
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_START = 2; // 0x2
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; // 0x384
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!> getPssh();
-    method public java.util.List<java.util.UUID!> getSupportedSchemes();
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.DrmResult extends androidx.media2.common.SessionPlayer.PlayerResult {
-    ctor public MediaPlayer.DrmResult(@androidx.media2.player.MediaPlayer.DrmResult.DrmResultCode int, androidx.media2.common.MediaItem);
-    field public static final int RESULT_ERROR_PREPARATION_ERROR = -1003; // 0xfffffc15
-    field public static final int RESULT_ERROR_PROVISIONING_NETWORK_ERROR = -1001; // 0xfffffc17
-    field public static final int RESULT_ERROR_PROVISIONING_SERVER_ERROR = -1002; // 0xfffffc16
-    field public static final int RESULT_ERROR_RESOURCE_BUSY = -1005; // 0xfffffc13
-    field public static final int RESULT_ERROR_UNSUPPORTED_SCHEME = -1004; // 0xfffffc14
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PREPARATION_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.DrmResult.DrmResultCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer.PLAYER_ERROR_IO, androidx.media2.player.MediaPlayer.PLAYER_ERROR_MALFORMED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_LIST_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_REPEAT, androidx.media2.player.MediaPlayer.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_TIMED_TEXT_ERROR, androidx.media2.player.MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaInfo {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer.NoDrmSchemeException(String?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface MediaPlayer.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem);
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void onDrmInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaPlayer.DrmInfo);
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void onSubtitleData(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.SubtitleData);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.SeekMode {
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaPlayer.TrackInfo(int, androidx.media2.common.MediaItem!, int, android.media.MediaFormat!);
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method @androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; // 0x3
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.TrackInfo.MediaTrackType {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class MediaPlayer2 {
-    ctor protected MediaPlayer2();
-    method public abstract Object! attachAuxEffect(int);
-    method public abstract boolean cancel(Object!);
-    method public abstract void clearDrmEventCallback();
-    method public abstract void clearEventCallback();
-    method public abstract void clearPendingCommands();
-    method public abstract void close();
-    method public static androidx.media2.player.MediaPlayer2! create(android.content.Context);
-    method public abstract Object! deselectTrack(int);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract int getAudioSessionId();
-    method public abstract long getBufferedPosition();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public abstract long getCurrentPosition();
-    method public abstract androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public abstract android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) public abstract android.os.PersistableBundle! getMetrics();
-    method public abstract androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public abstract float getPlayerVolume();
-    method public abstract int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public abstract int getState();
-    method public abstract androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public abstract java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public androidx.media2.player.MediaPlayer.TrackInfo! getTrackInfo(int);
-    method public abstract int getVideoHeight();
-    method public abstract int getVideoWidth();
-    method public abstract Object! loopCurrent(boolean);
-    method public abstract Object! notifyWhenCommandLabelReached(Object);
-    method public abstract Object! pause();
-    method public abstract Object! play();
-    method public abstract Object! prepare();
-    method public abstract Object! prepareDrm(java.util.UUID);
-    method public abstract byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void reset();
-    method public abstract void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long);
-    method public abstract Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public abstract Object! selectTrack(int);
-    method public abstract Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract Object! setAudioSessionId(int);
-    method public abstract Object! setAuxEffectSendLevel(float);
-    method public abstract void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public abstract void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public abstract Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public abstract void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public abstract Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public abstract Object! setPlayerVolume(float);
-    method public abstract Object! setSurface(android.view.Surface?);
-    method public abstract Object! skipToNext();
-    field public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; // 0x1
-    field public static final int CALL_COMPLETED_DESELECT_TRACK = 2; // 0x2
-    field public static final int CALL_COMPLETED_LOOP_CURRENT = 3; // 0x3
-    field public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 1000; // 0x3e8
-    field public static final int CALL_COMPLETED_PAUSE = 4; // 0x4
-    field public static final int CALL_COMPLETED_PLAY = 5; // 0x5
-    field public static final int CALL_COMPLETED_PREPARE = 6; // 0x6
-    field public static final int CALL_COMPLETED_PREPARE_DRM = 1001; // 0x3e9
-    field public static final int CALL_COMPLETED_SEEK_TO = 14; // 0xe
-    field public static final int CALL_COMPLETED_SELECT_TRACK = 15; // 0xf
-    field public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; // 0x10
-    field public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; // 0x11
-    field public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; // 0x12
-    field public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; // 0x13
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; // 0x16
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; // 0x17
-    field public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; // 0x18
-    field public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; // 0x1a
-    field public static final int CALL_COMPLETED_SET_SURFACE = 27; // 0x1b
-    field public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; // 0x1d
-    field public static final int CALL_STATUS_BAD_VALUE = 2; // 0x2
-    field public static final int CALL_STATUS_ERROR_IO = 4; // 0x4
-    field public static final int CALL_STATUS_ERROR_UNKNOWN = -2147483648; // 0x80000000
-    field public static final int CALL_STATUS_INVALID_OPERATION = 1; // 0x1
-    field public static final int CALL_STATUS_NO_ERROR = 0; // 0x0
-    field public static final int CALL_STATUS_PERMISSION_DENIED = 3; // 0x3
-    field public static final int CALL_STATUS_SKIPPED = 5; // 0x5
-    field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int MEDIA_ERROR_SYSTEM = -2147483648; // 0x80000000
-    field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_DATA_SOURCE_END = 5; // 0x5
-    field public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6; // 0x6
-    field public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7; // 0x7
-    field public static final int MEDIA_INFO_DATA_SOURCE_START = 2; // 0x2
-    field public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; // 0x384
-    field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int PLAYER_STATE_ERROR = 1005; // 0x3ed
-    field public static final int PLAYER_STATE_IDLE = 1001; // 0x3e9
-    field public static final int PLAYER_STATE_PAUSED = 1003; // 0x3eb
-    field public static final int PLAYER_STATE_PLAYING = 1004; // 0x3ec
-    field public static final int PLAYER_STATE_PREPARED = 1002; // 0x3ea
-    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
-    field public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5; // 0x5
-    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
-    field public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4; // 0x4
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-    field public static final int SEPARATE_CALL_COMPLETE_CALLBACK_START = 1000; // 0x3e8
-    field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PAUSE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PLAY, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SEEK_TO, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_SURFACE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE_DRM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallCompleted {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_STATUS_NO_ERROR, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.CALL_STATUS_INVALID_OPERATION, androidx.media2.player.MediaPlayer2.CALL_STATUS_BAD_VALUE, androidx.media2.player.MediaPlayer2.CALL_STATUS_PERMISSION_DENIED, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_IO, androidx.media2.player.MediaPlayer2.CALL_STATUS_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallStatus {
-  }
-
-  public abstract static class MediaPlayer2.DrmEventCallback {
-    ctor public MediaPlayer2.DrmEventCallback();
-    method public void onDrmInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaPlayer2.DrmInfo!);
-    method public void onDrmPrepared(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.PrepareDrmStatusCode int);
-  }
-
-  public abstract static class MediaPlayer2.DrmInfo {
-    ctor public MediaPlayer2.DrmInfo();
-    method public abstract java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public abstract java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public abstract static class MediaPlayer2.EventCallback {
-    ctor public MediaPlayer2.EventCallback();
-    method public void onCallCompleted(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.CallCompleted int, @androidx.media2.player.MediaPlayer2.CallStatus int);
-    method public void onCommandLabelReached(androidx.media2.player.MediaPlayer2!, Object);
-    method public void onError(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onSubtitleData(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.SubtitleData);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, int, int);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_IO, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_MALFORMED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_TIMED_OUT, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_SYSTEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT, androidx.media2.player.MediaPlayer2.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_TIMED_TEXT_ERROR, androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaInfo {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PLAYER_STATE_IDLE, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PREPARED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PAUSED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PLAYING, androidx.media2.player.MediaPlayer2.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaPlayer2State {
-  }
-
-  public static final class MediaPlayer2.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  public static class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer2.NoDrmSchemeException(String!);
-  }
-
-  public static interface MediaPlayer2.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PREPARATION_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.PrepareDrmStatusCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer2.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.SeekMode {
-  }
-
-  public abstract static class MediaPlayer2.TrackInfo {
-    ctor public MediaPlayer2.TrackInfo();
-    method public abstract android.media.MediaFormat! getFormat();
-    method public abstract String! getLanguage();
-    method public abstract int getTrackType();
-    method public abstract String toString();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; // 0x3
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class MediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public MediaPlayer2Impl(android.content.Context!);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public long getDuration();
-    method public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object!);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void reset();
-    method public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-  public static final class MediaPlayer2Impl.DrmInfoImpl extends androidx.media2.player.MediaPlayer2.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public final class MediaTimestamp {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaTimestamp(long, long, float);
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method @androidx.media2.player.PlaybackParams.AudioFallbackMode public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.PlaybackParams! getPlaybackParams();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_MUTE, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_FAIL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackParams.AudioFallbackMode {
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PlaybackParams.Builder(android.media.PlaybackParams!);
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(@androidx.media2.player.PlaybackParams.AudioFallbackMode int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SubtitleData {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public SubtitleData(androidx.media2.player.MediaPlayer.TrackInfo, long, long, byte[]!);
-    method public byte[] getData();
-    method public long getDurationUs();
-    method public long getStartTimeUs();
-    method public androidx.media2.player.MediaPlayer.TrackInfo getTrackInfo();
-    field public static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
-    field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
-    field public static final String MIMETYPE_TEXT_VTT = "text/vtt";
-  }
-
-  public class TimedMetaData {
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(android.media.TimedMetaData!);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(long, byte[]!);
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
-package androidx.media2.player.common {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TrackInfoImpl extends androidx.media2.player.MediaPlayer2.TrackInfo {
-    ctor public TrackInfoImpl(int, android.media.MediaFormat!);
-    method public android.media.MediaFormat! getFormat();
-    method public String! getLanguage();
-    method public int getTrackType();
-  }
-
-}
-
-package androidx.media2.player.exoplayer {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class DataSourceCallbackDataSource {
-    ctor public DataSourceCallbackDataSource(androidx.media2.common.DataSourceCallback!);
-    method public void close();
-    method public android.net.Uri! getUri();
-    method public long open(DataSpec) throws java.io.IOException;
-    method public int read(byte[]!, int, int) throws java.io.IOException;
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ExoPlayerMediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public ExoPlayerMediaPlayer2Impl(android.content.Context);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat! getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem! getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]!, byte[]!, String!, int, java.util.Map<java.lang.String!,java.lang.String!>!);
-    method public String getDrmPropertyString(String);
-    method public long getDuration();
-    method @RequiresApi(21) public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp! getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object);
-    method public void onBandwidthSample(androidx.media2.common.MediaItem!, int);
-    method public void onBufferingEnded(androidx.media2.common.MediaItem!);
-    method public void onBufferingStarted(androidx.media2.common.MediaItem!);
-    method public void onBufferingUpdate(androidx.media2.common.MediaItem!, int);
-    method public void onError(androidx.media2.common.MediaItem!, int);
-    method public void onLoop(androidx.media2.common.MediaItem!);
-    method public void onMediaItemEnded(androidx.media2.common.MediaItem!);
-    method public void onMediaItemStartedAsNext(androidx.media2.common.MediaItem!);
-    method public void onMediaTimeDiscontinuity(androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onMetadataChanged(androidx.media2.common.MediaItem!);
-    method public void onPlaybackEnded(androidx.media2.common.MediaItem!);
-    method public void onPrepared(androidx.media2.common.MediaItem!);
-    method public void onSeekCompleted();
-    method public void onSubtitleData(androidx.media2.common.MediaItem!, androidx.media2.player.SubtitleData!);
-    method public void onTimedMetadata(androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoRenderingStart(androidx.media2.common.MediaItem!);
-    method public void onVideoSizeChanged(androidx.media2.common.MediaItem!, int, int);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]);
-    method public void releaseDrm();
-    method public void reset();
-    method public void restoreDrmKeys(byte[]);
-    method public Object! seekTo(long, int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String);
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-}
-
-package androidx.media2.player.subtitle {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class Cea708CaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public Cea708CaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ClosedCaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public ClosedCaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface MediaTimeProvider {
-    method public void cancelNotifications(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public long getCurrentTimeUs(boolean, boolean) throws java.lang.IllegalStateException;
-    method public void notifyAt(long, androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public void scheduleUpdate(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    field public static final long NO_TIME = -1L; // 0xffffffffffffffffL
-  }
-
-  public static interface MediaTimeProvider.OnMediaTimeListener {
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SubtitleController {
-    ctor public SubtitleController(android.content.Context!);
-    ctor public SubtitleController(android.content.Context!, androidx.media2.player.subtitle.MediaTimeProvider!, androidx.media2.player.subtitle.SubtitleController.Listener!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! addTrack(android.media.MediaFormat!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! getDefaultTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack! getSelectedTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack![]! getTracks();
-    method public boolean hasRendererFor(android.media.MediaFormat!);
-    method public void hide();
-    method public void registerRenderer(androidx.media2.player.subtitle.SubtitleController.Renderer!);
-    method public void reset();
-    method public void selectDefaultTrack();
-    method public boolean selectTrack(androidx.media2.player.subtitle.SubtitleTrack!);
-    method public void setAnchor(androidx.media2.player.subtitle.SubtitleController.Anchor!);
-    method public void show();
-  }
-
-  public static interface SubtitleController.Anchor {
-    method public android.os.Looper! getSubtitleLooper();
-    method public void setSubtitleWidget(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-  public static interface SubtitleController.Listener {
-    method public void onSubtitleTrackSelected(androidx.media2.player.subtitle.SubtitleTrack!);
-  }
-
-  public abstract static class SubtitleController.Renderer {
-    ctor public SubtitleController.Renderer();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public abstract boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SubtitleTrack implements androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener {
-    ctor public SubtitleTrack(android.media.MediaFormat!);
-    method protected boolean addCue(androidx.media2.player.subtitle.SubtitleTrack.Cue!);
-    method protected void clearActiveCues();
-    method protected void finishedRun(long);
-    method public final android.media.MediaFormat! getFormat();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget! getRenderingWidget();
-    method public int getTrackType();
-    method public void hide();
-    method public void onData(androidx.media2.player.SubtitleData!);
-    method protected abstract void onData(byte[]!, boolean, long);
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-    method protected void scheduleTimedEvents();
-    method public void setRunDiscardTimeMs(long, long);
-    method public void setTimeProvider(androidx.media2.player.subtitle.MediaTimeProvider!);
-    method public void show();
-    method protected void updateActiveCues(boolean, long);
-    method public abstract void updateView(java.util.ArrayList<androidx.media2.player.subtitle.SubtitleTrack.Cue!>!);
-    field public boolean DEBUG;
-    field protected android.os.Handler! mHandler;
-    field protected androidx.media2.player.subtitle.MediaTimeProvider! mTimeProvider;
-    field protected boolean mVisible;
-  }
-
-  public static class SubtitleTrack.Cue {
-    ctor public SubtitleTrack.Cue();
-    method public void onTime(long);
-    field public long mEndTimeMs;
-    field public long[]! mInnerTimesMs;
-    field public androidx.media2.player.subtitle.SubtitleTrack.Cue! mNextInRun;
-    field public long mRunID;
-    field public long mStartTimeMs;
-  }
-
-  public static interface SubtitleTrack.RenderingWidget {
-    method public void draw(android.graphics.Canvas!);
-    method public void onAttachedToWindow();
-    method public void onDetachedFromWindow();
-    method public void setOnChangedListener(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget.OnChangedListener!);
-    method public void setSize(int, int);
-    method public void setVisible(boolean);
-  }
-
-  public static interface SubtitleTrack.RenderingWidget.OnChangedListener {
-    method public void onChanged(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_1.0.0-beta02.txt b/media2/media2-player/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index a035571..0000000
--- a/media2/media2-player/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,695 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media2.player.MediaPlayer.DrmInfo? getDrmInfo();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.PersistableBundle! getMetrics();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(@androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.player.MediaPlayer.DrmResult!> prepareDrm(java.util.UUID);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public byte[]? provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void releaseDrm() throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, @androidx.media2.player.MediaPlayer.SeekMode int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer.OnDrmConfigHelper?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_END = 5; // 0x5
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_LIST_END = 6; // 0x6
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_REPEAT = 7; // 0x7
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_START = 2; // 0x2
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!> getPssh();
-    method public java.util.List<java.util.UUID!> getSupportedSchemes();
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.DrmResult extends androidx.media2.common.SessionPlayer.PlayerResult {
-    ctor public MediaPlayer.DrmResult(@androidx.media2.player.MediaPlayer.DrmResult.DrmResultCode int, androidx.media2.common.MediaItem);
-    field public static final int RESULT_ERROR_PREPARATION_ERROR = -1003; // 0xfffffc15
-    field public static final int RESULT_ERROR_PROVISIONING_NETWORK_ERROR = -1001; // 0xfffffc17
-    field public static final int RESULT_ERROR_PROVISIONING_SERVER_ERROR = -1002; // 0xfffffc16
-    field public static final int RESULT_ERROR_RESOURCE_BUSY = -1005; // 0xfffffc13
-    field public static final int RESULT_ERROR_UNSUPPORTED_SCHEME = -1004; // 0xfffffc14
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PREPARATION_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.DrmResult.DrmResultCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer.PLAYER_ERROR_IO, androidx.media2.player.MediaPlayer.PLAYER_ERROR_MALFORMED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_LIST_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_REPEAT, androidx.media2.player.MediaPlayer.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaInfo {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer.NoDrmSchemeException(String?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface MediaPlayer.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem);
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void onDrmInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaPlayer.DrmInfo);
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.SeekMode {
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaPlayer.TrackInfo(int, androidx.media2.common.MediaItem!, int, android.media.MediaFormat!);
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method @androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.TrackInfo.MediaTrackType {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class MediaPlayer2 {
-    ctor protected MediaPlayer2();
-    method public abstract Object! attachAuxEffect(int);
-    method public abstract boolean cancel(Object!);
-    method public abstract void clearDrmEventCallback();
-    method public abstract void clearEventCallback();
-    method public abstract void clearPendingCommands();
-    method public abstract void close();
-    method public static androidx.media2.player.MediaPlayer2! create(android.content.Context);
-    method public abstract Object! deselectTrack(int);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract int getAudioSessionId();
-    method public abstract long getBufferedPosition();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public abstract long getCurrentPosition();
-    method public abstract androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public abstract android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) public abstract android.os.PersistableBundle! getMetrics();
-    method public abstract androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public abstract float getPlayerVolume();
-    method public abstract int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public abstract int getState();
-    method public abstract androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public abstract java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public abstract int getVideoHeight();
-    method public abstract int getVideoWidth();
-    method public abstract Object! loopCurrent(boolean);
-    method public abstract Object! notifyWhenCommandLabelReached(Object);
-    method public abstract Object! pause();
-    method public abstract Object! play();
-    method public abstract Object! prepare();
-    method public abstract Object! prepareDrm(java.util.UUID);
-    method public abstract byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void reset();
-    method public abstract void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long);
-    method public abstract Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public abstract Object! selectTrack(int);
-    method public abstract Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract Object! setAudioSessionId(int);
-    method public abstract Object! setAuxEffectSendLevel(float);
-    method public abstract void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public abstract void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public abstract Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public abstract void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public abstract Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public abstract Object! setPlayerVolume(float);
-    method public abstract Object! setSurface(android.view.Surface?);
-    method public abstract Object! skipToNext();
-    field public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; // 0x1
-    field public static final int CALL_COMPLETED_DESELECT_TRACK = 2; // 0x2
-    field public static final int CALL_COMPLETED_LOOP_CURRENT = 3; // 0x3
-    field public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 1000; // 0x3e8
-    field public static final int CALL_COMPLETED_PAUSE = 4; // 0x4
-    field public static final int CALL_COMPLETED_PLAY = 5; // 0x5
-    field public static final int CALL_COMPLETED_PREPARE = 6; // 0x6
-    field public static final int CALL_COMPLETED_PREPARE_DRM = 1001; // 0x3e9
-    field public static final int CALL_COMPLETED_SEEK_TO = 14; // 0xe
-    field public static final int CALL_COMPLETED_SELECT_TRACK = 15; // 0xf
-    field public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; // 0x10
-    field public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; // 0x11
-    field public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; // 0x12
-    field public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; // 0x13
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; // 0x16
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; // 0x17
-    field public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; // 0x18
-    field public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; // 0x1a
-    field public static final int CALL_COMPLETED_SET_SURFACE = 27; // 0x1b
-    field public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; // 0x1d
-    field public static final int CALL_STATUS_BAD_VALUE = 2; // 0x2
-    field public static final int CALL_STATUS_ERROR_IO = 4; // 0x4
-    field public static final int CALL_STATUS_ERROR_UNKNOWN = -2147483648; // 0x80000000
-    field public static final int CALL_STATUS_INVALID_OPERATION = 1; // 0x1
-    field public static final int CALL_STATUS_NO_ERROR = 0; // 0x0
-    field public static final int CALL_STATUS_PERMISSION_DENIED = 3; // 0x3
-    field public static final int CALL_STATUS_SKIPPED = 5; // 0x5
-    field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int MEDIA_ERROR_SYSTEM = -2147483648; // 0x80000000
-    field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_DATA_SOURCE_END = 5; // 0x5
-    field public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6; // 0x6
-    field public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7; // 0x7
-    field public static final int MEDIA_INFO_DATA_SOURCE_START = 2; // 0x2
-    field public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int PLAYER_STATE_ERROR = 1005; // 0x3ed
-    field public static final int PLAYER_STATE_IDLE = 1001; // 0x3e9
-    field public static final int PLAYER_STATE_PAUSED = 1003; // 0x3eb
-    field public static final int PLAYER_STATE_PLAYING = 1004; // 0x3ec
-    field public static final int PLAYER_STATE_PREPARED = 1002; // 0x3ea
-    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
-    field public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5; // 0x5
-    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
-    field public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4; // 0x4
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-    field public static final int SEPARATE_CALL_COMPLETE_CALLBACK_START = 1000; // 0x3e8
-    field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PAUSE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PLAY, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SEEK_TO, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_SURFACE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE_DRM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallCompleted {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_STATUS_NO_ERROR, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.CALL_STATUS_INVALID_OPERATION, androidx.media2.player.MediaPlayer2.CALL_STATUS_BAD_VALUE, androidx.media2.player.MediaPlayer2.CALL_STATUS_PERMISSION_DENIED, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_IO, androidx.media2.player.MediaPlayer2.CALL_STATUS_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallStatus {
-  }
-
-  public abstract static class MediaPlayer2.DrmEventCallback {
-    ctor public MediaPlayer2.DrmEventCallback();
-    method public void onDrmInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaPlayer2.DrmInfo!);
-    method public void onDrmPrepared(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.PrepareDrmStatusCode int);
-  }
-
-  public abstract static class MediaPlayer2.DrmInfo {
-    ctor public MediaPlayer2.DrmInfo();
-    method public abstract java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public abstract java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public abstract static class MediaPlayer2.EventCallback {
-    ctor public MediaPlayer2.EventCallback();
-    method public void onCallCompleted(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.CallCompleted int, @androidx.media2.player.MediaPlayer2.CallStatus int);
-    method public void onCommandLabelReached(androidx.media2.player.MediaPlayer2!, Object);
-    method public void onError(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onSubtitleData(androidx.media2.player.MediaPlayer2, androidx.media2.common.MediaItem, int, androidx.media2.common.SubtitleData);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, int, int);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_IO, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_MALFORMED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_TIMED_OUT, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_SYSTEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT, androidx.media2.player.MediaPlayer2.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaInfo {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PLAYER_STATE_IDLE, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PREPARED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PAUSED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PLAYING, androidx.media2.player.MediaPlayer2.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaPlayer2State {
-  }
-
-  public static final class MediaPlayer2.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  public static class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer2.NoDrmSchemeException(String!);
-  }
-
-  public static interface MediaPlayer2.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PREPARATION_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.PrepareDrmStatusCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer2.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.SeekMode {
-  }
-
-  public abstract static class MediaPlayer2.TrackInfo {
-    ctor public MediaPlayer2.TrackInfo();
-    method public abstract android.media.MediaFormat! getFormat();
-    method public abstract String! getLanguage();
-    method public abstract int getTrackType();
-    method public abstract String toString();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class MediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public MediaPlayer2Impl(android.content.Context!);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public long getDuration();
-    method public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object!);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void reset();
-    method public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-  public static final class MediaPlayer2Impl.DrmInfoImpl extends androidx.media2.player.MediaPlayer2.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public final class MediaTimestamp {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaTimestamp(long, long, float);
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method @androidx.media2.player.PlaybackParams.AudioFallbackMode public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.PlaybackParams! getPlaybackParams();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_MUTE, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_FAIL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackParams.AudioFallbackMode {
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PlaybackParams.Builder(android.media.PlaybackParams!);
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(@androidx.media2.player.PlaybackParams.AudioFallbackMode int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(android.media.TimedMetaData!);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(long, byte[]!);
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
-package androidx.media2.player.common {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TrackInfoImpl extends androidx.media2.player.MediaPlayer2.TrackInfo {
-    ctor public TrackInfoImpl(int, android.media.MediaFormat!);
-    method public android.media.MediaFormat! getFormat();
-    method public String! getLanguage();
-    method public int getTrackType();
-  }
-
-}
-
-package androidx.media2.player.exoplayer {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class DataSourceCallbackDataSource {
-    ctor public DataSourceCallbackDataSource(androidx.media2.common.DataSourceCallback!);
-    method public void close();
-    method public android.net.Uri! getUri();
-    method public long open(DataSpec) throws java.io.IOException;
-    method public int read(byte[]!, int, int) throws java.io.IOException;
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ExoPlayerMediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public ExoPlayerMediaPlayer2Impl(android.content.Context);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat! getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem! getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]!, byte[]!, String!, int, java.util.Map<java.lang.String!,java.lang.String!>!);
-    method public String getDrmPropertyString(String);
-    method public long getDuration();
-    method @RequiresApi(21) public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp! getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object);
-    method public void onBandwidthSample(androidx.media2.common.MediaItem!, int);
-    method public void onBufferingEnded(androidx.media2.common.MediaItem!);
-    method public void onBufferingStarted(androidx.media2.common.MediaItem!);
-    method public void onBufferingUpdate(androidx.media2.common.MediaItem!, int);
-    method public void onError(androidx.media2.common.MediaItem!, int);
-    method public void onLoop(androidx.media2.common.MediaItem!);
-    method public void onMediaItemEnded(androidx.media2.common.MediaItem!);
-    method public void onMediaItemStartedAsNext(androidx.media2.common.MediaItem!);
-    method public void onMediaTimeDiscontinuity(androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onMetadataChanged(androidx.media2.common.MediaItem!);
-    method public void onPlaybackEnded(androidx.media2.common.MediaItem!);
-    method public void onPrepared(androidx.media2.common.MediaItem!);
-    method public void onSeekCompleted();
-    method public void onSubtitleData(androidx.media2.common.MediaItem!, int, androidx.media2.common.SubtitleData!);
-    method public void onTimedMetadata(androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoRenderingStart(androidx.media2.common.MediaItem!);
-    method public void onVideoSizeChanged(androidx.media2.common.MediaItem!, int, int);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]);
-    method public void releaseDrm();
-    method public void reset();
-    method public void restoreDrmKeys(byte[]);
-    method public Object! seekTo(long, int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String);
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-}
-
-package androidx.media2.player.subtitle {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class Cea708CaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public Cea708CaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ClosedCaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public ClosedCaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface MediaTimeProvider {
-    method public void cancelNotifications(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public long getCurrentTimeUs(boolean, boolean) throws java.lang.IllegalStateException;
-    method public void notifyAt(long, androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public void scheduleUpdate(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    field public static final long NO_TIME = -1L; // 0xffffffffffffffffL
-  }
-
-  public static interface MediaTimeProvider.OnMediaTimeListener {
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SubtitleController {
-    ctor public SubtitleController(android.content.Context!);
-    ctor public SubtitleController(android.content.Context!, androidx.media2.player.subtitle.MediaTimeProvider!, androidx.media2.player.subtitle.SubtitleController.Listener!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! addTrack(android.media.MediaFormat!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! getDefaultTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack! getSelectedTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack![]! getTracks();
-    method public boolean hasRendererFor(android.media.MediaFormat!);
-    method public void hide();
-    method public void registerRenderer(androidx.media2.player.subtitle.SubtitleController.Renderer!);
-    method public void reset();
-    method public void selectDefaultTrack();
-    method public boolean selectTrack(androidx.media2.player.subtitle.SubtitleTrack!);
-    method public void setAnchor(androidx.media2.player.subtitle.SubtitleController.Anchor!);
-    method public void show();
-  }
-
-  public static interface SubtitleController.Anchor {
-    method public android.os.Looper! getSubtitleLooper();
-    method public void setSubtitleWidget(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-  public static interface SubtitleController.Listener {
-    method public void onSubtitleTrackSelected(androidx.media2.player.subtitle.SubtitleTrack!);
-  }
-
-  public abstract static class SubtitleController.Renderer {
-    ctor public SubtitleController.Renderer();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public abstract boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SubtitleTrack implements androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener {
-    ctor public SubtitleTrack(android.media.MediaFormat!);
-    method protected boolean addCue(androidx.media2.player.subtitle.SubtitleTrack.Cue!);
-    method protected void clearActiveCues();
-    method protected void finishedRun(long);
-    method public final android.media.MediaFormat! getFormat();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget! getRenderingWidget();
-    method public int getTrackType();
-    method public void hide();
-    method public void onData(androidx.media2.common.SubtitleData!);
-    method protected abstract void onData(byte[]!, boolean, long);
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-    method protected void scheduleTimedEvents();
-    method public void setRunDiscardTimeMs(long, long);
-    method public void setTimeProvider(androidx.media2.player.subtitle.MediaTimeProvider!);
-    method public void show();
-    method protected void updateActiveCues(boolean, long);
-    method public abstract void updateView(java.util.ArrayList<androidx.media2.player.subtitle.SubtitleTrack.Cue!>!);
-    field public boolean DEBUG;
-    field protected android.os.Handler! mHandler;
-    field protected androidx.media2.player.subtitle.MediaTimeProvider! mTimeProvider;
-    field protected boolean mVisible;
-  }
-
-  public static class SubtitleTrack.Cue {
-    ctor public SubtitleTrack.Cue();
-    method public void onTime(long);
-    field public long mEndTimeMs;
-    field public long[]! mInnerTimesMs;
-    field public androidx.media2.player.subtitle.SubtitleTrack.Cue! mNextInRun;
-    field public long mRunID;
-    field public long mStartTimeMs;
-  }
-
-  public static interface SubtitleTrack.RenderingWidget {
-    method public void draw(android.graphics.Canvas!);
-    method public void onAttachedToWindow();
-    method public void onDetachedFromWindow();
-    method public void setOnChangedListener(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget.OnChangedListener!);
-    method public void setSize(int, int);
-    method public void setVisible(boolean);
-  }
-
-  public static interface SubtitleTrack.RenderingWidget.OnChangedListener {
-    method public void onChanged(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_1.0.0-rc01.txt b/media2/media2-player/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index b53505e..0000000
--- a/media2/media2-player/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1,695 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public void close() throws java.lang.Exception;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media2.player.MediaPlayer.DrmInfo? getDrmInfo();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.PersistableBundle! getMetrics();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(@androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.player.MediaPlayer.DrmResult!> prepareDrm(java.util.UUID);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public byte[]? provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void releaseDrm() throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, @androidx.media2.player.MediaPlayer.SeekMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer.NoDrmSchemeException;
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer.OnDrmConfigHelper?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_END = 5; // 0x5
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_LIST_END = 6; // 0x6
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_REPEAT = 7; // 0x7
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_MEDIA_ITEM_START = 2; // 0x2
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!> getPssh();
-    method public java.util.List<java.util.UUID!> getSupportedSchemes();
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.DrmResult extends androidx.media2.common.SessionPlayer.PlayerResult {
-    ctor public MediaPlayer.DrmResult(@androidx.media2.player.MediaPlayer.DrmResult.DrmResultCode int, androidx.media2.common.MediaItem);
-    field public static final int RESULT_ERROR_PREPARATION_ERROR = -1003; // 0xfffffc15
-    field public static final int RESULT_ERROR_PROVISIONING_NETWORK_ERROR = -1001; // 0xfffffc17
-    field public static final int RESULT_ERROR_PROVISIONING_SERVER_ERROR = -1002; // 0xfffffc16
-    field public static final int RESULT_ERROR_RESOURCE_BUSY = -1005; // 0xfffffc13
-    field public static final int RESULT_ERROR_UNSUPPORTED_SCHEME = -1004; // 0xfffffc14
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_PREPARATION_ERROR, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer.DrmResult.RESULT_ERROR_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.DrmResult.DrmResultCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer.PLAYER_ERROR_IO, androidx.media2.player.MediaPlayer.PLAYER_ERROR_MALFORMED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer.PLAYER_ERROR_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_LIST_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_MEDIA_ITEM_REPEAT, androidx.media2.player.MediaPlayer.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.MediaInfo {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class MediaPlayer.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer.NoDrmSchemeException(String?);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface MediaPlayer.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem);
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void onDrmInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaPlayer.DrmInfo);
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, @androidx.media2.player.MediaPlayer.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.SeekMode {
-  }
-
-  public static final class MediaPlayer.TrackInfo {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaPlayer.TrackInfo(int, androidx.media2.common.MediaItem!, int, android.media.MediaFormat!);
-    method public android.media.MediaFormat? getFormat();
-    method public java.util.Locale getLanguage();
-    method @androidx.media2.player.MediaPlayer.TrackInfo.MediaTrackType public int getTrackType();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, androidx.media2.player.MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer.TrackInfo.MediaTrackType {
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class MediaPlayer2 {
-    ctor protected MediaPlayer2();
-    method public abstract Object! attachAuxEffect(int);
-    method public abstract boolean cancel(Object!);
-    method public abstract void clearDrmEventCallback();
-    method public abstract void clearEventCallback();
-    method public abstract void clearPendingCommands();
-    method public abstract void close();
-    method public static androidx.media2.player.MediaPlayer2! create(android.content.Context);
-    method public abstract Object! deselectTrack(int);
-    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public abstract int getAudioSessionId();
-    method public abstract long getBufferedPosition();
-    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public abstract long getCurrentPosition();
-    method public abstract androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public abstract android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract long getDuration();
-    method public float getMaxPlayerVolume();
-    method @RequiresApi(21) public abstract android.os.PersistableBundle! getMetrics();
-    method public abstract androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public abstract float getPlayerVolume();
-    method public abstract int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public abstract int getState();
-    method public abstract androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public abstract java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public abstract int getVideoHeight();
-    method public abstract int getVideoWidth();
-    method public abstract Object! loopCurrent(boolean);
-    method public abstract Object! notifyWhenCommandLabelReached(Object);
-    method public abstract Object! pause();
-    method public abstract Object! play();
-    method public abstract Object! prepare();
-    method public abstract Object! prepareDrm(java.util.UUID);
-    method public abstract byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void reset();
-    method public abstract void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long);
-    method public abstract Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public abstract Object! selectTrack(int);
-    method public abstract Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public abstract Object! setAudioSessionId(int);
-    method public abstract Object! setAuxEffectSendLevel(float);
-    method public abstract void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public abstract void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public abstract void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public abstract Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public abstract Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public abstract void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public abstract Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public abstract Object! setPlayerVolume(float);
-    method public abstract Object! setSurface(android.view.Surface?);
-    method public abstract Object! skipToNext();
-    field public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; // 0x1
-    field public static final int CALL_COMPLETED_DESELECT_TRACK = 2; // 0x2
-    field public static final int CALL_COMPLETED_LOOP_CURRENT = 3; // 0x3
-    field public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 1000; // 0x3e8
-    field public static final int CALL_COMPLETED_PAUSE = 4; // 0x4
-    field public static final int CALL_COMPLETED_PLAY = 5; // 0x5
-    field public static final int CALL_COMPLETED_PREPARE = 6; // 0x6
-    field public static final int CALL_COMPLETED_PREPARE_DRM = 1001; // 0x3e9
-    field public static final int CALL_COMPLETED_SEEK_TO = 14; // 0xe
-    field public static final int CALL_COMPLETED_SELECT_TRACK = 15; // 0xf
-    field public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; // 0x10
-    field public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; // 0x11
-    field public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; // 0x12
-    field public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; // 0x13
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; // 0x16
-    field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; // 0x17
-    field public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; // 0x18
-    field public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; // 0x1a
-    field public static final int CALL_COMPLETED_SET_SURFACE = 27; // 0x1b
-    field public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; // 0x1d
-    field public static final int CALL_STATUS_BAD_VALUE = 2; // 0x2
-    field public static final int CALL_STATUS_ERROR_IO = 4; // 0x4
-    field public static final int CALL_STATUS_ERROR_UNKNOWN = -2147483648; // 0x80000000
-    field public static final int CALL_STATUS_INVALID_OPERATION = 1; // 0x1
-    field public static final int CALL_STATUS_NO_ERROR = 0; // 0x0
-    field public static final int CALL_STATUS_PERMISSION_DENIED = 3; // 0x3
-    field public static final int CALL_STATUS_SKIPPED = 5; // 0x5
-    field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int MEDIA_ERROR_SYSTEM = -2147483648; // 0x80000000
-    field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
-    field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_DATA_SOURCE_END = 5; // 0x5
-    field public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6; // 0x6
-    field public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7; // 0x7
-    field public static final int MEDIA_INFO_DATA_SOURCE_START = 2; // 0x2
-    field public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; // 0x323
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; // 0x2bf
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
-    field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
-    field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1
-    field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field public static final int PLAYER_STATE_ERROR = 1005; // 0x3ed
-    field public static final int PLAYER_STATE_IDLE = 1001; // 0x3e9
-    field public static final int PLAYER_STATE_PAUSED = 1003; // 0x3eb
-    field public static final int PLAYER_STATE_PLAYING = 1004; // 0x3ec
-    field public static final int PLAYER_STATE_PREPARED = 1002; // 0x3ea
-    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
-    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
-    field public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5; // 0x5
-    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
-    field public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4; // 0x4
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-    field public static final int SEPARATE_CALL_COMPLETE_CALLBACK_START = 1000; // 0x3e8
-    field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PAUSE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PLAY, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SEEK_TO, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SELECT_TRACK, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_SURFACE, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE_DRM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallCompleted {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.CALL_STATUS_NO_ERROR, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.CALL_STATUS_INVALID_OPERATION, androidx.media2.player.MediaPlayer2.CALL_STATUS_BAD_VALUE, androidx.media2.player.MediaPlayer2.CALL_STATUS_PERMISSION_DENIED, androidx.media2.player.MediaPlayer2.CALL_STATUS_ERROR_IO, androidx.media2.player.MediaPlayer2.CALL_STATUS_SKIPPED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.CallStatus {
-  }
-
-  public abstract static class MediaPlayer2.DrmEventCallback {
-    ctor public MediaPlayer2.DrmEventCallback();
-    method public void onDrmInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaPlayer2.DrmInfo!);
-    method public void onDrmPrepared(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.PrepareDrmStatusCode int);
-  }
-
-  public abstract static class MediaPlayer2.DrmInfo {
-    ctor public MediaPlayer2.DrmInfo();
-    method public abstract java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public abstract java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public abstract static class MediaPlayer2.EventCallback {
-    ctor public MediaPlayer2.EventCallback();
-    method public void onCallCompleted(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.CallCompleted int, @androidx.media2.player.MediaPlayer2.CallStatus int);
-    method public void onCommandLabelReached(androidx.media2.player.MediaPlayer2!, Object);
-    method public void onError(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaError int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, @androidx.media2.player.MediaPlayer2.MediaInfo int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onSubtitleData(androidx.media2.player.MediaPlayer2, androidx.media2.common.MediaItem, int, androidx.media2.common.SubtitleData);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoSizeChanged(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!, int, int);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_IO, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_MALFORMED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_TIMED_OUT, androidx.media2.player.MediaPlayer2.MEDIA_ERROR_SYSTEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaError {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNKNOWN, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_RENDERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT, androidx.media2.player.MediaPlayer2.MEDIA_INFO_PREPARED, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_TRACK_LAGGING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_START, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_END, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NETWORK_BANDWIDTH, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_BAD_INTERLEAVING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_NOT_SEEKABLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_EXTERNAL_METADATA_UPDATE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_AUDIO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_VIDEO_NOT_PLAYING, androidx.media2.player.MediaPlayer2.MEDIA_INFO_UNSUPPORTED_SUBTITLE, androidx.media2.player.MediaPlayer2.MEDIA_INFO_SUBTITLE_TIMED_OUT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaInfo {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PLAYER_STATE_IDLE, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PREPARED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PAUSED, androidx.media2.player.MediaPlayer2.PLAYER_STATE_PLAYING, androidx.media2.player.MediaPlayer2.PLAYER_STATE_ERROR}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.MediaPlayer2State {
-  }
-
-  public static final class MediaPlayer2.MetricsConstants {
-    field public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-    field public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-    field public static final String DURATION = "android.media.mediaplayer.durationMs";
-    field public static final String ERRORS = "android.media.mediaplayer.err";
-    field public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    field public static final String FRAMES = "android.media.mediaplayer.frames";
-    field public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-    field public static final String HEIGHT = "android.media.mediaplayer.height";
-    field public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-    field public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-    field public static final String PLAYING = "android.media.mediaplayer.playingMs";
-    field public static final String WIDTH = "android.media.mediaplayer.width";
-  }
-
-  public static class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
-    ctor public MediaPlayer2.NoDrmSchemeException(String!);
-  }
-
-  public static interface MediaPlayer2.OnDrmConfigHelper {
-    method public void onDrmConfig(androidx.media2.player.MediaPlayer2!, androidx.media2.common.MediaItem!);
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_PREPARATION_ERROR, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, androidx.media2.player.MediaPlayer2.PREPARE_DRM_STATUS_RESOURCE_BUSY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.PrepareDrmStatusCode {
-  }
-
-  @IntDef(flag=false, value={androidx.media2.player.MediaPlayer2.SEEK_PREVIOUS_SYNC, androidx.media2.player.MediaPlayer2.SEEK_NEXT_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST_SYNC, androidx.media2.player.MediaPlayer2.SEEK_CLOSEST}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaPlayer2.SeekMode {
-  }
-
-  public abstract static class MediaPlayer2.TrackInfo {
-    ctor public MediaPlayer2.TrackInfo();
-    method public abstract android.media.MediaFormat! getFormat();
-    method public abstract String! getLanguage();
-    method public abstract int getTrackType();
-    method public abstract String toString();
-    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
-    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
-    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
-    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
-  }
-
-  @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class MediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public MediaPlayer2Impl(android.content.Context!);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String!,java.lang.String!>?) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public String getDrmPropertyString(String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public long getDuration();
-    method public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object!);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void releaseDrm() throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void reset();
-    method public void restoreDrmKeys(byte[]) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public Object! seekTo(long, @androidx.media2.player.MediaPlayer2.SeekMode int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String) throws androidx.media2.player.MediaPlayer2.NoDrmSchemeException;
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-  public static final class MediaPlayer2Impl.DrmInfoImpl extends androidx.media2.player.MediaPlayer2.DrmInfo {
-    method public java.util.Map<java.util.UUID!,byte[]!>! getPssh();
-    method public java.util.List<java.util.UUID!>! getSupportedSchemes();
-  }
-
-  public final class MediaTimestamp {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaTimestamp(long, long, float);
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method @androidx.media2.player.PlaybackParams.AudioFallbackMode public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.media.PlaybackParams! getPlaybackParams();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_MUTE, androidx.media2.player.PlaybackParams.AUDIO_FALLBACK_MODE_FAIL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackParams.AudioFallbackMode {
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PlaybackParams.Builder(android.media.PlaybackParams!);
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(@androidx.media2.player.PlaybackParams.AudioFallbackMode int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    ctor @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(android.media.TimedMetaData!);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public TimedMetaData(long, byte[]!);
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize {
-    ctor public VideoSize(int, int);
-    method public int getHeight();
-    method public int getWidth();
-  }
-
-}
-
-package androidx.media2.player.common {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TrackInfoImpl extends androidx.media2.player.MediaPlayer2.TrackInfo {
-    ctor public TrackInfoImpl(int, android.media.MediaFormat!);
-    method public android.media.MediaFormat! getFormat();
-    method public String! getLanguage();
-    method public int getTrackType();
-  }
-
-}
-
-package androidx.media2.player.exoplayer {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class DataSourceCallbackDataSource {
-    ctor public DataSourceCallbackDataSource(androidx.media2.common.DataSourceCallback!);
-    method public void close();
-    method public android.net.Uri! getUri();
-    method public long open(DataSpec) throws java.io.IOException;
-    method public int read(byte[]!, int, int) throws java.io.IOException;
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ExoPlayerMediaPlayer2Impl extends androidx.media2.player.MediaPlayer2 {
-    ctor public ExoPlayerMediaPlayer2Impl(android.content.Context);
-    method public Object! attachAuxEffect(int);
-    method public boolean cancel(Object!);
-    method public void clearDrmEventCallback();
-    method public void clearEventCallback();
-    method public void clearPendingCommands();
-    method public void close();
-    method public Object! deselectTrack(int);
-    method public androidx.media.AudioAttributesCompat! getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method public androidx.media2.common.MediaItem! getCurrentMediaItem();
-    method public long getCurrentPosition();
-    method public androidx.media2.player.MediaPlayer2.DrmInfo! getDrmInfo();
-    method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]!, byte[]!, String!, int, java.util.Map<java.lang.String!,java.lang.String!>!);
-    method public String getDrmPropertyString(String);
-    method public long getDuration();
-    method @RequiresApi(21) public android.os.PersistableBundle! getMetrics();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method public float getPlayerVolume();
-    method public int getSelectedTrack(int);
-    method @androidx.media2.player.MediaPlayer2.MediaPlayer2State public int getState();
-    method public androidx.media2.player.MediaTimestamp! getTimestamp();
-    method public java.util.List<androidx.media2.player.MediaPlayer2.TrackInfo!>! getTrackInfo();
-    method public int getVideoHeight();
-    method public int getVideoWidth();
-    method public Object! loopCurrent(boolean);
-    method public Object! notifyWhenCommandLabelReached(Object);
-    method public void onBandwidthSample(androidx.media2.common.MediaItem!, int);
-    method public void onBufferingEnded(androidx.media2.common.MediaItem!);
-    method public void onBufferingStarted(androidx.media2.common.MediaItem!);
-    method public void onBufferingUpdate(androidx.media2.common.MediaItem!, int);
-    method public void onError(androidx.media2.common.MediaItem!, int);
-    method public void onLoop(androidx.media2.common.MediaItem!);
-    method public void onMediaItemEnded(androidx.media2.common.MediaItem!);
-    method public void onMediaItemStartedAsNext(androidx.media2.common.MediaItem!);
-    method public void onMediaTimeDiscontinuity(androidx.media2.common.MediaItem!, androidx.media2.player.MediaTimestamp!);
-    method public void onMetadataChanged(androidx.media2.common.MediaItem!);
-    method public void onPlaybackEnded(androidx.media2.common.MediaItem!);
-    method public void onPrepared(androidx.media2.common.MediaItem!);
-    method public void onSeekCompleted();
-    method public void onSubtitleData(androidx.media2.common.MediaItem!, int, androidx.media2.common.SubtitleData!);
-    method public void onTimedMetadata(androidx.media2.common.MediaItem!, androidx.media2.player.TimedMetaData!);
-    method public void onVideoRenderingStart(androidx.media2.common.MediaItem!);
-    method public void onVideoSizeChanged(androidx.media2.common.MediaItem!, int, int);
-    method public Object! pause();
-    method public Object! play();
-    method public Object! prepare();
-    method public Object! prepareDrm(java.util.UUID);
-    method public byte[]! provideDrmKeyResponse(byte[]?, byte[]);
-    method public void releaseDrm();
-    method public void reset();
-    method public void restoreDrmKeys(byte[]);
-    method public Object! seekTo(long, int);
-    method public Object! selectTrack(int);
-    method public Object! setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public Object! setAudioSessionId(int);
-    method public Object! setAuxEffectSendLevel(float);
-    method public void setDrmEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.DrmEventCallback);
-    method public void setDrmPropertyString(String, String);
-    method public void setEventCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer2.EventCallback);
-    method public Object! setMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItem(androidx.media2.common.MediaItem);
-    method public Object! setNextMediaItems(java.util.List<androidx.media2.common.MediaItem!>);
-    method public void setOnDrmConfigHelper(androidx.media2.player.MediaPlayer2.OnDrmConfigHelper!);
-    method public Object! setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public Object! setPlayerVolume(float);
-    method public Object! setSurface(android.view.Surface!);
-    method public Object! skipToNext();
-  }
-
-}
-
-package androidx.media2.player.subtitle {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class Cea708CaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public Cea708CaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ClosedCaptionRenderer extends androidx.media2.player.subtitle.SubtitleController.Renderer {
-    ctor public ClosedCaptionRenderer(android.content.Context!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface MediaTimeProvider {
-    method public void cancelNotifications(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public long getCurrentTimeUs(boolean, boolean) throws java.lang.IllegalStateException;
-    method public void notifyAt(long, androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    method public void scheduleUpdate(androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener!);
-    field public static final long NO_TIME = -1L; // 0xffffffffffffffffL
-  }
-
-  public static interface MediaTimeProvider.OnMediaTimeListener {
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SubtitleController {
-    ctor public SubtitleController(android.content.Context!);
-    ctor public SubtitleController(android.content.Context!, androidx.media2.player.subtitle.MediaTimeProvider!, androidx.media2.player.subtitle.SubtitleController.Listener!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! addTrack(android.media.MediaFormat!);
-    method public androidx.media2.player.subtitle.SubtitleTrack! getDefaultTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack! getSelectedTrack();
-    method public androidx.media2.player.subtitle.SubtitleTrack![]! getTracks();
-    method public boolean hasRendererFor(android.media.MediaFormat!);
-    method public void hide();
-    method public void registerRenderer(androidx.media2.player.subtitle.SubtitleController.Renderer!);
-    method public void reset();
-    method public void selectDefaultTrack();
-    method public boolean selectTrack(androidx.media2.player.subtitle.SubtitleTrack!);
-    method public void setAnchor(androidx.media2.player.subtitle.SubtitleController.Anchor!);
-    method public void show();
-  }
-
-  public static interface SubtitleController.Anchor {
-    method public android.os.Looper! getSubtitleLooper();
-    method public void setSubtitleWidget(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-  public static interface SubtitleController.Listener {
-    method public void onSubtitleTrackSelected(androidx.media2.player.subtitle.SubtitleTrack!);
-  }
-
-  public abstract static class SubtitleController.Renderer {
-    ctor public SubtitleController.Renderer();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack! createTrack(android.media.MediaFormat!);
-    method public abstract boolean supports(android.media.MediaFormat!);
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SubtitleTrack implements androidx.media2.player.subtitle.MediaTimeProvider.OnMediaTimeListener {
-    ctor public SubtitleTrack(android.media.MediaFormat!);
-    method protected boolean addCue(androidx.media2.player.subtitle.SubtitleTrack.Cue!);
-    method protected void clearActiveCues();
-    method protected void finishedRun(long);
-    method public final android.media.MediaFormat! getFormat();
-    method public abstract androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget! getRenderingWidget();
-    method public int getTrackType();
-    method public void hide();
-    method public void onData(androidx.media2.common.SubtitleData!);
-    method protected abstract void onData(byte[]!, boolean, long);
-    method public void onSeek(long);
-    method public void onStop();
-    method public void onTimedEvent(long);
-    method protected void scheduleTimedEvents();
-    method public void setRunDiscardTimeMs(long, long);
-    method public void setTimeProvider(androidx.media2.player.subtitle.MediaTimeProvider!);
-    method public void show();
-    method protected void updateActiveCues(boolean, long);
-    method public abstract void updateView(java.util.ArrayList<androidx.media2.player.subtitle.SubtitleTrack.Cue!>!);
-    field public boolean DEBUG;
-    field protected android.os.Handler! mHandler;
-    field protected androidx.media2.player.subtitle.MediaTimeProvider! mTimeProvider;
-    field protected boolean mVisible;
-  }
-
-  public static class SubtitleTrack.Cue {
-    ctor public SubtitleTrack.Cue();
-    method public void onTime(long);
-    field public long mEndTimeMs;
-    field public long[]! mInnerTimesMs;
-    field public androidx.media2.player.subtitle.SubtitleTrack.Cue! mNextInRun;
-    field public long mRunID;
-    field public long mStartTimeMs;
-  }
-
-  public static interface SubtitleTrack.RenderingWidget {
-    method public void draw(android.graphics.Canvas!);
-    method public void onAttachedToWindow();
-    method public void onDetachedFromWindow();
-    method public void setOnChangedListener(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget.OnChangedListener!);
-    method public void setSize(int, int);
-    method public void setVisible(boolean);
-  }
-
-  public static interface SubtitleTrack.RenderingWidget.OnChangedListener {
-    method public void onChanged(androidx.media2.player.subtitle.SubtitleTrack.RenderingWidget!);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_1.1.0-beta01.txt b/media2/media2-player/api/restricted_1.1.0-beta01.txt
deleted file mode 100644
index 5ae3ddb..0000000
--- a/media2/media2-player/api/restricted_1.1.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo {
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_1.2.0-beta01.txt b/media2/media2-player/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index 5ae3ddb..0000000
--- a/media2/media2-player/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public MediaPlayer(android.content.Context);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getAudioSessionId();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public float getMaxPlayerVolume();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
-    method public float getPlayerVolume();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method public int getShuffleMode();
-    method public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method public androidx.media2.player.VideoSize getVideoSize();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method public void reset();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field public static final int SEEK_CLOSEST = 3; // 0x3
-    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public MediaPlayer.PlayerCallback();
-    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo {
-  }
-
-  public final class MediaTimestamp {
-    method public long getAnchorMediaTimeUs();
-    method public long getAnchorSystemNanoTime();
-    method public float getMediaClockRate();
-    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  public final class PlaybackParams {
-    method public Integer? getAudioFallbackMode();
-    method public Float? getPitch();
-    method public Float? getSpeed();
-    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  public static final class PlaybackParams.Builder {
-    ctor public PlaybackParams.Builder();
-    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method public androidx.media2.player.PlaybackParams build();
-    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  public class TimedMetaData {
-    method public byte[]! getMetaData();
-    method public long getTimestamp();
-  }
-
-  public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_1.3.0-beta01.txt b/media2/media2-player/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index c999693..0000000
--- a/media2/media2-player/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  @Deprecated public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public MediaPlayer(android.content.Context);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getAudioSessionId();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public float getMaxPlayerVolume();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @Deprecated @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public float getPlayerVolume();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method @Deprecated public androidx.media2.player.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public void reset();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field @Deprecated public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @Deprecated public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @Deprecated public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @Deprecated public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field @Deprecated public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field @Deprecated public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field @Deprecated public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field @Deprecated public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field @Deprecated public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field @Deprecated public static final int SEEK_CLOSEST = 3; // 0x3
-    field @Deprecated public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field @Deprecated public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field @Deprecated public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @Deprecated public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public MediaPlayer.PlayerCallback();
-    method @Deprecated public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method @Deprecated public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @Deprecated public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo {
-  }
-
-  @Deprecated public final class MediaTimestamp {
-    method @Deprecated public long getAnchorMediaTimeUs();
-    method @Deprecated public long getAnchorSystemNanoTime();
-    method @Deprecated public float getMediaClockRate();
-    field @Deprecated public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  @Deprecated public final class PlaybackParams {
-    method @Deprecated public Integer? getAudioFallbackMode();
-    method @Deprecated public Float? getPitch();
-    method @Deprecated public Float? getSpeed();
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @Deprecated public static final class PlaybackParams.Builder {
-    ctor @Deprecated public PlaybackParams.Builder();
-    ctor @Deprecated public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method @Deprecated public androidx.media2.player.PlaybackParams build();
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  @Deprecated public class TimedMetaData {
-    method @Deprecated public byte[]! getMetaData();
-    method @Deprecated public long getTimestamp();
-  }
-
-  @Deprecated public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor @Deprecated public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/api/restricted_current.txt b/media2/media2-player/api/restricted_current.txt
deleted file mode 100644
index c999693..0000000
--- a/media2/media2-player/api/restricted_current.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.player {
-
-  @Deprecated public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public MediaPlayer(android.content.Context);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getAudioSessionId();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public float getMaxPlayerVolume();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.player.PlaybackParams getPlaybackParams();
-    method @Deprecated @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public float getPlayerVolume();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public androidx.media2.player.MediaTimestamp? getTimestamp();
-    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
-    method @Deprecated public androidx.media2.player.VideoSize getVideoSize();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
-    method @Deprecated public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
-    method @Deprecated public void reset();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-    field @Deprecated public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
-    field @Deprecated public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
-    field @Deprecated public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
-    field @Deprecated public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
-    field @Deprecated public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
-    field @Deprecated public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
-    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
-    field @Deprecated public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
-    field @Deprecated public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
-    field @Deprecated public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
-    field @Deprecated public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
-    field @Deprecated public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
-    field @Deprecated public static final int SEEK_CLOSEST = 3; // 0x3
-    field @Deprecated public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
-    field @Deprecated public static final int SEEK_NEXT_SYNC = 1; // 0x1
-    field @Deprecated public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
-  }
-
-  @Deprecated public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public MediaPlayer.PlayerCallback();
-    method @Deprecated public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
-    method @Deprecated public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
-    method @Deprecated public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
-  }
-
-  @Deprecated public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo {
-  }
-
-  @Deprecated public final class MediaTimestamp {
-    method @Deprecated public long getAnchorMediaTimeUs();
-    method @Deprecated public long getAnchorSystemNanoTime();
-    method @Deprecated public float getMediaClockRate();
-    field @Deprecated public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
-  }
-
-  @Deprecated public final class PlaybackParams {
-    method @Deprecated public Integer? getAudioFallbackMode();
-    method @Deprecated public Float? getPitch();
-    method @Deprecated public Float? getSpeed();
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
-    field @Deprecated public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
-  }
-
-  @Deprecated public static final class PlaybackParams.Builder {
-    ctor @Deprecated public PlaybackParams.Builder();
-    ctor @Deprecated public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
-    method @Deprecated public androidx.media2.player.PlaybackParams build();
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-    method @Deprecated public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
-  }
-
-  @Deprecated public class TimedMetaData {
-    method @Deprecated public byte[]! getMetaData();
-    method @Deprecated public long getTimestamp();
-  }
-
-  @Deprecated public final class VideoSize extends androidx.media2.common.VideoSize {
-    ctor @Deprecated public VideoSize(int, int);
-  }
-
-}
-
diff --git a/media2/media2-player/build.gradle b/media2/media2-player/build.gradle
deleted file mode 100644
index 3f9d5f7..0000000
--- a/media2/media2-player/build.gradle
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 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.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-apply(from: "../constants.gradle")
-
-dependencies {
-    api(project(":media2:media2-common"))
-    api(libs.guavaListenableFuture)
-    implementation("androidx.collection:collection:" + COLLECTION_VERSION)
-    implementation("androidx.concurrent:concurrent-futures:" + CONCURRENT_FUTURE_VERSION)
-    compileOnly(libs.checkerframework)
-    // Depend on media2-exoplayer so that the library groupId is set to match media2.
-    implementation(project(":media2:media2-exoplayer"))
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(project(":internal-testutils-runtime"))
-    annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
-}
-
-android {
-    namespace "androidx.media2.player"
-}
-
-androidx {
-    name = "Media2 Player"
-    publish = Publish.SNAPSHOT_AND_RELEASE
-    inceptionYear = "2018"
-    description = "Media2 Player"
-    failOnDeprecationWarnings = false
-    metalavaK2UastEnabled = true
-}
diff --git a/media2/media2-player/lint-baseline.xml b/media2/media2-player/lint-baseline.xml
deleted file mode 100644
index 0eb9903..0000000
--- a/media2/media2-player/lint-baseline.xml
+++ /dev/null
@@ -1,868 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 27 (current min is 19): `android.app.Activity#setTurnScreenOn`"
-        errorLine1="                mActivity.setTurnScreenOn(true);"
-        errorLine2="                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 27 (current min is 19): `android.app.Activity#setShowWhenLocked`"
-        errorLine1="                mActivity.setShowWhenLocked(true);"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 19): `android.app.KeyguardManager#requestDismissKeyguard`"
-        errorLine1="                mKeyguardManager.requestDismissKeyguard(mActivity, null);"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 27 (current min is 19): `android.app.Activity#setTurnScreenOn`"
-        errorLine1="                mActivity.setTurnScreenOn(true);"
-        errorLine2="                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 27 (current min is 19): `android.app.Activity#setShowWhenLocked`"
-        errorLine1="                mActivity.setShowWhenLocked(true);"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 19): `android.app.KeyguardManager#requestDismissKeyguard`"
-        errorLine1="                mKeyguardManager.requestDismissKeyguard(mActivity, null);"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: BaseResult.RESULT_SUCCESS, DrmResult.RESULT_ERROR_PROVISIONING_NETWORK_ERROR, DrmResult.RESULT_ERROR_PROVISIONING_SERVER_ERROR, DrmResult.RESULT_ERROR_PREPARATION_ERROR, DrmResult.RESULT_ERROR_UNSUPPORTED_SCHEME, DrmResult.RESULT_ERROR_RESOURCE_BUSY"
-        errorLine1="            return super.getResultCode();"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    @Override"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/player/TextRenderer.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    @Override"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/player/TextRenderer.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    /** Clears any previous selection. */"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/player/TextRenderer.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    /** Selects the specified track type/channel for extraction and rendering. */"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/player/TextRenderer.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(sleepBetweenRounds);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(SLEEP_TIME);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(SLEEP_TIME);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(SLEEP_TIME);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME / 2);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(durationMs);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(1000);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(playTime);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(sleepIntervalMs);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);  // let player get into stable state."
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);  // let player get into stable state."
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(playTime);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(SLEEP_TIME);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(SLEEP_TIME);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(playTime);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(sleepBetweenRounds);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(1000);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(playTime);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(sleepIntervalMs);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);  // let player get into stable state."
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);  // let player get into stable state."
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(playTime);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(SLEEP_TIME);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SLEEP_TIME);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/player/MediaPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanUncheckedReflection"
-        message="Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API."
-        errorLine1="            return (FileDescriptor) method.invoke(object, fileDescriptor);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/FileDescriptorUtil.java"/>
-    </issue>
-
-    <issue
-        id="BanUncheckedReflection"
-        message="Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API."
-        errorLine1="            method.invoke(object, fileDescriptor, position, /* whence= */ SEEK_SET);"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/FileDescriptorUtil.java"/>
-    </issue>
-
-    <issue
-        id="BanUncheckedReflection"
-        message="Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API."
-        errorLine1="            return (FileDescriptor) method.invoke(object, fileDescriptor);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/FileDescriptorUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onPrepared(MediaItem mediaItem);"
-        errorLine2="                        ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onBufferingStarted(MediaItem mediaItem);"
-        errorLine2="                                ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onBufferingEnded(MediaItem mediaItem);"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onBufferingUpdate(MediaItem mediaItem, int bufferingPercentage);"
-        errorLine2="                               ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onBandwidthSample(MediaItem mediaItem2, int bitrateKbps);"
-        errorLine2="                               ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onVideoRenderingStart(MediaItem mediaItem);"
-        errorLine2="                                   ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onVideoSizeChanged(MediaItem mediaItem, int width, int height);"
-        errorLine2="                                ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData);"
-        errorLine2="                             ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData);"
-        errorLine2="                                                  ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onMediaItemStartedAsNext(MediaItem mediaItem);"
-        errorLine2="                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onMediaItemEnded(MediaItem mediaItem);"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onLoop(MediaItem mediaItem);"
-        errorLine2="                    ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp);"
-        errorLine2="                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp);"
-        errorLine2="                                                           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onPlaybackEnded(MediaItem mediaItem);"
-        errorLine2="                             ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onError(MediaItem mediaItem, int what);"
-        errorLine2="                     ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/ExoPlayerWrapper.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, int width, int height) { }"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, int width, int height) { }"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, TimedMetaData data) { }"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, TimedMetaData data) { }"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, TimedMetaData data) { }"
-        errorLine2="                                                 ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @MediaError int what, int extra) { }"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @MediaError int what, int extra) { }"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onInfo(MediaPlayer2 mp, MediaItem item, @MediaInfo int what, int extra) { }"
-        errorLine2="                           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onInfo(MediaPlayer2 mp, MediaItem item, @MediaInfo int what, int extra) { }"
-        errorLine2="                                            ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @CallCompleted int what,"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @CallCompleted int what,"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, MediaTimestamp timestamp) { }"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, MediaTimestamp timestamp) { }"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, MediaTimestamp timestamp) { }"
-        errorLine2="                                                 ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) { }"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onDrmConfig(MediaPlayer2 mp, MediaItem item);"
-        errorLine2="                         ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onDrmConfig(MediaPlayer2 mp, MediaItem item);"
-        errorLine2="                                          ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) { }"
-        errorLine2="                              ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) { }"
-        errorLine2="                                               ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) { }"
-        errorLine2="                                                               ~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @PrepareDrmStatusCode int status) { }"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                MediaPlayer2 mp, MediaItem item, @PrepareDrmStatusCode int status) { }"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public abstract Map&lt;UUID, byte[]&gt; getPssh();"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public abstract List&lt;UUID> getSupportedSchemes();"
-        errorLine2="                        ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/MediaPlayer2.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onCcData(byte[] data, long timeUs);"
-        errorLine2="                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/player/TextRenderer.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-player/src/androidTest/AndroidManifest.xml b/media2/media2-player/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 4111a98..0000000
--- a/media2/media2-player/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application>
-        <activity
-            android:name="androidx.media2.player.MediaStubActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
-            android:exported="false"
-            android:label="MediaStubActivity"
-            android:screenOrientation="nosensor">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-    </application>
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-</manifest>
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTest.java
deleted file mode 100644
index 5a12598..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTest.java
+++ /dev/null
@@ -1,151 +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.media2.player;
-
-import android.Manifest;
-import android.net.Uri;
-import android.os.Environment;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.Suppress;
-import androidx.test.rule.GrantPermissionRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@Suppress // Disabled as it 100% fails b/79682973
-public class MediaPlayer2DrmTest extends MediaPlayer2DrmTestBase {
-
-    private static final String LOG_TAG = "MediaPlayer2DrmTest";
-
-    @Rule
-    public GrantPermissionRule mRuntimePermissionRule =
-            GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-
-    @Before
-    @Override
-    public void setUp() throws Throwable {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void tearDown() throws Throwable {
-        super.tearDown();
-    }
-
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Asset helpers
-
-    private static Uri getUriFromFile(String path) {
-        return Uri.fromFile(new File(getDownloadedPath(path)));
-    }
-
-    private static String getDownloadedPath(String fileName) {
-        return getDownloadedFolder() + File.separator + fileName;
-    }
-
-    private static String getDownloadedFolder() {
-        return Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_DOWNLOADS).getPath();
-    }
-
-    private static final class Resolution {
-        public final boolean isHD;
-        public final int width;
-        public final int height;
-
-        Resolution(boolean isHD, int width, int height) {
-            this.isHD = isHD;
-            this.width = width;
-            this.height = height;
-        }
-    }
-
-    private static final Resolution RES_720P  = new Resolution(true, 1280,  720);
-    private static final Resolution RES_AUDIO = new Resolution(false,   0,    0);
-
-
-    // Assets
-
-    private static final Uri CENC_AUDIO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/cenc/clearkey/car_cenc-20120827-8c-pssh.mp4");
-    private static final Uri CENC_AUDIO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-8c.mp4");
-
-    private static final Uri CENC_VIDEO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/cenc/clearkey/car_cenc-20120827-88-pssh.mp4");
-    private static final Uri CENC_VIDEO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-88.mp4");
-
-
-    // Tests
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV0Sync() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V0_SYNC_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV1Sync() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V1_ASYNC_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV2SyncConfig() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V2_SYNC_CONFIG_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV3AsyncDrmPrepared() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
-    }
-
-    // helpers
-
-    private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
-        playModularDrmVideo(uri, res.width, res.height, testType);
-    }
-
-    private void download(Uri remote, Uri local, Resolution res, ModularDrmTestType testType)
-            throws Exception {
-        playModularDrmVideoDownload(remote, local, res.width, res.height, testType);
-    }
-
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java
deleted file mode 100644
index 2663a7e..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2DrmTestBase.java
+++ /dev/null
@@ -1,1014 +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.media2.player;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.DownloadManager;
-import android.app.DownloadManager.Request;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.media.MediaDrm;
-import android.net.Uri;
-import android.os.Build;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Base64;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.WindowManager;
-
-import androidx.annotation.CallSuper;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.player.MediaPlayer2.DrmInfo;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.Vector;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Base class for DRM tests which use MediaPlayer2 to play audio or video.
- */
-public class MediaPlayer2DrmTestBase {
-    protected static final int STREAM_RETRIES = 3;
-
-    protected Monitor mSetDataSourceCallCompleted = new Monitor();
-    protected Monitor mOnPreparedCalled = new Monitor();
-    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
-    protected Monitor mOnPlaybackCompleted = new Monitor();
-    protected Monitor mOnDrmInfoCalled = new Monitor();
-    protected Monitor mOnDrmPreparedCalled = new Monitor();
-    protected int mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-
-    protected Context mContext;
-    protected Resources mResources;
-
-    protected MediaPlayer2 mPlayer = null;
-    protected MediaStubActivity mActivity;
-    protected Instrumentation mInstrumentation;
-
-    protected ExecutorService mExecutor;
-    protected MediaPlayer2.EventCallback mECb = null;
-
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-    public PowerManager.WakeLock mScreenLock;
-    private KeyguardManager mKeyguardManager;
-
-    @Before
-    @CallSuper
-    public void setUp() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mKeyguardManager = (KeyguardManager)
-                mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-        mActivity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Keep screen on while testing.
-                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                mActivity.setTurnScreenOn(true);
-                mActivity.setShowWhenLocked(true);
-                mKeyguardManager.requestDismissKeyguard(mActivity, null);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        try {
-            mActivityRule.runOnUiThread(new Runnable() {
-                public void run() {
-                    mPlayer = MediaPlayer2.create(mActivity);
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-
-        mContext = mInstrumentation.getTargetContext();
-        mResources = mContext.getResources();
-
-        mExecutor = Executors.newFixedThreadPool(2);
-    }
-
-    @After
-    @CallSuper
-    public void tearDown() throws Throwable {
-        if (mPlayer != null) {
-            mPlayer.close();
-            mPlayer = null;
-        }
-        mExecutor.shutdown();
-        mActivity = null;
-    }
-
-    private static class PrepareFailedException extends Exception {}
-
-    //////////////////////////////////////////////////////////////////////////////////////////
-    // Modular DRM
-
-    private static final String TAG = "MediaPlayer2DrmTestBase";
-
-    protected static final int PLAY_TIME_MS = 60 * 1000;
-    protected byte[] mKeySetId;
-    protected boolean mAudioOnly;
-
-    private static final byte[] CLEAR_KEY_CENC = {
-            (byte) 0x1a, (byte) 0x8a, (byte) 0x20, (byte) 0x95,
-            (byte) 0xe4, (byte) 0xde, (byte) 0xb2, (byte) 0xd2,
-            (byte) 0x9e, (byte) 0xc8, (byte) 0x16, (byte) 0xac,
-            (byte) 0x7b, (byte) 0xae, (byte) 0x20, (byte) 0x82
-            };
-
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-
-    final byte[] mClearKeyPssh = hexStringToByteArray(
-            "0000003470737368"    // BMFF box header (4 bytes size + 'pssh')
-            + "01000000"          // Full box header (version = 1 flags = 0)
-            + "1077efecc0b24d02"  // SystemID
-            + "ace33c1e52e2fb4b"
-            + "00000001"          // Number of key ids
-            + "60061e017e477e87"  // Key id
-            + "7e57d00d1ed00d1e"
-            + "00000000"          // Size of Data, must be zero
-            );
-
-
-    protected enum ModularDrmTestType {
-        V0_SYNC_TEST,
-        V1_ASYNC_TEST,
-        V2_SYNC_CONFIG_TEST,
-        V3_ASYNC_DRMPREPARED_TEST,
-        V4_SYNC_OFFLINE_KEY,
-    }
-
-    // TODO: After living on these tests for a while, we can consider grouping them based on
-    // the asset such that each asset is downloaded once and played back with multiple tests.
-    protected void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        final long downloadTimeOutSeconds = 600;
-        Log.i(TAG, "Downloading file:" + path);
-        MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
-        final long id = mediaDownloadManager.downloadFileWithRetries(
-                uri, path, downloadTimeOutSeconds, STREAM_RETRIES);
-        assertFalse("Download " + uri + " failed.", id == -1);
-        Uri file = mediaDownloadManager.getUriForDownloadedFile(id);
-        Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
-
-        try {
-            playModularDrmVideo(file, width, height, testType);
-        } finally {
-            mediaDownloadManager.removeFile(id);
-        }
-    }
-
-    protected void playModularDrmVideo(Uri uri, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        // Force gc for a clean start
-        System.gc();
-
-        playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
-    }
-
-    protected void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
-            int playTime, ModularDrmTestType testType) throws Exception {
-
-        // first the synchronous variation
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
-                playLoadedModularDrmVideo(file, width, height, playTime, testType);
-
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // we can fail because of network issues, so try again
-                Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i
-                        + ", trying playback again");
-                mPlayer.reset();
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
-                playedSuccessfully);
-    }
-
-    /**
-     * Play a video which has already been loaded with setMediaItem().
-     * The DRM setup is performed synchronously.
-     *
-     * @param file media item
-     * @param width width of the video to verify, or null to skip verification
-     * @param height height of the video to verify, or null to skip verification
-     * @param playTime length of time to play video, or 0 to play entire video
-     * @param testType test type
-     */
-    private void playLoadedModularDrmVideo(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        switch (testType) {
-            case V0_SYNC_TEST:
-            case V1_ASYNC_TEST:
-            case V2_SYNC_CONFIG_TEST:
-            case V3_ASYNC_DRMPREPARED_TEST:
-                playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
-                break;
-
-            case V4_SYNC_OFFLINE_KEY:
-                playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
-                break;
-        }
-    }
-
-    private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        final float volume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        mECb = new MediaPlayer2.EventCallback() {
-                @Override
-                public void onVideoSizeChanged(MediaPlayer2 mp, MediaItem item, int w, int h) {
-                    Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
-                    mOnVideoSizeChangedCalled.signal();
-                }
-
-                @Override
-                public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                    fail("Media player had error " + what + " playing video");
-                }
-
-                @Override
-                public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                    if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                        mOnPreparedCalled.signal();
-                    } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                        Log.v(TAG, "playLoadedVideo: onInfo_PlaybackComplete");
-                        mOnPlaybackCompleted.signal();
-                    }
-                }
-
-                @Override
-                public void onCallCompleted(MediaPlayer2 mp, MediaItem item,
-                        int what, int status) {
-                    if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
-                        mCallStatus = status;
-                        mSetDataSourceCallCompleted.signal();
-                    }
-                }
-            };
-
-        mPlayer.setEventCallback(mExecutor, mECb);
-        Log.v(TAG, "playLoadedVideo: setMediaItem()");
-        mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
-        mSetDataSourceCallCompleted.waitForSignal();
-        if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
-            throw new PrepareFailedException();
-        }
-
-        SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
-        surfaceHolder.setKeepScreenOn(true);
-        mPlayer.setSurface(surfaceHolder.getSurface());
-
-        try {
-            switch (testType) {
-                case V0_SYNC_TEST:
-                    preparePlayerAndDrm_V0_syncDrmSetup();
-                    break;
-
-                case V1_ASYNC_TEST:
-                    preparePlayerAndDrm_V1_asyncDrmSetup();
-                    break;
-
-                case V2_SYNC_CONFIG_TEST:
-                    preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
-                    break;
-
-                case V3_ASYNC_DRMPREPARED_TEST:
-                    preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
-                    break;
-            }
-
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-
-        Log.v(TAG, "playLoadedVideo: play()");
-        mPlayer.play();
-        if (!mAudioOnly) {
-            mOnVideoSizeChangedCalled.waitForSignal();
-        }
-        mPlayer.setPlayerVolume(volume);
-
-        // waiting to complete
-        if (playTime == 0) {
-            Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-            mOnPlaybackCompleted.waitForSignal();
-        } else {
-            Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-            mOnPlaybackCompleted.waitForSignal(playTime);
-        }
-
-        try {
-            Log.v(TAG, "playLoadedVideo: releaseDrm");
-            mPlayer.releaseDrm();
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-    }
-
-    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
-        Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
-        mPlayer.prepare();
-        mOnPreparedCalled.waitForSignal();
-        if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
-            throw new IOException();
-        }
-
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
-            @Override
-            public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                mOnDrmInfoCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V1: calling prepare()");
-        mPlayer.prepare();
-
-        mOnDrmInfoCalled.waitForSignal();
-
-        // Waiting till the player is prepared
-        mOnPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V1: setupDrm");
-        }
-    }
-
-    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
-        mPlayer.setOnDrmConfigHelper(new MediaPlayer2.OnDrmConfigHelper() {
-            @Override
-            public void onDrmConfig(MediaPlayer2 mp, MediaItem item) {
-                String widevineSecurityLevel3 = "L3";
-                String securityLevelProperty = "securityLevel";
-
-                try {
-                    String level = mp.getDrmPropertyString(securityLevelProperty);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
-                            + securityLevelProperty + " -> " + level);
-                    mp.setDrmPropertyString(securityLevelProperty, widevineSecurityLevel3);
-                    level = mp.getDrmPropertyString(securityLevelProperty);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
-                            + securityLevelProperty + " -> " + level);
-                } catch (MediaPlayer2.NoDrmSchemeException e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
-                }
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
-        mPlayer.prepare();
-
-        mOnPreparedCalled.waitForSignal();
-        if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
-            throw new IOException();
-        }
-
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
-            throws InterruptedException {
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
-            @Override
-            public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
-
-                // DRM preperation
-                List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.isEmpty()) {
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
-                    asyncSetupDrmError.set(true);
-                    mOnDrmInfoCalled.signal();
-                    // we won't call prepareDrm anymore but need to get passed the wait
-                    mOnDrmPreparedCalled.signal();
-                    return;
-                }
-
-                // setting up with the first supported UUID
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
-
-                try {
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
-                    mp.prepareDrm(drmScheme);
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
-                    asyncSetupDrmError.set(true);
-                    mOnDrmInfoCalled.signal();
-                    // need to get passed the wait
-                    mOnDrmPreparedCalled.signal();
-                    return;
-                }
-
-                mOnDrmInfoCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
-            }
-
-            @Override
-            public void onDrmPrepared(MediaPlayer2 mp, MediaItem item, int status) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
-
-                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
-                           status == MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS);
-
-                DrmInfo drmInfo = mPlayer.getDrmInfo();
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                mOnDrmPreparedCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V3: calling prepare()");
-        mPlayer.prepare();
-
-        // Waiting till the player is prepared
-        mOnPreparedCalled.waitForSignal();
-
-        // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
-        mOnDrmPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V3: setupDrm");
-        }
-    }
-
-    private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
-            final Integer height, int playTime) throws Exception {
-        final float volume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
-        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setSurface " + surfaceHolder);
-        mPlayer.setSurface(surfaceHolder.getSurface());
-        surfaceHolder.setKeepScreenOn(true);
-
-        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        DrmInfo drmInfo = null;
-
-        for (int round = 0; round < 2; round++) {
-            boolean keyRequestRound = (round == 0);
-            boolean restoreRound = (round == 1);
-            Log.v(TAG, "playLoadedVideo: round " + round);
-
-            try {
-                mPlayer.setEventCallback(mExecutor, mECb);
-
-                Log.v(TAG, "playLoadedVideo: setMediaItem()");
-                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
-
-                Log.v(TAG, "playLoadedVideo: prepare()");
-                mPlayer.prepare();
-                mOnPreparedCalled.waitForSignal();
-
-                // but preparing the DRM every time with proper key request type
-                drmInfo = mPlayer.getDrmInfo();
-                if (drmInfo != null) {
-                    if (keyRequestRound) {
-                        // asking for offline keys
-                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                                 MediaDrm.KEY_TYPE_OFFLINE);
-                    } else if (restoreRound) {
-                        setupDrmRestore(drmInfo, true /* prepareDrm */);
-                    } else {
-                        fail("preparePlayer: unexpected round " + round);
-                    }
-                    Log.v(TAG, "preparePlayer: setupDrm done!");
-                }
-
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            Log.v(TAG, "playLoadedVideo: play()");
-            mPlayer.play();
-            if (!mAudioOnly) {
-                mOnVideoSizeChangedCalled.waitForSignal();
-            }
-            mPlayer.setPlayerVolume(volume);
-
-            // waiting to complete
-            if (playTime == 0) {
-                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-                mOnPlaybackCompleted.waitForSignal();
-            } else {
-                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-                mOnPlaybackCompleted.waitForSignal(playTime);
-            }
-
-            try {
-                if (drmInfo != null) {
-                    if (restoreRound) {
-                        // releasing the offline key
-                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
-                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
-                        Log.v(TAG, "playLoadedVideo: released offline keys");
-                    }
-
-                    Log.v(TAG, "playLoadedVideo: releaseDrm");
-                    mPlayer.releaseDrm();
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            if (keyRequestRound) {
-                mOnPreparedCalled.reset();
-                mOnVideoSizeChangedCalled.reset();
-                mOnPlaybackCompleted.reset();
-                final int sleepBetweenRounds = 1000;
-                Thread.sleep(sleepBetweenRounds);
-
-                Log.v(TAG, "playLoadedVideo: reset");
-                mPlayer.reset();
-            }
-        }  // for
-    }
-
-    // Converts a BMFF PSSH initData to a raw cenc initData
-    protected byte[] makeCencPSSH(UUID uuid, byte[] bmffPsshData) {
-        byte[] pssh_header = new byte[] { (byte) 'p', (byte) 's', (byte) 's', (byte) 'h' };
-        byte[] pssh_version = new byte[] { 1, 0, 0, 0 };
-        int boxSizeByteCount = 4;
-        int uuidByteCount = 16;
-        int dataSizeByteCount = 4;
-        // Per "W3C cenc Initialization Data Format" document:
-        // box size + 'pssh' + version + uuid + payload + size of data
-        int boxSize = boxSizeByteCount + pssh_header.length + pssh_version.length
-                + uuidByteCount + bmffPsshData.length + dataSizeByteCount;
-        int dataSize = 0;
-
-        // the default write is big-endian, i.e., network byte order
-        ByteBuffer rawPssh = ByteBuffer.allocate(boxSize);
-        rawPssh.putInt(boxSize);
-        rawPssh.put(pssh_header);
-        rawPssh.put(pssh_version);
-        rawPssh.putLong(uuid.getMostSignificantBits());
-        rawPssh.putLong(uuid.getLeastSignificantBits());
-        rawPssh.put(bmffPsshData);
-        rawPssh.putInt(dataSize);
-
-        return rawPssh.array();
-    }
-
-    /*
-     * Sets up the DRM for the first DRM scheme from the supported list.
-     *
-     * @param drmInfo DRM info of the source
-     * @param prepareDrm whether prepareDrm should be called
-     * @param synchronousNetworking whether the network operation of key request/response will
-     *        be performed synchronously
-     */
-    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
-            int keyType) throws Exception {
-        Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm
-                + " synchronousNetworking: " + synchronousNetworking);
-        try {
-            byte[] initData = null;
-            String mime = null;
-            String keyTypeStr = "Unexpected";
-
-            switch (keyType) {
-                case MediaDrm.KEY_TYPE_STREAMING:
-                case MediaDrm.KEY_TYPE_OFFLINE:
-                    // DRM preparation
-                    List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                    if (supportedSchemes.isEmpty()) {
-                        fail("setupDrm: No supportedSchemes");
-                    }
-
-                    // instead of supportedSchemes[0] in GTS
-                    UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                    Log.d(TAG, "setupDrm: selected " + drmScheme);
-
-                    if (prepareDrm) {
-                        final Monitor drmPrepared = new Monitor();
-                        mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
-                            @Override
-                            public void onDrmPrepared(
-                                    MediaPlayer2 mp, MediaItem item, int status) {
-                                drmPrepared.signal();
-                            }
-                        });
-                        mPlayer.prepareDrm(drmScheme);
-                        drmPrepared.waitForSignal();
-                    }
-
-                    byte[] psshData = drmInfo.getPssh().get(drmScheme);
-                    // diverging from GTS
-                    if (psshData == null) {
-                        initData = mClearKeyPssh;
-                        Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH."
-                                + " Using default data.");
-                    } else {
-                        // Can skip conversion if ClearKey adds support for BMFF initData b/64863112
-                        initData = makeCencPSSH(CLEARKEY_SCHEME_UUID, psshData);
-                    }
-                    Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: "
-                            + Arrays.toString(initData));
-
-                    // diverging from GTS
-                    mime = "cenc";
-
-                    keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING)
-                            ? "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
-                    break;
-
-                case MediaDrm.KEY_TYPE_RELEASE:
-                    if (mKeySetId == null) {
-                        fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
-                    }
-                    keyTypeStr = "KEY_TYPE_RELEASE";
-                    break;
-
-                default:
-                    fail("setupDrm: Unexpected keyType " + keyType);
-            }
-
-            final MediaDrm.KeyRequest request = mPlayer.getDrmKeyRequest(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    initData,
-                    mime,
-                    keyType,
-                    null /* optionalKeyRequestParameters */
-                    );
-
-            Log.d(TAG, "setupDrm: mPlayer.getDrmKeyRequest(" + keyTypeStr
-                    + ") request -> " + request);
-
-            // diverging from GTS
-            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
-            byte[] response = createKeysResponse(request, clearKeys);
-
-            // null is returned when the response is for a streaming or release request.
-            byte[] keySetId = mPlayer.provideDrmKeyResponse(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    response);
-            Log.d(TAG, "setupDrm: provideDrmKeyResponse -> " + Arrays.toString(keySetId));
-            // storing offline key for a later restore
-            mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
-
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            Log.d(TAG, "setupDrm: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.d(TAG, "setupDrm: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrm
-
-    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
-        Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
-        try {
-            if (prepareDrm) {
-                // DRM preparation
-                List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.isEmpty()) {
-                    fail("setupDrmRestore: No supportedSchemes");
-                }
-
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
-
-                mPlayer.prepareDrm(drmScheme);
-            }
-
-            if (mKeySetId == null) {
-                fail("setupDrmRestore: Offline key has not been setup.");
-            }
-
-            mPlayer.restoreDrmKeys(mKeySetId);
-
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.v(TAG, "setupDrmRestore: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrmRestore
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Diverging from GTS
-
-    // Clearkey helpers
-
-    /**
-     * Convert a hex string into byte array.
-     */
-    private static byte[] hexStringToByteArray(String s) {
-        int len = s.length();
-        byte[] data = new byte[len / 2];
-        for (int i = 0; i < len; i += 2) {
-            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
-                    + Character.digit(s.charAt(i + 1), 16));
-        }
-        return data;
-    }
-
-    /**
-     * Extracts key ids from the pssh blob returned by getDrmKeyRequest() and
-     * places it in keyIds.
-     * keyRequestBlob format (section 5.1.3.1):
-     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
-     *
-     * @return size of keyIds vector that contains the key ids, 0 for error
-     */
-    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
-        if (0 == keyRequestBlob.length || keyIds == null) {
-            Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
-            return 0;
-        }
-
-        String jsonLicenseRequest = new String(keyRequestBlob);
-        keyIds.clear();
-
-        try {
-            JSONObject license = new JSONObject(jsonLicenseRequest);
-            Log.v(TAG, "getKeyIds: license: " + license);
-            final JSONArray ids = license.getJSONArray("kids");
-            Log.v(TAG, "getKeyIds: ids: " + ids);
-            for (int i = 0; i < ids.length(); ++i) {
-                keyIds.add(ids.getString(i));
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
-            return 0;
-        }
-        return keyIds.size();
-    }
-
-    /**
-     * Creates the JSON Web Key string.
-     *
-     * @return JSON Web Key string.
-     */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
-        String jwkSet = "{\"keys\":[";
-        for (int i = 0; i < keyIds.size(); ++i) {
-            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
-            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
-
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id + "\",\"k\":\"" + key + "\"}";
-        }
-        jwkSet += "]}";
-        return jwkSet;
-    }
-
-    /**
-     * Retrieves clear key ids from KeyRequest and creates the response in place.
-     */
-    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
-
-        Vector<String> keyIds = new Vector<String>();
-        if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
-            Log.e(TAG, "No key ids found in initData");
-            return null;
-        }
-
-        if (clearKeys.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids="
-                    + keyIds.size() + ", keys=" + clearKeys.length);
-            return null;
-        }
-
-        // Base64 encodes clearkeys. Keys are known to the application.
-        Vector<String> keys = new Vector<String>();
-        for (int i = 0; i < clearKeys.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeys[i],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
-            keys.add(clearKey);
-        }
-
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
-        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
-
-        return jsonResponse;
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Playback/download helpers
-
-    private static class MediaDownloadManager {
-        private static final String TAG = "MediaDownloadManager";
-
-        private final Context mContext;
-        private final DownloadManager mDownloadManager;
-
-        MediaDownloadManager(Context context) {
-            mContext = context;
-            mDownloadManager =
-                    (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
-        }
-
-        public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
-                throws Exception {
-            long id = -1;
-            for (int i = 0; i < retries; i++) {
-                try {
-                    id = downloadFile(uri, file, timeout);
-                    if (id != -1) {
-                        break;
-                    }
-                } catch (Exception e) {
-                    removeFile(id);
-                    Log.w(TAG, "Download failed " + i + " times ");
-                }
-            }
-            return id;
-        }
-
-        public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
-            Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
-            final DownloadReceiver receiver = new DownloadReceiver();
-            long id = -1;
-            try {
-                IntentFilter intentFilter =
-                        new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
-                if (Build.VERSION.SDK_INT < 33) {
-                    mContext.registerReceiver(receiver, intentFilter);
-                } else {
-                    mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED);
-                }
-
-                Request request = new Request(uri);
-                request.setDestinationUri(file);
-                id = mDownloadManager.enqueue(request);
-                Log.i(TAG, "enqueue:" + id);
-
-                receiver.waitForDownloadComplete(timeout, id);
-            } finally {
-                mContext.unregisterReceiver(receiver);
-            }
-            return id;
-        }
-
-        public void removeFile(long id) {
-            Log.i(TAG, "removeFile:" + id);
-            mDownloadManager.remove(id);
-        }
-
-        public Uri getUriForDownloadedFile(long id) {
-            return mDownloadManager.getUriForDownloadedFile(id);
-        }
-
-        private final class DownloadReceiver extends BroadcastReceiver {
-            private HashSet<Long> mCompleteIds = new HashSet<>();
-
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                synchronized (mCompleteIds) {
-                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
-                        mCompleteIds.add(
-                                intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
-                        mCompleteIds.notifyAll();
-                    }
-                }
-            }
-
-            private boolean isCompleteLocked(long... ids) {
-                for (long id : ids) {
-                    if (!mCompleteIds.contains(id)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
-                    throws InterruptedException {
-                if (waitForIds.length == 0) {
-                    throw new IllegalArgumentException("Missing IDs to wait for");
-                }
-
-                final long startTime = SystemClock.elapsedRealtime();
-                do {
-                    synchronized (mCompleteIds) {
-                        mCompleteIds.wait(1000);
-                        if (isCompleteLocked(waitForIds)) {
-                            return;
-                        }
-                    }
-                } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
-
-                throw new InterruptedException(
-                        "Timeout waiting for IDs " + Arrays.toString(waitForIds)
-                        + "; received " + mCompleteIds.toString()
-                        + ".  Make sure you have WiFi or some other connectivity for this test.");
-            }
-        }
-
-    }  // MediaDownloadManager
-
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2StateTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2StateTest.java
deleted file mode 100644
index 1acfcf8..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2StateTest.java
+++ /dev/null
@@ -1,1186 +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.media2.player;
-
-import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PAUSE;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PLAY;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_PREPARE;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SEEK_TO;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SELECT_TRACK;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SET_SURFACE;
-import static androidx.media2.player.MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT;
-import static androidx.media2.player.MediaPlayer2.CALL_STATUS_INVALID_OPERATION;
-import static androidx.media2.player.MediaPlayer2.CALL_STATUS_NO_ERROR;
-import static androidx.media2.player.MediaPlayer2.PLAYER_STATE_ERROR;
-import static androidx.media2.player.MediaPlayer2.PLAYER_STATE_IDLE;
-import static androidx.media2.player.MediaPlayer2.PLAYER_STATE_PAUSED;
-import static androidx.media2.player.MediaPlayer2.PLAYER_STATE_PLAYING;
-import static androidx.media2.player.MediaPlayer2.PLAYER_STATE_PREPARED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Build;
-import android.util.Pair;
-
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.player.MediaPlayer2.MediaPlayer2State;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.media2.player.test.R;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Executors;
-
-@RunWith(Parameterized.class)
-public class MediaPlayer2StateTest extends MediaPlayer2TestBase {
-    private static final String LOG_TAG = "MediaPlayer2StateTest";
-
-    // TODO: Underlying MediaPlayer1 implementation does not report an error when an operation is
-    // triggered in an invalid state. e.g. MediaPlayer.getTracks in the error state. Check the
-    // cause and update javadoc of MediaPlayer1 or change the test case.
-    private static final boolean CHECK_INVALID_STATE = false;
-
-    // Used for testing case that operation is called before setDataSourceDesc().
-    private static final int MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE = 400001;
-
-    private static final MediaItem FAKE_DATA_SOURCE = new CallbackMediaItem.Builder(
-            new DataSourceCallback() {
-                @Override
-                public int readAt(long position, byte[] buffer, int offset, int size)
-                        throws IOException {
-                    return -1;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return -1;  // Unknown size
-                }
-
-                @Override
-                public void close() throws IOException {}
-            })
-            .build();
-
-    private static final PlayerOperation sCloseOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.close();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;  // synchronous operation.
-        }
-
-        @Override
-        public String toString() {
-            return "close()";
-        }
-    };
-    private static final PlayerOperation sPlayOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.play();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_PLAY;
-        }
-
-        @Override
-        public String toString() {
-            return "play()";
-        }
-    };
-    private static final PlayerOperation sPrepareOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.prepare();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_PREPARE;
-        }
-
-        @Override
-        public String toString() {
-            return "prepare()";
-        }
-    };
-    private static final PlayerOperation sPauseOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.pause();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_PAUSE;
-        }
-
-        @Override
-        public String toString() {
-            return "pause()";
-        }
-    };
-    private static final PlayerOperation sSkipToNextOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.skipToNext();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SKIP_TO_NEXT;
-        }
-
-        @Override
-        public String toString() {
-            return "skipToNext()";
-        }
-    };
-    private static final PlayerOperation sSeekToOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.seekTo(1000);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SEEK_TO;
-        }
-
-        @Override
-        public String toString() {
-            return "seekTo()";
-        }
-    };
-    private static final PlayerOperation sGetCurrentPositionOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getCurrentPosition();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getCurrentPosition()";
-        }
-    };
-    private static final PlayerOperation sGetDurationOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getDuration();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getDuration()";
-        }
-    };
-    private static final PlayerOperation sGetBufferedPositionOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getBufferedPosition();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getBufferedPosition()";
-        }
-    };
-    private static final PlayerOperation sGetStateOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getState();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getState()";
-        }
-    };
-    private static final PlayerOperation sSetAudioAttributesOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setAudioAttributes(
-                    new AudioAttributesCompat.Builder().setContentType(CONTENT_TYPE_MUSIC).build());
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_AUDIO_ATTRIBUTES;
-        }
-
-        @Override
-        public String toString() {
-            return "setAudioAttributes()";
-        }
-    };
-    private static final PlayerOperation sGetAudioAttributesOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getAudioAttributes();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getAudioAttributes()";
-        }
-    };
-    private static final PlayerOperation sSetDataSourceOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setMediaItem(FAKE_DATA_SOURCE);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_DATA_SOURCE;
-        }
-
-        @Override
-        public String toString() {
-            return "setMediaItem()";
-        }
-    };
-    private static final PlayerOperation sSetNextDataSourceOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setNextMediaItem(FAKE_DATA_SOURCE);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_NEXT_DATA_SOURCE;
-        }
-
-        @Override
-        public String toString() {
-            return "setNextMediaItem()";
-        }
-    };
-    private static final PlayerOperation sSetNextDataSourcesOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setNextMediaItems(Arrays.asList(FAKE_DATA_SOURCE));
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_NEXT_DATA_SOURCES;
-        }
-
-        @Override
-        public String toString() {
-            return "setNextMediaItems()";
-        }
-    };
-    private static final PlayerOperation sLoopCurrentOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.loopCurrent(true);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_LOOP_CURRENT;
-        }
-
-        @Override
-        public String toString() {
-            return "loopCurrent()";
-        }
-    };
-    private static final PlayerOperation sSetPlayerVolumeOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setPlayerVolume(0.5f);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_PLAYER_VOLUME;
-        }
-
-        @Override
-        public String toString() {
-            return "setPlayerVolume()";
-        }
-    };
-    private static final PlayerOperation sGetPlayerVolumeOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getPlayerVolume();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getPlayerVolume()";
-        }
-    };
-    private static final PlayerOperation sGetMaxPlayerVolumeOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getMaxPlayerVolume();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getMaxPlayerVolume()";
-        }
-    };
-    private static final PlayerOperation sNotifyWhenCommandLabelReachedOperation =
-            new PlayerOperation() {
-                @Override
-                public void doOperation(MediaPlayer2 player) {
-                    player.notifyWhenCommandLabelReached(new Object());
-                }
-
-                @Override
-                public Integer getCallCompleteCode() {
-                    return CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED;
-                }
-
-                @Override
-                public String toString() {
-                    return "notifyWhenCommandLabelReached()";
-                }
-            };
-    private static final PlayerOperation sSetSurfaceOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setSurface(null);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_SURFACE;
-        }
-
-        @Override
-        public String toString() {
-            return "setSurface()";
-        }
-    };
-    private static final PlayerOperation sClearPendingCommandsOperation =
-            new PlayerOperation() {
-                @Override
-                public void doOperation(MediaPlayer2 player) {
-                    player.clearPendingCommands();
-                }
-
-                @Override
-                public Integer getCallCompleteCode() {
-                    return null;
-                }
-
-                @Override
-                public String toString() {
-                    return "clearPendingCommands()";
-                }
-            };
-    private static final PlayerOperation sGetVideoWidthOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getVideoWidth();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getVideoWidth()";
-        }
-    };
-    private static final PlayerOperation sGetVideoHeightOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getVideoHeight();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getVideoHeight()";
-        }
-    };
-    private static final PlayerOperation sGetMetricsOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            // Validate media metrics from API 21 where PersistableBundle was added.
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                player.getMetrics();
-            }
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getMetrics()";
-        }
-    };
-    private static final PlayerOperation sSetPlaybackParamsOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setPlaybackParams(new PlaybackParams.Builder().setSpeed(1.0f).build());
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_PLAYBACK_PARAMS;
-        }
-
-        @Override
-        public String toString() {
-            return "setPlaybackParams()";
-        }
-    };
-    private static final PlayerOperation sGetPlaybackParamsOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getPlaybackParams();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getPlaybackParams()";
-        }
-    };
-    private static final PlayerOperation sGetTimestampOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getTimestamp();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getTimestamp()";
-        }
-    };
-    private static final PlayerOperation sResetOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.reset();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "reset()";
-        }
-    };
-    private static final PlayerOperation sSetAudioSessionIdOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setAudioSessionId(0);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_AUDIO_SESSION_ID;
-        }
-
-        @Override
-        public String toString() {
-            return "setAudioSessionId()";
-        }
-    };
-    private static final PlayerOperation sGetAudioSessionIdOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getAudioSessionId();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getAudioSessionId()";
-        }
-    };
-    private static final PlayerOperation sAttachAuxEffectOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.attachAuxEffect(0);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_ATTACH_AUX_EFFECT;
-        }
-
-        @Override
-        public String toString() {
-            return "attachAuxEffect()";
-        }
-    };
-    private static final PlayerOperation sSetAuxEffectSendLevelOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setAuxEffectSendLevel(0.5f);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL;
-        }
-
-        @Override
-        public String toString() {
-            return "setAuxEffectSendLevel()";
-        }
-    };
-    private static final PlayerOperation sGetTrackInfoOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getTracks();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getTracks()";
-        }
-    };
-    private static final PlayerOperation sGetSelectedTrackOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_VIDEO);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "getSelectedTrack()";
-        }
-    };
-    private static final PlayerOperation sSelectTrackOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            // The trackId may be an illegal argument
-            player.selectTrack(/* trackId= */ 0);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_SELECT_TRACK;
-        }
-
-        @Override
-        public String toString() {
-            return "selectTrack()";
-        }
-    };
-    private static final PlayerOperation sDeselectTrackOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            // The trackId may be an illegal argument
-            player.deselectTrack(/* trackId= */ 0);
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return CALL_COMPLETED_DESELECT_TRACK;
-        }
-
-        @Override
-        public String toString() {
-            return "deselectTrack()";
-        }
-    };
-    private static final PlayerOperation sSetEventCallbackOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.setEventCallback(
-                    Executors.newFixedThreadPool(1), new MediaPlayer2.EventCallback(){});
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "setEventCallback()";
-        }
-    };
-    private static final PlayerOperation sClearEventCallbackOperation = new PlayerOperation() {
-        @Override
-        public void doOperation(MediaPlayer2 player) {
-            player.clearEventCallback();
-        }
-
-        @Override
-        public Integer getCallCompleteCode() {
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            return "clearEventCallback()";
-        }
-    };
-
-    private @MediaPlayer2State int mTestState;
-    private PlayerOperation mTestOperation;
-    private boolean mIsValidOperation;
-
-    @Parameterized.Parameters(name = "{index}: operation={0} state={1} valid={2}")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] {
-                { sCloseOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sCloseOperation, PLAYER_STATE_IDLE, true },
-                { sCloseOperation, PLAYER_STATE_PREPARED, true },
-                { sCloseOperation, PLAYER_STATE_PAUSED, true },
-                { sCloseOperation, PLAYER_STATE_PLAYING, true },
-                { sCloseOperation, PLAYER_STATE_ERROR, true },
-
-                { sPlayOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sPlayOperation, PLAYER_STATE_IDLE, false },
-                { sPlayOperation, PLAYER_STATE_PREPARED, true },
-                { sPlayOperation, PLAYER_STATE_PAUSED, true },
-                { sPlayOperation, PLAYER_STATE_PLAYING, true },
-                { sPlayOperation, PLAYER_STATE_ERROR, false },
-
-                { sPrepareOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sPrepareOperation, PLAYER_STATE_IDLE, true },
-                { sPrepareOperation, PLAYER_STATE_PREPARED, false },
-                { sPrepareOperation, PLAYER_STATE_PAUSED, false },
-                { sPrepareOperation, PLAYER_STATE_PLAYING, false },
-                { sPrepareOperation, PLAYER_STATE_ERROR, false },
-
-                { sPauseOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sPauseOperation, PLAYER_STATE_IDLE, false },
-                { sPauseOperation, PLAYER_STATE_PREPARED, true },
-                { sPauseOperation, PLAYER_STATE_PAUSED, true },
-                { sPauseOperation, PLAYER_STATE_PLAYING, true },
-                { sPauseOperation, PLAYER_STATE_ERROR, false },
-
-                { sSkipToNextOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sSkipToNextOperation, PLAYER_STATE_IDLE, true },
-                { sSkipToNextOperation, PLAYER_STATE_PREPARED, true },
-                { sSkipToNextOperation, PLAYER_STATE_PAUSED, true },
-                { sSkipToNextOperation, PLAYER_STATE_PLAYING, true },
-                { sSkipToNextOperation, PLAYER_STATE_ERROR, false },
-
-                { sSeekToOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sSeekToOperation, PLAYER_STATE_IDLE, false },
-                { sSeekToOperation, PLAYER_STATE_PREPARED, true },
-                { sSeekToOperation, PLAYER_STATE_PAUSED, true },
-                { sSeekToOperation, PLAYER_STATE_PLAYING, true },
-                { sSeekToOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetCurrentPositionOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetCurrentPositionOperation, PLAYER_STATE_IDLE, false },
-                { sGetCurrentPositionOperation, PLAYER_STATE_PREPARED, true },
-                { sGetCurrentPositionOperation, PLAYER_STATE_PAUSED, true },
-                { sGetCurrentPositionOperation, PLAYER_STATE_PLAYING, true },
-                { sGetCurrentPositionOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetDurationOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetDurationOperation, PLAYER_STATE_IDLE, false },
-                { sGetDurationOperation, PLAYER_STATE_PREPARED, true },
-                { sGetDurationOperation, PLAYER_STATE_PAUSED, true },
-                { sGetDurationOperation, PLAYER_STATE_PLAYING, true },
-                { sGetDurationOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetBufferedPositionOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetBufferedPositionOperation, PLAYER_STATE_IDLE, false },
-                { sGetBufferedPositionOperation, PLAYER_STATE_PREPARED, true },
-                { sGetBufferedPositionOperation, PLAYER_STATE_PAUSED, true },
-                { sGetBufferedPositionOperation, PLAYER_STATE_PLAYING, true },
-                { sGetBufferedPositionOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetStateOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetStateOperation, PLAYER_STATE_IDLE, true },
-                { sGetStateOperation, PLAYER_STATE_PREPARED, true },
-                { sGetStateOperation, PLAYER_STATE_PAUSED, true },
-                { sGetStateOperation, PLAYER_STATE_PLAYING, true },
-                { sGetStateOperation, PLAYER_STATE_ERROR, true },
-
-                { sSetAudioAttributesOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetAudioAttributesOperation, PLAYER_STATE_IDLE, true },
-                { sSetAudioAttributesOperation, PLAYER_STATE_PREPARED, true },
-                { sSetAudioAttributesOperation, PLAYER_STATE_PAUSED, true },
-                { sSetAudioAttributesOperation, PLAYER_STATE_PLAYING, true },
-                { sSetAudioAttributesOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetDataSourceOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetDataSourceOperation, PLAYER_STATE_IDLE, false },
-                { sSetDataSourceOperation, PLAYER_STATE_PREPARED, false },
-                { sSetDataSourceOperation, PLAYER_STATE_PAUSED, false },
-                { sSetDataSourceOperation, PLAYER_STATE_PLAYING, false },
-                { sSetDataSourceOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetNextDataSourceOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sSetNextDataSourceOperation, PLAYER_STATE_IDLE, true },
-                { sSetNextDataSourceOperation, PLAYER_STATE_PREPARED, true },
-                { sSetNextDataSourceOperation, PLAYER_STATE_PAUSED, true },
-                { sSetNextDataSourceOperation, PLAYER_STATE_PLAYING, true },
-                { sSetNextDataSourceOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetNextDataSourcesOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sSetNextDataSourcesOperation, PLAYER_STATE_IDLE, true },
-                { sSetNextDataSourcesOperation, PLAYER_STATE_PREPARED, true },
-                { sSetNextDataSourcesOperation, PLAYER_STATE_PAUSED, true },
-                { sSetNextDataSourcesOperation, PLAYER_STATE_PLAYING, true },
-                { sSetNextDataSourcesOperation, PLAYER_STATE_ERROR, false },
-
-                { sLoopCurrentOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sLoopCurrentOperation, PLAYER_STATE_IDLE, true },
-                { sLoopCurrentOperation, PLAYER_STATE_PREPARED, true },
-                { sLoopCurrentOperation, PLAYER_STATE_PAUSED, true },
-                { sLoopCurrentOperation, PLAYER_STATE_PLAYING, true },
-                { sLoopCurrentOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetPlayerVolumeOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetPlayerVolumeOperation, PLAYER_STATE_IDLE, true },
-                { sSetPlayerVolumeOperation, PLAYER_STATE_PREPARED, true },
-                { sSetPlayerVolumeOperation, PLAYER_STATE_PAUSED, true },
-                { sSetPlayerVolumeOperation, PLAYER_STATE_PLAYING, true },
-                { sSetPlayerVolumeOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetPlayerVolumeOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetPlayerVolumeOperation, PLAYER_STATE_IDLE, true },
-                { sGetPlayerVolumeOperation, PLAYER_STATE_PREPARED, true },
-                { sGetPlayerVolumeOperation, PLAYER_STATE_PAUSED, true },
-                { sGetPlayerVolumeOperation, PLAYER_STATE_PLAYING, true },
-                { sGetPlayerVolumeOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetMaxPlayerVolumeOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetMaxPlayerVolumeOperation, PLAYER_STATE_IDLE, true },
-                { sGetMaxPlayerVolumeOperation, PLAYER_STATE_PREPARED, true },
-                { sGetMaxPlayerVolumeOperation, PLAYER_STATE_PAUSED, true },
-                { sGetMaxPlayerVolumeOperation, PLAYER_STATE_PLAYING, true },
-                { sGetMaxPlayerVolumeOperation, PLAYER_STATE_ERROR, false },
-
-                { sNotifyWhenCommandLabelReachedOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE,
-                        true },
-                { sNotifyWhenCommandLabelReachedOperation, PLAYER_STATE_IDLE, true },
-                { sNotifyWhenCommandLabelReachedOperation, PLAYER_STATE_PREPARED, true },
-                { sNotifyWhenCommandLabelReachedOperation, PLAYER_STATE_PAUSED, true },
-                { sNotifyWhenCommandLabelReachedOperation, PLAYER_STATE_PLAYING, true },
-                { sNotifyWhenCommandLabelReachedOperation, PLAYER_STATE_ERROR, true },
-
-                { sSetSurfaceOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetSurfaceOperation, PLAYER_STATE_IDLE, true },
-                { sSetSurfaceOperation, PLAYER_STATE_PREPARED, true },
-                { sSetSurfaceOperation, PLAYER_STATE_PAUSED, true },
-                { sSetSurfaceOperation, PLAYER_STATE_PLAYING, true },
-                { sSetSurfaceOperation, PLAYER_STATE_ERROR, false },
-
-                { sClearPendingCommandsOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sClearPendingCommandsOperation, PLAYER_STATE_IDLE, true },
-                { sClearPendingCommandsOperation, PLAYER_STATE_PREPARED, true },
-                { sClearPendingCommandsOperation, PLAYER_STATE_PAUSED, true },
-                { sClearPendingCommandsOperation, PLAYER_STATE_PLAYING, true },
-                { sClearPendingCommandsOperation, PLAYER_STATE_ERROR, true },
-
-                { sGetVideoWidthOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetVideoWidthOperation, PLAYER_STATE_IDLE, true },
-                { sGetVideoWidthOperation, PLAYER_STATE_PREPARED, true },
-                { sGetVideoWidthOperation, PLAYER_STATE_PAUSED, true },
-                { sGetVideoWidthOperation, PLAYER_STATE_PLAYING, true },
-                { sGetVideoWidthOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetVideoHeightOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetVideoHeightOperation, PLAYER_STATE_IDLE, true },
-                { sGetVideoHeightOperation, PLAYER_STATE_PREPARED, true },
-                { sGetVideoHeightOperation, PLAYER_STATE_PAUSED, true },
-                { sGetVideoHeightOperation, PLAYER_STATE_PLAYING, true },
-                { sGetVideoHeightOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetMetricsOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetMetricsOperation, PLAYER_STATE_IDLE, true },
-                { sGetMetricsOperation, PLAYER_STATE_PREPARED, true },
-                { sGetMetricsOperation, PLAYER_STATE_PAUSED, true },
-                { sGetMetricsOperation, PLAYER_STATE_PLAYING, true },
-                { sGetMetricsOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetPlaybackParamsOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetPlaybackParamsOperation, PLAYER_STATE_IDLE, true },
-                { sSetPlaybackParamsOperation, PLAYER_STATE_PREPARED, true },
-                { sSetPlaybackParamsOperation, PLAYER_STATE_PAUSED, true },
-                { sSetPlaybackParamsOperation, PLAYER_STATE_PLAYING, true },
-                { sSetPlaybackParamsOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetPlaybackParamsOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetPlaybackParamsOperation, PLAYER_STATE_IDLE, true },
-                { sGetPlaybackParamsOperation, PLAYER_STATE_PREPARED, true },
-                { sGetPlaybackParamsOperation, PLAYER_STATE_PAUSED, true },
-                { sGetPlaybackParamsOperation, PLAYER_STATE_PLAYING, true },
-                { sGetPlaybackParamsOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetTimestampOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetTimestampOperation, PLAYER_STATE_IDLE, false },
-                { sGetTimestampOperation, PLAYER_STATE_PREPARED, true },
-                { sGetTimestampOperation, PLAYER_STATE_PAUSED, true },
-                { sGetTimestampOperation, PLAYER_STATE_PLAYING, true },
-                { sGetTimestampOperation, PLAYER_STATE_ERROR, false },
-
-                { sResetOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sResetOperation, PLAYER_STATE_IDLE, true },
-                { sResetOperation, PLAYER_STATE_PREPARED, true },
-                { sResetOperation, PLAYER_STATE_PAUSED, true },
-                { sResetOperation, PLAYER_STATE_PLAYING, true },
-                { sResetOperation, PLAYER_STATE_ERROR, true },
-
-                { sSetAudioSessionIdOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetAudioSessionIdOperation, PLAYER_STATE_IDLE, true },
-                { sSetAudioSessionIdOperation, PLAYER_STATE_PREPARED, true },
-                { sSetAudioSessionIdOperation, PLAYER_STATE_PAUSED, true },
-                { sSetAudioSessionIdOperation, PLAYER_STATE_PLAYING, true },
-                { sSetAudioSessionIdOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetAudioSessionIdOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sGetAudioSessionIdOperation, PLAYER_STATE_IDLE, true },
-                { sGetAudioSessionIdOperation, PLAYER_STATE_PREPARED, true },
-                { sGetAudioSessionIdOperation, PLAYER_STATE_PAUSED, true },
-                { sGetAudioSessionIdOperation, PLAYER_STATE_PLAYING, true },
-                { sGetAudioSessionIdOperation, PLAYER_STATE_ERROR, false },
-
-                { sAttachAuxEffectOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sAttachAuxEffectOperation, PLAYER_STATE_IDLE, true },
-                { sAttachAuxEffectOperation, PLAYER_STATE_PREPARED, true },
-                { sAttachAuxEffectOperation, PLAYER_STATE_PAUSED, true },
-                { sAttachAuxEffectOperation, PLAYER_STATE_PLAYING, true },
-                { sAttachAuxEffectOperation, PLAYER_STATE_ERROR, false },
-
-                { sSetAuxEffectSendLevelOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetAuxEffectSendLevelOperation, PLAYER_STATE_IDLE, true },
-                { sSetAuxEffectSendLevelOperation, PLAYER_STATE_PREPARED, true },
-                { sSetAuxEffectSendLevelOperation, PLAYER_STATE_PAUSED, true },
-                { sSetAuxEffectSendLevelOperation, PLAYER_STATE_PLAYING, true },
-                { sSetAuxEffectSendLevelOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetTrackInfoOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sGetTrackInfoOperation, PLAYER_STATE_IDLE, true },
-                { sGetTrackInfoOperation, PLAYER_STATE_PREPARED, true },
-                { sGetTrackInfoOperation, PLAYER_STATE_PAUSED, true },
-                { sGetTrackInfoOperation, PLAYER_STATE_PLAYING, true },
-                { sGetTrackInfoOperation, PLAYER_STATE_ERROR, false },
-
-                { sGetSelectedTrackOperation, PLAYER_STATE_IDLE, true },
-                { sGetSelectedTrackOperation, PLAYER_STATE_IDLE, true },
-                { sGetSelectedTrackOperation, PLAYER_STATE_PREPARED, true },
-                { sGetSelectedTrackOperation, PLAYER_STATE_PAUSED, true },
-                { sGetSelectedTrackOperation, PLAYER_STATE_PLAYING, true },
-                { sGetSelectedTrackOperation, PLAYER_STATE_ERROR, false },
-
-                { sSelectTrackOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sSelectTrackOperation, PLAYER_STATE_IDLE, false },
-                { sSelectTrackOperation, PLAYER_STATE_PREPARED, true },
-                { sSelectTrackOperation, PLAYER_STATE_PAUSED, true },
-                { sSelectTrackOperation, PLAYER_STATE_PLAYING, true },
-                { sSelectTrackOperation, PLAYER_STATE_ERROR, false },
-
-                { sDeselectTrackOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, false },
-                { sDeselectTrackOperation, PLAYER_STATE_IDLE, false },
-                { sDeselectTrackOperation, PLAYER_STATE_PREPARED, true },
-                { sDeselectTrackOperation, PLAYER_STATE_PAUSED, true },
-                { sDeselectTrackOperation, PLAYER_STATE_PLAYING, true },
-                { sDeselectTrackOperation, PLAYER_STATE_ERROR, false},
-
-                { sSetEventCallbackOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sSetEventCallbackOperation, PLAYER_STATE_IDLE, true },
-                { sSetEventCallbackOperation, PLAYER_STATE_PREPARED, true },
-                { sSetEventCallbackOperation, PLAYER_STATE_PAUSED, true },
-                { sSetEventCallbackOperation, PLAYER_STATE_PLAYING, true },
-                { sSetEventCallbackOperation, PLAYER_STATE_ERROR, false },
-
-                { sClearEventCallbackOperation, MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE, true },
-                { sClearEventCallbackOperation, PLAYER_STATE_IDLE, true },
-                { sClearEventCallbackOperation, PLAYER_STATE_PREPARED, true },
-                { sClearEventCallbackOperation, PLAYER_STATE_PAUSED, true },
-                { sClearEventCallbackOperation, PLAYER_STATE_PLAYING, true },
-                { sClearEventCallbackOperation, PLAYER_STATE_ERROR, false },
-        });
-    }
-
-    public MediaPlayer2StateTest(
-            PlayerOperation operation, int testState, boolean isValid) {
-        mTestOperation = operation;
-        mTestState = testState;
-        mIsValidOperation = isValid;
-    }
-
-    private void setupPlayer() throws Exception {
-        final Monitor onPauseCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PAUSE) {
-                    onPauseCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PREPARE) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        if (mTestState == PLAYER_STATE_ERROR) {
-            DataSourceCallback invalidDataSource = new DataSourceCallback() {
-                @Override
-                public int readAt(long position, byte[] buffer, int offset, int size)
-                        throws IOException {
-                    return -1;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return -1;  // Unknown size
-                }
-
-                @Override
-                public void close() throws IOException {}
-            };
-            mOnErrorCalled.reset();
-            mPlayer.setMediaItem(new CallbackMediaItem.Builder(invalidDataSource)
-                    .build());
-            mPlayer.prepare();
-            mOnErrorCalled.waitForSignal(1000);
-            assertEquals(PLAYER_STATE_ERROR, mPlayer.getState());
-            return;
-        }
-
-        if (mTestState == MEDIAPLAYER2_STATE_IDLE_NO_DATA_SOURCE) {
-            mTestState = PLAYER_STATE_IDLE;
-            return;
-        }
-        if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            fail();
-        }
-        if (mTestOperation == sSkipToNextOperation) {
-            MediaItem item = createDataSourceDesc(R.raw.testvideo);
-            mPlayer.setNextMediaItem(item);
-        }
-        assertEquals(PLAYER_STATE_IDLE, mPlayer.getState());
-        if (mTestState == PLAYER_STATE_IDLE) {
-            return;
-        }
-
-        mPlayer.prepare();
-        // TODO(b/80232248): The first time one of the tests reads from the resource preparation can
-        // take ~ 1.5 seconds to complete with the pre-P implementation. Later calls take ~ 100 ms.
-        // Find out why the first preparation is slow and reduce this timeout back to one second.
-        mOnPrepareCalled.waitForSignal(2000);
-        assertEquals(PLAYER_STATE_PREPARED, mPlayer.getState());
-        if (mTestOperation == sDeselectTrackOperation) {
-            mPlayer.selectTrack(1);
-        }
-        if (mTestState == PLAYER_STATE_PREPARED) {
-            return;
-        }
-
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal(1000);
-        assertEquals(PLAYER_STATE_PLAYING, mPlayer.getState());
-        if (mTestState == PLAYER_STATE_PLAYING) {
-            return;
-        }
-
-        mPlayer.pause();
-        onPauseCalled.waitForSignal(1000);
-        assertEquals(PLAYER_STATE_PAUSED, mPlayer.getState());
-        if (mTestState == PLAYER_STATE_PAUSED) {
-            return;
-        }
-        fail();
-    }
-
-    @Test
-    @LargeTest
-    public void operation() throws Exception {
-        if (!CHECK_INVALID_STATE && !mIsValidOperation) {
-            return;
-        }
-        setupPlayer();
-
-        final List<Pair<Integer, Integer>> callCompletes = new ArrayList<>();
-        final Monitor callCompleteCalled = new Monitor();
-        final Monitor commandLabelReachedCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                callCompletes.add(new Pair<>(what, status));
-                callCompleteCalled.signal();
-            }
-
-            @Override
-            public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
-                callCompletes.add(new Pair<>(
-                            CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED,
-                            CALL_STATUS_NO_ERROR));
-                callCompleteCalled.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        commandLabelReachedCalled.reset();
-        Object tag = new Object();
-        mPlayer.notifyWhenCommandLabelReached(tag);
-        commandLabelReachedCalled.waitForSignal(1000);
-
-        callCompletes.clear();
-        callCompleteCalled.reset();
-        try {
-            mTestOperation.doOperation(mPlayer);
-        } catch (IllegalStateException e) {
-            if (mTestOperation.getCallCompleteCode() != null || mIsValidOperation) {
-                fail();
-            }
-        }
-        if (mTestOperation.getCallCompleteCode() != null) {
-            // asynchronous operation. Need to check call complete notification.
-            callCompleteCalled.waitForSignal();
-            assertEquals(mTestOperation.getCallCompleteCode(), callCompletes.get(0).first);
-            if (mIsValidOperation) {
-                assertNotEquals(CALL_STATUS_INVALID_OPERATION, (int) callCompletes.get(0).second);
-            } else {
-                assertEquals(CALL_STATUS_INVALID_OPERATION, (int) callCompletes.get(0).second);
-            }
-        } else if (!mIsValidOperation) {
-            fail();
-        }
-        if (mTestOperation == sCloseOperation) {
-            // The player has already been closed so prevent a second call to close in tearDown.
-            mPlayer = null;
-        } else {
-            // Clear the resource for resource leak checking in tearDown.
-            mPlayer.reset();
-        }
-    }
-
-    interface PlayerOperation {
-        void doOperation(MediaPlayer2 player);
-        /* Expected call complete notification. {@code null} if operation is synchronous. */
-        Integer getCallCompleteCode();
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
deleted file mode 100644
index 11585c6..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
+++ /dev/null
@@ -1,3019 +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.media2.player;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.hardware.Camera;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaRecorder;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.Visualizer;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.media2.player.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingDeque;
-
-@RunWith(AndroidJUnit4.class)
-public class MediaPlayer2Test extends MediaPlayer2TestBase {
-
-    private static final String LOG_TAG = "MediaPlayer2Test";
-
-    private static final int  RECORDED_VIDEO_WIDTH  = 176;
-    private static final int  RECORDED_VIDEO_HEIGHT = 144;
-    private static final long RECORDED_DURATION_MS  = 3000;
-    private static final float FLOAT_TOLERANCE = .0001f;
-    private static final long PLAYBACK_COMPLETE_TOLERANCE_MS = 100;
-
-    private String mRecordedFilePath;
-    private final List<TrackInfo> mVideoTrackInfos = new ArrayList<>();
-    private final List<TrackInfo> mAudioTrackInfos = new ArrayList<>();
-    private final List<TrackInfo> mSubtitleTrackInfos = new ArrayList<>();
-    private final Monitor mOnSubtitleDataCalled = new Monitor();
-
-    private File mOutFile;
-    private Camera mCamera;
-
-    @Before
-    @Override
-    public void setUp() throws Throwable {
-        super.setUp();
-        mRecordedFilePath = new File(Environment.getExternalStorageDirectory(),
-                "mediaplayer_record.out").getAbsolutePath();
-        mOutFile = new File(mRecordedFilePath);
-    }
-
-    @After
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        if (mOutFile != null && mOutFile.exists()) {
-            mOutFile.delete();
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void playNullSourcePath() throws Exception {
-        final Monitor onSetDataSourceCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
-                    assertTrue(status != MediaPlayer2.CALL_STATUS_NO_ERROR);
-                    onSetDataSourceCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        onSetDataSourceCalled.reset();
-        mPlayer.setMediaItem((MediaItem) null);
-        onSetDataSourceCalled.waitForSignal();
-    }
-
-    @Test
-    @LargeTest
-    public void playAudioFromDataURI() throws Exception {
-        final int mp3Duration = 34909;
-        final int tolerance = 100;
-        final int seekDuration = 100;
-
-        // This is "R.raw.testmp3_2", base64-encoded.
-        final int resid = R.raw.testmp3_3;
-
-        InputStream is = mContext.getResources().openRawResource(resid);
-        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-
-        StringBuilder builder = new StringBuilder();
-        builder.append("data:;base64,");
-        builder.append(reader.readLine());
-        Uri uri = Uri.parse(builder.toString());
-
-        MediaPlayer2 mp = createMediaPlayer2(mContext, uri);
-
-        final Monitor onPrepareCalled = new Monitor();
-        final Monitor onPlayCalled = new Monitor();
-        final Monitor onSeekToCalled = new Monitor();
-        final Monitor onLoopCurrentCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    onPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    onPlayCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT) {
-                    onLoopCurrentCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    onSeekToCalled.signal();
-                }
-            }
-        };
-        mp.setEventCallback(mExecutor, ecb);
-
-        try {
-            AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                    .build();
-            mp.setAudioAttributes(attributes);
-
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            /* FIXME: what's API for checking loop state?
-            assertFalse(mp.isLooping());
-            */
-            onLoopCurrentCalled.reset();
-            mp.loopCurrent(true);
-            onLoopCurrentCalled.waitForSignal();
-            /* FIXME: what's API for checking loop state?
-            assertTrue(mp.isLooping());
-            */
-
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
-            long pos = mp.getCurrentPosition();
-            assertTrue(pos >= 0);
-            assertTrue(pos < mp3Duration - seekDuration);
-
-            onSeekToCalled.reset();
-            mp.seekTo(pos + seekDuration, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-            onSeekToCalled.waitForSignal();
-            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
-
-            // test pause and restart
-            mp.pause();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            // test stop and restart
-            mp.reset();
-            mp.setEventCallback(mExecutor, ecb);
-            mp.setMediaItem(new UriMediaItem.Builder(uri).build());
-            onPrepareCalled.reset();
-            mp.prepare();
-            onPrepareCalled.waitForSignal();
-
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            // waiting to complete
-            while (mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } finally {
-            mp.close();
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void playAudio() throws Exception {
-        final int resid = R.raw.testmp3_2;
-        final int mp3Duration = 34909;
-        final int tolerance = 100;
-        final int seekDuration = 100;
-
-        MediaPlayer2 mp = createMediaPlayer2(mContext, resid);
-
-        final Monitor onPrepareCalled = new Monitor();
-        final Monitor onPlayCalled = new Monitor();
-        final Monitor onSeekToCalled = new Monitor();
-        final Monitor onLoopCurrentCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    onPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    onPlayCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT) {
-                    onLoopCurrentCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    onSeekToCalled.signal();
-                }
-            }
-        };
-        mp.setEventCallback(mExecutor, ecb);
-
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                    .build();
-            mp.setAudioAttributes(attributes);
-
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            //assertFalse(mp.isLooping());
-            onLoopCurrentCalled.reset();
-            mp.loopCurrent(true);
-            onLoopCurrentCalled.waitForSignal();
-            //assertTrue(mp.isLooping());
-
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
-            long pos = mp.getCurrentPosition();
-            assertTrue(pos >= 0);
-            assertTrue(pos < mp3Duration - seekDuration);
-
-            onSeekToCalled.reset();
-            mp.seekTo(pos + seekDuration, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-            onSeekToCalled.waitForSignal();
-            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
-
-            // test pause and restart
-            mp.pause();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            // test stop and restart
-            mp.reset();
-            mp.setMediaItem(new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build());
-
-            mp.setEventCallback(mExecutor, ecb);
-            onPrepareCalled.reset();
-            mp.prepare();
-            onPrepareCalled.waitForSignal();
-
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            // waiting to complete
-            while (mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } catch (Exception e) {
-            throw e;
-        } finally {
-            mp.close();
-        }
-    }
-
-    /*
-    public void concurrentPlayAudio() throws Exception {
-        final int resid = R.raw.test1m1s; // MP3 longer than 1m are usualy offloaded
-        final int tolerance = 70;
-
-        List<MediaPlayer2> mps = Stream.generate(() -> createMediaPlayer2(mContext, resid))
-                                      .limit(5).collect(Collectors.toList());
-
-        try {
-            for (MediaPlayer2 mp : mps) {
-                Monitor onPlayCalled = new Monitor();
-                Monitor onLoopCurrentCalled = new Monitor();
-                MediaPlayer2.EventCallback ecb =
-                    new MediaPlayer2.EventCallback() {
-                        @Override
-                        public void onCallCompleted(MediaPlayer2 mp, MediaItem item,
-                                int what, int status) {
-                            if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                                onPlayCalled.signal();
-                            } else if (what == MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT) {
-                                onLoopCurrentCalled.signal();
-                            }
-                        }
-                    };
-                mp.setEventCallback(mExecutor, ecb);
-
-                AudioAttributes attributes = new AudioAttributes.Builder()
-                        .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
-                        .build();
-                mp.setAudioAttributes(attributes);
-
-                assertFalse(mp.isPlaying());
-                onPlayCalled.reset();
-                mp.play();
-                onPlayCalled.waitForSignal();
-                assertTrue(mp.isPlaying());
-
-                assertFalse(mp.isLooping());
-                onLoopCurrentCalled.reset();
-                mp.loopCurrent(true);
-                onLoopCurrentCalled.waitForSignal();
-                assertTrue(mp.isLooping());
-
-                long pos = mp.getCurrentPosition();
-                assertTrue(pos >= 0);
-
-                Thread.sleep(SLEEP_TIME); // Delay each track to be able to ear them
-            }
-            // Check that all mp3 are playing concurrently here
-            for (MediaPlayer2 mp : mps) {
-                long pos = mp.getCurrentPosition();
-                Thread.sleep(SLEEP_TIME);
-                assertEquals(pos + SLEEP_TIME, mp.getCurrentPosition(), tolerance);
-            }
-        } finally {
-            mps.forEach(MediaPlayer2::close);
-        }
-    }
-    */
-
-    @Test
-    @LargeTest
-    public void playAudioLooping() throws Exception {
-        final int resid = R.raw.testmp3;
-
-        MediaPlayer2 mp = createMediaPlayer2(mContext, resid);
-        try {
-            AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                    .build();
-            mp.setAudioAttributes(attributes);
-            mp.loopCurrent(true);
-            final Monitor onCompletionCalled = new Monitor();
-            final Monitor onDataSourceRepeatCalled = new Monitor();
-            final Monitor onPlayCalled = new Monitor();
-            MediaPlayer2.EventCallback ecb =
-                    new MediaPlayer2.EventCallback() {
-                        @Override
-                        public void onInfo(MediaPlayer2 mp, MediaItem item,
-                                int what, int extra) {
-                            if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                                Log.i("@@@", "got oncompletion");
-                                onCompletionCalled.signal();
-                            } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT) {
-                                onDataSourceRepeatCalled.signal();
-                            }
-                        }
-
-                        @Override
-                        public void onCallCompleted(MediaPlayer2 mp, MediaItem item,
-                                int what, int status) {
-                            if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                                onPlayCalled.signal();
-                            }
-                        }
-                    };
-            mp.setEventCallback(mExecutor, ecb);
-
-            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            onPlayCalled.reset();
-            mp.play();
-            onPlayCalled.waitForSignal();
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            onDataSourceRepeatCalled.waitForCountedSignals(3); // allow for several loops
-            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            onCompletionCalled.reset();
-            mp.loopCurrent(false);
-
-            // wait for playback to finish
-            while (mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-                Thread.sleep(SLEEP_TIME);
-            }
-            assertEquals("wrong number of completion signals", 1,
-                    onCompletionCalled.getNumSignal());
-        } finally {
-            mp.close();
-        }
-    }
-
-    static class OutputListener {
-        int mSession;
-        AudioEffect mVc;
-        Visualizer mVis;
-        byte [] mVisData;
-        boolean mSoundDetected;
-        OutputListener(int session) {
-            mSession = session;
-            /* FIXME: find out a public API for replacing AudioEffect contructor.
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            mVc = new AudioEffect(
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
-                    0,
-                    session);
-            mVc.setEnabled(true);
-            */
-            mVis = new Visualizer(session);
-            int size = 256;
-            int[] range = Visualizer.getCaptureSizeRange();
-            if (size < range[0]) {
-                size = range[0];
-            }
-            if (size > range[1]) {
-                size = range[1];
-            }
-            assertTrue(mVis.setCaptureSize(size) == Visualizer.SUCCESS);
-
-            mVis.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
-                @Override
-                public void onWaveFormDataCapture(Visualizer visualizer,
-                        byte[] waveform, int samplingRate) {
-                    if (!mSoundDetected) {
-                        for (int i = 0; i < waveform.length; i++) {
-                            // 8 bit unsigned PCM, zero level is at 128, which is -128 when
-                            // seen as a signed byte
-                            if (waveform[i] != -128) {
-                                mSoundDetected = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
-                }
-            }, 10000 /* milliHertz */, true /* PCM */, false /* FFT */);
-            assertTrue(mVis.setEnabled(true) == Visualizer.SUCCESS);
-        }
-
-        void reset() {
-            mSoundDetected = false;
-        }
-
-        boolean heardSound() {
-            return mSoundDetected;
-        }
-
-        void release() {
-            mVis.release();
-            /* FIXME: find out a public API for replacing AudioEffect contructor.
-            mVc.release();
-            */
-        }
-    }
-
-    public void playAudioTwice() throws Exception {
-
-        final int resid = R.raw.camera_click;
-
-        MediaPlayer2 mp = createMediaPlayer2(mContext, resid);
-        try {
-            AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                    .build();
-            mp.setAudioAttributes(attributes);
-
-            OutputListener listener = new OutputListener(mp.getAudioSessionId());
-
-            Thread.sleep(SLEEP_TIME);
-            assertFalse("noise heard before test started", listener.heardSound());
-
-            mp.play();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse("player was still playing after " + SLEEP_TIME + " ms",
-                    mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-            assertTrue("nothing heard while test ran", listener.heardSound());
-            listener.reset();
-            mp.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-            mp.play();
-            Thread.sleep(SLEEP_TIME);
-            assertTrue("nothing heard when sound was replayed", listener.heardSound());
-            listener.release();
-        } finally {
-            mp.close();
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void playVideo() throws Exception {
-        playVideoTest(R.raw.testvideo, 352, 288);
-    }
-
-    @Test
-    @MediumTest
-    public void getDuration() throws Exception {
-        if (!checkLoadResource(R.raw.testvideo)) {
-            return;
-        }
-        final int expectedDuration = 11047;
-        final int tolerance = 70;
-
-        final Monitor prepareCompleted = new Monitor();
-        MediaPlayer2.EventCallback callback = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PREPARE) {
-                    prepareCompleted.signal();
-                }
-                super.onCallCompleted(mp, item, what, status);
-            }
-        };
-        mPlayer.setSurface(mActivity.getSurfaceHolder2().getSurface());
-        mPlayer.setEventCallback(mExecutor, callback);
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-        try {
-            assertTrue(mPlayer.getDuration() <= 0);
-        } catch (IllegalStateException e) {
-            // may throw exception
-        }
-
-        mPlayer.prepare();
-        assertTrue(prepareCompleted.waitForSignal());
-        assertEquals(MediaPlayer2.PLAYER_STATE_PREPARED, mPlayer.getState());
-        assertEquals(expectedDuration, mPlayer.getDuration(), tolerance);
-    }
-
-    @Test
-    @MediumTest
-    public void getCurrentPosition() throws Exception {
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-        try {
-            assertTrue(mPlayer.getCurrentPosition() <= 0);
-        } catch (IllegalStateException e) {
-            // OK to thrown an exception while in the IDLE
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void getBufferedPosition() throws Exception {
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-        try {
-            assertTrue(mPlayer.getBufferedPosition() <= 0);
-        } catch (IllegalStateException e) {
-            // OK to thrown an exception while in the IDLE
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void getPlayerParams() throws Exception {
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-        assertNotNull(mPlayer.getPlaybackParams());
-    }
-
-    /**
-     * Test for resetting a surface during video playback
-     * After resetting, the video should continue playing
-     * from the time setDisplay() was called
-     */
-    @Test
-    @LargeTest
-    public void videoSurfaceResetting() throws Exception {
-        // b/239017530
-        assumeTrue(Build.VERSION.SDK_INT != 29);
-        final int tolerance = 150;
-        final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
-        final int seekPos = 1840;  // This is the I-frame position
-
-        final CountDownLatch seekDone = new CountDownLatch(1);
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    seekDone.countDown();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        if (!checkLoadResource(
-                R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz)) {
-            return; // skip;
-        }
-        playLoadedVideo(480, 360, -1);
-
-        Thread.sleep(SLEEP_TIME);
-
-        long posBefore = mPlayer.getCurrentPosition();
-        mPlayer.setSurface(mActivity.getSurfaceHolder2().getSurface());
-        long posAfter = mPlayer.getCurrentPosition();
-
-        /* temporarily disable timestamp checking because MediaPlayer2 now seeks to I-frame
-         * position, instead of requested position. setDisplay invovles a seek operation
-         * internally.
-         */
-        // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        Thread.sleep(SLEEP_TIME);
-
-        mPlayer.seekTo(seekPos, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        seekDone.await();
-        posAfter = mPlayer.getCurrentPosition();
-        assertEquals(seekPos, posAfter, tolerance + audioLatencyTolerance);
-
-        Thread.sleep(SLEEP_TIME / 2);
-        posBefore = mPlayer.getCurrentPosition();
-        mPlayer.setSurface(null);
-        posAfter = mPlayer.getCurrentPosition();
-        // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        Thread.sleep(SLEEP_TIME);
-
-        posBefore = mPlayer.getCurrentPosition();
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-        posAfter = mPlayer.getCurrentPosition();
-
-        // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        Thread.sleep(SLEEP_TIME);
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("Fails to connect to camera service")
-    public void recordedVideoPlayback0() throws Exception {
-        testRecordedVideoPlaybackWithAngle(0);
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("Fails to connect to camera service")
-    public void recordedVideoPlayback90() throws Exception {
-        testRecordedVideoPlaybackWithAngle(90);
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("Fails to connect to camera service")
-    public void recordedVideoPlayback180() throws Exception {
-        testRecordedVideoPlaybackWithAngle(180);
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("Fails to connect to camera service")
-    public void recordedVideoPlayback270() throws Exception {
-        testRecordedVideoPlaybackWithAngle(270);
-    }
-
-    private boolean hasCamera() {
-        return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
-    }
-
-    private void testRecordedVideoPlaybackWithAngle(int angle) throws Exception {
-        int width = RECORDED_VIDEO_WIDTH;
-        int height = RECORDED_VIDEO_HEIGHT;
-        final String file = mRecordedFilePath;
-        final long durationMs = RECORDED_DURATION_MS;
-
-        if (!hasCamera()) {
-            return;
-        }
-
-        boolean isSupported = false;
-        mCamera = Camera.open(0);
-        Camera.Parameters parameters = mCamera.getParameters();
-        List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
-        // getSupportedVideoSizes returns null when separate video/preview size
-        // is not supported.
-        if (videoSizes == null) {
-            videoSizes = parameters.getSupportedPreviewSizes();
-        }
-        for (Camera.Size size : videoSizes) {
-            if (size.width == width && size.height == height) {
-                isSupported = true;
-                break;
-            }
-        }
-        mCamera.release();
-        mCamera = null;
-        if (!isSupported) {
-            width = videoSizes.get(0).width;
-            height = videoSizes.get(0).height;
-        }
-        checkOrientation(angle);
-        recordVideo(width, height, angle, file, durationMs);
-        checkDisplayedVideoSize(width, height, angle, file);
-        checkVideoRotationAngle(angle, file);
-    }
-
-    private void checkOrientation(int angle) throws Exception {
-        assertTrue(angle >= 0);
-        assertTrue(angle < 360);
-        assertTrue((angle % 90) == 0);
-    }
-
-    private void recordVideo(
-            int w, int h, int angle, String file, long durationMs) throws Exception {
-
-        MediaRecorder recorder = new MediaRecorder();
-        recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
-        recorder.setOutputFile(file);
-        recorder.setOrientationHint(angle);
-        recorder.setVideoSize(w, h);
-        recorder.setPreviewDisplay(mActivity.getSurfaceHolder2().getSurface());
-        recorder.prepare();
-        recorder.start();
-        Thread.sleep(durationMs);
-        recorder.stop();
-        recorder.release();
-        recorder = null;
-    }
-
-    private void checkDisplayedVideoSize(
-            int w, int h, int angle, String file) throws Exception {
-
-        int displayWidth  = w;
-        int displayHeight = h;
-        if ((angle % 180) != 0) {
-            displayWidth  = h;
-            displayHeight = w;
-        }
-        playVideoTest(file, displayWidth, displayHeight);
-    }
-
-    private void checkVideoRotationAngle(int angle, String file) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(file);
-        String rotation = retriever.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-        try {
-            retriever.release();
-        } catch (IOException e) {
-            // Nothing we can  do about it.
-        }
-        retriever = null;
-        assertNotNull(rotation);
-        assertEquals(Integer.parseInt(rotation), angle);
-    }
-
-    @Test
-    @LargeTest
-    public void skipToNext() throws Exception {
-        testSetNextDataSources(true, true);
-    }
-
-    @Test
-    @LargeTest
-    public void setNextDataSourcesWithVideos() throws Exception {
-        testSetNextDataSources(true, false);
-    }
-
-    @Test
-    @LargeTest
-    public void setNextDataSourcesWithAudios() throws Exception {
-        testSetNextDataSources(false, false);
-    }
-
-    private void testSetNextDataSources(boolean video, boolean skip) throws Exception {
-        int res1 = video ? R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz
-                : R.raw.loudsoftmp3;
-        int res2 = video ? R.raw.testvideo : R.raw.testmp3;
-        if (!checkLoadResource(res1)) {
-            return; // skip
-        }
-        final MediaItem item1 = createDataSourceDesc(res1);
-        final MediaItem item2 = createDataSourceDesc(res2);
-        ArrayList<MediaItem> nextDSDs = new ArrayList<MediaItem>(2);
-        nextDSDs.add(item2);
-        nextDSDs.add(item1);
-
-        mPlayer.setNextMediaItems(nextDSDs);
-
-        final Monitor onCompletion1Called = new Monitor();
-        final Monitor onCompletion2Called = new Monitor();
-        final Monitor onPlaylistEndCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    Log.i(LOG_TAG, "testSetNextDataSources: prepared item MediaId="
-                            + item.getMediaId());
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    if (item == item1) {
-                        onCompletion1Called.signal();
-                    } else if (item == item2) {
-                        onCompletion2Called.signal();
-                    } else {
-                        mOnCompletionCalled.signal();
-                    }
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
-                    onPlaylistEndCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnCompletionCalled.reset();
-        onCompletion1Called.reset();
-        onCompletion2Called.reset();
-        onPlaylistEndCalled.reset();
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mPlayer.prepare();
-
-        mPlayer.play();
-
-        if (skip) {
-            mPlayer.skipToNext();
-            mPlayer.skipToNext();
-        } else {
-            mOnCompletionCalled.waitForSignal();
-            onCompletion2Called.waitForSignal();
-        }
-        onCompletion1Called.waitForSignal();
-        if (skip) {
-            assertFalse("first item completed", mOnCompletionCalled.isSignalled());
-            assertFalse("second item completed", onCompletion2Called.isSignalled());
-        }
-        onPlaylistEndCalled.waitForSignal();
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void setNextDataSource() throws Exception {
-        final MediaItem item1 = createDataSourceDesc(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz);
-        final MediaItem item2 = createDataSourceDesc(R.raw.testvideo);
-        final MediaItem item3 = new FileMediaItem.Builder(
-                ((FileMediaItem) item1).getParcelFileDescriptor().dup())
-                .setFileDescriptorOffset(((FileMediaItem) item1).getFileDescriptorOffset())
-                .setFileDescriptorLength(((FileMediaItem) item1).getFileDescriptorLength())
-                .build();
-        final MediaItem item4 = new FileMediaItem.Builder(
-                ((FileMediaItem) item2).getParcelFileDescriptor().dup())
-                .setFileDescriptorOffset(((FileMediaItem) item1).getFileDescriptorOffset())
-                .setFileDescriptorLength(((FileMediaItem) item1).getFileDescriptorLength())
-                .build();
-
-        final Monitor onPlaybackCompletedCalled = new Monitor();
-        final List<MediaItem> playedDSDs = new ArrayList<>();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE
-                        || what == MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES) {
-                    if (status != MediaPlayer2.CALL_STATUS_NO_ERROR) {
-                        fail("Unexpected status code: " + status);
-                    }
-                }
-            }
-
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    playedDSDs.add(item);
-                    onPlaybackCompletedCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        onPlaybackCompletedCalled.reset();
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-        mPlayer.setMediaItem(item1);
-        mPlayer.setNextMediaItem(item2);
-        mPlayer.setNextMediaItems(Arrays.asList(item3, item4));
-        mPlayer.prepare();
-        mPlayer.play();
-        onPlaybackCompletedCalled.waitForCountedSignals(3);
-        assertEquals(3, playedDSDs.size());
-        assertEquals(item1, playedDSDs.get(0));
-        assertEquals(item3, playedDSDs.get(1));
-        assertEquals(item4, playedDSDs.get(2));
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void setNextDataSourceBeforeSetDataSource() throws Exception {
-        final MediaItem item1 = createDataSourceDesc(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz);
-        final MediaItem item2 = createDataSourceDesc(
-                R.raw.testvideo);
-
-        final Monitor onCallCompletedCalled = new Monitor();
-        final List<MediaItem> playedDSDs = new ArrayList<>();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE
-                        || what == MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCES) {
-                    if (status == MediaPlayer2.CALL_STATUS_INVALID_OPERATION) {
-                        // expected
-                        onCallCompletedCalled.signal();
-                    } else {
-                        fail("Unexpected status code: " + status);
-                    }
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        onCallCompletedCalled.reset();
-        mPlayer.setNextMediaItem(item1);
-        assertTrue(onCallCompletedCalled.waitForSignal());
-
-        onCallCompletedCalled.reset();
-        mPlayer.setNextMediaItems(Arrays.asList(item2, item1));
-        assertTrue(onCallCompletedCalled.waitForSignal());
-
-        mPlayer.reset();
-    }
-
-    // setPlaybackParams() with non-zero speed should NOT start playback.
-    // TODO: enable this test when MediaPlayer2.setPlaybackParams() is fixed
-    /*
-    public void setPlaybackParamsPositiveSpeed() throws Exception {
-        if (!checkLoadResource(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            return; // skip
-        }
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    mOnSeekCompleteCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnCompletionCalled.reset();
-        mPlayer.setDisplay(mActivity.getSurfaceHolder());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnSeekCompleteCalled.reset();
-        mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        mOnSeekCompleteCalled.waitForSignal();
-
-        final float playbackRate = 1.0f;
-
-        int playTime = 2000;  // The testing clip is about 10 second long.
-        mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer2 should be playing", mPlayer.isPlaying());
-        Thread.sleep(playTime);
-        assertTrue("MediaPlayer2 should still be playing",
-                mPlayer.getCurrentPosition() > 0);
-
-        long duration = mPlayer.getDuration();
-        mOnSeekCompleteCalled.reset();
-        mPlayer.seekTo(duration - 1000, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        mOnSeekCompleteCalled.waitForSignal();
-
-        mOnCompletionCalled.waitForSignal();
-        assertFalse("MediaPlayer2 should not be playing", mPlayer.isPlaying());
-        long eosPosition = mPlayer.getCurrentPosition();
-
-        mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer2 should be playing after EOS", mPlayer.isPlaying());
-        Thread.sleep(playTime);
-        long position = mPlayer.getCurrentPosition();
-        assertTrue("MediaPlayer2 should still be playing after EOS",
-                position > 0 && position < eosPosition);
-
-        mPlayer.reset();
-    }
-    */
-
-    @Ignore("b/213625878")
-    @Test
-    @LargeTest
-    public void playbackRate() throws Exception {
-        final int toleranceMs = 1000;
-        if (!checkLoadResource(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            return; // skip
-        }
-
-        final Monitor labelReached = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) {
-                labelReached.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        float[] rates = { 0.25f, 0.5f, 1.0f, 2.0f };
-        for (float playbackRate : rates) {
-            mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-            Thread.sleep(1000);
-            int playTime = 4000;  // The testing clip is about 10 second long.
-            int privState = mPlayer.getState();
-            mPlayer.setPlaybackParams(new PlaybackParams.Builder().setSpeed(playbackRate).build());
-            labelReached.reset();
-            mPlayer.notifyWhenCommandLabelReached(new Object());
-            labelReached.waitForSignal();
-            assertTrue("setPlaybackParams() should not change player state. " + mPlayer.getState(),
-                    mPlayer.getState() == privState);
-
-            mPlayer.play();
-            Thread.sleep(playTime);
-
-            labelReached.reset();
-            mPlayer.notifyWhenCommandLabelReached(new Object());
-            labelReached.waitForSignal();
-
-            PlaybackParams pbp = mPlayer.getPlaybackParams();
-            assertEquals(playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
-            assertTrue("MediaPlayer2 should still be playing",
-                    mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-            long playedMediaDurationMs = mPlayer.getCurrentPosition();
-            long expectedPosition = (long) (playTime * playbackRate);
-            int diff = (int) Math.abs(playedMediaDurationMs - expectedPosition);
-            if (diff > toleranceMs) {
-                fail("Media player had error in playback rate " + playbackRate
-                        + ". expected position after playing " + playTime
-                        + " was " + expectedPosition + ", but actually " + playedMediaDurationMs);
-            }
-            mPlayer.pause();
-
-            labelReached.reset();
-            mPlayer.notifyWhenCommandLabelReached(new Object());
-            labelReached.waitForSignal();
-
-            pbp = mPlayer.getPlaybackParams();
-            assertEquals("pause() should not change the playback rate property.",
-                    playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
-        }
-        mPlayer.reset();
-    }
-
-    @FlakyTest(bugId = 190043361)
-    @Test
-    @LargeTest
-    public void seekModes() throws Exception {
-        // This clip has 2 I frames at 66687us and 4299687us.
-        if (!checkLoadResource(
-                R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz)) {
-            return; // skip
-        }
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    mOnSeekCompleteCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnSeekCompleteCalled.reset();
-        mPlayer.play();
-
-        final long seekPosMs = 3000;
-        final long timeToleranceMs = 100;
-        final long syncTime1Ms = 67;
-        final long syncTime2Ms = 4300;
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to previous sync or next sync.
-        long cp = runSeekMode(MediaPlayer2.SEEK_CLOSEST, seekPosMs);
-        assertTrue("MediaPlayer2 did not seek to closest position",
-                cp > seekPosMs && cp < syncTime2Ms);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or next sync.
-        cp = runSeekMode(MediaPlayer2.SEEK_PREVIOUS_SYNC, seekPosMs);
-        assertTrue("MediaPlayer2 did not seek to preivous sync position",
-                cp < seekPosMs - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer2.SEEK_NEXT_SYNC, seekPosMs);
-        assertTrue("MediaPlayer2 did not seek to next sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer2.SEEK_CLOSEST_SYNC, seekPosMs);
-        assertTrue("MediaPlayer2 did not seek to closest sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        mPlayer.reset();
-    }
-
-    private long runSeekMode(int seekMode, long seekPosMs) throws Exception {
-        final int sleepIntervalMs = 100;
-        int timeRemainedMs = 10000;  // total time for testing
-        final int timeToleranceMs = 100;
-
-        mPlayer.seekTo(seekPosMs, seekMode);
-        mOnSeekCompleteCalled.waitForSignal();
-        mOnSeekCompleteCalled.reset();
-        long cp = -seekPosMs;
-        while (timeRemainedMs > 0) {
-            cp = mPlayer.getCurrentPosition();
-            // Wait till MediaPlayer2 starts rendering since MediaPlayer2 caches
-            // seek position as current position.
-            if (cp < seekPosMs - timeToleranceMs || cp > seekPosMs + timeToleranceMs) {
-                break;
-            }
-            timeRemainedMs -= sleepIntervalMs;
-            Thread.sleep(sleepIntervalMs);
-        }
-        assertTrue("MediaPlayer2 did not finish seeking in time for mode " + seekMode,
-                timeRemainedMs > 0);
-        return cp;
-    }
-
-    @FlakyTest(bugId = 264905014)
-    @Test
-    @LargeTest
-    public void getTimestamp() throws Exception {
-        final int toleranceUs = 100000;
-        final float playbackRate = 1.0f;
-        if (!checkLoadResource(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            return; // skip
-        }
-
-        final Monitor onPauseCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PAUSE) {
-                    onPauseCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mPlayer.play();
-        mPlayer.setPlaybackParams(new PlaybackParams.Builder().setSpeed(playbackRate).build());
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        long nt1 = System.nanoTime();
-        MediaTimestamp ts1 = mPlayer.getTimestamp();
-        long nt2 = System.nanoTime();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        assertEquals("MediaPlayer2 had error in clockRate " + ts1.getMediaClockRate(),
-                playbackRate, ts1.getMediaClockRate(), 0.001f);
-        assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
-                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
-
-        onPauseCalled.reset();
-        mPlayer.pause();
-        onPauseCalled.waitForSignal();
-        ts1 = mPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        assertTrue("Media player should have play rate of 0.0f when paused",
-                ts1.getMediaClockRate() == 0.0f);
-
-        mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        mPlayer.play();
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        int playTime = 4000;  // The testing clip is about 10 second long.
-        ts1 = mPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        Thread.sleep(playTime);
-        MediaTimestamp ts2 = mPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts2 != null);
-        assertTrue("The clockRate should not be changed.",
-                ts1.getMediaClockRate() == ts2.getMediaClockRate());
-        assertEquals("MediaPlayer2 had error in timestamp.",
-                ts1.getAnchorMediaTimeUs() + (long) (playTime * ts1.getMediaClockRate() * 1000),
-                ts2.getAnchorMediaTimeUs(), toleranceUs);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz, 1280, 720);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented,
-                480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz, 480, 360);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
-    }
-
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
-    public void localVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
-    }
-
-    private void readTracks() {
-        mVideoTrackInfos.clear();
-        mAudioTrackInfos.clear();
-        mSubtitleTrackInfos.clear();
-        List<TrackInfo> trackInfos = mPlayer.getTracks();
-        assertNotNull(trackInfos);
-        for (TrackInfo trackInfo : trackInfos) {
-            assertNotNull(trackInfo);
-            switch (trackInfo.getTrackType()) {
-                case MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO:
-                    mVideoTrackInfos.add(trackInfo);
-                    break;
-                case MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO:
-                    mAudioTrackInfos.add(trackInfo);
-                    break;
-                case MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
-                    mSubtitleTrackInfos.add(trackInfo);
-                    break;
-            }
-        }
-    }
-
-    private void selectSubtitleTrack(int trackId) {
-        mPlayer.selectTrack(trackId);
-    }
-
-    private void deselectSubtitleTrack(int trackId) throws Exception {
-        mOnDeselectTrackCalled.reset();
-        mPlayer.deselectTrack(trackId);
-        mOnDeselectTrackCalled.waitForSignal();
-    }
-
-    @Test
-    @LargeTest
-    public void deselectTrackForSubtitleTracks() throws Throwable {
-        if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            return; // skip;
-        }
-
-        mInstrumentation.waitForIdleSync();
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    mOnSeekCompleteCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK) {
-                    mCallStatus = status;
-                    mOnDeselectTrackCalled.signal();
-                }
-            }
-
-            @Override
-            public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                assertNotNull(data);
-                assertNotNull(data.getData());
-                mOnSubtitleDataCalled.signal();
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-
-        // Run twice to check if repeated selection-deselection on the same track works well.
-        for (int i = 0; i < 2; i++) {
-            // Waits until at least one subtitle is fired. Timeout is 2.5 seconds.
-            selectSubtitleTrack(mSubtitleTrackInfos.get(i).getId());
-            mOnSubtitleDataCalled.reset();
-            assertTrue(mOnSubtitleDataCalled.waitForSignal(2500));
-
-            // Try deselecting track.
-            deselectSubtitleTrack(mSubtitleTrackInfos.get(i).getId());
-            mOnSubtitleDataCalled.reset();
-            assertFalse(mOnSubtitleDataCalled.waitForSignal(1500));
-        }
-
-        // Deselecting unselected track: expected error status
-        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        deselectSubtitleTrack(mSubtitleTrackInfos.get(0).getId());
-        assertNotEquals(MediaPlayer2.CALL_STATUS_NO_ERROR, mCallStatus);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void changeSubtitleTrack() throws Throwable {
-        if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            return; // skip;
-        }
-
-        mInstrumentation.waitForIdleSync();
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-
-            @Override
-            public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                assertNotNull(data);
-                assertNotNull(data.getData());
-                mOnSubtitleDataCalled.signal();
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-
-        assertNull(mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        // Waits until at least two captions are fired. Timeout is 2.5 sec.
-        selectSubtitleTrack(mSubtitleTrackInfos.get(0).getId());
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-        assertEquals(mSubtitleTrackInfos.get(0),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        mOnSubtitleDataCalled.reset();
-        selectSubtitleTrack(mSubtitleTrackInfos.get(1).getId());
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-        assertEquals(mSubtitleTrackInfos.get(1),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void getTrackInfoForVideoWithSubtitleTracks() throws Throwable {
-        if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            return; // skip;
-        }
-
-        mInstrumentation.waitForIdleSync();
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-
-        // The media metadata will be changed while playing since closed caption tracks are in-band
-        // and those tracks will be found after processing a number of frames. These tracks will be
-        // found within one second.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-        assertEquals(2, mSubtitleTrackInfos.size());
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void getTrackInfoForVideoWithoutSubtitleTracks() throws Throwable {
-        if (!checkLoadResource(R.raw.testvideo)) {
-            return; // skip;
-        }
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        readTracks();
-
-        // R.raw.testvideo contains the following tracks:
-        //  MEDIA_TRACK_TYPE_VIDEO: 1
-        //  MEDIA_TRACK_TYPE_AUDIO: 1
-        assertEquals(1, mVideoTrackInfos.size());
-        assertEquals(1, mAudioTrackInfos.size());
-        assertEquals(0, mSubtitleTrackInfos.size());
-
-        // Test getSelectedTrack
-        assertEquals(mVideoTrackInfos.get(0),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_VIDEO));
-        assertEquals(mAudioTrackInfos.get(0),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_AUDIO));
-        assertNull(mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void mediaTimeDiscontinuity() throws Exception {
-        if (!checkLoadResource(
-                R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz)) {
-            return; // skip
-        }
-
-        final BlockingDeque<MediaTimestamp> timestamps = new LinkedBlockingDeque<>();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    mOnSeekCompleteCalled.signal();
-                }
-            }
-            @Override
-            public void onMediaTimeDiscontinuity(
-                    MediaPlayer2 mp, MediaItem item, MediaTimestamp timestamp) {
-                timestamps.add(timestamp);
-                mOnMediaTimeDiscontinuityCalled.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-        mPlayer.prepare();
-
-        // Timestamp needs to be reported when playback starts.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mPlayer.play();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 1.0f) > 0.01f);
-
-        // Timestamp needs to be reported when seeking is done.
-        mOnSeekCompleteCalled.reset();
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mPlayer.seekTo(3000, MediaPlayer2.SEEK_NEXT_SYNC);
-        mOnSeekCompleteCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 1.0f) > 0.01f);
-
-        // Timestamp needs to be updated when playback rate changes.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mPlayer.setPlaybackParams(new PlaybackParams.Builder().setSpeed(0.5f).build());
-        mOnMediaTimeDiscontinuityCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 0.5f) > 0.01f);
-
-        // Timestamp needs to be updated when player is paused.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mPlayer.pause();
-        mOnMediaTimeDiscontinuityCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 0.0f) > 0.01f);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void positionAtEnd() throws Throwable {
-        testPositionAtEnd(R.raw.test1m1shighstereo);
-        testPositionAtEnd(R.raw.loudsoftmp3);
-        testPositionAtEnd(R.raw.loudsoftwav);
-        testPositionAtEnd(R.raw.loudsoftogg);
-        testPositionAtEnd(R.raw.loudsoftitunes);
-        testPositionAtEnd(R.raw.loudsoftfaac);
-        testPositionAtEnd(R.raw.loudsoftaac);
-    }
-
-    private int testPositionAtEnd(int res) throws Throwable {
-        if (!loadResource(res)) {
-            Log.i(LOG_TAG, "testPositionAtEnd: No decoder found for "
-                    + mContext.getResources().getResourceEntryName(res) + " --- skipping.");
-            return 0; // skip
-        }
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        mPlayer.setAudioAttributes(attributes);
-
-        mOnCompletionCalled.reset();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        mPlayer.setEventCallback(mExecutor, ecb);
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        long duration = mPlayer.getDuration();
-        assertTrue("resource too short", duration > 6000);
-        mPlayer.seekTo(duration - 5000, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        mOnCompletionCalled.waitForSignal();
-        long pos = mPlayer.getCurrentPosition();
-        assertTrue("current pos (" + pos + "ms) does not match the duration (" + duration + "ms).",
-                Math.abs(pos - duration) < 1000);
-        mPlayer.reset();
-        return 1;
-    }
-
-    @Test
-    @LargeTest
-    public void mediaPlayer2Callback() throws Throwable {
-        final int mp4Duration = 8484;
-
-        if (!checkLoadResource(R.raw.testvideo)) {
-            return; // skip;
-        }
-
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        mOnCompletionCalled.reset();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer2 mp, MediaItem item,
-                    int width, int height) {
-                mOnVideoSizeChangedCalled.signal();
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
-                    mOnSeekCompleteCalled.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        assertFalse(mOnPrepareCalled.isSignalled());
-        assertFalse(mOnVideoSizeChangedCalled.isSignalled());
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-        mOnVideoSizeChangedCalled.waitForSignal();
-
-        mOnSeekCompleteCalled.reset();
-        mPlayer.seekTo(mp4Duration >> 1, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        mOnSeekCompleteCalled.waitForSignal();
-
-        assertFalse(mOnCompletionCalled.isSignalled());
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-            Thread.sleep(SLEEP_TIME);
-        }
-        assertFalse(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-        mOnCompletionCalled.waitForSignal();
-        assertFalse(mOnErrorCalled.isSignalled());
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void playerStates() throws Throwable {
-        final int mp4Duration = 8484;
-
-        if (!checkLoadResource(R.raw.testvideo)) {
-            return; // skip;
-        }
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        final Monitor prepareCompleted = new Monitor();
-        final Monitor playCompleted = new Monitor();
-        final Monitor pauseCompleted = new Monitor();
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PREPARE) {
-                    prepareCompleted.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    playCompleted.signal();
-                } else if (what == MediaPlayer2.CALL_COMPLETED_PAUSE) {
-                    pauseCompleted.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-        prepareCompleted.reset();
-        mPlayer.prepare();
-        prepareCompleted.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PREPARED, mPlayer.getState());
-
-        playCompleted.reset();
-        mPlayer.play();
-        playCompleted.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-
-        pauseCompleted.reset();
-        mPlayer.pause();
-        pauseCompleted.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PAUSED, mPlayer.getState());
-
-        mPlayer.reset();
-        assertEquals(MediaPlayer2.PLAYER_STATE_IDLE, mPlayer.getState());
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("MediaRecorder.setAudioSource fails")
-    public void recordAndPlay() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        /* FIXME: check the codec exists.
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
-                || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-        */
-        File outputFile = new File(Environment.getExternalStorageDirectory(),
-                "record_and_play.3gp");
-        String outputFileLocation = outputFile.getAbsolutePath();
-        try {
-            recordMedia(outputFileLocation);
-
-            Uri uri = Uri.parse(outputFileLocation);
-            MediaPlayer2 mp = MediaPlayer2.create(mActivity);
-            try {
-                mp.setMediaItem(new UriMediaItem.Builder(uri).build());
-                mp.prepare();
-                Thread.sleep(SLEEP_TIME);
-                playAndStop(mp);
-            } finally {
-                mp.close();
-            }
-
-            try {
-                mp = createMediaPlayer2(mContext, uri);
-                playAndStop(mp);
-            } finally {
-                if (mp != null) {
-                    mp.close();
-                }
-            }
-
-            try {
-                mp = createMediaPlayer2(mContext, uri, mActivity.getSurfaceHolder());
-                playAndStop(mp);
-            } finally {
-                if (mp != null) {
-                    mp.close();
-                }
-            }
-        } finally {
-            outputFile.delete();
-        }
-    }
-
-    private void playAndStop(MediaPlayer2 mp) throws Exception {
-        mp.play();
-        Thread.sleep(SLEEP_TIME);
-        mp.reset();
-    }
-
-    private void recordMedia(String outputFile) throws Exception {
-        MediaRecorder mr = new MediaRecorder();
-        try {
-            mr.setAudioSource(MediaRecorder.AudioSource.MIC);
-            mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-            mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
-            mr.setOutputFile(outputFile);
-
-            mr.prepare();
-            mr.start();
-            Thread.sleep(SLEEP_TIME);
-            mr.stop();
-        } finally {
-            mr.release();
-        }
-    }
-
-    private boolean hasMicrophone() {
-        return mActivity.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    // Smoke test playback from a DataSourceCallback.
-    @Test
-    @LargeTest
-    public void playbackFromAMedia2DataSource() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final int duration = 10000;
-
-        /* FIXME: check the codec exists.
-        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
-            return;
-        }
-        */
-
-        TestDataSourceCallback dataSource =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        // Test returning -1 from getSize() to indicate unknown size.
-        dataSource.returnFromGetSize(-1);
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-        playLoadedVideo(null, null, -1);
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        // Test pause and restart.
-        mPlayer.pause();
-        Thread.sleep(SLEEP_TIME);
-        assertFalse(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        mPlayer.setEventCallback(mExecutor, ecb);
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        // Test reset.
-        mPlayer.reset();
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-
-        mPlayer.setEventCallback(mExecutor, ecb);
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
-
-        // Test seek. Note: the seek position is cached and returned as the
-        // current position so there's no point in comparing them.
-        mPlayer.seekTo(duration - SLEEP_TIME, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-            Thread.sleep(SLEEP_TIME);
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void nullMedia2DataSourceIsRejected() throws Exception {
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
-                    mCallStatus = status;
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        mPlayer.setEventCallback(mExecutor, ecb);
-
-        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        mPlayer.setMediaItem((MediaItem) null);
-        mOnPlayCalled.waitForSignal();
-        assertTrue(mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR);
-    }
-
-    @Test
-    @LargeTest
-    public void media2DataSourceIsClosedOnReset() throws Exception {
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
-                    mCallStatus = status;
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        mPlayer.setEventCallback(mExecutor, ecb);
-
-        TestDataSourceCallback dataSource = new TestDataSourceCallback(new byte[0]);
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-        mOnPlayCalled.waitForSignal();
-        mPlayer.reset();
-        assertTrue(dataSource.isClosed());
-    }
-
-    @Test
-    @LargeTest
-    public void playbackFailsIfMedia2DataSourceThrows() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        /* FIXME: check the codec exists.
-        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
-            return;
-        }
-        */
-
-        setOnErrorListener();
-        TestDataSourceCallback dataSource =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        // Ensure that we throw after reading enough data for preparation to complete.
-        dataSource.throwFromReadAtPosition(500_000);
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mPlayer.play();
-        assertTrue(mOnErrorCalled.waitForSignal());
-    }
-
-    @Test
-    @MediumTest
-    public void clearPendingCommands() throws Exception {
-        final Monitor readRequested = new Monitor();
-        final Monitor readAllowed = new Monitor();
-        DataSourceCallback dataSource = new DataSourceCallback() {
-            @Override
-            public int readAt(long position, byte[] buffer, int offset, int size)
-                    throws IOException {
-                try {
-                    readRequested.signal();
-                    readAllowed.waitForSignal();
-                } catch (InterruptedException e) {
-                    fail();
-                }
-                return -1;
-            }
-
-            @Override
-            public long getSize() throws IOException {
-                return -1;  // Unknown size
-            }
-
-            @Override
-            public void close() throws IOException {}
-        };
-        final ArrayDeque<Integer> commandsCompleted = new ArrayDeque<>();
-        setOnErrorListener();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                commandsCompleted.add(what);
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mOnErrorCalled.reset();
-
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-
-        // prepare() will be pending until readAllowed is signaled.
-        mPlayer.prepare();
-
-        mPlayer.play();
-        mPlayer.pause();
-        mPlayer.play();
-        mPlayer.pause();
-        mPlayer.play();
-        mPlayer.seekTo(1000);
-
-        // Clear the pending commands once the prepare operation starts.
-        readRequested.waitForSignal();
-        mPlayer.clearPendingCommands();
-
-        // Make the on-going prepare operation fail and check the results.
-        readAllowed.signal();
-        mOnErrorCalled.waitForSignal();
-        assertEquals(0, mOnPrepareCalled.getNumSignal());
-        assertEquals(2, commandsCompleted.size());
-        assertEquals(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE,
-                (int) commandsCompleted.peekFirst());
-        assertEquals(MediaPlayer2.CALL_COMPLETED_PREPARE,
-                (int) commandsCompleted.getLast());
-    }
-
-    @Test
-    @LargeTest
-    public void dataSourceStartEnd() throws Exception {
-        final int resid1 = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final long start1 = 6000;
-        final long end1 = 8000;
-        MediaItem item1;
-        try (AssetFileDescriptor afd1 = mResources.openRawResourceFd(resid1)) {
-            item1 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd1.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd1.getStartOffset())
-                    .setFileDescriptorLength(afd1.getLength())
-                    .setStartPosition(start1)
-                    .setEndPosition(end1)
-                    .build();
-        }
-
-        final int resid2 = R.raw.testvideo;
-        final long start2 = 3000;
-        final long end2 = 5000;
-        final int expectedDuration2 = 2000;
-        MediaItem item2;
-        try (AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid2)) {
-            item2 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd2.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd2.getStartOffset())
-                    .setFileDescriptorLength(afd2.getLength())
-                    .setStartPosition(start2)
-                    .setEndPosition(end2)
-                    .build();
-        }
-
-        mPlayer.setMediaItem(item1);
-        mPlayer.setNextMediaItem(item2);
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    assertTrue(status == MediaPlayer2.CALL_STATUS_NO_ERROR);
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mOnCompletionCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        mOnCompletionCalled.waitForSignal();
-        mPlayer.setPlaybackParams(new PlaybackParams.Builder().setSpeed(0.5f).build());
-        mOnCompletionCalled.reset();
-        mOnCompletionCalled.waitForSignal();
-        assertTrue(
-                Math.abs(mPlayer.getCurrentPosition() - expectedDuration2)
-                        < PLAYBACK_COMPLETE_TOLERANCE_MS);
-        assertEquals(expectedDuration2, mPlayer.getDuration());
-    }
-
-    @Test
-    @LargeTest
-    public void dataSourceStartEndWithLooping() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final long start = 6000;
-        final long end = 8000;
-        final long expectedDuration = 2000;
-        MediaItem item;
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            item = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .setStartPosition(start)
-                    .setEndPosition(end)
-                    .build();
-        }
-
-        mPlayer.setMediaItem(item);
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        final Monitor onDataSourceRepeatCalled = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT) {
-                    onDataSourceRepeatCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    assertTrue(status == MediaPlayer2.CALL_STATUS_NO_ERROR);
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mPlayer.loopCurrent(true);
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        onDataSourceRepeatCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-        onDataSourceRepeatCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PLAYING, mPlayer.getState());
-        mOnCompletionCalled.reset();
-        mPlayer.loopCurrent(false);
-        mOnCompletionCalled.waitForSignal();
-        assertEquals(MediaPlayer2.PLAYER_STATE_PAUSED, mPlayer.getState());
-        long pos = mPlayer.getCurrentPosition();
-        assertTrue(
-                "current pos (" + pos + " ms) does not match requested pos ("
-                        + expectedDuration + " ms).",
-                Math.abs(pos - expectedDuration) < PLAYBACK_COMPLETE_TOLERANCE_MS);
-    }
-
-    @Test
-    @LargeTest
-    public void preservePlaybackProperties() throws Exception {
-        final int resid1 = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final long start1 = 6000;
-        final long end1 = 7000;
-        MediaItem item1;
-        try (AssetFileDescriptor afd1 = mResources.openRawResourceFd(resid1)) {
-            item1 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd1.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd1.getStartOffset())
-                    .setFileDescriptorLength(afd1.getLength())
-                    .setStartPosition(start1)
-                    .setEndPosition(end1)
-                    .build();
-        }
-
-        final int resid2 = R.raw.testvideo;
-        final long start2 = 3000;
-        final long end2 = 4000;
-        MediaItem item2;
-        try (AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid2)) {
-            item2 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd2.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd2.getStartOffset())
-                    .setFileDescriptorLength(afd2.getLength())
-                    .setStartPosition(start2)
-                    .setEndPosition(end2)
-                    .build();
-        }
-
-        mPlayer.setMediaItem(item1);
-        mPlayer.setNextMediaItem(item2);
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
-                    mOnCompletionCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                    assertTrue(status == MediaPlayer2.CALL_STATUS_NO_ERROR);
-                    mOnPlayCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        mOnPlayCalled.reset();
-        mOnCompletionCalled.reset();
-        mPlayer.setPlaybackParams(new PlaybackParams.Builder().setSpeed(2.0f).build());
-        mPlayer.play();
-
-        mOnPlayCalled.waitForSignal();
-        mOnCompletionCalled.waitForSignal();
-
-        assertEquals(item2, mPlayer.getCurrentMediaItem());
-        assertEquals(2.0f, mPlayer.getPlaybackParams().getSpeed(), 0.001f);
-
-    }
-
-    @Test
-    @MediumTest
-    public void defaultPlaybackParams() throws Exception {
-        if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            return; // skip;
-        }
-
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-        mOnPrepareCalled.reset();
-        mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-
-        PlaybackParams playbackParams = mPlayer.getPlaybackParams();
-        assertEquals(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT,
-                (int) playbackParams.getAudioFallbackMode());
-        assertEquals(1.0f, playbackParams.getPitch(), 0.001f);
-        assertEquals(1.0f, playbackParams.getSpeed(), 0.001f);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @MediumTest
-    public void getWidthAndHeightWithNonSquarePixels() throws Exception {
-        assertTrue(loadResource(R.raw.testvideo_with_2_subtitle_tracks));
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer2 mp, MediaItem item,
-                    int width, int height) {
-                mOnVideoSizeChangedCalled.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-        mPlayer.prepare();
-        mOnVideoSizeChangedCalled.waitForSignal();
-
-        assertEquals(160, mPlayer.getVideoWidth());
-        assertEquals(90, mPlayer.getVideoHeight());
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @MediumTest
-    public void skipUnnecessarySeek() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final TestDataSourceCallback source =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        final Monitor readAllowed = new Monitor();
-        DataSourceCallback dataSource = new DataSourceCallback() {
-            @Override
-            public int readAt(long position, byte[] buffer, int offset, int size)
-                    throws IOException {
-                if (!readAllowed.isSignalled()) {
-                    try {
-                        readAllowed.waitForSignal();
-                    } catch (InterruptedException e) {
-                        fail();
-                    }
-                }
-                return source.readAt(position, buffer, offset, size);
-            }
-
-            @Override
-            public long getSize() throws IOException {
-                return source.getSize();
-            }
-
-            @Override
-            public void close() throws IOException {
-                source.close();
-            }
-        };
-        final Monitor labelReached = new Monitor();
-        final ArrayList<Pair<Integer, Integer>> commandsCompleted = new ArrayList<>();
-        setOnErrorListener();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                commandsCompleted.add(new Pair<>(what, status));
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-
-            @Override
-            public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) {
-                labelReached.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mOnErrorCalled.reset();
-
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-
-        // prepare() will be pending until readAllowed is signaled.
-        mPlayer.prepare();
-
-        mPlayer.seekTo(3000);
-        mPlayer.seekTo(2000);
-        mPlayer.seekTo(1000);
-        mPlayer.notifyWhenCommandLabelReached(new Object());
-
-        readAllowed.signal();
-        labelReached.waitForSignal();
-
-        assertFalse(mOnErrorCalled.isSignalled());
-        assertTrue(mOnPrepareCalled.isSignalled());
-        assertEquals(5, commandsCompleted.size());
-        assertEquals(
-                new Pair<>(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE,
-                        MediaPlayer2.CALL_STATUS_NO_ERROR),
-                commandsCompleted.get(0));
-        assertEquals(
-                new Pair<>(MediaPlayer2.CALL_COMPLETED_PREPARE,
-                        MediaPlayer2.CALL_STATUS_NO_ERROR),
-                commandsCompleted.get(1));
-        assertEquals(
-                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
-                        MediaPlayer2.CALL_STATUS_SKIPPED),
-                commandsCompleted.get(2));
-        assertEquals(
-                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
-                        MediaPlayer2.CALL_STATUS_SKIPPED),
-                commandsCompleted.get(3));
-        assertEquals(
-                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
-                        MediaPlayer2.CALL_STATUS_NO_ERROR),
-                commandsCompleted.get(4));
-    }
-
-    @Test
-    @LargeTest
-    public void cancelPendingCommands() throws Exception {
-        final Monitor readRequested = new Monitor();
-        final Monitor readAllowed = new Monitor();
-        DataSourceCallback dataSource = new DataSourceCallback() {
-            TestDataSourceCallback mTestSource = TestDataSourceCallback.fromAssetFd(
-                    mResources.openRawResourceFd(R.raw.testmp3));
-            @Override
-            public int readAt(long position, byte[] buffer, int offset, int size)
-                    throws IOException {
-                try {
-                    readRequested.signal();
-                    readAllowed.waitForSignal();
-                } catch (InterruptedException e) {
-                    fail();
-                }
-                return mTestSource.readAt(position, buffer, offset, size);
-            }
-
-            @Override
-            public long getSize() throws IOException {
-                return mTestSource.getSize();
-            }
-
-            @Override
-            public void close() throws IOException {
-                mTestSource.close();
-            }
-        };
-        final ArrayList<Integer> commandsCompleted = new ArrayList<>();
-        setOnErrorListener();
-        final Monitor labelReached = new Monitor();
-        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                    mOnPrepareCalled.signal();
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                commandsCompleted.add(what);
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-
-            @Override
-            public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
-                labelReached.signal();
-            }
-        };
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(ecb);
-        }
-
-        mOnPrepareCalled.reset();
-        mOnErrorCalled.reset();
-
-        mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-
-        // prepare() will be pending until readAllowed is signaled.
-        mPlayer.prepare();
-
-        Object playToken = mPlayer.play();
-        Object seekToken = mPlayer.seekTo(1000);
-        mPlayer.pause();
-
-        readRequested.waitForSignal();
-
-        // Cancel the pending commands while preparation is on hold.
-        mPlayer.cancel(playToken);
-        mPlayer.cancel(seekToken);
-
-        // Make the on-going prepare operation fail and check the results.
-        readAllowed.signal();
-        mPlayer.notifyWhenCommandLabelReached(new Object());
-        labelReached.waitForSignal();
-
-        assertEquals(3, commandsCompleted.size());
-        assertEquals(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, (int) commandsCompleted.get(0));
-        assertEquals(MediaPlayer2.CALL_COMPLETED_PREPARE, (int) commandsCompleted.get(1));
-        assertEquals(MediaPlayer2.CALL_COMPLETED_PAUSE, (int) commandsCompleted.get(2));
-        assertEquals(0, mOnErrorCalled.getNumSignal());
-    }
-
-    @Test
-    @LargeTest
-    public void close() throws Exception {
-        assertTrue(loadResource(R.raw.testmp3_2));
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        mPlayer.setAudioAttributes(attributes);
-        mPlayer.prepare();
-        mPlayer.play();
-        mPlayer.close();
-        mExecutor.shutdown();
-
-        // Tests whether the notification from the player after the close() doesn't crash.
-        Thread.sleep(SLEEP_TIME);
-    }
-
-    @Test
-    @LargeTest
-    public void reset() throws Exception {
-        assertTrue(loadResource(R.raw.testmp3_2));
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        mPlayer.setAudioAttributes(attributes);
-
-        mPlayer.reset();
-
-        assertNull(mPlayer.getAudioAttributes());
-        assertNull(mPlayer.getCurrentMediaItem());
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java
deleted file mode 100644
index f8e93c6..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java
+++ /dev/null
@@ -1,645 +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.media2.player;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.view.SurfaceHolder;
-import android.view.WindowManager;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-
-import java.io.IOException;
-import java.net.HttpCookie;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.logging.Logger;
-
-/**
- * Base class for tests which use MediaPlayer2 to play audio or video.
- */
-public class MediaPlayer2TestBase extends MediaTestBase {
-    private static final Logger LOG = Logger.getLogger(MediaPlayer2TestBase.class.getName());
-
-    protected static final int SLEEP_TIME = 1000;
-    protected static final int LONG_SLEEP_TIME = 6000;
-    protected static final int STREAM_RETRIES = 20;
-
-    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
-    protected Monitor mOnVideoRenderingStartCalled = new Monitor();
-    protected Monitor mOnBufferingUpdateCalled = new Monitor();
-    protected Monitor mOnPrepareCalled = new Monitor();
-    protected Monitor mOnPlayCalled = new Monitor();
-    protected Monitor mOnDeselectTrackCalled = new Monitor();
-    protected Monitor mOnSeekCompleteCalled = new Monitor();
-    protected Monitor mOnCompletionCalled = new Monitor();
-    protected Monitor mTracksFullyFound = new Monitor();
-    protected Monitor mOnErrorCalled = new Monitor();
-    protected Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
-    protected int mCallStatus;
-
-    protected Context mContext;
-    protected Resources mResources;
-
-    protected ExecutorService mExecutor;
-
-    protected MediaPlayer2 mPlayer = null;
-    protected MediaPlayer2 mPlayer2 = null;
-    protected MediaStubActivity mActivity;
-    protected Instrumentation mInstrumentation;
-
-    protected final Object mEventCbLock = new Object();
-    protected List<MediaPlayer2.EventCallback> mEventCallbacks = new ArrayList<>();
-    protected final Object mEventCbLock2 = new Object();
-    protected List<MediaPlayer2.EventCallback> mEventCallbacks2 = new ArrayList<>();
-
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-    public PowerManager.WakeLock mScreenLock;
-    private KeyguardManager mKeyguardManager;
-    private List<FileMediaItem> mFileMediaItems = new ArrayList<>();
-
-    // convenience functions to create MediaPlayer2
-    protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri) {
-        return createMediaPlayer2(context, uri, null);
-    }
-
-    protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri,
-            SurfaceHolder holder) {
-        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        int s = Build.VERSION.SDK_INT >= 21 ? am.generateAudioSessionId() : 0;
-        return createMediaPlayer2(context, uri, holder, null, s > 0 ? s : 0);
-    }
-
-    protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri, SurfaceHolder holder,
-            AudioAttributesCompat audioAttributes, int audioSessionId) {
-        try {
-            MediaPlayer2 mp = createMediaPlayer2OnUiThread();
-            final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes :
-                    new AudioAttributesCompat.Builder().build();
-            mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
-            mp.setMediaItem(new UriMediaItem.Builder(uri).build());
-            if (holder != null) {
-                mp.setSurface(holder.getSurface());
-            }
-            final Monitor onPrepareCalled = new Monitor();
-            ExecutorService executor = Executors.newFixedThreadPool(1);
-            MediaPlayer2.EventCallback ecb =
-                    new MediaPlayer2.EventCallback() {
-                        @Override
-                        public void onInfo(
-                                MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                            if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                                onPrepareCalled.signal();
-                            }
-                        }
-                    };
-            mp.setEventCallback(executor, ecb);
-            mp.prepare();
-            onPrepareCalled.waitForSignal();
-            mp.clearEventCallback();
-            executor.shutdown();
-            return mp;
-        } catch (IllegalArgumentException ex) {
-            LOG.warning("create failed:" + ex);
-            // fall through
-        } catch (SecurityException ex) {
-            LOG.warning("create failed:" + ex);
-            // fall through
-        } catch (InterruptedException ex) {
-            LOG.warning("create failed:" + ex);
-            // fall through
-        }
-        return null;
-    }
-
-    protected MediaPlayer2 createMediaPlayer2(Context context, int resid) {
-        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        int s = Build.VERSION.SDK_INT >= 21 ? am.generateAudioSessionId() : 0;
-        return createMediaPlayer2(context, resid, null, s > 0 ? s : 0);
-    }
-
-    protected MediaPlayer2 createMediaPlayer2(Context context, int resid,
-            AudioAttributesCompat audioAttributes, int audioSessionId) {
-        try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid)) {
-            MediaPlayer2 mp = createMediaPlayer2OnUiThread();
-
-            final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes :
-                    new AudioAttributesCompat.Builder().build();
-            mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
-
-            mp.setMediaItem(new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build());
-
-            final Monitor onPrepareCalled = new Monitor();
-            ExecutorService executor = Executors.newFixedThreadPool(1);
-            MediaPlayer2.EventCallback ecb =
-                    new MediaPlayer2.EventCallback() {
-                        @Override
-                        public void onInfo(
-                                MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                            if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                                onPrepareCalled.signal();
-                            }
-                        }
-                    };
-            mp.setEventCallback(executor, ecb);
-            mp.prepare();
-            onPrepareCalled.waitForSignal();
-            mp.clearEventCallback();
-            executor.shutdown();
-            return mp;
-        } catch (IllegalArgumentException | SecurityException | InterruptedException
-                | IOException ex) {
-            LOG.warning("create failed:" + ex);
-            // fall through
-        }
-        return null;
-    }
-
-    private MediaPlayer2 createMediaPlayer2OnUiThread() {
-        final MediaPlayer2[] mp = new MediaPlayer2[1];
-        try {
-            mActivityRule.runOnUiThread(new Runnable() {
-                public void run() {
-                    mp[0] = MediaPlayer2.create(mActivity);
-                }
-            });
-        } catch (Throwable throwable) {
-            fail("Failed to create MediaPlayer2 instance on UI thread.");
-        }
-        return mp[0];
-    }
-
-    private void isBuggyDeviceForPlayback() {
-        if (Build.MODEL.contains("Android SDK built for arm64")) {
-            Assume.assumeTrue(
-                    "Emulators API 26 and 28 have a bug for playback b/272341857",
-                    Build.VERSION.SDK_INT != 26 && Build.VERSION.SDK_INT != 28
-            );
-        }
-    }
-
-    @Before
-    @CallSuper
-    public void setUp() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mKeyguardManager = (KeyguardManager)
-                mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-        mActivity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Keep screen on while testing.
-                if (Build.VERSION.SDK_INT >= 27) {
-                    mActivity.setTurnScreenOn(true);
-                    mActivity.setShowWhenLocked(true);
-                    mKeyguardManager.requestDismissKeyguard(mActivity, null);
-                } else {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-                }
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        try {
-            mActivityRule.runOnUiThread(new Runnable() {
-                public void run() {
-                    mPlayer = MediaPlayer2.create(mActivity);
-                    mPlayer2 = MediaPlayer2.create(mActivity);
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-        mContext = mActivityRule.getActivity();
-        mResources = mContext.getResources();
-        mExecutor = Executors.newFixedThreadPool(1);
-
-        setUpMP2ECb(mPlayer, mEventCbLock, mEventCallbacks);
-        setUpMP2ECb(mPlayer2, mEventCbLock2, mEventCallbacks2);
-
-        isBuggyDeviceForPlayback();
-    }
-
-    @After
-    @CallSuper
-    public void tearDown() throws Exception {
-        if (mPlayer != null) {
-            mPlayer.close();
-            mPlayer = null;
-        }
-        if (mPlayer2 != null) {
-            mPlayer2.close();
-            mPlayer2 = null;
-        }
-        mExecutor.shutdown();
-        mActivity = null;
-        for (FileMediaItem fitem : mFileMediaItems) {
-            assertTrue(fitem.isClosed());
-        }
-    }
-
-    protected void setUpMP2ECb(MediaPlayer2 mp, final Object cbLock,
-            final List<MediaPlayer2.EventCallback> ecbs) {
-        mp.setEventCallback(mExecutor, new MediaPlayer2.EventCallback() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer2 mp, MediaItem item, int w, int h) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onVideoSizeChanged(mp, item, w, h);
-                    }
-                }
-            }
-
-            @Override
-            public void onTimedMetaDataAvailable(MediaPlayer2 mp, MediaItem item,
-                    TimedMetaData data) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onTimedMetaDataAvailable(mp, item, data);
-                    }
-                }
-            }
-
-            @Override
-            public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onError(mp, item, what, extra);
-                    }
-                }
-            }
-
-            @Override
-            public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onInfo(mp, item, what, extra);
-                    }
-                }
-            }
-
-            @Override
-            public void onCallCompleted(
-                    MediaPlayer2 mp, MediaItem item, int what, int status) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onCallCompleted(mp, item, what, status);
-                    }
-                }
-            }
-
-            @Override
-            public void onMediaTimeDiscontinuity(MediaPlayer2 mp, MediaItem item,
-                    MediaTimestamp timestamp) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onMediaTimeDiscontinuity(mp, item, timestamp);
-                    }
-                }
-            }
-
-            @Override
-            public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onCommandLabelReached(mp, label);
-                    }
-                }
-            }
-
-            @Override
-            public  void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onSubtitleData(mp, item, track, data);
-                    }
-                }
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                    @NonNull List<TrackInfo> tracks) {
-                synchronized (cbLock) {
-                    for (MediaPlayer2.EventCallback ecb : ecbs) {
-                        ecb.onTracksChanged(mp, tracks);
-                    }
-                }
-            }
-        });
-    }
-
-    // returns true on success
-    protected boolean loadResource(int resid) throws Exception {
-        /* FIXME: ensure device has capability.
-        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
-            return false;
-        }
-        */
-
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            FileMediaItem item = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build();
-            mFileMediaItems.add(item);
-            mPlayer.setMediaItem(item);
-        }
-        return true;
-    }
-
-    protected MediaItem createDataSourceDesc(int resid) throws Exception {
-        /* FIXME: ensure device has capability.
-        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
-            return null;
-        }
-        */
-
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            FileMediaItem item = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build();
-            mFileMediaItems.add(item);
-            return item;
-        }
-    }
-
-    protected boolean checkLoadResource(int resid) throws Exception {
-        return loadResource(resid);
-
-        /* FIXME: ensure device has capability.
-        return MediaUtils.check(loadResource(resid), "no decoder found");
-        */
-    }
-
-    protected void playLiveVideoTest(String path, int playTime) throws Exception {
-        playVideoWithRetries(path, null, null, playTime);
-    }
-
-    protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception {
-        playVideoWithRetries(path, -1, -1, playTime);
-    }
-
-    protected void playVideoTest(String path, int width, int height) throws Exception {
-        playVideoWithRetries(path, width, height, 0);
-    }
-
-    protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime)
-            throws Exception {
-        boolean playedSuccessfully = false;
-        final Uri uri = Uri.parse(path);
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                mPlayer.setMediaItem(new UriMediaItem.Builder(uri).build());
-                playLoadedVideo(width, height, playTime);
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // prepare() can fail because of network issues, so try again
-                LOG.warning("prepare() failed on try " + i + ", trying playback again");
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
-    }
-
-    protected void playVideoTest(int resid, int width, int height) throws Exception {
-        if (!checkLoadResource(resid)) {
-            return; // skip
-        }
-
-        playLoadedVideo(width, height, 0);
-    }
-
-    protected void playLiveVideoTest(
-            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
-            int playTime) throws Exception {
-        playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime);
-    }
-
-    protected void playVideoWithRetries(
-            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
-            Integer width, Integer height, int playTime) throws Exception {
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                mPlayer.setMediaItem(new UriMediaItem.Builder(uri, headers, cookies).build());
-                playLoadedVideo(width, height, playTime);
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // prepare() can fail because of network issues, so try again
-                // playLoadedVideo already has reset the player so we can try again safely.
-                LOG.warning("prepare() failed on try " + i + ", trying playback again");
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
-    }
-
-    /**
-     * Play a video which has already been loaded with setMediaItem().
-     *
-     * @param width width of the video to verify, or null to skip verification
-     * @param height height of the video to verify, or null to skip verification
-     * @param playTime length of time to play video, or 0 to play entire video.
-     * with a non-negative value, this method stops the playback after the length of
-     * time or the duration the video is elapsed. With a value of -1,
-     * this method simply starts the video and returns immediately without
-     * stoping the video playback.
-     */
-    protected void playLoadedVideo(final Integer width, final Integer height, int playTime)
-            throws Exception {
-        final float volume = 0.5f;
-
-        boolean audioOnly = (width != null && width.intValue() == -1)
-                || (height != null && height.intValue() == -1);
-        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
-
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(new MediaPlayer2.EventCallback() {
-                @Override
-                public void onVideoSizeChanged(MediaPlayer2 mp, MediaItem item, int w, int h) {
-                    if (w == 0 && h == 0) {
-                        // A size of 0x0 can be sent initially one time when using NuPlayer.
-                        assertFalse(mOnVideoSizeChangedCalled.isSignalled());
-                        return;
-                    }
-                    mOnVideoSizeChangedCalled.signal();
-                    if (width != null) {
-                        assertEquals(width.intValue(), w);
-                    }
-                    if (height != null) {
-                        assertEquals(height.intValue(), h);
-                    }
-                }
-
-                @Override
-                public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                    fail("Media player had error " + what + " playing video");
-                }
-
-                @Override
-                public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                    if (what == MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START) {
-                        mOnVideoRenderingStartCalled.signal();
-                    } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                        mOnPrepareCalled.signal();
-                    }
-                }
-
-                @Override
-                public void onCallCompleted(MediaPlayer2 mp, MediaItem item,
-                        int what, int status) {
-                    if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                        mOnPlayCalled.signal();
-                    }
-                }
-            });
-        }
-        try {
-            mOnPrepareCalled.reset();
-            mPlayer.prepare();
-            mOnPrepareCalled.waitForSignal();
-        } catch (Exception e) {
-            mPlayer.reset();
-            throw new PrepareFailedException();
-        }
-
-        mOnPlayCalled.reset();
-        mPlayer.play();
-        mOnPlayCalled.waitForSignal();
-        if (!audioOnly) {
-            mOnVideoSizeChangedCalled.waitForSignal();
-            mOnVideoRenderingStartCalled.waitForSignal();
-        }
-        mPlayer.setPlayerVolume(volume);
-
-        // waiting to complete
-        if (playTime == -1) {
-            return;
-        } else if (playTime == 0) {
-            while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } else {
-            Thread.sleep(playTime);
-        }
-
-        // Validate media metrics from API 21 where PersistableBundle was added.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            PersistableBundle metrics = mPlayer.getMetrics();
-            if (metrics == null) {
-                fail("MediaPlayer.getMetrics() returned null metrics");
-            } else if (metrics.isEmpty()) {
-                fail("MediaPlayer.getMetrics() returned empty metrics");
-            } else {
-                int size = metrics.size();
-                Set<String> keys = metrics.keySet();
-
-                if (keys == null) {
-                    fail("MediaMetricsSet returned no keys");
-                } else if (keys.size() != size) {
-                    fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
-                }
-
-                // we played something; so one of these should be non-null
-                String vmime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_VIDEO,
-                        null);
-                String amime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_AUDIO,
-                        null);
-                if (vmime == null && amime == null) {
-                    fail("getMetrics() returned neither video nor audio mime value");
-                }
-
-                long duration = metrics.getLong(MediaPlayer2.MetricsConstants.DURATION, -2);
-                if (duration == -2) {
-                    fail("getMetrics() didn't return a duration");
-                }
-                long playing = metrics.getLong(MediaPlayer2.MetricsConstants.PLAYING, -2);
-                if (playing == -2) {
-                    fail("getMetrics() didn't return a playing time");
-                }
-                if (!keys.contains(MediaPlayer2.MetricsConstants.PLAYING)) {
-                    fail("MediaMetricsSet.keys() missing: "
-                            + MediaPlayer2.MetricsConstants.PLAYING);
-                }
-            }
-        }
-        mPlayer.reset();
-    }
-
-    private static class PrepareFailedException extends Exception {}
-
-    protected void setOnErrorListener() {
-        synchronized (mEventCbLock) {
-            mEventCallbacks.add(new MediaPlayer2.EventCallback() {
-                @Override
-                public void onError(MediaPlayer2 mp, MediaItem item, int what, int extra) {
-                    mOnErrorCalled.signal();
-                }
-            });
-        }
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java
deleted file mode 100644
index e6a44a6..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerDrmTest.java
+++ /dev/null
@@ -1,1095 +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.media2.player;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.Manifest;
-import android.app.DownloadManager;
-import android.app.DownloadManager.Request;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.media.MediaDrm;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Base64;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.WindowManager;
-
-import androidx.annotation.CallSuper;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.player.MediaPlayer.DrmInfo;
-import androidx.media2.player.MediaPlayer.DrmResult;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.Suppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.rule.GrantPermissionRule;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.Vector;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * DRM tests which use MediaPlayer to play audio or video.
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@Suppress // Disabled as it 100% fails b/79682973
-public class MediaPlayerDrmTest {
-    private static final int STREAM_RETRIES = 3;
-
-    private Monitor mOnVideoSizeChangedCalled = new Monitor();
-    private Monitor mOnPlaybackCompleted = new Monitor();
-    private Monitor mOnDrmInfoCalled = new Monitor();
-    private Monitor mOnDrmPreparedCalled = new Monitor();
-
-    private Context mContext;
-    private Resources mResources;
-
-    private MediaPlayer mPlayer = null;
-    private MediaStubActivity mActivity;
-    private Instrumentation mInstrumentation;
-
-    private ExecutorService mExecutor;
-    private MediaPlayer.PlayerCallback mECb = null;
-
-    @Rule
-    public GrantPermissionRule mRuntimePermissionRule =
-            GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-    public PowerManager.WakeLock mScreenLock;
-    private KeyguardManager mKeyguardManager;
-
-    @Before
-    @CallSuper
-    public void setUp() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mKeyguardManager = (KeyguardManager)
-                mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-        mActivity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Keep screen on while testing.
-                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                mActivity.setTurnScreenOn(true);
-                mActivity.setShowWhenLocked(true);
-                mKeyguardManager.requestDismissKeyguard(mActivity, null);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        try {
-            mActivityRule.runOnUiThread(new Runnable() {
-                public void run() {
-                    mPlayer = new MediaPlayer(mActivity);
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-
-        mContext = mInstrumentation.getTargetContext();
-        mResources = mContext.getResources();
-
-        mExecutor = Executors.newFixedThreadPool(2);
-    }
-
-    @After
-    @CallSuper
-    public void tearDown() throws Throwable {
-        if (mPlayer != null) {
-            mPlayer.close();
-            mPlayer = null;
-        }
-        mExecutor.shutdown();
-        mActivity = null;
-    }
-
-    private static class PrepareFailedException extends Exception {}
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Asset helpers
-
-    private static Uri getUriFromFile(String path) {
-        return Uri.fromFile(new File(getDownloadedPath(path)));
-    }
-
-    private static String getDownloadedPath(String fileName) {
-        return getDownloadedFolder() + File.separator + fileName;
-    }
-
-    private static String getDownloadedFolder() {
-        return Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_DOWNLOADS).getPath();
-    }
-
-    private static final class Resolution {
-        public final boolean isHD;
-        public final int width;
-        public final int height;
-
-        Resolution(boolean isHD, int width, int height) {
-            this.isHD = isHD;
-            this.width = width;
-            this.height = height;
-        }
-    }
-
-    private static final Resolution RES_720P  = new Resolution(true, 1280,  720);
-    private static final Resolution RES_AUDIO = new Resolution(false,   0,    0);
-
-
-    // Assets
-
-    private static final Uri CENC_AUDIO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/cenc/clearkey/car_cenc-20120827-8c-pssh.mp4");
-    private static final Uri CENC_AUDIO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-8c.mp4");
-
-    private static final Uri CENC_VIDEO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/cenc/clearkey/car_cenc-20120827-88-pssh.mp4");
-    private static final Uri CENC_VIDEO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-88.mp4");
-
-
-    // Tests
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV0Sync() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V0_SYNC_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV1Sync() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V1_ASYNC_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV2SyncConfig() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V2_SYNC_CONFIG_TEST);
-    }
-
-    @Test
-    @LargeTest
-    public void carClearKeyAudioDownloadedV3AsyncDrmPrepared() throws Exception {
-        download(CENC_AUDIO_URL,
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
-    }
-
-    // helpers
-
-    private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
-        playModularDrmVideo(uri, res.width, res.height, testType);
-    }
-
-    private void download(Uri remote, Uri local, Resolution res, ModularDrmTestType testType)
-            throws Exception {
-        playModularDrmVideoDownload(remote, local, res.width, res.height, testType);
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////
-    // Modular DRM
-
-    private static final String TAG = "MediaPlayerDrmTest";
-
-    private static final int PLAY_TIME_MS = 60 * 1000;
-    private byte[] mKeySetId;
-    private boolean mAudioOnly;
-
-    private static final byte[] CLEAR_KEY_CENC = {
-            (byte) 0x1a, (byte) 0x8a, (byte) 0x20, (byte) 0x95,
-            (byte) 0xe4, (byte) 0xde, (byte) 0xb2, (byte) 0xd2,
-            (byte) 0x9e, (byte) 0xc8, (byte) 0x16, (byte) 0xac,
-            (byte) 0x7b, (byte) 0xae, (byte) 0x20, (byte) 0x82
-            };
-
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-
-    final byte[] mClearKeyPssh = hexStringToByteArray(
-            "0000003470737368"    // BMFF box header (4 bytes size + 'pssh')
-            + "01000000"          // Full box header (version = 1 flags = 0)
-            + "1077efecc0b24d02"  // SystemID
-            + "ace33c1e52e2fb4b"
-            + "00000001"          // Number of key ids
-            + "60061e017e477e87"  // Key id
-            + "7e57d00d1ed00d1e"
-            + "00000000"          // Size of Data, must be zero
-            );
-
-
-    private enum ModularDrmTestType {
-        V0_SYNC_TEST,
-        V1_ASYNC_TEST,
-        V2_SYNC_CONFIG_TEST,
-        V3_ASYNC_DRMPREPARED_TEST,
-        V4_SYNC_OFFLINE_KEY,
-    }
-
-    // TODO: After living on these tests for a while, we can consider grouping them based on
-    // the asset such that each asset is downloaded once and played back with multiple tests.
-    private void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        final long downloadTimeOutSeconds = 600;
-        Log.i(TAG, "Downloading file:" + path);
-        MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
-        final long id = mediaDownloadManager.downloadFileWithRetries(
-                uri, path, downloadTimeOutSeconds, STREAM_RETRIES);
-        assertFalse("Download " + uri + " failed.", id == -1);
-        Uri file = mediaDownloadManager.getUriForDownloadedFile(id);
-        Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
-
-        try {
-            playModularDrmVideo(file, width, height, testType);
-        } finally {
-            mediaDownloadManager.removeFile(id);
-        }
-    }
-
-    private void playModularDrmVideo(Uri uri, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        // Force gc for a clean start
-        System.gc();
-
-        playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
-    }
-
-    private void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
-            int playTime, ModularDrmTestType testType) throws Exception {
-
-        // first the synchronous variation
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
-                playLoadedModularDrmVideo(file, width, height, playTime, testType);
-
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // we can fail because of network issues, so try again
-                Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i
-                        + ", trying playback again");
-                mPlayer.reset();
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
-                playedSuccessfully);
-    }
-
-    /**
-     * Play a video which has already been loaded with setMediaItem().
-     * The DRM setup is performed synchronously.
-     *
-     * @param file media item
-     * @param width width of the video to verify, or null to skip verification
-     * @param height height of the video to verify, or null to skip verification
-     * @param playTime length of time to play video, or 0 to play entire video
-     * @param testType test type
-     */
-    private void playLoadedModularDrmVideo(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        switch (testType) {
-            case V0_SYNC_TEST:
-            case V1_ASYNC_TEST:
-            case V2_SYNC_CONFIG_TEST:
-            case V3_ASYNC_DRMPREPARED_TEST:
-                playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
-                break;
-
-            case V4_SYNC_OFFLINE_KEY:
-                playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
-                break;
-        }
-    }
-
-    private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        final float volume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        mECb = new MediaPlayer.PlayerCallback() {
-                @Override
-                public void onVideoSizeChanged(MediaPlayer mp, MediaItem item, VideoSize size) {
-                    Log.v(TAG, "VideoSizeChanged" + " w:" + size.getWidth() + " h:"
-                            + size.getHeight());
-                    mOnVideoSizeChangedCalled.signal();
-                }
-
-                @Override
-                public void onError(MediaPlayer mp, MediaItem item, int what, int extra) {
-                    fail("Media player had error " + what + " playing video");
-                }
-
-                @Override
-                public void onInfo(MediaPlayer mp, MediaItem item, int what, int extra) {
-                    if (what == MediaPlayer.MEDIA_INFO_MEDIA_ITEM_END) {
-                        Log.v(TAG, "playLoadedVideo: onInfo_PlaybackComplete");
-                        mOnPlaybackCompleted.signal();
-                    }
-                }
-            };
-
-        mPlayer.registerPlayerCallback(mExecutor, mECb);
-        Log.v(TAG, "playLoadedVideo: setMediaItem()");
-        ListenableFuture<PlayerResult> future =
-                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
-        assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-        SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
-        surfaceHolder.setKeepScreenOn(true);
-        mPlayer.setSurface(surfaceHolder.getSurface());
-
-        try {
-            switch (testType) {
-                case V0_SYNC_TEST:
-                    preparePlayerAndDrm_V0_syncDrmSetup();
-                    break;
-
-                case V1_ASYNC_TEST:
-                    preparePlayerAndDrm_V1_asyncDrmSetup();
-                    break;
-
-                case V2_SYNC_CONFIG_TEST:
-                    preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
-                    break;
-
-                case V3_ASYNC_DRMPREPARED_TEST:
-                    preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
-                    break;
-            }
-
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-
-        Log.v(TAG, "playLoadedVideo: play()");
-        mPlayer.play();
-        if (!mAudioOnly) {
-            mOnVideoSizeChangedCalled.waitForSignal();
-        }
-        mPlayer.setPlayerVolume(volume);
-
-        // waiting to complete
-        if (playTime == 0) {
-            Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-            mOnPlaybackCompleted.waitForSignal();
-        } else {
-            Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-            mOnPlaybackCompleted.waitForSignal(playTime);
-        }
-
-        try {
-            Log.v(TAG, "playLoadedVideo: releaseDrm");
-            mPlayer.releaseDrm();
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-    }
-
-    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
-        Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
-        ListenableFuture<PlayerResult> future = mPlayer.prepare();
-        assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V1_asyncDrmSetup()
-            throws InterruptedException, ExecutionException {
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mPlayer.registerPlayerCallback(mExecutor, new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onDrmInfo(MediaPlayer mp, MediaItem item, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                mOnDrmInfoCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V1: calling prepare()");
-        ListenableFuture<PlayerResult> future = mPlayer.prepare();
-        assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-        mOnDrmInfoCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V1: setupDrm");
-        }
-    }
-
-    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
-        mPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
-            @Override
-            public void onDrmConfig(MediaPlayer mp, MediaItem item) {
-                String widevineSecurityLevel3 = "L3";
-                String securityLevelProperty = "securityLevel";
-
-                try {
-                    String level = mp.getDrmPropertyString(securityLevelProperty);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
-                            + securityLevelProperty + " -> " + level);
-                    mp.setDrmPropertyString(securityLevelProperty, widevineSecurityLevel3);
-                    level = mp.getDrmPropertyString(securityLevelProperty);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
-                            + securityLevelProperty + " -> " + level);
-                } catch (MediaPlayer.NoDrmSchemeException e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
-                }
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
-        ListenableFuture<PlayerResult> future = mPlayer.prepare();
-        assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
-            throws InterruptedException, ExecutionException {
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mPlayer.registerPlayerCallback(mExecutor, new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onDrmInfo(MediaPlayer mp, MediaItem item, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
-
-                // DRM preperation
-                List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.isEmpty()) {
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
-                    asyncSetupDrmError.set(true);
-                    mOnDrmInfoCalled.signal();
-                    // we won't call prepareDrm anymore but need to get passed the wait
-                    mOnDrmPreparedCalled.signal();
-                    return;
-                }
-
-                // setting up with the first supported UUID
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
-
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
-                final ListenableFuture<DrmResult> future = mp.prepareDrm(drmScheme);
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
-                future.addListener(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            DrmResult result = future.get();
-                            Log.v(TAG, "preparePlayerAndDrm_V3: prepareDrm status: "
-                                    + result.getResultCode());
-
-                            assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
-                                    result.getResultCode() == DrmResult.RESULT_SUCCESS);
-
-                            DrmInfo drmInfo = mPlayer.getDrmInfo();
-
-                            // in the callback (async mode) so handling exceptions here
-                            try {
-                                setupDrm(drmInfo, false /* prepareDrm */,
-                                        true /* synchronousNetworking */,
-                                        MediaDrm.KEY_TYPE_STREAMING);
-                            } catch (Exception e) {
-                                Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION ", e);
-                                asyncSetupDrmError.set(true);
-                            }
-
-                            mOnDrmPreparedCalled.signal();
-                            Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
-                        } catch (ExecutionException | InterruptedException e) {
-                            Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception ",
-                                    e);
-                            asyncSetupDrmError.set(true);
-                            mOnDrmInfoCalled.signal();
-                            // need to get passed the wait
-                            mOnDrmPreparedCalled.signal();
-                        }
-                    }
-                }, mExecutor);
-
-                mOnDrmInfoCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V3: calling prepare()");
-        ListenableFuture<PlayerResult> future = mPlayer.prepare();
-        assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-        // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
-        mOnDrmPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V3: setupDrm");
-        }
-    }
-
-    private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
-            final Integer height, int playTime) throws Exception {
-        final float volume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
-        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setSurface " + surfaceHolder);
-        mPlayer.setSurface(surfaceHolder.getSurface());
-        surfaceHolder.setKeepScreenOn(true);
-
-        DrmInfo drmInfo = null;
-
-        for (int round = 0; round < 2; round++) {
-            boolean keyRequestRound = (round == 0);
-            boolean restoreRound = (round == 1);
-            Log.v(TAG, "playLoadedVideo: round " + round);
-
-            try {
-                mPlayer.registerPlayerCallback(mExecutor, mECb);
-
-                Log.v(TAG, "playLoadedVideo: setMediaItem()");
-                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
-
-                Log.v(TAG, "playLoadedVideo: prepare()");
-                ListenableFuture<PlayerResult> future = mPlayer.prepare();
-                assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
-
-                // but preparing the DRM every time with proper key request type
-                drmInfo = mPlayer.getDrmInfo();
-                if (drmInfo != null) {
-                    if (keyRequestRound) {
-                        // asking for offline keys
-                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                                 MediaDrm.KEY_TYPE_OFFLINE);
-                    } else if (restoreRound) {
-                        setupDrmRestore(drmInfo, true /* prepareDrm */);
-                    } else {
-                        fail("preparePlayer: unexpected round " + round);
-                    }
-                    Log.v(TAG, "preparePlayer: setupDrm done!");
-                }
-
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            Log.v(TAG, "playLoadedVideo: play()");
-            mPlayer.play();
-            if (!mAudioOnly) {
-                mOnVideoSizeChangedCalled.waitForSignal();
-            }
-            mPlayer.setPlayerVolume(volume);
-
-            // waiting to complete
-            if (playTime == 0) {
-                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-                mOnPlaybackCompleted.waitForSignal();
-            } else {
-                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-                mOnPlaybackCompleted.waitForSignal(playTime);
-            }
-
-            try {
-                if (drmInfo != null) {
-                    if (restoreRound) {
-                        // releasing the offline key
-                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
-                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
-                        Log.v(TAG, "playLoadedVideo: released offline keys");
-                    }
-
-                    Log.v(TAG, "playLoadedVideo: releaseDrm");
-                    mPlayer.releaseDrm();
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            if (keyRequestRound) {
-                mOnVideoSizeChangedCalled.reset();
-                mOnPlaybackCompleted.reset();
-                final int sleepBetweenRounds = 1000;
-                Thread.sleep(sleepBetweenRounds);
-
-                Log.v(TAG, "playLoadedVideo: reset");
-                mPlayer.reset();
-            }
-        }  // for
-    }
-
-    // Converts a BMFF PSSH initData to a raw cenc initData
-    private byte[] makeCencPSSH(UUID uuid, byte[] bmffPsshData) {
-        byte[] pssh_header = new byte[] { (byte) 'p', (byte) 's', (byte) 's', (byte) 'h' };
-        byte[] pssh_version = new byte[] { 1, 0, 0, 0 };
-        int boxSizeByteCount = 4;
-        int uuidByteCount = 16;
-        int dataSizeByteCount = 4;
-        // Per "W3C cenc Initialization Data Format" document:
-        // box size + 'pssh' + version + uuid + payload + size of data
-        int boxSize = boxSizeByteCount + pssh_header.length + pssh_version.length
-                + uuidByteCount + bmffPsshData.length + dataSizeByteCount;
-        int dataSize = 0;
-
-        // the default write is big-endian, i.e., network byte order
-        ByteBuffer rawPssh = ByteBuffer.allocate(boxSize);
-        rawPssh.putInt(boxSize);
-        rawPssh.put(pssh_header);
-        rawPssh.put(pssh_version);
-        rawPssh.putLong(uuid.getMostSignificantBits());
-        rawPssh.putLong(uuid.getLeastSignificantBits());
-        rawPssh.put(bmffPsshData);
-        rawPssh.putInt(dataSize);
-
-        return rawPssh.array();
-    }
-
-    /*
-     * Sets up the DRM for the first DRM scheme from the supported list.
-     *
-     * @param drmInfo DRM info of the source
-     * @param prepareDrm whether prepareDrm should be called
-     * @param synchronousNetworking whether the network operation of key request/response will
-     *        be performed synchronously
-     */
-    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
-            int keyType) throws Exception {
-        Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm
-                + " synchronousNetworking: " + synchronousNetworking);
-        try {
-            byte[] initData = null;
-            String mime = null;
-            String keyTypeStr = "Unexpected";
-
-            switch (keyType) {
-                case MediaDrm.KEY_TYPE_STREAMING:
-                case MediaDrm.KEY_TYPE_OFFLINE:
-                    // DRM preparation
-                    List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                    if (supportedSchemes.isEmpty()) {
-                        fail("setupDrm: No supportedSchemes");
-                    }
-
-                    // instead of supportedSchemes[0] in GTS
-                    UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                    Log.d(TAG, "setupDrm: selected " + drmScheme);
-
-                    if (prepareDrm) {
-                        ListenableFuture<DrmResult> future = mPlayer.prepareDrm(drmScheme);
-                        assertEquals(DrmResult.RESULT_SUCCESS, future.get().getResultCode());
-                    }
-
-                    byte[] psshData = drmInfo.getPssh().get(drmScheme);
-                    // diverging from GTS
-                    if (psshData == null) {
-                        initData = mClearKeyPssh;
-                        Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH."
-                                + " Using default data.");
-                    } else {
-                        // Can skip conversion if ClearKey adds support for BMFF initData b/64863112
-                        initData = makeCencPSSH(CLEARKEY_SCHEME_UUID, psshData);
-                    }
-                    Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: "
-                            + Arrays.toString(initData));
-
-                    // diverging from GTS
-                    mime = "cenc";
-
-                    keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING)
-                            ? "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
-                    break;
-
-                case MediaDrm.KEY_TYPE_RELEASE:
-                    if (mKeySetId == null) {
-                        fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
-                    }
-                    keyTypeStr = "KEY_TYPE_RELEASE";
-                    break;
-
-                default:
-                    fail("setupDrm: Unexpected keyType " + keyType);
-            }
-
-            final MediaDrm.KeyRequest request = mPlayer.getDrmKeyRequest(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    initData,
-                    mime,
-                    keyType,
-                    null /* optionalKeyRequestParameters */
-                    );
-
-            Log.d(TAG, "setupDrm: mPlayer.getDrmKeyRequest(" + keyTypeStr
-                    + ") request -> " + request);
-
-            // diverging from GTS
-            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
-            byte[] response = createKeysResponse(request, clearKeys);
-
-            // null is returned when the response is for a streaming or release request.
-            byte[] keySetId = mPlayer.provideDrmKeyResponse(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    response);
-            Log.d(TAG, "setupDrm: provideDrmKeyResponse -> " + Arrays.toString(keySetId));
-            // storing offline key for a later restore
-            mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
-
-        } catch (MediaPlayer.NoDrmSchemeException e) {
-            Log.d(TAG, "setupDrm: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.d(TAG, "setupDrm: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrm
-
-    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
-        Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
-        try {
-            if (prepareDrm) {
-                // DRM preparation
-                List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.isEmpty()) {
-                    fail("setupDrmRestore: No supportedSchemes");
-                }
-
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
-
-                ListenableFuture<DrmResult> future = mPlayer.prepareDrm(drmScheme);
-                assertEquals(DrmResult.RESULT_SUCCESS, future.get().getResultCode());
-            }
-
-            if (mKeySetId == null) {
-                fail("setupDrmRestore: Offline key has not been setup.");
-            }
-
-            mPlayer.restoreDrmKeys(mKeySetId);
-
-        } catch (MediaPlayer.NoDrmSchemeException e) {
-            Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.v(TAG, "setupDrmRestore: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrmRestore
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Diverging from GTS
-
-    // Clearkey helpers
-
-    /**
-     * Convert a hex string into byte array.
-     */
-    private static byte[] hexStringToByteArray(String s) {
-        int len = s.length();
-        byte[] data = new byte[len / 2];
-        for (int i = 0; i < len; i += 2) {
-            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
-                    + Character.digit(s.charAt(i + 1), 16));
-        }
-        return data;
-    }
-
-    /**
-     * Extracts key ids from the pssh blob returned by getDrmKeyRequest() and
-     * places it in keyIds.
-     * keyRequestBlob format (section 5.1.3.1):
-     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
-     *
-     * @return size of keyIds vector that contains the key ids, 0 for error
-     */
-    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
-        if (0 == keyRequestBlob.length || keyIds == null) {
-            Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
-            return 0;
-        }
-
-        String jsonLicenseRequest = new String(keyRequestBlob);
-        keyIds.clear();
-
-        try {
-            JSONObject license = new JSONObject(jsonLicenseRequest);
-            Log.v(TAG, "getKeyIds: license: " + license);
-            final JSONArray ids = license.getJSONArray("kids");
-            Log.v(TAG, "getKeyIds: ids: " + ids);
-            for (int i = 0; i < ids.length(); ++i) {
-                keyIds.add(ids.getString(i));
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
-            return 0;
-        }
-        return keyIds.size();
-    }
-
-    /**
-     * Creates the JSON Web Key string.
-     *
-     * @return JSON Web Key string.
-     */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
-        String jwkSet = "{\"keys\":[";
-        for (int i = 0; i < keyIds.size(); ++i) {
-            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
-            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
-
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id + "\",\"k\":\"" + key + "\"}";
-        }
-        jwkSet += "]}";
-        return jwkSet;
-    }
-
-    /**
-     * Retrieves clear key ids from KeyRequest and creates the response in place.
-     */
-    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
-
-        Vector<String> keyIds = new Vector<String>();
-        if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
-            Log.e(TAG, "No key ids found in initData");
-            return null;
-        }
-
-        if (clearKeys.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids="
-                    + keyIds.size() + ", keys=" + clearKeys.length);
-            return null;
-        }
-
-        // Base64 encodes clearkeys. Keys are known to the application.
-        Vector<String> keys = new Vector<String>();
-        for (int i = 0; i < clearKeys.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeys[i],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
-            keys.add(clearKey);
-        }
-
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
-        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
-
-        return jsonResponse;
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Playback/download helpers
-
-    private static class MediaDownloadManager {
-        private static final String TAG = "MediaDownloadManager";
-
-        private final Context mContext;
-        private final DownloadManager mDownloadManager;
-
-        MediaDownloadManager(Context context) {
-            mContext = context;
-            mDownloadManager =
-                    (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
-        }
-
-        public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
-                throws Exception {
-            long id = -1;
-            for (int i = 0; i < retries; i++) {
-                try {
-                    id = downloadFile(uri, file, timeout);
-                    if (id != -1) {
-                        break;
-                    }
-                } catch (Exception e) {
-                    removeFile(id);
-                    Log.w(TAG, "Download failed " + i + " times ");
-                }
-            }
-            return id;
-        }
-
-        public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
-            Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
-            final DownloadReceiver receiver = new DownloadReceiver();
-            long id = -1;
-            try {
-                IntentFilter intentFilter =
-                        new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
-                if (Build.VERSION.SDK_INT < 33) {
-                    mContext.registerReceiver(receiver, intentFilter);
-                } else {
-                    mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED);
-                }
-
-                Request request = new Request(uri);
-                request.setDestinationUri(file);
-                id = mDownloadManager.enqueue(request);
-                Log.i(TAG, "enqueue:" + id);
-
-                receiver.waitForDownloadComplete(timeout, id);
-            } finally {
-                mContext.unregisterReceiver(receiver);
-            }
-            return id;
-        }
-
-        public void removeFile(long id) {
-            Log.i(TAG, "removeFile:" + id);
-            mDownloadManager.remove(id);
-        }
-
-        public Uri getUriForDownloadedFile(long id) {
-            return mDownloadManager.getUriForDownloadedFile(id);
-        }
-
-        private final class DownloadReceiver extends BroadcastReceiver {
-            private HashSet<Long> mCompleteIds = new HashSet<>();
-
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                synchronized (mCompleteIds) {
-                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
-                        mCompleteIds.add(
-                                intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
-                        mCompleteIds.notifyAll();
-                    }
-                }
-            }
-
-            private boolean isCompleteLocked(long... ids) {
-                for (long id : ids) {
-                    if (!mCompleteIds.contains(id)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
-                    throws InterruptedException {
-                if (waitForIds.length == 0) {
-                    throw new IllegalArgumentException("Missing IDs to wait for");
-                }
-
-                final long startTime = SystemClock.elapsedRealtime();
-                do {
-                    synchronized (mCompleteIds) {
-                        mCompleteIds.wait(1000);
-                        if (isCompleteLocked(waitForIds)) {
-                            return;
-                        }
-                    }
-                } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
-
-                throw new InterruptedException(
-                        "Timeout waiting for IDs " + Arrays.toString(waitForIds)
-                        + "; received " + mCompleteIds.toString()
-                        + ".  Make sure you have WiFi or some other connectivity for this test");
-            }
-        }
-
-    }  // MediaDownloadManager
-
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
deleted file mode 100644
index 726c980..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ /dev/null
@@ -1,2104 +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.media2.player;
-
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import static junit.framework.TestCase.assertFalse;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.player.TestUtils.Monitor;
-import androidx.media2.player.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(AndroidJUnit4.class)
-public class MediaPlayerTest extends MediaPlayerTestBase {
-    private static final String LOG_TAG = "MediaPlayerTest";
-
-    private static final int SLEEP_TIME = 1000;
-    private static final long WAIT_TIME_MS = 300;
-    private static final long LARGE_TEST_WAIT_TIME_MS = 10000;
-    private static final float FLOAT_TOLERANCE = .0001f;
-    private static final int INVALID_SHUFFLE_MODE = -1000;
-    private static final int INVALID_REPEAT_MODE = -1000;
-    private static final String TEST_PLAYLIST_GENRE = "GENRE_TEST";
-
-    private Object mPlayerCbArg1;
-    private Object mPlayerCbArg2;
-
-    private final List<TrackInfo> mVideoTrackInfos = new ArrayList<>();
-    private final List<TrackInfo> mAudioTrackInfos = new ArrayList<>();
-    private final List<TrackInfo> mSubtitleTrackInfos = new ArrayList<>();
-    private TrackInfo mSelectedTrack = null;
-    private final Monitor mOnSubtitleDataCalled = new Monitor();
-    private final Monitor mTracksFullyFound = new Monitor();
-    private final Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
-    private final Monitor mOnErrorCalled = new Monitor();
-
-    @Before
-    @Override
-    public void setUp() throws Throwable {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @Test
-    @MediumTest
-    @Ignore("Test disabled due to flakiness, see b/138474897")
-    public void playAudioOnce() throws Exception {
-        assertTrue(loadResource(R.raw.testmp3_2));
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        Future<PlayerResult> setAttrFuture = mPlayer.setAudioAttributes(attributes);
-
-        final TestUtils.Monitor playing = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor, new SessionPlayer.PlayerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull SessionPlayer player, int playerState) {
-                playing.signal();
-            }
-        });
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-
-        assertTrue(playing.waitForSignal(SLEEP_TIME));
-        assertFutureSuccess(setAttrFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-    }
-
-    @Test
-    @LargeTest
-    public void playAudio() throws Exception {
-        final int resid = R.raw.testmp3_2;
-        final int mp3Duration = 34909;
-        final int tolerance = 100;
-        final int seekDuration = 100;
-
-        Future<PlayerResult> setItemFuture;
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            setItemFuture = mPlayer.setMediaItem(new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build());
-        }
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        Future<PlayerResult> setAttrFuture = mPlayer.setAudioAttributes(attributes);
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(setAttrFuture);
-        assertFutureSuccess(prepareFuture);
-
-        assertNotEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-        assertFutureSuccess(mPlayer.play());
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        assertEquals(mp3Duration, mPlayer.getDuration(), tolerance);
-        long pos = mPlayer.getCurrentPosition();
-        assertTrue(pos >= 0);
-        assertTrue(pos < mp3Duration - seekDuration);
-
-        assertFutureSuccess(mPlayer.seekTo(pos + seekDuration, MediaPlayer.SEEK_PREVIOUS_SYNC));
-        assertEquals(pos + seekDuration, mPlayer.getCurrentPosition(), tolerance);
-
-        assertFutureSuccess(mPlayer.pause());
-        assertNotEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-        assertFutureSuccess(mPlayer.play());
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // waiting to complete
-        while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
-            Thread.sleep(SLEEP_TIME);
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void playVideo() throws Exception {
-        if (!loadResource(R.raw.testvideo)) {
-            fail();
-        }
-        final int width = 352;
-        final int height = 288;
-        final float volume = 0.5f;
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-
-        final TestUtils.Monitor onVideoSizeChangedCalled = new TestUtils.Monitor();
-        final TestUtils.Monitor onVideoRenderingStartCalled = new TestUtils.Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, MediaItem dsd, VideoSize size) {
-                assertVideoSizeEquals(size);
-            }
-
-            @Override
-            public void onVideoSizeChanged(@NonNull SessionPlayer player,
-                    @NonNull androidx.media2.common.VideoSize size) {
-                assertVideoSizeEquals(new VideoSize(size.getWidth(), size.getHeight()));
-            }
-
-            private void assertVideoSizeEquals(VideoSize size) {
-                if (size.getWidth() == 0 && size.getHeight() == 0) {
-                    // A size of 0x0 can be sent initially one time when using NuPlayer.
-                    assertFalse(onVideoSizeChangedCalled.isSignalled());
-                    return;
-                }
-                onVideoSizeChangedCalled.signal();
-                assertEquals(width, size.getWidth());
-                assertEquals(height, size.getHeight());
-            }
-
-            @Override
-            public void onError(MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                fail("Media player had error " + what + " playing video");
-            }
-
-            @Override
-            public void onInfo(MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
-                    onVideoRenderingStartCalled.signal();
-                }
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-
-        onVideoSizeChangedCalled.waitForCountedSignals(2);
-        onVideoRenderingStartCalled.waitForSignal();
-
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertFutureSuccess(mPlayer.setPlayerVolume(volume));
-
-        // waiting to complete
-        while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
-            Thread.sleep(SLEEP_TIME);
-        }
-
-        // Validate media metrics from API 21 where PersistableBundle was added.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            PersistableBundle metrics = mPlayer.getMetrics();
-            if (metrics == null) {
-                fail("MediaPlayer.getMetrics() returned null metrics");
-            } else if (metrics.isEmpty()) {
-                fail("MediaPlayer.getMetrics() returned empty metrics");
-            } else {
-
-                int size = metrics.size();
-                Set<String> keys = metrics.keySet();
-
-                if (keys == null) {
-                    fail("MediaMetricsSet returned no keys");
-                } else if (keys.size() != size) {
-                    fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
-                }
-
-                // we played something; so one of these should be non-null
-                String vmime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_VIDEO,
-                        null);
-                String amime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_AUDIO,
-                        null);
-                if (vmime == null && amime == null) {
-                    fail("getMetrics() returned neither video nor audio mime value");
-                }
-
-                long duration = metrics.getLong(MediaPlayer.MetricsConstants.DURATION, -2);
-                if (duration == -2) {
-                    fail("getMetrics() didn't return a duration");
-                }
-                long playing = metrics.getLong(MediaPlayer.MetricsConstants.PLAYING, -2);
-                if (playing == -2) {
-                    fail("getMetrics() didn't return a playing time");
-                }
-                if (!keys.contains(MediaPlayer.MetricsConstants.PLAYING)) {
-                    fail("MediaMetricsSet.keys() missing: "
-                            + MediaPlayer.MetricsConstants.PLAYING);
-                }
-            }
-        }
-        MediaItem item = mPlayer.getCurrentMediaItem();
-        mPlayer.reset();
-        assertTrue(((FileMediaItem) item).isClosed());
-    }
-
-    @Test
-    @LargeTest
-    public void playVideoWithUri() throws Exception {
-        if (!loadResourceWithUri(R.raw.testvideo)) {
-            fail();
-        }
-        final int width = 352;
-        final int height = 288;
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-
-        final TestUtils.Monitor onVideoSizeChangedCalled = new TestUtils.Monitor();
-        final TestUtils.Monitor onVideoRenderingStartCalled = new TestUtils.Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, MediaItem dsd, VideoSize size) {
-                assertVideoSizeEquals(size);
-            }
-
-            @Override
-            public void onVideoSizeChanged(@NonNull SessionPlayer player,
-                    @NonNull androidx.media2.common.VideoSize size) {
-                assertVideoSizeEquals(new VideoSize(size.getWidth(), size.getHeight()));
-            }
-
-            private void assertVideoSizeEquals(VideoSize size) {
-                if (size.getWidth() == 0 && size.getHeight() == 0) {
-                    // A size of 0x0 can be sent initially one time when using NuPlayer.
-                    assertFalse(onVideoSizeChangedCalled.isSignalled());
-                    return;
-                }
-                onVideoSizeChangedCalled.signal();
-                assertEquals(width, size.getWidth());
-                assertEquals(height, size.getHeight());
-            }
-
-            @Override
-            public void onError(MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                fail("Media player had error " + what + " playing video");
-            }
-
-            @Override
-            public void onInfo(MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
-                    onVideoRenderingStartCalled.signal();
-                }
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-
-        onVideoSizeChangedCalled.waitForCountedSignals(2);
-        onVideoRenderingStartCalled.waitForSignal();
-
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-    }
-
-    @Test
-    @MediumTest
-    public void getDuration() throws Exception {
-        if (!loadResource(R.raw.testvideo)) {
-            fail();
-        }
-        final int expectedDuration = 11047;
-        final int tolerance = 70;
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder2().getSurface());
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-        assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getDuration());
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-
-        assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
-        assertEquals(expectedDuration, mPlayer.getDuration(), tolerance);
-    }
-
-    @Test
-    @MediumTest
-    public void getCurrentPosition() throws Exception {
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-        assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getCurrentPosition());
-    }
-
-    @Test
-    @MediumTest
-    public void getBufferedPosition() throws Exception {
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-        assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getBufferedPosition());
-    }
-
-    @Test
-    @MediumTest
-    public void getPlaybackSpeed() throws Exception {
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-        try {
-            mPlayer.getPlaybackSpeed();
-        } catch (Exception e) {
-            fail();
-        }
-    }
-
-    @Test
-    @LargeTest
-    @Ignore("May be flaky if emulator runs slowly")
-    public void playbackRate() throws Exception {
-        final int toleranceMs = 1000;
-        if (!loadResource(R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            fail();
-        }
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-
-        float[] rates = {0.25f, 0.5f, 1.0f, 2.0f};
-        for (float playbackRate : rates) {
-            Future<PlayerResult> seekFuture = mPlayer.seekTo(0, MediaPlayer.SEEK_PREVIOUS_SYNC);
-            Thread.sleep(1000);
-            int playTime = 4000;  // The testing clip is about 10 second long.
-            int privState = mPlayer.getPlayerState();
-
-            Future<PlayerResult> setParamsFuture = mPlayer.setPlaybackParams(
-                    new PlaybackParams.Builder().setSpeed(playbackRate).build());
-            assertFutureSuccess(seekFuture);
-            assertFutureSuccess(setParamsFuture);
-            assertEquals("setPlaybackParams() should not change player state. "
-                    + mPlayer.getPlayerState(), privState, mPlayer.getPlayerState());
-
-            Future<PlayerResult> playFuture = mPlayer.play();
-            Thread.sleep(playTime);
-
-            PlaybackParams pbp = mPlayer.getPlaybackParams();
-            assertEquals(playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
-            assertEquals("The player should still be playing",
-                    MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-            long playedMediaDurationMs = mPlayer.getCurrentPosition();
-            long expectedPosition = (long) (playTime * playbackRate);
-            int diff = (int) Math.abs(playedMediaDurationMs - expectedPosition);
-            if (diff > toleranceMs) {
-                fail("Media player had error in playback rate " + playbackRate
-                        + ". expected position after playing " + playTime
-                        + " was " + expectedPosition + ", but actually " + playedMediaDurationMs);
-            }
-            assertFutureSuccess(playFuture);
-            assertFutureSuccess(mPlayer.pause());
-
-            pbp = mPlayer.getPlaybackParams();
-            assertEquals("pause() should not change the playback rate property.",
-                    playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
-        }
-        mPlayer.reset();
-    }
-
-    @Ignore("Test disabled due to flakiness, see b/272342480")
-    @Test
-    @LargeTest
-    public void seekModes() throws Exception {
-        // This clip has 2 I frames at 66687us and 4299687us.
-        if (!loadResource(
-                R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz)) {
-            fail();
-        }
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-
-        final long seekPosMs = 3000;
-        final long timeToleranceMs = 100;
-        final long syncTime1Ms = 67;
-        final long syncTime2Ms = 4300;
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to previous sync or next sync.
-        long cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
-        assertTrue("MediaPlayer did not seek to closest position",
-                cp > seekPosMs && cp < syncTime2Ms);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or next sync.
-        cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to preivous sync position",
-                cp < seekPosMs - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to next sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to closest sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        mPlayer.reset();
-    }
-
-    private long runSeekMode(int seekMode, long seekPosMs) throws Exception {
-        final int sleepIntervalMs = 100;
-        int timeRemainedMs = 10000;  // total time for testing
-        final int timeToleranceMs = 100;
-
-        assertFutureSuccess(mPlayer.seekTo(seekPosMs, seekMode));
-
-        long cp = -seekPosMs;
-        while (timeRemainedMs > 0) {
-            cp = mPlayer.getCurrentPosition();
-            // Wait till MediaPlayer starts rendering since MediaPlayer caches
-            // seek position as current position.
-            if (cp < seekPosMs - timeToleranceMs || cp > seekPosMs + timeToleranceMs) {
-                break;
-            }
-            timeRemainedMs -= sleepIntervalMs;
-            Thread.sleep(sleepIntervalMs);
-        }
-        assertTrue("MediaPlayer did not finish seeking in time for mode " + seekMode,
-                timeRemainedMs > 0);
-        return cp;
-    }
-
-    @FlakyTest(bugId = 189489889)
-    @Test
-    @LargeTest
-    public void getTimestamp() throws Exception {
-        final int toleranceUs = 100000;
-        final float playbackRate = 1.0f;
-        if (!loadResource(R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            fail();
-        }
-
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        Future<PlayerResult> setParamsFuture = mPlayer.setPlaybackParams(
-                new PlaybackParams.Builder().setSpeed(playbackRate).build());
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        long nt1 = System.nanoTime();
-        MediaTimestamp ts1 = mPlayer.getTimestamp();
-        long nt2 = System.nanoTime();
-        assertNotNull("Media player should return a valid time stamp", ts1);
-        assertEquals("MediaPlayer had error in clockRate " + ts1.getMediaClockRate(),
-                playbackRate, ts1.getMediaClockRate(), FLOAT_TOLERANCE);
-        assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
-                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertFutureSuccess(setParamsFuture);
-
-        assertFutureSuccess(mPlayer.pause());
-
-        ts1 = mPlayer.getTimestamp();
-        assertNotNull("Media player should return a valid time stamp", ts1);
-        assertEquals("Media player should have play rate of 0.0f when paused",
-                0.0f, ts1.getMediaClockRate(), FLOAT_TOLERANCE);
-
-        Future<PlayerResult> seekFuture = mPlayer.seekTo(0, MediaPlayer.SEEK_PREVIOUS_SYNC);
-        playFuture = mPlayer.play();
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        int playTime = 4000;  // The testing clip is about 10 second long.
-        ts1 = mPlayer.getTimestamp();
-        assertNotNull("Media player should return a valid time stamp", ts1);
-        Thread.sleep(playTime);
-        MediaTimestamp ts2 = mPlayer.getTimestamp();
-        assertNotNull("Media player should return a valid time stamp", ts2);
-        assertEquals("The clockRate should not be changed.",
-                ts1.getMediaClockRate(), ts2.getMediaClockRate(), FLOAT_TOLERANCE);
-        assertEquals("MediaPlayer had error in timestamp.",
-                ts1.getAnchorMediaTimeUs() + (long) (playTime * ts1.getMediaClockRate() * 1000),
-                ts2.getAnchorMediaTimeUs(), toleranceUs);
-        assertFutureSuccess(seekFuture);
-        assertFutureSuccess(playFuture);
-
-        mPlayer.reset();
-    }
-
-    private void readTracks() {
-        mVideoTrackInfos.clear();
-        mAudioTrackInfos.clear();
-        mSubtitleTrackInfos.clear();
-        List<TrackInfo> trackInfos = mPlayer.getTracks();
-        if (trackInfos == null || trackInfos.size() == 0) {
-            return;
-        }
-
-        for (TrackInfo track : trackInfos) {
-            assertNotNull(track);
-            switch (track.getTrackType()) {
-                case TrackInfo.MEDIA_TRACK_TYPE_VIDEO:
-                    mVideoTrackInfos.add(track);
-                    break;
-                case TrackInfo.MEDIA_TRACK_TYPE_AUDIO:
-                    mAudioTrackInfos.add(track);
-                    break;
-                case TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
-                    mSubtitleTrackInfos.add(track);
-                    break;
-            }
-        }
-    }
-
-    private int selectSubtitleTrack(int index) throws Exception {
-        assertTrue(index < mSubtitleTrackInfos.size());
-        final TrackInfo track = mSubtitleTrackInfos.get(index);
-        Future<PlayerResult> future = mPlayer.selectTrack(track);
-        int result = future.get().getResultCode();
-        if (result == RESULT_SUCCESS) {
-            mSelectedTrack = track;
-        }
-        return result;
-    }
-
-    private int deselectSubtitleTrack(int index) throws Exception {
-        assertTrue(index < mSubtitleTrackInfos.size());
-        final TrackInfo track = mSubtitleTrackInfos.get(index);
-        Future<PlayerResult> future = mPlayer.deselectTrack(track);
-        int result = future.get().getResultCode();
-        if (result == RESULT_SUCCESS) {
-            mSelectedTrack = null;
-        }
-        return result;
-    }
-
-    @Test
-    @LargeTest
-    public void deselectTrackForSubtitleTracks() throws Throwable {
-        if (!loadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            fail();
-        }
-
-        mInstrumentation.waitForIdleSync();
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                if (track != null && data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull SessionPlayer player,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-
-        // Run twice to check if repeated selection-deselection on the same track works well.
-        for (int i = 0; i < 2; i++) {
-            // Waits until at least one subtitle is fired. Timeout is 2.5 seconds.
-            assertEquals(RESULT_SUCCESS, selectSubtitleTrack(i));
-            mOnSubtitleDataCalled.reset();
-            assertTrue(mOnSubtitleDataCalled.waitForSignal(2500));
-
-            // Try deselecting track.
-            assertEquals(RESULT_SUCCESS, deselectSubtitleTrack(i));
-            mOnSubtitleDataCalled.reset();
-            assertFalse(mOnSubtitleDataCalled.waitForSignal(1500));
-        }
-        // Deselecting unselected track: expected error status
-        assertNotEquals(RESULT_SUCCESS, deselectSubtitleTrack(0));
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void changeSubtitleTrack() throws Throwable {
-        if (!loadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            fail();
-        }
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                if (track != null && data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-
-            @Override
-            public void onTracksChanged(@NonNull SessionPlayer player,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-        assertEquals(mSelectedTrack, mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        // Waits until at least two captions are fired. Timeout is 2.5 sec.
-        assertEquals(RESULT_SUCCESS, selectSubtitleTrack(0));
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-        assertEquals(mSelectedTrack, mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        mOnSubtitleDataCalled.reset();
-        assertEquals(RESULT_SUCCESS, selectSubtitleTrack(1));
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-        assertEquals(mSelectedTrack, mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void getTracksForVideoWithSubtitleTracks() throws Throwable {
-        if (!loadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            fail();
-        }
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onTracksChanged(@NonNull SessionPlayer player,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 3) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // The media metadata will be changed while playing since closed caption tracks are in-band
-        // and those tracks will be found after processing a number of frames. These tracks will be
-        // found within one second.
-        assertTrue(mTracksFullyFound.waitForSignal(3000));
-
-        readTracks();
-        assertEquals(2, mSubtitleTrackInfos.size());
-
-        // Test isSelectable
-        assertFalse(mVideoTrackInfos.get(0).isSelectable());
-        assertTrue(mSubtitleTrackInfos.get(0).isSelectable());
-        assertTrue(mSubtitleTrackInfos.get(1).isSelectable());
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void getTracksForVideoWithoutSubtitleTracks() throws Throwable {
-        if (!loadResource(R.raw.testvideo)) {
-            fail();
-        }
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onTracksChanged(@NonNull SessionPlayer player,
-                    @NonNull List<TrackInfo> tracks) {
-                assertNotNull(tracks);
-                if (tracks.size() < 2) {
-                    // This callback can be called before tracks are available after setMediaItem.
-                    return;
-                }
-                mTracksFullyFound.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-
-        mTracksFullyFound.waitForSignal(1500);
-        assertFutureSuccess(prepareFuture);
-
-        readTracks();
-
-        // R.raw.testvideo contains the following tracks:
-        //  MEDIA_TRACK_TYPE_VIDEO: 1
-        //  MEDIA_TRACK_TYPE_AUDIO: 1
-        assertEquals(1, mVideoTrackInfos.size());
-        assertEquals(1, mAudioTrackInfos.size());
-        assertEquals(0, mSubtitleTrackInfos.size());
-
-        // Test isSelectable
-        assertFalse(mVideoTrackInfos.get(0).isSelectable());
-        assertTrue(mAudioTrackInfos.get(0).isSelectable());
-
-        // Test getSelectedTrack
-        assertEquals(mVideoTrackInfos.get(0),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_VIDEO));
-        assertEquals(mAudioTrackInfos.get(0),
-                mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_AUDIO));
-        assertNull(mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void mediaTimeDiscontinuity() throws Exception {
-        if (!loadResource(
-                R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz)) {
-            return; // skip
-        }
-
-        final BlockingDeque<MediaTimestamp> timestamps = new LinkedBlockingDeque<>();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onMediaTimeDiscontinuity(
-                    MediaPlayer mp, MediaItem dsd, MediaTimestamp timestamp) {
-                timestamps.add(timestamp);
-                mOnMediaTimeDiscontinuityCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-
-        // Timestamp needs to be reported when playback starts.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 1.0f) > 0.01f);
-
-        // Timestamp needs to be reported when seeking is done.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        assertFutureSuccess(mPlayer.seekTo(3000));
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 1.0f) > 0.01f);
-
-        // Timestamp needs to be updated when playback rate changes.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        assertFutureSuccess(mPlayer.setPlaybackParams(
-                new PlaybackParams.Builder().setSpeed(0.5f).build()));
-        mOnMediaTimeDiscontinuityCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 0.5f) > 0.01f);
-
-        // Timestamp needs to be updated when player is paused.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        assertFutureSuccess(mPlayer.pause());
-        mOnMediaTimeDiscontinuityCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (Math.abs(timestamps.getLast().getMediaClockRate() - 0.0f) > 0.01f);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @LargeTest
-    public void playbackFromDataSourceCallback() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final int duration = 10000;
-
-        TestDataSourceCallback dataSource =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        // Test returning -1 from getSize() to indicate unknown size.
-        dataSource.returnFromGetSize(-1);
-        Future<PlayerResult> setItemFuture = mPlayer.setMediaItem(
-                new CallbackMediaItem.Builder(dataSource).build());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // Test pause and restart.
-        assertFutureSuccess(mPlayer.pause());
-        assertNotEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        assertFutureSuccess(mPlayer.play());
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // Test reset.
-        mPlayer.reset();
-        setItemFuture = mPlayer.setMediaItem(new CallbackMediaItem.Builder(dataSource).build());
-        prepareFuture = mPlayer.prepare();
-        playFuture = mPlayer.play();
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(playFuture);
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        // Test seek. Note: the seek position is cached and returned as the
-        // current position so there's no point in comparing them.
-        assertFutureSuccess(mPlayer.seekTo(duration - SLEEP_TIME, MediaPlayer.SEEK_PREVIOUS_SYNC));
-        while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
-            Thread.sleep(SLEEP_TIME);
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void nullMedia2DataSourceIsRejected() throws Exception {
-        try {
-            assertNotNull(mPlayer.setMediaItem(null));
-            fail();
-        } catch (NullPointerException e) {
-            // Expected exception
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void media2DataSourceIsClosedOnReset() throws Exception {
-        TestDataSourceCallback dataSource = new TestDataSourceCallback(new byte[0]);
-        assertFutureSuccess(mPlayer.setMediaItem(
-                new CallbackMediaItem.Builder(dataSource).build()));
-        mPlayer.reset();
-        assertTrue(dataSource.isClosed());
-    }
-
-    @Test
-    @LargeTest
-    public void playbackFailsIfMedia2DataSourceThrows() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onError(
-                    MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        TestDataSourceCallback dataSource =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        // Ensure that we throw after reading enough data for preparation to complete.
-        dataSource.throwFromReadAtPosition(500_000);
-        Future<PlayerResult> setItemFuture = mPlayer.setMediaItem(
-                new CallbackMediaItem.Builder(dataSource).build());
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(prepareFuture);
-
-        mOnErrorCalled.reset();
-        assertFutureSuccess(mPlayer.play());
-        assertTrue(mOnErrorCalled.waitForSignal());
-    }
-
-    @Test
-    @LargeTest
-    public void preservePlaybackProperties() throws Exception {
-        final int resid1 = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final long start1 = 6000;
-        final long end1 = 7000;
-        MediaItem dsd1;
-        try (AssetFileDescriptor afd1 = mResources.openRawResourceFd(resid1)) {
-            dsd1 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd1.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd1.getStartOffset())
-                    .setFileDescriptorLength(afd1.getLength())
-                    .setStartPosition(start1)
-                    .setEndPosition(end1)
-                    .build();
-        }
-
-        final int resid2 = R.raw.testvideo;
-        final long start2 = 3000;
-        final long end2 = 4000;
-        MediaItem dsd2;
-        try (AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid2)) {
-            dsd2 = new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd2.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd2.getStartOffset())
-                    .setFileDescriptorLength(afd2.getLength())
-                    .setStartPosition(start2)
-                    .setEndPosition(end2)
-                    .build();
-        }
-
-        List<MediaItem> items = new ArrayList<>();
-        items.add(dsd1);
-        items.add(dsd2);
-        Future<PlayerResult> setListFuture = mPlayer.setPlaylist(items, null);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-
-        final Monitor onCompletionCalled = new Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull SessionPlayer player) {
-                onCompletionCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setListFuture);
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-
-        Future<PlayerResult> setParamsFuture = mPlayer.setPlaybackParams(
-                new PlaybackParams.Builder().setSpeed(2.0f).build());
-        Future<PlayerResult> playFuture = mPlayer.play();
-        assertFutureSuccess(setParamsFuture);
-        assertFutureSuccess(playFuture);
-
-        onCompletionCalled.waitForSignal();
-        assertEquals(dsd2, mPlayer.getCurrentMediaItem());
-        assertEquals(2.0f, mPlayer.getPlaybackParams().getSpeed(), 0.001f);
-    }
-
-    @Test
-    @MediumTest
-    public void defaultPlaybackParams() throws Exception {
-        if (!loadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
-            fail();
-        }
-        assertFutureSuccess(mPlayer.prepare());
-
-        PlaybackParams playbackParams = mPlayer.getPlaybackParams();
-        assertEquals(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT,
-                (int) playbackParams.getAudioFallbackMode());
-        assertEquals(1.0f, playbackParams.getPitch(), 0.001f);
-        assertEquals(1.0f, playbackParams.getSpeed(), 0.001f);
-
-        mPlayer.reset();
-    }
-
-    @Test
-    @MediumTest
-    public void skipUnnecessarySeek() throws Exception {
-        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-        final TestDataSourceCallback source =
-                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
-        final Monitor readAllowed = new Monitor();
-        DataSourceCallback dataSource = new DataSourceCallback() {
-            @Override
-            public int readAt(long position, byte[] buffer, int offset, int size)
-                    throws IOException {
-                if (!readAllowed.isSignalled()) {
-                    try {
-                        readAllowed.waitForSignal();
-                    } catch (InterruptedException e) {
-                        fail();
-                    }
-                }
-                return source.readAt(position, buffer, offset, size);
-            }
-
-            @Override
-            public long getSize() throws IOException {
-                return source.getSize();
-            }
-
-            @Override
-            public void close() throws IOException {
-                source.close();
-            }
-        };
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onError(MediaPlayer mp, MediaItem dsd, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setItemFuture = mPlayer.setMediaItem(
-                new CallbackMediaItem.Builder(dataSource).build());
-
-        mOnErrorCalled.reset();
-
-        // prepare() will be pending until readAllowed is signaled.
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-
-        Future<PlayerResult> seekFuture1 = mPlayer.seekTo(3000);
-        Future<PlayerResult> seekFuture2 = mPlayer.seekTo(2000);
-        Future<PlayerResult> seekFuture3 = mPlayer.seekTo(1000);
-
-        readAllowed.signal();
-
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureStateEquals(seekFuture1, RESULT_INFO_SKIPPED);
-        assertFutureStateEquals(seekFuture2, RESULT_INFO_SKIPPED);
-        assertFutureSuccess(seekFuture3);
-        assertFalse(mOnErrorCalled.isSignalled());
-    }
-
-    @Test
-    @LargeTest
-    public void playerStates() throws Throwable {
-        if (!loadResource(R.raw.testvideo)) {
-            fail();
-        }
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-
-        assertEquals(MediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-
-        assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
-                mPlayer.getBufferingState());
-        assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
-
-        assertFutureSuccess(mPlayer.play());
-
-        assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
-                mPlayer.getBufferingState());
-        assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
-
-        assertFutureSuccess(mPlayer.pause());
-
-        assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
-                mPlayer.getBufferingState());
-        assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
-
-        mPlayer.reset();
-        assertEquals(MediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-    }
-
-    @Test
-    @LargeTest
-    public void playerCallback() throws Throwable {
-        final int mp4Duration = 8484;
-        if (!loadResource(R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
-            return; // skip;
-        }
-
-        final TestUtils.Monitor onSeekCompleteCalled = new TestUtils.Monitor();
-        final TestUtils.Monitor onPlayerStateChangedCalled = new TestUtils.Monitor();
-        final AtomicInteger playerState = new AtomicInteger();
-        final TestUtils.Monitor onBufferingStateChangedCalled = new TestUtils.Monitor();
-        final AtomicInteger bufferingState = new AtomicInteger();
-        final TestUtils.Monitor onPlaybackSpeedChanged = new TestUtils.Monitor();
-        final AtomicReference<Float> playbackSpeed = new AtomicReference<>();
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) {
-                playerState.set(state);
-                onPlayerStateChangedCalled.signal();
-            }
-
-            @Override
-            public void onBufferingStateChanged(@NonNull SessionPlayer player, MediaItem item,
-                    int buffState) {
-                bufferingState.set(buffState);
-                onBufferingStateChangedCalled.signal();
-            }
-
-            @Override
-            public void onPlaybackSpeedChanged(@NonNull SessionPlayer player, float speed) {
-                playbackSpeed.set(speed);
-                onPlaybackSpeedChanged.signal();
-            }
-
-            @Override
-            public void onSeekCompleted(@NonNull SessionPlayer player, long position) {
-                onSeekCompleteCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-        Future<PlayerResult> setSurfaceFuture = mPlayer.setSurface(
-                mActivity.getSurfaceHolder().getSurface());
-
-        onPlayerStateChangedCalled.reset();
-        onBufferingStateChangedCalled.reset();
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        do {
-            assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
-        } while (bufferingState.get() != MediaPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED);
-
-        assertFutureSuccess(setSurfaceFuture);
-        assertFutureSuccess(prepareFuture);
-
-        do {
-            assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
-        } while (playerState.get() != MediaPlayer.PLAYER_STATE_PAUSED);
-        do {
-            assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
-        } while (bufferingState.get() != MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
-
-        onSeekCompleteCalled.reset();
-        assertFutureSuccess(mPlayer.seekTo(mp4Duration >> 1));
-        onSeekCompleteCalled.waitForSignal();
-
-        onPlaybackSpeedChanged.reset();
-        assertFutureSuccess(mPlayer.setPlaybackSpeed(0.5f));
-        do {
-            assertTrue(onPlaybackSpeedChanged.waitForSignal(1000));
-        } while (Math.abs(playbackSpeed.get() - 0.5f) > FLOAT_TOLERANCE);
-
-        mPlayer.reset();
-        assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
-
-        mPlayer.unregisterPlayerCallback(callback);
-    }
-
-    @Test
-    @LargeTest
-    public void setPlaybackSpeedWithIllegalArguments() throws Throwable {
-        // Zero is not allowed.
-        assertFutureStateEquals(mPlayer.setPlaybackSpeed(0.0f), RESULT_ERROR_BAD_VALUE);
-
-        // Negative values are not allowed.
-        assertFutureStateEquals(mPlayer.setPlaybackSpeed(-1.0f), RESULT_ERROR_BAD_VALUE);
-    }
-
-    @Test
-    @LargeTest
-    public void close() throws Exception {
-        assertTrue(loadResource(R.raw.testmp3_2));
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        assertNotNull(mPlayer.setAudioAttributes(attributes));
-        assertNotNull(mPlayer.prepare());
-        assertNotNull(mPlayer.play());
-        mPlayer.close();
-
-        // Set the player to null so we don't try to close it again in tearDown().
-        mPlayer = null;
-
-        // Tests whether the notification from the player after the close() doesn't crash.
-        Thread.sleep(SLEEP_TIME);
-    }
-
-    @Test
-    @LargeTest
-    public void reset() throws Exception {
-        assertTrue(loadResource(R.raw.testmp3_2));
-        AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .build();
-        assertNotNull(mPlayer.setAudioAttributes(attributes));
-
-        mPlayer.reset();
-
-        assertNull(mPlayer.getAudioAttributes());
-        assertNull(mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @LargeTest
-    public void cancelPendingCommands() throws Exception {
-        final Monitor readRequested = new Monitor();
-        final Monitor readAllowed = new Monitor();
-        DataSourceCallback dataSource = new DataSourceCallback() {
-            TestDataSourceCallback mTestSource = TestDataSourceCallback.fromAssetFd(
-                    mResources.openRawResourceFd(R.raw.testmp3));
-
-            @Override
-            public int readAt(long position, byte[] buffer, int offset, int size)
-                    throws IOException {
-                try {
-                    readRequested.signal();
-                    readAllowed.waitForSignal();
-                } catch (InterruptedException e) {
-                    fail();
-                }
-                return mTestSource.readAt(position, buffer, offset, size);
-            }
-
-            @Override
-            public long getSize() throws IOException {
-                return mTestSource.getSize();
-            }
-
-            @Override
-            public void close() throws IOException {
-                mTestSource.close();
-            }
-        };
-        MediaPlayer.PlayerCallback ecb = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onError(MediaPlayer mp, MediaItem item, int what, int extra) {
-                mOnErrorCalled.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, ecb);
-
-        mOnErrorCalled.reset();
-
-        Future<PlayerResult> setItemFuture = mPlayer.setMediaItem(
-                new CallbackMediaItem.Builder(dataSource).build());
-
-        // prepare() will be pending until readAllowed is signaled.
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-
-        Future<PlayerResult> seekFuture = mPlayer.seekTo(1000);
-        Future<PlayerResult> volumeFuture = mPlayer.setPlayerVolume(0.7f);
-
-        readRequested.waitForSignal();
-
-        // Cancel the pending commands while preparation is on hold.
-        seekFuture.cancel(false);
-        volumeFuture.cancel(false);
-
-        // Make the on-going prepare operation resumed and check the results.
-        readAllowed.signal();
-        assertFutureSuccess(setItemFuture);
-        assertFutureSuccess(prepareFuture);
-        assertFutureSuccess(mPlayer.setSurface(null));
-
-        assertEquals(0 /* default value */, mPlayer.getCurrentPosition());
-        assertEquals(1.0f /* default value */, mPlayer.getPlayerVolume(), 0.001f);
-
-        assertEquals(0, mOnErrorCalled.getNumSignal());
-    }
-
-    @Test
-    @MediumTest
-    public void setAndGetShuffleMode() throws Exception {
-        final TestUtils.Monitor onShuffleModeChangedMonitor = new TestUtils.Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onShuffleModeChanged(@NonNull SessionPlayer player, int shuffleMode) {
-                mPlayerCbArg1 = player;
-                mPlayerCbArg2 = new Integer(shuffleMode);
-                onShuffleModeChangedMonitor.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        int shuffleMode = mPlayer.getShuffleMode();
-        if (shuffleMode != SessionPlayer.SHUFFLE_MODE_NONE) {
-            onShuffleModeChangedMonitor.reset();
-            assertFutureSuccess(mPlayer.setShuffleMode(SessionPlayer.SHUFFLE_MODE_NONE));
-            assertTrue(onShuffleModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-            assertEquals(mPlayer, mPlayerCbArg1);
-            assertEquals(SessionPlayer.SHUFFLE_MODE_NONE, ((Integer) mPlayerCbArg2).intValue());
-            assertEquals(SessionPlayer.SHUFFLE_MODE_NONE, mPlayer.getShuffleMode());
-        }
-
-        onShuffleModeChangedMonitor.reset();
-        assertFutureSuccess(mPlayer.setShuffleMode(SessionPlayer.SHUFFLE_MODE_ALL));
-        assertTrue(onShuffleModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.SHUFFLE_MODE_ALL, ((Integer) mPlayerCbArg2).intValue());
-        assertEquals(SessionPlayer.SHUFFLE_MODE_ALL, mPlayer.getShuffleMode());
-
-        onShuffleModeChangedMonitor.reset();
-        assertFutureSuccess(mPlayer.setShuffleMode(SessionPlayer.SHUFFLE_MODE_GROUP));
-        assertTrue(onShuffleModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.SHUFFLE_MODE_GROUP, ((Integer) mPlayerCbArg2).intValue());
-        assertEquals(SessionPlayer.SHUFFLE_MODE_GROUP, mPlayer.getShuffleMode());
-
-        // INVALID_SHUFFLE_MODE will not change the shuffle mode.
-        onShuffleModeChangedMonitor.reset();
-        assertFutureStateEquals(mPlayer.setShuffleMode(INVALID_SHUFFLE_MODE),
-                RESULT_ERROR_BAD_VALUE);
-        assertFalse(onShuffleModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.SHUFFLE_MODE_GROUP, mPlayer.getShuffleMode());
-    }
-
-    @Test
-    @MediumTest
-    public void setAndGetRepeatMode() throws Exception {
-        final TestUtils.Monitor onRepeatModeChangedMonitor = new TestUtils.Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onRepeatModeChanged(@NonNull SessionPlayer player, int repeatMode) {
-                mPlayerCbArg1 = player;
-                mPlayerCbArg2 = new Integer(repeatMode);
-                onRepeatModeChangedMonitor.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        int repeatMode = mPlayer.getRepeatMode();
-        if (repeatMode != SessionPlayer.REPEAT_MODE_NONE) {
-            onRepeatModeChangedMonitor.reset();
-            assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_NONE));
-            assertTrue(onRepeatModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-            assertEquals(mPlayer, mPlayerCbArg1);
-            assertEquals(SessionPlayer.REPEAT_MODE_NONE, ((Integer) mPlayerCbArg2).intValue());
-            assertEquals(SessionPlayer.REPEAT_MODE_NONE, mPlayer.getRepeatMode());
-        }
-
-        onRepeatModeChangedMonitor.reset();
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_ALL));
-        assertTrue(onRepeatModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.REPEAT_MODE_ALL, ((Integer) mPlayerCbArg2).intValue());
-        assertEquals(SessionPlayer.REPEAT_MODE_ALL, mPlayer.getRepeatMode());
-
-        onRepeatModeChangedMonitor.reset();
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_GROUP));
-        assertTrue(onRepeatModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.REPEAT_MODE_GROUP, ((Integer) mPlayerCbArg2).intValue());
-        assertEquals(SessionPlayer.REPEAT_MODE_GROUP, mPlayer.getRepeatMode());
-
-        // INVALID_REPEAT_MODE will not change the repeat mode.
-        onRepeatModeChangedMonitor.reset();
-        assertFutureStateEquals(mPlayer.setRepeatMode(INVALID_REPEAT_MODE), RESULT_ERROR_BAD_VALUE);
-        assertFalse(onRepeatModeChangedMonitor.waitForSignal(WAIT_TIME_MS));
-        assertEquals(mPlayer, mPlayerCbArg1);
-        assertEquals(SessionPlayer.REPEAT_MODE_GROUP, mPlayer.getRepeatMode());
-    }
-
-    @Test
-    @MediumTest
-    public void setPlaylist() throws Exception {
-        List<MediaItem> playlist = createPlaylist(10);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        try {
-            assertNotNull(mPlayer.setPlaylist(null, null));
-            fail();
-        } catch (Exception e) {
-            // pass-through
-        }
-        try {
-            List<MediaItem> list = new ArrayList<>();
-            list.add(null);
-            assertNotNull(mPlayer.setPlaylist(list, null));
-            fail();
-        } catch (Exception e) {
-            // pass-through
-        }
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(playlist.size(), mPlayer.getPlaylist().size());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void setFileMediaItem() throws Exception {
-        MediaItem closedItem = createMediaItem();
-        ((FileMediaItem) closedItem).close();
-        try {
-            assertNotNull(mPlayer.setMediaItem(closedItem));
-            fail();
-        } catch (Exception e) {
-            // Expected.
-        }
-
-        final List<MediaItem> closedPlaylist = createPlaylist(1);
-        ((FileMediaItem) closedPlaylist.get(0)).close();
-        try {
-            assertNotNull(mPlayer.setPlaylist(closedPlaylist, null));
-            fail();
-        } catch (Exception e) {
-            // Expected.
-        }
-
-        List<MediaItem> playlist = createPlaylist(2);
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-
-        try {
-            assertNotNull(mPlayer.addPlaylistItem(0, closedItem));
-            fail();
-        } catch (Exception e) {
-            // Expected.
-        }
-
-        try {
-            assertNotNull(mPlayer.replacePlaylistItem(0, closedItem));
-            fail();
-        } catch (Exception e) {
-            // Expected.
-        }
-
-        List<MediaItem> reversedList = new ArrayList<>(
-                Arrays.asList(playlist.get(1), playlist.get(0)));
-        assertFutureSuccess(mPlayer.setPlaylist(reversedList, null));
-    }
-
-    @Test
-    @MediumTest
-    public void playlistModification() throws Exception {
-        final TestUtils.Monitor playlistChangeMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor, new SessionPlayer.PlayerCallback() {
-            public void onPlaylistChanged(@NonNull SessionPlayer player,
-                    @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
-                playlistChangeMonitor.signal();
-            }
-        });
-
-        List<MediaItem> playlist = createPlaylist(3);
-        MediaItem item0 = playlist.get(0);
-        MediaItem item1 = playlist.get(1);
-        MediaItem item2 = playlist.get(2);
-        MediaItem item3 = createMediaItem();
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertTrue(playlistChangeMonitor.waitForSignal(WAIT_TIME_MS));
-        playlistChangeMonitor.reset();
-        // mPlayer's playlist will be [0 (current) 1 2]
-        assertEquals(playlist.size(), mPlayer.getPlaylist().size());
-        assertEquals(item0, mPlayer.getCurrentMediaItem());
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(1, mPlayer.getNextMediaItemIndex());
-
-        assertFutureSuccess(mPlayer.addPlaylistItem(0, item3));
-        assertTrue(playlistChangeMonitor.waitForSignal(WAIT_TIME_MS));
-        playlistChangeMonitor.reset();
-        // mPlayer's playlist will be [3 0 (current) 1 2]
-        assertEquals(playlist.size() + 1, mPlayer.getPlaylist().size());
-        assertEquals(item0, mPlayer.getCurrentMediaItem());
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(2, mPlayer.getNextMediaItemIndex());
-
-        assertFutureSuccess(mPlayer.removePlaylistItem(1));
-        assertTrue(playlistChangeMonitor.waitForSignal(WAIT_TIME_MS));
-        playlistChangeMonitor.reset();
-        // mPlayer's playlist will be [3 1 (current) 2]
-        assertEquals(playlist.size(), mPlayer.getPlaylist().size());
-        assertEquals(item1, mPlayer.getCurrentMediaItem());
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(2, mPlayer.getNextMediaItemIndex());
-
-        assertFutureSuccess(mPlayer.movePlaylistItem(1, 0));
-        assertTrue(playlistChangeMonitor.waitForSignal(WAIT_TIME_MS));
-        playlistChangeMonitor.reset();
-        // mPlayer's playlist will be [1 (current), 3, 2]
-        assertEquals(playlist.size(), mPlayer.getPlaylist().size());
-        assertEquals(item1, mPlayer.getCurrentMediaItem());
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(1, mPlayer.getNextMediaItemIndex());
-
-        assertFutureSuccess(mPlayer.skipToNextPlaylistItem());
-        // mPlayer's playlist will be [1, 3 (current), 2]
-        assertEquals(playlist.size(), mPlayer.getPlaylist().size());
-        assertEquals(item3, mPlayer.getCurrentMediaItem());
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(2, mPlayer.getNextMediaItemIndex());
-    }
-
-    @Test
-    @MediumTest
-    public void setPlaylistAndRemoveAll_playerShouldMoveIdleState() throws Exception {
-        List<MediaItem> playlist = new ArrayList<>();
-        playlist.add(createMediaItem(R.raw.number1));
-
-        final TestUtils.Monitor onPlayerStateChangedCalled = new TestUtils.Monitor();
-        final AtomicInteger playerState = new AtomicInteger();
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) {
-                playerState.set(state);
-                onPlayerStateChangedCalled.signal();
-            }
-        };
-
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        assertNotNull(mPlayer.setPlaylist(playlist, null));
-        assertNotNull(mPlayer.prepare());
-        do {
-            assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
-        } while (playerState.get() != MediaPlayer.PLAYER_STATE_PAUSED);
-
-        mPlayer.removePlaylistItem(0);
-        do {
-            assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
-        } while (playerState.get() != MediaPlayer.PLAYER_STATE_IDLE);
-    }
-
-    @Test
-    @MediumTest
-    public void skipToPlaylistItems() throws Exception {
-        int listSize = 5;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_NONE));
-
-        // Test skipToPlaylistItem
-        for (int i = listSize - 1; i >= 0; --i) {
-            assertFutureSuccess(mPlayer.skipToPlaylistItem(i));
-            assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-            assertEquals(i, mPlayer.getCurrentMediaItemIndex());
-            assertEquals(playlist.get(i), mPlayer.getCurrentMediaItem());
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void skipToNextItems() throws Exception {
-        int listSize = 5;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_NONE));
-
-        // Test skipToNextPlaylistItem
-        // curPlayPos = 0
-        for (int curPlayPos = 0; curPlayPos < listSize - 1; ++curPlayPos) {
-            assertFutureSuccess(mPlayer.skipToNextPlaylistItem());
-            assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-            assertEquals(curPlayPos + 1, mPlayer.getCurrentMediaItemIndex());
-            assertEquals(playlist.get(curPlayPos + 1), mPlayer.getCurrentMediaItem());
-        }
-        assertFutureStateEquals(mPlayer.skipToNextPlaylistItem(), RESULT_ERROR_INVALID_STATE);
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, false);
-        assertEquals(listSize - 1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(listSize - 1), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void skipToPreviousItems() throws Exception {
-        int listSize = 5;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_NONE));
-
-        assertFutureSuccess(mPlayer.skipToPlaylistItem(listSize - 1));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-
-        // Test skipToPrevious
-        // curPlayPos = listSize - 1
-        for (int curPlayPos = listSize - 1; curPlayPos > 0; --curPlayPos) {
-            assertFutureSuccess(mPlayer.skipToPreviousPlaylistItem());
-            assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-            assertEquals(curPlayPos - 1, mPlayer.getCurrentMediaItemIndex());
-            assertEquals(playlist.get(curPlayPos - 1), mPlayer.getCurrentMediaItem());
-        }
-        assertFutureStateEquals(mPlayer.skipToPreviousPlaylistItem(), RESULT_ERROR_INVALID_STATE);
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, false);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void skipToNextPreviousItemsWithRepeatMode() throws Exception {
-        int listSize = 5;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_ALL));
-
-        assertEquals(listSize - 1, mPlayer.getPreviousMediaItemIndex());
-        assertFutureSuccess(mPlayer.skipToPreviousPlaylistItem());
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(listSize - 1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(listSize - 1), mPlayer.getCurrentMediaItem());
-
-        assertEquals(0, mPlayer.getNextMediaItemIndex());
-        assertFutureSuccess(mPlayer.skipToNextPlaylistItem());
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void playlistAfterSkipToNextItem() throws Exception {
-        int listSize = 2;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-
-        assertFutureSuccess(mPlayer.skipToNextPlaylistItem());
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(1), mPlayer.getCurrentMediaItem());
-
-        // Will not go to the next if the next is end of the playlist
-        assertFutureStateEquals(mPlayer.skipToNextPlaylistItem(), RESULT_ERROR_INVALID_STATE);
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, false);
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(1), mPlayer.getCurrentMediaItem());
-
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_ALL));
-        assertFutureSuccess(mPlayer.skipToNextPlaylistItem());
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void playlistAfterSkipToPreviousItem() throws Exception {
-        int listSize = 2;
-        List<MediaItem> playlist = createPlaylist(listSize);
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-
-        // Will not go to the previous if the current is the first one
-        assertFutureStateEquals(mPlayer.skipToPreviousPlaylistItem(), RESULT_ERROR_INVALID_STATE);
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, false);
-        assertEquals(0, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-
-        assertFutureSuccess(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_ALL));
-        assertFutureSuccess(mPlayer.skipToPreviousPlaylistItem());
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-        assertEquals(1, mPlayer.getCurrentMediaItemIndex());
-        assertEquals(playlist.get(1), mPlayer.getCurrentMediaItem());
-    }
-
-    @Test
-    @MediumTest
-    public void currentMediaItemChangedCalledAfterSetMediaItem() throws Exception {
-        final int currentIdx = -1;
-        MediaItem item1 = createMediaItem();
-        MediaItem item2 = createMediaItem();
-
-        final TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                    @NonNull MediaItem item) {
-                assertEquals(currentIdx, mPlayer.getCurrentMediaItemIndex());
-                onCurrentMediaItemChangedMonitor.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        assertFutureSuccess(mPlayer.setMediaItem(item1));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-
-        // Test if multiple calls to setMediaItem calls onCurrentMediaItemChanged.
-        assertFutureSuccess(mPlayer.setMediaItem(item2));
-        assertWaitForSignalAndReset(onCurrentMediaItemChangedMonitor, true);
-    }
-
-    @Test
-    @MediumTest
-    public void currentMediaItemChangedCalledAfterSetPlaylist() throws Exception {
-        int listSize = 2;
-        List<MediaItem> playlist = createPlaylist(listSize);
-
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor,
-                new PlayerCallbackForPlaylist(playlist, onCurrentMediaItemChangedMonitor));
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, null));
-        assertTrue(onCurrentMediaItemChangedMonitor.waitForSignal(WAIT_TIME_MS));
-    }
-
-    @Test
-    @LargeTest
-    public void currentMediaItemChangedAndPlaybackCompletedWhilePlayingPlaylist()
-            throws Exception {
-        List<MediaItem> playlist = new ArrayList<>();
-        playlist.add(createMediaItem(R.raw.number1));
-        playlist.add(createMediaItem(R.raw.number2));
-        playlist.add(createMediaItem(R.raw.number3));
-
-        TestUtils.Monitor onPlaybackCompletedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor, new SessionPlayer.PlayerCallback() {
-            int mCurrentMediaItemChangedCount = 0;
-
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                    @NonNull MediaItem item) {
-                assertEquals(player.getCurrentMediaItem(), item);
-
-                int currentIdx = player.getCurrentMediaItemIndex();
-                int expectedCurrentIdx = mCurrentMediaItemChangedCount++;
-                assertEquals(expectedCurrentIdx, currentIdx);
-                assertEquals(playlist.get(expectedCurrentIdx), item);
-            }
-
-            @Override
-            public void onPlaybackCompleted(@NonNull SessionPlayer player) {
-                onPlaybackCompletedMonitor.signal();
-            }
-        });
-
-        assertNotNull(mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface()));
-        assertNotNull(mPlayer.setPlaylist(playlist, null));
-        assertNotNull(mPlayer.prepare());
-        assertNotNull(mPlayer.play());
-
-        assertTrue(onPlaybackCompletedMonitor.waitForSignal(LARGE_TEST_WAIT_TIME_MS));
-    }
-
-    @Ignore("Test disabled due to flakiness, see b/189898969")
-    @Test
-    @LargeTest
-    public void repeatAll() throws Exception {
-        List<MediaItem> playlist = new ArrayList<>();
-        playlist.add(createMediaItem(R.raw.number1));
-        playlist.add(createMediaItem(R.raw.number2));
-        playlist.add(createMediaItem(R.raw.number3));
-        int listSize = playlist.size();
-        int waitForCurrentMediaChangeCount = listSize * 2;
-
-        TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
-        TestUtils.Monitor onPlaybackCompletedMonitor = new TestUtils.Monitor();
-        PlayerCallbackForPlaylist callback = new PlayerCallbackForPlaylist(
-                playlist, onCurrentMediaItemChangedMonitor) {
-            int mCurrentMediaItemChangedCount = 0;
-
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                    @NonNull MediaItem item) {
-                super.onCurrentMediaItemChanged(player, item);
-
-                int expectedCurrentIdx = (mCurrentMediaItemChangedCount++) % listSize;
-                assertEquals(expectedCurrentIdx, player.getCurrentMediaItemIndex());
-            }
-
-            @Override
-            public void onPlaybackCompleted(@NonNull SessionPlayer player) {
-                onPlaybackCompletedMonitor.signal();
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        assertNotNull(mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface()));
-        assertNotNull(mPlayer.setPlaylist(playlist, null));
-        assertNotNull(mPlayer.prepare());
-        assertNotNull(mPlayer.setRepeatMode(SessionPlayer.REPEAT_MODE_ALL));
-        assertNotNull(mPlayer.play());
-
-        assertEquals(waitForCurrentMediaChangeCount,
-                onCurrentMediaItemChangedMonitor.waitForCountedSignals(
-                        waitForCurrentMediaChangeCount, LARGE_TEST_WAIT_TIME_MS));
-        assertEquals(0, onPlaybackCompletedMonitor.getNumSignal());
-    }
-
-    @Ignore("Test disabled due to flakiness, see b/144876689")
-    @Test
-    @LargeTest
-    public void onVideoSizeChanged() throws Exception {
-        List<MediaItem> playlist = new ArrayList<>();
-        playlist.add(createMediaItem(R.raw.testvideo));
-        playlist.add(createMediaItem(R.raw.testmp3));
-        playlist.add(createMediaItem(R.raw.testvideo_with_2_subtitle_tracks));
-
-        VideoSize sizeFor1stItem = new VideoSize(352, 288);
-        VideoSize sizeFor2ndItem = new VideoSize(0, 0);
-        VideoSize sizeFor3rdItem = new VideoSize(160, 90);
-
-        CountDownLatch latchFor1stItem = new CountDownLatch(1);
-        CountDownLatch latchFor2ndItem = new CountDownLatch(1);
-        CountDownLatch latchFor3rdItem = new CountDownLatch(1);
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onVideoSizeChanged(@NonNull SessionPlayer player,
-                    @NonNull androidx.media2.common.VideoSize size) {
-                if (latchFor1stItem.getCount() > 0) {
-                    assertEquals(sizeFor1stItem, size);
-                    latchFor1stItem.countDown();
-                } else if (latchFor2ndItem.getCount() > 0) {
-                    assertEquals(sizeFor2ndItem, size);
-                    latchFor2ndItem.countDown();
-                } else if (latchFor3rdItem.getCount() > 0) {
-                    assertEquals(sizeFor3rdItem, size);
-                    latchFor3rdItem.countDown();
-                }
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        assertNotNull(mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface()));
-        assertNotNull(mPlayer.setPlaylist(playlist, null));
-        assertNotNull(mPlayer.prepare());
-        assertNotNull(mPlayer.play());
-
-        assertTrue(latchFor1stItem.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertNotNull(mPlayer.skipToNextPlaylistItem());
-        assertTrue(latchFor2ndItem.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertNotNull(mPlayer.skipToNextPlaylistItem());
-        assertTrue(latchFor3rdItem.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("Test disabled due to flakiness, see b/144972397")
-    @Test
-    @LargeTest
-    public void seekToEndOfMediaItem() throws Exception {
-        List<MediaItem> playlist = new ArrayList<>();
-        playlist.add(createMediaItem(R.raw.testmp3));
-        playlist.add(createMediaItem(R.raw.testmp3));
-        playlist.add(createMediaItem(R.raw.testmp3));
-
-        List<CountDownLatch> latches = new ArrayList<>();
-        for (int i = 0; i < playlist.size(); i++) {
-            latches.add(new CountDownLatch(1));
-        }
-
-        MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                    @NonNull MediaItem item) {
-                for (int i = 0; i < playlist.size(); i++) {
-                    if (playlist.get(i) == item) {
-                        latches.get(i).countDown();
-                        break;
-                    }
-                }
-            }
-        };
-        mPlayer.registerPlayerCallback(mExecutor, callback);
-
-        assertNotNull(mPlayer.setPlaylist(playlist, null));
-        assertNotNull(mPlayer.prepare());
-        assertFutureSuccess(mPlayer.play());
-
-        for (int i = 0; i < playlist.size(); i++) {
-            assertTrue("onCurrentMediaItemChanged is not called for item i=" + i,
-                    latches.get(i).await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            if (i + 1 < playlist.size()) {
-                long duration = mPlayer.getDuration();
-                assertNotEquals(SessionPlayer.UNKNOWN_TIME, duration);
-                assertFutureSuccess(mPlayer.seekTo(duration));
-            }
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void onPlaylistChangedCalledAfterSetMediaItem() throws Exception {
-        List<MediaItem> playlist = createPlaylist(3);
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_GENRE, TEST_PLAYLIST_GENRE)
-                .build();
-        int listSize = playlist.size();
-
-        TestUtils.Monitor onPlaylistChangedMonitor = new TestUtils.Monitor();
-        mPlayer.registerPlayerCallback(mExecutor, new MediaPlayer.PlayerCallback() {
-            public void onPlaylistChanged(SessionPlayer player, List<MediaItem> list,
-                    MediaMetadata metadata) {
-                switch (onPlaylistChangedMonitor.getNumSignal()) {
-                    case 0:
-                        assertEquals(listSize, list.size());
-                        for (int i = 0; i < listSize; i++) {
-                            assertEquals(playlist.get(i), list.get(i));
-                        }
-                        assertEquals(TEST_PLAYLIST_GENRE,
-                                metadata.getText(MediaMetadata.METADATA_KEY_GENRE));
-                        break;
-                    case 1:
-                        assertNull(list);
-                        assertNull(metadata);
-                        break;
-                }
-                onPlaylistChangedMonitor.signal();
-            }
-        });
-
-        assertFutureSuccess(mPlayer.setPlaylist(playlist, playlistMetadata));
-        assertEquals(1, onPlaylistChangedMonitor.waitForCountedSignals(1, WAIT_TIME_MS));
-        List<MediaItem> playerPlaylist = mPlayer.getPlaylist();
-        assertEquals(listSize, playerPlaylist.size());
-        for (int i = 0; i < listSize; i++) {
-            assertEquals(playlist.get(i), playerPlaylist.get(i));
-        }
-        assertEquals(TEST_PLAYLIST_GENRE,
-                mPlayer.getPlaylistMetadata().getText(MediaMetadata.METADATA_KEY_GENRE));
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-
-        assertFutureSuccess(mPlayer.setMediaItem(playlist.get(0)));
-        assertEquals(2, onPlaylistChangedMonitor.waitForCountedSignals(2, WAIT_TIME_MS));
-        assertNull(mPlayer.getPlaylist());
-        assertNull(mPlayer.getPlaylistMetadata());
-        assertEquals(playlist.get(0), mPlayer.getCurrentMediaItem());
-    }
-
-    private MediaItem createMediaItem() throws Exception {
-        return createMediaItem(R.raw.testvideo);
-    }
-
-    private MediaItem createMediaItem(int resId) throws Exception {
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resId)) {
-            return new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build();
-        }
-    }
-
-    private List<MediaItem> createPlaylist(int size) throws Exception {
-        List<MediaItem> items = new ArrayList<>();
-        for (int i = 0; i < size; ++i) {
-            items.add(createMediaItem());
-        }
-        return items;
-    }
-
-    private class PlayerCallbackForPlaylist extends MediaPlayer.PlayerCallback {
-        private List<MediaItem> mPlaylist;
-        private TestUtils.Monitor mOnCurrentMediaItemChangedMonitor;
-
-        PlayerCallbackForPlaylist(List<MediaItem> playlist, TestUtils.Monitor monitor) {
-            mPlaylist = playlist;
-            mOnCurrentMediaItemChangedMonitor = monitor;
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
-                @NonNull MediaItem item) {
-            int currentIdx = mPlaylist.indexOf(item);
-            assertEquals(currentIdx, mPlayer.getCurrentMediaItemIndex());
-            mOnCurrentMediaItemChangedMonitor.signal();
-        }
-    }
-
-    private void assertWaitForSignalAndReset(TestUtils.Monitor monitor, boolean assertTrue)
-            throws Exception {
-        if (assertTrue) {
-            assertTrue(monitor.waitForSignal(WAIT_TIME_MS));
-        } else {
-            assertFalse(monitor.waitForSignal(WAIT_TIME_MS));
-        }
-        monitor.reset();
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTestBase.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTestBase.java
deleted file mode 100644
index 5f3890f..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTestBase.java
+++ /dev/null
@@ -1,183 +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.media2.player;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.view.WindowManager;
-
-import androidx.annotation.CallSuper;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.UriMediaItem;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-/**
- * Base class for {@link MediaPlayer} tests.
- */
-abstract class MediaPlayerTestBase extends MediaTestBase {
-    private static final String TAG = "MediaPlayerTest";
-
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-
-    Context mContext;
-    Resources mResources;
-    ExecutorService mExecutor;
-
-    MediaPlayer mPlayer;
-    MediaStubActivity mActivity;
-    Instrumentation mInstrumentation;
-
-    List<AssetFileDescriptor> mFdsToClose = new ArrayList<>();
-
-    private void isBuggyDeviceForPlayback() {
-        if (Build.MODEL.contains("Android SDK built for arm64")) {
-            Assume.assumeTrue(
-                    "Emulators API 26 and 28 have a bug for playback b/272341857",
-                    Build.VERSION.SDK_INT != 26 && Build.VERSION.SDK_INT != 28
-            );
-        }
-    }
-
-    @Before
-    @CallSuper
-    public void setUp() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.getActivity();
-        setKeepScreenOn();
-
-        try {
-            mActivityRule.runOnUiThread(new Runnable() {
-                public void run() {
-                    mPlayer = new MediaPlayer(mActivity);
-                }
-            });
-        } catch (Throwable e) {
-            Log.e(TAG, "Failed to instantiate MediaPlayer", e);
-            fail(e.getMessage());
-        }
-        mContext = mActivityRule.getActivity();
-        mResources = mContext.getResources();
-        mExecutor = Executors.newFixedThreadPool(1);
-        isBuggyDeviceForPlayback();
-    }
-
-    @After
-    @CallSuper
-    public void tearDown() throws Exception {
-        if (mPlayer != null) {
-            mPlayer.close();
-            mPlayer = null;
-        }
-        if (mExecutor != null) {
-            mExecutor.shutdown();
-            mExecutor = null;
-        }
-        mActivity = null;
-        for (AssetFileDescriptor afd :  mFdsToClose) {
-            afd.close();
-        }
-        mFdsToClose.clear();
-    }
-
-    boolean loadResourceWithUri(int resId) throws Exception {
-        Uri testVideoUri = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                .authority(mResources.getResourcePackageName(resId))
-                .appendPath(mResources.getResourceTypeName(resId))
-                .appendPath(mResources.getResourceEntryName(resId))
-                .build();
-
-        return mPlayer.setMediaItem(new UriMediaItem.Builder(
-                testVideoUri).build()).get().getResultCode()
-                == SessionPlayer.PlayerResult.RESULT_SUCCESS;
-    }
-
-    boolean loadResource(int resid) throws Exception {
-        try (AssetFileDescriptor afd = mResources.openRawResourceFd(resid)) {
-            return mPlayer.setMediaItem(new FileMediaItem.Builder(
-                    ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                    .setFileDescriptorOffset(afd.getStartOffset())
-                    .setFileDescriptorLength(afd.getLength())
-                    .build()).get().getResultCode()
-                    == SessionPlayer.PlayerResult.RESULT_SUCCESS;
-        }
-    }
-
-    private void setKeepScreenOn() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (Build.VERSION.SDK_INT >= 27) {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                    mActivity.setTurnScreenOn(true);
-                    mActivity.setShowWhenLocked(true);
-                    KeyguardManager keyguardManager = (KeyguardManager)
-                            mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-                    keyguardManager.requestDismissKeyguard(mActivity, null);
-                } else {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-                }
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-
-    static <T extends SessionPlayer.PlayerResult> void assertFutureSuccess(Future<T> future)
-            throws Exception {
-        assertFutureStateEquals(future, SessionPlayer.PlayerResult.RESULT_SUCCESS);
-    }
-
-    static <T extends SessionPlayer.PlayerResult> void assertFutureStateEquals(Future<T> future,
-            int expectedResultCode) throws Exception {
-        assertNotNull(future);
-        SessionPlayer.PlayerResult result = future.get();
-        assertNotNull(result);
-        assertEquals(expectedResultCode, result.getResultCode());
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java
deleted file mode 100644
index 6636bb4..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java
+++ /dev/null
@@ -1,452 +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.media2.player;
-
-import static android.media.AudioManager.AUDIOFOCUS_GAIN;
-import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
-import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-import static android.media.AudioManager.AUDIOFOCUS_LOSS;
-import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
-import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
-import static android.media.AudioManager.AUDIOFOCUS_NONE;
-import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
-import static android.media.AudioManager.STREAM_MUSIC;
-
-import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
-import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_SPEECH;
-import static androidx.media.AudioAttributesCompat.USAGE_ALARM;
-import static androidx.media.AudioAttributesCompat.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
-import static androidx.media.AudioAttributesCompat.USAGE_GAME;
-import static androidx.media.AudioAttributesCompat.USAGE_MEDIA;
-import static androidx.media.AudioAttributesCompat.USAGE_UNKNOWN;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.os.Build.VERSION;
-import android.os.HandlerThread;
-import android.os.Looper;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.player.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.testutils.PollingCheck;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaPlayer} for audio focus and noisy intent handling.
- * <p>
- * This may be flaky test because another app including system component may take audio focus.
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaPlayer_AudioFocusTest extends MediaPlayerTestBase {
-    private static final int WAIT_TIME_MS = 2000;
-
-    static TestUtils.SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    private AudioManager mAudioManager;
-    private AudioFocusListener mAudioFocusListener;
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaPlayer_AudioFocusTest.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaPlayer_AudioFocusTest");
-            handlerThread.start();
-            sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    TestUtils.SyncHandler handler;
-                    synchronized (MediaPlayer_AudioFocusTest.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-            if (Looper.getMainLooper() == null) {
-                InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        Looper.prepareMainLooper();
-                    }
-                });
-            }
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaPlayer_AudioFocusTest.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @Before
-    @Override
-    public void setUp() throws Throwable {
-        super.setUp();
-
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mAudioFocusListener = null;
-    }
-
-    @After
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        abandonAudioFocus();
-    }
-
-    private AudioAttributesCompat createAudioAttributes(int contentType, int usage) {
-        return new AudioAttributesCompat.Builder()
-                .setContentType(contentType).setUsage(usage).build();
-    }
-
-    private void sendNoisyIntent(MediaPlayer player) {
-        // We cannot use Context.sendBroadcast() because it throws SecurityException for such
-        // framework related intent.
-        Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
-        player.mAudioFocusHandler.sendIntent(intent);
-    }
-
-    private void initPlayer(AudioAttributesCompat attr) throws Exception {
-        loadResource(R.raw.loudsoftogg);
-        Future<PlayerResult> setAttrFuture = mPlayer.setAudioAttributes(attr);
-        Future<PlayerResult> prepareFuture = mPlayer.prepare();
-        assertFutureSuccess(setAttrFuture);
-        assertFutureSuccess(prepareFuture);
-    }
-
-    private void testPausedAfterAction(final AudioAttributesCompat attr,
-            final PlayerRunnable action) throws Exception {
-        final CountDownLatch latchForPlaying = new CountDownLatch(1);
-        final CountDownLatch latchForPaused = new CountDownLatch(1);
-        initPlayer(attr);
-
-        mPlayer.registerPlayerCallback(sHandlerExecutor, new SessionPlayer.PlayerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull SessionPlayer mPlayer, int playerState) {
-                    switch (playerState) {
-                        case SessionPlayer.PLAYER_STATE_PLAYING:
-                            latchForPlaying.countDown();
-                            break;
-                        case SessionPlayer.PLAYER_STATE_PAUSED:
-                            latchForPaused.countDown();
-                            break;
-                    }
-                }
-            });
-
-        // Play here for registering noisy intent.
-        assertFutureSuccess(mPlayer.play());
-        // Playback becomes PLAYING needs to be propagated to the session and its focus handler.
-        // Wait for a while for that.
-        assertTrue(latchForPlaying.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        // Ensures that it hasn't paused yet.
-        assertTrue(latchForPaused.getCount() > 0);
-
-        // Do something that would pause playback.
-        action.run(mPlayer);
-
-        // Wait until pause actually taking effect.
-        assertTrue(latchForPaused.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void testDuckedAfterAction(final AudioAttributesCompat attr,
-            final PlayerRunnable action) throws Exception {
-        final CountDownLatch latchForPlaying = new CountDownLatch(1);
-
-        initPlayer(attr);
-        mPlayer.registerPlayerCallback(sHandlerExecutor, new SessionPlayer.PlayerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull SessionPlayer player, int playerState) {
-                if (playerState == SessionPlayer.PLAYER_STATE_PLAYING) {
-                    latchForPlaying.countDown();
-                }
-            }
-        });
-        assertFutureSuccess(mPlayer.play());
-        // Playback becomes PLAYING needs to be propagated to the session and its focus handler.
-        // Wait for a while for that.
-        assertTrue(latchForPlaying.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        final float curVolume = mPlayer.getPlayerVolume();
-        // Do something that would pause playback.
-        action.run(mPlayer);
-
-        new PollingCheck(WAIT_TIME_MS) {
-            @Override
-            protected boolean check() {
-                return mPlayer.getPlayerVolume() < curVolume || curVolume == 0.0;
-            }
-        }.run();
-    }
-
-    @Test
-    public void noisyIntent_pausePlaybackForMedia() throws Exception {
-        testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
-                new PlayerRunnable() {
-                    @Override
-                    public void run(MediaPlayer player) {
-                        // Noisy intent would pause for USAGE_MEDIA.
-                        sendNoisyIntent(player);
-                    }
-                });
-    }
-
-    @Test
-    public void noisyIntent_lowerVolumeForGame() throws Exception {
-        testDuckedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_GAME),
-                new PlayerRunnable() {
-                    @Override
-                    public void run(MediaPlayer player) {
-                        // Noisy intent would duck for USAGE_GAME.
-                        sendNoisyIntent(player);
-                    }
-                });
-    }
-
-    private void requestAudioFocus(final int gainType) throws InterruptedException {
-        if (mAudioFocusListener == null) {
-            mAudioFocusListener = new AudioFocusListener();
-        }
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                assertEquals(AUDIOFOCUS_REQUEST_GRANTED,
-                        mAudioFocusListener.requestAudioFocus(gainType));
-            }
-        });
-    }
-
-    private void waitForAudioFocus(int targetFocusGain) throws InterruptedException {
-        assertNotNull(mAudioFocusListener);
-        mAudioFocusListener.waitFor(targetFocusGain);
-    }
-
-    private void assertNoAudioFocusChanges(int expectedFocusGain) throws InterruptedException {
-        assertNotNull(mAudioFocusListener);
-        mAudioFocusListener.assertNoAudioFocusChanges(expectedFocusGain);
-    }
-
-    private void abandonAudioFocus() {
-        if (mAudioFocusListener != null) {
-            mAudioManager.abandonAudioFocus(mAudioFocusListener);
-            mAudioFocusListener = null;
-        }
-    }
-
-    /**
-     * Tests whether the session requests audio focus, so previously focused one loss focus.
-     */
-    @Test
-    public void audioFocus_requestFocusWhenPlay() throws Exception {
-        // Request an audio focus in advance.
-        requestAudioFocus(AUDIOFOCUS_GAIN);
-
-        initPlayer(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA));
-
-        // Play should request audio focus with AUDIOFOCUS_GAIN for USAGE_MEDIA
-        assertFutureSuccess(mPlayer.play());
-
-        // Previously focused one should loss audio focus
-        waitForAudioFocus(AUDIOFOCUS_LOSS);
-    }
-
-    @Test
-    public void audioFocus_requestFocusWhenUnknown() throws Exception {
-        // Request an audio focus in advance.
-        requestAudioFocus(AUDIOFOCUS_GAIN);
-
-        initPlayer(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_UNKNOWN));
-
-        // Play should request audio focus with AUDIOFOCUS_GAIN for USAGE_MEDIA
-        assertFutureSuccess(mPlayer.play());
-
-        // Previously focused one should loss audio focus
-        waitForAudioFocus(AUDIOFOCUS_LOSS);
-    }
-
-    @Test
-    public void audioFocus_requestFocusTransient() throws Exception {
-        // Request an audio focus in advance.
-        requestAudioFocus(AUDIOFOCUS_GAIN);
-
-        initPlayer(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_ALARM));
-
-        // Play should request audio focus with AUDIOFOCUS_GAIN_TRANSIENT for USAGE_ALARM
-        assertFutureSuccess(mPlayer.play());
-
-        waitForAudioFocus(AUDIOFOCUS_LOSS_TRANSIENT);
-    }
-
-    @Test
-    public void audioFocus_requestFocusTransientMayDuck() throws Exception {
-        // Request an audio focus in advance.
-        requestAudioFocus(AUDIOFOCUS_GAIN);
-
-        initPlayer(createAudioAttributes(
-                CONTENT_TYPE_SPEECH, USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
-
-        // Play should request audio focus with AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK for
-        // USAGE_ASSISTANCE_NAVIGATION_GUIDANCE.
-        assertFutureSuccess(mPlayer.play());
-
-        waitForAudioFocus(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
-    }
-
-    @Test
-    public void audioFocus_pauseForFocusLoss() throws Exception {
-        testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
-                new PlayerRunnable() {
-                    @Override
-                    public void run(MediaPlayer player) throws InterruptedException {
-                        // Somebody else has request audio focus.
-                        // Session should lose audio focus and pause playback.
-                        requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT);
-                    }
-                });
-    }
-
-    @Test
-    public void audioFocus_pauseForDuckableFocusLoss() throws Exception {
-        testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_SPEECH, USAGE_MEDIA),
-                new PlayerRunnable() {
-                    @Override
-                    public void run(MediaPlayer player) throws InterruptedException {
-                        // Although ducking is possible, CONTENT_TYPE_SPEECH should prefer pause.
-                        requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-                    }
-                });
-    }
-
-    @Test
-    public void audioFocus_duckForFocusLoss() throws Exception {
-        if (VERSION.SDK_INT >= 26) {
-            // On API 26, framework automatically ducks so we cannot test it.
-            return;
-        }
-
-        testDuckedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
-                new PlayerRunnable() {
-                    @Override
-                    public void run(MediaPlayer player) throws InterruptedException {
-                        // This will trigger duck (lower volume).
-                        requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-                    }
-                });
-    }
-
-    @FunctionalInterface
-    private interface PlayerRunnable {
-        void run(MediaPlayer player) throws InterruptedException;
-    }
-
-    private class AudioFocusListener implements OnAudioFocusChangeListener {
-        private final Object mLock = new Object();
-        @GuardedBy("mLock")
-        public int mAudioGain = AUDIOFOCUS_NONE;
-        @GuardedBy("mLock")
-        private int mTargetAudioGain;
-        @GuardedBy("mLock")
-        private CountDownLatch mLatch;
-
-        public int requestAudioFocus(int gainType) {
-            synchronized (mLock) {
-                int gainResult = mAudioManager.requestAudioFocus(
-                        mAudioFocusListener, STREAM_MUSIC, gainType);
-                mAudioGain = gainResult == AUDIOFOCUS_REQUEST_GRANTED
-                        ? AUDIOFOCUS_GAIN : AUDIOFOCUS_LOSS;
-                return gainResult;
-            }
-        }
-
-        @Override
-        public void onAudioFocusChange(int focusGain) {
-            synchronized (mLock) {
-                mAudioGain = focusGain;
-                if (mTargetAudioGain == focusGain && mLatch != null) {
-                    mLatch.countDown();
-                    mLatch = null;
-                }
-            }
-        }
-
-        public void waitFor(int targetFocusGain) throws InterruptedException {
-            final CountDownLatch latch;
-            synchronized (mLock) {
-                if (mAudioGain == targetFocusGain) {
-                    // it's already the same as target. Skipping.
-                    return;
-                }
-                mTargetAudioGain = targetFocusGain;
-                mLatch = new CountDownLatch(1);
-                latch = mLatch;
-            }
-            assertTrue(
-                    "Audio focus didn't change as expected. Expected focusGain=" + targetFocusGain,
-                    latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-
-        public void assertNoAudioFocusChanges(int expectedFocusGain) throws InterruptedException {
-            final CountDownLatch latch;
-            synchronized (mLock) {
-                assertEquals(expectedFocusGain, mAudioGain);
-                mTargetAudioGain = AUDIOFOCUS_NONE;
-                mLatch = new CountDownLatch(1);
-                latch = mLatch;
-            }
-            assertFalse("Audio focus unexpectidly changed",
-                    latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaStubActivity.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaStubActivity.java
deleted file mode 100644
index e8cdcdb..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaStubActivity.java
+++ /dev/null
@@ -1,72 +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.media2.player;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.media2.player.test.R;
-
-public class MediaStubActivity extends Activity {
-    private static final String TAG = "MediaStubActivity";
-    private SurfaceHolder mHolder;
-    private SurfaceHolder mHolder2;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.mediaplayer);
-
-        SurfaceView surfaceV = (SurfaceView) findViewById(R.id.surface);
-        mHolder = surfaceV.getHolder();
-
-        SurfaceView surfaceV2 = (SurfaceView) findViewById(R.id.surface2);
-        mHolder2 = surfaceV2.getHolder();
-
-        // disable enter animation.
-        overridePendingTransition(0, 0);
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-
-        // disable exit animation.
-        overridePendingTransition(0, 0);
-    }
-
-    @Override
-    protected void onResume() {
-        Log.i(TAG, "onResume");
-        super.onResume();
-    }
-
-    @Override
-    protected void onPause() {
-        Log.i(TAG, "onPause");
-        super.onPause();
-    }
-    public SurfaceHolder getSurfaceHolder() {
-        return mHolder;
-    }
-
-    public SurfaceHolder getSurfaceHolder2() {
-        return mHolder2;
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTestBase.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTestBase.java
deleted file mode 100644
index 3278715..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTestBase.java
+++ /dev/null
@@ -1,55 +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.media2.player;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.BeforeClass;
-
-/**
- * Base class for all media tests.
- */
-abstract class MediaTestBase {
-    @BeforeClass
-    public static void setupMainLooper() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Prepare the main looper if it hasn't.
-                // Some framework APIs always run on the main looper.
-                if (Looper.getMainLooper() == null) {
-                    Looper.prepareMainLooper();
-                }
-
-                // Initialize AudioManager on the main thread to workaround b/78617702 that
-                // audio focus listener is called on the thread where the AudioManager was
-                // originally initialized.
-                // Without posting this, audio focus listeners wouldn't be called because the
-                // listeners would be posted to the test thread (here) where it waits until the
-                // tests are finished.
-                Context context = ApplicationProvider.getApplicationContext();
-                AudioManager manager =
-                        (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            }
-        });
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTimestampTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTimestampTest.java
deleted file mode 100644
index 8b9a411..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaTimestampTest.java
+++ /dev/null
@@ -1,63 +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.media2.player;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.os.Build;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link MediaTimestamp}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaTimestampTest extends MediaTestBase {
-
-    @Test
-    public void constructor() {
-        MediaTimestamp timestamp = new MediaTimestamp(123, 456, 0.25f);
-        assertEquals(123, timestamp.getAnchorMediaTimeUs());
-        assertEquals(456, timestamp.getAnchorSystemNanoTime());
-        assertEquals(0.25f, timestamp.getMediaClockRate());
-    }
-
-    @Test
-    public void voidConstructor() {
-        MediaTimestamp timestamp = new MediaTimestamp();
-        assertEquals(0, timestamp.getAnchorMediaTimeUs());
-        assertEquals(0, timestamp.getAnchorSystemNanoTime());
-        assertEquals(1.0f, timestamp.getMediaClockRate());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
-    public void timestampUnknown() {
-        assertEquals(android.media.MediaTimestamp.TIMESTAMP_UNKNOWN.getAnchorMediaTimeUs(),
-                MediaTimestamp.TIMESTAMP_UNKNOWN.getAnchorMediaTimeUs());
-        assertEquals(android.media.MediaTimestamp.TIMESTAMP_UNKNOWN.getAnchorSytemNanoTime(),
-                MediaTimestamp.TIMESTAMP_UNKNOWN.getAnchorSystemNanoTime());
-        assertEquals(android.media.MediaTimestamp.TIMESTAMP_UNKNOWN.getMediaClockRate(),
-                MediaTimestamp.TIMESTAMP_UNKNOWN.getMediaClockRate());
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/PlaybackParamsTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/PlaybackParamsTest.java
deleted file mode 100644
index c503747..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/PlaybackParamsTest.java
+++ /dev/null
@@ -1,67 +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.media2.player;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.os.Build;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link PlaybackParams}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PlaybackParamsTest extends MediaTestBase {
-
-    @Test
-    public void constructor() {
-        PlaybackParams params = new PlaybackParams.Builder()
-                .setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_MUTE)
-                .setPitch(1.0f)
-                .setSpeed(1.0f)
-                .build();
-        assertEquals(PlaybackParams.AUDIO_FALLBACK_MODE_MUTE, (int) params.getAudioFallbackMode());
-        assertEquals(1.0f, params.getPitch());
-        assertEquals(1.0f, params.getSpeed());
-    }
-
-    @Test
-    public void constructorNullValues() {
-        PlaybackParams params = new PlaybackParams.Builder().build();
-        assertEquals(null, params.getAudioFallbackMode());
-        assertEquals(null, params.getPitch());
-        assertEquals(null, params.getSpeed());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) // For framework PlaybackParams
-    public void constants() {
-        assertEquals(android.media.PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT,
-                PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT);
-        assertEquals(android.media.PlaybackParams.AUDIO_FALLBACK_MODE_MUTE,
-                PlaybackParams.AUDIO_FALLBACK_MODE_MUTE);
-        assertEquals(android.media.PlaybackParams.AUDIO_FALLBACK_MODE_FAIL,
-                PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/TestDataSourceCallback.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/TestDataSourceCallback.java
deleted file mode 100644
index 8880913..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/TestDataSourceCallback.java
+++ /dev/null
@@ -1,119 +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.media2.player;
-
-import android.content.res.AssetFileDescriptor;
-import android.util.Log;
-
-import androidx.media2.common.DataSourceCallback;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A DataSourceCallback that reads from a byte array for use in tests.
- */
-public class TestDataSourceCallback extends DataSourceCallback {
-    private static final String TAG = "TestDataSourceCallback";
-
-    private byte[] mData;
-
-    private Long mThrowFromReadAtPosition;
-    private boolean mThrowFromGetSize;
-    private Long mReturnFromGetSize;
-    private boolean mIsClosed;
-
-    // Read an asset fd into a new byte array media item. Closes afd.
-    public static TestDataSourceCallback fromAssetFd(AssetFileDescriptor afd) throws IOException {
-        try {
-            InputStream in = afd.createInputStream();
-            final int size = (int) afd.getDeclaredLength();
-            byte[] data = new byte[size];
-            int writeIndex = 0;
-            int numRead;
-            do {
-                numRead = in.read(data, writeIndex, size - writeIndex);
-                writeIndex += numRead;
-            } while (numRead >= 0);
-            return new TestDataSourceCallback(data);
-        } finally {
-            afd.close();
-        }
-    }
-
-    public TestDataSourceCallback(byte[] data) {
-        mData = data;
-    }
-
-    @Override
-    public synchronized int readAt(long position, byte[] buffer, int offset, int size)
-            throws IOException {
-        if (mThrowFromReadAtPosition != null
-                && position <= mThrowFromReadAtPosition
-                && position + size > mThrowFromReadAtPosition) {
-            throw new IOException("Test exception from readAt()");
-        }
-
-        // Clamp reads past the end of the source.
-        if (position >= mData.length) {
-            return -1; // -1 indicates EOF
-        }
-        if (position + size > mData.length) {
-            size -= (position + size) - mData.length;
-        }
-        System.arraycopy(mData, (int) position, buffer, offset, size);
-        return size;
-    }
-
-    @Override
-    public synchronized long getSize() throws IOException {
-        if (mThrowFromGetSize) {
-            throw new IOException("Test exception from getSize()");
-        }
-        if (mReturnFromGetSize != null) {
-            return mReturnFromGetSize;
-        }
-
-        Log.v(TAG, "getSize: " + mData.length);
-        return mData.length;
-    }
-
-    // Note: it's fine to keep using this media item after closing it.
-    @Override
-    public synchronized void close() {
-        Log.v(TAG, "close()");
-        mIsClosed = true;
-    }
-
-    // Whether close() has been called.
-    public synchronized boolean isClosed() {
-        return mIsClosed;
-    }
-
-    public synchronized void throwFromReadAtPosition(long position) {
-        mThrowFromReadAtPosition = position;
-    }
-
-    public synchronized void throwFromGetSize() {
-        mThrowFromGetSize = true;
-    }
-
-    public synchronized void returnFromGetSize(long size) {
-        mReturnFromGetSize = size;
-    }
-}
-
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/TestUtils.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/TestUtils.java
deleted file mode 100644
index 1a573bb..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/TestUtils.java
+++ /dev/null
@@ -1,112 +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.media2.player;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utilities for tests.
- */
-public final class TestUtils {
-    private static final int TIMEOUT_MS = 1000;
-
-    /**
-     * Handler that always waits until the Runnable finishes.
-     */
-    public static class SyncHandler extends Handler {
-        public SyncHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void postAndSync(final Runnable runnable) throws InterruptedException {
-            if (getLooper() == Looper.myLooper()) {
-                runnable.run();
-            } else {
-                final CountDownLatch latch = new CountDownLatch(1);
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        runnable.run();
-                        latch.countDown();
-                    }
-                });
-                assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-    }
-
-    public static class Monitor {
-        private static final long DEFAULT_TIME_OUT_MS = 20000;  // 20 seconds
-        private int mNumSignal;
-
-        public synchronized void reset() {
-            mNumSignal = 0;
-        }
-
-        public synchronized void signal() {
-            mNumSignal++;
-            notifyAll();
-        }
-
-        public synchronized boolean waitForSignal() throws InterruptedException {
-            return waitForCountedSignals(1) > 0;
-        }
-
-        public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException {
-            return waitForCountedSignals(targetCount, 0);
-        }
-
-        public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException {
-            return waitForCountedSignals(1, timeoutMs) > 0;
-        }
-
-        /**
-         * @param targetCount It should be greater than zero.
-         * @param timeoutMs It should be equals to or greater than zero. If this is zero,
-         *                  then internal default timeout will be used.
-         * @return The number of received signals within given timeoutMs.
-         * @throws InterruptedException
-         */
-        public synchronized int waitForCountedSignals(int targetCount, long timeoutMs)
-                throws InterruptedException {
-            long internalTimeoutMs = timeoutMs > 0 ? timeoutMs : DEFAULT_TIME_OUT_MS * targetCount;
-            long deadline = System.currentTimeMillis() + internalTimeoutMs;
-            while (mNumSignal < targetCount) {
-                long delay = deadline - System.currentTimeMillis();
-                if (delay <= 0) {
-                    break;
-                }
-                wait(delay);
-            }
-            return mNumSignal;
-        }
-
-        public synchronized boolean isSignalled() {
-            return mNumSignal >= 1;
-        }
-
-        public synchronized int getNumSignal() {
-            return mNumSignal;
-        }
-    }
-}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/TimedMetaDataTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/TimedMetaDataTest.java
deleted file mode 100644
index eaf749d..0000000
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/TimedMetaDataTest.java
+++ /dev/null
@@ -1,44 +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.media2.player;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.TestCase.assertTrue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-
-/**
- * Tests {@link TimedMetaData}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class TimedMetaDataTest extends MediaTestBase {
-
-    @Test
-    public void constructor() {
-        byte[] meta = new byte[] { 0x42, 0x4f };
-        TimedMetaData timedMeta = new TimedMetaData(123, meta);
-        assertEquals(123, timedMeta.getTimestamp());
-        assertTrue(Arrays.equals(meta, timedMeta.getMetaData()));
-    }
-}
diff --git a/media2/media2-player/src/androidTest/res/layout/mediaplayer.xml b/media2/media2-player/src/androidTest/res/layout/mediaplayer.xml
deleted file mode 100644
index b9b2b92..0000000
--- a/media2/media2-player/src/androidTest/res/layout/mediaplayer.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:keepScreenOn="true"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <SurfaceView android:id="@+id/surface"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1">
-    </SurfaceView>
-
-    <SurfaceView android:id="@+id/surface2"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1">
-    </SurfaceView>
-
-    <SurfaceView android:id="@+id/surface3"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1">
-    </SurfaceView>
-
-</LinearLayout>
diff --git a/media2/media2-player/src/androidTest/res/raw/bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4
deleted file mode 100644
index a0c38ff..0000000
--- a/media2/media2-player/src/androidTest/res/raw/bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/bug13652927.ogg b/media2/media2-player/src/androidTest/res/raw/bug13652927.ogg
deleted file mode 100644
index 065d9e5..0000000
--- a/media2/media2-player/src/androidTest/res/raw/bug13652927.ogg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/camera_click.ogg b/media2/media2-player/src/androidTest/res/raw/camera_click.ogg
deleted file mode 100644
index b836e10..0000000
--- a/media2/media2-player/src/androidTest/res/raw/camera_click.ogg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftaac.aac b/media2/media2-player/src/androidTest/res/raw/loudsoftaac.aac
deleted file mode 100644
index 1534ef2..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftaac.aac
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftfaac.m4a b/media2/media2-player/src/androidTest/res/raw/loudsoftfaac.m4a
deleted file mode 100644
index b4895b5..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftfaac.m4a
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftitunes.m4a b/media2/media2-player/src/androidTest/res/raw/loudsoftitunes.m4a
deleted file mode 100644
index b01b36b..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftitunes.m4a
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftmp3.mp3 b/media2/media2-player/src/androidTest/res/raw/loudsoftmp3.mp3
deleted file mode 100644
index b32c8bd..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftmp3.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftogg.ogg b/media2/media2-player/src/androidTest/res/raw/loudsoftogg.ogg
deleted file mode 100644
index dc122d9..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftogg.ogg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/loudsoftwav.wav b/media2/media2-player/src/androidTest/res/raw/loudsoftwav.wav
deleted file mode 100644
index 9930dcb..0000000
--- a/media2/media2-player/src/androidTest/res/raw/loudsoftwav.wav
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/midi8sec.mid b/media2/media2-player/src/androidTest/res/raw/midi8sec.mid
deleted file mode 100644
index 746aca1..0000000
--- a/media2/media2-player/src/androidTest/res/raw/midi8sec.mid
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/number1.mp4 b/media2/media2-player/src/androidTest/res/raw/number1.mp4
deleted file mode 100644
index b8d9236..0000000
--- a/media2/media2-player/src/androidTest/res/raw/number1.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/number2.mp4 b/media2/media2-player/src/androidTest/res/raw/number2.mp4
deleted file mode 100644
index c29d88c..0000000
--- a/media2/media2-player/src/androidTest/res/raw/number2.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/number3.mp4 b/media2/media2-player/src/androidTest/res/raw/number3.mp4
deleted file mode 100644
index 767bd5c..0000000
--- a/media2/media2-player/src/androidTest/res/raw/number3.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/test1m1s.mp3 b/media2/media2-player/src/androidTest/res/raw/test1m1s.mp3
deleted file mode 100644
index d54a9df..0000000
--- a/media2/media2-player/src/androidTest/res/raw/test1m1s.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/test1m1shighstereo.mp3 b/media2/media2-player/src/androidTest/res/raw/test1m1shighstereo.mp3
deleted file mode 100644
index 2a97077..0000000
--- a/media2/media2-player/src/androidTest/res/raw/test1m1shighstereo.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/testmp3.mp3 b/media2/media2-player/src/androidTest/res/raw/testmp3.mp3
deleted file mode 100755
index 657faf77..0000000
--- a/media2/media2-player/src/androidTest/res/raw/testmp3.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/testmp3_2.mp3 b/media2/media2-player/src/androidTest/res/raw/testmp3_2.mp3
deleted file mode 100644
index 6a70c69..0000000
--- a/media2/media2-player/src/androidTest/res/raw/testmp3_2.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/testmp3_3.raw b/media2/media2-player/src/androidTest/res/raw/testmp3_3.raw
deleted file mode 100644
index 1d5ee19..0000000
--- a/media2/media2-player/src/androidTest/res/raw/testmp3_3.raw
+++ /dev/null
@@ -1 +0,0 @@
-SUQzBABAAAAAegAAAAwBIAUPNyZSM1RQRTEAAAAOAAAATmV3IFdhdmUgTGFic1RJVDIAAAAPAAAAVW5jb21tb24gU3RvcnlDT01NAAAAMwAAAAAAAAAoYykgQ29weXJpZ2h0IDIwMDggQW5kcm9pZCBPcGVuIFNvdXJjZSBQcm9qZWN0//pwBDV5AAABqxzGvSTAAD9DmHGnsACJ3IV9uUoQETYQrXc1IAIgBAWoAHtdG2ogYXCgYQRXFYrbUAYDTYgQIZZBDLJ388RH93rE77Z71rJ2Udz4ICQ4D6nfwwEGoafUbm2IYqFOJoLgdEVPlvJ2hakADAOW0gkCIJCg7BoTHTszfymrFiztXr32DM/+lNbfoZn74gDHUCBRwP4DgcDgcDgcDgYAAAAAAJKgbF6umkB5UoGyQJWgZIQAcM8DSlQMWH8BgGBYQAkP+AoTAxgoDEBAtM/xygbqBfQAYWG3/+RMzJ83My/++D78BgMBgMBgMBgKAAAAAAabN7JmcJXTG7HmYGSrGziBgiOH5duAfqDYLXdMOXDjAvm2+Fp4NkA20EAFNsnkOD4BHgXBil//K6Zums3/fB9MQf/6cgTaUgANgiQ+Vx88oARCRksH55QAiAzJVGw8p9EAnSqM9Ik6ABd3Aa1SH2V0YlB7IWiFbGlh73twkBhYOKPGu5WbGuHw6wACw1XG/rzXFb/+V5V1//Ra0KQ5WIb/8w26OVDDUvFh/UHgHABy7gZOBwuqS6Qha25y3BvTWeugYPAMOFRqiSU7BMqFHkd1b9ptAFEQ7dH+LGGlLWXf/1epSzVGfqBmJTINU7uoAKcjKHEoG1ZQ5sw8YfSjWDslm1pGlHXqKLaBe8lfrzbQFcmEQJZVX1fWNBRA+v+LWUdN//NShkNEh389jRLt9QBM3AGAzw7mZBBjCSgZhZ4xgQTDjXPA+88I2ZR0SpTv+uJG2aPkWame+9A8uCHR//QE5t7//q99UMZhnT31XIGTEFNRTMuOTcAAAAAA//pwBOp9AAyCFyNTmww8JERHWoNlgk6IOI1SbDCn0QgRqpz0iTMAObADFtkjB9TSwuVAKwwdHcYq78pk1DcsrCSnNhNDLVWmrsb5esBv+Pw4IbFrer2zGEgxnNf1dS5I1X11FYMD2fbaADdwBMockXHliie6kSMBQV7DR5IWVrSkxcWhzGox4o2vSfacLUorxlPsmLf81+Kt/2MLOyo6My9fypa6/Vqps6stBlWAJfwBjICCCWUKJEv2iyGJMdEFeiSQWCp+RRft5Fs3yv1JExnLAbN+r+gCB5yuj32dR51njawqJjYDABIoh80BBZIQU9+AMBHTOKRtBCjYEhEggWdSbLJG1s0HGieVmE9yWJOV9psEOJfHqsGclVGY+n4tAzZZG/vq76kddCIi69/Pa/aYgpqKZlxybv/6cgR03QAIghM6VBsJE7RCArqnPwkiiLTDVOkwRZkWCmnNh60iALv4A1LyHEOQcrhLqSplRqTs+5Zr3La+WcBtw5GSPfV7Hh/MOMJqzL61biDUVP5Kran9GzkU53QhSMLamiXRlecIwAQibvwOFcnytWjRamTibaBhPLXpph1L6RIUX2d+Vytj+A2wizntEWkZM/IHTJz+N1kEgQuwXYTAAWWUsmhYyQRisQVL/wJMAmAdhwlCYNwTPlY5u8cZgo2SS0pnXmXZHDGYDGqpWT1Sy1BGN/0NRr/zJr3oZQ4OQmlX05S7ShTt6PtXfilDegUZtgNCzSwFisYFBICxEAqj2V0TNniTVUaNU94pWa3AnnpVYgKxXb5WAmkTEwG74xjmv8yZWHGBx5lr2kzOy8VZFo7XMuTEFNRQ//pwBBnqAAiCESLU0wkSdEPlumNlYkzIZI9K7KSykQqYKtzziVoAAAglLvgNTMKKCy5QZUNM1mdaCD8mlEE5YBywn2zzc83E+r6Kj63yOTzrnJgblbf1YEFDq/bqkiy3khR4LLJJvReAnPwB0ebJSaXQyIugPZTwWKH66MjwigwLzlDaShH5mevRwznEUWFfb5xVLEMpWp/Qd3/6FV2RVFItgCjRBlX/dbEHDYglXYAZ2hGuRQy0Rzo+vYm5QzylUslt6ST7ZsKeG5li3dXbza0q9dewECSFPFoq/6pdRf/5QkLSQ5Dru4qDpFaL/uTEF3f8CopBQreGclAh5GoaAhyJrAwaNKOGk99YAeDHBdvdW7sKAZ3T6oURKiU23si7JRpwZYJzr4FOJQywWfQR1IWlMQU1FAAAAP/6cgQ3PgAJghoi1VMMKURCBKqXYOZfiDjLVOwcbdEUGKkNhJ3LAAwAJvf8D5aVUvLtEEMzRG/ca1KpbiMyj2CxDNvo4liRBbO6djjzvcRDa/9BiwicZpmFtWLFAwbOUwfUtxyumYn6ZAAIJ0sAGki2wtTqqNwSJSbd2ma3neJhqBIlHjQktzdYTGsqHmq7p275TAxNz7V/pZHHTY3VkrE0K5MOizA0KGxFcmILm+nIBvPfSMZNJoBq5Ri1P5Q2OrOUO2S+kdz2XZ/nnq+UDSN/3o4WMoJod9CvubZmxwjIh84KOFkWk5ufrUq9iABJsAJkGMgQrHLkZUjwXQcKthEdsWGXI8s9VdZuaAETuGVZKqoCVY9prN/bSgbJEFo29c25zX/2bbmnnlCJ7xHv1pgy/UxBTUUzLjk3//pwBP+kAAiR+yNUOwwRVEHGGqo9AluIyMlS56RHOQQYaiTxihcAIKV2wH2FootzQywEBzEA5cHaqOpfGWbzlAFUvdKwLKprR/r8gpf/eKDagrYlT2zx0o2u+deZItUsotbrmL1AAIGSQ7IBINEoEFETh/ByqhlIczmeNCwqbfB3dl0PqlYDmkxlqG289TUf/7ueXp9brpIlnxIfLI7WCTwuSdyKIAYUEuSQD1LoKpUkvIaKoLNBEBTspB9FO8NqDpr6wFpziVcIOj+3ugMb/mI5ToJp3vv61SxEUoysVcELVwNaoVx32/aJ5qFUqRA/KCaFOVT8sj+A9nseqcYYz/bW8MxjmXMOTGQa7w4g4YZZkb7ht2UUXRW+aSRQ1Pq93dHNYpYsBARDvfrlEr+JiCmopmXHJuAAAP/6cgRSzAAMghYwVVHpEbY/4SqqPEZQiJh1Tmwwp5ENEupc9BVaAAAYJTf8Dvg+DibQH87TkG0jIgXdzo5mFc1Rz799XQ6gnT0p1c4nT+ZbOOVv2TRX3fEKTV4P36blbt+hsy54zzu6YAAAIFS/8DAsJUTYUylNKNcjmBx7jQGHAXFi5B4oXo3aEuFRA9GgHq3ZcIpMnXgyIgZMH3yDX3vGDFVlwGkk+KgqXbAfICqyHIPWFW8UJUlw3La1AlkNm5oXP2dMOpFFl2Vyi7rT9HQzxgONHX8GipN188szS9rSBkR1EkrKrQq5bi0ZseMJrbbDhlGEgHFtZRqL6KVscluFB0dRHHJCeltWM0lOtaUt/8w0GnIPlkOFCuKwDywMEQtAy0CiAXRmFGwniLN5vF9CYgpqKZlxybgA//pwBB5iAAiCGSXS0wcb1j6Eqqc1hQjIqJtVp5hHuQMS6ujyoVYAAAgDbsAMoWMoKAw4IpInKrx7BTl9dQn441GksGyL0GpSTORHVcu3/v6RqOZar2HHsxetbPR9fiFugSoTJbKLT/8KgBBc//Ag+Jg2VoaQuwpAzQ1FKQg0SI2VPSbVi906Wl1HM//FlBHUQIWZ5QY/yf1yA+b0O7cpfIsrRr86v47oAEBUtkA7A+IjIiypDtoWkKKngSO7zX/6wdeIFAn4Qceyv+90QrHO9PoYzFsIgrOfKsH50uImBnltHu2Zn+Gvb5L7nvvgAZTlsgFh/qdAwJdoKjaqpEHczwnP9pHp5WDr65VojRX+fbFeK5b1x/6bLaiNBV7FiOceaQoLIQ+cw2ndzKHKqLpiCmopmXHJuAAAAP/6cgR/lgAAkhUi1dCvEExDREqHPWVDyMg1SOwwylEIkypo84lvAEAcJu2QCFlGasYn5ol8hyrFQTwRwbQURUQczYKm52Mc7r8qCXKdbf84UXEAseaeNOfi8lY02JGMvS4+KSrrzrUqWAEFSWQCCEcChoF6xsagal8SJIP7BHDouM2u+kJZhyyvsnatyGOJJ/iuhRXdv094Y/9a0T08Mqekwa/sb3e6dV23CYYAQQ5baM3eKn4dfkUu7Qvl/Yw1HjQ3xCKI8eU4GsxYskuAUpQKmtOTEHqJ7NS6ios5llZ19YiJOrOqVc9olLZh41SkGaL1iGSnZBIwerOtajKl/CPFrh9DTGiA4Wp0u88Lsw+NmR7ebZEPL/wrkoqIEt3niwLp66Xx1Pnfwgo7sVd+Fui80n5CkxBTUUAA//pwBCTPAAiCFSNT0eMTpEJDioo9JRmImHdQ5+EEcQ8KqN2HpFoAAAgVNtqMIaBtK+OZAaoQ9ean7+Vaz3c7OsfxUJerfvbdPU1tRbrf/CnFQyTAp+xS3PCY8YepQnTULkYrrVaHWgVYAABAlSNsShJgoJDxIMIhONlCeC5qcemLEB87mtRy6grMyPb2toP+gRxYWmnnjMYEYpYhpJ1O03nkNz652tj+9D/aUtSRwDn2yjbXgSEbHnT1oLkDW5sQatrv6a/+UhwcOJIqRO9fzUrOwsTB4EGXvNLFelceq5wBfa2/axgcEh8OiqDRFbCoQkbbQJg1JTlKRqcPpUHYwOAqk8MiTJmik+H1SJfpVsc2t+wsypdrOfUXJhn5VBcGQ0cKnK6yGcrSBaCFjl22O2PWPWmIKaigAP/6cgTOAwAIghgcVNHjKexEZKqKPGVpiCyRV0ecy/EKkipo9IinAABcFOSRjowgBrKk6ick4C0U6iJcJYVS5PFUtLiQImb73pUgqjyhZZ8A+fsboLCip4y4PB60TLQtAs4BCIRMHIb/6wAASJMljYlAtEAFUxOTRLI1woemjFQWOiMMp2zhAjYmIHE1pv0bSolF3N+7odnGIoSpWPbrUbB1AqTaHFAyCST7d3/9AJtPWyAdnHtZzKNoGbErElluc7LblXMOdCwpGTyYMsn9UuiHiFKvLmd3f/+sSuaofOPeZSw4Uta56GwvIHlmS0goAgU7ZANA8A+DyVKZZaL2OYkzdujQEh0uV7Ulwg5M1OwJ953Iz/5gQEM8yUn7r/KjpRdd/sZHZFvFPvpf8utiGf78qYgpqKZlxybg//pwBFhOAAiCCBxUUeY5rEJjieNp6CyIZHFPR5RrcRWUqejxFT8AABgSpJGMqwU5AOAYppDjFUNo5F7CvU5dzTX9ndAz1f83Spb6QuVDRt/VpRFGXhiTyDz4wYKBRwtNKQtPpRzLbQQXIAH+OqVEMxlgsHR5IWBbLCTwDyXYjDfiqyfzhIO/AWUbUSop7GwLo4CSCWFer/4jqtR5P0itPRW9qHYoljUtvUAQSUjYGWsZQbi+WwdYNJVsz/AwXoLlaIR+jDn8JWjIKUL0/5M0NRjvoJOUJQ8o0tqiq5UwatHQ0Zep5w68ogstT96BCKTlkAbQBQIMWjZAV6qPZiA1BD0UjOMcUvvyhiGARhaODb3TZ5g1//ciUP9sU6F7jp8zvFJftWddBlCoAYvAdW83bxv0mIKaigAAAP/6cgTslQAIgfccVNHmKmxCRgqaPOJNyHBzTUegSXEYE6ldhJUaAABcluVwDLc+G0qhBGsdCR479f6ynhJGn6Oo77+A6tPc1Fb1upD0BXfGl0KC1sqKLGZoVTq+stW8WNKJmGOWAAAaKdsgEgf74iHFsqeD+MH0Luk549IdWI7IEpCdTL+g29Trf/UxDuRFJBde9k3dQa+bIpIp3fojhvzn/yhC5B/0AOAnJGxs/QhYNU8BZhMAjKEKEiZblrtQzmTj4etMKkpl/3vUK6jncjjgG5xk8YXW2KocoDwqSNEXoVtzqa6rrdIxPLBBc22ooRGZRdQlzo29DWWkxkNLdrJdV2DagLrm8em5fXsMKV0YjjS9vuiVZyMPS8AsicUStqBpd4UWsaGkvT32MsDBX1dKYgpqKZlxybgA//pwBNaBAAGCFhzUUekaTEOFmmc8wlWIJJVVR5hpcRAOKc0HjCMAAKylJZAO+BJpVjGGeAW5hRxb273lwmOGo1I8JNCqIxP4jJdv5ze9gQS15uDOmXSwHp8+VQXf2pJBKHWMWYGVvaq0CIBjrYEgApB6l5ePYwZY7G9GQy0FvgicQOF2d3bCRJkZzafo9VUOJK5kL653qrH/QqEmQQ8+ETx9KjTr21wj0TKtY4AFXynb3opq3OKaf64T740xlS2RmimnHD1l7UWKYIppDpf4yy7oQSb/24uVDGxpkT+1yTQMhxAqRLLqJPx2qtT9ITe/4CYE4HGzq1cJktyCVyNeGMUwPIVD6l/4TfBwGalPymrQgo////wdBx++k7ydjJUvZ0xhVhkmZqYA+zX7Q6adt1uJiCmooAAAAP/6cgRN8gAIghU0UznjEuxAw4qKPGZsiNUPRuwkqpD/Dmpo8wleAGEVJGBhmFlCzPE4RrhMiyqhNvkFtDhE5PDEXLLMP0MtGWCwZa3q2Ocn/IqA5s7a/6LvdrWooISAUyLDAsfOf0hY0ABBQbm/4GgSpITbc3biFgRlgZ7RHGNmIyC5xgFSM+6/GFUEQ1/stItBxD4WSNWeYhMewaSdRQK7xWYr22GbBqXBBLu2A1DrACi8PDHFBkw0bHJh56+Lp2L9zKdg+7M97kuMdmO7q36PUYZf+6jTmmZjr6OxFZisSylT8jmR01vtvsSvqSNmaoaCUtkA4XbYa8RTpA5TQen3DMvH2qwzX3Ryd1vOkrMcb6G2mAgwL+Cw9pvKJWSYGDYqk1fDcTHRddjK61scxD0xBTUUzLjk3AAA//pwBBImAAiCFCTSOwIz7kLkelpgYlyISKtCbKSq2QgOKR2DCV4BIBcsAGpOOIKBRgRwVoSuS2dd7Icp8qklpr6HIoBZyLtcUrK4vvb6O1A4x6f+wbVrFI7E/GsUgdvpraxKdKP6nqBgAACCcu2AlJkAOAZtZlMDE00qmtw9MBjtQHM7PRqSlL5xOKDGnnBC7LcwJjTf46jb2WjgVjgeyy0nXITscPYzYh9dQBd2AHv4QWK+nSz5FCogIw2VO6yJCYI0wgzFqCQM5aiO3cBTvHs8v966Bu/+pAmzsWeazkYRyK8vgYlbd/Z6AWoopUAFxsCaBKSFUOT7CFppFDIZtVnVLp1CHW2BmaS+aEQ6M+3966i3/bOtfKBWdFksaBGAJCFteKiIAlS4xwpknqJPWmIKaimZccm4AP/6cASXkQAIghYj0rsPKTZD44qKPMVTiHynTueY6NkHDioo8SV/ACC3tsBuRFVzaXSpNlXxZWiOi6zREBUs5gPlu3hSRBxLf5aVsYG/+c4DMB02znk1/cHM9va1vYagfjzAg7/tp136wAAZclqSRiUNoojagIpWh4SrIbT2sd9H1dC48x9CzkezUd/a7mZhwaj7hHF+dFqs7g6UCqWhY6ostqXFRxVgo1osi7ytA5N/wMsgUIPEEM0p2IukWlEpHtrbkWS/92lBYF2JGv50m8sapJ2/7ur/alpca+mip6cbJrkKnNdNuaZWdZSJhhWTrrFMFS2QCQGyxlbRTn4SUolNBhJS4DtH5mUiI3BCHzf32gglItr/jI5QtIOYyPhGedr+RkC6mekWun+pkvq1+6Nb/FMQU1FAAAD/+nIEAoIACIIDE9LTBjrcQAOKmjxiU4iwcVNHmEixHI4qqPCVYwAATBKkbA7K4GKGvMIgKSg5kUH3oCzq67Ss6Q329jzjZc6KDlSR7hjBge8EmiaBk3Ota8il2+YkiuWYeIN1tXJAAKMS3JJBwrijSsNIqYWs52JH3QfhgWiivtnYHM43v9WRnoP+UcnvwBQL5QwMQEBcmF2GmRRgovUjQgDaE0LyVLkq2yQeAQMhrYFM8VaLjWLImEp03DouRakYyl3G3R/01oEMH3axhkREjhCXQfDcmGYHQaLpHBlVYESRpn5J5nmuA3VIPgGJN//xgSpU5jvE+Q5hgpb3KHM12Q0a01BSIdHRtb540HBfvfXIWguvfSXMpe065JD79ZPSBpf7/6h+R3dKWbv37Sdv9d7cTEFNRQAAAP/6cAQk7gAIkhEc1WnmEexAg4qaPGdPiLxzUUeYSXEDEanphIjfAAARSgKtskHhinrL1XIGQFRrEkTW7a5u6/PMXWzpxvf9suJnxd46KtGNXekgDwZiponpxcWE5CtXGgQTss73VRb1AAAOE5bIBYJszUvo9j1IQkGsMudh/pjcuTHEMslHlZ//eZQ0NAC/KrMoAKCqWA0eHFRdGNRICI8ocwbEqEC/YKI2S5LIx5AdrmtBtngfSIqgKKtvv3iO2WBVuRtw7QYlu/0CqhbB3ML6EuG1qQJoqdJhsCrS4/WQlTZoagecIiRZAgpxXZ6lWCTtUuhON9RUbkD0V1xyldpbduORamRuCmwQoPE/+XQV/9gw2CQGA/HAOiZfIRZNXQgMM+AZiJjKex38PcX5vBMQU1FMy45NwAD/+nIESIkACAIPHVVR4xJMQgOZ+mUiWojgTTpsvMjBFo5qdPSIzgAIrKdtkg8EqUu6Ha5I99GVxI/TsQUNbhVd3pLkd62/FS6AnFtD2bKAdA9M9Jj2tePS3byQrCq2n6axz1p+LKj2XgAAEAnJIA8RtzkhREUuMqmkCxppoIXlX645UxaXUfwzJrMPFlNrNdeVJgm33pvIKkT2VFAk+xyV+QsVuo1bMb0r1AGf/8ZorCJUfiQRhAgG/AYQjDZ0P8trGyZSKEIRTkZmgtMU+zPKir2ltXySiAlKHPLgIYTfFT6GEUEO76GMQQpMO9vRoIOWAACCFCnLZIJRdDY3h6zgehrssRuHsaZJ3IZGWuzB1Mv5CU46s8wLnzrZtJQOJWJkDRa04bIqINHSlqbkqNoY2rtDA+7UhMQU0P/6cATyjgAIghQc0FMmE6RB44oHZSJGh9SbU0eMSXEMjiq08RU+AAAIAuSSDSvU5CLNFYqyNPDpGWy1k0EfB3MHyepaQSlLbOHU1AmLqOn/vlYoxw5fnGhKu5i9jVClTOv7WCtSKv+2oAIAckkEFGFCOkKwK9IBiEsKENXdYOFlB10eKJ2R22REMya5hk2//UWDLE6A+w269Ua/GoFBlaGoE6lebt78nfrxoLkt2yQdyKhLJQuyGHSwVQ0LlczBZz3l4jI++yf2sVlb/9SG2xw6IQawCgwlG1Wl6FpoXHo8RPgZC/WNQA1JJSgLlskGRcrOnbE6ERewIMEd8cUcQcXRrnP01Gf/oy6jQ1noHHRcxJIippNI1SBMRWi0BjtKYofdoFo58csTiE/45aYgpqKZlxybgAAAAAD/+nIEBWwAAIHnHM+7LxG0QUR6mjzCOIksNVFHmEZxIQzn6Zeg2gAgJySQUKVhLYz5AWFSSYQTTLkhwTKdalXldEWw0sTFLe9iJO2yPf1R3uFnfuHGzDtCl8zRq2e86xje9dQAA1hy//8cQFrcWxFpBIxCymX/oLO0WiI7OzddRP/ZF7EFJ/6MDQL2pmxrFNULQylzil6LpeaWx90zNLNPJnafiwAINgu2yQdnGUmlURlMm8LOEoOPUezHiVjHTLBtFL6xMMcCgJP2lweAg+SUEWiFQqw4Q1nS5QVLCeNa8YwdYHjzEc49JtmxR9KACCTkkglxPcMkvagNLmpnE8xLoZTl7YTWjWkIwe5MghMN9uyWM4CUI1Hzd/xsO6gaPX4sdS6tQjxZd1pzoSlnFS1eRle81dxVMQU1FP/6cAQw8gAIkh8RVOnoEcxD44qNPSU5iERxTUegSbD4Eqoo8xTnAAACRhTlskHVimNpoVrkNo2A3c3hk4h6Wxe7mstgSisHdS9NfSWoCgy57AACCXlbUIpdZPkGCCo9jC7RfaLvKovucxwAAALMKctkAqIApXJCz4RaHTXehXf/YXWjpYV4m+oytiBqHrX5ztoOIublEiUs5osZdULOLmcQlyxlArMGBDWseyx2VBdFSRtiyLO8iELMAlQt8BtCKyaxo6G0kZ6Mq8EWAmh9uv/+iizpt+lSUDMybe8XUKCgpV64uSomA+8+n60YqLi7Vo2UpbCH0Z1IfzKQxE0gIL3/VS2UB+bujOkYYatf6X0HM3f45RakZj8uf3qbk1vO/VX7lwG2/qt8T++n6ZK2mIKaimZccm4AAAD/+nIEN3YACIIWHNRQ7xBMQKOKOWHlDYgwc0tHjErREo4o6YSU5gAAbBktkghQs0axGFDDe3KsZjbA7mjbqhq7av/utjneUQxH3k3JcxWaLoF1nzAwpDpBFb0tILCTCcUcE5kuW05GtaAAADB/qI6IDHYoh0mGWwOWlC0Q+Dry2TxrqLbOZqWRmOwz/29AxCuUmTKlRy+wzSlYqpzGHR889lSM8+9VqKtVILAub7YYL6IKlSlVxOQYkVqMWYy4QQY7LaWuk3H/9BvjO3yqwteeh08LGDh4uGFzYo4kkLEGmxcwRem6En3Rvk8cAQCjaTFAMFfa0vdhMNIlth4CRZd3vAdzE7FOQ0rY21l0+6avEwUGjSUtIknOqKu7J1jKWsb0vsO/p4lMlbgm484s0yHUxBTUUzLjk3AAAP/6cARo4wAAAfQZ0DsMEjRDA5pKPMJTiMyNTUwYSvEKjir0wxTuAiA5LbBdL5FVMLSvLzl/DoGMNEAeSliO9kZKf5jR1eiL4Ncf/qEFkAAiPhTt9FFh8aPodVlhySRbQT9z6v6QAECBVkbQlANmNlSSgOsl066R6mlUPxDQiCe5m9M413M3612IBgYRqFRMLGXKJ4YcUlENcJxjHohcnY9NA9spEE4uoASLynLZGPb2AYHiT0xoaXI7755kaV+Lr0iwEkx5aK16DpZH+hlTKN/9lBoCbyQseI2sJkBY24RvKDDIq6xo7Cz3IAL1/9AABFKbUltkgkDMOKS5ppFhywCbvxe9OE29lfuxqhYqVSBFfYpqrUdk8UIJGMmnC0xuS19DJgILS6KMqZtb5iLo2UDExBTUUzLjk3D/+nIErfEACIIIHE0TLyqkQkOJo2snHgicc1FFvEDxEBXp9PQdPgb/yPjb6wAlMDzVIGLKduzK1ZXwVyhcRmo9OFA3GW/MEk22GqHDHK73G/16oOd9SB62jmo5Ltczo3N/t07exm/sALotoh4JhEg9axzjgYozBDgDZSSBhAywFNn+LFGcz91W2XRagqzxzbT7MFRlVYv9lZUSPjAcp7Op///RT9nXI/3a4fSdtkgkNF5aR7FUHQ8qICRi4I8ZwCjGbrCMmTb+e2gtwgbo2ikNh9KBkNqaTaXUWuGBYsTMqHBb0ubOpADbXK1jFCjEsBRNWWQDItEV+uDDgI+raFGRFgc3jM/Wn7lEtKQHBOiWIJjf/KggOGNTZ3u9fenR5yDkyOXnBxxZmckHrpSMUBBtDhAmIKaigAAAAP/6cAT6YgAIAggT0LMJGcxBo4mjZMKECJhPSUwZCPEbkWmo8IlmACC/0Vi5bMo2zx6CoAqlGkS4nd6+CuWB6lvWy+EDI5hnM1saVf9s0c6lCjIrpeRCgUEZ2BQuOmzxz2WUeoUY9+sAPbbB0j7hYUwA8BTAbAy4GqXlEBsFOnc5EnodWd3PMDpauTdxvoFjqtIgeijf9pQv+2YnN9moMXV0/0IX23Ve30QuSpI2xdYRnxtZ22NedvTiU8P7gqE2o+fhrmpwmqRlFu90mCxrpQ08HmOcwYXNgwQLiSElvc12tK2NTQuB+5iO5RAYQSACC6KtsjGRJGxmHmdjOjI68rYRTJtUwHhQ2nEh2d1elv37sEV133yAnqeLA0RMiQsYDS3ZCPYLB5lyEIeWAaXkmmbWVVM60xBTUUD/+nIEqQ0ACIIVHE4bBxQgPcOKmi0iCYikcUNMGEkRGY5pHPMVVgHP/+KAQZT/ZMLTe4UecIKldWTMgnsKnLDCKK3M2RoumdH4XagwXj/nmSw5/6isOHFXEfJoQnYf7YzIN2762/uGH3AAI2k5bJBAvOKnAegZVgP6j0dSzROVfVhDaDVX/2oM70plAHmkBYRhfk2AA6yqKDZooA5sa+ncej0V8Uel4JAyW60VUEz4vPBEEv4NpUMv0tBW/kpvDA3+HaqrJ26v0IiEs8UD13AIol9zzwXSxCMCih0gQsA6ix9qyTEO0YpptV84gwKkjbEoVTEjBKqtKVUjWeV+PZkgLdkoctF/D6VYzdfdr6hlKrZ1ywCgapyQiYQok0NnyJVdhoyKxzrTV5OROJxxnOqi9NKYgpqKZlxybv/6cAR3twAAghocU1HpEdxBA5o3PSIpiMSPT0eMSLEHjqp0pIguAACsFy2RjA4YrtllSRCz7U16y721YOSAvbkjSVe26afr9RkdcKjia0nhYqrA4KgusylyhyT8sFEBNAuGVkMQGoQ/54AJJkiTFAHFtQgOcabl2hsSbM1OniNNBezN1OIM1UKzN/9RMWdsY5pww5sVzYCJE3u37V61p/0rSXLSJNoKGybaAARXKctkg5NpIztrYxjHvMgSHg2jXUNsze9pyLKtPtZstP+0cxQXOimtbMMm96GMaMKCiwuLmlE7VsDjH8BEBUXHoAVYnWiREnrZIIHl0IWKvbO4Lcu+GJaqv0Ik7uCZUlqjf0Zs4gSIB6DICx5eepSFFAyhBFsTG1qXOb4aZPas6qXcWUuAO1MQU1FAAAD/+nIE3LAACJH1Fk6bLBpEPmOKiimCB4j4kU9HhE8Y/g5qNMENXgi5JILIt4jJaVghhOcJUQGNBq3cb2Qx85n9qaz70L+WnrqrQWC64mPfljRxJNq+y+/qWtysYLSwolNW9zV/WAAgwUtskEIEvBCcK2mGBMGR0cNopNE/6IZKfonuEB+m8qtR9KjofgukjIInlJDz1MzCJx6dVhtZEfouPpspWsSb//jknXUz9MLg5TOjz5ljejrsCsjQk/LJmwkQvmm1EHdLL1k4+KaUpVL4aouwa3ct7FfVtWm/728zfO3V23u2Dg6/20+ggiJyXArQ1QfkLlCZpXKjNh7C2G8lBQ4cK58hf/vnbBnf4dCohoTJTr3LUspecQg8GqxVtSj19KqbmCxFnaLmkxBTUUzLjk3AAAAAAAAAAP/6cATa+wAAoggT09HhGtxDI4o6PWUyiMRNOGwwqMD2jinowwjeABRcKSyNjJOJHUBJPCPQ+fcI1JG04ikSfjyEI/hEY02dc/nD4wMoMjgIXQtYtOImjo8IHCT2r61UmHUrcvUpeioAAAgnbtqIIXjicZGlWZBYQzSCVx24ELDqCu6zKq10cqqf+SbQFC7ELjhZeuudU9km4gXRPHlIcIQAKD3ZVtTlLLtLAS//8R4y9kSQLSUZzEMkIIMqcAY7lPl4/vReQXypXtuOY4SDGX0lmMLAX7Sws121x5bXLvr4RnThkg0VSzSESmNZtY29GhSrJBg0lx4DIn0F4KRrWfusKeCQdtP10c23/ZqjB7qUpUqoDHnGygaCjHKQ3MH06iDr1MSfAaKqzCkJz4GWmIKaimZccm4AAAD/+nIEIWgAAIIPE04bL0IgQ0OKajDCRYhcTVWkhMixAhJpqMOVVgFf/+IiVYk7XMfYQ3xQfNAAycJWMuH+tldAVgGNEwn3/ZyZQmcL8ppBb1iEsESA5ucxddErbHd9tNO5KbF+kjQ/GAAIOUrI3BIJCYsCM5JYfEBDDZS++icPHTvsjkzDM5Tf39RCTLSThVlStf0v8uXMaEKsOUPakvLpNA4QHg4cmwGYMOQAACS1W5rZIKAVXIiETIweeN3N9wKQz+2zvUaCzQENJCi8IlBxdI0ydQQQUSN9XRsh43FVsvGOGCpJrb19d95wtqHPCtJyxtiQpEwfSAkOwMK3kWXrKGuGXSVll5hPGsSc30R7pQFunX3u49j484RRi7V4uneae/RpWLto2972UJTXGKTEFNRTMuOTcAAAAP/6cATrlAAIggcc02nlGlxBA4nmYSJHiKgvU6ewZHEGjmp0wIk2AAAIBibsjbGRcm/cYWRNl6LezA8RqrLjfIsYaCHPMEVWZ0/7M7wekAxQoY5uipTb3Na3DSG01L3WktSuoCcl5hYAwalTMSkw4eYGJQ+TIDSigYcVIyprpuJ5UIoKWM1u5FKzDqqP62WaokPu1lMiVuQL9wvLPRNnEOqK79HsTq9/NotVOW2SDl5rfBVIlApdbUQmMimIFHD48XIkGB9JHhNsg59EsUUYsCCjLSDJF6HuP4pC6zTnNSLGC94o4+RaxL8MJeTKS5IVTltkgkiF9Eb3SB+xznmkpx8jQjB242jr3RbVKMdafPGEhwygo4HBG/VL2hgqLMwCbQB6aUILlBGJxZw4X22vpTEFNRTMuOTcAAD/+nAE7N8AAMILHFLRrxC8QyJ6KmGCM4iotUtHoEhxC4nnTZMNGgAALKlkbYggnUspcpFCShQsnRqTPTwiyVRitUV3f2RHd5gEF7hQWY1jK6nDhqkXtBtSfTxRaCAsxqUJZe59vWtFQAABAqNpIZKbkI4jADosLFrAeQYmFGqM+Q4rq1KTUK489qeiKhFLlmnguxiFJvFmNvQoUi4pQp6h3vRrcjvUxLX6MUEARYN2VtioZEXajBDuKFDuWgrs63B6sL1Vvvq7sV3I2lt6mMDor5FXPZvO6KY9rlEpBo6eGq5NClvROUVo/e48MG5x1KAU5JIJksoSVotBgsClUk4iiQGDEZkMWaoAXCRafUJjI81JRctxR4qh1/eaWCebSKj6ktTdX76pMNlOtS44keeTTt9aYgpqKAAA//pyBJ/aAAmCHxzS0eMq/EODmn08ZVuHsFNLR4yqsQeOKaj0iNoAAag5ZG2LBz7jyCKmO4F/UT/OcgwhVoUEXKX/37Jjp+pxnJSlDJ9MTOCIVSyaqUsnW5j1XHLmcU17AwlavWhzn2vF1AAAMFQp6WNjCTO0103CXARLhp3nEp5sVo3nVXtMHejy6f6dAOzmMLr3udUNFbxKggHELjzZQFBIdYLta2j2567r6EVROG3MiSVsyieFqxlDMluDNYUMSuC9Wf01ljQ4105u+kXZDTmoJEmtpxZL2hrtvU0kgUChR556qR+w4zcyKdMHb/9hk/kQRacuewaZEualkNSwQkqg2yf9B3didaoxjugQf6RVThK0+H9gkcYBVR8sKijz8KoFhbrJbumta/GLuTEFNRTMuOTcAAAAAAD/+nAEA8sAAIIDHdVp4ipcQ8OaWmBiVYhUTUEsHGqxDoqnHaYNUAgAWmo3dbJBhJy5ciq1CeQQWIgjzKFML++jnRFdKa7UBy7kVBqb3rFXPexEEiyUAFay58LoR/+5zGS+95qdcr1gAYuErI2x7uodG2d3F0xpUbryDjnJgjNIXe57JpM14J/o/oKImMFkLASQBeh/Nkl6r3JNQk8PMIt0UrjB55T9Vo849QAADDVVFCDUVKFaiEoeYRETao1PVUNw3uUhH8yOcNYUHaLcStcR/0PZ77xSiFWNJOaDGxKHUDbCahUmPI0UCqbZ5LQhT//8UIKLEMpQAmLJkDBE2hZxXcJiEHe5MC0C9MPlgtqyHJ5AuUQnhlZGbNIY3j6SaLepQ9N5/Ta9qlqvX/Z+pG5FqYgpqKZlxybg//pyBMwCAAgCERxMm0wSsD2DKik3BwmIXGVLp5hIsR8I5l2mCVgBW22h4DfL2QImAwWYvmClgXIoBy7y8Xi/IBrD5VX34oqv0fVwSoihCdP1M0+JBNvZOcVmUF1Tj7pNhPWlO93//vcACo0/9xCEHTFAHclS0A1soeiI2zGzwkNlabJ7MENXvr9DiYsgCm/756jYc9vQtWt4uiBiQgDQQGgWN1d/UiVCXJG2NiBQqoIDaXsRJJ4H1PcsZmFojq2h0ku43/xDhUE7PFT14u9JJS1HHl2DzRMImkPGilXRFYE69j3GWNSkVpACAdttg7RmQIkXJ0KqpbsgVHtXtXYwGO2pOINdoAEb8JC9Ran7JJDKBADbz1QnfJuFp9q9Qvet19LVsKTdykXT4UrcNarphNMQU1FMy45NwAD/+nAElRgAAAIHHFTpaROcQGJqnTyjRYigcT1MsEiRFonpaPGlbgQAEXMlJbJBhFklgeBOHimuma9b2A1WVZMKkQ6OPqN7/rQlB/wqgIskHiNEypzM/ATzxprUrnkGV3+7SLkMklE+AAAQ1W5rZIOyFWsztanEajww3fqmK0v2EdpLmgx5HIiihUHDiwZJjCkSsXWSFHjm0zAxlFS24pcazBAx13+9xAABAgnJJIKYHdz0acEk+TeDPWAWRYoaixh1tLsOsYirbKuJW7N2YgFYRMPoNqy4cIOULpvKqPn6GMoS0Wfv2OGdXXT0ZIAFm03ZHGMIsEi/eTFGBKRVypHFx9CALliSc59vUmHHWQIfbVqAYWaMxhkEiqbr0UIrXGKVYvKgYgAR8ebY9OfvvdxhCpiYgpqKAAAA//pyBLZSAAiCCATUaYx4HD2CejphgjSIXHFLp4ynsREOJ6WTIVYEABEmGTWyQaBxy5aAscBJ7CEOWOWqOfsUOMB8PT4TRQ0kVMqhFqj4uQS2kk97zTVqUVkpJyW08Vcdtm7Ebza16wAITEet2o28SOqtdlsjCA9JYWhBdn/S5qK+wQzGJ9RakjL5R1ZlZEHwQF+i3R/AaWMDjAi6y54yXTjLT6q0CIU7I2xgb8KCeALsekGkLZ1HH/h6QvIDdSPCaukbdX/S1VExGYIoNpSdc2q2c7hLA8NPqEA9U0KhDPJctcWxiG7dQBD9KmCXqYhRE4zDhgw4yX6gBM2mkZjWRV+W3Dvbrxk9Ght56/rkJDz7eWPDrw++9ai5baOUzs/bTtUrYqiq9ypvJ2dSYgpqKZlxybgAAAAAAAD/+nAEvkMACYIWINLRuBAsQ2OZ6WUCWYeclUtHhOqxDA4pKQeIXgAITRekbYWGDcw8ZAV2xYADFBBbPc7s8fcHTVBhZGgv+muDAh6OrCTiAbJ0New7VkNqFSrs3TlQFxcOsTpKLUy1rKwAACC6VMUNhhweCe1MtAifIDnsigGkwptYkfmQpMnFre63sGH6/ghTpqLU8A0TI2m7uTfLK9Yyq2NhOVqTZqT9TXLKA2U7cEhw9Qo+g5hDYlcR0uFIzFSzFtrd1ufr/PTqHSk3mdztHHQSWAno0VRRtVFSEpa8wxQYaKtJp3M/FBDCckbYYbwRiYbT1I2BKStyXqjbr0E7M7Km2jv6+jo/FDarWgqqg+ZA7G0pdLz0WGpOGlFAsEUYELz5LOrr0oawai5MQU1FMy45NwAAAAAA//pyBHt6AACCHhPT6eMx7ESBqbpnSRYIiHU4dZKAAQmM52awgAYEABhmNx2yQZLleZYCgIUDXFOSwTfkJSG/vbC5n0IDxZ7lu+F0OW+80Qbi4CEayIuAI+FgpBwsNucuafNBKJLRtDm4EAAAMI///jIWTC9w9Ys5TAVCHWEsVf0WFQ+MI6g0p2dDxPuIQs9wdhY4royKmplueyLq/Ww3RTchD0Jdc5Tcav3MoAt6gX//+F0G4Kre7CRZn3GMaS0LUdJzWAUE/n+5eLD2GAOc66a1xg//+2ixR6d0hK1WNOs1oXe8XKKcyvPsJUsaNW9iO9awCC6VJlXCkhtCpBxA5Q8pcpjcev0EL7ruSSowN9nPrSes11N13/oMLw+4y8YM3vWKuu0zG+wVEdXInkW0IQiriyvUmIKaigD/+nAEXuUAAAImJ1S2PkAAQ+TqUMxMAAhw7WlchQARB51sj5agAgACAQwwwAEvIeJmocW0EDBRzYHgivmuRACX55Y0xo+ecGx8G3APg9A0ThkcXCQw79vL50tkmY//kmRhMmxWO/xxRP/1IZ+0CSs0hlAkjdkHFiaURb7XcehkEF6jRYe+IN7lo+AcsAhAHxHmhEE4AIw20UgR/0Fvk2WSuPZR//K5QLpsbL/jv/9AAQAAJOXcaWx5LOB5BQsGJeOT7C91UVSDDwFA4scJzTzNEP1IXyDPNFjU58jXUk1M5nm9v/06f5GflSryDu6co+T2dYBLkvHEqF7Wmgri3BQTKfbTz5DUqD4nmlAwA6OFjRZJ2VXRCj6mvpqR+nP2I2Pnmcznt1//Tp/kfKut6JT8hfV7aExBTUUA//pyBBrtAAACFz3aUakWDELHu0owwqSIxQdg40RH0QIiLKi2ncogMhAICi4Ka1nkUy8HRNRwXDaxwleROXTWsZhnU8OUlnG3IVeDh5VCJgjG4ntwfVeD6Pyc3+/L/+z6k47cpv+hYi/TgCKALLt3GLd9B2Yj3nsC40m2Lf3jCZ0cSjul4sz3Jb2M/YVv5pKqoKfHK3Py8rcXzcravy+PqJ9u+pH3bUV5CR/rSiAAlSbjmc6wKxgZHLxrFOQ1FfWOW2RhLKY2CvPssmW0G1TUC69E4E2z6gHJ76F69Opud9W/vwYfgLfoLcVf0xeKVuUyBKEAhABJLv470zRjrDK7YPQW9Q/yX1BQAkqipnoBXck2U6kObxxtSXO6N/9P/1fKj3ObTxo2BvT35z2Qf91cYyd6UxBTUUAAAAD/+nAED3cAAAIkPdjR6zrkQserKjUqkIh5CWOjPOeRFSIsaGac8hAhARIVm48sm7QqPSassaouW9UfvyZfQhDCICsFjGLD6vMprfLfDPR9CHN/oLf6voS/+pXlX5v8q2pTjduXqq7W0J75JAIQQSHbuLKSIJybiqyCIxxa6jbjw1BRCSFFgY3A++VbI30FHO5T2/jIUc3n8i//MOyo/5rfoMeY+heit89Zy8Cu+sIAQEEABKXgc4qcvBwZzueI3Tfyoz/s2s6cjagYYRESzws2V8vxnu+CgV42fFbaH9/f//zOOF9X68wtlRvxJ/qOYecQgBEAABO/jplUF7LBJE5iGMg6n4y+S46KSMBEZUAjbyr6E+nE3+/PLczt//6NsNvbVdDnwjL5x3bsmpPRMJe8mPbQ42BqkxBA//pyBOlYAAACAT3ZUG0R9EPnqxoxolyI0QlfR6hUkRqerKi1CXMgKQAAFbuNYIoOmCjQNZOKOcHjzHizF5U4TeM/XqPxPV9B+nX05f6m/+gPhx9W/mGwYbh3ulN3Ws4KgdyTKY4LYBgAAA7NxqyDV0MugHaU3D6n0+MjWJqa4sBWuoad/Kz/Nw/BdG5eFXQT36+/9BtD9f8VyvoL4GcvFBQUaYUWNFNaQ6kgFEACFJsO5X1iDdoCOPY1WquPZb1vvt/IUM8caalc+Bh7eLymgrJjwU83B/683tqN/9G4R/6dW1PqCFt1oFyB7KtuJa+iqAIBAAXb+NsNLG3IRa54VRTqX4n5wsNgiCl4iunF76n4z5n0F83IPo/Tg20/36AH9OpeLHqgEYHXtts73/X70JdXVfNs3dkmIID/+nAEID0AAAIaOVhQb2m0Q6ebLS1Cesh072dGpPIRC56sqDeoyhAkAABNm4xqWEajqZq7XDl/Kzcw1gRpumxMIzY72uvojZz/Kn5nzD27//v/9Rb0n7/zDE1D2pn7V9gwTjnCzKwukAJQBECAAACs3G7Y1C3AJHpiiPuIpI4mcnLJB4W9SXvyvUWORcG/N/R/69ff+Uft/+TlegQ1+8FT9fzvOOf3S2WO82sV9EBKAQDNdxTOrcym4bJ5lDQf0SBy/xNR+8WDb4iPq+hHjXp1D/9tW4/1bQtX0XrtHP/zuo3w7mxNvGLyZoZQsyDqLkEYAgAAKcu4yuOxOSGasISBtxzjfg+fhWCPQk6+2LgWea2DI7qbyJtH79un/6Py3R/0P5xexQ6tYsenPdBNKSVQuVWxCYgpqKAA//pyBOHbAAACGCNVGTlpcENHezoFpzTIhQ9pRChLWQciLKg2nOogB+cDhhOYIbGXIEUD/2uJ+YZ/MO73+tQtcyf1NmbvM/DjiGyja7sW5wpPUB7N3ysmdPnW1n5R6s/8WcWq9MtAG5nohDEAAK67izMA0QfHLWKnygt5biKG1jwJcb9OUbhrvwqX5nK9zNSn8o3/shmr//n8qX8d//Nzv8/9cjI86ri94FhaOkQNQGAZZ+N9lnUAG0sGRbQb8Vmyg7lQpBq6j99H0EcB6dRPbr1N06N//5f6P/R+eSKGoRzuoXOEGYOMz1ydm7nn6u4zmAcoEFPTYUrkD1DbUyhjFDOEO8p6g6C5bi5ZUDlt4g5UWcU8dO0I9upbt0+qKjW8qMMv0/06PoenVOiuW0bIqjdMpiCmooAAAAD/+nAEHfEAAAISO1YZbzl0PuPq1w3qPIjQ9WTmpFgRHh6tdJOV2gAE3IJMEFwmrZHgIJw449kB/d9r5DFv/GTf52AWSCoCxMVcDAKtOBc+Km78l0bm9/8//6J06f578la7J55Sr9BVWyEgBBbtG9akKIG4w4zDr/dBV+a/MACaPCBbL5qAEWvbBEN8hLNiM+Ubl/Jddf4lp/w/AmwpVQOpepoo5q62zIKCIZpsKKlmFM4EmPtIhG1nuLDk8tycMtJo5/5wp7LlhbcEQc+oI5fb+A++ptLL8ED/2/lfnsgU2baqlaHZ9AuhMaLhp7C7RQcRBICn348qa1KWj0nWAUGt51bkOI4MRCh/BL+MfQd/Cm/m6l7fyj9CETZFYNYiNxn+GOsUH8duPhw4GsUGMDZ/GHR94pemIKaA//pwBKVZAAACIz1bUQo7fEPnexotpVjITQ9m4SDg2QgZLSgWHI4QNgQE1JINoQ31A+DYKBj14/bF4nrUWuNP6idwqv2w8BBbd8XltPO9///JcuWqi1Y5sk7qwTJkwo1bA+aIHUJh7nbLYpAEgAALl2HUXpUuBCdWgfC3WWcp6w6o5wh6gnG8KH6Af+FNr43o+j9v//R9E79OiceP1n6FxjeaosrOtud9FetKXc761EEBS38RoQL6OAor3DuZxY+KQatSzYa6dU5T+PBvlOrZQnzOj9P/z+O9U/if10IF6MymZ6kHgfG2g0e79HwN6PLa3gGIhJJqkFlR1oKvJxN0FvFnEkvw96Fdxo+PJlQFdOPNyHTlH/pziC3R9UdB4kbkerPzKQWlVanmhCOnFldyqY6fcmIKaigAAP/6cgQNrgAEghQ7WFFtE9RER3s6DWc2yBD1aUQk5JEGnazoM5TiABIAAB23DM9Ew9wDRdckGHPUNeOZskA1nazPlHnG1o8r/zdf6CuH/y+vNqEXVNfZFiSaj7EIjLvLpHqg6FmnkgM4ugJgABHd+NAQAoOKC8PgVFnmBP4b4qfKh/fndW5HryZ7xMF+RfKP/+3/1bXStr+dRC5L/zacOYtavm710FTZt0mlbPdsCMBKb/jSUCvmArhiIhGOG+O8DA9amc721fjj5j9ento3M69P/bN5FWNZedZC4cymlLoUWsDFsTPI2LdYeMZEkpTAWZduO0h4w4DDSULAhoLuHOhQDfCxbU3XjQyoQDnAj6v36d36d+rf/VsTK9Xf0jTI8VF3MruzXuS1OkXPESQZKpiCmopmXHJuAAAA//pwBNofAAACETBZUW0T3ELHaxok4m6ItMFSZ8ipwRUerGizlcsBMQiSCy4N6sD/ewUJONFa3qpc49QqCdIn1i11l1sinsrHpslyp+P/toL/r7f/iPfyWq78ghUHBM5gNMOECi3ixM5ACJACT12Hq7pK2gnJ2Li3qS4N4SPiEzi3+VGNBH424hGdT+V4N/5ejlruqskQI1H5H/UTzDqzKigopKzdcYM2cuXWAyvvxz8TJ3hfN+7ClqJawjJV0BNHzA0qBOhkDA2UU1VGr5GtoCoskDlwGY0LCXD/VuDd/5h6adOgmQQ+y6GKVK+jmPT+qAIgACpb+Ns5ltOB5oyaLy31W8M6AY2FjuD/1Hagbs2Fj9U/hEfycz4x//jG5//ycRfcfRCmqcZDj9xLWahLpYl3KDZ+6TEFNP/6cgS4owAAAhc72dDnE3RDx3rnGaU8iGzvZ0McS5kFpOxchQluASJEhOffiquDGeIrPBV8TCvjfioOR5LjZv0Xcav+OgjyD5jak4X23N7Z1dUDWpshaqSk3QXnRZYnP9BiAo4URszGLggABVtw+ucTFwNDsk6waUdfidbD2GxSjqOoE7dRXhnPyvxX/ftydH/k7vsHnREMlNEPw8PezqvpU9gRCySBB0+ZLHhwGQnIBO/8eucA22DsriEvw1yxepwIo7C3gtX+/N09e/BNz9Orc//4/3QMLRp3TdIQEmw3nF0MQlvup9uibsp9H/g/eoUgEFqQdbiNLHgP7cMRvq3GuoZjVEKtoMeGbBnwQHxL6N36dG79+d//q+i83vz9XqGpcs1meput6PYc0LobPsXzCYgpqKZlxybg//pwBEV2AAACDjjUGw85UEHIKyodYpGIaPdlRBxLkRKg7CiVFTcAB/8DTkvA1sINLpoZ90icEiHvHX1Z68hTVEsiiDxx9QCnsJ2x8X5QAktiM2VM5bk2x9P5XqX7J7RX0Tp/jzyO76YAhCABUcguiixDyQkKcopBvLclwqJ3tVQbAuram6cF0LydW0E9OFfBk9teFTf29+G6EUzGe7ahjrXiZjZnNNqtMqAzFJAKbfjOGSCE0xmOcdD+GeNeJxflD9Q7+r8fr2fi+Xs2o/J0f10owVrIvM2jaqzuEfieLhlSBWB2tYqTSQFFjYumAEhQAE1IPCqQpRMBpotNBO5hPxAl8VQ6ps1sQO41uXifM3Tn6+/CfV9V//L79v530eqAK/KaYc9ZlG6Fy7uH+GLTB/dpiCmooAAAAP/6cgSJNAAAAiQ42NEnK6Y/p3saBYcWiLzzYUawshkXIiwoo457ASIVgF3fjuSUIfdGpZpQO/nFec+NQ9aF2cYAZhGvG9Sf0fU3fjG1J/t2+kn0uXutUBfFvi27PSUGNGPvlz2eafR/25/ZAgFAKe+4jKq+jIpidsOA3+IExMHrjPKdepfjT20DHM/25n9U7L+hCrCY+aqvXRCAyeg8O5Oqmnfrw8tZEqsMgBhGQi7fxKamEi9hwsmTg+FLn7zDUNARbdpB5u3G9gFbGvjH1bk4xuX+o7/4z/P1fQ3JsNARuNMafGref78dNvDn5pLf+kACCILlv4pNJCsdAcV4uC1dzdQX2xcGswnxW36pyfM4gCFKHNo2UJ9+vt/9D/6v/R++oJYmKOVeFFGavvpve1Xtp1KqsnExBTUU//pwBFGkAAACGyfUmw0TVEQnGtc9ZUbIlPdjRZxUuQCdqgzzFhIABN2j3ErtJYBzVghNPduFVj8bnTbURXWoCmGxBTpMyz7VETlbYIRwj4Ifk78N5ODyX/neXloBNJ80SSZ2tutPsQi5EKkCFbsPXNFbBfyDjZqgQhq5ef/DQfiz8eLtAD+oZxXhQ7Qf/tobv19enbpTJ17aRr+OSIpP3Xm5GJ71/qe/xfddd3XAAiGKJccA01aGmt4F2NgRho7k5+p8ksIqRTNAy2D8skUtuAe+c2Z9BPLw73BAO4J8nRq9avTrd6W1DPQQ9FFPz2pLafQQCVJB7NdCuXEYxQACXmnFOMb5lp/w6lX8HPyxVTFBmhNqoBdA53DNQG4pxo7RuX///v09SbdjYwXlvY5P0ez60xBTUUAAAP/6cgSZ6QAIAgI72NFnEvw+xGrKIwowiHj1ZUUw8LEiHuwoN5zOACEMkkxyDUVgn8uAga6jgZ25XicCpyj41ucG3x3z8qBnHWw78fv1fv/BdHdvqb+R/ym3HFUO0NODngNSab9M0pAMACAFZcNRsoGBlkJJGewMPsW1ANfICfQTDnQqWyr6PyZtW5/f+hPLblNj3NUJP8/EFHX7cCpGUkwKIhm1DCPZJVcgkKni1sG5zKJYm6NylN0SVd6uwWfZAp16Py/AN78Tvqn9i/9+c7smpyVdGnMf39+JPZ8oThlaHrd1Fnv8fc+jAJgkQVHINrkxDifyVsChfM8LcoGlcQAFjyRszMOl/4i9WyoJdujai7o2raHUmWt1flf8/UUtdB+sfg2hC4CigQVde9t7HqYHUxBTUUzLjk3A//pwBFIUAAACGDjYaQw5pEIHOwoFhxfIiONjp6RJcRQh7CgWFFIAAIEQoFO78YtuFOFAK7aENVM7cVkSpQRn1F70FP++gu68KvqvO6P35v+/9G/mb+hCl4mFgNeeWQaloGWZeLvEt/LJgCYEAJRyCjft1oBha9wgWjl+K2xM/G3HU0fTxG6NgaARp7ltW5Xq2//8Ybbp19C2g6au8O/Hzb2Qc775FH8fqf/UAAEDLQILrkHi4tBz7DCnkXB3fqSP/iCrgca+DZrm0CcGK5+E4PycvOmoLu+FfVzWq2Novdv0FysWLcd2C1SY40lKqlOgDeJAF/fi76H+AGVGUzzAcDcc+FAiGCwRkh4v9+I9G4L369T9P5hZ3OukiIh7RPo+yanHaPIxZEe70PQZLBR982g3d2LTEFNRQP/6cgTSewAAAhQ7VRlzEeRCB6sKDaU9yKTtX0G85vELHusdAx0TABUuoxmQTgvLyeEbkQPRQo2+f4y+xm64/oLs2ZsG2oLi2wgjQLzcG3L0//9T6M9WbtuLeod0BSNS0KoaJCoiRru+uUFWiinHAMoLdGcFCePWP9uYJVjuIKpWXVwBec71Gh7nfcM0F3wFfZsLAz7kwod/9vLyv+69myC0hRZ+u1MM6U89QAJzCADZAMAihJQAFhDtqKgvuHC40dlB6+JyzLMbK9W0DPN5Ru/8q3/yDVRmMdTGcqH3RUU0V7umVCyVGHKIG6jDmRK0fCoAA27C5YTQJRywAtFFKbH+2DcbFsqW1CRse6vq3ETq3IdPbt/+b9E0yXXrvfjxvWkg+DoAh+XKLn/fk/j9a+n/r2mIKaigAAAA//pwBByEAAwCFTBWOeoTfEJnatc9RW7IRONUZODmEQgdquj1CboEgIJibHY9zLMlGUAbU1bLhLfLcQfGHF4o5Ok4al8gI2nAr7vhWHP5/K3G/yf1JYIKaj3rOyKZtbVvLH+tAokcga1cKiAptgPRqo6hTaEbV1dytPmPxJL5ZsgDUkeMej/xX6tiMDTzW3Lan4r/MK0nKxKq07bHfsSndpWBzwb3dvx+n7lUIBNuosYTYXLqCcWy7pIwDhnQjOURNAUGEqHGPB5ynEwzqPczlDdX7dG/+Vbsmvct26v00LUvgkseHSaBO4i0gd/0gBAIgCrqB9tURVscz8Aco2maIP5PoBhJxFw8ArjJkYuso/8RD6Ni4T+R9W5Ob3+Vyf1NqZ1zttcgALIDBCKnOSN0piCmopmXHJuAAP/6cgTR8wAAAiMw0ZtLajBDp7rHJiI+iEj3YUG0R9EQnKqdhokzABdoAuKqvjYb5ur/rDG1iIHWwCz3tMBPvYatgt4olSjE1QcqEta62w+BnVJo4dhxHtJPWPDpPk4jalc526v9Z7dZxFKQqQAd2w8JISIZmoDXFd5KDa1oaxzzXdtMTIrHkjA/p08W27aG5P6Cf6Pp+leondE72drkDOkw7WD/yNrjENuPLnlWkRUVjYKm/4xQN2R1AwTzSe/P1TfkUnrYd5TUhCy/oPoAcHw4+heTnG4vryH0/ylXH6v31A1xVZxz02ycR6MUIuQiqqARIBmwH3a9E1GzRzIVTNIuHQV+3ESWoIoIrUM4xEce5/M2gvl4MbRefo2j9f8X/vybJZqFMgpCyC0QCSqRuLViSUa8yYgpqKAA//pwBOeBAAQCDzrVuTgppkOnKwos5XzITOtc4z1GGQWcrGizib4BAAMtwHEsXmDkCQUdT5bjsj+Ik4pwKGrCIcfFOfjB+gD/wsN6c+oi2NGWmbGH/vxB+vR+2cZZBD81Y3YMlI825uCARmIBP/8cKwT1LXBg67EIz6hvB8LHcm+oIlsSP9+Z17hvr3LaE6f1Sl7/MAP2Uj6eEh9iA0Bc+/1Lc11V1+9+fd/dsFAp3fjh2qdhwbcI12FPJeX4jPw0LUK2r05UL7+Qjui83kHVuv/09H/o3ZK9h9ZZMDzlwKfHSM0Qfkt4ZGEPW5p9u6BgUgEZJBqqpmyVMA8vsEK/8UrUTjNQoFySoE7f24u/iEHXM5Utof+vv/WtDfojI9mSccVQRLvWff1W98rllqHj0xBTUUzLjk3AAP/6cgTLTQAIAg4419DPOYxDJyraLecmyJD5XUG85pkQnGsc1JZKACIAgAxuDoNo8/rHg9OcKDNEHeW482JgRLOhfmdC2oItp79/5T/8q7ei+f3vo2i8jQdstRWxupbGjwRccVWpoZBlAEiEAVd8NythCYj0NelbFlrzj+BhK5gzoNj8h/VuJP8qMd/bkuf/Ul/6V7poWc35pLXMEzTEECV0ZQlDbX1b77v7tEQwS9vxi4oRXQOpmrgzLd0J8luKWwUFo5QfnfQtofz+jczmdH7f1f6XeYK7Jbn6+iXYp6jjVs8Rw9mx3O/i0benrfT8gKWAHdsLMSQl4/sWg6C6fh+I2sj8e7ZweuZQlJG8I7h+/Abr1Xl/25Ojat9tenEEPsl1bEg5XY5jn0kVgA8iRQZACdqExBTUUAAA//pwBPidAATCCSVZUYMrJELHWxMkYqKIMOti56RLkQcS68z0ndKAAAAAptuJpixSxCZhK5DGQK5WAGwg3RcrChfib6op0Yj0Jq+QUnt31dMQJD9BRxOFnOygDLswIsPLf/t4fnRStIAUu3HmkgE1rk48sq8YZq5ElWoe9tpbe83OhzPeUX6PYRq+FCTze1UTCEzu3p+fSvyMc4frVSQYGdE/+nlwowwAZ9MAALt2HhxTfc25qI8qNnNcHDEVhsS+RC/pm0sUikaz0CF+32kgrweKAl+41vl1VczZC/Pq2V/VW1/8uQFD/qf4pUAA7Lh8rx0l4bCoJuneQ9bRDLJK1CgamdMadE5b5p0VNmFz2JoVTHBmgbPlXa5av/vlC1CJhCwwNOVoo9bhNT/+lCYgpqKZlxybgAAAAP/6cgSCmQAIAhtJ2JnnEvRDiTsqPSc6iJkpYOwcS9EEnqyc84oiAClu49oZvRthsIIu8ApdEgZlVxVqXHyy42bAfy9BLScXjQENXxhpzL34UVlLt/1Lu/b5//EaCcr///bQuDEu+VN7A5AACgAMt3GL4nYZBBEscOiLpGPxgEZtiHIAepTZoScq9Xx3R6BTXjhtf+pRpQ/P/83tp106/pxrqv+vrSnXUai+/YVQEABy7j8cF2yDEqiljsXGv9A8EMTok3VwVUpHEaoQY49RVj7zAX7Pn//vhOv5O//RsF300fQ279PX7fROLckaYUCqaERkBAAXd+PjvE3YNal8EN1I4f2GvNa7fKh2koLXwp3xVfmgf+Hf/8H0y/gun/cRgn304Z7jtUfehc4uHSyJ+ypTDyExBTUUAAAA//pwBC3vAAiCHjzYuecT5EMnixcl5yqIlOtkZTzjcQYebJz2iPoA5ATuwHtkk0Og5iGLbGP658xdeqMRhs24ih5VSgvagEWlHxrlcuDrTUSnlHbv6YJs35++nV3VsrIn8DVg4iwPp95eVgAADLuB52NiiyBFJDhDrjYm+Emf1NVkoGc+UD3wX/inVpwUejY5p/1R5QWtOO+nfv3ubu+Z31L1ChsqE0Vjhr2yaCQAKjkFoJOWDfIlWwRaNHzD/akC8TvRxQ/Db4lPQ+hJZrROHaajBed/1Fue2b6Oun9XROVmJ+pmVI3bnPreMWIEELNh/oEAC9vx8Srypq+IlisQy5rohlePjsVHVUM25KPRsFq2DanR6f+2h//1GzE1oyJKP/yD5B5RRtyN9DXKMiF9rbVK6ExBTUUAAP/6cATk1gAMAhY8WJnpEsZD52sHPOVciIkfYmC84ZEOHGxo84oiIKmvA98D/eZBJo1PLo9cA6f2wOFdyBT+nloVzpp4MVrzr5BteQft/UbI2Re7iLAmzNMjOC5qJ/6j4MXb5FA63/SeLgAAC7vh6ysJj8ozxfSBczKWxAB9xKCV0GUYvUi2GmlOVuVa4npxMfR/+bRNP06dtci6D0nfN3wOZYKJSHyBdlmSp99IATu/EGScKmUEKj22GHqfSi17DXNhO2Mj7LE5mgZ71Ra5gxpyD0+n8pz0++j6/9V7V6GvKps1E6qypZu15CJk7nRdZRUACCAFTb4e2XJdYGy6b4Qje4MX/ZTaybL5JxRh2r8HuUfPWVaYCjUfCJ0/wugjcnunPZS6jYrP2XXBuEsTJTp1Jih1SYgpqKD/+nIED1AADAIGOtkZ5xx0QqSqs2EthojFIWBnqK7RAx5sHPYpWgAnv8PnB/rd06RcRtJZVsadddoVrSt3UZKykqMcPyj0FurThL/J9vfy+d0+f0TfvWKKANaEc+w9Qikw8lalbOugACSQD7NRkqLOBAeKrCNFEQqW7M94+LKPwWI1/Kdp4aoPiifoia4eP+gEi5XJ4fdT5mj/7KTeRCVgg9+qna1f3Q0twAN24HtKX5isEIFVdMBGaJhT5wepNEYJySopaVxrwpMZoD5GjQC7Zmr/xENuq1L+KUjnkornRHoLd/x+MDcVpm9kbQn3HGggSE9eB58Hkhd0Sea21BWza7HNRb+Asefj8LLMDnFzRcO4E829BK/Z6t/00P1/IHqxqo+7Xf9/7Z2aTTOpLsJDExBTUUzLjk3AAP/6cAQiogAMkhA6V5sPOrY/R2snPOJciL0fXmwkrtEHHaxcw5WrAAcvA/HSuWzTBIyGspUPLo0lEn7wPNsmHB7W5XiqhugO8eao9lMdDm+a3/52Y+379v/76t9TcoZnSmgexe0gSvVEgQCFN+B9YaFdeiWYcDP3HMoJdQJm1LleUH+K84vgs01C9thFW/5moTa/m5e+Z+t2WXR2c4zOEHWG1gJjiHiqSCndwP71X0N3CFCoJl6Vkbmn01t8I7bwwi53Tryvr77AS+rYqlMQFdm1HT/J4rjO/5deT9XeK6l347CAF2JVMtTavep9h2AQZN8J4w5CeE5DARWApRYlYLScqcr7g3hB+C8xinfVs7f/UbjBXM/70No3PYeU1lQVvNKIOpAiZsbNLDqT6//qhpiCmopmXHJuAAD/+nIE5BEACQIXLdg56Su0Q8erFz1Cboho8WBnrKvRFJ5sDNKjUgIIA3bgfXKxHZRwWMKADViQ9TduIuMiZXp4w9b07dUCsM6Ij8wpn47X/iYaiCzb29tXxeXQZaxb2zMGnCA13PrHC9sCgSp9wPTn4p8JKGp2ocNMSzPJaBgTReJhjnSpFxbahapIdKNYSGq2LD//V3gheR+zBHqpphmSBSpmbN/4HgxP775I2QXPnBC0IegbyGs66EDsqQM5WBqspU5owhuot8L//CcWpybDr9G+Ai8j+vG8uqfY2pMrasp2S6VZ/Q+NFDRru3ScmhgBU34FahNxKnJxSKEC2fUO16yHkseyMpztZB5BfPaF1NNxHvVxcm/+oE0NmX2JxDrqVXWOUizK7to7jBQbY2zegw5UWiVMQU1FAP/6cARlgAAJgg86WNILEfRCR1sHPOJsiDztYGecb9ERHWwos6KiAAQhAKb8CpYpAisI5iWEUBKWnF8ikzpRPVOk6vUr1R3VCahkcE9QLTinp/1TCPl/L+r7Kyslud9HUCqFcoxU73OSAIAObcD4yujE1bCuaxwas0TypTCpriYanOOUN4laFsbqcjWEGnGLQf/U+CEZS+dX11fpdvSt15HkCG0CkWSpm8P3EBz55sn3sMUh8bQeZpGr+VK55SriekTE+DnRsW571Lb8eL/26DGKn3/azgvqfJPNySEo9QvB3gcdIaNPYCSUuNWoCEgK/8DbjtIPYsQJQAr42X44X5NuF5XJ9Rb1CHRsWK3OFLU5hf/6kLiAJ8/60uo/U7u1iPxiwjTN6bj6kIgvarOG9ucTEFNRTMuOTcD/+nIEr3YACIIlOti55yteQeeq4z0iXIiM42VHnEtREJ4r3PWVqoMAEuSAe2UObNnINaKvF/3JXVgz1rNcrKC98HWjUCew66DD5eJPS38YKzOep0+mg+/S6KjN2IuRoK9RC6kh01KtWFX/mQE7uB7ZOIeW1JpOXEanTEDFnCPdTP/movQ7TLf8H2l/xfxt5TPTjm/p5dB9bebd/9GaxuZ/W9Qr2GPgNgjKG0rhwkCEpv+PrnCleKYqpLl63I5wxic6spShcvQJeWqW4YbRsbdv9W2H0XrLp0vLlZSn0Ojmqgay8kFYxepDGnnsrWmwlesYIEObcD6wcJeKt1HcMmmmthm3jKoHhvLFF0uCev6Hw7wTeFR+IN8AWqOT+iaE0dvY+zojc6KO73+1ExrRF47iI6crHhhMQU1FAP/6cAQF/wAIwhA5Vpg4OFQ95UrzKScciJzJXOeccZkekuvM1KiaAAduovqNTT7g6xvJLHVIcm5zscGMIGrSK6hovglq1TULXuBd68/X/+UfN9a6Zh93VaNmJ2boTqzodcRQ9zxRc3/pABu3AvA8AuSHBlQb42Fd50g+EyDjhcrUPL5/0GyFlL0EP5z1b/R7iAf5/WUgRLLw+lT9tsKCURoVYQlGsixACXtwPqg1EKwPoRCSRQ4X8/tWb3QuDVlYTVOL4w+mWQsVaFQMWvPb/6dH/wmtBHIZ/IRiNpmS/t5bljs3sFJKIYx2uxwAntuKEZApOuHBiwNcPufYIXUMyvFzs5Us+bq2qGq8jEb9Uq3p/QvVzw5t4usOBsi+Cj0GCiytiZde0WUoBEDQeIohkikywkmIKaigAAD/+nIEgH8AAJIfO1cZ6zp2QmYbJwUlDoiQ7WLnpEdRBhnsTMEOYiAXtwPeEQsxKhjipc7CayBX9IZzjbHrRq9IGdqKMqK4PxSLYFW+rV/6dX39NjKGHt0ITTEc+r7+Ocg/FzD4voXUuPcsA4Auf/idBcUYXZb57prbZwv9FjWQKbir4k1W2j7xAJtVsaO/p79N29Z6zKrNjjkSou1ph8apIcPWVsYgrEZz1K8GIBAFTfgZswE4bTfB9uJI056oWPaXCXwPTnKKRSJFKOo4ig1TaMoIXbeUDr/0JsOp2+nUjX6NQQkS5DtrwFiKKxddDfxcAm7YqCATRzAXh4uZjQyso+5tODVO/0AzJOV1CiMbNNaHR2Vj5//4AHpVHVL5lNqy8UnIyYQWFRNcTmC9/Qhb6noTEFNRQAAAAP/6cARabgAGAgUx2LnmE7RBJns6MMKEiLxzYmYlp5EbHWvM9ZXagAAC9uBmzAbqWHwPlBqOO43ozHHGlOD4TQeuYm/HBYpcgMN42JWlUp3qb/6vo/8tEdSJbrwjyWxuSoogle8mLWQAAgAFN/xtkhiRwKDijP40W74fvcabJLRPvZN2qxG4sYzDZeyyfzOj/9O6otNUS++n6DXE20FDqDm3Voy0WR4zp2/E4mKh4Ow6BuAFxlYPEYLWkDP7LfloeP+kYnM3hWrlbVOv1lvvM2+t/3jQPnBWA5NnTRegaaSUFR48qMqvDCUtrDcRtIAAS7gfCSYEQPUSUpxXXSjxI/EkrIo94YrthexqqGQH68VhaEFcSug5SBO6N33/xr/KWrV369t8x2Qz/NmvFKh6YiC6SqitCYgpqKD/+nIEsaIADIH7HNiZ6RJkQWZbFz2CaIi45WJnnE2RFRVs6MCO2gCXtwPpvLyoSMmA5FFGXOjSAM4cf9Wh/oeP4cIye38ifwvq1oGALMCHqN9b1v4CXNmi2Iot66bmsFhK69jWJeAgACfcD6uhKEhtqhBFBDefFijy5KnCp8u2henItHKPRWaz1CqkacJpfJ/9W+n0lPZdHdLOc48tbP6XihjEgZ8TqAKm/A+zPRyXcC/TEzylm5qbHKUL43P4WLuJhWJxMMyPetWRlMCXVHhxNG/wSZB0f5v17XSgUuz7u5qiDgxR1qWEyAiekVJEAGf/ifTnRbCUJlRPXHt80Or8Hs4ZOZXtpMES8lRypik36qj9yD/8l956Kw4VKio1hBTF0mhQQPA7zbd7d9TBbSu7i6UxBTUUzLjk3P/6cAQAAAAMgeoZWBnpWsRCA6rDYeY+iLxzXmewq1ERmSwo84mjAKe3A+TtVLUVT50XBhMhSyIfdCe7yHlktwsdFIpOxvvj9aai6cXxas+a9Ej9zAjkio8qPFuZU6EQ8pYEQkAB2gD7zVVNWlClhGqTBeqkVbBJAASdYafc75sTzZcevVax9H3ziaOWO7mGT+yRVZzW8JdH6SjGKngoRJXirTf0xcAGb8D7iDzQKvTsQsSmLimE4krObF07l9t0y9Ayeh0o1ar1dBfumlVyhqzHS4uoyXjgeUPI50BhsPPCzYHFBXniqaFOY8qYAAV34Hu1msgAtQjhiFlKdEfGBYgnhapp87PlC4kiYZhxsGPh73qRO0zbenBk6al717v+tRbUm34ZRxr34e/o+f/tWWTEFNRTMuOTcAD/+nIEnsgACAIWI1gZ6RLUPYY7Kj2HKIh0k2bgMOFRIY5r3BwsKgQJvwPhIIpXolhySGgz0mHxAX51B4uhaXpzNXhoWoQP4N5w0daHF9dR//g1k2WIKMEWtjAaFX2CrgaCYZHHwkLfGa4ADQAKu/A1A0qGMRdEGxm0cuTgMVNn6su6l6Eh1Hgc1XzO6RHfvcN7f6kefaya7u6J/SW1uqUbQyw3tp2mCBJX/8CchqmGqChDLiFDY+sLXrVHUsdoPloqaj2Iy6VKgsWivUv/8xMoOonkqdKNhKTo0sCVvNXNIgs0VQ97mnNdZFwCAgntwJ3F7woUKiex1/aqzotzNT66gk4OJdMMS7y4nt8WGUD27klsVVvcQH83okq/q3v2kUOShYVBYyZ9KVDnBQ+MSBBs1KvTEFNRQAAAAP/6cATVtQAIghUjWRnmK1RChssXPWVaiEzrXuecVJkWliuNhJWiKDv34+G544QmrRzyENXMrGfWNP868LLDS+4JV6DMdHbevdgX9Db/5B0rc5S4IOOmi51n3uviqowewybFsYEF09O3aAgEGb8D4kQhUBmBVqldVLArcxEjXB78qbxmLOXnlVsiGWdto9XlC+uo7/6E6f9dG30nZSLQt3ccEXtoJ3VFyB++9KASZ+APVjKVWwYbggGoNM7tPy52XpPhup4jTDTVszso0aIWn6ZcfOQVAmd2hG/+bx7b7lGOyMSS8pns7XNY3yJBoXBDm2A/Gaa3iI2CALbw3JGjulzbB6OgVCIDsemzoQqNuEY5xEeo5aGVOYJWVmjhN1Hn6Nofv69nSwvIIUNg4fYryJn5kbQmIKaigAD/+nIEc8IACMISHNg4DDhkQaSq8z0ldshQcWBnpKsRHo6rjYWJagEAFT/gBeqKKQVohCK4ARBgaIWoV8Vn2GSodMJCKJi1Nf1apJM9kFBajlWqe7DKJ+1pxdgoSJKLjQi5cLR0BsStA4FK/gD25gJsDXCQjbZYI6j1w3H55TE2wqb8X8Ob8sVi3Ud3PSo7UBSijMWiATt7d3qD6IGerdK2V+ZsTTLz8XxLqAkzfgep2wWCOYLOXWKFsuFlgJyn/sZUX2LdJEdcMro0pEsyjQd9cY5ng1N+q5LkrWMBcuKmC408UNipAo1iGk0WtSAFLth964yGDhRb7KIzFVUD0KDcQeoRdwKOhGFCFhzUR3jjPzxev4d/hAGl9EjJpVSujeqMa5aDNA5BoWjRZO8VQ9tVwpdNaExBTUUAAP/6cATzVAANgfgc2BlpKtRApZsnPSU6iNjJXGek7RkbGevNhYl6BKm3A7WCZCiGSQuLCCAHItrhLba/RP97Z5JKRcu7nd9qFRkaIgB1xjK9dY9Da9cTT5p1CKEr2wEQKKTNgZAAAIETf+B93T1R8CfJbrAOI1G1PpN+GmPbktEDqhko+cNxlOQQ0XQf/+P5L7ZXuriMXJpJiQERtjHjmCF+vbe1KVAAz4KA/kLThZHkn2MGkf0speMTN/jJn30z0zkjo3nVP2V7oy5wS/v/+Pl81yqa/ZVmOrHTkZRp93N8tfI37z0MpAqxrZrigJV34H8xabDwygcNUu1WJPoYFb9WuQfbni2LmDYrWLcqM+T3q7K9Mrri6xYLb063xahs/f0V1Ub0GEwaoSaaAV9A171WlM2mIKaigAD/+nIEnzYACFITI1i4DzhUQiZLAzzCWshkyWBmKE24+ZFsHQecmgBFB3fgGw2vkkRiZxYReMOYSvlUxwlY8qWMKVC7oBAXKISUrKGNoM/lX2TvxokiS9dxftJBdijIswisUFlnBHRsvWEXN+B7ymtEFvCSFZqpHK8Cn+UPyCTlP8Zkx6s8B+HHwyVyJpawfE/8neWnZK73RT3fcfHZsGwWy83lzmOa3zLX+VAJckAnCsWEAlhYUD4pBKVmHytYNMnJbOqnpUjLwnGZPWkpUqK1kZoV6f8UvWdHf9dklemhh6k5Kar0XaPfi9/3p6/kAaVmB0aBMglo4aj58ZbI3PHKYHYRNMLxihMw15QZqZqgy25tr6nb/5Q8sUPB2PO0KJ5U8YLPnlEYoueTph1MQU1FMy45NwAAAAAAAP/6cAR/XAAMwfQx2JnpEuxDRGsDPMJ4iKi5WmegT1ESEqsNgx3aKDdkA/Y01KiVGmq7IdKUoS8oJfsZVj+/P6YfEiFqLN7TFWtgz62gx509fbV45Vb00K9Qw+2Djl7xW2tXr3jQ0rvwPnnGtC2CbkNb5TuW5HiV+FX+LnzrK/Wi6+UvnK93x11h3a5Jxv/oNSA7EpHrSNdYFkjWMMly6HnGMQxd62MNIBLe2A/I3cxhyA+zxskR6Go+IBt9dCXVijRg0NuFy5sUTw5rZuH+bgQwCTpNRSF+Tg+r/0XOQ+4UczaZPScgu6mm5j2VkACpbgPywaw+grgcW6sssJ8t7Hpa0Ps242c3GvBBO7pOw/JB+jZ3qoRNMOaYtX9PNORS4LX7Fum7RKYc1HfQxMDzrXxKhMQU1FAAAAD/+nAEWr4ACYIULti4DyhkQOXbBz2HNIhou2JgMOGZGJ+rnYUJswCBGX/gJGV4nh2x4tDpitT1k7eeTMxkEx8NzEFcY0aPqZW4wC/mJQ/vyE7pf7H59RAfCARQgooew97RYSmg4aNBHsAMAZP+B6wD/cSFgwVtM0LzLD0PKh2cNpx4mDfWX3p1+gi6Jkzqn+c3O+32qjullMJEJdBQG4xN4gWqgJFacXQhwadSlE9B8OSOmeGf+gL8KZ5ayDd0GaHINXoWikNuYx5EeeYHvplFq/+UN1dLfc61maTO5ppXe89PP/DtrQyhKNVxv9CAKu3A+1QN2jZItbK34/XiEM9nGvuhHmFcdGQJrUjJC9Mo+naoXWl5T7/3CG1oT6V9kZtNNp0Re6MtqsrqyK4ahjvu3rsZMQU1FAAA//pyBN8NAAyCDi/YmSMtlEPl+wcwwmjIvL1eZ6RJmRQaq8zzCPoBObcC1gwCRGOioCxPqzMyRrUwjde6R4kf2pNGZQO5CWGTFmgszI0PqYqe+4h1/3pWi1IYLFiV6lvdcgK2UCj9HFgEABTbgQtGBZQxED4I3jgkwWdJFEyEsKQZ/o4/vqmg1mNIdTN4X+7qT+4T9X96edluFHHJFude67i4x3+bJGh0nOek2ARLuA1ockIL0eyqPdHMg7xtC7UUYrJOtdgs/UteZ3QVas6khDNodnTVKf+DFk2pX1vb6QBb38bAX8vjS7I0ZvfpnNN2qpAKl3Ab2YsZ7tAPo2G0gaDS6gPGucB+5anoprUosnOIElYEKPuozm1QWZpp/uFFNuiF9X/6AhNKCDJVRA5LyqUrqK608WTEFND/+nAEEF4ADpIMJ1eZ6RNEPuRbBySjtIgcyV5nmEfZFg7rnPQJMwArtwGtmL05ujfKkz0E6j8zUXLq2pe3Rz1spRm7jKprBDshm+1dLh8tvu3d3inljQqrNsY5s84l4aHsuKk5p7iREAgAV2+B1CBQDIgHJARErzOJuS9mvoKt+s+xvuyjq7ppSMc3vXzhFf/AhqOc+lHHB203el+iOWOuITA41KF9//Aa2Visxn6UY+wuVsQTSvseXmZctt53hyAhaJt3Zuy09AT/9SrqpWb8tDpMcMhnZ4LshywrhOjtX/r/UdljhWREAnZTFFQOFck9RatQEYew3fwpWBkSR1vMeNUG44x2a8RZYiEMUawQfBq/6DnmkP61z/0GDb8tVV//bm4IzA6zaVoMR+3tMQU1FMy45NwAAAAA//pyBO4UAAkB8h1YGYMrRECjmuc9Ij6IXL1e54ytkRMOa5z0CTsBO/8DUAbVQgcOAA3n3dXJnPVKxZczPeigwpxaq9mbRx8u9gVgwnUA51ukgLMHkQ4E4bLH3WIHzeAjkdtFY8AgCd24EBCDtHU1HYd+hyDGJ6SUoZRQA0EKhtf33zWOI2WwZFdveDc2joEAqux+Czr2J6I/tHtoXIJJtS+x7EpegBzQFR54KOD8IO1xq4bXe8maP4uSEVMxAWxvfNCzjhTaQ8Bg65adc4grsR0pRr3vWlhyCgmDAtJ2EEcRj3qdsOhuXBAAXd+AvQRVkQxq45aI3MqQWfnBHLBladhJ3yzmjMchNn8snzP1iiMTf/o96Mpd4r//ndP0yvlNIN+iS9Es0303XVvl1MQU1FMy45NwAAAAAAD/+nAEpYYABFIQEte56RnGQGOq9zBmeIh8TVhnmSYQ+pGrTPYgewQAN3/gNVTqxFGmGsbpTYTBwugzHJ1mGVuMd94Ria31PaDQ7hBN13/7Nvd/PX2i3+L0g4QL+QxR1/vNcu+/u3J35AQAN7/gJhDEAGbgnnqPLXYysFXz5YAGZGEiUicb982Ks++2N0ttz0ni26g7XJLWScPlZ9KFttAVLxx4Z0i4T6FgE3bULo60QgUcYgR4GKDwYOBYwF4cfKbu4/LufmYm0mC0dSPlQ7Qx1Z35+1/S0cKJBQwZelLlRRiWjXsMVuF3vegIY8FPbOIMkhCM9eFxfq83CxDx6PiFhC/HmYMmeXMRPiRzDXCK7TDNpF+YNuNP/4z/iPours7L8SEqq/utr9sGTpiCmopmXHJuAAAAAAAA//pyBN1lAAzB9BVYmYIbNEOE2uM8qHyI5L1YbBhNGRoSqozzFhIJTf/j0E56ILiGFLlXoYWF0WQ9ZW70GuEhbU0CBxq2vlxW3nFuZpegyATpRwobbqzjw0lbU+KCN2sRNqXczoAM//AazqHmX96vQmaSqxTMJqxRAi6q5Rqu4saWKjB1PuO8K94ug+c/7XskggOHMDYokSPa8VDMu9tQ6KKWyeIlps/qATu2AkrzMrgSVKZjA2U0V6MTFJm3pZIA5+ldTdbxWF6mBhundNooMzVwEql/0HNT3+12YxClWHQLuF0+tpp0t3LVU3l00pUAHdsAeq+CeDuTEdEQUKdmm/qzq3JxClvbsLSkZRNJASkfQUDhnPTRDHTcL/cZZfTRyGRhFTJYcuYYNKVn1MgZJvodL3jsmmIKaij/+nAEBn0ACcIEGVe56BLUPoMbFzDCOIhc1VxniG/ZFBLrjMGJ6gCAJ7/gNdWdnXxdQ+0PaIDLKyhHmHNk30k3UFXLkjar2v4CRzRK5nWsKi+wu8+tqAIZhQLgOaasVNaBxMdUZI6wCAqb/8QmoQEltnF4G4FDPXwVDuvRXLd5Qa0S3MmoJ+CHxdp+5VbZOkuAxY31rWJ81IBm9dU0hNpQrptSoYoFvQUqlYS4JAUY83HaPw11bMOuyq9pLImKBFdVR7O9oQ6Vbiu7fOc72drp8n627anCcnshQh9V5f233LXdvLkzv2J4C3dtxQRA5Bsx3vkiR1fvV/FI6mFrdYGYqrxic7YVRXh8vEM8q7/52ZlEeFzAu1MBUjRd3jvSXR2GxSBT40c4sRCzOEw4mIKaimZccm4AAAAA//pyBPavAAjCHR1XGeYaJkMjGoNh6RiIZJda55Rt0Q2OKwzxlaoFzf8CiUdNbeOYthD1mCAD4oJKTrtPlhEmcJw2cYc5lOmB0rlgxjOd9+CdU90dQa4yfBVeZtzvlmumESdOtb/Vn+2v6AA7aA0RxSkadLimG6Z/ODyqerNlYJAUraUPmKoEyp4zASjDWkSctpfEFeunZZ+27lRRKChI1z4zsaKBzRcBmNMocHyAp7fgNTpLHEqBPwUCDiNxYXjdlagxSkn2OUh2zqWyIP7I9QNelDin/vJnR6SaObLqScWxYoONYNOQUEsTKrsa5QhWAXdtgki3k4LWWOkIJcdDospH26BPMDE+VLxGCEQ4tM7dfhWnET/2PLtJ1scxG5V7VLQk4PuFUZoWPjLUrWcXewPK5NMQU1FAAAD/+nAEeLMACIH4HNe54yrMQMOatz0laIiwsWDnoEbxFo5rHPSVagAgFSyANT9dNDOcoszrPMePkIsH5ZyVamoWLHd9HrrlDOlFHfFTYlWhVy2lCyjzXh8OGVRrVCNxtGeLvOIOEwBkFzbAQixA/TEYqnsxHI3D7yd2ErzNS5Xadl6pTLZYdyFpL1j1bCelJRBfq4zFUKJNCpGp4qs8PWV3ZrS9Rka4QKUskEJbYGeYoD+SuSDidPmpss670ULK796L0MA9NS/+yGRU5r6FYmDGEgahMG2ElLNmBxUNHRGS3mBQdaKlVwo3ze1jSAp7bYL5YhfnWy3hIQwwSDW1UTytfOMP+zTbv5FJyV5q9VIo6UIX1w6tHYmYUUU8ilaskfrsIpakTIoe11JF7UYZiuLbVpiCmopmXHJu//pyBDEGAAyB+xHWmY8yBEShuscl6QiIWHNQbCRLUQwTK5zzCVsFO67CEfnwoLwzHEPX2hexz5apDM+tmP6/ac0Qz0KOvkbYn6oCe7wq0QqaHgggzR9mKmH6BYVIKalTXUpK9mLAGBTu3wA86JkpO1qdGONBQ1UiJO9L4iuVWHz4FecBmh72uBtyXFoC8XW/vWqwe8XabZVWhgiXKoSeNlAbCNBSjRc9mygAqW0BssDNTYSsCKNLBWCwp2hKcYB1Ay0TMoy4y35wm895KlHtqf0ZjK9rHppBjqfytTX5ApILPZ4I67nVtS7aTtQoQSv/4DWjRmJ1moxwHzkOuwEYT8q/L+bYsdkoRla4TkYnn9oorXL0zIbtHiDDN/Ov6lvx285UE43sOqosxvq/dWtQlTEFNRTMuOTcAAD/+nAEnVQACMHmHFaZgxKkQsS6szzFWog4Y1zmPGgRGg6qyPQJpgTLt8EqAlIBWCQOyr6Ual9ZNjTBR7KLLqCcKbYjWL9/xcOOu7L6DrmMkI0xupYpUABoAlWQ5EJu5S9X97wVdtgNDgHaplg2EixMbWJ5UUMFPpNu9X2LWeXTRZb/DO12W+BKojwFOVyu2dpRN1aMnBF9L2AehiVW0NA0Dn5x1mAEAqbbcVKBiJOh0eks+UDxiA37Ice+UFn/EilxPy56hKppb+0qWKNQ2LvXUq02y4Yx+KJZpHe7HnjYgCAGhQwkUPLB/vEgoQV6pep9OrTkoBDaQ3T2pCJ0baBnDVbDyFPo6/YU7wgj9wNyzOSYNPPUhltU/YjM7UVEh4RKPAZa4WlVHYrpU64mQTEFNRTMuOTcAAAA//pyBLH2AAiCIRjXGSwQREIDirM8IniIiHNc5ITs8Q8ObCjEjG4FTb/hIcTh8oB9QT22ANxhD85bWQgJ/q5myZ/mEuuD/ztDl3LhhNTEgAPDZg+J3iNTWHaBE0CLcUOA+wQHUBDfeJ0JDgJd21CSL+hBrpYzbqpLVCavqKrsVQASNIxASMImiHV5BRvUX+KZb0LFEFqkHQML3JPCYfIzYuphZbRRDyV5aAGf9YQFJZGDibYRJxKKiEkRgNy0H4CiyV1qSUaFqtpboG/yZcIpeuw2pynTpIIJF6QkC41JUMjgrY5ZuwgeCrCJ4aDWjZ395KBKWyQWNhbkECzza4MfuM4iEYIyU4c+kUGk9jugxUfcTzngJsFKrBygAfJUOqraEPSaJjhBqFJNOo2FFi/aYHIagzSmIKaigAD/+nAE52IACIH7GNYZ4zLMQ4Xa5zBldYiEY19GIOtw/YysHJCVzgS3GwG5HsBN4A2EwcrK+F7l4IIgb7kPKHHLz1FkwzWivm9//YhxaEoAq2FnFt6GjCjxbGiNhp9HUGzIo/is1eAYFOWQBDHYZEd0VA56NcXebqgz6CHsI3oImyGcd6ezHNKA7qiYeS/+6b2mqtKNVmnZx4econSHWrBU7za2plxxJwfUqSKdskGmzBMoqMNNMF+JKVJSySfUWNC701qUUBE1VpvWrAJ4dcmqA0MB52gWFGw8GxZoFHVIrmjLBoc9FqaA8Jlq+yhAClskB2YAkmy4OHmtCWp2giqsppLapBw1lX9NWiYG6D6OgGpGOWCigOL1nz6iNSnDgA4mhTIntrnwtdqcr7kpiCmopmXHJuAAAAAA//pyBMZXAAjCJCNWuek5dkTjKsM9hRqIVI1WZhircO6OKoz0CaICoSn/4DWtK5jXYnbY4gQgJpZDeRuFCDY3qb9yb7TvZLnroA79Lbfnj5nGqsRO1GNLtHoyKeHM/d9OZsYtwPSf+7/mGbCMu2wazhEwQ1omkYGKCYhR0LyWalAyMzObJCA5c92ViGK5OGdZy7mQp6lJBF45aDpwXIYuQB6RcH2FWDufdvQHrF7BooAJI2AXVBSBodBsBsMBpoA12wxKD0vwaFuxe53qr6OwuhZrygL+HBNlZ7zbHHSxgUa8Dk3p2NGa6Re0gwZUl6aGOeoBS22jQxBcUQ4HwMs6UKZCW4lcldZAejdkepSVkMHRvLJ9doIHqmHIKvNTLkK0uurFRIPoyef/Tsjf32JiCmopmXHJuAAAAAD/+nAEMy4ACIH7HVe54yrcQIRqszzCWIi8fVJnmKyZFZKrXMMJpgBoJ2yQZWHqtoPZrdKydLWqFmXh9NLv1BZO2/va9Ap+tg390FCSrRghudiaYXDaUj33KQofFzxVKlqHu77WYoArdsAuEoCfQnbGLqxpuMQ/iBw3Srbu+tJWLooKcjXKvL2RH1TILIZ20C5QW2uNYXjAqZC0XSEm1wkhSKIs/tRSApbsA2FYOc7pSHhAWlVTnArZaKqwARZkbnctkwxRRbU7X6A7crKOPIIr98LUH296uXPo/X6M0+hi34ao11hDRs8Qkb7MTMA5bIAXh7Rjf4SCZsnZe0pf3685WREVFO1HjTt+++gzgYRSBDNMjXfo8glVKQYeHbYij5RiVzs0CwsPrWIglVQKhhjEiyYgpqKAAAAA//pwBF6oAAiCIBjTmwwZxD4DGto9gxiIgJ1U57BHGRIRqtzxlZoAmW2hqj6tff26SXJWrjAqLB5HqG4wgNR/q0J1jrBSWAgqjFBuHxuf0T0/TdLMSwRG0FDYc6WC6XgZFW9P7SL31eOpkQABhCc3/AhLYqHUWLBLL3hjjTN48auJ51EaMD7/C+3BHcKetBH4nzCcWijcDi5gkoVeKuFEB2HYwox8sPrsICptsAkkLTSCoWAfZDxM5UU6XI+E8tO433BudUHjUbd7aNk6PEkRH+11UzmGdxVwrWi3OjNrXpi852wz5+2xazZlW/MwKm22Eo/lebceyYLmxtRz6w6fVnXU8JQuRRhr0bbU+9Qb8KI6/TiKsPwgLvYZS0eKvw8SKMM50Snr12tQRsuZZFJKapTEFNRTMuOTcP/6cgTyEwAIgewY17nmGbxCg4raPMhbiPBhUOfgxREgmWscxJV3AKS1bJB1qLXlUpEGXblM4x2GMoX8qfM4ZQjzPRihZh95L4NYtJ0jb2hUPBZz7mJiFA6IOWIjc5fQqvS8nvAABklSRwBfaRSmTe15jZIKFTsQR+aVmdhS27tGiVtkQ9V1etxIIccTlHSDJq8URXPPogZD0UtvbVWl5x1ITSYILUsJDduwCZKYfxavB1hQyhbHbkggOb27/HUa69xSomKS7ZLGBa5//3bZC1E1nFPQ/SOCZq229i4vUdkiR85cCTBIhqh888+MYRGUZbIAUyaBAutNQE3YRZVfPSHL/l0t84d050kD0Yl1XPa11blZvQMOiN2ynMaFCqtulZ3TdKbMjFEygy0+uOk/mhsWLzuVvLJiCmoo//pwBLy2AAiCECxV0ewZzEBjmtc8whuI6GNW5jzDeRMe6ozxneIAAMAFLYAFy4mAyN4UoUKAJDUKRnSZi8Mx8uqVPHn5mo4IY//G2hat9LxJFIV95hKRUyebNwtKQM5ALjIs9KUozZoAYJcsjEE4Bipwu+CkaBaNL7OMJnMY2VSuZ5BnW0Ercn9RVPDUaCz5BbhLMgVWbBQ45j3HN6iBwYgYYqICiUf9RAUpZAGRYHim6BC+QCE5Zn7ZRR9nUlomY3WV8a0jV627Gfh3j5hmfqynu//w02/2PNY6dXJDjDZ+7iF8B7s/79yrUQTo0TrYKl2wDYWADTM1qiPUPryurnNOzKkV4pM0puqD9BlzJF+tPF/aoVDH9ecj1N2RE9bog4smru923O313etkfLvFnikeKL2piCmooP/6cgR8bAAIggIYVjnpEjRDRbrDPMUaiFSlWuYYSfEIlWsc9AlfAICrv+BIlFGmpBTEUYiM44geoKLYDMLXxlgudD1K9G2rwbcsdKvsnCC5lKSDo+pYBhQ2EyjBYa42gLBNi0KOZEKTf8BMJEkx1JijB4ALCNuu5IeI1dnVGcsjsoj7ILWRomAh2GDnUaOy/5dhja25FVOLkD55Z15xp5loaCb7GxzGjdqyhJ2yAISUtu1H4sBQigC0wNhfja0ju52uvuJ47L+jFNb0W0Eki9E/+zWmUjqrg2FhVRMRKmYYEiRBVjZNiaWgeVdLiCLtkAXSvDcNGZjSDQq5GXreqhaIzukcZHoLa72WqLJlL+drP/IClUEM5lS6K4uWeeTErqa58lt4n//u1V5CR3plUxBTUUzLjk3AAAAA//pwBORrAAiR8BRWmeYpvEDDitcwwjeJCI1W56RnWRiS6xz0CTcJO2QBuZzp1sbK7MgjhyGEwLVrWMpa7c5KD7ghQVD5IuaW8s3y5p6jAOGbRq6DDT7SJW1Sr5lKR5UXjntF6gAguWyASFQkhU+tJRFBZHcq9w2lq74MYZpjjSPS8l8ggY5lXGYkN5UGDL0h0ckMF0LjVoddYi9TCo1CJx7HyLBR4Lv/4GziJjHoeA+ycKWy0WVK+hJG0TmfqhirLV2PyPTTG6Xc5hUOwsvLCA3fYLxwnIogdXvZ0CFDJuWCz7IjHEClr70615LbOSrWs8gnj6ipBXspfrjN6NosKIqHxCiDMtpIiofeCR7ernf4RYWeiyzBUBnUZI+EpjV+LTwK8H7QTy5bQ/uQ/6+GTma27iYgpqKAAP/6cgRFiwAJQgoc1zmCGrxBorqjPSJEiISNV0eMsLkYkWrc8ZU3AWS5LJBCqH9HIPCaVFX2dDwLDpB3wSXlWVWXsxeuNQQCdmXhJj1i1dRc7YhhBZclNmgNC9UYpMinXFKhQQr2v0qBL22wTJ6jsJOnlsJbKO+ZnpyJ7WBkeZ2RrvjWrpeOqngDs+IUKDZgoJmHqD4YQ9LyqiaLbRIOxt4zVcKKduuFddYQAp1fem4l5znJCijjrl89YoOcUb+yLcMoMk83QyZJYJfSBrZYkI/+aN2B8VmgTLK1ONaK+NtXNyTv/xh5WhPo0/WO8gQE7ZAFycAL0lilGyDvfk8zFxwlCC81PynODOQcKfvJqPQPBP8Qff+gbpOiI1H5WeCktNEe8rQ0WBr1CTWMkQsbdWoYc+KmIKaigAAA//pwBCETAAySIxVTmekzFEPEqscwQ3vHREtIZ+EjEQKOayjzCR4EK3YBIi0ARGJoCEkDFyBXIall8wDqeckX9CKrdgINXabHnTktnZMYmd574GcouSSu+l11JoclTg8RIFVNc94bBopUscAcA22QDgOAHHNITAXD8d1y+a+tnEaQxpmdnggq2R/tZdBWvNdWNUzyBBmWKCmv+tr/L127ru7iu3bEt+CyK+uX78rtgBySAMADQByW4dQwc9SRO+C2KRBwonLUkx5KkdQWkcMs1CcwnPHSRO3i3KLDvhtSBRDk1WamevoWmolQlIuE6JY5n5Ai+C/VMEPpVjvJLMLmjMSxhZpgWRKuEKM0o726iku7j1ppkVSZFmmHpPNeaOQTtalxRTBfS9comIKaimZccm4AAAAAAAAAAP/6cgTQQgAIkg4l1jmDEuxAo5rHPCVjyPQlWOSwwPkNkyro8x03AGE5bIAhGIEqIxUcHYZ3O/odM0+6wyvMdRVUtVNg1Nx4Yrb4vU1/rCKYwOxQizAoxowoCT2jfUAD4XF6V6AuD4usBgLksgC6TIdBbWQ5wxDWObrHk6wgpwOlBybmZW06o+pTPNqFgjf/m6d/W/f4qh16of+IS8KPeiNdztf0859+d6QJO2SAaJwYFlwrlwTwZaKcyr8Fzmv9/wXzYC16p+H+/h9//9TJhKC9jh067wVT+SXv/PuyfMqzpzb++CGCHdft/2//ZyKFKBcBMsIyg6SXPxZQNyBP9kCaAsJ0WhjUihYOkqf9pWJfom913P/Kh1vtlT7Z5X9w5tJLdMypcDw3770LgOnu5/n23f9JiCmooAAA//pwBN3jAAmCDglVuWxITEHCOto9AzOIJHVXR5hK+RecKpzzCW8AwKUsjDg2C1YtKpkKSOiFtDiS1Kj0MBkWfGyDzCENSGWVs+xTZCVLuaGxwqIRO4uYKgN7BRoUXaZHtMRAhhYnojAAAZJLlkglOIWs2oJPB9ENY0EuDAs1bVBKdEw/cSYyHzuEuS+cJJtoLA4oexIkDy0LetUORGlGYHr484n1MS6vMoCUBSNZ9iXZXaQVZKb6R8cpi9TGbNFIxrDnmu09vPe9glfIKaz//nv6xp+Kdhf0Wm7K99U/ypJYolsp3NgU3TKwsUAuSQBfL+JsKWfwnx7hKVGpy+bdA1+HNfZ3sYGv+z0evNB/pnEfnT/3dkdHt3pZJ90yS3cxy5XdZyAobt3e5unc1r3C3aTEFNRTMuOTcP/6cgTRkAAIgeUY1BnsEbxCAxqDYSJYiPiVVUekaLEZFesphIluAKkbAPlfJYq2A3h7hmjAzHBQhVMtSGryOOfdNzMzK/ksnB2vH/kxhJYLGnsSgqGbGpKLGJxa1m1IkFeRaCndsA+L5FHEfHdQ5liqgM9DyvZw7FboUCV5st4ePSlN4NpBrPujJVBPIfKGdDIZHo2EgUDNNwrrHGBQogItB5awCAxLlsgC5LAFyeChRYyTDMRmAnawS9gnpzRbTOKGQEyimp/TwSSDHAa+FBObF/Ppt3HOhxQEtUDNAfg0FQZGMTIvXiVRVCV9gEpN2yAVWuPrAen9JTuHhTPPfTuP7p+WO+HxaqT1uLlxZxn2Qe8EBNvhP/qcurURmZro8UFJ0wGiRs8LXilqkpxYYZUMR2piCmooAAAA//pwBCjvAAjCFx1VOeMTnkCiynNhiTCH3GdXR6BokRERak2DCVcA4CVsgGiwB1nGj1KPc6yOYHdcaWqSqpCsWYc/kGUJ39k6Mys2N///wXpMTqv++GLKnoLeNtpr+XzyNdK8T1D1ORk0EldsBEHNJzorQelMKPA1wIGoKDofoe0sEYSL+/gfd6Vdfsv9Xz9SmqD+r8bZP3uC6DsAJKPkD+boAUo5Vz2pSTCUKTf8BeQ0sSsbGprciOaHOTCm4aNFU7KPenlYXDLTOFtjaHAl9o/pjzTz62CNZtQvEKblKxAUWKiohIGVsWAnK4A+C+VgErYcgZTQOvBz8LVo5JK4gLqiGvheoyMURdf+dNCpBE39dhdlReu+cZUx4aJF3M5/4Wv39wdwO3SZyNO4omIKaimZccm4AAAAAP/6cgRpYwAIggwq1dHmKjxAxYqnYYI2yMhjSGwwqVEYkWlNlhVbAADQFy2QBdMYoEysrpcKIxbkCNYFux5M9q0EH5DDlRqPqqWZQt/mK3/s53teyWogyNAgcfkbR44w16Rd90lsHzjgBBO7/gR6FkQVXULVRxCQZFWAbdgfizRkWPRWR22SnZ9dZzDcrVF77fX2X7ntQP5zOjfbrusdJ3G34yVs3dekRsAu20CJBCkPS8yl8PocC2yGqvgBQeO1oCLVLTt96fdHiP74egYVpa70ltQZxtPaLGaGFliwwKkAbU17RSL2WrfMNXauxNbAVNsAMwUEBQQU4/a6BDw1lJFy1Hm+9DX4KycsdMb02K4PlIYivjHabVlILvUuHRjo6b9RV5khT1u7nWBeHsSys/6AXbgNSmIKaigA//pwBKreAAiCCCTV0ewQ3D8iendh6zKIxHFQ56RKeRyMqpz2FN8AAkQnLJAGs1hGjtaQyANSh4M09fT5CkMqdgSVQDMSm1FCzKMp19qE1SvqrQcFdazFYbrkmoLIYtRXOxd5IZPUkgBAp3bAQavcSkAhwYWBDtBZJEnEJVGoZtlIMXRUyk1qL38VdVXSnGXFroa+Wqdy5AIDlQ2wSr7KyFZqfdS9tSAS5ZAD5U4kg4jFimcPhDlSJgwFNC20ysNocMzYsEFh0mbd9tvwR/P/+S9Q7X+1sGt3omTC62O3AF3jEcxBmAhuDs0/MiXCE5bIAmWEWgRRxQ/YTIwMgzMmmHZoMUjX4gGutT5ri7ckzFIMZf8TTHfPoVUOso2wk+COhCQP7v9sYxGF0OemX07/98EN9CmIKaigAP/6cgT9IgAIgfYjVenhG+xDZWqHYQVpiKRzW0YErvENjmso8wluAAAEAARtsgC5OAeJiq/TcuELeHrHhYZWpe+b2SzScnGE5wEsf4xUPKth4NHgqteqXNpAMK1GGoMHGDmLfY2kAJScsYEEq4RFQxf9JUZWkyySy8URj+4VhgntI/EIq0c49xsUw5MaR1aoTXTUWo3pol6tt6PElA8VF9B8iooXFo0JdRgFO2yQcPgfjdcxixG2DKkNnr4tzaN10gEbid5XVB1gJ7xEspRY94oKM5woR2EA+Hw+1rxdYXMGHbDBTmAQOLbrA497+sJknLZIKIQOscTcrAZdVlwRsLOM9B8Ydu7h8IaXQtppsYt1dEagS9HxLvCi1GVRNl1uCz0obAbaNjktdXYKi2Swz0dyYgpqKZlxybgA//pwBCARAAiR/RxSGwYTxENEWrc9AkvIwNNS55hNcPOOKujzCRYJSSQBRpFVBYbAjC+069iST3j4Je6EXab+4XJmpWbBmXBW0zCF8tS/rJa1xbOtYUjTHKYzSb/cYMcXa5L6ez9YAwnbZAF45S5CArJvBilefTwIyWgbud6005LrjaZkJqrwmjkYXJR4IU7NzdwZrTkvcn/h22J1ne5LTpr/HG3bte9/SlDckjAPZjFSHWzscCKf64J3EXWYzOQxnynU2eqip4FvOCUMIooPiSs6y3Gbtrtd6/Wvt3qvSYqOYE5cFyJIFXAVzkvsI9AykuVdK8VAq7nOlBrLM6VPIVZaDEWyQj1VgBTm15e+Zi7wh84/LOSpRw696HQOpjS4ES2plRkgl8g4/NZRMQU1FMy45NwAAAAAAP/6cgRc4gAIshoYU5noMqxBYfp3YeY0iGQrVOew5LEKDGlBhgziCSkbAbhShsgOh9n6+cSoTorzMLkrlBW9KV8ZTerO/oP9kNrM7q6Rii31BsGQZUwPpcPFH9iUAm9loMjDYuKLY87qYoBILlttE0j8lqEAc9W0qoTiJqtqmWSiAwsjrzkvwLO9ohjW949ZVzzhpWjAKDDG3JHzxt51SXuFNCYov2vray7/QYIu2RiU1xrCPnAwXUn+AxWtOoE7SoDDqJs8UeH3iJbl8t8TEiqnDklg0OpFjYReKBcIoOtDLw2GwrZW5jUGXizE9aILKBi8jOlcA4QZ1DQMFyJp9IFcraMwtY5bL5iSGi5ZHelqQMGaSdLhV1NaLxLAwBc4tWoOy2POilyIK7Fyyaj0TWITEFNRTMuOTcAA//pwBHlSAAgCFg3UuegyjECjqqo8InnIiI9RR6xrORUMaM2WFOIAoJcjjCZV5cA1mdiZFk0ZEu3BjfSxTYR9Py0TgOB1MM49wty/xRrgcJuYN24qBOl2+5oCCIEaGknDQJioEu5Z4tckAIAQVLZAEypyUB3WWh7Hgs0gUzWF+6KNPOzEFK8/rwDThwXv//lZampNXRkVfEa/XBJnziNSWgMYaW453P0tJIAwLtsACRLAJUGCzn+k1QVF1JK5ZS8YzqXUURS1eZRZungiL9tYEBqalcGGzCA181apRGSxU6kxZFZm2T3lpyu3PSbvSAlbaArKXoKSDMRRXHBW/HlACOkr08QU6dMr7UqzOxnJGkJXJ2tZUCaKiXyRUwsLuDUBhM+pRUXAbGKpVWYe9LpNqxe+LpiCmooAAP/6cATJzAAIkg4c0RsMKlRDRMqXPMI/yChPXUYESbEHHmoM8Q3/ATkkgjIcwHjObkTFbRUboCMsYBqYEFQQYykA+D259qIuorc7ZXCB3jG9rzJWz4qr0OKKcBx8n+gXoIMVzjtEe/40A5KlsgEEcAmYVx2JwkZqgGYw2x2tsNeZm3UfcKiAPPvF6cV+omv+pCoUNH4dFnWOqtwe+id9zjcEJ/6Zu623XvL8/AZLmtkggPTIGKGkL7SjRSixYcjS6yC4S8o0eTlWBkThAYYcoQKUkKHGLeh0XaQjYssxfUxiZsUWSSgxtu2qmo5iQjIkBwhFAeTlO4ZNlBve4D+J9O3l9bbK0L6AyMxuvvCvBofq99KLtrQrN397copJnEL3dTZKO3a5hKQCbjq0/evzTEFNRTMuOTcAAAD/+nIEBGIACIIQHVEbBhO0QGWahzxDX4jYR1dHpEjxGY4pnPMV5gVJJIEll9kcCfZeZAUW5QHBgoegGzQUq6d2XstwKD4TAnW9P1lgeuEHz01iG3yDOacPUwNJaHSsh1fp1qLo+r9i6ACEmytgGcdoNQFezF3J6lmvcSo9kBoUqLWx9BnUG2DVrNXk94HWEMn71YXdV+Z9V4SYMGRNBgmxuktrvMVbDf3FCU5bJBCOJEk0infFRyLq/3EmasPwsjM7o/MosKeReZvBMZak3qmDKioSNKGBcshCSqnAERGhraBg9h0eoKJVdrng61jbyQSVJGwDuDqAIQEw4kNhKpK9D2ZhjUxdoZ3j/iHW5Q3Kk6GNuWv7r0A6d4dGNa1osSHilkkasrcKGgFtW51JJSrryhQVabkkxBTUUP/6cAQueQAJghYdVVHhG6xBIjpDYegYiFBzUOewSLkHjGqo9IxeAAIUp22QCU2xrkvQ0/T+RxzVdfOk1WdPYUgitNIh9Xrp7H573659BYjyNLiQobEouGhE8XGuj1o6Cx4NGDoG12eLqBUt1AlY1IneFTg1RsCRYPhjNJhKC+qKN9Ht8JhzbYyoJcIhjXvFuEy6dAu1phBVDmrt3k7CBARAuwqCIm7VK44igFWnyozkJAOISAZTmPOAn3eF/0hWCZzsr7Ow8M9Sd16BE3xgPyHfemnZiKX9D/JptCs7yskdfa+6cst0on5ey+NfY2hSWyAJlCy/DCF0RSYfoOUOuQI6bVhhAGNw8EWGLZ/Y8O1HDpFnHPhkKFSCCJwDnKxa88x0PMQ0wdNFxa0UpUrlkxBTUUzLjk3AAAD/+nIEsjYACIH/G1PR4RPMQkNamj0iJYjc/07noE15GB8p3PSIagACAAckbAPUkgNUTkto+g/E6q3FahYyZEWZixLRuSEFwo61FclBnuMoXEnQgeqsXoWFxGLXj3rWvesK7/svexgAAJAy2yAHyjAOAVrOkZtkCQG7AdkHrdtWUh2BDWbXbUZXEFuCPg4BC66ljbRIeFaBjFSBFwvIhuEovfkbJI8GlrU8FSRwB6KQHOHEdAQswB+mVlCIPwabWX4xVqyN4Vp8Jh0Iv3JstjF+cd1Y36vo60u+/bmNQy9Hoi7bonLY4OpXsC7HVQWJjhhO7b4IkIwSMF8ABhGSvXAFRaryLWaVFK6GVjNXU/7QZ+mrTHZ0rq8xbHI/5l629yov0Vn5f1IpIlQCBZpYmESLksxG75JMQU1FAP/6cASGPwAIwh4YzJNZeaA/AypDYeMYiLxjV0esy3EWjGjI9BVGEcABRQcxaeV4d56c86bKqDwZ0F4jTBUxoBigEdfgzKm16xNuU+nkupFMB0IYzxmeRVvGovgW0jErZvFo/h61hnqhL7wXbbaHgGgk6g6A5Uk9MEuLwEdV+w8qJeBGNj+lZSrn9LKcK4BQOKFSp1B9QkhVBrOLVbw9bQw0xncLY6j69yTSUctkgSTWTQ21QUB/qFQRlqHwYwiT/af/UMu7p73kAHKOkZBPIPz8hw186DFS2vNjLaRosl43rU0MuWlFnRbvex6K32ygH9QDiAkQBgAFixKtSGmK4vAY7AaSXM0xsw+rLZRRAQfSyJfxXhkbbQLLY1jCjA4Uubxp4cfc/QJ8dHJL0rvXUlaa0SFKUxBTUUD/+nIE1RgACRIPElZphTK8Q4OqujzCXYg4P1dFvMExDA5qaPGVTwAABCYU5bJA0OxJBhg5AqyeOHjGEkAUBsY3Cg2JQm+llKIyqSc/qb6GG1NGsewlpvSh3U4M1EHMexAsyK5en4hkFAgK2m9bJAulodJ+UccXg1T3Mav7/nZ68cP4e70GX7n7eLKzD28MOgTWlSXD1G2LHCLSlUOEZdjEpfenf3vc6yN9GXHjWW7ZC5FGWpx3kxaVSyKW7YRjFfjyG5pyDN1kw2YCoqfYPF+hraECQk4qgQ920kdLIQilMa5LWpoeabZpa37WsRQAAaYb1sLUOs3Ubid6mtENocVmDLATuiGIx1emKFMoqsz6t2hYcKBBkO+v92lFlkJ4j49tAD3wO350/bt70x9/1/O1piCmopmXHJuAAP/6cATOGAAIgh4T1NHiOp49g5qHPMJRiHBzWaekpbEdDGjNhIlSAAFkOS2QBMqM/CeHoSk5yxI2UrnjsBUA8KnszqzVVdLuV843oUOmuI///625e/Xvad7ucXeN5znjjNd06j/3zlFFzFABhOSNsLseYaQsSbjsbC8bh/3A7SzpHZ92opzvZro3q7xvaozrkOD6CLdo+UW9HIC4W55KH3N+cvDjNOr6QVSW7bJA1qgxUS2lANAUybBid3H2iqhBupwi3u09ley98/8HU3UxBkWFKHmh6RrEvEzBh9SnVeQQt8yEWsLHzqM776wpLbaE24ZEqCTbl5ma60RXjS+jRBMIeyDeIdqgNmRTqPBNLaZ+wwUDMUVS7SHJIy2sVPgA33PLvdh48tDVJp4oxi7NrtKK6kxBTUUAAAD/+nIEa3sACIIhFFXRgxKcPyMahzxlUoikK09HjEpxFplqqPKJdgAApLMtkgZDwRwSoIwFBKWHRDchMZjLAm2RkxkGkeDFOYBcjZ2eh9r9aSS07oUBsWbTVHGmDDVsi2aFGi7axridaGOJADDm3/ASI/CclOyL0FBoywzWUUgfitCtI1iIp70aiMzX6AyM6fmSa0JHX6gHUSpUVeYejawPCFwjIIWpjht6AICMkbYSAxQ6gjKqAfwxCqZkqHUjmGFuCLClRCQiEIiJ6Twca8B8qAjbt0kZqQhS9uumWZQZsbkAgsifPrra0DqS91elALJuWyQUNQm6chv3kZohHXYs1R5p+qsdLh4XKcMhBHjHfuJ/ca/+qEd3ZbU3ruZju7ZPDlB3qoakMCdF7koVc1jKKSDdKExBTUUAAP/6cAS/fwAIkg8P0znlSyxCA4qqJCJRiCg3V0eoxjEEDmrowYluAGErI2AVR8hEAci4lKBWEIL2SUNxDV9ggWB0kdybs9D6hI8+oHGvBhORFFnXHApaLTae5qZpT1aLlzLIhoeWc2ZYAAEoStskAHAZB4LolQegORBKNhuICqAs3t0oDaCbN9H/DHQjygfAdrLYsTBctRxYXMosKC9tjgi4GDz7XN62iazYFpclskDcjk6QxiLJFHAHXBzoY/23lLtVC1RM9Q8mda9ws7N+1pd6QihV8XtXecIImKU9gENjmuONbrXfpXHre8+DxbtLy8GCFEXCFjvFDPH2s5FWO7SFN6+Ya6aNn/BUIyzefeBQAhBVSlKoFR5cKDIqXR2ioRGmTTHSqN5wYAlLxiYgpqKZlxybgAAAAAD/+nIE69EACMIWHVXR5Rs8QeOaYzzCU4iQczxsrE0BDQ5nzYYhSAAAtLmtkgyaxXj/hu3qAONnQqWTMDLUa1UCI7teT8/WspcoxXm4AwXYIFPQQa80wUEje3Y51uLNbp0uGXkfTIIRmKwVJE0FMGUtmTBUR2HIssRl1i4eBzVvbXq5SO5f02CacEZUWqUGSDR6wbIBFJFZdL3/bsoYQkQSAQCQKKKPd9736UgL//8FUkOPFADikCACTsVzDIQds18PATQa27KGNCACZU+YXh5SMVOF03pNfo+ayNj/mHCZ58crtehV6Pu9/6UX/4p6TYU///CyyfQn5QJb5I9YcJQ4rXyzUDDU+Y8UHdxhx0CI0FLAbuoe6aYSZr6OEHeeND7i+kKsO8jYyq2xd2/9f993vv0piCmooAAAAP/6cAQ8pAAAAhkc0h1goARC4xp6p4gBiMSZUPmGgAEZmO/3FtASCdttoW2rgmiMjZ2sKDYtNL1Pow+Jymu5WGeCod9Y5VmO9OjxFXSxgx3XsOQB1CtSNlKRk9s7keLXKWpyVVxXbp329YAAUhKStsF3Wz6LROOBIy8OOUBqT0xnZkc7IMXOUeed9dLdhXNfQEtgvcCRRgTBdp09lepaq1MUwWFVWGE91Bm6w6wAAECAcDgAAAB5VMgqNApZgGQmpGLrigI4tSJ/N20F24JXhXBzXVOsoHeNJCT6HLDUe5c/8FqAGgP4Jgl//x6FR5wx/ewBggkSDQ2qhgQDgcAAAAfMCITK6w3CL+4BHKGdOJ8OWtVnAoYsSEr+LQlRhyQ/8E6ABQNYEAHn/+B6B+G1Iehr//m8uLIBfSD/+nIEPH4ADOIWMFgfPOAEQWOK4+ekAIhU9V5sNE9RExgrTYec6gA3f+JGVmclDKnTPJFKqWF1GeNbLhBwHJqjpEZSOnnsRen/0KE+zESZzH1bKGa//r/R+OnwAESntg1E1G6Oke6ptjgQlN+MKiMQAailN0LWJZNOlKsK9/pARNKAFuKZ4/sSrc7VlX++/f8N9HG89uxgIV1V/+RiV63lPbxN171EerP2dYATn/FBuQSp8pLdgsojLrtRcNm9k/WsJFzN8mOucbU+ptTaYnXVNB//v3//8VzPOaqoaVz130VMQ9TuQgJyanSMj7rrxQRWVIfJMy3AlGTB0s1IisGKs4rRWa9T2tiIimjQukcTKMaar0FRLKl2RxvyPdtv/zP78UhM8GpO5/M0f/eOrZMuZuTEFNRTMuOTcP/6cARB8gAJAgw715ntK2REB2rnYSpGiGjVXOeccdEMmiuc/CjKJEe/AkcqrBqRIxZA0Z7roudrLKPCQ8cCs+1kj2cfOFn6xac2isCD8KHadP/0fjqjQ7cgAC23Rv71oLIOZmkSzkGAEAh2/gUG4wlNAtBBjGSJlZCHhDXWU3yC+4SeLfHzZEWzy2vGAmaixLQuOPv0Jn1M6f/KtkI5nLuY6U3poUeKdWcmGLCSJe0pYtx4rzYQIMusZdilbmxb/Ah3wD2xFvC5HH3ygdoZlBvxJmnFDs4YSvTzP6vzeImgLjFiJ8H32NQKwrpbYGpWAAIc34DcgsCCF7nTCIHk02dYOnYtvKgvagTLjTleRjmaW1bEYe6CzPKCB7cxv7f37tYYjlyFHnNuUffwXGLlfdDCGJiCmooAAAD/+nIEFsEACAH8O9a7DTskQ4dq5z1FiIjIw19GLU0xFxhr3PaV7wAgh2/AXZ+kXJZtyiNkUrNyOo0WerO6wQnhzTZp5PFL4XL48R1I6JxGvCJtW1b9B/+v9B3kG1N0M6ejTibf+ZAQAHb+BladjYFG1twkBy+Nl3+uzm34A/cYA3jbs+a2QFteMg30JpkA3P3bX10bl6+fgnWeyLpO6EzC8RoSZfoE4EcQAIEJyQCGh6IRHQ7BqVpdKQNe6Sd8APr4DJnZMdWxeaFYd3J8qT5UI3xV4lt27aedz/fXlCLiDaaImXoSLoGBCPFrWDBduiCJATkgC+05TAiTZYoBf1jS03WAbWqwpnw5XHKhQJzqYvnsqIGd7hfu+o/t16dfPub9RgOtBH5heFz7R+Uv44+X/dntI6hMQU1FAP/6cATGnAAEggMu1zntE2RAh1rnPaJ4iOzVWGecstEfmGuM9h0bBACHr+BVkYzcK+k6dFBGqzCJz3UInWIzrFVeTucdbEUX8rS76xKO7JEPo3bMM1V5H/txeZi1J0OZ5odHnZ7ncYBAELT8BriMNwS7EwvAa0HEGTMaxl7wRhnhyeXu3GtPNx0zvV9XtEc39hGDEc3/0fQnP32OqTNaCRQHhqM0xYAUQI98BIqWZDwdS6ypAaN4B8mBXWxa/eEnt/BINtCM3LupoEQ/KkPygC+OOUSAY2cM1H/2/jX6lZQg+OHtchVYTV0aQMaez2IADv/AqsSm+DqU65TJTV7SJNE4hfrAAf4Az5raMsRRnE5bXqT4sqg37c/uZqb3zB/3o7HGgcAS5F8lBa9KRvNf9qkKru/tCwYmIID/+nIEBKsACIIMMFe550P0QMYK9xnnDojYuVhjPOGRGZ2rDYaVcgAoBz/gSVdQy1vVdKvV4gzabyWzNh/hObi0vjhc1WL6lu/G3Kzpjat3/v/03yPdVlYkB1SkHlmEIiOxjjzMIjxC4AIIl34DDbEDGs9b0m2e7U4+9CJiYlMz4PWyb5XhQNYpN/Kl9Fi5htx/jr69X7bp15VseHnm4oaeyVkbEPEd/IIAFt2AKHaEFJtDzIU2naSGParULLJJB2oOT6Ac2KOhfltDtR3iiNmALDjmMI4nDVCqPVerWX05ahxgwLkkP3AIhXHClqXiiQg7vgKCliBKNtnw2+8h1YrBZvJwFHzIIq2Sz5c5ce4xh4PLrZzhZODTRBsU5H2bGpy8jZU1HM4QBdiEsrrQ7n9xd/bdesQJiCmooP/6cARX7AAAghI8V7mNE3Y9x4r3PYcdyF0bX0ecT1EOIuuc8446IAGXb+BKM+FazpZYlxOFV+WE6XYhcnczbPtpocxzI9nBgG1uyyt9P+rdP6/yPuB5LVcc4JFshK7KNf33UD7TxQHegBABNyASR5BorH3F57R2EXXpCCV9kPvqLOT4uogjjGU6NqDNdpXv069W/+nM48/rppMs861FLYhf//KE00EDECQQ7v8JZ4BdSkxbbjuHBMTX0ROsJe5HGr4hJ4XJ9P0Bf1vI+V+3bl/RenDK8ondnQ/3Z6mz6nRvV0Wx0W0E5rdUEEOb8DtMYXUjki3bVmHbWcHvUVP8qa+Ou0MviK+Ols59Ohur2ijtzOv/99jsRBnHuZFm5pfaxSq8C1yYjXk/7J4YdamIKaimZccm4AAAAAD/+nAE5ZwACAIXMFe57RL2Q0dq5zzihogUwV9HnQ/RFRhsaPSoogAgGbfgS0mhio1m63uNISKpwN98ZXM+ZO80Lcilv98nhanzlTM+CH1GyebYFbbR0sBaCPn1I+h7vY93JOW1Q631QUgBQw5vwJXdzNDwrGKIhOYDYTXW2oudsvmfGKuEzYg54Sub0fqmojQL27tp7/26cM+T2KrzI7OrvsxAq0iBu6fGg8bJAEAHv+BlwfoYIjrK+tadUS2NSm57AV45aONgocsUh/f9Ql60j3v2aczf2axu1xS7iu7AnCwoPvFM46LRd8e0llUAAZDN3/4a9Vgkc3UoOUykG/iYa9i+gXJuPb3bGYK0p18FHJciEVxl1bP60RV579mkbJFxNaJTJ5NZb2EqTFoutW43YylMQU1FAAAA//pyBLwOAAiCJTJW0eoTxD+juvcZ6haIhMNaZ5xPURkMqxz2NRIAAAgArvsMLMcZQqa3SJLKS3VGtyEF1cC7j+0uWx8WWaHdffQ3Tg+v327fys3XSYoXDHSQDgUQSW4Y5f6YrtvzTUrpFUAGID22wcZIoVi7gpiHtbhgeeoalpo8bHedoQDixNHf/GAatR7qX27BWg2JEIcMXcqKJRO6W3xjIoiqq6hkf8UILu2waqsoMhW4yugeNp9Pa0uSbdgKtjTldSAwqigG9tCWUB715Xyvq++31e1LbqzorCCrWxljXql5/ppqbfCMWtfK8eEAGW7Cia2RgN1Osa4EbbS8QQMVyAz6kSPLnLq6w206YYClr6uLhP6Ouj3TnJ0Vr30vqKgc2lCUtaSWLm0AxE4sKbwAIBRiYgpqKAD/+nAEs+YAAIIBRlg55xL0PcQqsz0qYIkcwVTsNKvRCB7sNLEO2obgB7/gSOWSiPPGEkdO6Ydj4IthriXxxMKFtCXbtqH/06P3ZltVunOzn1U7GOYCR1XYiurMt12oDRSOzU/f4FUAHLbQ1MsgyitMC6TA9VPFgHTN9DW1BqfEHznZwSFJjhKGnbRsSgVPk9Gbmclev8/rD8KhHrWrpDe37E8tHABBDltA9859JYm/LogyIefyn4UWQODB8jcZJ9o8zyaAnwoOzlzo6ZrlYThsytDOZ9R5avq38rVdiYo1RN1jwlS0k4kAVPX62lwSYAJv/go/FTzY4H8nQfSJ/qnb5ISvJG3RLCmAWZ7xu3bBCNUjW4/M+7797V9bl3Q2hnCLOTJc59K9m+gccd+p6q0xBTUUzLjk3AAA//pyBKv6AAiCAiFWGwlqJEIj6qdho16IpGdfR52pERSW6+j1FXoAOy6i7PUkbKL07wN3GhXkWif3qDajfji5N0QrxtUxPIGt9fMB96NlF7qfOOwcf/AsEMdF7uTIPCAXZamDyDaqwAgBySQVnz2whk7Y8GNjSMKCnD0fZRQzhJvjjZpm9EDuQXmhZk89qbEcV6yyMwAXRMTGp4pv1ZBQaaKM6E7mf9upYyAFbbYS0od4iWV47To0MKOUUm2Q4VXMNYqpqYfyzW/bODieet6FsChQwhAHMHIoHS5oZHi6lLJpW5T2o5FUYwygxQL+kkQArtsMu7qZBTbYA84cZRYxgM9RW5i4w4XJPi4tlf5EB9/1/ktR9f7mad9ApYOouNHHiqhqTIYSLMfwfodOCi35cZbpQmIKaigAAAD/+nAEe9EACIIQO1nR5hJkQQMq9z0rXIhQ62RnpEtRChLsjMOJ2gAACACu3482z3VVGxUFpcHSDYfQQh3u++7DHK/NX+LGzvnahD/J/9jneQjf5NiZz9VV6ETt6EaQQ8LCKq0DMn5uGAAgAy2gS4h4PtcMA5DphmXOOOR6HhJKzrftHOqXz0NbvFZiFrt+5+xomHBgon0A+aEHgCibtNnyjlajhQMpu/vpABdu4xqRJxNkaHoOHBbMvfCYeEs97Xs3Lr//kDRmxtrByUVqCyOfn//mztr+fnzv23z9fqPxcP9P2gAqfFhYn2B2kAF67jaMEpH4qLwM0NJ+Ma3AluJnlRefP7ZEwQyVbbQDANs9v/pxq8gBVbAGYD8/aqWOF2J170UUAksBmXMSWwkLDkxBTUUzLjk3AAAA//pyBEj0AAxCEjtaOYkpxESpSyM84nqH0GNkZ7TqURck7FzziXqAAAL2/EFZE00qVwoBtIZ8vljQN3pzKmyJwkwNERbUujUBGl5v/59W0/vQW2e6ZuK6m+pdBaVey5ithBQfnrwJavpAAd3480JIucq/kpoAvdRrY8F9itonO2ObH4vNE5zYu71CwS1Laf/wvE/9nx9H/TCto/WpanGnY/0fK/0e5meGa83O69AADs3Eke8MrKidrA1pRRXPc6HplCPUnnn0UCZoxCg2bbKZQVv3L9Z5Tkc4qrb/KWU/TpiBAgSbaJmRcylQ0rAADJdxI44Zo1TDU6ukED2QKtc3CpeVBDrwqMg5cqc1TdHwIgm6ttW//BPvq/07f+fRsn4TiGqb712+y9LxAbWksVmMgTWmIKaimZccm4D/+nAEAZMAAqIYJVebCxL0P+SrBz0KgIg442bnnEtQ9x1sTPMpSgAnJMPo9O6lhsgGytfzwqaWafwKI3UwNF/NPQOamPpdGPSi3kP9+8JRq95bkf/UTmGu0yFmun1VH1//loX1jiZWsokAIADsuEl+QWkhGUPKRclR5oO/K0/BMLaBStqsPBOCUyEGpbKcF4VNmn9/+nJLKi0hZSvd4dnlt2fRe8oupHtfBHb8ZjdXGr1aZbxsJLQ6sTghYHTUPypekLT6lbANWqMNXiNP/6tv+ui5i6bcuz/MHQDEyxYIPVchkQZGxzjKtSiVuwxPdbWLqUxkbIHTRONaQBYUjkoL9ewYOFXJNSLKNiqQ96f/zuWzv/P0b3Vu3/V8o8TPoYMex9Rp8Er0fWmIKaimZccm4AAAAAAAAAAA//pyBIctAAQCBjrZUecqpD5kaxc9ZWiJHM9k55xL2SCdrSi0iOOAAAACZbuF+By+JXB6HmgGIde0KVHglqAnQ2hedgEkK2HdGlHAtHof/9C7Nk/b8v1tk/83FbetNFthkSgJVhWzqAgABLbhmeVGpqVEE2c4Qb930ltAS28UZ4NvQ+KIJYotUF4C6NoSlq//xGKwxqer2ylGkRQ2tv+zmDAT+NWL0AAvb8SuGTmgyFVCPFvP67u0RFoBB6CX07MJ48ZlSWj0Ej8Vp/76N/x9RGH0M7q1x0ob5ud0eyl9QxTvuf/41Zv0oop76ZqJV4AIQACv34MXZcRoChc0qFLyTusJ7kQPz5/ONYGJbDfhj/T/+bUm7bPM6r1FVdJGanTVc74dutL40PgfkdpSMfes2CK97LjaCiYgpoD/+nAEDloAAQImOto55TrmPodbWj0HNohZH2ZklE2ZFQyrzPQdwoAgJXb8ebJ2R+unldFbpxuhsHbFt151CUTI1fLxMA8prVtP+rZxag7+vbbv++j/kcqn26RkiNJNbcqJ1anvTaqqq+meRgAgQAK//iVw7Y6wWVbgFui3khE0e5bb1Y+VNbX8dT/t/zGoHY7+p9y1ldtW6y6d6PHaOODAV4vc1ym6v127gBJWVIJ+ZIfLAb7s2HuAL1B9PFXAGEXavXUDN9pzjf69WzftquiKqUfcmlvkerLlYy7I68qMlluMZzUtdZzHgrqyGgAXbsO6wnnvDaFYsbD1GmxNhFi0MoTm0GcbNzCwdEI1GKjZ6vgSGsoXkPTEPqemitLlPR2OOFQ7Wi03S+Em2IUgwaPMctMQU1FAAAAA//pyBAYBAAkCBDraOWU7xEBHWwM9JXaIbJdkZ6SqWReWLNz1nOIAQBU2/EXj0Ocxo8qG3KnoH6BLCwbXwpBaQ7bfKhIbTlv/7Zjf99X7W26frrJ6l3GjFL2g/iCoyE7zrQPSFowgoAB27DE2D4c4K2IhVrJxqPneUQSXdV3zN/la/67gSxVh1TNUfgKajHk//l0fX8z5/7fv/1bHvJWTMfYqsx5Wr6AAZ59KFBdtruCUmjbeUmE8a0a+MfgOUVhQnwfTs/z//o+JDuH/5Pr/c/Mzx9mtfc//iZyDvMazcozFno3RldxoDu2uYGADv/4kviZN0KNAvgdtRPqCdtmFxONtPC6hDs2V1fd9sbz7/856BjKP1qXQ0ccQcDAkKLlnseGlk1vb1dEzIr3kxVF9zExBTUUzLjk3AAD/+nAEQQkAAIIPONnRKBPMP0Sq12HnPoi862bllHORHYyrzMwcooEARABcbgTHuBYS6B70hDcjt0RkyU3jfsQ15kFNp2t40Se+gjXitf/5xqBfuyyNlfuyWpr+oKyxlNNPVtSxDlOX0gEACkkA1I68aUZtFWb8t1g9XrWPQ/wgzhNXXbUpr31/HO1NlltsDtQM1eo8dQto8qXr+g0OcTBLZ+nvR8hDV0BgC7v+Fbx0l2k0zxNZYy8xEBDSm9U5UYlYxxf6l1agB/7lf/V9mzerG0fV7/vqXb87QE4CaUQXVetiDBwrbCk/IXFx7RQAF27DUcIpCJOSHbeiqvxh8xlqIrMdh5pgZ17iIHhdhMHPhf8Fpa4pJ+mRsxz8F2L0kPQ8OLIShwLLPOWUJoDB5jRQAoquRYmIKaig//pyBJweAAQB6zjZuYcT1EKHWyc9JT6I5ONiZJxNmRISrSh2HEogQBk33G9ITvb5y8yTMqhTqwMXijUrqveOSjcvy0qEz172M//bBd/+hsj+SZpXnenIJEqmod0ftIjzAB+xMQwAZtuJbVU7lkwSPtQh/LOtYCd5f84Z33vvQ9sPIF4z8Bj/P//LoTXzLfNqbfN7f8nEJLv3MZuIgITFDMJINiRCEAFTb8d1mTFg+B6CAE0E0boKCd3BfqQ0TdonnG6E9GlQYXrxtf/4Jtv23Imz7btc1vo4p+tTWzZCXWnt8ZJkZVd58CuTBOcRQABYgq7/hSEKuYVA3GZfPaOBtnIPQNYoafHmKSpHn9sRU3zP/76EZmu1EhYo+5JSBRGReGc8KKQt4ofkjTQkgfSq8BbUxBTUUzLjk3D/+nAEPAcACAIRGNo5KyqUQqdbEyXlGIgI8WRjvEVRGZjs6PMI4wAgp7b8VkgdFuH0YQo2meV9IJs4Y9R/ek8SjRHQZ8gROEbSj+LB0i4R88LGOptrKE4nuYTTMP2NUIkMLgDayQNnTwAU23HdQ2gJAoBjtPdZyv6hMAsS4nn+zxoK2L9sx999P/TR87/SkkqNqhi9VopH20uJnh/fc/jjwiUJTw5iEEGJJEQCrvsEsDoGXquLTdpViBvrs4taT2oNvpK4WFPoJ+hW++b/z5OfpRbO1+7VK5ZFdDybszoDSMu1429rdBXWzb0KQCSAAPf/iSLip9cNaDHN/BR/6OyFjcFt6MWpW07cm+XRv9A7QQLAB/Kr3eptaRn66i+DtrRRV/99nk7X7hZJtT32n+0332mIKaigAAAA//pyBD1QAAAB/iVYmYsrhD/DKuMnBxaJGOtnRKxF0RuTrKiXnEoAObbjI8BiMZIAPDruwFlCnCCOkNFfGj8njJogd8H04v+eqt/qbGb6h9r0Y40UeqwlcdTelXo7ws0ycqJsOtYgAFW3BLcDy4eEJFaGo9pMoECUiTqBr4rxs2hMsFphZqiX+Io1vRZ+iyu8RWqi/21XGDlP20OAEIiUkgmsCC6pxAAAyDbv+O/QaM8VCIJ9ihe+toR8kXyt4e0KTgZVm6po9QL2hlcIN99S3DNt+iVb7UZdjZqNORAaMCHLtbNpbcXLp3jw+nbfxYAICAAft+KfogWaJ4eAS0SKmyGKHyD1SjCc0flG5b8Ri/3/+YhBmCxVlIFlhoWebkqO7ycKFw1wVCD2kL2i49YaQClwo5iEpiCmooD/+nAERVgAAAIZOVnRLykUQ+MbJzzFNIiEk2RnpKmRBIns3PKNIoAQEBCu/4OtWBh1JJoI/SnBleFAXBmqOydyxTvl3fFW+3/6k1/9tWs/tKp53QxtERBJ1MNkZs4ZNga5zmC7W/TEpgxBQAG7fjLvK6rg7x4D3O6yYygK8PNmyPGRFw44wVehtHwFdnf2XlnOelLl3NNIQje8g6PeEJwYmCxxiC4feQZfFAjJkwSbtsM1Yzjgp86yclc4i83bDCj94V/7vB+TkspqJhQTehtdB4Zq9CV/8TqMHBH1pWVcqkm0paJmCGSRnmY9Qym5up72AEABm/4zdjXn6fFnSqPiLYJkYwPhbZcMnDIjAhEUedU8tyH7al7dLBeousTta2C5lDOFW7QCLi4uohfFDjohkHNTEFNRQAAA//pwBI7ZAAhh/CVYmekTREPDiuNhhz6H0Mls4LDhUQyZ7Jz0iTIAGW7DOlgwNmCDbHyQidGSvWwxfNv8LbfxQcdLrFnRChhYK+f9C94Rt/9RWCnGMvpt1i6neNTtax9JRWe/vZkwAHbaN0rM1JL/U7BrXjfHwK1J7CDFQlKX139p0j5fi2dD0ImBwLJBaM76F7IMal5R3xPQQ1XrS/YdmGu0BN/9+vp/5MQBN//FITNgcFZ2gNLYPwju75ukoWdKDxaEbKa/71Jr9P/zPo+bvstNGWlWIsompdwZQYv3naCql7G9S9IeqB23HmiHkgi6DvOM6KVF6QX8wZ26/d0h32foVHsLHuFfN3sUXrx///3/0nRbd2ymwai9pCJOh5Ds6CbiAmCwoC+tpNiYgpqKZlxybgAAAAAAAP/6cgQivwAIkhci1rsMK0Q+46szMEd0iOhzXGww7REBDKyc9JTiAIAFJABcxyWlLi4RdkWEJduJBunLtoqVNXPq7M2oJw2ctJSLQetgpxdEA3DuFvEQEXGtChZytT8ecKKOEberWyz7kAFXb8ZwODuRgWJoZEWzPWwuaxfahYITUeNmiAtN5vfHhRq2PUeLDaGHlj67ImiD/TNDhWXRABqDalspdXUyZABdto1fkqvZWMtS8LlXInBEQ5kwFiGs/X5jbKRFsn9oexbY5RMCMXxdlawKlsrmkFeg6q7svHnESl++9obRJuZraYVxPZOOYCTMce0FbN80CTFebmxwFpMeurxDwoXrUyQm+2HtWwiHa1t+JaOt5te6WRObz+fx72CBQAE5elTxOPHgQpRQipMQU1FMy45NwAAA//pwBIKQAAgCGBjaOC84REIDG1oFhQ2IbHNo54UOkRUMrSjDCdoAgJN/3FypbZ0izKYk+Ea31CZy70HNzELVeJxyI7IPat2VCcrJfLvS46qdFnhdR1rSKlPD4ecEiTqv/5G4eLtc6k2XAAGAAF2SCy8JPkEaj5Blzq6ZS+gsQ7XoPVeLMAgbI2XVriTFvE31tVdy1XYED5Nqhdbxce+bSjLraYdbYjUqEW0IQGlv/xndVRYaBAlSTbo9q9HVXBUY5zJTdXUM3If1iX9qhij/75HXcpWA2lb3xosbnSEco+mtjBfydeLlEruMDaADdUgANIBF//Ev3JrhwC1S1kL0cjrK0mNIXrxO1DYxpPFr6N9WvByV3ylKbYBD17EOGGUElgELSRlVIiGvIyLCdVzl11lVO0JiCmooAP/6cgR5mQAAMegc21EjKrxCwzs3MCaCiOh1XGwwq1EJmGzgwo4ygACQIqWSBOFE+kyADJMqWX4+gkzEnYn3UwjDA0hx8i4m04O/zjlO5l6liHjGmIrJl9C91Dt1yvMqbXkLr4DAk3/8Q+JokkQahqJuXVM5cXfYs3ucfREnkWfsh+VnL3989GnZOr/Qp3DoweMuKpB5C8WqIE9xIqM3wgsU9tZabjwA3baN2MmYSsqvHSlrUoLBQRpEZXt4jAz2ySmuNt+txHVGcWHJGUg48z6tGhBa2i4uc16LCT6kt0JRT0v7LmkX+nOufm87mgAAg3hNjKC9YRSzU8TRcNpgH/rNRdK3GBOPQNiGratgMHfo//5dD7+UOsay7+eDAaTOdpJWsW6dK+dAphbs6aT5UimIKaimZccm4AAA//pwBMXUAASCARlZGE84REEDOwM9ImiIvJdo57DmkRcMbKjDCdoAG77CNhEzn0BNsI2m461O47qBZo3yzlXOR0B+0QTzO/bFuSKv+J7xLfGqJ0HxosDh0AA+MY9wteh/or+uKfFlgBTbYeKiWZAnqe54IRZcr95B10Hwt+v/UUm616QXLakL8j6/gI/Kr9dR4wrFkCp21YJm1aZJdkXI4AmbnljidPTAClv/xjWVdGIEO04ha2LklGOjpDIbOVc1HQR8qxXq2lCgbGaK00v/7mJsTiRcyNKvfJDj5/Mqpy94shTn3LmqFU7L1uNKCAEmfbjOOAqU1Hs8V+PxcjwKJ8ipyRX47B+62B25y+z6aPgVDk/uVVsKICIfOBhDbmLb0mNIsKc5vNBkRjxdwuJy74WJrTEFNRQAAP/6cgSoVwABggMc2jnmEpQ9hJs3JQVSiLiTaOC8QVEXkuzckYmjgAEXf/xjeVbtRngSZnqgGvKX6G4+VwbolA4uCsPoulJn+UV8s8o8rnCGacmTnz7fCzK6S4DS6TTc9TQEHn1LfRAAEvbbhKQOB6aQ3xXEcG0sH9D8Z3UNq8PtR5uunL2aVMv+iM4xrXIfrv+h1/YuuhCDFq6DAKkp0w5Sxds2AUMTXtpuUI0EcecBctl5XkEs66wY6lgo8IyDrDGy6n/c1H/2bDDyOPwsbCZ1AoLiWxz5YWW5QoRDSmM2CmMFClQEZUKMcQEu//ing8WH0lWxFyMgZOgpnPFlHMG3euhPx2305RPziaf+2g+zvvza/R3fFYO/7j+GL1zE/A+Wva22rSG6Q1+1Z/4OH6TEFNRTMuOTcAAA//pwBJvHAAGB3hjaGC8oREOiuzckonaIyItrRhTrUR6ZK0z2FWoIrf/i6LOzkEWcVFk8mN42g/bWFvWgwWnZX0Pk1DlUMK9rS58z3FX1tC6nmDBi4Xpat1Z3mLehiUahVqAEBB3/8d6wYDtYja1ETzsb6iHXOQayLoRguMDDY9Us/OK+28brExw+YEYEGh8cReAz59ZaNecD0wpxOkXvI1Tz+dYtEAA7BJaHMM8B4hB7sJMYoHsGLEm2Cw1QiPoeE8vqmjxwA19GmN/8ocWODxe0gnNkQ0L7R5B6C4CY3CzeSWMWtAbKMzS112ggO20S2PU0RVK9qV4crobRiHyEAr7EfcY/cRJUB+vSxWINVaEEAzfJwFN22/+hP/usjna9GPzqPzJBR1dRtybWCzN+qvT05BMQU1FAAP/6cgRbJgAAghMu2TmPKTQ/QxsHMQV2iPxnaUeYqlESEy0ogwhyAICHdthvXMFIFypHiakiEy6wlapl2ca9R61h0XGI+O10B/wov/1XR0d7bIYqmdsY50OgqSZ64cfpOktTvHjNKCJJABAindsN4Hz0bib64QLDugxJiTMKqqbkxc1Y9htBkfACCtT/h5pZwVUf917/4jyodqCRhVq0htPQU9KPyGi0mgASALe//HmyuXRuiRumSyXkdwu01a017qxoxzCn64WBhzLsV6SJZ762AFQUAQHMBQfXBoWuEEPj45VNQgurFjaFOVMD1BQaoJA0dt+NRKGaQTOAdnI/a7jKwzJE0R5lgx4ThN+Fahn3/85ROplCE2mH+FWZmI/ag0OKDgicBkglJkaca15a80HlsYNvTEFNRQAA//pwBJnFAAAB8AjaOM8wpEFjm0okI4aIxItjR4zskR+Mq9zGiYKBgJV//DYaAoiII2qlLU4vXBOpqFE8eGSyznid2/5WtTu0TPGAkmacScNZOOYMBJLA7wRD64oylkmtYvStagAAgCc3/4rFSdgHINzwyipOPUJZ63mznZizcnjzO6APDFYNFu6AmyLFaTILVhgzQVYtKALyWVUJLgLllP1K2OchcAAAAp3bYNU/WWgvx4jyXVUJpvCdgeeGKB5LI5UUZs05q/joBDOiPNIV/49KjqYu1uy4YfF26bSR4Djx/a1iUpIXvecYzvgACFNtghWoQRgRx0NxqsRzHawTYKdnC7iQZGjgwjuR99NeYfWfJeQpR51CSixMSEY9hmMaIhh0459rBjimWADnS6zahSwRpamIKaigAP/6cgRJXQAIgiEl1xnpK7RBhNs6MGWFiGCNaOC8oPEQjmycw5WKABttoxO3D+ZQ2wRgURTsSHy6uXq75Abxv0jml9W06NIeptP+uMArfUd/8qrQeMeS2kqT/foU2VE45jtouhd4p2dnIvSAAGgAcjbGedKjUNSl8P0a/H5hTUcWI+NRpnRDm5V++vESfCxCq/1Ei6kcXRh2PunlFe+111a/Q+q0xWlzb2F77UEBCtkguAoMspReDUz4ZEC3DjzsM9TikJMqclF0DW+o6v/HjxzQYHiCAhofxjUHbYUPsaXIvVEj6GplU3lGJKtvWlVZAE9tuJjQjxSahM4t6sLmHlQKvCzZ0DKvZJWV2374DFpegLZ0nTwaFXVsGPoAwMpWtOZahdBH3TvEB6AhzxhMCnCsIk0xBTUUAAAA//pwBOD6AAAB+RlX0ewp5EBjqxM8wjqJJJlnRJRUkRwTbOiUCbIAAAgCpbaM2gR3IUwWYtA0bUUlon54Z96rtvEUtjfxkRa44gxtfjQoV1nW+dtRr8eMXoY1D3Pv0fZsa1Cfe7UAZtthm0C0QTg7kodzgJHg/FBf4vPdFCa7JOMrt/yk+EA6/GyJNKECi+ACKFpQKD2OfNMWJq3Dw0xOt50U7RS+tAEAwW9/+LguFD7yoREdMT21OjJv0Pv4ljIjep1oMHZvQLKV66vT/QM9mxsoLPJCIYlxGDbmDRGVYVciaFuLMUfGFlD3VLUvsNAAgkRHt9xcJkHJjIcn81uzmreK39DBqljuEoSjtq+/0yjAnu3bQ3/uC7uwyvsRxRGHhELqcGlC6YaTzApsXXDraAADTXYiQmIKaP/6cgQdxQAIgiQlWlAsKGw8xGsTPQI0iAxxZ0CkQdEVEezo8xziAAUIAO2SCoWYxPE1a53ZPLZWmmJdhmJuMZKzlbb8U/Cx+/9mBXiUkLJscQfuGSrIRPhvLsk4sD5w09KDY216G5e48SFgVNtsPFh4finlWmqlpKwljnwWwwIdNUEwbTNjfgQr8D3/lBaiJx2LqYwwM1i8uKXMSB2rfDEdeL1ilSf6wgAT334pVo0YB+ZzW0/TXizKyd3BauhPVW/wQCT4N7fAr0k0rUKOBGF4Prp11JoUJEgJ7Xk2yA59zFEkFanPH3jAG3//xir1qdqkkeitQgNwbqOaUlTDZdeYRq056DaraANLVV6kv/VJYsLKYlyxdbj5MLCm5RJ88F8PlFt3WZp1o1nunxqYgpqKZlxybgAAAAAA//pwBJCgAAgCGCLXuYUT1EIDmvM8ZXSINI1k5IRMMRISrJzBHaqAAIN22EHuiC8KlJsA3XoO+nruwK+KBT48YKN2G/Xwb9f/wsSD6HrDYmFxSYMRZjlbyKhritqBglWxmxaxjmhdcXbpBKu2wk7axo4KQO8x1HhX4Y8Ib4bR4PjAVFJTgIGStV8vbA3V5RT5Vk2eniA41MtFSTloqe9TY5JpvKZpMZfjt1+cgAHLJBT1hPqMUEQk44rp3IFgXdNHQSIrMnfTUj/Lu3+g8iL20wZIjzwQxKgXeB2wWdItXNmxR2pDp1bYqWOpNqEMBA29/+MR2RvE4RQzpW3G3n1D5HufNdBhr5zUV81FBz11N/+hJqkkMfOAmJLXCrQhWjVeYYJxTViI01anDnEwG60VotYmIKaigAAAAP/6cgR0nQAIghAV1ZnpEtRCxGs6PYIpiJiVXuY8ptEQjmuc8wmaABlto7mbApNSQl2JkF2riVorWW8SBv6GcuzEuycfQVQdV6btiKpJ+b+Gq/F4asCG0iqtDgUo6yChSi8ws19JhFcUAAJAAqWSDzwJY5RmA3WYa3clTzsGC5NbpGlZT83fI/aif+wLboQEhUCWaxQXFDpcNsWhjHQw409gvNcLJKWiOxgboIAnttxvsCtQkfwm7acTXWSN7rTId1WrGR4RGxB0O2veQENTs238pquIvfUttDxt3R6BY42SVeE8+KLB5BtAifAQ80RUswAd12EtmxX3G6CNFiQ18ZcRQ0QfSJ57x82R7F8ockMNO2uj52+QB+Aj6ws3IkHuWwax9uptuOaUPgUKkDb1qpqspMzCYgpqKAAA//pwBOVgAAiCFhjXOek6lEAEu0o8xziIQI1eZ4jvGRaSa8xnlGoAgBV22GaKdkTTU9M8OqCSSR6w5y8LXmjBVjUqKZwglF53eg35L5VxR7sNnqxfrQ5ZtI2pLIG7GPfQ2l7VOe6VAyxQAAtAW//+M6tFoYBRQJc6g/Qw0GjCCs0qXrulWu2nfCQ38v/9bopdRGAyKFWTJky1ya51poOOcuiK/jzLvgy/oAS3/w9W9faG+ITPTUcsvhqv1ccFsMDGMuwqKZy9u8qDoX/bX/oT8bMZ/6vgolPmyJ/ovGQZW1n3dMnod/rLADd/7IL22wcacLcR9BiGoY2ECwwssf7Rh/jAs0ucNDEHXbXvg/6G/9xrogicDQqIx/egGV4saLuAq4FFka5IextraAZK7b2GsemIKaimZccm4P/6cgSpEAAEAhwY1psFQ1RAxGr3GecYh6RzY0ekSFD9iGzoxBzmBLu2w1ZlLlSbjYIJIkdXFWnMYhUUwvIGBYIURe54dYaV9tb1Cr8N/Q9Y4e+LlR7S6UipjqZYplQxzkSGEGaCXW9vSRAIClttgykQh0LvAF6g4Sbbk1WFje4tL5opFlqDojt30TvUO0tVv/WXWtVCmIWLJmNGWN6zz1qkXC71Xbt7nMT6JAAVN/+JKPJItl0nC0uGLxJWZsJowMZFeHz/trkC+0W6vSkXHG7xRJlzRSK3E01teRFGVKo3U6z76FaynXAAMhKkskCF27UcRDD0zId4XcW0AqKMpZNAhB+B1k7rhEzBie9q3myzSMqWIlHaRbUKi5As7H2GE2y9DoztZjExBTUUzLjk3AAAAAAAAAAAAAAA//pwBCQGAAiCIR5XOek6VEIEWzo9BTWIRHdnQLDhkRIeLBz2FOMAgBt22GNpI6o7a5iuE9kAyo3om6k/ZO6krOnoKvoLk6N9/VGbtdmMJuSp5hrL6pW4zel4ge4o9o2AGjdo2XsfZu2r9AAASAJWyQY3uTn4O9UghBqHQ0rPBx1HQd0Gj3LVX17XAO++O/9xpOgICoulZJJQUZejMCxBJtZevSPFmXduh6me5ZIBS//8WhOfQhpBlJRv+oqVexq9KtV0V668ztHgCy9na7Sj+UaAxwaQFBRS2EhZDJh5e94mcxloqXcfX3pYtX03Y0gKv/4Hj1h2JEEJZhbhuvUa7PuTo8/j0rcYLizmxr5NWuBPwGC6E+uIi6LRE790ZdKq1rIdtXOlVPp6DvCmjcfllIcOaTEFNRQAAP/6cATItQAFkfck2TjPKRREAysDPMU2h/BlZGCwQVEEE2wcZ5xawACrv/w9FYsUeqvI+CEGIgHmbCXdWS6DBaNdTvl+o9/oszf7OsaO7RCbWcQfFWONeg0ULNoqahtpRupvTXTUAlv/xmz1iySMYJNguUFSW3MnCDzujo7oLu61Xr2wdtT/pcQCKd4DYtSAgwGAHHutLvGlHCouZK8yuYJB+NhRzUtIvCYSeOnURnZyCZa4szFqagvB9lZUdATyOkej6tUTyr/ikB9LlhUGSxMasUmUmSFwbBV7HopKGnmXTQthEQPrmrVGBTzwmJ2T1uIpBaa4zqTqH42LKo4mry97yvTVsY/XRv76+XbNkCSB4SFJ4WDUqdHDZQhpC9W1tbB6ByaSD3qSs0mIKaimZccm4AAAAAAAAAD/+nIEuO0ACAIWGVSbDypkQwRbFzzCOIfom2VAvEGxFY5raYYU4gAZJIMZ1N9qr5qqoEAKUMg+wsaYUbUHS8SI8tdinxJWXsEk8iKe8orLoIP+2ItYXez61C1rde1Lc+31sE0Z7bur/6YGBl//8ZccM2yYECR4VaRvZumAPgHllRq1MErhxqNl5v4//sDM4CUOeiwk9L0H2iBZJF7SbZhxQMXuWtC1DHhwxG+wIASrZIKbXOyQLLjV3JeN1vysiFCNRrn7tDNCcmjbN9f/urOwvGaYvHOjrm9bVzQstazBhtqXGHUGKa0pcQeaAACAIy22jVFGIFpEthQ0fIuDZNtjmTwPX3jxHYgUCorKQYy7NQVez1d/iYABN9zxCH7Ji3dfbdcfZP5sw1iIQNs7cnl9CYgpqKZlxybgAP/6cARw7wAAAiIY2VGIEixCg5rXPWdUiHRzXOekqlEEDGx0wxTiQACEJOWSDO5fx0FggC2xukinAVdQNqY5FqqQbKrYPNwM+LSS/CtamKtHtKngIJiR55ZL0ALprddcqgiJN4sF8Yt1IuRgKCnbbg1UhJaCFrBcFIWmH7XpkrPCenFXuJhvPahranaENHmBht+JK3+QvQWkszXPqvzlf1Ckyz4aFnjVcoHniwqwAgKl22GNrpLxRlswXhY9n3zODPOCecUxJ4adamxjS831Fh/xRfqS2tjQkPMkjzxglYwFkTox6bkf0WirselKLnyIweAAADCAVP/+MjXFioKloINskVjovoNIaFZQkG+ZfM++j4bwEvx7hWkE3Gn1C8MkUlN9IRMA2BqicVDjmaTs9omS6YgpqKAAAAD/+nIE19IACIIHM1e55RvsQsOK+jynVoh8+2BnmEmRFJGsDPMI6gCABSyAfaS1MhIwzgCegsWL9m90c0uYfDauoe0HHJ2/Ff0F//imR6Fy/tTyzZDqk5KdMw03OgwmEZUUEioG5IXcAADAKl22Hj0zCJAQcrTr04Sng0orkaQKMH9SXyRfbR8Y/KBJpztSUxwlDOQM4HQOsv8tFm3tQ+tbIJnBGELSe8SIeAXNuBKztjmsEyE5JWQ9lDFp6rk3RbvepOO76+4xbQRrI31LcroIcWUy/uFMUl1T639HdUOwoS9f/9UXMswl0hMezdFQApduJXNSxpiDCQp9Iilm6rpk1cH9vsitVFUKgRLqjfTfQaf/uUcSJKP+I1njISE+uejAspzWK3XYLXGh5Y6ZCRqyZBR6YgpqKAAAAP/6cARpEAAMwfswWRmDEmRDZkrzPSI+iLSXYmeYSNECESwM8xTiAKm34hgLhaiEcCoN0TvTHKixllc8VRR099wpgmTt9OR0Gn1/crqiV/bp1lMoI4ZR+ZMuqBx7nAkZQSFe7vR9QATu2DafjFCQ4LWEJXAUahFNqcoGGcKbneR/x26iNy0PMLo4349kggEpHq33UbvX/+qrdyhnUME76B0FjKDK7VN2VawUtv+MxCMK6coZ1klm0DJ2nSAtwp6PH3q41w4kPKrL7vogcZP/QMLKqCjuZviz2QCs6fHHy8ulgkn2tEYqEbWlk2MYn7Fgp3b8c0duoJPVGQ0gk5SeV6BNysdJC9SIHHjGEVKg76iFUcYGmXt8WSVK7/SsFoduX+0YJmB+zQFSyUQ2/rLl2JiCmopmXHJuAAD/+nIEJI8ADIH0I1eZ6So0QoMq0z0FSoiAc15njOmRDIyrnPSM4gFN/wPOH0nKD4GEmi8vjKNWsvjdYR3W53ugw4OkbTX5xb2KOFCaflQeMC07SdLAmq50AKPkZRxCxQfsHL1hgBG7cDkub3BaDYGEEHNmlnoOCilAHZwPVIslaepIMHxzxmTvUWAphhv6BcDN1WZRoNKIpHzyptqWkmBpyjbljVPCiwDdtuO8LsWBbDaLooi+wBYl0OkALjH2wynhkAG0c3b8s3oERwKLDDIPji8UayrHHyyEvRdeqm19Xq4o8UAqUtNuPQEVKCrd+BgpUiyyDmYAKR0zFVzIn6FqDQZP2bPMbigNXHBoc8z8KYhiwIqE1aS1XL3OQOk5UULsSsckEFF0CIc1iAMczCYgpqKZlxybgAAAAP/6cASUUQAIkhgc1pnmEpRBgmrXPWNGh/hjXueUy7EJFyrNhJTqAUt2wxKnyILUb6BJ8ZWlpJfBnQBngRBljXXBuHa7mp+YoklMIKV5W6yuy90raoqRCwoPMmkpOrLNay80mQ1kSVaUbgAgBXbYYHmd7O9J+lQgg5pzNA9JIgyCHAu22Cj2ElC6yDuK9mh/xLALEOvzRcQpqqPIPmzZKphhhs3JqZ3i5sfaoICUkcHsQNCWkPk31QeV3beqnwto1C3cnUhxh5TtRPjUKPnhpH4oYArtKvoUlqo02EQJqpv+lyEELQG121gWSsAu7Wo8Mi2naxgOiGx8Gj8lkJRYQ3xW4cy2zSYmxmKMBmzN6M9+VD/+QzolC67m1o6MIg6GhYoOUeYTi5eWa1D/doTEFNRTMuOTcAAAAAD/+nIEbEUACJIjGNcZ5hm0QcJ69z0iSIhocWDnmKWRDQyr3MMVSglNt+O9DvXaWQguadC5MQ2afQbNGF1wRdzj2cAbKXp+LZmg14YeNc9VUVKW926ZQ9rXCqhc04YQKqaNPhFDwTD42pt1AAgTN/+OadVuJFTwx0S9QfO1R8Cvysl9yLes+gpvLuwq3xZ504hz3w84SPLpOXirws4mi4MRiyVA221N0P3LDFDlkBc3/4+zNQ9mGwwWGwadjKedr06T7jV0Z0uV/3jQCauo9zeoTWttTF3vAQQWF1NVNDHPPRjULUo0aNtQ0hbsFnjtouEIvaPDB9VoewJKIihuSNcnOIOLcFTVZBlBYqU/Di5X60iiFBTJOUyG0BsyGoCCLBzqxIXCFDRqqgigJpYKFyM3Y5VCYgpqKAAAAP/6cAQO9QAMggcc1pgvKGQ/I4rnPShEiOBlUmekTREdjGmNhJXaCMu2wpOjHwaRYEucm1W366v7gjdxpBt2q6mECWGs0rU8E+o+E39VKvFUiboFRIaK1hkNi3Ywwi80JldhFKuPY1AAQnd/wOdqYc/IuQmEk+Xc/mk6BlcrMng2+utm4ObWL/4ADldXwYACHMAGYt1sg2C8Jm2pgWGaWF2pYqITxqsAqW2jvxOA0zaA3heKJToecykcm09qJAa3I4HDsZHs+2jD6FG6BjZ/hAo6JY99IS/FlGkuaICwSCyLULEO/2UP3ML3VyatKgCpJINhUBYM8U2xhPQQFXXAxQJd2GpbVVgZk29dAKStg5OM3fKOlc53qI9qhh9yg3dzj1J0vbWVHO6gGpLGp5+9mwb419uhaYgpqKD/+nIE0FUACBHQHFi5gRNkRGR7GjDHO4kAc1zgsEGZE5grTPGV3wDBK7/8TgnH7o0n0peIn/ZSBMkMgmFLzCw8P+Qn8MJvefVX3A2sZcnGbFISypjWOepEkeapKEvd8qkEBJAHLZIMJpSdftwbgZtG52aU6w3F/IfaaidenLUOCScYiEe391LqwJBw8achUsG3OJWpMjTKUiY1mV0TL395p/mqMqUFXb7iENKqFAVh4SGErMXjNraNBIPZhmR8Mz98v7v3uLF9t/hsaN7uppZudt322GricaSGkxt//HH3+68uv7hZxX7TQuX3isArNlMxNHX0iXx5BSknlgyR5SI6kJh7l0MqEDP17WUI1IyhrB5CrNnOrikxDy/LJTRaO5xlwR+NqE7m16/4pv733fJW0FMQU1FMy45NwP/6cARvRQAIAhcc2DmCM1xABHqzPSI2iHRzUGeEsBEaDqwckpm3AOS5LJBPic6oCxDNMsRmK3JiD6i1NaxuQOL69H84kKz7cUjINL6mXJXKEgygSizrxRrKCzDCXtItRomxfKSGbSPb1AJW24ZIY3k1uwrsInqIWJFxjmwQwF1S2jVcq1P07yjp8//5B2MY1bei+59mBSCTNc5WzNUuSIxUUUIYCM0gmpQIdttHZB3B0ESF8JUHWSOUyEKOtdEZug2bHTt1QpUoyMER8T1d0TVvw6UO+cDQJQaYxq/lh6EOxAwwKSCCkfv60O/+soYKsskHDY2ytTYbUWprNVUzvGcXfRlezPFyfR7AiaVCjZXjPyPG9pl+pNJEp+SV3F31FRSWW+T/uVdob/J1P0x9JvN39LxMQU1FAAD/+nIEIxEACYISHNdRbyjEQwR6wz0iNohccVrgvEFQ8o4rXMKJjgAANCd2245TEmRd1aTZBUYXs9VEc5cKjVQRdL2ejyi2b6CpPUVjVdYrYq2qtU9sUpVvWtbWzCCteNA6RZriSFnCuoFTb/DJbCNxZoCLFqxYjBW5CKzCHAxrmRerq+gvro+La+oQf/0cdyAaWkWJvZualREc5+s2qBUhpqQuFQFbBtei3+YCC7pGGqND5WkogpTWfOoqVBoIwNiZXW8jOj07dlRRvKHIgx1nTtHvySCoFUodfLKHLW466y5rzzJJBo8pokmkqOjnqALkcYwawtLfplJgTamwnfxikVLC46rfrYB0dv3y9KiVeoSBQ3tKuRZm9qLHsdqDrCINhMEhWJg7ZqRvpTEFNRTMuOTcAAAAAAAAAP/6cAQ7SgAOgfkeVhnpGcRDBIqjPYJUiJhzWmeMSlEUH6rc8wmTCd22wwwiDvJxdCVCGlCoIRxAbTYf+Zob3PzwiQEUZvFlSIoHXy2ZcxLNAuxKDhtLnVNY9Gmwgn9qVO2/0ggLgqbbAZJEUJD0ZISDa4YToRfOiVthd3LLoUZR+DGYEZpjZ++Y3eJVET+yGHZIKDSyg6CSkpMC+eyyloMXhSBmphpyiO247soTXZh3JwhZqSFLYUcoi4Eh7K+9mn1eqsqaC/qQSA1q58KKF0JGLap60Flss7lqaKTq7G9amHiqjwoLiQiJgCk8LlBV23AwQk5CPVLGQAvQzmIsM2NITzTKkKjMYtFsoyVbPk/IXsoYIVSr6yJPrKbW99mRmto1Sfeu+T7nSgM5kdcwSVaYRpiCmooAAAD/+nIEe4wACIIhIta56BIUQeRrCjAidYfkZ1zhPEExEZarKPQJUgAhJ7bceaKY2i/nKXIarcAfY9bGxMewMdUdSt4h9WROM/7f+4UWNitNRqukXMoF1TgsvYmWQtkO0Wy9h0REFLOihsJVAALIC5bJBI7CYg+yXnT+xv9ny1nGuFDma8ay0Z6/SOasqhCpb+jCSaE5AMRO2wfWLz1qRQwrmEFGVoFIvsF7k+buWCpLJBFnog35gpMXBG3R3ZxaFFbqSIDj0qV7YBz/QGI1GhF9jjYWTSXieAhoqXUQchy2RAmmlj7bnxb2IZXHBAA5vvhkjhBMLEFiJahEq6kzgRhi1dSzSRtqA7bP++ILtVne3+5n2/RTpNOUYPhguSE11uutJCWF1AkXLxR5tL+mhvF0xBTUUzLjk3AAAP/6cARSJAAMkh4UVZnmErRAgyr3MCJ1iNhzVGwYqFDxEauoxIjmCD22wywB3MyuTiHD8MdrVHlIk2l/CsDRjj+hb1DDRXmsq/1GDiWHiq0m0uJm2Cm15u0XPvH1EmMcNFiCh90coVcx1CACgtWyQSLweNGanaIfJNYdxXMaNQV1JQSujRjad6DsBEI+lqzBM4MFyg8PjAuTZtYaUen1uPme4KY9WpfWh96KQXLdsPmVtOLOtidJIMrHeEnpuAcKuFmGkZXpuSR1Htp2NEBql3KMea7zb1PYylD9Zs2GijBEHWrkhYrNBUbs0FZZqEJbjugZQVZoHGhsuSD2YCWTbEXEljZaTK1HzNTDEc07Kqe9ABXfilV2/weji63IHJCrydaRs6G3EVV80JH0KeSTEFNRTMuOTcAAAAD/+nIEY7kACIIgGVXRhRMkQkPa6jEoCYhUc17mBKsxBJGqDPSJEgAACBU2+wlMQh0fGo9GQNqroMdad1whBkqAOw6zM9TNX+HNoJelKjakgZhiSK0wqaMkjyVCQVCRYCsnlpbcW1UW+teRAACIJa2QDAnEYccNAqHi+NPpC/DUU738Dlj9NaU4jtq4vgSr8+YZdyUICAkU8LnR6Dqk5eHDISMk5QT76XENj7LKUAuyyQTU7H1ZdQHpYxF6UwDzMFmt42V6N+04CFsZ5xhU41Ce7IkQQi4dNQ6dGCiDzrAkQoeRWsFr2my9alwPXJpRHgiW20NoMoHqmkLL+eKiIdMyeNRBJMKAS5mOmCZE1aZwg7Ut8V+QZn9H6DMAgytttRZUq9aVISQdNLfd02erbambTEFNRTMuOTcAAP/6cAQ4rQAMghwjVBsJEtQ+xLrXMKNZiPytUGwYStENCevotIgmBUtto+RDp4EbVGduEAl+aZr9Q+1+LXDwozNp7ve9srq3oi1tjVF/lZSszP8oxgUWEmzyUYpHuSfZFEvI1M1WJ6k/+NACEZbIBgAoWvvF0vDuH1z9+4LQNzW0Yu62vsFu5Fr9I/+uU5l/6hDa0SxZ5VkNQKNInnk2ZJxOtMSttVHlo8Au66j4yVG5zY4NT6Y0fkr7Rsks4oJvOzJ28DQdRYwtaMmvbL+bt/VzyMlivoxTGDFypsQrAlb2dyWKNoBQeDpJoVeXNrd7ygMlKWyQYbHTcVCwNo7Wy49hWWtgzJqjSbDiZt5VmFEeAlFwUMC6w8QL3NmiASRk3GQsUMoWWGkBG2pAXIM1KcM2R6XJiCmooAD/+nAEKSsACIIaHFY57BFEQyR6xzWCC4hwcVTpMKUQ9Q4r6LCVRgCgO7b8ecgJFRB8B+Ig34oInerj+blS3Wsjrt/ykXbFgiwj5N4nUkWfYymgCz7dVabBOKJITaWoCSQ+kXqSXNFEMeUACCZI2wwZXCOrVFIfCK6MG91RBlw19GbXhtGpqvVvcPdWb3lZQQMERr5ZgSToOhUipLI7NY0USfqnlx4x03eh665FwwU7tsJ4gge9BrREAaPUAfnV4j71zuqliyUUisR1qOLr+R/gwz4AotUReIXnw69wxDnGQhGBl0ji48J9yXvv71tCB9ANFKSyQYnMF3SoER4cqgkeR41XeRkxquZmdhanXr9Q1h/ykg7bF02gdxIVqZHGBQ1DulJ9lLRS95inHtrNpiCmopmXHJuAAAAA//pyBHMcAAiCFRzXUYMrTEGDqoM8RnqH+HNQbBiskRUMa2jxiW4AAFgWpZIJ8LBkqH8PS3l122lFTFkYT3tOIXyXrNmfpDUvlH/jTb0j7RceeUMPFBAKSK5pbz5+19z7WNLbnIke22lASlttEEFQZjtwZFUOM63IaybmePtrEd4hS7+quxaufJ+EI3IHhTidCjECExEzyXucmWXtiKqhjmVzCkzdhtqRfbaCpLbR8sKiI9DZDlYF1lKZx5caS+25agbMplSZ6p3G6lTN+Aq+oQHh5j9V3gNCD6GtsWyfaotXopQje6yv5enQBYSskkHL+uqWbEmQ5JvXkUelFCln2lsoMuGbFDCnp8wx2WICY8aW0F5piHV6Ev7hQUcFUNNrMIQYCusFUoth19K1BTlVJiCmopmXHJuAAAD/+nAE6Q4ADYIfGNQbDCokPgOa6jBiWYiQc05sGE0REIfpjYYJIgFJbaNZkpmgvOW5T7oGQVRmweowGUGI2QpUF2HR31GF7f8JPEKwUNpYmqxfCwZQOUfMsFosLkH2nOOCbHPpSu235l/WAABQRtskGiZBFa5VEQua77XgoJ5aESqd4CXyPk/M/xFtGOignQmThd03gY6iTGKNka6xcJaspc5SSAY1WdACt1UR2JkxYRSRSg9xp9/KaL6WHFDwv69pFI0rWx9xNsZn/bT3RxhC9pq1oQK1Lcw24caSxSrqwWvpcaTYLL4CQh6UIQCZbbRcBAaGQWmfsgG0smh4MiqYUtY6Ee3v47nXthACFB4Vt0vfD7EKlqgMLvFCi0lWpkiRI2yNcoyLXS72NX9F2QR5hMQU1FMy45Nw//pyBJVZAAiR5RlRmykTQECDquckQ12JFHFa56RHeRwOa2j0HOYBX/8Dr7qZg8FyAbgAClxI2wUvljOUgLmJCIEqtmYkLE4enNZ3WWYQMUMv+wnSIDPwygBUIRyTt9f7LvioBypLZIOBouy9UFgNge5iQyYsW2my30Egunmql6BRyPDuPPLNBM6TjjAowOqSHK6+sBDWIF+iH1UVvvPI9AWYhbwXJZIPQgZ4umcyHmc0o6A30cftVGR+hdGmejRBv3eddcB/f//jO8tT31tKEgHryxWMxlvnuQqr/3jot7vq5LDqXXsZ9Pb/7HAwKcmCarmHOpGMVQGUKkPpnizEIMfX0FhceMmulG0dISrV3Vy7vQ9OfUi+MKtAC4fMJWZYs00g4cGQQjnACpxIuSFUpehoHTEFNRQAAAD/+nAEBQ4ACMIdHFbR5ilEPyOaxz2CDIi0j1tGsENRBQxqjYSU3gABhDm3348EKd6mh7ooB3QrbEOc1lZ1ePo32UUMyHer1CjaNEg8pPRuc26TKJLjAghmuuyo8BXBo69iryrwmlXc3clQBg3dtsMEPL7cioOQaE543uj3d1Shainq3QkvctDZ+KH+BGh/akETZApS1Iqq9FaaHt0oWxB+LL54WT2WS3xYlw3f/+MFCLZSKSfVByFn6pamrLVUhRCoQJPatW7vXqX4Mr1/li5F6BHijBLaMGqB9V4DmB4eS6HRa2Meh6i1CWzCXmSVIKcjbHFYIjA9hrb8DSQC2iVS1q2Ai8ijr26NFkoMM6I3qCvY0IUczJoUaoFXPoUeNBEV3NnkEUisd7krFfRl2ClKYgpqKZlxybgA//pyBA2/AAOSARzVOekRtEBjmuowwjmIbI1QrD0DGROOq2jzCN8B4Tu22Hgk9CaWE4S9QugiSzAtT0CueUrOM6cqMyyvf6CTvtcU3THBEJDVvIJsHuF2IrTKKQy/Ly/+tyUbK2r0AABKE5bJBgN2FcjSPQhqlWq6vCe6BY1rfBy0Cd22oV6tifwiSeggMU7jZmPexVKF3B8zXXcBaGAJxNq2Crr6ICBDuXKGCSnoOA/1eNSj2P5E7jCrzLvXpb4mS5r0hb9fwjiKeoGkV8d8/NmC9amrIG95aXS2GPHpu6Y3DHvV3rO30Z+0BQKslMBej0Vi0Hoh5iar4HwdWZSOuiO9ydtdG+goW4RV8TRnbf7s985ckX/Uj7mOAvywGu6tsZrrlVrXb9yz/vYy/+r1MQU1FMy45NwAAAD/+nAEVdQADAIXHVObDDm0QuPK6jBiT4hol1BsCQnRCAxojaeNEAFbdgMZwkgiM5yISvWHC19+GKNC9SlSIzUHqHL3KNt/4LDZ+hcdUXDIdXQLIhSZ2B481sIRC4OpXScYSDLkpEpbe1IAIlBrWyQSOJS6INCmPC55YLzgVvnoN0v5CvTU0vSbF/mdziwsPNixkXC49iVHiEVQjeOehdpKwIKF0N307lu+K35EF3bYDciLDoCg9NNfjwMnrjCA4MSgwVoNsSlfDHTGPr74AXK4Sbe2Wv/6VJhZH4WJmasYbeH3qFjCKlUvIMOED48f6gF//+KExI5HZUErQHCxEbIhUi7hTN6icT8kYSsFqtYIMpD5xAMhPKXG+F0E5VsMHqlmxWatMVUM7bBZPgfI///9aUxBTUUzLjk3//pyBIrtAAiSIRzV0eYqVkQieqo9IzWIVNlQZ5hqUQkOKp2EjN4AAJgpt/wMsgUx9HGJyxNZrfavGa4n++wZpa1tM1lDNPOigAW6socGEud+/TLX847+ju1c3Ac/HqNT/X9/dzkzm7GP/gAA0hCSOMYDpa2iAJ2fhZgIylZiCGMAnA5wJTy+EDVujSZLUe4wNoUsBFh5JyjSUn0VGDpmSZF0HmFAmRcNUDUPpXNawXNvgPDFmA8jyNQeRd0VhUSwgTcco+aSwWecY0yBjN+f8KJb9Gpz//3WW5vLyeX2Fved/Og3zJlCGwSgE+/uauu0COCdmStTxyCKshecmOAJpDTsJqSQkQTODpn82jdO1EbO7weHNgAMiQ4RfIqeggvallY9AbfYo+3rOPDZhgGLpefWDCYgpqKAAAD/+nAEn9AACIIVGNS55iokQqOa2ikiC4hMcVVHmKtxDhTojZSJ2gCBObfYd4SMGiwBQFWqRmWFsrsWUReUlB6j/MtWUUdt+kJ8NhTxUe83cRyhuw2gQAIAmYYOnlPD/fcbMITdR2qvT2gABMU7bJBA4MvbBQEw0FebzeZsY0t7xyrBAh5ZB2/tOb1EOXXyThKdMYAdSk4XsD9KnRRcKULSsXMRcWtQARSbV0pBIJSRtjDWDjLzUuyEoYPG6WstZr4Pz2+uuzviLnXj5JB+j/Cu2LDbKh5gkVAS9qhax0VU5Ms8ObAFLNGp+mv33J94JckkEqNMViKn2eDMSmgSwLKMpYRG41PJRWou9da1bF0kP7XODT63VlcirRHxH4g238q9+vwVjChNRhB/sRLXaG/9NSYgpqKAAAAA//pyBN5QAAuCHBzW0eMqTEHjmqo9IkuHIJVbBYRPMQUK62jzHSYAAJSXbZIPYaLbEXbivM8ohTdtQyXqO1HeMOiVI//Cb74irLJKgKCI6ZItNoRUwSBM016VWvFhUGUKKoHDm3da77LusAAXQlZHAJSQNi00KoWxCacGczRy1msp3VTXmfz+g3qFHI6O6Pi/sHFiBzWEANcYejXjK34zW26seFiwIAGLODThoQaIUjIcCLJ4L53rOw0ybESpL0x1QcvwTZ2i3N33+V2GKINB9jmRhGAxh+uoXUa2UMGso77em1pVAfJqWyQdIt0JzMN4Uyr2P1+m01rbw+QaMroFmPK0c9GKxep+9/mjRwrokFwOUFXu6IhBdUwPIaZlMYmixRKzS5NKYgpqKZlxybgAAAAAAAAAAAAAAAD/+nAEk3QACYIREVbQTBBsQsOayjBiV4fgL1tBMEFxDxZpzPCJ+gABlJjtkgiloydrGhj9lN3lWPL8YwuWy84Jg8SOcqDVSXemcHlRGGWD8VZPTaQ2SHrqABsY5pEfaq0G5Efctsf5JYAAMlOWyQaISZ3AVEsrDPTinpHvtWxaj7wSsV7pY+ugVPi3+Hg7FxgcKwyOvFdzlNcQFHgKlGVOGmsYh63Z1il6Y5AlBO2MPARQIViSWMrn8iuK0ggiRZFgisQ35V94Vdi0BFyjHhlsmSM2pa98OhJNaRYNtV20ILqABMWepD5XQpAKl22EgQWCyLAQAcYDSWJsMWD4kHuSxmYDK96HdnL8gq8hunL+T/KnQE7tFZokxm1ZGXUKjgHJE3PSdcFixelz0/agcmIKaimZccm4AAAA//pyBNOEAAiCFxjUueUbJEKjKndgxVTIhHVQ57BFUQ6MqhzzHVIAIcm+2GUsHcd0VdabRU2aWOnS9QJidpu9SWregQnVuGUopzIG+s9MD3IB6HybAwSEITig1TnV2fqSE0C9TJw+12rWAQJ23YCsk1K7MXHUPkqm8lxe9bvDhCq8Uy0f5iPQ7XTmwOnHU3RXo9X7aV//1z/vtXSam808iZg2RfD/avVP806pVgTu22FToChI4X7mcgWD1OfLHLCysBTnz17KrbIq1xWq9sB/EA0UpUokBTL0mXFHC4oRfEdMuuXJXxWQUAb3yDkKW+1bQpdtsOLyVLrxLxih2jUb09tniDhb6m1Wnb58yhbt1RQgHS5MsH9MiRBUqXOJJEJzF+Ta0C1Kgcy6fFEpX7Gr1IUhGhMQU1FAAAD/+nAE3ygACIHeGNQ5rBDkP8Jqh2ECUoiIdVVGJEjxERFrKJCNtgFAO63YRLwYhQA0cnIIAzhNH1+C/qo+xNMghRuh6H3/8NaoKiJ9NyEJWKClkxMbfr/40SBCKGuipGkWoACEZddhkruUXHVKhXhZk2vVu1Yhc4N4i9ldZkNvUQwvyeUs9kwttOKmQ5NBBdzvWqOfIgOMG1EEBLoWQWuloEGQF2RtiSomCW62yGG9DMNsSeYw/iNzoe3izOnTXplI3ix3DpEITx5iSZZTlQCNeDpJAHBcyZBIlkWPxb3PfQrUXWu7oBYJW1yCwEUonEo8KF0hXX7UIkn0OVkJwTBmL5teQgy+cqamOSHBs0eFkuFiSBUzuF2e+wJNMR5YJwKYelLTSK1KfZaSTEFNRTMuOTcAAAAAAAAA//pyBJGhAAiCGhNSuewZtESDmsoh5ReIGE1TR6RikQgMa7SRIVYBATlktEhQkjEqQ5XSgWwLLi2vcqBvtOzwrsYuCfOmd9xgdE0usBw0JmT77Q8hKr3+v+25raToshzjrTaTrSVztx1rQAAmKdskgwyUa3EYO6dpqxXqGSihivpO6kxZ67p29QsV0xUwQks0Agaass3H20MsCUeETceog5QrWTvfUEzRJjtaZlGkQg3dtsOhR3kxDFHSAGKFJcQcC/Ms4pU7xsqAhsTuJyOVFmNVQs4gXUMnR5hzgLTFip4Zm1C7iTErT6F7qoy+yYALhc1skFlvjIiIDYq09DF42SzOGR4x7i5tvmeGr/Gg44gsyDRsQIrNNhMPy6CZsu2Ab0s5BNnPhdMWat+Re5nejrTEFNRTMuOTcAD/+nAEOHcAAIIDI9bRgypcQ0MaqjGFCYgYT07mPKLREoxrKJEV1gAWrDdtkgk9S2NcoMSzJDnDdCIpFs9rLStQia7tvYqf7Xq9p4yfBxzhGXYu1WNGkC1AGc9xlF17jS1jk9lNXoLAAAuUZI2xgUoeH4XqFp+mJ97gJGHZw/4WPYO0MEKNvSyroC8gV+iBSio5LSMsXWpJ4eSqVOocXF7mX2bSioB0rTcgoAEKW23DQiQxWmrrgUb7MeKk1g3PHqpj7bMtRrQ5oC4EHAu0GAsHGgMoSBygLAQmERxRoZp/dpZpX99DFNsBy7JjOWrbJBwaXtoQkYlBlUTf6nazVwC2DNC3Y9Wu7zsp9A3hoUc49EahG4DvPEFgYKEnGgALF22sJT+uxy0NOtrxRjyHQm9MQU1FMy45NwAA//pwBHcsAAiCBhxVOYMSLEKh2no841CIjE9XpgjHcRSG6Vz2CNoCoUskcEjCAGwwZThUfyMy1JZoqWm9AGWpy69sCMtjoJFWB5qECdIwyxDapxNilgZ5gIVD9i6amruvomZsVYhuZAAAIFS27DBEKWdAQR9rA9DePCDcIrBXLKZA9BJUqC9/lm5J3iY+saXi0uv73e0D0PCx+XNjhRixfWuNDAfNikM1MUATClZZIMJ6RQe5CEAKdZ3uJzxvKUnGmBNvruhZleBbVoorXyA4+B2xUyUSbePasVS5ZgFFLFxKFUoKgIBZhAqgrDZjrQC5bbhCCV7nP8nhI0OBuEwALDPkGi9rNHdwd61ByFr7RZ7FB53UOekKRc1IOWnWvc1xusXWadWLorzHDzhdzTABKra4OpiCmooAAP/6cgSxGAAMggsZTxsME7BDAxoTYGJ2iHRjPmwwqYEEDmsoYJ4eBNttopTMpDgcYIjOenYQNUHqMqerbvz9WmYPvAUgLdlswyxTjAW3RR49q5muHpQUob37UIqvfaS/3/1IX39C/t0ApySAR0twzBoayh1S4UmhkLdBqd+hxZlnLqXILZHS24MNVfBM+j/GNigDCowWolkFhuSMIAwAEzc66i1be1S5feu5IK222FADVqmA+FYXJdcj8wF7QMkslmRIZJYHlnh3q6Frb8xFljS2fI5fm0AR+968UXF4Avo93pLRld8ZZ1Vd7moFuu49i5LZIOLKgamQHCiyX3CMhCX5Fe0+YgCK0CRd54NM2mS/0FkiiliYPsNXjC829Zo4aedFGk3uZbS88rrVRsL9yExBTUUzLjk3AAAA//pwBFBqAAyB/BzTmewpRkMjmsolhQeIrGVCbDEoSPuIahzBic4N27ajJhc8hfxV4kCj/d6kO0NFiFvo0zdRz0fdsDkryii7za8PvdzxHQeTujVaqb//65v/UgV8rnWEnkZMtJ1QAAqSltkgsag98aYPXXDOG1dIldR7v1F2YrNWifEwget0i0nM1uOEyxdCEC4owyOctqnNYp7RtZAmjF1I1UvaJrI7eHNv+BULcsBGqF0kllsIUqrypgrICAKMAgB5UXydMTdaWXq8F8TnvrHZ8qxLvfbFwav2Ym79pld2avIYk3/ebc997X1vXDlGSNsYBmAsNKR+Io147uSLO9yKHso7P7gwXHKNH1UPwy54tkzLpznhyWEzB40XqejGdrp9Kzh1hp/t0UPnUpiCmopmXHJuAAAAAP/6cgQtMQAAEh8M1lEmELw/ASrKJQYJiFxxTGekRRENDir0kQ2eABB0N2WSDhpcL+jB85bvC1HamGOQ1rh+DN5Z+LCq1Eb29yw2EUA4DocEh1csNILgQLBsSKLrSNYOmgsJgypK5+9K9NAAIlpvWyQUPFm0BzGmU3lE+oZcNhlgiDZMHz4PKTM3qa4nHBYcSais3SkaLNV0JUZZKqnUusAiF6WuZWxsWioku22FBSWs0FWTMQli1uis0NU2SYPML1vndc45XEijFfCgFuCWYlVQykLC2SHBYAFsjW9qS/U1N3eKTjGkkigB11ZYAAAExFzXjgfYe9zZbdrzLQwDceEcSDG38ini3fL6aAwd1mociPYBSD3mYvMmFNS3Cri7lrSp5FVaSOYneFt7qW6LLUxBTUUzLjk3AAAA//pwBAblAAgR/hNSGeUbpEDDmrokRl2I0HFA7DzFEREOKlzElR4JS26iwPNxBRmgTFXlalY8eiahInMjK8j4ajk9McGUFtLQ3B3QSd5FI9CP21B/q9wwiFEiMrKhFyiCyvervF+4AMDC7rZIOJwfOwKq6nJARPAaD7SKW3lOUFoSm29LCD9RjdW4ceozARAcsVDQ88dSkUthIz887GPmluW3VtYMRoIEpJJBVB2HhFSBxS8hXuDItnNQ/1YLlfR5+zJkSDI7r/6cs3PqLbPea/BEvLfBpc0KqH1GPXteg1M0jNzes9V352yxlKvuIOU7dMCCMj1LCeGNHXtGuyLxp9NoO3kYSSiD7+9gVK8axoGuJCIy5gJAhGg6C50eJRqyrnDLHbk0naJ1I91tabZHNprTEFNRQAAAAP/6cgQd9QAMwhEY0BsGK1RD4yqXJCV3iChzQGwgTRD9DynM9gxuDUkggyAT4sIiv0hOjK6lU4wpx7NrzXX0C8GvQkJXkt3Wzfn3B0GD3zhM66CsqACKjx9IYqFhMSN61cWXYQSr//s3AHKdtkg4fQCdq0jJBRufi6k1KK6JbMoQrHVX4kLhmRIoaHqSCS7QxEwOOdAbyaQg2hutLSNxsljDDkHVnnG48m+xYugBSSSCUgg04ATDUHpd27AUYd+OtcqtCw9EzsX1YUXSLkejvjR+/epx23UUwVGktR+dI2qWhKdaetKH3qo16BnX+oN2RxjKKKAr1jB0MjJeq7onckoa3bf2nfgLxgI4tX4yDEV4MZBzGTps28ZpvfONSyvatdgBx6qHBR6RBW3R/rTEFNRTMuOTcAAAAAAA//pwBD9JAAiCIRlVUSYQ3ESDGoo9Ih2IEHFM57CjEPsOKej0iG4AAqSXbZIKA9QdFwa52jmR6ELW8qi0X67EqP/fQRpFHYRFQ85LSUkBBySCRU0ggMZUhTQGRMhEQoQhqZhUVLnGNjNqNYAAdBOSOQdQjYUBKsuDQJ8Wr43SiHZt602U9YdEah9exoILqm35Lef2KSxJ5KWavpa2VE5xw5LBdhhLFiyADUNJoRlgyoF3XbDIwmIYwGj49NzFti6uxPhwFzQIjr2VldMf/fEfx4eKisRmQiVGGXOd1L8iccg9NLWPFekbc9tJp6PGwFcEISsjbHQoaR4jzZUSGYCubrSpY2SJV3rN0K6sobb9BH4GfNBq98E6y4wULqLEZKmmbsWrSq0UXxqq31Pb+lKYgpqKZlxybgAAAP/6cgSFNwAIghol1DmHG95Cg5ozYeUYiHBxSuewRRD8Ceno8ZlmAeQ7bIBgEUIbGoP0YNbIHWlZah+0sioZbaaYPmMj/y+ET6XYVyQvKMurXIGb7vfW1PXLjtp/TkTjGd818H6revoFv0J222jS4IeGiFgTswoRySnNqPpWx4BZVvVi8SCUbiRKW8wOO1oJzA+bFDAmsQPMXPTudcbQ4ktLLPfXaxNL1pqZ9yyBO7bYcLpqKYf5PQNGTi4ltouM6NnyQKZGt9Kbn7/C+8d3UsRvEQiFljGC4fljLWkkP1CjT5LcVUi99jGscvQ5ou5CASKMkbYwjxdytet0BbT+EDgdhVfasRfjBpyhef5Z9y6ebVtcbBG4tavB+xrYU0qocaA0VVIRZqHwnb26FCtT0xBTUUzLjk3AAAAA//pwBFk+AAiSEAzRGwwRtEEhKiNh5haIRGVS5jxC8QyOaN2GDGoFS22jhQyqTBTCFEMSXcL14DikcyXJVhA8HaLAbHiYZH05amcajx1i4DOFwAt59jR41OKqNqmDYol6RBpaKFzbWRQFS22j2WDuYARzHCEST8QOs/xBancFYFg04LsKrcVZ5rKGIfQ1SDrAq6CSFDrGZzUWCwuoLHTDyvoA7Jmlx6yqlRypbZIMBn5mgCBKhULXdSiqqCB3Z6jnH9Q6vuj07XILuYHnP1CFQ4GACOAlYsnUbStuJwI5iUIMMZyXLtaaiJatuwQUl0yMca0MYDi0wBIewHEg0rnLWmYuouPw6MKjej8/hRQDhQ4I/rFyLj9E+pMZDJQppcZQokMqacBNWx/1oCOLGG0piCmopmXHJuAAAP/6cgSI+wAJkhEZ01HpEbxAQ5qtMMVXiARJTOwkpTEIDmposwh+AAAIJORtjIuDCZBli3loTUTcTqQuu4TZtXUERKqWlDbB/oUVyf33LUkwm2IxdDhRd8USHW3l3oSPaXhfj777w/P0gAAFOIpa2SDUxyEVWIB9rpEvNSxNR0h8pDURbdQ5VuTf9PwSpxQNPJ3nwT6UFjGFqRVUUYaFkGFWPbq70Jq3/WUouaYH7pxwgOGwDmJhR7NzpdiULarQxOJs4m4WcpTZru+LJtcXFIRsGvPiVpQpMG1120tHW3KeE1zlCWPU576QtKmuTQUEyRwgFCDCKCe7yhv5G7iXfqAL1XZvyidyKJIIg+de0JmyCRI47WihQsoZvFXqKxaaxax1Y9xJ9M019HkUxBTUUzLjk3AAAAAAAAAA//pwBEkLAAiB/xjQGwkq1ELiehNl4xyIjFdPR5hKsReKqij0lOYJSSQCSkppGjYHJDAAa4cJ1YssWGSZ8LCwBb6lTpnxv7dlew+yK/S0oK/Ol0ta4QGaYxL+ggsvNIYu81FW3rueE5bbRxiRVgXccDAUIZQlVMNZHvpWyqkaftu/ZTmYNE3IGHMsG9dQuE2WY1Vg5Q+mVgMiRLxI80LENUZ3tuRy7kuvqDUNSRuDBKZV81wpl2SzVk3WqvqHIbcGvfexR0eEIYgSyIuFGK3GWEji7RS0xgchbilFFTkYgre7a5YGHoeTLDhGwgMIBMW5bJBhZGkcDHDM1JK+nOpD3i8dQ9q3XUospZQdVUZZfln/fSMFASP1CkOD7lnw4BQuJwmUcfiou55BiwNezM06x65NSUxBTUUAAP/6cgQcKAAIgd8SUBsCM6RAo5qKPMU3iLxlQuwkZtEUjiqoJggmCUkkgyBRmxsqTkLvCwEVV/S1/ICs2ohhD9Jqw1itvYRcLSKsvhPSYE4oQWBjj2sLo3nPxrdeOo/TVuXUAAK4jsjcGFtyK1HN6RQgez7LB7PSzot0ZDRsb0KHo1KI/01cHMDA0zoMt1eituqw3Zz012M8qoXFBUJAmBhMuXGG5bbRcFivi8apSUq1BN4TwQiOpABtI8arCZmVofgkh9HUFT/+irFjtDy4ZKD1ucm3S1BGwqoRiqk3OHBJhZ25T2p+Nu0gOrLbJBEA+GKzml59pvh4iUml1x11QICI5wADGCo16SNfinu8BB+IhpZIaPWMUulYCWblUlE2wAABVjCpMqnFGsWTuH6ExBTUUzLjk3AAAAAA//pwBBKiAAiCFRlROeMTLEMDGnM8wjeITGU8dZKAERMMqR6wIAYAgLjiYGwRcw6htg7T/CqPC7EyeUynUDQiguWvvUKxtKo/2AhgSDoDCN7jKrarxMNhsTi6VnG+q1MqPag00cfMnbiBLtjcHT4O9U3XjYKfgYpNbH/A2va/qY4di+34d9Rs+LspKCgMg00Lg+bteaWd5ub8xjLGptoFDFAcaLiFp8mUAAocUCpJJBDoKYcBNtIhIVJInVYj1vabKUIiy+NTocmUSIHBNK8BY6qjrLpsBXZQJRU8KlrzG5zC8h21XOPNWza73MZsmQguNpMRN9hg8HxaeWw+zQZlWizY5QVIxLZzWjKKoxGOjbJR+0OGRjihSx5i61uoEFOY51VWux4p/tVFKGIcSCSRoPrZrTEFNRQAAP/6cgQmTwAAAiI+WVYZoAREx8wNwUQCiKjXf1hWkBEWGvA3CtICAAABDCAoFAoFAAA24qzAvkuiPbPyAR5eKH4VcE8AzP8c4Tgmjz/8K8ToKoB5iAf/hcAuhGHOMgk//83N1IJIf///eboAAAAACAnFAwHA4HAAA+/iBfkJ5kafjNibBBlvw1eAqBqiP//GfJIZgTmOj/8QAEBBvDNiwE3//lcvpGiRo3//9brTk+gAABCCDAcDgcAAABBSmHgZasNR4pc93DAFuFNanoOHITYpP0OCOBOwbAHqE+/8CggZ41gchxkT/8jkkkzUv/8+tcopDcdAAAABAYEAwHA4HAAAAtfcGWsajyXPeBCAjwINan3ABYPM8F+/wdAXMJACshdv/BsgpY1g/DvIn/5aTkmal//umtZRSIR0//pwBAUsAAACER1Yvz3gBEPHexfntACH7QllQz1G0Qkg7JymHkJAQDCjuGMybcoT1xKRQ1kO41ZdZn/ydkLfeKqvYwdcaz5NxcfO/8uW3sBqm9khP8+b9slTVW2e5Kv9lkv56S/eVZRAABBSUDE0jcy5vsv9LQlUQ17XMZs0VSA+IZECOk6ykkzGfOH6JifooljqRMfbp9jbodLoJdu3trR6P9ZzpmzrfycR0kBkACkZNx6OGoH4ofUaPhuPrHlUHEW1iN6AyGS1Fnml8qZxX79CHj18Y9X6/1O6/5bnP3/u+hNx7/l937FBmkFACTl/FyiCfGcFhXNA2EOVLcb8FoiUywu0BDm9HypN8nzC2hXkH0M0N5vt2/oWyolc9+3IthA9hOLf7EpF33JDUVTEFNRTMuOTcAAAAP/6cgSfHwAAAgI9WmhtOexD52sHLadciOEJWGw9SVESHaxoxp0iCAEKABJKagGriKDMWKrGqaYkFzg288+P5/MQ1C10FCYoL6keIOVfQSOI38Qijjz8vyP/0fqX7/zS+UI8bv1nZuFAJDlvHChqSlDGQNu1QLJB1lmobNYKMbVKMEtQXN6RU+lzQec18o3JfxHDtC/P6t/9y3Evs+nQSeSOk/z30wE9TOL1gAGOgfIrvJFLIvAQUNHbVXZEwswkf/zS18g7yH3qNAOFKgqJaC58qN+FC+Y+LhN49bPbUa8zo+nr/Qh5A/9+j5QmpLP/JMnoEQAJJ13GndlKBnPgxrJ4QJDQTl3jfpiAk1UDCeoCMsfx9soDno+CjatyjZVv/mm9e3E5PQp3fvoNuNMl1uWf+qKb+LJiCmoo//pwBN+oAAAB8zvZ0ExQVD/nqxoxp2aJBQdU7Eit0RGZKs2JlbIQNgAApb+I0O4hZEL3q+lQKxmBFyHiSFtZEGz4iX06vyL+Mgp+74vbn8V+j6L/+Td30f+R8oNcP8UlH2dErXAGIAADt3GtbRuFh4BT0XjyN66HHTSJAjrRH4JfQAXfypbQZ5RsTf68dbUvzvfv/UvoO8z+pPiM2pP9lPyrr+TRAAAG4B79zcr+GoYa2YFtBwzmXytSiLBZIpH2ksFXI+WRrI4WNMxpaofI4NhlWxfp6Q7uWGzFuTt/Qf/9V4QfR+3EehuMf+LgAJuAfZktloUy8VscbLLOTIECdR0C1dxDmWgITFaw5YVkW2SDGT0DPi7HjWnxy2wk/BOR8K6k5G0f/83FJbct0xFup60xBTUUzLjk3P/6cARkCQAAAgs9WNBPUNQ/J1saPaVmiE0HX0e1TlEPIOsc+BW6EBJABKWfCEvqWr4QSPP6Rf1YSV8g4YAmuL+C7v7B9MOEWXnhNFPJeJbdtBV0O4v9P/qQc7o39G0Jax465YfmEf34AkAAC7NxjcstIEKgsl5+bC1WVE3kHnXxDg/LUF7cLDNQbir4mP0X+MfUvPxj//MO1X/8nGOn+H4l/qPebaHCAxAAAUuw8uI0Vty4gw4UWRIOHxIefL/CIDZXGYdfCc+jYuHvCgbQvxv09uX6der//m8gbQ3n8ZlunHzfqPJZy3/VCAABSUDEzXHcW7qkAHt1GoPq0a5C8W7QEJD64RxykXUTnLHI0q7B9GyLPj+W+lznGPydurdv6hmrd2/QL5x3FunGtKJiCmopmXHJuAAAAAD/+nIEY0sAAAILQ9nRajvkQ0h7KjFHZojE41bsPUlRCaHsKPUJ6mEmRADv340lHP3SMDrIEYaNiY/ca8Kx04XiI5X20L8Z++I4Fz6NkZbtwN5jZUJP98oNOSLa+nIFtA7li39uVd/AFQSAAFW3gZdlWrb3ggZc4hGlak3GuYFYthiFhsb8/z8TAL7NgaH9+PtlyTygS6En1b/6m8v2bT34oLaG99hwtdB1K1ggIAMco+I0GMQmL8uAJHmZ6j2FBvUFKY+jS16FFEpagqUyg3LxLLYwEJwDC+VL5UTOf1bl+nt16/0H/ZtW/lHKw91+/oUhcFUCAXbgPuA5wm2XQmTNH79Zn7WyaCG2C7FBkSQ1LSoStjeUE/PC+fObCj9OXh+g/CdW/+QfTztp1AuONdB/5m0EJiCmooAAAP/6cASwmwAAAh9D1hl4UaRAqHr3PaVciFT1W0e0TZEWoiuomCjCAAUmAx+UJAlUaiQLnMNQUoR6nD3gZ5EWRxGFXQQCNGPEYX8RbZhfm8l9tSnM9tDP/qdzm1N69WygmZqtqiohyHxm9FUIAAXLwPtqvNmV+DNcbZK2QyzW2sD+nmJK6AwLauVC/jRXiPInHd+Fv/O2jf/R9Dcrd9RXhwdMMMryvKIj9Bto8AIBAEFJcOs5eIXusQFg9o9fis3Uf4sHrME84Qkqxh9SL5kHt1mZJm7RmLeTm6NqJ5/6L/9X5W1H6cI+gPhX89M+pcAAIAEOTYWiD0HXawQRuWmVlCVXQe7gDQjIzRme9AjfXi4O5CDZyj5Qt35nIU47zeUb//y3P/yTkxa5QlX9H0LXv+KMu6kxBTUUAAD/+nIEeB0AAAIHQlc56it2P0drOhlHLIkM7V9CvUFRJR7rXPaVsgEgEuTgdzh1V2fci2a+D3Inc1OF22IAUq6jbheP+MwVPYGzo+UFHN9tU4r0fm/+o7n6+nN0Am4MGoiS1Gt0b2PisEqBAc3/Hw4ZcIoA9CsSGShPybQMBcsTmaiJ06F9CP8DgZ05NsoW1Trzv/x8tqKGxr1bHB9jVDAIyKNNTPo3AAiGQE/bxBJaLNfcgsWFKVZq+68aPQMBUjcXifwVtrxeNOP/4jCL5G+rcTOd0fKEf6c+NTHjF+vN5pexwqynu/FFPjYy8XYm2AAAKs2HrDcXcjVHAlGFywfCxWshccugDeKCkh9B8bJFs3bKjbUJF9XCgXUX5eMHdenV9l/UaO1Dvf+i8PDsaCWLhqI2No41vj0xBP/6cAQB8wAAAgREV7kNKfRCJKsaPSpEiLEJYUecTbETIexo04myASQDLOBi404A/KBoTPMEGL2s25LcWQ2IOdMUsber6h3in9AbjP4UAPCmzPx//0FffR/4x+KUUSqbd0GUQYqCcXlCIggqWfjGknFeWxYm0YwwsLbRVbi1qF2OJMCFqE3K9C2VFTo2cbqPO/GQo5JL1v+cib89JFxKX1Rx5aqDlildiEgJEIACWpB4Hhq740b9n9yzdYwxx3iMHJV0oEzbF8qXeglcq/bh+CbQ3B9X5P/oK7dE6cOPwuwA3rcdIOMNtzEueaKV3tQgExCCUt/FZW5quwRU1KMScWuOhjiTngNbEASo5UXqtOgxsD1sLthx+A8nOXQT/v/8E+p+bp1PwzYUJ/Q1kFwP6Hv1X9SYgpqKAAD/+nIENIQACEIRO1W58FIUQ+d7GhnnNIgo91bnwOuRBJ6rXGec0gEAAKTAYXOH4iETezxZTkI6KcXCroMuE8IUQ0aGuoqti4vkIUnHvNL8W/6NoW6dX1f+vn87oW/kJfQghh04s/DSScAIgEIz78dZqQ99QHduNkz6+hbiVxM2ICbrEjv0GMTEOWfKl9H78qT0Jc3oZ/WkRi74a6Np1DuPlmWE14fkaNC6NNnpIQgpKBlPSNxf4WbCTRouQlj2YZJsqYtWJWVc4VUKkHXKXMBstPB2SwoGqFQ1zPbQX8zo+pD/6jb/N+s5NCPJ+UpgIBVuw+CB5zaLB90iXE5R+KLwG8SxhGCwFnoEfZtAzoJfO4VD9D+bzBn+nQt/8dL6H9eraDnIFsqSQpaGXqJ8xQmIKaimZccm4AAAAP/6cAQi1QAAAgg9VrmNOkRAqErnHac8yEkPYUWIt1ELHuxok4m+ASQS5cBJ6kThV/Qifh+DCL5jOnNZnxYk1NFTmVB02nEw34QNu+JxI5P+eJXL9fN/+pvHm1O/hct0uhELc48nE40BJAJu4FxASDkUZBWDFGJqKbVePbWKqacqIWoRd/Laj3bml9SXfo/f+pbr/V9B7szV48C/QSyFh0hPZ2uhHncyGAiKyALu/GeUWBiaksaceIo1x5b+aflIzlqSD6sAV/9BXD9X1E9unVeXr5P/wnVsGI/iRHbhMfZ/K9Rj37s5MznKUogkKSUo5B4HFCOrgHWm5FAl6eNeD0Ez1KHNg5ZFEL5VeBVsm2JkyoY404Z9Sc/tqn9G5evTzdhrRe1wOuEyMXRzbEJiCmopmXHJuAAAAAD/+nIEAQIAAAIUPdhRaTsEQecqtz2iWojhEVhnnU3ZG57r6GaU/gIDNKCe/AzLKCDlJRm3MQ62Y0F+SGJVRduG+ANoOaD5z450fKncl0bUv/Xjg1/+OCT0rNGvbPCYJaO+YOPKpvE56aBAAArLR8rpZPK1FkG6ywrALAetR/j23BQDcnMBzvkLhHwaaD8K+g3XvwZO3Lwh3cuqJww1ZP6NqGaBUp5I2tsj8sATNeB0fLoS3tAb/PtOEfvGeEug0LrE4J8Crb8rygK8RuzchyvJSZqCxxY4VnV0SjWPcMn689+2o9bzUIhrfRnW7mkjCTD4cAJBkAhqQeDxoQ7UEyPmrBoNrOlyviOJkpEsI0+NvCn1FeO4x9D8INjW1L/Tobv/R/fv15R3FKRz7QOkCPULxPuIVbzCBcXTAP/6cAS78AAIghND1jnnK2ZCB2rnPUVoiBz1XUeY7FkSnutc84n6BYAAreB1BKwksxaiengUE4QU0TkmY4HWgKGUEITaButuJ24Tc3jH0X+jaF7876evd+Tkf8gJztNM6y3jTuqiK8w7QIUSHb8PlfiQa2dh/wL4LKJ/oKnDINEcqTnK4WmeJNq/AzYx9Cc/8abl6PovL/n4z3/jG4dSWS8XayH2qIM6Lzfi4aEAO7gdjlajpx8ofSDCDuxfuqLA1bDIxiEhqU6cq2pb+Ufv/ltC/8q/f+VL9ec9UfKF2VSYwtw445nhCkg9cijQgE5th9pG7TJuguDLA0YTR/76iW2KgYkqHnGHhgt+pDjX3xAAa2QfRs0CnO6v3//NzcERHm2MMhoThG0vq33NvG27XzaYgpqKZlxybgD/+nIEpXUAAAIgQlYZ7RN2QWd66gWHBsh071zmHE25GZ8rXNMWhyAXrwMr0sIX98xycSz0E6NS2a87rDujUPotGRWaNnedboc7ycf1p9JsGN/B8QKTvkbEitfBenPxI1jhaOt8SYvSnuOoQAIhgl28CFDXsQURAGLR8LB2rcSS7Hg/GKDoSFihgO2/QY0EbmPlA309tBr05V+nTp1N5nR/5nkdEU/9yEIzPLUAoJJLcAk5qdkrd4f7OcuDCWReKtQIi8+oZMdBq1yJfFNomBzoIj4IfU3Btq2o/J/v16E1P3ZK7OGtFCLNrlH1d16/ZgdIAFuAXlpdTQMwC6WF5ahWdp7jibNSzJgeRizADgiIcKEeVsK8Dd+raCXE/fU/f34huZqDDcjrCnqcSWoaO11x+WFK7VEJiCmooP/6cAQ2TQAAAgE7VjnqE3ZBB6saJaU9iNTvXUaU+rkUnKxoBhw2RASC7eB1FaUtKYcRwuMXQ7yt3Q/hQ6BID89VKuyDrvErs/D/nc/xr/CdRH87fX4Ib+CbX1bEjeGr/hQNwIv/0/ALgoBKuQWwMBLU7WCSJnmKyMxql5XziNYzDDg0wuyUbQfxPp0K2HerYwM5P40QbVqoq2At/2/nfjpoiiwyiQ0VggECCQY4BKKjYT96QTI+6waSiy1Jcd2oQyqiKYJtNOZtUZ8mhOG2bC30P/Cupe/Gt1pJsZdH4+1pmwc+NiyrD5O1L/3Pz2QQTARBckgg+jl+AeuXzDF9YnuOco24JC8xyhFmOLdOIntw3zujXEAP+nHy3N1bo+iblU2a+pijhdQvLwIKofFxX5p9+pCYgpqKAAD/+nIEBToAAIIfOtfRLzmkQ6d7CgmHFYho5V1EvUa5AZ6rqDecwgAiEYKd3459Kx6+8gdKZKlz09+EPEotlQQIuOCWen1bQa92xC/O5TlPM5v//q+jdfbKI2Oua9rSE4QqP1jV3B4WRXrXABQLARjkFgXoNqA1SdC4ZSvn8QBhIhEQRRoeVZqtobwf/wPAOepvOLc7v1bRP/oT1Mf/6PqWuOwOgWciZq1E3LZtNJADEBAgxwDklHRJVqDAzR+iUH5r8KLi8tkAJK5hOyz+Ly2RC0+JL5XqT8/nvobzuQf0+O2+Q9F0Ff4z4Yn+EFaH3cpokCCpt8NwRGyighTsixt1LmcjwODkmi1sebX34kPpxOCDa9W0LNjX20Wys38bZ576s07WJy/ebG5o8a3B8kK7NSYgpqKZlxybgP/6cAQAvQAAAhc7VdHtK6REp8rqPOVbyKDtYUGw5rEDnqt09IkyACEgAJS4DCv28QmSjoNNih2Tha6xLjiCoqcYRsQgZW3fK+NDNRfiHFBRo0BOO6j9H5On/6J29/4k+E2kY2bbPsIsLyAtIQCZIB8SOGs1uELgP7RY0gzzinEYGJmrgtdPzaG4V1DvGdW0PzdG5k70Ss4nqKOlXVkPoOFmYIledB5/6AGmi7/psQIiCADJIMFVHeOHtbNCcq5It4qfEpsFDUeZynKtqNO/KNzf8v/fz6f9C2q0kXZ9pUbUUWEXgipzM+DSmmiLDQqpLEuxYAEEwgIBPbgf+a8uLCGPUozPSV4r+0iz2NhyuoJIegSh/xuL4JuC4Po2o/J0b6L6vRQDm9/4dsGK4AB1vDjHA12JiCmooAD/+nIENU4ACAHzPdhQbDmcP2fayj1CfMiE92FHpOixJhyrqDec+xAzACJLUgwqn12AVG7wWzChbinUD+IA2+dxcWeVJpKA55vEBbR+U6P35vX/9W/nf5H+OE9mTEd/bGzK3MJ9UAJAIAuXgekOLbNrBpMzLi82cTuWoz1BkTUURgvTKhQrr0XjntoH3To2pOH/3//Lyf6cK3acoEUG4HhfqBb39cAAFySDvsxZtbqRmZnBp1Y0P8e4Qi9Y8GnoW5XqX0F3P4qLaLx/kmspDu+p3/mq6acyi+gkOlK0ILTNVQGhND9PG2IWgE4DBTu/HQZDIGcT2aNuDNjL23+GD/rmv1Yh6WzZSVg16eAc34mFPM6tqT7f1b/8hzuideOFta8sbbLuLRDxdvU9oTzfee+tUYmIKaimZccm4P/6cASEmQAAAg09WFHnE7RDZ2sKDeU0iMTrX0Msp9kRHaxolZWmABtAgp7fjxc28mNEgzvLe0Viv4XHeEAyuX4b/31JyPoXj/1b+nZE6bz0H12m/oLbsjoAlFCJc0H1sHxu4VS9OxV0AaAMAz/8cB4sFIUGWzTfFztxfjXwsAg+jgxP0bh3p0H8/8aM/o+6nUpkbtGBN3U23zpU4q6FFI14aqcxqE/G45am6xAxDQSd344eSG1FgFuaeRm/W/v4CoHBo4RjCtUHFf75ScAmxo/Un8a3J/t/9H0Nyt03O9BjiZFwtc4bC/5usPlOeK1r985qGxiAakkFRTbRShwTkrY3m8T+dM2sVr0RgQq9FFfxh+JcY2FBBsCcQHagvf/b0s/RNN/ej4ozMUCvSfrNraiisU7tHQmIKaD/+nIE+p4ACQIROVXR6hPWREcbGgUnGYeE71RHtOzRDZ3saJOJvgAmAAAduA6xRznXdXMMlliYXEaJBke8ebCGDQgkQU+gv5nUvoKOQ9R/6co2hf5xv68H0a22lahth3Jt7Wgcyb/4EiCbGIkuOQXZ5PIillbQ+/M7jYgfPbKA9MPcCZLvoH8xty2hP+jan8avq/N1/5Pvrr7qPOsaEVixBlSUh8PmqNC+EAgpgDdR2eRFyxsAE85QaHxEvn+O/WKpBVMwp+JXHm1fQA/+VD+nH+ON3681t028UmaetK2qbypbNJWLXsqQvQKgCAVJIPijSNPcBbfYHj+5vK8fbQPdoo/zuJ31bUNf1biObq+ifeyWkrTdmKRnuzFaUS442x5QebXYy5hZIpuVJxyYgpqKZlxybgAAAAAAAP/6cARsTgAEAhA41tDPUaxAp7sKIYVQyMjrVueoS9EJHesow5XLACEAAERtjmM6hGyJDEr151ip3FTiBfEcNnqS8jbMbKiR15UbaG++Ub/7TqTdVRiVloeqV0foNJZw/26GJUp17ujpQDoIgOffjsY4fI7hOy6UZdvxDhYZZQlxP/bjP4WHOKcraCvJ78pLoRT3JdET5T/xnY3GMexu1vWzgL2ct+wfV+goBO3Ydr1CS6SiWBQQ59D4OLUk4g+QOsEY2PaSvmvlG4s9OENoL7PhhuP36O01foCZnfqV0t08G5Gxg4ww5awu0UXMdlooAGwBICt2E0QoggLkLxQdv1Cj33/jdsQvnD5khz35eE20bUvf+j8/93Z5L39v0V//xX3S2Daj2X2jM9/d7GVd5Vv+JiCmooAAAAD/+nAEefIADEHaHNiZJhpUQyMrGiTDPIjw6WBnpE8RCBKsDPSJYgApb+PJcjaXIxOCdezw1sQU+mEPEP+wlCcQusTvFco4Ge7+CGVlqJQ4U9RzVwx+IIEL7XULRS9h8Xz6YAAAACev4gujbRzEbZ1eymtiCn0xDxBe/ljpxCKwnfJy8DNbZtjpCQiCuh6wQsnFSjvOKDJdTlK3jAIKip9ByZsdTQAApdx5YBLHBcjLbkDo0oK/LJBPae6GV+HoL7HVRx22fhC82CBK71BOzdm//H7b/ro+f6I9Rbb61IuCPoDTbLeeWeS9KTAaGAAu3YY67cDybw+zjlyVz9RJegUBuenWOchepwVDA2BMRZA5kKHGqYXBnxIjb7aD6CI/C8jJWCJ6XG7YEU9YY/2WXpiCmopmXHJuAAAA//pyBBngAAiCD0lZOaIttEOHmwc9Z3SIuOtaZ6xNkQkcbFzziioBAAUu3F4c9NYNJLjmMhEsRT+VPk5UqdB0qzRsg4NqAaQbKKA877jdv6D4Uarfpobb+/mRPor1AiTM/+v/z2EwT90AAAXZsMXlVKgawZJD4jaf2aWrqcVdY8t0hrfzoBxsaNVsicpVp4Yer5Mjt/x7QjmepzNktW513//+U5F/uZLvLvU9AACktHmlE8SzEI6B7JCkAw50SY+swLPTnKjMgmGvLFuAneY3qj16JljtB1ecvgZq/9DZG2/Jo2v6vk0f82omGhXi3XV99RAApruPiVme5P0a+IA8dYv8JlIUmu/ngktomF74P9WxtdcwEvwGH1r08U1HwxG7GGwHn62XBP/wUq8p/r1iBhTt8imIKaigAAD/+nAEgGkACAISOtg56VLEQadbKg3nHIjQ82BonE+ZFByrzYYpUgDAAqXYePgoWSwshDjsXZJrqBP85Oe07nGm3S6dNUTMhPxZyBkKho0i0H+t/9tG1/P0b/330f9cqXkK+3YgvI+5u1ACFAQnd+O0Q02MArqwiX7pBr9DVnzRsiEeWlw0+GHlWwErvUJe2TTdOjc3ma/q2Z3/JZz7/u+JbCvYrql3Fgn6UIEzX4UFiFidhKGQ60Q2hh+J56Qt6uJ3oxVsqWNigF5iXySymFgulWxc9H9fF5xsUXoyZnzPy359P4nBphxu20fS/VUt4X4ABS3Af3TjxigJBNSxjrMs862iYK9eKj8qJjRfKkHAubfEFlHkAX3bMNy/26PKCbqa3a2f061ZKvm/ml1hpZyosgvRYgMJiCmg//pyBLzsAAwCGUfZGW8pXEOnqzo9oj6INO1eZ6yrGQWdbOizihIAJyRjmA1L8To1IjUicJiFrt6C1RSeg5eoZxd6JWt6mA35A2Y7/0Fsq1VX1YmpMn1GkZQJUxbroHqlE8Yzev+2fQfJQAQAAS9/xmbDAy4I1JUF9ZM03FJpuUc9oZrxw6vg7pqDajaPv/yadf0b6M1M4+w7u17WQ1hAk0ToaPthColbYA262DAAXb8PjirV1D7Cwcn4HKMKkWwFYeIWBHWzAym19AOahDXorM1gPp0ft/viHInz6js7bPL//i/H9sm2VtA1+O/tiugBAoAM2/HcG5DyLhhbgbtVj8xJG0imzIqsTofwtqP9OI053//PyZDe1sn6HuzN6fh3wKqeFRdvFzqHJFBQU9UwYTEFNRTMuOTcAAD/+nAEiGwADAICHFiZ6TukQIcbFzxHhojMeWRnnNERAh2tKDSIegCXduPXJNEpQPsxXq5IRpuUO/UakrDL+cyGs84Z0EXH3oRVhyghBdo2Ws2USF9SJL5gvRrYUlDH/va1qefazPvAkADLth9YbGKsYhsrabnzi30Uu+nG+hg7DUEcK1R8JZGmC/9v/5HKB2cn2bN1/qqlfpsw1iQ0QyDlcUFCKfv1MADm34+8GW/2FK5xmwfutqi3XBw72d00TTpKJieoPs18X6PcO14WhzofbEMPdWTi62D3KV1hFTiyIkS5zZoCmqjJlzWYq3VAIopAV2/G8AT2DZlJQCc6Wc4K/yJBtMJw0EAvh3q+H+42vPo3+ivBB8J+jZu9L5Pv/tg8MC6OgpNsrFjhNnYYWmIKaimZccm4AAAA//pyBPX3AAyCFxjZGec0NECkarNh6liIzM9eZ6TwURwdbFzDlXMAmbfj6yiFqGO018Qhf7a3lOkkfnZ3xomL5Ggv4S6PUpqtzAedmvqcUcWbvWeVlTCznlYpMD0rSUQysWYpqE9iStMgACpIB+FdQJg9YsHkE9GiK9qJ4v2LVqUTtT6XmVtna6xhayagBRIxRoTEJuSAqxe+VSjf8pFJruTLQsslt5JIetAAV2w95BynBBL4Hb0iZ0NIrubxgsq2Lg95wXSw16foCWUOxJuVeglfq3/76dvV2/9WoWdCqsXEDD4OF2FiCKL2aFi1S/pQYECbfidkdTLxTUxZEDUFOVIYZNiYJ75USuE/bCPKvcuvZv/ztQEys/drSGZG5dEq2jfI2KOohmPf791fxC9JVqrncHx3f6yYgpr/+nAEE7YABAINOtmYLyhkQccrNx0lKIhw62TlPEORCZ1tKLKOMiCpv+JZNJ7IKYlq0HjqWXfb1VYDGoPdi06gn03vU+vO3b+pszb+j9Fan0QjTdW9hao0VO0rm0WnSKhG1SwBQ6x3RAgEC7/ikPDFU2UoCc6F9Jr4SNhqWqCNgr/EJ1fJr3//QRxhc/0T9G5LjkVGstWkcYKvIZoLL1GAIqlRkymsuKhxMBBV2/FYEKXhCDPHzcWO2m7fbUZ6qVlxwc1H0b897w4Frxtf+h9T53+fQf1oif76arqPPPOkFB1d2aGtSlcwYYTHti8AAqABz/8bcfE/LjdwzuTS9VKM3OxpkvQV4Df7q+P/BHq//6Plf9JZXzYvbZD5Z5ykeZVMcewVm6UsUlIurbWirYQTEFNRTMuOTcAA//pyBPTeAAACDTrZuWkRxEKjGyck6YSIjJVeYLzhEROO68wWHDqARAc3/HGJ1JFIIFUwNbS2eAR/ONVMFfQfgb71NrxvxI8r/6dH0/bQ390S7llbtqIVQZo57JhYuL0qYwwoutnzCkwGBKm348qBMTbVMqghFMgz0ObZDNyoYytCfCH1/69wt/SCPDi/snFaHWLe2NiScTUkq4geLDhdikElmEUOK2KsJ3pACl2wqUOgpWEkYqUQ2h4mcV4gHbkxJ44+cH6iDMfDOOtMH9OPP369DcphvyE6km0VILIvaLNULxthJto21ziVqF1tdNABu7YVggAw7Lh00CMl+qLUIr7+MUDKRRnPhZ8vjSWXUNadB9ypZanWqd5DW+8BChAzKkTLiriIsSJjIsPaoq9SKxXZYPTEFNRQAAD/+nAEkGYAAIImOtk55xLmQOcrBy2iZIiAY2LniOsRGJ5sTLOJq4AEBXb8feDTVfFPcWdqU+CpuUF2YdSk6pc+gEdHyaUbN+Z/6dTfT+56NmdJZ2m6p+jaCnmbicveCe/k+GTj9dj+6r/rkAEADLth3BBjzwXi9gNWIYizTFJs3WVGOdp33bfF9sTp2f/99D6f22a/qr0ZGZ2dfKOdPTAq5+PZhIoTatIBFe1ACCCptuPqp6KqwyVtYyXq4MM8MPh1oPlqcvgDPKaojJqPyEOq9brkvhlQnrNRCKPbViKIGnMjQlGlCwIuaAh2BHRUWOAgzb8bJBKGWoeDwqPmRUV4nB5YoRdHWbUZL687DFI2T83/9u2tu1t3leSymdKvsz+jlhMFRxhFO2Nq5GrFH9xssV/ZcbupMQU0//pyBGMwAAyR/STYmYU65Dily0cFIgyJQONgZ5xNmRkdrNyTijIBKbbibwAVImkkMz7R8qNDeL4CGx6zVj8C5uVNorCDPbLZ3/VMpKZGupsBF9R9yxPENNbu3NS5BJCLMe5z0iRMAiQ9/+I6DJJ1Ofknzq2+ARecNwdKkH25MVa2P+FWjf99CZV6a78c2HgAeKRTUH1E3mOqv3zt7sXBCm34+OLk25LobZ+0Tvq71HwbiW1RnHasXxK00ICazUDOvB//kGwTa+81Abam7IUdprm9KiC1mJySzFVVvBH3Cea3SzXLWQ+kjxRBe9cCiXh7hXGudJssaA39TX2KutAy+Kmo2t5aYGfznp/1FmUZpVGyrvJJshjPVZh3d+ZIV2gbER4lVSB+4uE4u3QYSb6UxBTUUzLjk3AAAAD/+nAEqTQADIIMOVeZ4jwkP2WLSgElDIi45WBnsEcRFp8sjJOV0wAntsPnQtKCsFJCM6Mltti1+vvtaQ28GKw2QeoD+TRymPBP+V0v/T//ZtW/elTa71XjSKhsw9S0AsnGpffuZntpkAACAC7/+OLD+nGAOm7pm88x7IMdHEHxg18J6Nte1wN/FP/y6HyGbU67iKzTGGBUa1umLgouxnW2pSJM2nYj2AAzYce8ch7AXwWRNDWxErLiMwiqEGYNWQ9CHUWCAxdEo9U0eoFzvU9f+ZtH3tse9X0p9Gsd5r9A9Yb//tSDaxQsInOicSgAzb8ebwLKBQKCwjSTbVE5JlhjKH3cojq4eH3UIJRd18pf1X/6E5P+T1m81Op8jXTG3ptKNPWn2m336na5xxf7+2+sNUZTEFNRQAAA//pyBAp1AAzB+xzYGe8R9EFiOyMxDHSIpONeZ6xNER0Ma8z0vZoAF67D3P0UhGivoWYgui20bSTAeOsMPyi6+HqixJLF28jVFfj7M+J71VXn3FL9dVybB5cO7yKrfTT/rsvR131AAzb8TpyBwfQLmYwHBG58nw+9KXyzZfGBSJVnI+Dt5G5zx3EX1vU5yc9N58qpwx1d5xPWkqskE2xqrTwGdFhW6IQAJtgPkzyVoM4Rb0CDDanBuSSGEPtQ27Hw9vrDmVtO3chwh5CcdW0YA4PVv/ovqye8ppJ5jl7IBIoMq818UJixZvuxl9wIDt2Hye58l7MkQtKjjjXhNSqES2kX6FFbUTgR24mWpb4yXeZp/JGzfz+87Z0T/tU9q7LgRC+RVXuzq6dqi7BzLNimzJTSpKYgpqKAAAD/+nAE6lgACKH5Mlk55iokQQZa42FnaIiQ62LnpKmZDg5r3PMp0gFAEzbgfSRXUIqj1ySSqZpwYAYUfh5tiBkLYOiboo+b1ZHmArYi8aO3/yP6uvpXRjbfUc9/WTVQRahCTFka9IADtoGrLRYIdIgMm/MrFitJySTyQFafBAKonJIiLbe4bg7AKmcs9CKpLzwl6vUzf/QnolPp0WmrW1l5PTQd+77VGBBm/A+3pvppnQl2P6ceIlyUh8wnlgLv/SPUdkQrn+CW9m6jkeYH/Ud/8STSpntnporU9dTVR363RqDH2/U1PLb1r0LSLth7tphloPUDZQYUJybctT4Oe0qq/XnR0oO1rQBuDsfvbpLXkQtdXoS/sVftPdlxU8IRwsGwyoaDVJmmjl3VdTFe5KYgpqKZlxybgAAA//pyBPBFAAgCCTHXmekTtEMEutNhKmaItMVjR6BO2ROJbLTAmcoAGbYD4RAczGMlsS4GqqS8KVJrK1ZfYswpn32ZmdRHSwvgBW/bGsroFBdGhy//K+lW/ZfJf3dBMvULPvQlaH2dNIADttH3LCRTWxmKtqhaCPKB5RJOoBMVFvZ3El5UiXTFQZApAnCby3Q3lRZvUtR//quheNVJMKDljZ8ecZf6vb/9e7QEAAN/4H5mHitMCehB9PmFuTMCfdUAdRN942klnAdcFD9OfWjF9rP/8O+6q4ifUcyykVWa00NesoM0cz1/elX6312vduH1AAAQAAFTb8TdCJJaASThkNlqhvMDtpQScDuWcs4mSD8CV0XPcCSbggr171bUzt2Scl3qsOBF04lQSCpUqNzKXPEIrXxqYgpqKAD/+nAEJ44ADAIFHNm5gzs0PaObIz2FNojQc2BnpKtRGpjsnPSVEgCAk3/8TheVuF61QL2G6GlePMEAnBumVN84IzRBlGzle1jOrTS/yryj3qiNjASPGzdEkUKUKPHBgzu2WdPF6qNAAd2/Hw3KpUBCB3LQTLkNzIRq3HNm4xjLBzD8Mx2/az/iQHUOq6hjbmCsYNz4hP9fnSUgzuEbVS1GPcpyOsAGbbj9+Ohlb3tx0J00EkqGwv1hfrL6nWJn4eqRHuHjFGarK+okC9dR93LMU96NE2eJrhkwZO5+yY55e67rAanPWITxCEZqBoIV//HrImoIwhgWM/jxr2AsFgvMYS4UdRodDOMDKBm1bY79Qb/8mh48ldHfms9kchIwms8de9CSTlzba1LQjYhPe5rJssmIKaigAAAA//pwBKbgAADCHjlZOYcTZEEHeyM84l6IsGNe56EtUQgMas2HpTKAgRd/4E+Xy+jOdjCo6BiVYLnWSpdTGKjJ9J7RWWiORqaVLlcwE3zcoqrf5S6P7fdQaF+WgZ1q+6s+w2PeKqJXxZinqCKn/A++0tIi5PFhWVUD10AKqEzTl5QvUsLWLUDkKEaErntMDXtKI/+vvb6mrvKapaJ0cNZr+jnoJLapIXFnuyAwBABM22HqVK6am5ITiOwAuy/Ly6Lyhc+LVIpQZBlZIF1qRKutfzc5R0aaqHF+mschNpwXdqiaNVrVS9t/JnNaihndeZNbggFbQPvR9PBoAIyXqJiK6d42R6qHcDR0uQiLQVOyb7hc1psvI8c1PBI7CW6uhVQJ52x1Is4Sr9bvxEw5ud/1U9P60xBTUUAAAP/6cgT9VAAI0e842VAsKGQ/gtsnBeUKiQTHYOC8oVEQHaxMF5Q6AAIAAi/8C2SuphE5QFiGPRdgsR40qzE8jjQ2NQPiDRX/oyqBV00b/7Ns6f2XP1pSrEtfT2i43eZJX2Cymx0AAi7/wLyzxRPDjNVc5QpS+dajABxUfIw0emYXDImGXFs0dgJOpbKs7PkyksbPi6IPJvNqdQ5SrRclW28cKOMMAKv/wkiCxM8ymTRP5AnUxttQwsMwAeR1DYWoDhAdH/9KuHMiNEAWrN/M2MMj09avHm0Uy2YXQF7AhBxwhCSCilJFFNRL7P7AUrBlWJYLUO1KvNHgmMMbR5Gn4XqyOFoNuoOGSjo199HUwfUydAzb/QnH3Knk573MSquw/szO5lXRLjWXDAuRN2Mc+WTEFNRTMuOTcAAA//pwBPrjAAjCAjLZUAwoRENmOxM9JTyIrLlk55xNkRWXaw2GFWoAAkAUb/8BoLLSEQ0SxCCtKpAtI7Dzbq8acTQdEXo23qEABSayI3/2P9qtoW1HPkOVdVHMQU+LC1gy/ePWtu1qwC7/wPfKneCfhJXaywXD9TP9cX/RwrvTdC9RjwyMavXR1cU7NCQTo//L2Wlt6OkioNO7OMqIgGZEInNJscg6cfquZJkCEv/4+F1t1DIzpmbyxMXsrnkKhXyruVPKlS0wvctRkfqM/nPBI3XoPq8ZfLutiOoQ+1RgJWn7zr3MvsZFCjK63oWro5IAB22j6ldojLRFcVWlnG5tXTPCkdQyQRwyocr8M1UJrGhtp6GsjR0iY7GSI8oP04t/+/ya9rtL4+JJjqOLlun1CPZV6PLpiCmooP/6cgQEqwAAAhAyWJnrKuRAxNsnNWI+iHi5ZUYkp/EQGSyc8xVyCJn/A/fnhDSJGUerYIoE7FjHULfKL/MbbNomNQRPTW44cph8BPz//jH72+lsx1KxzSNMHsRICDLlHWLShgSzdSAVAEEZt+BXKy0JoQxTbQxHtRKOBjeUXJucrlWi05WRKw42HsTMN7ztQzfwXK6A0IP+YoUfJGQtOPFC49VrnSVrVlsAAVJBlkAtYhoao4YDdY4DpFWCfpCe9bzw+PlHVQPboEghAJtUkfrAv43/8T91vf1RL1YODiBU48w4VqPrqgwtf0ChGAgRd3/Hthmag2ieG21QkPYALTeUA/oND4p9Z4lX4rPau8brZX/UH1f/HdZn/99jfx4YeL6oRNsrQsc3hIujlB6LKGl0xBTUUzLjk3AA//pwBFJPAAiB8hxYOesTTEBkatNhB3SIpMdi56BPcSGY612EndsAIALjgH5mvmQ3gxEdB2KJtkqnNMJVmtwzUhGH3S8iOtg9T36E1TBuZ5K0WW5aF3gbvqswvehwYJqciIipO8EuW2j8aBxXVLrhQzfR2u1p14MjsBcrsGSgyuYaGUyTA2VeBxa7ZBSXcf/HxRkm/lW1kmJzTCxu5x1rftrvu/9RAUnZAPttjvUWIIlNNZkQt6Ye1qyKEnCWMpuYTxX6vkUibuAcAttY8GWrf4N8rq3113oUZqpcYO1obFINmGx9gXWLra+wgJM2wH36zjO2KREZH4luKoYRBU64P0LmbF119D454syLv4w7ufUbq3QJPfI//Vu8xb91TR+OKx5qFaNX9B+Tq38yq3NlzXnrJiCmooAAAP/6cgQLPAAMggsu2NHsKeRAhdsHPOVmiMSZZHTygBEaEaxqnnAGAACAkO/8D9sRzmrSVFovYD12Hi1PBi9zP5kcHbtWBVvtQtu4b/erenEOlf9lW2wk1IFCBE3abGi61NwKEyrSyMDAGALv/A+pE0+FsEyKR9ZLrTVMhVBPocjowdsaNHzd8necH98fs/Toy2H1/Q2s9hIoHEYFD1Q+SQLsqa8sje1r0Aqb/8fCQzheLeUjyRBTy4ddsgkFxciO4U07ELnarYtbhYAN8gO6ftme6KouBnxjiYAKmSy58ACwsylTkC7drkrXTobuuSsqAC5JAPe0eIGM/IZFyvOMkQ8X8c6qGFZQsBgtSqiKSVHq9S7b0DHZ6Pmmv2zi0NCJR9aSS2jHxYLLULjyubQAkWjDVT7o9MQU1FAA//pwBAkhAAACIVxingVABEFrjEPAqADIBAN/vAGAARIB73eEMAAACsVCgUCj9d/////////3RlU93mW/nkjR4YZVT//z0YjC7EOWPf///kY/Fhh4AmcPAbwKf/t//xDg3j4WAK46PBcLFAAFYqFAoFHzX/////////3MZ1PfM/55I0eMZVT//3RiMRYtlj3///1IyckMHgF5xIIsG/////FsRZMPAvy48KEnBAEqN9v7aIoGnCUPGFh9D1FRMazqHlmgsrAqGhRpV4+TOpOgMBGgEg9O7uma48l/DZnhNzSpGtmIyzBy9x7TrGoBhmWWXfRFGYrQocqWmDxNa0iEXEATCz49L1yqD5omCrwqpp2dMgsaASD07u6ZrjyX86Z4TdApatmXLMHLRcHH05catyYgpqKZlxybgP/6cgS7QwAAAgEc4GgjEixDYtrgPMJMCJE1aOQEVdEeFu4okJTWJIEcbabqaANmpFUPpJFlHbVW34NHmR5De7+1AZ4RB6GvywiBoShoKGluBUKnVhIcPLLARb+R8K/7SOiS62lXdq5Vwlx62EchpIo1Z0ccNeUnPlfZm0l4hMwSlSKq2OKyyrSv/yyf4luu/0rcp4dPagZOlXZE7Oh0FYlhorUBQVKnciGgFAJb/+jWDdowGKFDh8Bgi4tY08bXX+TAKj4vgKeeL/zQX8N8l3ImcEpubQTBGMfNWY0HYx09bnDqzfRB1mT9SRpt/hkUG3iVLrbLikHlBEpRXJKNA2jFjiO1ndRAfMr7O1sVr7BQs90/HLdVRr90mKdUjIzZeLQnSTeNlx6B7zesoqZhQqf2AcRSnqTEFNRQ//pwBJgiAAgCCBxZOSMSfEPDmzowwiaIELlk5KxE0RqObzQwidYBIBTjbSrJBBsD2g4DLSUWMnZaGLSaoh7BgIlWQqUvxM2kOL0aqOtDBaVDBjikjDjw6rY+554VJrEe+ote/e9/7AAgIBUu34lEMCrcviBB+IQ01jVdJmBdtAWzHdkr3AvSgAr0PW9TaBdlh06vpQwzyQ1iiiB6CYWraKPQYPDzsakwCaRVAKl22vMDjNEgZkYSeoiayyskHnrO/Ehd3QiMj8wq1M4o5Nt92N0/avZiQW3nBkWDzK98fcfICtSxZ70b1s7/h4glxJNpLb7axHYlZAK9irpczVkganMJiktX8v4oBWHXYEHjQAswiFnNUKESW8chpZQGAC2CJeLqE1TB7Ug9BWWFhj1iRLfWmIKaigAAAP/6cgQSlgAEAh0c3NChHDxB5KtaJCKFiC1vdOGEV/kbkuxMwYjqAEa8mXa2tGPjAuOatn3eMsB2JFTJIGor9ChosT3FWJXCRksOwXXKPtGpTPBUofPHjb3lV6DbCiiFx1ws8i0M1Z9hH1gBLSClbJGkuJiWRybD++p41VvvpPYBtS70HG2P0N65//I5M4wYek1faBrxViey96hdB21UOSayAqMWmzOqNsCV/tBSk2tum/uFHGygOF6XfJ5sVhxqKGVlju4WbGsgz/xDM/w4t3wwspdLjpkHPy/6ym0/Wz+8tn2vzLy3OjDo27nd5AObbatH4UFGq5ng6s9gnFpYxykHZOjxOvWmQoYq72Y+v6GnNHJUokKklNS5g9Y5Y5aiVaBVr0zp8glsLlxwxNbgrxrBnqetMQU1FAAA//pwBJLqAAACCRLXGeETlELEi2cYJVmIzL15Qwym8QUMrEzxiPoEm22uRHnSzvihUg8liyFr6lcofiPRNI1uMW6QgMaEouuJEZn4QfB14jfmHVVmwunedU/KoeqtIUPMXZoGIXxv9QPQXLpY+kRGMWb24zCEzIyI3zSWixnvWK+AJuac2v+mNInYxZYHARDY4ihK2rsHsEtpQ2lBxKAEFGhkszUiS2sez9gwt/qO/7a/Z1wQqhPJHaYpZ76K66I4RxhKMcXJoX9Xr/utVW0qM2mi12iA42LBGWJmHtS0KhIlUuTqpPvhsUSKRDYYNE/ngU9ttWtVE3QuWsvzAzBUSQyXXOSiTP4wOkVugtNgL2fD4OhUVCzjChzaBZ5Ml45VKq0DcVHvrvuWIHiuoigd+a6ExBTUUAAAAP/6cgRSsAAIAgMcWrjMESxDQ5taDCIviKFtXGY8RRELBaxo8KBCAGA5bJG9EtgUJixjEVct+vQIzr4VXr99hhS2JhVnesOhAUPnRt1rZvm0EQ8DCKYu5InyZvQIgRRcadx7GE2nPaBglBKdskdVgiBhpRIguzCwkjpPsFCmY689Ee4C30G+1ggBkzB1Aut1LR0Pw2bBqKm2zTCY0c6L1sGB9yBUmt2rSM9YEdttiVEDyszFZ3CUXb442KWzTeznrqwIbqqBATlb13QWZwAwgaor/4OrdVVDF8+y3fV//fb017+1rN/Vbevtr+3X/8G4AAQAp3ba5aC0XbZkJAVbq5OKILFSYKAG4KBxzyg1JVmo/6ycl8XirWPGJ4dQMFFMlnjTStxIANSrNjXVi2tDVcz9aYgpqKZlxybg//pwBOJpAAACGg5a0SMxzD3GO2oMI7eIuGNi5LxksRsMbBzEjGYAAagnLZJLcwxAqC44IoFZpRM1VOyJ4bRy3HgMSkqWBep1+oFp0xYcNrLhcieYJUm0PNmGClSc9Y1dnS+weAsW1K+GQBEZJVukkxDhitiYvkTJe5Gyw/z1BIRlxouX82kJvlmRAnNehkZyM8ecXC/Ij05ZeKoe6wkEEKvfffmxb+oI4KcjbNDiZC0QCdzWofUB8LFG65egUlL0FgxvpDVsvgSlMyn1GyxAAdjQwPsFmlgkRXOmH2OUcUhbaJktQ+5hN5m5jm+oA4DcjbSElAcYwwPpbZRY5i8oq08s4P6tt+TZakvKgGFTrhGsx2i0LhzXTQGFixWrnXC6hdB4Y00WcRY5ili0LgOlrGY93qTEFNRQAP/6cgS7XwAMAgMdVxnsEbREIxtaGGU9iKBzYGeMStEchuvctIyeBNttrXEVRsTiXYLSWrqYD6Gl8+yyBg9JJqZoSx7T/O/4UdAfKhs0WREpXjAqhd9r7GB6far5TUY7H1kdt+tLvQCAP6Ml0kjWEVbF0+t1mORWrkK0fIapQI+3Z7MExjZwt88PRnnCzwRMlAKeMmg+hVTkqYhdAoRmBWRXAaL3rYcN6qPW4F3batqfyssI/2gtcXVULMas5yF2yXq0M1bW9XTo6HGhcUQTGANZA0VDQxoYUGnxqmvTDYhE5srShAkWImCrjSSfTR/+sBABbjbWCcBKOEsqEt0eStleoP8bvBmTgjXknQR4XHNc103oW5yTxoogNmbh44oZLsLAMRIMvh08FhgLjo9R0RAe4+9zpb9aYgpo//pwBN/PAACCEFtZuYYRrEPie0okYjmIqW1zQwxHcPsOLFw0iJoC4KsjbmI1qOFaNSDjhnZe2HwaWq7v3CB2+qNq3Xhjox7UZ2r7WarV+lPtSfS//+j/1oevXTN7U+39K10/wouihesAAVg1LZJLaAueUofYysIFqXuuQEMz3I9nhiCEdjuGKOsUEZkSGEkqRr4ywGZ9xEZOKnqnLQJUsaq9d4xiEOjnFf38IqC1clrtba6VwxQrxLARzU5l7pXXhrhb+6c4sKY5jiFCf/ebZSX5DUVmY31tK1ykq+re/R6odMu+i/Y6JfPon5tX5Kf8I5QndttAYUNK4KG50mlrCGg2F9Ff2VHh+3qt02gY5LGiqUzaSmYJLlou5acJTqVXFqXNFEkEuQhreiSSsim/2JiCmopmXHJuAP/6cgR4bgAIoh5bVzmGEdRCYjsqMMM5iHxxZPSSgDEHhyxqmCAGAEC3bbYlsZCOnMhMGJFAo8C+M6DvTbAaB60DMDV29UdWQ55qVDPT/d9lt7crtrMTXtr27/oS3/rTZtf19Nq/b20/4JgABYBTkbc1EMX40MTNbGsuR2lE/ZWKhoZfxgbSb3vsZcZV9Yq4E0Oalm58/P3Kxlk3qCMwpSyz2oIMvsq3XZZ2p80slOWNtN0CmB9cld7qs9baVV0HCA9l3BBAzqvRH7k/GP+LHUuYguPERMNkXgkASoiAx4YOMjwqHg10MXbNVaze1P2eHgIjbbBMbR0WB0e3r3Q8inXt2FIrD5dAdg4UdUG9J5HUXiRTjNagK1iGAzpLRYZaL8qYiEXatAuclnvkz7WOnh7PJJiCmopmXHJu//pwBJ54AAACBBnYPjBgBEJrvAPAlACI7ZV0GDKAARmyrkMMIAAAAdNS2i22AAA9LJ8eDgPwgvH5yGRtuKscmMKY0BdDpktB1szyN/y5gjR0ilFMWUkHDwIEzbulum1bb1irz1h6kADUaDQZjMiIjndf////////9/LOZf8UZjndSq5f/kJiAoLnc5CkNIU//3IR5BdGcQ9E2d//+KMsDh8XU9lF6CwD157EhIMci58/p///p/9bf/2NWv96XZ1c5Fdv/znIRXIQWRVGqIf/Y8hDnOc4hcUkEUuYBf/+wmdB7PF9RfExcaIMKmFzDDGF7OBCinGuuo3H7MU1bp///p/9bf/21r/dSbVc5Ftp37zuiK6MZFU6k/+x5CBzqdwmIkCpdAH//sc5BynU+ovOLBhGEoeQzjpiCP/6cAQ3awAAAhgrXu8UYABECGu94ogACDRlZ6MErsD9DK40UZXi6rZ9/3/tiAAwTsqs5KwiQt0LkOG59LWa/dfY91bjrcucMy/5V/046t0/QVczLUuPEhFWGmHvyyuLHdmjmt7aWEc7kQCV3JttrYiBMzRoWUkWdaoh0Ml86WZWNbW2bVkopaEpQzt6K3koctSvkFen5u+bZDdSskz/9OKU9sJHdmjv3tlmJzuRAIAUQEv9MRDLgdZZVIyFc2XxjePZvzaAyQ1SrVKgcygoIjxEJbF+JTsO/w6RgrUDR4r6agqQ5IkVO/WDWr/PSp1IAAJMbltjjSUwvkC3Zhzb3BleuSnSkQ4JV+X5TdcwIwwGtLW2fEp2Hf4dIuDVQNHhD6agZIckhbvrI//PSp0smIKaimZccm4AAAD/+nIEAZ4ACIIdWtexKxFMP8OaozzCdojFbVpmHE9RCAysXMCV3gEC3/aY9YRYQCpJZYISnU1aWpKuo/no3M1j7YB00X/5T+nMv9X8tnM3Rbs71R9OctK0MZlyHKS6FZrN9+n//f9IdzdYJckkZ6rkp3BzH0YoWSaOA34TdgxNItXTJDVWWBunmJXjVruTd36Af8V9hhoYpqTeqA3b/bZa4hZ7tugjyXX9YLV1tiV4tj/peGg4jXAPwp4z3VSxzqxV3W4/Uxn97S1AHpnd//abu+nUY0lX73yJZr/R0TkVPfot6t23df2tu/eZd9v/hXKApI25CpIq/2xQqPdVbONzqISEXNicWNWvT6gbw93LBNZLAZnmlnmTym3K9gcSaAjTaHsr62UL32E7lCd4CudFEJTEFNRTMuOTcP/6cAS8fQAAgiFa2DkhE0RCYxqTYMV2CEFrcUKEW3EPCOvcZgg2ASDFdtaq5lB1w8JCqYWmn9AEsQdGgTnNpJ9bOFi9DV/+qvX9EzUZqnp83dVSyFqlFWrMTLzU1Rtbqzf2//679PwhqrWAv//5eTUVLcisOjpH8e151mSigmWtV6jav5tKqNSmXKbJAshHeO+oZbLsqJoceuzhHY3sMNo2cAbrl/lXL9y4ur6gQGuTk2tssx5bJHVtfqhaq3YaxRMMMjVSLeIfkv/ABnA6OPPewEqYaSAMjDSI8p5RpPV0wGnqcrzspn58hpbuP/ghAFuNNiFAyP5gJRYUnye+ca9B+khXT0sgsBsvvJd3XeQRiPOjFAEohxMqwiIBCu4Qb6UUJ6qQoJmkRqTKGNQL+WOI+hMQU1FAAAD/+nIENxsAAAIaW1rQoRc8QoMbWhgjdYh9a3GihF0xEIxrGPYUbgAA+LdtkdRjJRRwvB333oQe6HVnoqsijqu6KpewT/YZr+zjKRy4WRlVKBZ/yJ53KW+0fOzLPG9S9P///t1yTjHrGWJBAGtKW2SVrAmqi0EOk36/C1GaLTTJVMRWv8+miDcaoK3LArXCy7A4tC9as3behb4xDqbFd2ymXzxJFrcwsZNNi0q8AgAtxuTbWWJMs41mVOSKmoJWOU8m5RQwmGIoa89+4V24rV008DPnNAWKGih5Hmp50U8kHGf/z/y+Yt4dpqZohfEX/BgBRfqYSWGqnXGQ1JBZNR0O9dMMda2JuNYe7q1QHR9rWXjuR/Y1ou0VS+k6o/0TIuScEzNCFDoeVac0JSLNuSfUekS3rTEFNRQAAP/6cASWBAAAAfdbX2ihFtxCoxttDCN3iMFtWueET9EKDipM9hVKKJBUtku/+29yJUbSXnQ7U3ZDuvrES49J76lAXpk2bP115FVH/94f88jbnOU//SFMvKjl+a/++567mVgEfoRUAQAU1E3brJaKA5pYRKmiw6yE9pwCGdlVbpQiLYXzdlVl7QPOCEXFQ0MDBoBSAnWwMrWFEDXUKGL0UYo4j/S/boN+4AoRlttmUdDVN04VZ9nzg1oeeuM7nKFgIV7VEWbkE94vxgL50U7GOz16MVvlullld+l+vnonmutba3Sh3al9vbrfaqf/gnATkkiPVXhZGTO3xDSeviPLmn4fnyOM2jUeLDJ0Z2aXQ2fupwM3aKOMbcVbC6FvjKJZ1u+xqKs3nHC7JFCh7FO9n+xMQU1FMy45NwD/+nIEp0UAAKH+W15oQRdMP8Oaoz0jGoi1a1rmDK5RFy1sKJGIfkQAi7JLf/rsrn0u0QmtmWV90brcMAlq5p5L3P+wOrNp7JeBgCWiPN0+jDWs7Jq4bf+vLv/TNgY/G8rICYv/4ZgXJJJFyWEMgh6xCIgSEpOgAwMrsJqoqpOO5w8yaUE+Zw+cpwoqL+QtBKTijcYjRfhoa366OTob/rf6/RZ00RrwHBOS22JVJVWyZiABo+sV7wcVqOq5rirQTeWcj0ty3UgJ/NT/+v12ctqKlHp9Of7m3Wn9aL5F6N1ut5nez1X0oq7f8Td0ItG28DAMklBMChqHJyxIyEdQuPlDMY7bI9uyBW24Yn/c927uqzkR/SltDtX6q2rulG1yWWRGZb5qorJd75Z+ddddZ8v/BpiCmopmXHJuAP/6cAQ77wAIAhFa21BBFtw945sqJCUZiN1taUKEXTEeDGscwwjiABX8qbWySwV4MMZZmOoDdnuwhSpa7Fren3tFt+EaJUXKAAjuQJIBYKm1zPnCRGApsSeIIgf8ry//+eSH6BxIjX/BgCIkE7I24k4Ok8ZwgYGx2DHTbRGeRqpom9+4v+JA7ghXKMuXimvn+NKP97atdgkT4q5VyZw2pLmUtGm1o+8nynbZJUY7oIBIwlK6qLOgf1iKLepnG6Ptr2sL19RBUdutQ4gaUKAREEsmvGbBmud/p5PL3UwTayWZLLvOSvP8su+rCkz60gBBTttsE4ewLCBYgwowT4DxtMDAc07HajMkpHQ+v/iOAviMBDBjDCnqfHPMLFjxitdzVnUDHqTGOKFtDAzAa20pEgvMv6n9KYgpqKD/+nIEz3kACAH8ENpRIRIMQgtrvRQi24i0dWtDBEaxIo5rJPGI9gAXuTctkiq1n8aGKfrTRKiQSyR1fdbgxCnkpF8Ccr8adKrY4WMEGwsQIpKN7FkytzGNITrCXWsA0vOXKvYp3qAKSajql2+u1xyyI59DWmDUdnd6n1KTbV9LdBXLye//R9l8AYgoqKSdlcBG/kCIzSL8j+/8+FSZq4gyGtkbIn9eCqP9Sa2ORtIfkxGDpyZnDtFMT+RSzPITNkaUjJmsIUmKvMoEFYDEsoHcrjQO7OjWTyXHCHQX+LBUSQ2AXhsE54RrNfNPOKWAAAwv01KS4ozewnkRQRYHBwXQYFbamajjr0INacr2TXc37jFD+bJCeSEKhdn9CjCJ8Rk3obdcmfd1jJAqHAks8fLNJTjOXcitMQU1FP/6cAQsRAAAAfxa3+hhF0xDa2u9FCLXiEFtZUSMqXEdj2xokJXeTBVcm1//+220TsJtlBmR5fdcpEpKfBLZp0OpwrQ5FTnBv//8jfyZe2fs5+mb6ET+uRFfWXOWa1/6znnS9S/4MgBtqOyb7XXK48tUY+l0lkKttH3EVDHM5E69o47646FlrK5PIVUef6m5/7v87ka91EudGKVaFru2nWn/X6/2+DcV7tIAK9pOyNtp6JndAbEpu4ajz3eozHIr+1kN+1uQZ+Egmysi3WWqlRCom+/e7t3+l9rf5msmmnpSlu2n3U/27fr/8a4AIVBOyNuJqiQekgaZddR+x9sMhs062Zo04mfftOG/qiR511rIMcwmeLGW2PFHHzjYojFgg4VvHkk1BOjJFfGVKQt33qdyJpMQU1FAAAD/+nIE+i8AAAIkW1ZVPEAIQeMrKqSUAYiYX2NY8oARFC7utwRQAAAAPMt/22XbKnFRYkRRk1j6xNAng/dCzWZ0OhnqqhW3zoerqF7biv/v1197y6tZtfX6L9v/yaVaTvsm/6tS/0X+nTqEcAKKynEySrEgMnpolYKfYKPf/HKz9KuyyDLkPb2qCEXuB9YWXV6w8HwwoEAIEWu5TEAnz8/1BhPWc2VHG6tQY7PyYAAAsn0jtmo1FAbCJcFP1YuyIgzO0fbfx896sIsSeiastwXOLG4ssXZKFFDJJYBY+Lm0Pc4O+cJoY4cdJK9UrHp//0/3gAAABiAWUD0eDsdDAcAiiw5Q4mVjfanL/uz+1P+jao1f9+81P/D6RN91p/877HV+6e3/yK5zmB6lD////+PQhAObIyMLpiCmgP/6cAQ3ogAAAgxd3xYEoARCwctdxIwACNENd7wxABEWFKwrljAGwM8888mOGu//////T///Oqo5v53nxiEIW39vzzKPOKsjC+v9XbPchLi8RExJSi4N/9iXFCVehGxooIjR4o7GYijgAAAFJJbB5BWAgAAACYEZEZKjlUrEm+1/l1TVLoQVY0wrLjBYRqQjh0NndfvoQ34rAvWz6yQ9yKn6LfXWMvilUiU7SSCm5LLI2klFM6lNCLCNWfPodasoUSVXMrVZ5EZOpbGV1Z02oZ/+yGNTsKff//7/1KYz/69BRV2oFRpVudoka3C0jRkcREgACaTkaABUMQ0IOhQJAgtzYiuXsvD+AiM9xBw28pAyP/WY7fi7MdX8/bil6qtCtBp8RfwVDv/iIGvUJQmd/Erv/dWCqgaTEED/+nIE+dwAAIIUGN7oYR0cRAtqg2GCNgita19GDEmxDBHs6JGI9iAUlLbL/a25DL5HIrzM2UzVW2cS1Igi0qViHbooCnZ2FZUA87hqWJZ6w7dDobdLD/5Z7GaywlI09QF4doth1T4lSWCm///uIpF/VFrivwsJS4DrYRnadAUdz7SDMzoMjIcJm2u5fcp/0TT67J/32//96+3v9PVFPdqe97fb/f/dUb/9Qh+igAAGinHGkpJyG1y8UEc10dwyDZH+gkpSLQleXfZ2dLFQBsqZ06WTz/19yluVHaxZWKtCb9Hk7mPJ6aX/vun/o36fa1P/BFeXLbI3bQdQxGQeQIaS/qEnGVqwxN0FlwV6X7u+z7yfOh1cSUBCEVWWvF7swIdMePCEDjgocSQNoYNEd7TqV776f9SYgpqKAP/6cAR3jQAIgg9bXehBFtxEIgqTPYM3CGltbaMIrjEKLWuc8Ip+JIBMcs2/1uyuKeoK1u5g/q9rHS8iIzO7sZy2lAtOh2RfrDGNnkdmv/80X2VzP+5bOfw/6TebaJBlCWdnMR/8M3bWFNbJKmS7jSEaUQVAEkBQPWh9L40vLrRFl6GIeyEf0MAg8pxS0QmlsuA7GjZFb7rmpxZbM81XRes9b28x+v7Ri67EempNyNz2ySlyAGYwUgrp38GdYzIEGSWJmQ5wx3Xr+X9TO4xylot1fXnfsT6XTvoXrpndaTJ3Ra3p/9P9OXv/bpZmL8JjlhONpJyrA2kBy7ZKSJpak1Pq0af/G7Y8il0Z7zmsiWF6Z05/GZmdepdbuZDdfPq5cpU/R2l/2b5Pf6ezk//4ey5GWTEFNRQAAAD/+nIE1YwAAAILW1dRgRT0QwI6Yz8JCAiRbXOihFmxHQyuNICN3gAAKDlttsZJgxUuJi8Hho4kpPH/0rMzd3/11Kj5NEBj6J+VNsG1tH3idESv//yykupO/35ZltVvuy/f6ff92/8G4b222zoDIX/L5xUZWS/DgPxFWUPiWH2piYJ+jlseeu9WkrUDpo6aK675EVeUPyj+kn0Ij/Ummr/1+aj1/rb8a8WVTcAgSW5Hb9rbrD3ONUhWLyWBuz3V1TZhqYm/OZ8xe5inebAV5LOOUGcud9H/+XIiXrLvyDLmA13J975XkRz9tWIPe/PDVkAIpxt3ba22FE8ahPCosJdTI/iVn4AIk8y3U6dDCGWKLcrqKpYY0LEZogLvS1ybXyMVYeSUTvIsFhdYDOnyK1jFjrsVFfZ1piCmgP/6cASC4wAIAgcY2FEhE8xBa1vdFCK/iHxJRsykzFEelS50IojeAAJYKWNtFIqKjMkwfClzHs9Ecm2OLnAXRFm/W8Q2MDp0AlhcwtLwuaHtus7UlCSVXj1ijlmHLIFy4UphLrV+/9RADJlsc3/23RSLRXZ3pyimEBZ3f6E0y6/fLMf/gjZnimbMjJReedRHm/KqAf4O5ge4vQPgh7ynad7g9pbYYZRtSlANVUo8n2TmszZI8BIuW4Hh1RhxEBROUoeEZASzxYKzR7wuB8dRwMsVNlU0Nx5L3kmaEU71PoUzQ/zP6PtRZb/12Bz0AAIpuyW/e264k12FXo5dgV5mc4700GJD4ojI9uja+PbZiyXumVKaq4Q7Ai0ACciJChBUnH2EyzUs+VoSxF/S/AVbK2B7wK+tMQU1FAD/+nIE8+oAAAIGGNPp5hMAQkILWiRiNYiFb3GhhLfxDghsaPGI9gAABAAFdttnBBjBCyCKVTBskuGeuBXz9e1KkwJlpAlTINu17Op7UtzAlk4cpJUKLjtIv4sT9vV9b9vRdSneX/+sASa2prZJLaNnudGCZotUY5uV+rroIWHV06j/WNNLFQkeAwjRJtFqypDYRItW4o4UrY0n0OVirlIcVLQLrCjrn9vqAYBOrjt21s1MdxXTbaTgoM3QwPshfy8BcVSPzH3gH/2KfKREyWtpC3DKZsjfJF9/c7mU58iZb//+Xlf022DobEsK7hGAAh5Usjbkr9RI+7wgTYaCwzIWz/Pd67FIEkrd6Dj5o/YyLHhzHWnm03pKtdc+SXhbheLYlGppisubKK7Fp3zhR4u8emIKaimZccm4AP/6cAReHgAAAhBbWNEhKsxAojudDCY5iM1tbaKEW3EWAyxoZKwGAAOdKWRttOIYWheNm8MpwBkHJuirI7IF/p3nABqvx5rtRdF/6rXfLb2rrSim0XIpqJf7dbr59H1+y9/6s7tS//EHIAJRd8m+1tlIYRwEIojP3m8zlBR+dICVB1W94mNC7zcKCgntAiDz3BFj6ylnFm3XMVUo06XJWsn2rSc7awFa/1AEoKpxya6WRRQVKU6HO7osSOcN7pHZtjVFHkfSe9Bv7BBiKpaI+oswGCEYLlbv665G6OcSZ2GOFBTM52xQyHl1OX/kf/gmAAl8tyxtyQhCI3puCD9ZAIwlMAMRBAVjHqQtxEuy3YDTKAQFQ8DoWDATeQpJNQ1Ts6GJ/M7lo636lUVoS+gZU0+YhyhBZMQU1FD/+nAEXZIACIILGVQZ4jNQQOMbCjxiM4jVa1tGGKTxHIfpDY0YmA5///mAmgJcWJLOKmNNDo5cE+wPWVg/jlRWZeRQb6I1jmtlitbhQhL2KQqe/NCzP0uUyyyVVY9bT66Bm+PeZb/DwACNBOyNN0dIM/u2lGgFMevEbbu7QdqstV3bfvieTJETUYBgTAb1ISX4WXj0MiiksQkfsWj2XLODLzlrULLOR4DDEJxtJIpFUchbhcYL2BKFo7ijmOj3snRFXbra0OgCtHaciEWRubJ3l7d2ddOt9bZXRKJptvnqi9E2957s/VKe6eu5tf10GgO222p+CgRQxsS6SlA7Ha43CRteeiW6a/sBPs1Fe2im/kYYCFtyx4jbidQYAr03IXld/Sa0Ndo6W5XO/2UJFbYo3xUAv+KoTEFN//pyBME9AACB7ltc6KEVfEFjGjNlImYI7WtjRARbcR6trCjBlP4gpE2zSzbWzI452dlLM68IAK8tNFNdhZqeZF8v4cvPeqF+ZnnSu5YZlwtEGnyF6rneU0n//0////qIH5WgWAckkkTuXsUmgbpnKdQI/DsmkwwvWAaCdXJ1CZLCN8E+W2qgJATBjNrn1oguo6zrY0ZZj/X/usU/7f//q5DqVzrEgAdepbI23h8HYeOJRkpI6hVE2O/pbh+Zq7Hd1K3NR7ulBarrHpc2M5HbA3y+DzQffPs+dl0XnJpnjtcyI5ryOfc658kf+ngiqTdkbck+MxhZDPQiSP3CxhrF1+fxAwiqRHE5P3T8jyt00+Y6NZKejMlem3L1p+X/dfTvtyr1s/fTSWvZrEUuisyhTJl0qoSmIKaigAD/+nAEMZkACAIKW1GTCTrEQ4IbXRgjdYixbUz1gQABFgkp2rAgAAaqpmI0sfsZ05skRWi1eHnXiwXxmDwn/37dnYQt04cyqidPmi1/2np+lK0b7W32////29P7en//3/0+vr/x4g1aAAEi425bbJK6xwW7AQ5PV2/SvUwCk63LRjbhtK4RstXbjGKU0ZSH3DCIfTilUVWUiCv6kkErWnU7s6VPoQAwi4NuNGiAPbbasbfFCxBAwwgE0R/IzeuUr4162/lh3Qu68rEZiPRv2oBUXkFrp27PvZf8/o//T3v9/r+/r6PNT/t/83/r8OViLWJAEX9Vt3bKgSAgFyL4KtmTQ3NznZmneW7cywbjpWi6srjFhViUr7UF4j4f1QycYsLl/y59ZTU6Q8/5RmTxP84RCJ8pav5xMQU0//pyBMOEAAACKCVa1hhABEJhK93CjACItA1vvDEAAOcMqWuwYAAABHr/uW7XabTY2M1RdiAtX7Bv3z6yHadalqp0Z37ZDdujNiMWdU0MqO24uGzu5EycIJkvPVXOMEKyl9+4/3ep36PPjOkAABuWO6uDf7ejXDUbZWVUO81okxYTKEjjkB8NvFZTrDTQ0F0GNisspCr/zuuZiIUAyfwyC9B9Lm/12CQKvHUaKuLgAmSX7aRoknIW0DiA+CIsFEj3rDcIF1Cpo5nVtQfS5EUble8XcXSAytnnzSqnPOSiKZlZtzyCKrXCrnnR4tF3GrDjWO4bAAAYTaQACIKwAJ4VAyiLIbEVYbl0W3HLcMTUdiUSlsmWXsa+6qhP76k1ZpXfTKKE0vgyBGu/bI1srTEFNRTMuOTcAAAAAAD/+nAE4yQAAAIcQ1tooRXUQMMrTRhGY4ikZWujCMexBobvNDEZzgACU65I2iSShUFXY9ytWQzh8HQaazbMrTKFDpqJ6+cnJ8TKFfzkITnyF//oXmvyES//1Eu4iJZHBWpbqJJ5JNaj2CqQCEmXK7tbGAxpxFjD1E8aBTH3GwpcZYGPvJeXLL3kUtkbma2QfYWegUpZB1Y0WUTSkGEQqBTo+mULi6nqPB765gAAouRy2xlFtoxLgRbmvu8oAkE8jqz92LFBjkpySmfvkI6hjXoPN5kiVOjjzw7t/hL2fiIkeDuPJf87lnw7NMcdWGiJUYggpJLrd/9tpSNTvNnnKGXTuDDxIMOPZuiRuuM8VipUBre9goUEaHqU4MoOPHEWrHMOR70NW1IurSFePKMoR5d1qUxBTUUzLjk3//pyBDlhAAACEVrdaEMXnEOBuvowYjWIcC1bRhhG8RCH7GiRlGYAlxTSXX7623shynO7drBzT97JyMUEynfVG6mb8Vr3p/S+isVWax2eUjmXmpEb2musMoFdt/trR/0+/o9v33yJ/8GAChxTjjaeFpDFi5kgox4MgJSo+vcKPKBgCXxzo/mXtIJRGqtOrCcQvYDgCG0mIeHExcC2nmJFgiRDcpIBIb2ebv78qACVRSjaSUJuEQVnIW1LcedbU6TFLCy7ysc4PCr6ajusVW98YCEWoGwiNLPpDeuPjGMeOXSZK3PUsqCSTUqhTdrv7Q2AAn6csjblnw6TqhDrvXX3xQz4QHqrCgcKmmxbAF16Sa5iKvQoX3uil8LoWlLEZaSVDjhKswpSCO/Yh7ECNbtw6JH9uhMQU1FAAAD/+nAE1h8AAAIVW1bRARX8Q+Macz0iNghha1VHjEfhBIDudBEMBgAS5KcbSSEEkAgAAoPAVB+RR89pwNVHPvif+7SRmpPVcbb3Mygx+58+q5/Oyb7Bsryf3mV3i2Z/8nvPzwGi5/0/wjhX//9lSf5LA4EMSRAFcIGyJIZQbRdppitgrKzOyO7pz90QTL3HpudS1gOoU6he99L6V32CouvFppHrvRICcq1kXIq+oAA6ip/9bW0yBikjVYpxGH4nESblQk2QsgZ3uy1+J1/f9fnRZ3TSz2W/51SyX9L3JSu3p/7fpv6r7V9W9v+zdPhnPqAKRblsu+1txoYTiYRoWHC7kFUh5YgNFHonFyzC8ikCjjQfj9yWNyoxbrHtels+z19LrZKjIlUJT2sQY0nXsYcVSmIKaimZccm4//pyBIZFAAgCEFrQEywSYEHCelNhIhoIjKdjpJRJsRcMbGiRlXYmoqaYEUHWOORD0AkeZEy1S/LrDiZqnXL4t/Mma6NL/7sKi9X/1bsRnR9N/fv36f0/3///////9///7v1///6BzOkQ7bXKGQwhb8ukCgVJy2UCZRcRom8FrGaWFetNAWYYwdPSHESr6Ci1qHF6mpMoADX3PfRLelmKMsZUuu2pK6koR6ynInbK20khRFXjJY8x3rTHrlMkIu5WtGLq2XkKLPlboZpqve331XnDgEwFWiIOA44Xe15RTiuKsqOva5+uePtZX3PX9YIJfqOyNuJEomZyCeywenctBPpOk5nOMUYoHGLi/wfnES7xBZhUTMWGkj0ESmf8iwe0SPM7WNc/Wsi8mbbpdWa42Nfxw1SYgpqKAAD/+nAEGIMACIIHEdCTOEDAQkMadzxlZQiwTVlHjQexBq1r6FCLbhv/8qhLHK7DNOT3BwAFYX4x17UxInGJdQoEoBkyCkYdNJJqlnFbxLPKkp3d777f0o27OxVPSmz/oVYzvFG8ob/SAEG62SQexugtCvbdummGyjEmYHy3hD5C6VftaOZ1/4TCRt2AkuLbQd1OuezTzJBDRiVKaRpeZ03ZvXr11beUXdT2A+m42khIcSnLCpB7LtAAODSc8bzpqVGWdCd4N4NGCV6FMrQeONFxMOuFmIWQPmWzahqbUMtmcVQeikGEP+wDK1UqDLlfYM5jkjTYwDAUEFoQH0DtJN222UNpFeeh0qpHtUV/6rh0o4TsXg4KERTntFpHDCA6fvl///nCR7YUVzhIHWBs/0gq0xBTUUzLjk3A//pyBJfYAAgB+BBZ6MJBzELBuuotAxuIXWtlpARX8Q0tbGgwizYAAgpWuXWtuOscOPTEoTj16Lqlwc6i3wGZ9uUWWflgKIUPNWHBYcdCoqPGkHCYZZu0Xbu/X6f+rSXvw412xBUAA6inW0k8ahmSxzAUlODPOXakHU+EDHiEgWKnbbiXNGgdBN4DFEahEnsUajHLRa110pcdsSqxLkULUfJ/705d2jc4hSuWyRtjZFBEgJQLge5mrx9/320GHJoGR2bwTTcyjiNVvIdFNZEcOfdb5yNn+SLYQDPiG9/701ylIty9vPWUw//8OCA9yksjblFigxUMIVwDLBZ27FwgZ/My1zbKQ7CmP+D0AjfnMM949+d8TH/m5d3e3wX+8WW+7kc6Zcjqttl9AeLbHi6YgpqKZlxybgAAAAD/+nAEFFQAAAH8WtxoYRX8QkMaijzCUwilbWmhBFtxGK1uNHCLbgCAU7a7ftbbUJzQGZ953sQkXpessvt5NQ/n+ij06AAKfXGXj15dFu4bqFSikyI9k/zrGYAZarVz5wS2V/+FfqAAAsTa2SU9XAVBkqt6nyGMjUOl1WCc0OleoN1Khw7ed9ruewAwFVIdAIp1Vd+zQ5rUezZ3Vmr/vcTzsUremliH6KAAAEo2rdbJJECwAoYo7saZOJPBG20Shy7SGaZV1YF7SPeKhBRkbPsiWjMcHWYG6w8/nZRepgzKeuw3mfWek+Ra2Vf8I9IJKTlvl32ttmK9CrGIG3WdKkqGNZB9RRU4f2sr9ee5F/hmOhkBEMjqd5KZjz24ymXutciZ3RI7U9n5eYUv/5akRSAj2v+CTEFNRQAA//pyBFOHAAgSExjY0MEa3EQCOx0ZIyGH5W1towRX8QatbHRQizYAAf1ZZG2WIkgdI9Aet1c+h2eQTZk00YljGSg53gNuJUi45oCNKUWD8YxU/sbL3oJFVn4regUQ1Tn0P9ZpNTyRy71AEAEtRy2RtxlB1TYy0ShimJ6hFX506hwzIHMxVxBiheRHMPNOvaOYbGvqUhaofSXRtux16KE1XIt7HtXY1b2uVqdrw2nbJdtZJW6mcsq91/G53qPK7bX3+rZ80NyFv5hGe1yQpn+VGOMhOV/8sr/8/tdRcp8t55ZfNeV88sir0TnGspAAIAkhcsGCLC7jREGVhXvjg8WZzlvv58ZzfjPvHrfPPPhv+Y6qUiwdbl5J4gJBmrPIsueZ+cz8o/a1INei0Du29aYgpqKZlxybgAAAAAD/+nAEQ+AACIIdINjpIhm8QytqrSwivwikST5MsMpBFojnyYyMoAAACY2pdI21RpMhgoNLipoJ3uwtWmTZpm04YxsRZmvXUEfZ5mZE5lRBuHtK2uRqePSsnZvH108o+ueKhUzTjlMTRZ9YAAAFLmv2FuMCHAlJjiQIgOnngT+aJmoimeRT/jfz2ySy5apB7l1xkwagUjb7Kn1/Pf////8//l//n/f6f2qiwb57raqqmEj2QnEbpwsWI6hTgwD0ckgR5WSBN5YWlwmmvoybnNLz5FHEhqAEBA6GfKJXelpCpNisrbqqp2f09cvTo/K6FVxYNg1VXAnRpCXzX4VUCK4FDLjYOnM/UB1FD/dImLEjhwJKPRARSHwGByLZ90D2wgEX2rmLH3ii6O0hze9pVP+Sb+/3VZ7lHoTA//pyBOXVAAACIVtabQhADD8jGuqkiAGJCTd5uBKAERoSbHcScAIABEqVu3WyOLECZwgCMz9902Mrpf3q2zoeW5UBv+RnKlln7bK1Gz3rMqoqn2dqVRF/oa7qdapT3W9Wfeqnq/////+cI4AAXJuNpJLnyKW2B0H4z7uXtpmyrVu5Ho7bujHPx+tiUT55BJsyRjKTw1WhwmXSooKnSin+e0w6b3VxSX/q+oAAAI0OW7UagYDAYDAWZjjOL5RtmwIKPuiHEAAQ+d8EHhwU/TiYfFxT/+fnT//I+xG///PcgcFBc7///+c52U4okMf/8vB/KOAAAABAKLjrdttttlAEPQhMRNv0eg4zeZSMnNd4kGLmLoNN0LvYMFxEEhUvXwfg/JiRuvWuhN2ntqRDFvbT793PoeT////qTEH/+nAEyYoAAAIUM11uJOAARAVLncNIAIi0c3NchQAxDYyqz7KwAgAAABgIBQOBwOBwOBwMY2OSOspLh43u/J0NglKqa4ac3m2d6/5wFBWYt/993//qdY7//8geAAb/6T5dZo7/6lMPooAAAAEARESxPA/oAAAAxDX6QlEosVdb8lY5wnE6tGS6ZipltmxuiYv1bEGAnAYTFkJU1tdBWQ4hyaRxf/9SnWcMQWEm4AJiilJEk4qHObEgcscoQfQQ1i6cicY9G0b9DMoBa1SI0shGbiE6HHBIwVWHTANSxM8dlXYc89+VcJXVPo/lcSrO69bpJYIJcklxvYDERbDJE+LZBcF2s4ilZTU2b/fpMrkZzWPbWtMjztxa2alrZPeYj7BiFXlpar/1yHX9yPv/7lJr9NKOG76kxBTQ//pwBARcAAACGTDe0ScTfEFEawc84laI4Pd3VIKAMRgW7SqWUAOcLn3Fdttq+RPz9H/qQVq5LifnNjj5xbUtiZ9OgJ9LRV7/qNy0ZG7ekobh3n1pnVHmQzYScqZFE6UMjixmxVtiPKP1AIBDs2wki8jJDILWVQ6dx4AFc0i9RL4Y4Bw3D8F14N9Bp4FwvbYHVYz5OI7Cva0owWWeWNwpOZBaKcKzTBGLCjXRBsO2tvsJB0yhMyI3TBv5rxLjeLahMdlWgQB3pxoAcIsOIKjsQH/VVt9dyEoZ3iekAB688rHYxPtVcg1rBGg4bDIaj2f//UgAwJIe//BQ9EihA4wQhB1cEPPlnUOcN3F2xNsr6fxpND0iv/36P/Q90ow0pQU+j9XU/fbsumeZ0Y3eGxa5CN9z6sP4p/ypgP/6cgSzvgAAAhgQWr4xIARDR8t6xhQAiLV3lngDgBELrvJPAHACAACAIY222AoAgU8+dpFJJGolm7730lAwuyB6arzXtj2e7RxYEwyE4tURQOf2/hABRpNYxRw9HO51qUmw+IP/uDBN6QAAEAGQAAL9/wMALHtSjCYDUjP7Dyp/fz6nDoTiJiMo545qvmar1xVOP8UX///+kiq0Xb2Gu1NLWRNO7GfFxB/+cJvAAoFdotFo////////7o3/9EPPNMfZvz3n6nkio2caGL/2PdrE3nuaaTHSg0Gw+SIhT/+hhhjKfV+okioah4kER8cMHxUcABQKLRaLR////////3dv/6Ieeaz/+e8/U84qNnGhh//Y92ybz3NNLmlBoPD5IiFP/6MhjT6v1EkTDUNCQRKjiD4qOTEFNRQA//pwBLuzAAACIBlgbxUABERIa83ilAAIPNt3Q5RvcRCbbGj1CeBoEtNuSttIkqRkbQrmEg/uG+BKqyJrbq9m5UJNRkCJDPfweutlIhqyU6xk9t63FaX4iPM/ESOJa5FklqNdrUIIypHErGCA3G59dokSroMHLEUsq6gTQC8U5cxylYUFqDHSvGP7lRWo6F5q6tUtIzMZ/L//7/zGo//XQWQewVr8lqNdtCCMqnErEAqrKVjIAs6NoTyoBz4SPiT4G4U34U2MHfqEOTRLqTTlWlD9mPnVKNxr/GP9mW7fwwruxL/EXzx4qAq+wOpnWf8RABICCPbEjLShUKmkNSlb5suG8yMOrgefE/kXTiQmLuvQnLVgpWMblb/8uahvv6OVWN0cKd2Jf4i+ePFS1fh3kfv4iQRTEFNRQP/6cgRXfgAIAiJbWTlnE2REBbs6MOUriLR1XnTDgBEBDKyqmHACAKEbvtrB1wjHa7E3ogxWgD0urYQ8bt+UGMQEf0EXl7VbDNqOu3Tl5ikQ584klBh/bb7ddruyXsn29VZe313629vf/hmABAIASSSDYvDR/CsOQ9BZqCXBDjuObE6Q8OzN+FCvHzQ8+Rsb76JyXb341ixqjjQItAoPj4WAeQaQcpCCcBsWVcfnukEO3axjTQFCe95fYmg8Dje52D0SEBqfiB8an5TobiY99+S5Zj3Kv1mhR8XuneOlhyrYdsWgqUZkGAMmeQoIqbsqp2Eb/DwACBBG3baTXAcF6+rS2cYL9+hJnUB/y/G/Z8Vh2e22gUCc+LIB9xdyH910H7bS5PNnbO8ItH+16Ew9/n7FLa/2piCmooAA//pwBCIVAAACE13ingFABEOLvKPAKACIsK17vGGAAQ+VrzeMUAAACgUCgUCj///////8kcww8g//khOhg8b///KBcCwcPC3//+LBIeLBgiAp0YLgCh////iEBsFgkAbEOYLhYYYAsA0AAYDgdjgcf///////tsen/8kJ0MFRv//1jAGwQhwqFt39v/iwSEYsEAXgK0g2AFP///8KQCoIgWACYX5ALhYYYAsAgalpxzZ+2sgDp2Wv1r+ZM1nHdYFyq/Byvf8V3JqX1QA+zKyk3/5NrTnFDHGL0Ho0arCS8Sih79Bp7Yq+rhXu67RERXPYVREDHXrlpbCSG1+tqqbc0lrOH5UAVS2rHstfFq2e2pUfSyos35HtdMqPRsg+VbK/JdAoe/QaedcKvSpGDNczQ/pI57CqExBTQP/6cgQOMwAAAgApYGhnGvw946taCScICSjBZ0Qco1kkGC1okoomZLdadprsRINPo/2YuC3iziNzHx3s+hI3fXlActpLLsa6qX7Xh7M0AuHf4axL+34SAz8jKnf+ocHTvW4NR506JUCx/bOzkRj6uHdSpONKhjUSGsNuS5HhR89+2vVf5Glt1mDSj0O/w1iX7m/CQGNTwdlTvdVwEOPO63Hazp0qACA4Qm3/soaB4uowYmFBfoDvcAeLdB7QrhQp/8G78P9fbWq3TK8iL9JuMs6i7oU3a/XYRehLvtPSlINQW2FGEM71K8p3cv//8gAC8giWySXtFx65U/4kKanQpyDbswLzczZB+ra8aD98MK5erGVbF2qiZLMbMLZnKINs9qEGWuCwYHgWcsKiNVTjVzHzLNV/D3YmIKaA//pwBJkeAAAB9x3Z1SzgBD/DO2qklAGJJZV8eCOAARkyr4sCcAIAABwFdttdZcXmjIKB95el7yJVQfGNNfMdY82Ji3/lQ5chuLS8MPEsNRpRXUtwlcSe92/XRGx3jNvnXV9/5d0AI/hh2yVnWOXahuH/qx/fx7WIK6D3wFXZsKAAUIIttwmJefs57KKSAhRVJ0rFUUpWir1bfturaEhYk8DT4r9YAAoFAolAsZD0udm1mlX//+//////j5xh7//40Q+Op///IIQJjccGg+v//38iPER8bigHg4QGoBf////7ikH48BYSRkqSU0Xg4ADDDDDgnECIzu///r9/7e/p//2HzjD3//xoh8qn//8ghAbjccGh6///+RHiI+NxQDwcLDUA/////9xSD8eCYSS5UkpoyDhMQU1FAP/6cgTKGwAAAfkZ3u8IQAA+Yyvd4YgACNxlc+KJEoEhna28sI8YqCGjUrkkQABC1GrHoZeK5uuZ+jdtO+iDXcRKGyZ1SHBwCIW+3+pKaDQrjewPCqc27w01bWzqgkgJrEC69AXMEIBvT9eyskjLqPamVG+AK5LsFzPJK+ozMlyGajiTHvZWascWcccTrT/ZfMjG0N1Fxh99Jr1jYuKysVri3bmUEAFGREl27WIlB6kDNRKWHCqoZqC8KYK4gqTDddH9eUAeUHzolw61Sxh6HORwVGhIOxKREWaCn9gmflSXw7rOyoLfuPVHg0YAACZkdrvWwgKHdUyp+OIfBNqYX/f+baJ7LOBZHop1oXuhc+p4Zh8T5TRC5L8mLhCE1jH9WHG/zIMn+Jn5VHyOs7WEv56o8GkxBTUUAAAA//pwBONDAAACFB3YUeY6FD7jKtoZ6haIzIdrpJyr0RwM73QzHRYAGAgBLbZe7nMArYEsMZmLoFipPi7itsvwuMak//UVdeVw3OWVOWiGyaxc5C2PtPP6Mx4hah6er++MSo1S7EwqZQBQAADACkkkAky0RQHxKNAF7dysN3qX4w4q3YZqRMCGKLM+UbtlRYitEtUVU7Rd1oXFE7DP/5kX+ie//xfWKIHgEBKFEBb/f29wPIa5YGd2w1qHEsjwePrxP5f/zQc8e3EDNPkcEFhZxVzT9BwDljowVT9nJtJvMEw890QPemIR8xUQUi1nrcIEms6Zu220HoYb4/GBX4kVFBDxtQ8IXHFAiuPvlC2nQY91twsiy3447PIeBQ4IV0FVmiD0zo9JNZE8WGpE8nIjDAJ6keulMQU1FP/6cgTVUgAAAh0V28jBM5xCoyrDPwwwiI0NdOOET1ETlu2cgRXiAAAcDtmeMEJvZILu9iO93t4qbsm3ld3Af+EOXdZhEHz96M/iC6IBoOAg6f4IOEAIeIBoOLB+TKOILBwMeoEJfwff/1gAuSAZVSkFkGoZ5zkeRrgkD5+weKISCT6tkc/+Xa3nL4wKEZ2+NJvfGDTrv9OicIrJQuf1QQdV+lihR9leo4XHLfqguAXZJJZ3LzSYfGljXlS8zcm5eTUnkuphHIs//zUuQlQ6FdGaz1ZPT7vvlp7kVjeVfSrDAcVc0TGtzGjr5gr3MFe/FWwSBLv3+lodUFQ7AkoePSJEnY2g6U97SC1VQr0bAgPImJvV/7uLrGPib95FoPaMA6BUnJrol2lIu/x6HljaErF3WdVtXqTEFNRQ//pwBDamAAACIjrcOGkQtD9kq0clZUSIlKltRLDkkQudrajAlUrAYIVmt0UhB+gcJzVEffgxGTQVn9I8E+nTV3+b/9OGM8nTo//9WbUqgLoZWsYSCv6gaDXOuuyp2LCXaBQVh1QKiICnZEAIAD13F5wpiw0CoYOBr03hItwZqgzudqTw/ChfUQ/AcGo2n/8mg/y7ybiGJd5XnZNZ263sbPFAeWTDO1I1rE4AQqCHfv8MM4jF+FxyohH65I/gicoy1CeYnH5CPDXrlOLPw6xj/8Zz3191Jng0ubSOdcR5y9DhbPG6UR+4XNLulKrvh6UBBQApv+KopVq7oSIi2GfoLp4ReNEtk41xRxh+XR8QBKcv/83N/eNfHafIpSb95G0HXD6Cq1MLPeaIllXPcwHfeeQmIKaigAAAAP/6cgTr1gAAAiE621GCK7RD51uKGEVoiIzrcUScrPEXjm30YpUogAgEAu7f2BHLKPSsajHRZ3VpsC24C+bOfldYcr1T4wInpeuM/6kWw//t3yNqyHbf/1oo559pC5HMipOGFyRNowZ1j/XAAogJd227Kcpb6EIuCfY2YmEeDG/3FZ+XR8Jifz6N/10XJ+tU+/v16fnZY4z+LE0VoA5ComaQpXvSEQ4DiA+7fBYyEMgcbBElkF4/R1FhcwZWEPxSMoM4W0GuNCDlHGTxEo+gF14MP+BLnbb7FwgPqLfQyUH52TarVTTfod40sl9Q3///pKD9dAACgCAUtjbKvlh8ByA9gBuMxgXiTYr9Ixw7U/Jo+FG+M3eLXqoCIoCZQwfRQqXFT5QPFJTHeGGMzhqnLhGlMIzSFrNK+pMA//pwBHaJAAACFEDeaMUTTEEoa50cIp4ImHFm5hRKkRwMrNyXnKKFBCFJolXXWGpe0OXA+w/yoqLYDtQ2c1HOUKZEeg+mpXrz//yZnxv9ETL0REa5K+nAtTb6dVDi6j5QkEAotvr6h36qAAIlUBF9dYgzcvC7EoakLXGcR2lCiT8opvj6zPan/9y4pQhksXQv/v7/hQQs/1SWhgoCbBoVDq1MwFWtm5v53JQRACm22ZpJJImik+IyURKuF4fTBXgl0LSWAw76CNNVb4r9cn4Oh4hNrOgtL0Fzo9q4q4N0s+TbL397DC3RUZQsOWCuUbBAAiWSS30AppcjKjiQSwaewf4LzB6Xx/M85irKHvkNOFxyWV9aZWakB5YS+Skagrm7P0FluyIiWGvx7qyzyNZISjD1QdUDSYgpoP/6cgR9JQAAAh9bWzknE3RA51tqHMKgiLTtYmecS9ETjKxc8x1SggAVf/9bubPfDZqAZ882a2NtH/5KVP5ur0Dm7W1/8VkHz/ukEV1SSWdaq5NdSKUQlDrIyl7l9Sft1SV9LyNr9rW/8O0AADIBz7+lUiOyhUEyBQIZfoGcQPjmTKN0tR9iB33/CF+v//319N2RnVDvarv2/vR8HKmhE1LTTISYKIAWpiP/rADm2sxNzwNrC8o6QiH3QTY62BHKC/P81wKxEJvmZV5QR3r8oV/9T4J837pQ+6FR2KZXQI1a6vSgIdFmc26TcNeKblf/8nAAAFW7CC4c5UphSDOZbjOfmduIAf0eqg+0if7DSJg8vUtrwuLqF2+un1y0tcTqc/ph6LlS5oVCIwWLNUokwAKnCY+3c5MQU1FA//pwBNXXAAACHCbY0ScT1EIjKxI9pVQIqLd3phxK8Q6VsHQjFYaAAAAArrhRhDwmEcA5UdOg5z25rbGKHoGsc8QGBuFjC9T/w0Z3jf/ZQC5xdRNZEJLOMEzCybV1S8hZhpt29Sf//XEg8CpV7q6vNpsDYLW8EmlCiy5mKTR90XEi92A8KHvimr4U1JS31Xbyg3nH/E59Z9b5+4T6svOXfBCID8ocEdpNlpzxBAmkg2yS7rbEJBlhTJ0pdwW0UrozqMth+HJwMoi4h6ppqw1OL1/8LhR6E9VM8GSwcNVN0Oy0QoPISoa82PNFpfZf5el31RAJORop2MkoEJUjQV0Bxe4sAWGPj9fW2/XV9WpwHNUqUZWm0etnYWo2g9Q4jSqjJcsLup0qEyXZY9VZFn1d7nyqNmFUxBTUUP/6cgRCzgAAAh8cV9GJOpREJsuKDWUSiFTrgaGgrTD3HK3okR4aCAAAAF22yUdmW3k7vJgrsPOfwQQ3Fuozih+C0u0UjmgZz9TDfUbU+Wkb9qy5Rwo888TUl6xDFGzvFzqESiOpZC+n/D0ggfAm6WSAnoEti4snA0ybArqHHhYbkfq5t+O152pwp0Qu3lKpcXZ3age5n//lL0cRDp53Z+Coh5Xb0Vlnyyg72+s6JYG2k7I03vt7R+C6uNRV548LqcH98x5a/gUwvp76tQEf7af9T5n09/UfsZfurLqhfl1HMNav7CI8qBBYkHjeMFiI71oCFUCrf/xT+cjREEI2Y+V7IhX9IsgyZlrRwtX4/4gJ0fHGcUPX+b3/3V+9Hz+m5+it5WgwKADVut9FcZVzKYgpqKZlxybgAAAA//pwBGl7AAACGCVfaGEoXEOnezc8o46IjOtk5ZxPUQGOLaiXlISAoAqRJvb7ag2pyJVXcGYgE1E9POwlOV40mQdGAclOKXFm/daYmPuVAVsiTi54Qj0hKpix5jIrqjhWkANOwdtFeW/pAIADN9x58LznzvIl3h/jN6/C1rxZsE+sKjI19S/hZPrp/6fr9qWNVHLs1eq/VkO7ETm42jt8CZkyMFRIePC7w6FB70wGAKu20RWw2HMKDwjCqiqt42oWJNUzVa2g9lXeVbRqgHG1Zo3/8TRh8T+7I29luWvfZ7KzRcqj3sFPfUk453dN2WMT6sAALANrSKTao4P2VbKaJXG4iTD+i660aym1N2wkX4hT6wfOCAYNBx3BA5ydQIQQcXWfUCGJ3nIgUcE4Ibvn+XPpiCmooAAAAP/6cAS8awAIAgVa3FDhFyxCxWwdFCOLiG1rb0SMSJEcC6xM1QjogBSYFSWAVi0WF8wvJS6ajCKKXz9NZ8Spg89S3yoSE5ugl3Hi3+XJ7/Ry1NspM2eiIl42Zn////////+mnaGdv0OkItNtJWMkpWOUfGqKWoV7lwx5Hl5o8Tn6w/mp8lfpXudME8yXUdYAdTpsBnmh7K28Avc6LHn3YUeqYW0Q3MY/fjWgWAXv//Bnit9p4L2lk8EJwXJtkllgk5/2f5tf/531p72IbKy2uqNT3JqoTR3s3602VF7U+yH/abe+1HkfP4NpFFUqWAEtqcnllYioGkMwQIP6iQ8x6BKbFbPq7MJeKeonQaB11W/qhJ4caHasRSoakioNBWZBd34iArvldZ3ulQWJQpEow8CtR4ktMQU1FAD/+nIEuOIAAAIRJNrQxRGEQQVbaixnNIh4ZWdGJKcRBpLtaKAfQoQAGALv/9KtwhJgMWDWjLhD4fi8+kFNcnTTQb/l/98G8pmBKIlovagYJVJMNKiJREu0ZFLAuMFKHgas+56Xn4CR9cASshF7/8KFuYr2dEy72Bhs7EJaoX2+w9o9R7RsodX9W/52ov09JVoVMOyi9Qo8uqYc8asgWY2lR5s3felLXCKLIAosQZNvrj+Cbnsj2gplZVW+kLx121Jhw/A4qWNZsV0egH6km9XJzutZpSHzLgEhylKL9LGgySQsECS1t4pqoxZrfDMAIkAgb/+lD1F6KEwFQnyOytRcq+LD09ZWvj38qW/2icNVpjDobQFQGkJw8BiCH1A4gzumSzUsvFRYo7XTOx6znqTEFNRTMuOTcAAAAP/6cAS8owAAAh4x3NCpFQxCxNsDQAjQiKCTYGeY6ZERjG2owxTylCW5EuayuPgMyocMnMPTUWwk1Xzkq2FZCjIF77vhj/bV/6ej5vWjtaVyWSgcj2jgIkmWBUSq+i3oLKkRscSoELhQ16gANbqFH4uyQQCMNMWJjBInyVTrJOogBtmzV6j5Knx0TrNl7vkob/hTyTAyKVJRFfmaXoxZFeKQ48JFTYrW+1H9qdygCpJJK7yK6x0NhRDpykWTCyiEoXxKuwQq82DUztAe22vK5gORnU8yfMf+3QfeXicBwfE4eD8/W+XPz/+8uP/9RDV/S76kBAWBUv/42OV7uBclVEundRjPwZD9WXDRDq56mbXj7BZEVBwGwI362rzLyZ4ibWHw455BhLpeK8vLtTFlpc5lhJ6VoyqYgpr/+nIEkWIAAAIcIdzRIRIkQuO7qiSreYfUwWrlnEqRF5gtnPGdNsAAADN22+jnb2kkA5SNtNqg2idavkqEaxk9FH5KmEhsrYe4CrNFCS4lfQ8o5jZaaTERQRJt8yxyYUfBtUFZUmQHtR64gEKBUdtsuudZ1ZCLfbqVVrsR0CRHibR6OWrt9V+HjO4lRiz5Ue23v9C63w2VCoscVICgU3qc/RLx5I1ECFOo6EK9YEABNvtescL2kGA+2fVGnFjYJcPyVfD1N21ehTaLHmXC/Q6WuVC63pUuqEww5owCoMB5akoJnm/3v6K+z/4bgMAY5JBmJ0nEzDSg1bIoBCwIPcOfCL/ooM06c31J0gZRBYp7uPF5rZVtmqn/9tTNx996z4qk6w+QDL25lgCI8m9qUfi7UJiCmopmXHJuAP/6cAQTFQAAAdIu2ZmHEtQ/5gtTCYUGiSzBavTzgBExly4qllACADu20zpCtHU7CQI9PuDOgrMqGOInPqXSI0cds+r4vx5FVNP7f7ciNa5wQ0RMY+bbrIJy2mQqf//v+oES/7iNg8jCo1hIJPnICrhgNhzh/60mzNQur6gXvGREz7a92qXT9mKVTKg1sECwGh0eBqZgOf7Z7W57AJFaCkfRQEO//2B8qyNldvCu1EpBlvdXamA/0FfbdWGsoe2ZrqhP5qmW//H2ad7qfW7vaiBtMsPNWBwqQYcQDChdI5Z5hwe22XMXcVv+yAAGCgrrdkDumOIIo8Cej0F8+bseH+CH9WSHd+n6C3atP/87+2R0IKKdyOKH1eOAa96k2nOJ++TsPmQQBc4QOCpA+XFRAZCDybiCKyCYgpr/+nIEpgMAAAIYYV2eJKAGQ6a7p8YIAIi8b3e8M4ABApLsj56AA0AAAIBAIBKclVPFE0mguxrtb7E5F3223r96REBF4r7//////IhJ0b//8jRQTF1P///9hMXZCHRh5P////7qLh8+To8ABwkiC3WiwUDznT6poEi+6sO0CPvZ5CK4AcMV7CmciIGfXp4yaH9/+n/2Q6vpMtNWuiIplmFVLi/4gMiJI+//rfPMQQQEgp79a20s8NAahwhP7cp6ZwrNNRnKRROsZjciu2IRojmEL5tTNh2MZNlDppn6Sp5Z2xGP3ND0yKqCz7MsiKJunU4zVoBV2AAxO+J8PFWnKLOTJwP2Kan8JWt7HAA1rB2/L/ii1gRfGmfiZZGhFUKuHBoqWbQr9KzMMq5HOjQbakurSrkGpiCmooAAAP/6cARaRQAAghM629DKEbRC5gt6LONviLh5YGwsq1EHGG1ctojqhAQIkxpEk2uAZNMJhBJLn46TYPhNVrFs0E2DbVqFG55jblYzVUu2nQ1XzG//0Nlzb8sMAv1hoFeee3lXfrcr1uliyACBAF3SSSviId4JYQteCuLi4ljTjmpZ7nsZi1qluWxwO6yv/yKluaYXt3ZS/p1mpjnAOEn1O8UTMCn2WdJnvqdf5EASSSS7hm0K/MKyN2Hkyp9duVtELDRtKr1B5+ENV5Jz2bijV6tgfqaEI4JuuUfkrNCdjSXwLj/3aFGuzLXdKMKHuKIZpas4Ae3/CrsXF8GBUFK5QkLkY0sPN8k9A1XB0qM2N21F9Ian/mRt9O/u/tsDllWtOT5UUdONQ95oYLWAFqUlSHkoxqExBTUUAAD/+nIEx/gAAAIkHFYdYYAAQWPLN6ecAIi8c2h484ARFghtTx6QAAAt+BqsUqIkxeSFVgUctHImFTt5ENPoylZvryAeUKRStKUr3E95NZFgW/FOZ01i0vnRhTddmJKdVFtv7It9HR/////hwAQBkkkmaYIATedIFQp3je3sFPRXxcUMYYGH1qXNfKlm2/OFDY7EWeOVhlX0xJIKODnfE4P/OS4f5D0l/h9FlThGAJBLNsBABAbm5C6mWbx/BI4BEoZpaiobhmJjQCJQKiURQyTNQgXd6DhZZOlRb/oQoRTf+2hD9kq9xYDlYoFhAFAyz/84bAOwv/++H4qWLBDF4lZdjyQ94SlWOLY0LvTc9gq4P7uQ2owtoGY9o6VYo9L0dlvTgFj6LGon1iKFNvWYigvCKv9jLEL9W9MQQP/6cATlbgAAAhNd4x4EoARDw5t6xgwAiKkPavzCgAELEq2rmJAGAAgtFotFo/Kyr/////////r/sc8+UWECFVf/5CSQIYziYkI/8jSYvIc5HD7CJhiEYM/+5zyEZT5CYw6DwUOWMcgkgAAAADCS1mw2GAAEIjaMcHceOJQ/EM8uyqXK1oU40Ysvp7HZimmZeYoz7w8xiZdqRIH5dtBZRo4o67vDQ97FRf6kwWRAEl2EonPsPdeC8ujtU/Zen5ZVUCy0fRqvfcv/QSB9HuWjzOpfvrM8q+hv/+3q3+hSlbR/QxhIex1dbvpbPMddHp3SWSLYAVeUbaGAkb0eRdbTksY2yJWvWDLspFnXBpqUIc6eUqXOC0/Sf9b/oG9/Z4r0Za/c++q+qn+nTyCJxiQEZi7v//s1piCmooD/+nIEUEEAAAIOHVrJDDksQ6S7Nz0FRIicZW1AsOExGAytKLGc6lAAGU/Vt8HjHGkllisGhhMfTL6F9RtnAYYdIFv01GTu9I4HP2NRDbA7UmhHnfZ+SIqVqESCJ34seqs1sLPIyoCUPgOAHttxiPYmaq8sQhuxbBhLk26maND2rsrGbQd/40K5pIzp9HsyjlDY0UPLaiydaZ6WiLeW7LwzcylLCZ9o9L0rLLIIAAuCVbJJfGMW1VjbDWAj1LTRI5e0aykR2SUIN+0qBAjRK+VAETPFhYi9LWB1QX2j03ipl5zet2lMXioKtIvFUM6b2eaAACAAy263tgFSzVIABxMcF7z1Xq+vFbakf/KAGuKrLmFV7iaReJAxcfB9vC61BhlRCmCBxwXFw+IxOPUvVXOZyiXPv6/k0xBTQP/6cATh1gAAAiAk21UkQARDw8vKoSgBiK13enhTgAERIK9rBlACQABsID//y/MmMKCAmEtI5G5/1lAPQAyGgol8OJbTXQY///zmWoMHiOPKT94nYEhCZeXbPI5q4YLqU08ftJque58uj+uAG7wnbG3Fw4jQe8U7SCdePDJxBWQvqNW376gqq509VUtYpKRQmo49syst8ZYBbobCB2klrKLe9iAy+NZF3VchoQ7ZrAAwHA4HA4tZBQNMUVDDvfbT6aaW7d+/t7/+r5//9BeZHDf//nnkypxYsjaf//UeIqPlBKYweI/+d//3FA0NCRyCDSaUFgAEszCtk/3A/HA4GJx8QE2MGisltMZEIjWOyPQWbH9zZ0bUYz4RdJBKjTiFXsn2Nlf//WNS5pcrf/9hF0gf/9TAmYepKYD/+nIEAN4AAIIXGV3vGKAIQWQrB+esAMiw22BnlE7BGQyrjPSpEoCADq67fEAC0dJFjkE/nQ/OAe4joTJuys1gd99KsNAx4qsck8ISLLrwKMNlQW/9l8qATv4kArHyr6+RqmdzKJLPZJABwlLgAMOoTsJ1lgm6nJoupYO/MqlqNa4AeHnGiOqcFTuFRpLd5DN5bwIoCV8EnJbUNfMP0h+Dosxu3hJuKCDeFwAfaNvFIInjjV2apTUhtSh/lUXoFYwHzpUcIMsKHevR6gfvIV0NTZL/M9SspdHK3l1R9DdHMBNxE78SuZz0RP+s7/5W0GgApbaJIj0KQStG+CKyzdJKbIRgw8fNhaa5mKBBFlmtqb31BJxZSU3KpqskHijCYcKhZwXtFgcNn3Q01ZkvfUhPcLtQshYMTEFNRf/6cARBzgAAAhElWtEjKZRBoysHJKd4iGS7b0OcbfEOk+yc9JRqhABZETf/1R+yboHyVsVA3gnAfRMceoYAYqaLPr07H+WRNf2bGxbS5seHyiA4oCIq3saHgKcLrjE3CFSdemLiu38PQhACl1wOPmAhLR0sKbhUUvYeJXhHgC8p4wMjiRBtTflAd0ade9HpRh1K3jSIWwiCbqzsSSi6I4q7PGkrHLPisyq1ACpyS7pI7wiLxCpayg+ygDw+4YfGcXDSVIgRJtV/00BaLu//1nHLi9rf9ptSNAoo4Os/zh5dzjL7EMVKUKqiZGyr1ggBC224xPOSI300Qv523udkfuJaBL6MIMeFBjxvtjRn//W6XZR2eMY5uu0qlCDEUYpSdwQtxAthSYcSKGmh402LBhDkxBTUUzLjk3D/+nIEboYAABIXHde56yrQQ2UrFyzlhIjRbXVBiKpRAhJsnLOO0gAgD///5niIEr4ZdUG0Ye1HDd41Htg7q5M/Ov1aFB05IPx+mgBgn1eIHykza6qo1n7L+sc4g0nEG596gx4z5RPU794BwA7tsNdgeAndLl3ExPk8nUw5WC9qZRxUMM4gBXqX+vyydOvIbf+L1CowuZU5DjLLzJ1YuJC+2tlCHi9JpSnqHoKjoCsrCV/3/sxQ/Bxo8CrjMiher5mrfoXt+oZ2vdP+pnlLKis6GZiC0pSuyb/TTb+nb9dKxZjLaVkOZWSqb6/oxu+/vcKfZkgbgGaXUCcF9Rc5tYQrbzhCbSfWq699caZk7MBa9TPzAccySl2LXbVpWVAYIupMipFcpaUZzCMhqL6upumiBvyiUxBTUUAAAP/6cATgSgAAwhsdW1EjEbQ8xKrDPUKyiFxxa0SYRtEhEOsM86LCQJAcFX//680lF6D2pHz+ONiNR8poSD7jPUv1HE6LJGJLrteS3DRhFZIhYlT7HDRCKRjljkUWVMyyA6HW941abO4OfeAHJJA2xHZaBPWOxXMP1I54/gp3e1TP5WD+LC1iog737YgU6jHltAX+fLAUGo223UZbF6Pp66V2GPf7fRTAEgIJv//uGaC4vxqAZzr7Rwa8fVvp8GzwfvjN8SI3KqeSMAgMCgBTJBsSVmkDxYICh4KY1E5cqpcpD88qVF/Y1V/tAEkkjqstwQ8rn5AMnlquXLH6eNvWEV/Vkm8KXyOiYpfG7hlqj3X2ADX8adHG7GcjHHkigkbl2HB7866jusFW7WsuZ2j/9Hb60xBTUUAAAAD/+nIEhDsAAAH9Mlk5JRvkRGM7OiEihIitbWVGhFtREJZr3POJaoAECb7awdgZNXIUM9j5ftgO4Lq/WMczvDr433yjv7nb/yTZIZGt5wj9O7JVWIDXaWj+TfQIhvWeRVO1fa4r8NwgoySHtvgQItGtQYNaTpM/WOS66QBd6+ak5AL/2XUf8o3s9wigAPKaduOUSLUl6n7SQlEJoOIAoghwVJgidCIzbK2S6oAEIBL221JjOHcoSeSBuoz0XUTyFUlrID1oTlDqNsxS1H8vEHv5/+MjazUPWsCbnvOZmQSIw9ovxev8Kk/2kXS+vZf/CPAQAy62yT7KN1RJH0lNMmCO5cjjV5QVZh0pCxfFA+DftgRut6lfa19VszL03drjMAQfWHK02PeLRRq70o9r7/rVdkmepMQU1FAAAP/6cARjZgAAAhtbXehHFgxEI+tqGEc9iIx1bUCkQZEOj+ycFhwaZABShJU29tqHdxq3j6YcQjCm3+kM2p9G4zUFBH1F1o5HryPm7PVHzgindTq97FK66WyzWv5dFp/y1r2Sn/6f+n/XBNAAkwS1skDJ6yvYQNWP25nwPMsEXusKBMXicaNrUwM86KDR8deROmnFxIQYLUsn4tK3uUL0cJD2saKIQmq6Z3kinnkh1ACfQlf//rxCLfrn7Y8qy8HXYLgynq0FD9h3o+vQfXaI6klFsDEe8+wIR5hVY5IraiTGqS57WNJmTh9BtjfQt5gnazw8AcM3bbW6HKKo1mCu4amdzNuIjzTaFxx2cQBMG4pFbb5gTvmx2ytHGobjyFJ5dhgB0GNpwetrVk2M/Q4X17StrkDa/rTEFND/+nAEteAAAIImQtg55yrkQSMrSjCnTIg4d21EmEURBIytaBSUGgCgmW7V5tU0iO8h7q6RCeTXKEbu9RdlBtKRgvhQo8qQ1agEJ9Zb371XX/9/nLqyvRlMytPdKXWyHy9XbFHlR0Bfd6KfrAAQsJXbb3c0yd0a7oOglV0XfgZowxrhVIUWL1IaPjcIJYutCXJch5incUAHn7I8+zFaZdav8H0KHHQZIIQIcPfbABzQK3//sa0gFvpozh+ZNc4PkwYnULw3MI16tR17UJCIF0IYcEcgISCAJiP7RaoyTaoB9yUyw++t6a0rL2joHT961hKf7+1p7OMitqhs16huObAXkdZmxgK+P/HAXLK/KgMOBkJCQ8DLyoFGm82vd0OyTiAVsVGEjJ0WFj6dY4UEX7ExBTUUzLjk3AAA//pyBB+GAAASClra0UEWZEKDm1okp0+I0RdzooRcsRgU7SiVFP+AAugQ9//mLIIIli9g4pBLdxtUT8qdoUrI2ypbV9eJ/3/9vti+Q76JxsExCmJL6k322qLJn86iX/EvI73Isr/u4RECQwlLZI+7k09VE45xadMaIZ+bdGGMEx2YFTB/wdA+7RrD3n1paLFWliaWqeoIyNsioJ3xynWPNvfqz9SJiL4jV6mEAFGyCrtbYQdCAPGiQLFYmkocXDrxo76u3EX766lBu8fbm6zCxER65olZM5DpB6Ti+RXkVLdkUiIx6fiDz0KYuqpKb4v6oAEWBLqh7CTbGB4NYomJxzHj1NypHKKJK8fw3V8o35v/0Y5G1S+IJU5fg6uL8fzqjsSl0lvH0yUbfrRpg36s+3v9Hq5/bNMQU0D/+nAEMJAAAIH7W15ooRdMRIM7Fz0nNoh0d3eiiRDxFQytXMKJFoAAAk4lPvrcRKD8SPPflfGcdzxo80lH066hF+8xCjEKX+JnN64jGYjVGbcAKhJWlBRWXyy//+9/l7l5/L7eHLAABS7bSTe1t3xsH8KZBervOUXxs22mjmvy+pftSEXCeSwmHFZG59ZeDJ8kiJ4eLCQDJFgWhSSSu0Ua174eZGlrhk2n1oAANOQufa2y8YGxhg1TKJNnSoB6isjQo5OcU85+nAVuOfncoItaVgEwxopC6xZAv+ti3jFNbkA0WL28bW9iD9rGjP6zgAWySY/7n4Jhwr0PXXOG4XQm6Y6PghWr9txP/EhMKCrAqYEl1LTggmkqBitShdh0sACBZF7ohUsPDiqyxsMhKxrmeG+xMQU1FAAA//pyBHhYAAACICVe0EcS/EMLW4oFJR2IkFNrRaTlEQmMbAz0lNqgCfyrv9trsUeCUe0Y9shyPO3O7HavxiJguddWkouT5ZNhiJIEhQvEtrBRjVpJGo1rR7KQVSONTZITFVMVXtjxDfOeGQQL7JItlltDlRwyf7vmbbYgrDr6D8rso87rChbX+Uf//+ZeTnS6d21PfSh/Xs1ul72NNVN1RXL6kRk0/9/Sv/14x+lACCQjf//JdRuF9YBSal6IZZ4OzuE3K0cryg80q+4t2hN77B6hd08HWPNlzzmkkHqRRguuH2ode+XPUN5R5FEDqehF3qALu20zGqwalZxilqFe2l+ZCLwhz6LV1HpCjdu2LHL2/1OPLxEDRNYtCUDIocoKupWLb31k1G3gySYkjdPi5cXcV9SYgpqKAAD/+nAENg4ACAIcE1rRJRKEQ6OLmhhFO8gQT2tGNEZREwyvdGEoPkABWBM3/+v6MhLjYVBjmtDdHPcvE7hKOR8whyaJzSn6a28JmQwpx8Dk0OGjYswVe8YKuWfD6VBAieFVSr5dzC2tlPmoQI7KW2tofNfJFojTZmmXFtUN2jYQfGG0ftiKfEFhfv/uc0NEpZ02Vz/fDdz4uzf6J6Yxr6/X0x+WBM51fFP+T/c6jSVAq//+bXBWq0TdFaUFhqRQ8gz4E9gOkNwYmp6nKfdF/kCFY4UMLiIwkcsU3iS4XGoMD6qLyDnofpkSZJVwtT9cLBMLlbe/21kbSgONGkO1nwupVtHKmBkWuc+hf9BRlF1ajKFAyG71OC0zp2IizhIPiNwiBKElhw80886sRk2QOVit932piCmooAAA//pyBFZKAAgCERlZ0YUSlESjK10sR1mITH1tQ4jLMRWMqp2ENVJAABgVd9vZioFrkjyIwtaS8GcMNm4njUg2wq6PpoI8oGRZy12PHaC8qKg+gFdMbSp6WpquV+0BgdhsPD0GC9rSfw2iQAQViS7ZJGGSxruFpDPgv64cmj4iMACAoBEcO5+hf84J9avLkC4oFHMcT4lcSFk6Lr1CzqkRMhxYnIU6W1vMC0moz6UbSdtkjRoKhiKAHE4yherZdDbCoNAMu8iv9j9t5QWhjtPhjzvy2lUMCoKhUnTGuc1TN0BO/RpMxzKLHuJn09Ebt8iAEAKSSK5yfadIomzkost6UvttFYsDgI3TXlJs4yRfDCavJ5f5t+TR81JZwBexj9iYo02qoxFPqEKFWsRu4acml7U5nb7ExBTUUAD/+nAElccAAAIgWtg9PEAERGMrGqeUAIfxF4U4E4ARERxwdwJwAABhG2220y/O8UCsD4Q4bbWwUd/e30XFaCXyqHI74czQq7agXvCnAis6UXe9f/Zf9KN+3/////X2Sn92/+uv79wwylXtAAWkAzbbDF7OO6F0Ju70qffGKK2PcjVAVc9XK+ov/4wO63sVWXGAkWaoHqVEXoYKZF0Q55gLCt6LyrWtJo7loJW0WDWAAAEEEGGGGGGVcArYf+YK68Jxuip883/55i2/+7mVT//QbuMC8///2OIDUXnmoZ///lCB8AAef//KCg1I8CAsAAAEBQGAgGBQKBQKBRkcnKn17hPbgAC4uKiXzTWT/sSNm//KR41V//og3lib//+xcgNSYOiD/xAfGAef//KCg0yPHAsmIKaigAAA//pyBOI4AAwB9w7dFyXgAEOBvArhlAEIkENgLJtHCR8r7mj1FOwCl/YSFYXARGhEqhLJCNGo18768us1E7SkLI0BGqxEK0EZ0goKjDyj2JlxFxFW7vwNIz3JLUm0S5UzXo//y1QABgFutpylA4GEMhkVlDHUqzVbGAU9GPlREeKkiM6QGBUBHhE/ExWIuDWm6+oGnkZ4NViVak2lcq6t1dMS0LtivLNAwuLDGMgC9mzssRXTFVKJ3QApOzDthN0exKHxiSmO7dW5f5dMCC041yAoghcEentyZF1kd801dEnFt2fb33Q35003///2ACgYi9rtz4ggOpRwFe4VOdTGfd721DIADhs9E1WykdF9MZR2KiHfTvGtX/8rajOZPfUrdMq6e9rsPvW3pla3on///QWf2e1CYgpqKAD/+nAE/oUADAICDtmbAsIwRGPLE2DTNggA12JszElBFKvvqNUIvgCU5aEqAEoaEyiBoLcF838yGDotHBafqxIyKTim4yFRh9QNrXmWtXkSOhJA7lkuSlQNZakiJDdaiw3xjxo1H/pAFqtFLmHUwtKApc2VyXascYerJZxTJNvD8gC3kyXmsZJ3Wpdd/15nn0WWYCZ9J0DNwke8ru2Bpy+jZKv+oz//rZrtZ1AmWS7MSE91lwxfemC3eiNMZscQ3M1Tq6ycAVkYmhNPSTMyZfq7zc31p0Pp/7br231/0G1t5TTb+9vo27N3qJdQgfwJzNtyJCmm9all2VWnma+MwVN0YttLpunMZwBpGPRRijIvo/7fp8W3evrLZymbR//oP9vqiv32p9r6sqn/EuVW+GOhMQU1FMy45NwA//pyBNvvAAAB8UDe0OEVXEFIC1c9ojyJFH1mbDRLISAb7eT0CL4FL0k1G23AqDwrLmDyLLnHTMtp1EUNHq3GUKDiP4f0+38n39P5/lv9TkK9Y1a9O98FihtVr1Ur6L/rduuDtIEAnHE5BORCIc/1dlQY/oKqR0L4QIWhM109Cvddy1qDBBdmdQTFqftNb1+ySaFH8j/79un/4Z/2v3t9OT1B7SLdai0ityspOF/ZPJ3qgzkaxv1113S1qakEkFutb7BizCXam3UbsrS6CTzwobqqflH3BJXlnw6nsfLsJNObrQHamWGsyKPoTVVaHwAlUV6azNB4Mm/I4Ji13HPMP7JSWCACLz5itoW3/JIK2daEdVN1oWnkb7vo013/KlHegVzx2tMFipVLdTI3xd+hmHaecDepjUxBTUX/+nAEe60AAAIaId1R6hpsQMsMDRwix4h9X3FHnEXxFpAsTYeMuABUyLdbSclG14+7U890Mt1mq+gVgQDLpbnzp8b79MjKqUiGx2QG15U5Y/BqkfDfWAILR29mfT/ccVdQa13oAVbt4bsCRSiRCjsklyhxS6KcfarbPr48M/ryL6fx6zg/5lMMua/Nenl96wzZBMGmj+ZN+blptXUvkb5WVd+WZ5IFnWfcHAAJQCUbSUYxUtUfbepq2vayZnYVAxP9RTK9/nak91wRmnaSa117Nvwb9iH0/vsR75E+/Sqv1l/b//6r/9CiOALiM8RUFPnP+yMjhAVTsrZCJUMSLeNj4esH+txpYiTBBTRP3KF0Sn/59oSo5nC6+SBnNDzy9XXp/KtvJewKExs83YM/UjqRu9KOpMQU1FAA//pyBPPSAAgCEBnZUwlo1EOD23o8yguHzFdcbJpnARCgcPSAiuYACAAk43JB0JRRuUTeAoDsseacGG+2/dbY0hCkzVX1H1bqX3SLIkIv2NLgFAs2DPXtZs29r82twm7tbbv306l0VZEAGEAVEknCjEqlOFnE498ErM16UoMQLEb6Vb+3NqZVjjkKkJqKllM2dOzkFBkDRhVa+t2jzd6ppTD66KKBG8fU0IG+kMS24YdrFkWFM5mF8O3bIdjMVDjke+uTizIT2A2wjD1UmOkdNajZxeg7gSdRWWdZ8N1G+dgVNI8O+un7///SE0lLapL9btxq9VVnN3mv2cHdcIsDQ/Ct/2Ws+v+ZiwuLzAI6OFhfyH+f357Uoot6I+Vf6EVUCshV9Mr/OuL2YNd2myhMQU1FMy45NwAAAAD/+nAEQLMAAAINEN1RYFOMQwl7hzxnDYhs53lDKG7xEqAtqLaKDgEeATckclClddtJHOerWq5p3GYap2LpWWuSliSUDtYptwu2hTqg7Q9gzHC4VQUH8sSUcadlXEph1Cu11FDuy61nSHAFVxSQjNaXHEV2r3zb+IgO79X7f6Z+m4sSYY5Vnur90+9tzD255vr/MbtRF9EVaGKO5lTDeyKifyg4deD5yvo/f1iDeEpNbJaiOucePnl067vf6hPBtRGqMfHXZbCL+as0RRc3pXt2dtnLhdh/xPyjzh//w1TuOeN66lAkmn/VxTkjVf/oBASAoxtNwdoTVukkC2aW4q9aHTU7D2D0irUotKCG+nk13cyPOzPZSK9vp9zfKmh9NWn2rZv8rIZgcV1z2U+lG3t8+rzyYgpqKAAA//pyBMvpAAACB0Ff6KEV3EIIG8ok5XWIAQVkR8BLMSEgLajDig4EJJwAuXXXYBB1aphZLzPUc/8BQ1V4wfuPJcoCMxGA4YiszdMPn39sayAvJx86GFm3n+dNrAlYROH4qW4ffiyF0infgmtbLaIGo7nrZVdOhdKkN38Thr1I+gI3/l0wWpRGiNupy7L+T0fxttdWokj5moOv7oejRN0GTOM7l9rdX+D30C91gZzgu7Uis7VfdrnnzVtdrhYkO5Z590aQ6ULXzPbsPqlVzNZNH/plWZDI/NkTVyPqJ3Vv26sJ7PpXI1p3kzAEtQAGOOQAWU1enWSsqmO7/9J1lepnA0Ai3Be3/o+rR3K62Vm0NNdd+8HZUa07ao9KmVRi0Qem3Uro/BsDjjqYfe8iyY1MxQcmIKaimZccm4D/+nAEdgQAAAIZP9q55jhsQmarFz4CWIf0ZWNGLElREaAtHJUNPgcBONuQDvWS6wCsZb7k72mMzjoPQZfo2UNPnlqVsYHuvtpVr1qqq9NP5R/+1pqXY52ZWlW+j1YyPiFo1CUio3dXlwCEQFSS3AB7cKT2llQL7NqunmqD0mZBMcwCyfXulKjf37b9Es5T+kpGROn918j/2cyQjBcOihBk9cKz9nnhZrF2ldywAACBUlmwAKbHp95WUJTatQf+Lns/8PCgey8+srP0EPt2THtQkUAQrkXL3WkzkHnD3fXR1uNkjNJzhvta5k11lQBVccgACPKN7c1XejPufr4tAXsdu5r1La+9qXo3qobBM2lsrNkT/mfnmdb7BtO3/14HU7S8p/5bcOB5l2uS2NZHkaLExBTUUzLjk3AA//pwBB5zAAgCDxjaUak4LEMICwc+AmaIjNVUbCBUkPUM7GjBNRoAAIQnHHIBUZA1pdgTjayvo7TyCZgUAuz818qQ0L8+aVOFjZ8eYEDVuAwafdo5K2hCz6RKoWdKF0A0di2lj9SEZ9YEAVJLsA1ixIjTva7RzL9XrTdjdqtahFRKRo1dE5e3FZmz4wz5pV9p1t1/q3R+vVnQrh+ve/uZVmUW8OXvz1G3UnTeCk3IAuuVpPTzvocXf20CtAlWKWfyo9ZZc1bzniUTPJZ/dsB28ip8bNUxep+8VU2fa/WTy9H9rP1Vtes90ECS1Xa1ZfsAICAJ227A/GAnOx4lgPshbYMARVJxNQpEj1dTdFW3TVUWu2CARChceESMDJlwEu2VZ60OWiP2yt+vlffxZMQU1FMy45NwAAAAAP/6cgRiVgAAAgM3Wj0lQAxDKAsapDQBiNTxcVhlABkZGa4rDFACA4kpHJA3hzGkDZkdqPZ/+xz+wtAcfqTlkYifTpdTS7p3qjGMvzFZN7XX6fb/VqmEbHSGNDOxg1J4UXxVPT9MtaABAARbbjAsIZQayKcLbw8twbuqggOwEy6b2c9qWgr3ZdaHrUkiuzr9tnv1f2buj/tWqp2XQu62bXTSRU888Uuj1f6QAKgALXJ9v/xwAACAIog5hOoQQu4Z060WgTySLIswyHds9e/M6ELZw6mVNLUNJHQ02apE76kJxqOxiU1U5mvY5rp/+hZR6oAEoIF8s3///4AACkJUMiug5fXWHu79jgc0BR0WZWXHGxzc6ElEGpHM+iI8RHHDpVF0OZCknno1YorqiNOqIPfaKnMuQOVJiCmg//pwBILJAAACGlfhHgTgAkQq/CPAnABIlOFwXPKAARUb7p+eIAAYDj8fv8fj54b/+s/XT///3f/5yOyGf6uQM1FI+MjpP/MmGMfnQ4Rip5xI/Wb2bPPcxswTmJZjTzL///mN+RifhHEoRQOPx+/x+D///7zM55Vv//+7//OR2Qz/VxoZqKR8ZHSf+ZMMY/OhwjFTziR+s3s2ee5jZgnMSzGnmX///Mb8jE/COJQgPX8UgYMc+EmaKnYnydebTrLS3mKxkdBIHBBI0xjK1HSVi9Stly1LQxv0TSlDGTREN1MdvWno6aipS11Yx6vwC7p//5UAQTjblujvFjXS+oX8KAw02rXuLZmLMY11FEeYz26SqWUylLtQpVLQxpf/No/0N6Hb9PR01Ezy8SiKz5UNSW+xT9d9CedTAP/6cgQovgAAwiQlWMsjijBB4dryaM9GCHjZYswoR6ECiesJpkEgACAABa8a8Qzkx7eOyr1oTewPTOVWsCDbIaSimE6Mi6aqdNqj7K09Va097X1af385Wsku5yXOmkEEBJT1zSSyA1dR6f7Rv39dRUgjx9oDopAridxr8uCmTwtgZB+UunqYYxIUyAIfGtlxrFxWRYIb5EudZtySFhzurAMjbw6VMBzTsv0V//1gIABq2GhK4utGdgxypWIiOj4nOdOztk4BAKdTdSrU3Qy80qO381HNy5v+hWfWbYpabWcpnjJJNnWbzMqVY9tbF8l+xAt/mFApzw6nwsFVojzbX2p5PvP497V979+ycKQJpPm7ZS0lnVDgZFVJqqNLDvLLRF1Ke5SxWjSvIVr6YMf///+tMQU1FMy45NwA//pwBNdKAAACBjVbUeMoTEMoG1pBAi+I6QFfTRxHgRqgsTRiiX4AGpEnGiU0mAVLCCEw2lGvi3LaYSN8KMWrmay7WUdNqRaTiP0bppVX+q+6LZf8QO3ifzlX+gqx3MpQ2vR3oEz+TECpAtxElMX4B8zoqGu0slGJwI3v/AOBH37HLqKo012qQkgyqyzva3+T1J/+M/ff66Q/uv/oN+k5oG6NKz/cveoPryIABABzW3bCAGUiW0fqBa0tdwLnDVi15cP0eaNCAYrWGbUV1b063Qt6K/anT8b+71dC0r+qKK5mV169/DjFO+17/aj9HYVEtQkktucu32l3cMEah5Rjoa59WlGdAkAxE2EU07M2+wJd/0K3/VfUT+yetmkf+r6jvdH/N4Ua+9lo9pY8aXjRJi+hF8s1j149MP/6cgTCaAAIgiIrVxssKPBDwzr3ZY0JCHxnXuyY5yEMlyvNligkBdse2IACPl9IGfFZJUjN1Bt18nrZ1K/MYOAPeqOy1Ho/3k7u1qSW9fkV3p/Gcf8s+dT32EhahtLrGUIljJ3Yv2aV1YYAIU9EnKQAEdCl8MXUI9MYHYUOpB56uwTMEArWeQ7qpfaxw1sjmCR4oa0teW4nxvz2LPe91bp7KeuK1YuYO5zx3OhzkyFTRpykiBGq3ap7RYnA4CczOvfKvdtUwBQ2n5pVlqL2Rf0cPdw8LnSh4qi+Tofq3Eg35iFVPfMMTOau7Yi+ghr6e/fQEpEZcVEBNERrIEC4M0XPwK+YVyeqOIUEF9WX3ZTWbbO/e//6zloqt+r+TuV6nWE/PPGvfksMihKZ1uZrQ55vtTtirkxBTUUA//pwBG31AAgCGEBYuwgR+EPm+601Ai+IdQFUbTxFwRMZb7SAidYBlV0ttxCgfLTSDOxL7ZDG9J8L6z0wTBT/HKLxSj0L8sKATO8d9Lel/T/7H6HJb/oQlkIzon9JKBHKm8MGK7/6/7ELABSjQbcsicoshQdmyUU7Zb12M8z/GBeb+QpaXq3UsNpkqSrk+jM606f2FdSJen+Xxl1alXJ+P0AZ7xRgScuKN9drH9IBVjtAJFDa1nSjDbFQEkHpBYx5yu969Xf8C+9PRTC82n17qbHLt+YTo1//WTs7muWnurZwx1b2+dfat/u8zQXTz1v5IJlSRkyz223MFURZzHxMrrzD41xP2BK9JaotORtNCCThQ5ykd1v29Cfu/iV+cZ5wDJNjz156pxla9cDKaMWnPsq/Ztd6kxBTQP/6cgRNDQAIAh8uWlHoEfxDiBuaJOVZiChnXsw0abEYqG1o84l/AAqJNVotgWcSqve7bmDa0mQfyd0tR+GACEP8iGVM0OWpVVm0TXIxItWZ1tPfX3/T4l2nsmE6TjzRQyynUNXc+tV1rjAb/Zu2yOQAuEYwa1RCqexJy2Yu5/KhF6hOkwIjsZJrkCQncTRvyOntZK+v7G6FrrV/Uflly29qMfMPqLUxpbL7YtKyJCvlgl2sVncZ943YuSrZru08u12WmLIVledT5u2I7kkYOYMHzoUZyhIuDI4ojIxYPmhj33EZYa/WjZp+RQh77W9AAKQNyxJwBTnDB38YW5P7omz5byxb3EkZypp1H1LUEwe7FldXUiMOrLe90MVn0/T5v6dYgvNorf6rM/zei9arl2kd1OCPyTEFNRQA//pwBJTzAAgCGS5WmwsS9EJIK2o84j+ICGdlR4zm8RmgLGj0CTYNyRyAQXGhqOyuTLZpnktdo7sXO93dfogfDWHcV38oO1Hsr21I/W2kqMeyFLRbOe8ifdPOTVQQJjHs7riXa+J2vu/2gg7Ao7Y5AUKDzC3uWMfZiZ7Ir0P8oHfFXUxWZn+778y3qy6XKkLoyIN+T4WyHoXpFv2W1euVeEHU9fh2WJuZVJUyVABuNJwCSDOXLm6is7iF5HXxk36AtL+yPqQPVjUdWVB8EWNhkY88/foi/bt7LU2XXpCzT4fVX3gogAl7MBHxTJAABSk42WwF4S5whRm+UpG3V/PEmQgzRbB8H7aoktkoYRJn1e53JLnEpK5vVKVo3/urcRfX/bsmNT1Q8zsEeYYcyL4A9aeRTEFNRQAAAP/6cgSnQQAMAg9A1hsoKbRCiBsXYUJLiNitTGy8R4EFGqppl5UoBTcdoEcTcKVmzmKKMjvBdTWhL3NqWOiQBAdWpGP2198YfN+t/Wtdf7LiA70JabX/zb/o7UoJvnDKIaqPlXVNFGIWDYNxtNgdJPXLV+lmn5mMSa6aDjst3YVQ2/bUZTq5oXiXVX3szufs6G7v/9jdxbro9eWVdRstvJ0SGHXT1MjEXtjdQIu2wBx7h6IgKQHKICpQUNYOIdO0nq8gU663ncP5izJ8Vb+P5DhmO6DGVlRvMbbddDFz9le5UzJduGCcso64WnBEFdv2iveAEAAM+24BoxoGLDNpAy0F5By6xp+Ht9CphXLn7gf0XAOwUjVdyTNryfmC0oSysIoRC3P25V6f5erlTv/EGW++0smIKaigAAAA//pwBIC7AAECDDlZUeoSXEKoGtJg5UuH4E9lR6UCsP8XLOjDldYAmkinZHIBg0PRiu/ZVXazGLzSLToJAKl72Sg2zN9A+9EbNFErd+ZjM8G3TUnQRSVrtvl6XjFO5gjfSfst2sZqZoG+qAs124aob8FQCr568mYsbi4Py/PAsAah1GPXFsidUQLDGPftSmuroqUK7nenqP8i9ftQP0oZmHI7ryJfEDI+n/WADeKbsHY0phgbA9QeM9WTWzkuC1+QeBCL9kz7g10BqOQHS4wHwMJWuWKPUcejcQco+33B5AieEiUykqNDvrWADUKbsjkAKCVel21o+Rd1FNZ1efWN6geCavqEXyOtV1rRvqY9JyVs3vZ6m/K/ILrXbg+7YW2PQUaaQy+ccr8kmIKaimZccm4AAAAAAAAAAP/6cgRJbwAAggM1WNGKEexDhvqTYec8iLDfY1SVADEKFuoesRACABrIp2RwA2aKRYVrRVAOXK2OWqmubxIDVydqg7XPonapyujciGBsV0N0vS2lF8Ivuuvf0GPQyojuD7BIBMNZ06EnHKAmETFhl2ZUgRgoOKUGNvo77yw1jH3u3hkQFku5cZhS1FZzDnafQhext533XTtXo9Pv7L+3satJIUW6ZeXsXvfWAE1RTlsgBtlsIEwgAZg33/b9pVmucccF0BeY75rvYn7/khNTnIdb+eqqYx5itLvXV1PzGRKGUu7rH5LnFws0DjkqXOK9hAVJJAFMl/tJjTiN6qYI+1+Tz/ZF3l3kCs60UFE4E8Gba6zMnj3XqR7pSug7u6R48ykV19fXWyq3//MESvWIonvd3JiCmopmXHJu//pwBLzMAAACHSPZVjEABD/Hm0rEiACIHDdzXGOAEQqb7reGIAIABVACqybXZisBgAWggJI+HbD1i1Ym0tTru9Fx/HBdQHDxJ3slar5AQpVQz1093lp86XQ5gKZsQbTIOQsH/0dZtn//SAAgqBfHP9s7oBAAOqRKBwiDXDMRNDP7yqNqapHU+3LLs1Rfp1E3kGuikKqkUzTFRqv/a/Of3/297K4tjv/7gOqLe6iRAJWyBm7szHzu3ab16DwIhRZZypEaEfU9qmtH9kV4hx9t22Xn9ISLsNJcX6DzzXx/dSYh4VFAicMpIKCrgSC26i40gSVHzgYhCeql8HO5D8QT6PoxUdrffmR66L3VK9FyH/Rvdf28wM1otwWbSrNJl+hlHx/dSYmlsFThmQUFXJiCmopmXHJuAAAAAP/6cgR7+QAAAhY5WFGBKjBB64tdGOJ0CKUDd6OEWrEaIGuoxQkoAADIUoAAJeehaV6DWNJ6RLHHx1T4WCp6toj5Wf/0+hq6/1KyG/v1Fei69WEi0UmJA0erDTlu25b0NV+V2mcOxFsUOBAK0Ae7qJTpwiMwWYNj60Z/d/wMBzfmbVnlK3/0emYyrZf9t//qJ6f8zcmZDP6lZtW+v/f9lzf/y+tnphniJthYcQADFTldoylLKeyra8w3szI/Kj4rG5+vQ11T3W4aPY690bw9Z6Dt+oUu+9X9RPlqX9DOhqAxJGJbxFd2f+xYSrIqHm4NLAAgAbbQMZCFbT68HTYjw6jszabcwGoOmHdOpzVL1waPNf77drq1l3f2qVsj96/q3l/6O96KMRiW8s93/9p5Z15cioebg0VTEFNA//pwBNkOAAACHR/b6Eko3EGoGpM9olgIpW9/o4RXMRUgbnQjlg4AAtyIu21pOEx3Vsm7UxmPiYCLu9ZA8P9slDm5fwTKikTxltFsaJOFdQbDHWFagOVyC3ySXfWxa+BGuFko9Kj1QIUKWGY2pIhIuUJGPhuAwwgx55dRT/KJfrN6qYCyFqZtAniv18//s/2/6vranoetF/0631/dX0DiGUuqEz693cn+/UzkESRLLJb/Y7MjI7IjvFsXvnH6dwjD16itz9/05nRMQ/Gc/X+f8z+PX/YfRqunuWzs0Wsx6b0TSuu/+1aL/qi68dSgf1kh4YBkUat2jScUpAsErDw93z8nQUBn4BlcVGJQxKLRAmARSsRXmxNrruv+op+/wYr7vb/kHPrT+2Jt9SD9aVfTOaqq2wzxCmIKaP/6cgQ84AAIAiAn1tHoOaBEaAq6MOJMB4Stb6GcqzEUCayokKlSACgAW6xW6O8HsYR9QjcRYPIW5EdiWtJMBgTHryr8ubVO3M23+9abftkXbHkCil+fl195YfOsmfy1WlZ6u+W4Eng7TU8AEApbY05UMIkpwvLQBAEjLkVnzCG26g6AqOJQqEJfcnG252mt9GbT/2VS6+nkO7W/1bTuv9LYlz/u2WVU6NDLrXqevQ0xHJIylKQII9lGygktqmniYHd+OB7oDNX27ej/L9fmYdRfxJ2NEzeSrcn0y2xlOg96kM9aU0RMgo9UaaEKiFy2NS2BiN8bGA2ipmR1SK/CeJ/uJD8tJZMgHjApcHXnfQXPaz9KvrqPJ97SLm39RxRozeMCL2mBLeGTZR6pEqyMUOTEFNRTMuOTcAAA//pwBEoLAABB+xvaaMcyfEQl2xcZQkyI3XtxoZRR8RMXasz1CdQAAAlGF2NApvZRAO+mDefJ+bp0Cw4JATKjFH0JN++2eMn+ijmnFH2DiqsWzbgW/RH8V2a/vYlbsn70prv22FlgkctjUtoL9YJJUrn/O3bqEkdXuN21EtX1Sw7HdUaVtXXp9Lpb9X8ZGM85Sv2jkmS2u4eMFRpcI9d089KZgalx0wtCwSIAJcjTd0ibu1EH6PWob9VLfxoQDnlCujt++QVyK5ea0io/+mzVtILtqBK0X/Tfyf+tUEVmlb7u/Vbf9X9fyNq93CPNKPYhBU0QTqTQzE0c3EeEwgcvvWnyzLed2C4DZWx2XW+/1FbXmuerWfs3WiW/Q3Qex/jokW5fWsimvWrWjZCtcJulcaxAw7bKpiCmgP/6cATrdQAIAiBBVWnoEshEBdqzPMc3CGkFY0SUSXEToC10I4j2AAAAgBMrQTki+RtoCfRS+yCJvUZECs+xV2n5AGAFhuFQv9vzv0VvT9//L/VtAZVp3+bqudf3fdVE20aQm0W/TcztXh1a1oqVtL++jq4jSZK1CQjFt3kdm8oFgnXjI8+ynsqTLVEghn071dv/2nGf38v78PRdbPjzn4BU5ykZ4XcMk6UyShz3m62TlRKTtoPoRNJVGJqLA75Tu514kDkNR2tWu/yhB50VB3MlWKiIZ///p8z/RuqqXVmUVbqS0mwzutpjqrtF/VvXprrDABTcTl0abkMohnCjvW61FKbzjCwlAkr4qWp+pfgig3QuWVHKxkmR/+i/kM2oJOrI3qjTSZU/kdKAuZvYz+pE6v9JJHemIID/+nIEkKEAAIITOFdRJRK8Qoaq5zDiPYidCWNEnKvxCCAqDPCK7AAAgCcaJQtCCoXbTKjZJTdm7qzq/CYJO/oM1W70j5VpRq6NiqslkYrs6+Z+pGW2jdpE1DjC11p1Rd27oS9yCSqnc0BQJxklLCbWV41jyPwTy8sby2OdAgBNV3V+Pqf6GH3oS9kN7TvzWZifmXyr+W9Yl4WHgwFexSnbt8hvYiUJTxD/p9oAc4JyRpsQP2jffcHORYLeyIc53FZH5W1yJ2d3yoSef2qvTVXz3SiL40j6CZtHdW9Ky7QXTs7uqoIlj6g9JJQhTqKLL6TJqxIG8ym+dmHyhzeroqMpFgW7I477fnfsEmQymPiO4z135oLcuasKHyO9YnP3kf0DlIcmr+Lykvr+oYay7rib66UxBTUUzLjk3P/6cAT3DAAAAhRR2VDDOV5CiBsKICLFiIUFTOe0sgEHICvok4l+BC/ZyWNOBkChYRMYT2DFJdoXmsPr4QOdUMPVWrxwf3VDKo6vtdr9qVX3m/M69etU5Bk/1atGf2T/RGZNt9aOxbR6MQBpU3YymJEymjgkA4khlx/wQJ6sZcag+CN3+BTTlj41v9Yz+3nLTkk3NkcDP3txyKs/+VuejW9b+CbT0ZkpZec1mgGAzVX8RzTEETYNXsYhQb8Wkq3n4oy4xP9WgosI9XCTLUdq3fYY/td6J6v81v+jf/RPjeh2O9/dyewvYPZxxJLk8ajNAABAVK2mxxP3zGgcczJ/OXuzkTONSfx/qT7dXQIQ1U6hId9LvV99rf+K5BK6l07Ic1FRUp5kUtcEde92yfRtXSzSmIKaimZccm7/+nIE7XkAAIIMONY5JxLMPsb6kzziXojRAWFGKK7xEKZr9IOKXwcBONJMRDAHHmatgGu7DDaW07gRD18OMlRPE90gTLeolWmddtpbdrk/cvq3aZbJZqUwoY0FixkEslp3Ht6iWLLc4JyOSBfNB5ZaN9xLgSV62cMM6EzrvfEQFhjOtuhN9+fiOEhqa79X6q1PLQv6l6Jf/zDHrEgIIj60gaQ1aO/6wh+iTukcgxtpgIFmMtY+8718y9z+4qBsRrwqrqObTukaJtziKIyRZUIVn/tVvykNqDlsVnv+TQOYZ+zszMooeznDqNLcaHjEAW62nBIhxNAaB4oRjtW3KT6e9AfDk92ziz1O01hnd1VUzPoi+1bb/qO2jkda/4En5Vf7I7Yppf/s1VdrOC271Gm+zpiCmopmXHJuAP/6cATQAgAAgho62FDIK+xDhcsaLQWXiIERW0QUdzkLGqsoxAnWADmJy6RwBjrGE2ZNXamr9e1mT+EwJd+LByGy+amZL6Q6mGgS110kmUVq/30afsX9CNqCqXb/Qt6aqLPLpLDjxVC64wAB81LrHINKN9D2kvb9e9QffplfisC3epv73Dx+et18SBVYoyAZSaO27JVWsYun7P3FWj+1R2cIo55IsGz1/CyUM95EAIVinY3AMORBsCR5BAi45duabofpwcRt50D00tV9PBOkxkPRX6H7Wd1RvxjfbXynlAFNiCLUsXyWhyXICGl7QI/uXCtQCdbTYgMcPYA0ZVE+r2vQZh/T2T4D0LOvvZpXSjtVndD8rmnaEqjNO370f+P4qj+9tgsWCwdIVE2MDQBTJe26mtTExBTUUAD/+nIEH9AAAAIjQNS9LKAMQgW6+qScAYh8h2dYgQAREorudwwiQgwAuNpgG4OhaOKENISLIPz91mpzFecUb3K56DiVdqNjgB0Y46tkb0evnsw/vnV8Tc1UvP8DKq2WdWI1HYsxFQcpUXZDggKttyyOQaalcSIKNyi63Z7Wjm9wdDY0xGJq5p5x58oe1c2ONzFd1q/03pVr9tT/KiaVnpcnEbwpuUmLi0xoKX/i4AiIsHTC/Ww+ABAIq4+VD/sNs/T/UJssdEK1Nyrc2nv/pc7h38EYMGHJqK0hwOyiHvBA8OaIRVT4Ab//k3OOf/8DxoHAAAARDAMdbmz2WxAYCCbTE44VwAufmffl0pXCPO1zbS3FnEb4YB8c3RQ6gqhqmBeUQfMgKtYhWUwB//Q8PuOBH//m7jaYgpqKAP/6cATjQQAIAiYFXk8YYAA/YVuR5IwACIjhakwMTcEUG/CkcYi+AAMIblv8YUCBBDLIAMLBh4gGjjgDQJz+IBp+H0Fz7ShARtPjKxqSh/UdlXLpy7lNSnWcYKufdpHsS1mswy9I2a2KrV4w59hFIEAwKGFxQ5duofegAIE4gGH4PvqDBO97hOuxTV2UNeCfEOGiJ+/DxYQ3vpEihYkmh1hsWj1dozv/2s6GAKNV0kAPgViqtq7lqYmZTEXJjMtEkJ9gR0qGARJM1UulxjL6VUslVu/GY5bzJmSrObpr83/6tzCrRcZXitVjJv/b/8RAAtdX01xMGCVk5qolVUiyjMZSKpM5Sl+aXLUralf0MamX6G2Q36+gp9n0+3QUtYlZaLgJsiWGCoiV74NLLPloVs4CPD3JiCmooAD/+nIENxIADgIQJVcLJhowQwga0mTCSggM11ZNGKbBD6Av9ICWtgMRDOmQNBJhpkQApwtaksiGgkn6axykdsjyTRsMQWlBLb4U90LS7ZoeUIOf+S5xwTxKWEaVsbMYYhZjFnGoR4vKEwWqgRhnncD0Sg6uFgUoXCm5ORBETUnGiEEfeVVo5W4aWqKatZjoxmmtR+V3+x3SZP96619NGT5tSssl/7MUKxH9/+saABnIibAUMWoy1gsiAVWWCh/KsQ7cUQMtKQxjOhjOpbqpZSlSvylaUrIY3+rcu6t7u6ZumYfrX6EO1IpVR8rywSLcckktkclA2ySa1tm5V/6+p+hUf/xSyFXh0KkS3MPy/rOHtEuYGeP/828p+u1giadYxosGnb2bXoHbZEqxT1tcxYTTEFNRTMuOTcAAAP/6cASTZgAIAhI11rNJKMhBSBrTaSIKCMTfTA2wR4EbICtplJwYARXyLF4T4Boq3ISh8aaTuuNtdJ7o1FGg98YrN2aqvqlivsb56Wq3+f+yrVnLMyf4iSge/QvPpPV67xf03M+/TmKaipUXLS8J8hAMllSFUVDOTpg9dFym4QnWMy60o++rUv1rGulvv9v/yp+/06hN0Zfr4Mf3TzonufS1yVN701yocskjCJI1hLYikC/rSVDmkFVIkOydKzNtWzd9xGPIbttX0peioWVEzpoyzt0ZKdWaur2Et8JSuKV3t9Y5eFHqqpyP/+1Wr++7aIABBUjZlogqNA9GZSZgEECbyb1MLs53cRg7sYlPd/MnOyUfvc1c+19Gq+b/T7OlW/o3IN/1vx5739Jt+SbjxDs6dYlSX70JiCD/+nIE1HYAAIIfH1jTBhAoQyIaommIFAigO4WjBKOxC5dqJbMJKBADjUdkEmIdKjBkjhAMofUh1la8uiIxXktoxjshv+ZVS3C2swKSLcRDcqDzeVdenS+xgy0/VkgbtJ0Pb1Utk+b3gQWSF1fiPUJM1HIZUNkIWiU8KKDbbVtKn2AeFY6uSw8qeLwDh19oSeLP0DXDn0F5AYQN88uQGvullQ0L0De39l+/TX3J6BE3ZtPdvrLcBLkqULghcmFkxcYExBAqFXKGkWjOx7XgBkuKPFA2EgKNxbMQl4m73RwjKjw8RlNZtzWKU6WDmq5Pvj00lQDAkOyxJC1Ola+MIocmbuXO2citVHhqZcNjgEminMMg70s7U57/Z6Ov/9Zdv7+KoZ4brW7iUqBWCZXcMPav93/P2R6YgpqKAP/6cATJxQAAAhtA2tIjEFxDYhv9JCIbh9xBTk2ZCMEIGSqJphRkAa+pJxIpMJYT8hm4CFBEJjEMzN1yoOvYqaNQ/S7l3mInOzOVkJ+a+36O2gtV//OzT7W9p6NDkgqWy6Gx+7V+Y98jTUIk7JXrrrHLgOeV1TZSEH04S/QECNw1ITChcbY95dw0uZISgCYwcxWIqzH4cpX3VXk6tI2fCJl7mNGR6hR9da2MHnO0WkvILgWCmlvBDiaLC4LajEk9odrVWySv3LBZE8kJPAyLC7Y89CL0FRRd75FFuhESMVRq2l/YhZ36GW/939NImi2SdF/Fv3edPnw2Ehc5hz20KrirZQ+GI2VyHqulvr9noyHdzb1t7TE/v479R0WJ1RItO9BZ354ld1KTYp36kI9qYgpqKZlxybgAAAD/+nIEM9YAAMISK1nR6BH8QkaqQW3iLgi9AW20sQAxFpvqCrJwBAAmubcaKTA7DocK5mvg2mWLrueIR/DgK98yEjJUZ3cr+ptXKartZ8yytQpc169Q2VSmTr58nrYcaXDOymZUq33f+objE7g1gqHhJJKIJHCnE5OGMdaYirLuzlGr3sOsKGJyLySt7IHKaVUmvsqPrY31L+m6ei/ob1/V/iD4SPCqfbd//rABTiarksabgJ4QMt0XCsPdbnzdzH7GChLFhhm3KY+erpe4V9FO8jb3NWQystn/+eWVf2lVcHUn611g7Z6+Z5FlS//TX8PC8tBwkh/BfRxqd/3odaW7l3ZrDvdcU5FmQyBV3eimNTm26GGtRP7HJWhhuqm/+7t5zXqk6tY+daRLDH6oXKIQWxb1effvTEFNRf/6cASIdgAAAg0N3W4tIAREBKt9x4wAiIhNd7ySgDERBy/3hiAGAAASUSpMlu221G22wACzCtwNOdft7Z3XHd9cESVYiuZSYrMGTBYuSSIjQB4nJx2HAzY3xwGJZgq1zwqKzD/P+p9wAQKTSSKtlt21ttlGAA1MrvYvpZNV9qaxr31TzkFzX2LK9zT8+krPDg78rbshHCJUhC4k49ooawE4hp41Ib0Kz//soGhNJVy2W1opQZP7s9y2szlc33DWjw6BBFlYqCq0Fk2JowjFQG70tn9Z6Xcv1Z0R9jmFTzZ/SBe3q3C2acppULpU1TjIMgtuW5+7atN0KbZloesU/V0ObwYEEY0N0LPMTpIiq6bQqMaWOgQ/rfFf1VHRXscERCxs/pAqEW9SMWzTizSoPkVNU5wMpiCmooD/+nIEnXgAAAIRE11pARDcQycK3WElCwixA11HnEzxEZrq5YQI/ggG4q5LdGnKAxledAIyEDqwTucv7JQUpgorWeERwoImQ62VaMfqdLdOV3li2WDr7V272BIO0HVPFwnuK7Q1hqGtlAAABCOcegCdBkla5oBcYUi391rbnR3buKmFwsz0kpV2+n78iIY21HXqpVZH//N/f69BaIk6Q69bf2OPdT/O+GsNTvoAAplJxopAAXETPh61vla0bjXvTUoszyoXF/nEHXehbslqqykK5V5lqesxZ1eyHJ+cr6s9kbT0dWq1W/qRSwphXe/2bvSoAFEleRQyqQMfWEy6tGJ8fJ5YjRWlXC/4YCF6+yXT6o39256IZUg7LZUZpjqzGWn26qnZv0D5K7FTpVYStK30Ydq1KpUmIKaigP/6cASNAAAIAggu0ptJElBDBYngceI8CIUBVMecTPEUIOxoxIg+BlkuwBtkgEu80FjzcGbsmX7mGEMV5Vya43n8iEJL3lKYmpqI/kU2zsxK1prSvRF1b8qdBLtdcJeLOCb+mzL+7+gwrIzMxJMDCckAKTY6GV2pfH+QMLJTg9kJW1wsCAwk+cTFaEiwVSBevtZghg9SzDoxGQ6IXS/6a/6Lq/8dqDX1V6Prq3y0AlBLPHdD4PVEKvcGkat77eoUGV0cEIyEUyCnxikO13U67lOm190lJahf0J5v/fRrKzR7dz32QSMJWMJMbusqHpWAdOzksakAtfQtvVXYiXQclz+W3otHlF+5nNkA6MyIdHv0Ds50zXnzTWWSpL+j9DMlf+EfVKP+012Q54ZZiKrFHNDF78umIKaigAD/+nIEjMEAAAINK9lpKBJsREcLHSTiS4hUn0h1hQAhGiAstoxwBgAAU2a5LI5ACT9zNiy/t0nMLpFfAkBYj3oy/AzIzrRy5ds1VVFb+ibKqN+41Rafv1wuLwle0yInirACmWmUJ9LPaAAQizHJZE4ASR63JKeN4WndC5sXvwoAQamZi5SK7ItEJEcy6Jq8qrXadVPR6umgvoyGsT/+NIp5NpZ7bOtiptusSLtSCpEZAQEMgHEtZMjgQtm82MC8s8vcx7OeecThKD5Ds1UaUGjI83071OVEdDm7fd1ZGYvwnULi6/Dup/KmJ9H+73EuwAAJGJSzWSQCAo5PAL3rG3X/fVHRRrEYOfVBsclyKljXVUrJc6rmFjZ42Rr09t5NG+f6XSj5s3dzKHEc66fMpkbL1dv5ZmhMQU1FAP/6cAS4XgAAAiBlWZYg4ABCrKtjwpQACLlBgbgigAkXKC+3AlABDCDCDDAKbcrV5kiMP/3fxoOLtmDcn/uTPf/57mN//mUM//2qTGgkf//3HCAOxLcxT////JozK9rmf////yGee5AxphACAQCgMCgUCIBIucjilT+r+Qi7ZDn/53//O5P/+SQn/+SofAACf//3EBQAw+5FP///4ujMc7tMT////+KZzuKEaICgAADAwEAgGAwGAwGAwH6UfE3pIO7h8RBmWhzjK16P0dl/85lZyf/soWoBDU//zCR2DwcUVJun/+IOtxkqOv///jonoclIYAAAAQBAgFAoFAoFAoDgDYJO1fRvOosyuh4U6ovT0dl/85lZyf/soWoBDU9v+YSOweDiipN0/6+IOtxkqOv///jonoclIYD/+nAEez0AAAIbHNrXLQAARAOLauQgAAhdBWtFtE6RDK3u6DOJJwASABrSMddUsbENFrLyshW1DPw4bgWD28JQwzMD5rX/jTtVFf1uFJMql3X9nF63WTuZlbNSS0jYnSVv//rLNiJ6qn1ABCADk0pc12Si3Vs2A8Rfg3+vgG4VagNEjX3+nyo/Vb9j6gkn3yPU/W2SqlawUfOq+eU/fvOiIOpVt8Kb2g3UHbEkc8ICQgKEjkvc2v7hQl2RSN6ifL7Vr7BkbEPa2DG0EcF7f19UxbdurKjt3XP0+r6CnW2SuZS0DCk5W389+9OWmg1I3MqkiOAKJMp2NSGja9RPqUP5LiHgxuI4ZuPqL4A2rcnC8z9+vW1H6e/f/oav/qWgYf/b/S1eb2pt3zX0Md3YK7j5sphEiYgpqKAA//pyBIEcAAACDDXeUGIpHEKIGycxqlCI0Nde58TpQR2vrahwnsf+NxUS1HJRiWJVi7QsdqG9uR8KAUlBJtfbn/oTgLy+P5f99V+RFEv5GUe26tpMKuSDbDri62lBzotx6TjP9FQR0rBACAm5JViFhehdgqj7Q6jfoX4s54NobqoxGtUEHuM20fUvz+jc3pyN+3v3/orbn0P/6Nalvn3xlMU609S/s/X1J5ABAABk26YZ8v6y5ehgqlli9KudLmoc7cSxqx1BXl6g/5XqfwcdW0FnHvbU7//p702TT3KlqY/rTOMf+4XR0vr+/MwgH6Yu8AIQQSAilLFZI9oR2c0P78lwMDSxWL9QXaTyWoSc18o3ft1f//t6EnZOhn08nel/Y++FGT3r1c4/z7f3/ovTR7o+p61Ylq8kmIL/+nAEjsEAAAH3IVeRL2mEQuSrWgUnGIjpjXWjHEsxHrFtqIOJYgFr8aJKafc6A7O3jFbUNvHbqCIICkhrCfLxsfW+dVxz9BsiE/otmZ6Sqq6nZ1HQWdONi++Xiv6zjOXltnT60oJEAAVI7aObYTugEvaKD3s/EnQIjMQncSf9tB33xAW/ndi/IIFDvZIECDdCq3pti5l60ve5rkzvZn04jbUlLovKP6UYRFECACmm6zs5gj6L80VzwsA/lOUGElRbxX+I5OjaD9ev+v+2rfdL9PlXtQdGpqTSt+rJ6ej++33M3/MinfRkruhOj0UMqIAxAAJct2WtlW8I2koQF9X424+2ISHH/xtDcFwXv16N//0X3Vbat1a+UGrDzlfzP0frV/1QrqY13r0Ozt7m3vO9LeUsrdlWOmII//pyBAS4AAACCTlYuG9RhELEa4oY4kqI2QN/oZxLMRYkLmhjiX5EgBSbkgMCDUoUJIynZbUTmqQcVgSKSo/4Nv8oJuhRsSxzQpxhzW5nTr/ZPd3Q1P9Up5eXjiR8Qv27WVXN1P2f9ckbqIFxzblMVWtrjcmAcZisbcSeQ5UMNUA/24vn531Ec3Qan6tH68421awkOCrDyZ5CDGgzpVyqVnUE3MQZEEYsYxDbY0rSJUlluomKWSNvGFmJwWc/j4xqnAv6LwPg+4Hzto2i8nX+ZW1EJvyfV1W1SWVOyyLILQGkOlxr4ul3Kvpa9ZDqedbZXgnahBKaktmN+2MDbbBEHZoY424qbhzZvX21FXI8G/L/Qb+383o+z/8zcsL23yyKcSyGvLtmKLRUkeSbGrbcKc7YKKUK9SYgpoD/+nAESxUAAAIaQVrRZxN0RCXLChmqPIiJBWlFnE9REJ6uKDSU2hApQAClm23XC07wpqoIMaOwt5HiEtmB3Hf6F+Q68qGu3tobj/58jNoDOj+7enpnSp6QaUVQQqfWGrECNAid8jneheRACEAgFtyQDwqET1WBpPKWZkbMBt49eJOUVRoCxMoVdrPlT9AUtvxeO6Hc7qalX5zfo3QuOI0+4UVP72Le93uT9St//WCGQKATt2GtLE/cBSXNTW9An8e4VaaIAfusRn06Jxn+DAO3VtG5f99yeK5rMranpfcObBhqOcQj7DlbJEk2rFJ+p5TvTwncJBU2/wVTEd4MeojZn8svwNwiL4UvFunV9Afk6v369X7dPfMZtRJtkonWKPMo2YquV9x4beEQIfFU7b8/0ouUe/QlMQU0//pyBMu2AAACHExaUScTNkLqK0oco7qIbOVjRZxNkQ+crBzTiloIMQEAXbuLndEVfAbaTmdd8lxPyvhofCNp0PxPXqJ7/0f/9de0ivErn6OVUejhSVU+WtcF7ytR6kXZUWFe96syId9SAAJECCpLuEqzsdAHONcU9C/I8aDKyj8MdeNDdQ9zN31N/IG/zt+vy6tx3JE881m3fKiHYdF/lV/+f7ylr67XrUTeTWAEIgBMkuEnFVDLh4ApbEitbqQ4EDsfbKpxP7ag3QRuV9+J6N34ZtW/Y3nvJoX0paWglImir5ZjJjQiOcg6LgVcYdauGJCDktBk5WikVJQpC6bUG1lLU+oIIbNCwkIpQAdPxODcDR/UIW0bt/BD9uXr5N7LPSpeaTuqEGWGyybVj2MCDW8z/2JiCmooAAD/+nAEIbsAAAIAJVg4zTmkQmgbGjDldojdBWlDqFhxFh2s6MOJugUAMOS0MfJIBs9Aljd1hjT5uwO2ypbUhwp7YnG1xJB+Rx0u2Q53Rv5Xocp9A8VJF3quYfJReImlmO3ku4pqvWiAJUCAHJcLuQ/0FaAGe6BCUT0Llo9wfCxImGGqET5+NDeBP6N/To/X+rdm7tTqb0+iK0yPkMm8o+tfqPpSchY0a7UIIkEAk2pAhRHF2oEzmgqX53I8qMIxoxwj/lQxoCC/fGAUrZjbltX6Pgm/Vvpm6v3drzU6irqErOgMZzBVKxVqpAupalPbcqEOQAU5buORbarddBXlOLyhNhjjvC5bEx/I8x9SWJgP6viYGaNyrYdtH4T/foV4J9T/dleyG4N4M2Xv90gdMJJreATyBiExBTUU//pyBIiDAAACEDlZ0UkshkBnKvcZ6jaIuO9c56hNERwgrOizidMBIQyApbuJVHGmomGllBkU9tB9xHHYOiFeAD9uK838YD8J9W1Hcn+2v7Yv79v5h3zb2ZVi5+ve55m/dVR9BPmB41kBAALktHxljwN4B2X3lJuPxI94gtQuT6iUG70O4y5A3EN28l5f+Ly389sz20mTfk3z60eXaZjXi5d73ucrWnR9IIQQFJcOzN0iH/HH05R7mCR+s/hV4IxPVjBEPjdsONxT4X+huE6eXv/Q/um9lJr6teiLZso+GHsbrouHhwmwCKMsA4QmiaIRCEFvX8TSmlVyB+KkQ3+kX8RdCj6B779eCG1G6cGuoLq+rd/6MkIdke5UB+vXfoqrh31ADlkoLL+cVj3HQjfxk4tC/rD+JiCmooD/+nAEQesAAAIdONlRZxNmP6cbKizlRIhk92dFnFoxCR2sqIOVugAhBIZdu4zsIj9xiLs1Q71Jcc4pDaxMHcj18hmCB8VvovP0bpy+/Tk6t1bdu5E+Cey7x2s15tMe7/Ux7bEclWWOq47gAImEIO3cdqMWU1wcNZQNDfJcBrFBn1BnxTl4UO4o2E21f+nt26/zepcq6G+j2rDjwI9R3Min0LWsXdefU/JmwAytRhKcg19pCfRIbcMDvyQl/ZzgbSPOYG7OENmjflAgaQPHGOxRHLdQT685tBv6P3//blbVDrpmfvRBcY6Vekh83WgEJBCTu3ENCO1QBJntQZ6+IvHS+qaFf4mbKDXtyof/PbGf6dX0fobRsYXRtkOpkUxjB8NtabJayRgxjoKhpgulK3piCmopmXHJuAAA//pyBLUXAAACFSVUHWGgAEEnaxelnADI3ZWMeAUAESAyrw8EcAAAhSQDjmU67E9ZfG7KvpHqAC+g9+H+zEO3UT1yoQANaKzYF43TTJfsfxYBvn1m48+gkyY/j8gyjz9s4d5hv/l6f09cACAnbuIe9p6WHAIVtAPg1v/zxGMoKC+pbPEr+U8v/HQ11/lPb+p/fm7p0T9vRXyL9m/3f1zFj/7R69DnNJ6rkKwABAMBgMBv////////2PPn//8wsBONgIzD///x4BIFIAUCwTjEL9P///AoBeEg+JQvyU5iQVP////wvCcjH5UmFgmVy5CF+AAGBgMBgNbwUEeYeSJ299dP//////zz6v//mMC8kBcw///8aAsCQAAWHlRLT///wGCQYcREsijGDn////4kEzyanDQ5XLmiWmD/+nAEuZMAAAIhLl3/FEAAREcLjeKUAAhc517EtKaA8AfvtGCJhiIDNThmNyVkpyRZKyZgofcolzNhuojm/qTm66OrTIrWZWt25lb6p40rRlmqeIyXY1h1Fm11Z1qOi4saWlyayANFmlq1UkhCGSxsgEpWVC7iaOYKbtzaB4FMikSgmXP5NF76GZpjLtVv+ZW+NTqObL2+LLoLxGSbsaw6ie2u7fRlkLTTkAMWlq1AGgkFujVEQI+9a8MBS47iPEG0BUwBL/HaArZeXqY1X//Q38raCz5tTeoqbdKCyprOhR4keKEtI3xkFfLaypY9anolc2xVG2koiCX0n0Oz1HrAIN6XC8GNqNVXunrYhtDosN7Ii699i34CqUe50KRJktLvGQV9WsSgI9aRcuiJUxBTUUzLjk3AAAAA//pyBFxkAAACEUFd6KI13EOoGuoxoloIiNdhpYjsQQ4ga2iWlOgkMwVtsFxluUmH0M10GNjAF5uJtjBx8G/24v+jcn7GpV7zP+Kf0TVv9epf9tau7SaNoJTu1s8AXPDUw8SfiVs7tsTACIKINZl0u8+eQc4Bo+d4fhW78fOLpGUy25P69X4X36cnVrTpnvR/eZ/d+3/6l/0oaqFKJAtfy1ySu76pUNOK1rpUbAABBgUBNu28IqJlW6D3wFQw8csoniEXrEwYZFGE/QtqPfynt/K9W6/2X7sz/rZhyKBV7pFeYuoRWtLUcDteP/ylwWoqgAAFgGSS29SaEs0CYEpLDefFHtrHG2LEdaCFxQ/8E4h76AnP/V+XqP7Z040e7yH2+XqfZHVv+Ijn76yb9CveSs/6OTTEFNRQAAD/+nAE8zgAAAIcNda5jzmgQkga5xmlOAi9A3OhnKoxDBwt8BKc9gGIAJqW6fJmAh7KImtNOQrNseS68YD1UTBl6W06C3Uj35T/9f9X/Rulasn+pGhw/Ci8+x79kqBy5XpIMO7d/hEQrTFAAkNViW+CqHnskBoMzJZWLX+RnxE9RgF4kT8YC7gO2JvhQjy9GmtenGm/p1HM///t/8/hNkUa2sl0X27Zrv1n1GukEoQuFtFtFqZSC+lNVC4xqzlU3CgaoUJmWE+Zu3J14l9NerdlyM3z/m//Em52/u960HMgFDO57Elq8+TO1WIY9bwELDxkiwSCYGa7muykbh8zRo7g/M2N8HdpXrxUE+6PicvoH9OjdecqOy/06nr/1sUfQi5E8zAdDn7FRrLDbX5LEepN6VlF11piCmoo//pyBIR7AAAB8xjaSUY8nENjWs0Z6zAIvXtrISir8QkxrKgWiPIAIZpmReXqNqnjO6gyb2ehDxKGMwEOTZwrdlf5QK5FRunlHT7n8JaEP87JqHdjrEUb8w5V/f2kxuPF1z9hQqAAAICEEUk7ewpQSYmNBurK+KzLqXfIizyrXAjgaXZaD51b+R+9hp/S9VNbu1yeQqOfamKXv09lXjI7qhLsatf2kaJIBImWrmuSURVwDUKpfTyLk45lCTjXs+vUDz/hb8n6jLZqlVm+noRm0/1aqsyD09FMrGqJNWSMf1Yu+jLT8qb6S/f6+0ZsTACojBLSkpBJMjIOx9KP57ZuYPYz50eEcghe+rcfo/P2/xv6/0+//9fff/VnYt0/7WV/sv+3Xr6XZSXsrXq+7SlC0piCmopmXHJuAAD/+nAEydcAAAIjXlnRBxPkQ4aq2g4FPAhxA2dBoEaRC6AuNDOJvhAzQBLckt3fX6EdncQQ33S8U8TtlCPfp1bUh/EPm8zp/r/ULogc6N6/OiGUzKRF+2rzDfv9n9lpr9Dt7lb3btsC8FFNiAJAEA227ZUSzBQMCKcOpky11o8+2Ps/mApUmB+j6n4F/qDc3RsxenX+rdTFZFXX4k0aeBc0nk0q+xlLWa0sUz3baCwAQCIJSS3aXH5Q9W7QN/4LhXwQt6hv6toXmbB/77+r2p/s2oE2TpqrkXV2Q6P7FNuhxl77irBxD63JRjHjSrlIk07CEKITaKBEbTd1pbqYArYW9H5vKl8TE+3TqnO/gqDw1/nltE68pP0ZNSN0onq73n0/3e8FfdzpLvqXtvWpC8DCMohth1MQU1FA//pwBFUsAAACHC5WOe0rMEKIC/0U4omIqQGBoKCocRUgbGhnnMoBAAdr3+FLWG8zaMFU5awUBjaqR5F1GCVEyBndorzNqGaeN69BPl3vqTt/Gs2o5S0I+QT2nDyg1kVZq/lkI7Eb7paquxwWyxt2Wy33C7rc0cguZd9zcUl9CXJf1GNgWty+onr1bQvb2/Pbhf0b4z6Eoq/yzOoLYcuazsqlmWd1NBIsJmXz6XVBXo1FNtdvFJRs6wGL6G4zn6j6Ibr7cfycYO1A3d8KH8trt/tuJk636GYr2nZU15K7IJhAYOJ0mHpuJMqUdtFn9XVrxeUJbAWpJbWsD3yVZMdS3NbdGsOPcKjOi83+gtzjy+ODOr8p7VQN/7fmr0JLZu/MjdkTRenaY9mHG+pVzl9Yietb/9ximUTEEP/6cgRJrwAAAgw52dDiLKxECBq3JeU8iMkFTmw0TcENoCuow4m6ECKRIkpOCaiUmAIV2FTbvxXxTwYotRa4kfBl4E5ug7tydH/r/Q3p31b8juppRWalki6wXBObfuegGZlizoITUyFAFEAJuQfSp9tIaoFxBtWtfWZ1/5d/DYg89jHmLNBunGC/CTaPwTr06i3RtW/U3uY0xtPkfZKknXKXrQJMWUu1CavxabABUWw9qVaJT1qMRUBTfbnvE1DNaS+ROGwR1pgwhfGrLmxO5mU84OPocqI3Q6Tbvov/zK+h2Svf6eSn+xasDjyNrMA9nkehQBgGAo5KNmhu9AhRATt2N5qSPM4mGcQhQamD4sGr087if+JgDee/argz4Xdm/s3Z//VKZBdRMyPpNtDO2fJvZllH9aYgpqKA//pwBAB/AAACE0FYUWcrlEKoCtctpXaImQVnpBxPcQwgbOhhFZYQJsAAlZsM5TNWHiVtyVmXtclgrxATGgvX+r83XmHaNy9X/+nCzIsxzorNRvm/O/yNK5GGlDbAC68PWlRrmuFCrMRQoQBUlomChcy4WCx11wt24/y3kQ2uiEKRk6zy/o3Cur4wH5OvUf/T05+wsU72aX0MW03snVdsZuOuuaZFHSCnrdYSCIDMRSLajkG7FWtsAtd5Ncmju/JhyIOs7GgFVbyozicIVxh6g10A+74Uf+vXt7O9bb2rXbO5v0RNlFDx63cXkclUmlxHUPhWAW3IMQK57vgC1pPGNU3Lwz4UEwg8HZfofr783P/GhzkLoXvoTz3+/wHI6KzFIjXzOeZIcFUhog7ScMVq5uLqvTEFNRQAAP/6cgRYEgAAghE62FBtKfRBI1raGe0yiMEPY0WcTfEUIitow4m7IDuFAGXbjCMIOOQVrHkh8Zp5uYcZ08nukqMDX9X5OnV+/J2H6i3L17mTglLZk33F0OcUO4YgoVCTqJzEP3PFh2RLIAsISCpLg2gUpOByMmJXCJR0yhx+apZ/MhSfLr3X5S7848VoqdOz/E0QuUAns7idsY3CNRpHylw0JC70PWMFIJrICcAApxyDV07U2Ao+8iLaX5/GpfKmcv16vlBF68qDdfLtQEI1Ec/vh3LoZ4RWydHEdUOAnQvkU1zIISV8XvFQvMQMyvvnWMABcuwzrRGnIdgEVtPmnsj8LmoeaXxWNuKv6mcadPBPp7am7e/9uhq1zdI5DaiFIyinPnBGdjIOW80F6h4GiXdxv7ma0xBTUUAA//pwBFG3AAACFDjY0QoS/EOnGtok4m6IiI1UdPaAERSa6+qScAIUN0SSTHINpVnRwAcLAeCZRS/IOLhssXiitnzHzG1Db24/Tl9v6fwq9Jpl7Ud0FvqhgA4PHFwM42aKvbljiafIi3FkAmAACduw802EdJyCTUbB8WtKcaPjj46McT/5vEv+IQdd+pbUR/R+nLrGbTvahELRFUENUwaKgYNkH1YoB8SXOQ0KdAAKktFFbeWKvtjUE4msw3ZDIlM/UT64kyCCY7wchogpuZc42socx6kua931nqOlxRySB9LdRTFmpdWTNJQwec2Bup/9MAJDEhS7cXq6ZN4UFc8y+eqZ5IzFQw54rGvFD/35DpxOHNm/4t7to+zZh/s++dfMNKEBGIXBgy9JJGmvqVNE4x5hJ1s0lMQU0P/6cgTccwAAAhkvWb4loAA+Zfthw0AAiKFpe1wlADEXJy2rktACABAAACAgEAgA2cYSkNDzzQlGzPH+WDd0VCfAA8XLEC4URJy6Nnd06knNij9vPGlJf/6RxNBFL//Onq2f8KkgMEv/9YBJDq98orlaEC8784LscPC6BPXl9ZmLmJk09003Jo6YE8ry+nN3QOGSi9/T8+klS//bWW0QVBT/ho8eT//5PgBQSVJJX5XAqAhi0nawPvB0Nkvo2o/6sqotEKF2quSau/fsVqYX3Wpxv31/us/m9fc3vboy2ttsuY3/X/1f9SA46sk+mAAACA46I6hZq0g8OXM1O5Za9KVJUT0AXCSSRSnDyrpoL9q1tu22xhTfm1qNn3VUtax+EwKT9X/pf7v0///////ojiDv+hMQU1FAAAAA//pwBFr5AAACAlLfUSoR/ENJy/0gLaeIiU1o5cyuWR0sLzSQKcbtHwklJrrab4lxJZySWHp+VbwfO60viZ3qKRwZHMI/jUf26CsE+2pqLryHvZL5H/rNQWd6dUo/6cz//gzfzVH6KiEkgGkXPbJDcXsOVRA5f1CnzjA9rMjGBG+pPNzW81jX7/bP/mYbIwVJA6MfRdnSQRN37a396taO399qv+//rG13+SoAAHLbxsxlKofxwS3CDsgT/y/NiyEujjZNjUM+B9OO7UD+vDB1X++bnH/UqK+3aTI3k/94gd9P///6t3cQHGWTbCw9zpIIaARCNkFsj9XDpulTPtvecDJwF5e2VEM+R98zmi8uDa9WzP/mSMesxMs5rkIIVuZ2q/9l/7Z3////Ibd3ZRGBtT+P/+VMTEFNRf/6cgQp4wAAAhJS3DlwE9RDKxsqPweAiNVNdUQoTbEVqS6odpWSgygndvtvyzkXrHJS0TP1Y2cOhGKy7VIayAau+pCXRxaL1AelunOTBvn1JJulJKI915+X6tFt7a/1ptXVf//tqN068AEAACTgdpEwxRciDrN7FPvTlvwin1bUZqo6yR7ZZUCj4id8b+rAu/Ed6MbT9H/6CxvfT+//9WzH//////l/9/+VLtmBKCBUktkxi5Shk9sTZAWrJM9wMCc3o2gktTppRRM04p6f6nE5B6kbCoX3q1d17Ot99ZKIfZv10R+mtW79nBVFp6SB9Z2TwF0AJTfj6Ts8QuxoiUErOl7uYApLaZefTimjo4A6cQajJRrbHVYP1aoCi6O+5BA6gz2dLX3b0Rjx7//////QLEPzhr/E6Ygg//pwBDifAAACG1Nd0EY4zEAFK60sSZuIqTtoZbSuURio73RwitbEBAkVLZbJYTGwPLH8pfoW2QFHpyh/Jfm2vQd+owMupk+7dS7R4kzroWEOps3TRzl9Fq3/Q6pn9KP/vpSzejsZ7cv9MBCDABALkldlncaK7EDXg98mXxRECf1o/G7b6O7Bu+CPRm2K2iD4pnO+wmebqkk3jBbUgVtS0vAXSxcC/K8n8mQXbtx3BA7gUGpBUNrIk+YJ3LzB2KDPaMB9RL9rcSJ+IBsjOr+5R+JvIRruDD00bWlyupWQvZfnmQVf/+v/rfVry0AtuLREMVEolK266sWNQujl445koNcfiAJGsZpxtkvk1feb/+QF2sRGoMpO11ZSsmNRHW1/Q6uzSG09Sm2zNStv/UX9AT9lZ7+pMQU1FP/6cgRySgAMAgpO2hmrKdQ+ScuaFOUfiK1LZmY86tEZJ25oEZwOACd34ssYY+sNCkZBiio5e4Z1lNiojLTNhugrpz2TIL/ij//vq+/hg+tPkvWxEO93/Sqd1N9L9C7tRUretdkB0Ro0AgEAtySyRQd4gLqokUTQZzTwsAW9np4jo30dDAv4oSeRap6aPp4lTZ+22hUTzfo+bN////u6/CIQfUM24nACk24n8D1yoPET4C7kJ5+3o7VFJQsnk3m1F3DWUap2jyo205PVv+WzH/OJP/b+xI4opypX2aVS1W///13R6UVEFSfqWzewvgkJyy0Q0AaCB5QFtz2iEAbKIcA57G5NM7Tv2UagFaNj61vWnQgbUXZngDFr7vMsxtJhxxhyN/c9Jx1Eb+v/9v/UMN2UpiCmopmXHJuA//pwBEMQAAQCHVLZuWk6tEDjytNhJ5CIFOtmZbylkQYnLWiWngKgAAcu3GzCSJoTwtbwatNDnOklYT2C2u86p3DGjZHV8Y/JFpv16Nu/8VMiKqafuZVT1Svobj5bv///+1qdkCFnp5YtUACjBB+rrI53iSzNI1PjzqPcT/V1qPcXfisPhvXsg1yVB8NNhA0o9Qy5zso8AToW1LyubREdt9H+QWOR2uO///lwEZd8OZDReCNrOJVBpre7/QPyy1yE3vMVr0A+rY/VyhABUlLUrf/lxr5U0s+pf/JlMv9DY0fct97VvsSJTPz2lEIFAAh3f6+PON9YdXiBHpU/ah32ViNYKuvqLGx/R8lrzu2WIV9/TofmpqHlk0Nrzr5xu60enukV876f2+v/9C3/SmIKaimZccm4AAAAAP/6cgQQrgAEwhc42bnyE7RDByrjPiKGiDTrZ0ZATLEVpyyM15YCgAgVLvh8YOeBcgLPeRCe1wN9qZbk1EZguR9qVazVjoxjrVtF8EbTq1f+o+UXm8/3m201ZzOn+LnEFr2i5wvXv+vsJAAp20fchMX2UmlVI2DlpRcW+32rF8RwciEcXzqacoDItjMPXydW5ZdFXjYsTsnT04/8rf9fqZ2dqfUOrBVW7/Z8R9MAAIKjiE10eFMJVI6ywv+q7pwk8sohPDfnVbPwHtVQZHewH+Jaj/69H184htR2rNU7juqLU36pIJZ+5gojYKq/+SACt24tGGPSKUDZwsnUTmqaw7pUCGz3dowHfDW+bGvQV/Dj0r07gzlGh53U743fVmtuizKi0r+XUUShv//7da9rxoDcXD6YgpqKAAAA//pwBIRbAAAB+znZGTArJECqW0olpWSIfOdo5pxzUSCpbRx3lgMguXeD7QKm+CgdtMt6XysetA+EGKnSY1X0O9Gxy1JFwNr7wlatt05+TxGbXdVb5pWs/fKiDDijapYDNaJVf/0JAAAAXNvx9oSl+qyhiDGVPM3zrDUEuUmmqLcO9vq6nALvjyRj/bvy6eLK7vT0Q/Wvc/9sxbd1X9P2Tf/2FW06jIIoJTf8WjDGyhHGxqw20jF51W6IPInTJ8q+oDtGz9SCmgzNfOf/9sq+viRd+WZfsUjVLYxcz9XAwEkT6Po5OmfFb3gjQgEyb/iZBouq4oQT0E+VErVLFRN61biXHZbK8gA99hrDBZHdieV8f/Dhqe7vMhEQejU6pWcrLOLXf1bV/InpolP1LXRImC9bTEFNRQAAAP/6cgSstgAAQgRS2blsFARD6ls3LgVkiEk5XmecUpkOqWzcloofgQAnL/h3AalkC4sNkif8Xokb2mwIHld6twHVs2gykA9HwTyX7e+z/wVvt/uYrotmLbYOyjOZqdX9PqWqW/+X/HgACXdvhzjAs1XH7EFqHV6yQ1HywBNLWIa9dB2Hk6OAXfIG0daU3j3jRbTqKTPI1plPVe3pp2bEetfSrK36vZP/RP4qwATt2HpYPTHmMahswUO7bGp8Rf7xAqiS+jnR0WthM8q2GbpzfmeZvf3zPv5LuyOrmye//rm/1+f82nv0vXeK1Ll0hQCEnJB64Fp9O0MRzpEG9Il3YWPgM777PpvUexMdBpQvfc1bbW1JqXVeP9//ejE3ZyvSu2/106X5F2srybfpdwg7GT9TEFNRTMuOTcAA//pwBEqJAAhCHinXOew6VEJKO0clBUiIgONYZ5y0kRacrMx3lgIBAASl2HvkLhLSmefRMJQg6E/7YVk47Y4G/tetnjHgzK6E5fniXpy2VN3t/Xt5Jv3l1gl61Nlt0owSGLFMqIGRrrolooAAXt/x7sKGudeWkXwRWwX+EAoABzaheT82g7FX+KtX/nXg7sVeNXt0rurT0JOrOz7tMlP/X2SpHfq7ZPpfoHkIkQAW7aPiEA7nRMH29FzXTft4tfwEp+xTFAOiP+xakRLzg3rz4yzRSIFpztb/0wt/5Wb//lRyd7viiyQXboyGtod/Ma2uAK3/4vAmQicZFcbZrR8JrD6lkX/GXTD/H98t0HUAL8z1/zHfEwNnO2UzOzsVHlZk2OYe21PGPMmzW17+pTYirQCLD16A0mIKaP/6cgTxVgAAAigpWj0k4ARCRlsDp7QAiMDzenhjgAEWrbEPAlADggiJt/x42CjNFXnsa4w/9KOVMCAffN1L1LP55104t/PZn3zXueQUxn1TYqToepaHpFHNSoUJODwYocoYnd2CVsWYtUobACd2w9YZMnGkyXVkVlzdW67WajsSJmHKAvkGdbLN0GQHqnOpdNJTUSRT+tXX/WrW23v9df/TNIMFF0xGz/t3en0ngAAhQKRQKmxp6ZW5qeb2lwWPECoZLf0////f//srP/54+TGhC1VOVP+fMG43cgY04uOsX+XSQE8Hdv/LqAYfBByIVe5xC8ACAVCoUiv7/5mqihByf//////4mdl//vExcAAI2VTKn/F0FADA4uBA4SjlTX/8ofc6EOeofX///+LyEOc4cEcMyn1rJ0qY//pwBOpuAAAB/xJcNzBAAEQD66rmlAAIxH97IyCjsRIP7ImEldgERVA1O6fYcMe5Ls5Vx0TwDCRLwIDMr9ivDj3L4pQ3Y/tJICgK8O0StGLHhKRIke6o8ue35qnkkYlDV7uKrLMVAACJjbITlaylJAmOhbmiKITAIipmGg9R+suFvs9bevsv0H+1CBwK89RK+o8thFPdUeXPb7jVPJIyQaU9zMQlQEeFKyvCQJNejbWaE1Ypt7euSCDLNqD1eiMjwEDatmRlfP7Rumg/e6uDQF2Zue8FZWe1maiOrUVU86sROAt55EqWahCjx09eu0SuN9L9dmYrOxZlFI7V/Otuo6OMoHXv4foQgticqrb03uB2yimEp1eQqcsq0jAbZ8RP2Zue8NSt6KzNRFVXK3uqr/To5760xBTUUP/6cARMZgAAAhdAXTjhFkRCiBvaFGJziKTXa0Y8rJEWoG/0UR42FCElbbtoLjxOcwpuqqSjgBt0ULhquhnpj7VZG4Qc1NffyX+/w/m34vIQMmrrpp4IKmPOSDnKHB1ZtCY6dRY1gaT3WgBJKEU4nLx7haMNk4eqAQjR4AlW9x6D7a2V7G/v0/o3/7Vp4s7/f0N6b59W9IIUAH+iVRERa202IjZW5RRxU7YtsngAAABUckmVLhYeWP0FG5ZDq5k9inrb4Kk++1vQguIvD2vpvMC9WqO33byftffzL9Lv/eHCrIEhz6FOyh+Xk9zU9V//4egAEIaYZdqcs6NZ2JFFxXAoRcSQEKeNUgOPv7ojK9y/xHR/mQ5b8px9WZDE5UNIbl2//M6zv25Sp/sNXpVoRStvequUIcmmIID/+nIEGhwAAAIVQOHQZxMsQegLVzzCeIig34WhoEyxFxwtqJgJWhtp/Ldtdu+OQKk+VreMmzYnDVdGOV5f3Sugv8GX3d/b/mb6+8eu/9353v/nIuCZ4gqsSswXUp9Dzgou2t+iPIycUHwADKjbk+GpX0Khdumbc+M5R28AFRLvliN//wvA/hfPH2zFW70De2Xp+yO/1U39OrM3/1fR+tvu3Dfxzb2d29v9sUpaCbcaUtsl2uih1y3ij3CcOtIYARqjspB4b/rUobpseuS/q/e1W/K/qy2v94gfOEHL/bWb11LNzL4MDW0PZ1vNmjrpOUgABAEqRy3xoUWTALEQ5C6yEwsNKMxWiNGt9jsPM9R89/L+hun9H76uX+61Z3Yv+9eg7b87K/70TAvWXBVUSZal9pkVfcPTEFNRQP/6cAQlXAAAAhgT2RgMiFBEQrrTBy8MCKznf0AMoTEOF23oZ4lSCB9n/L4gm483wCT58lYM7LNQeS2itEPyJGq1ExJQ2lI9M2S5CxPVDf3MthY5iLlg/+hS9woqH0Uar319CX02/5+lSwAWVBIs1U5GOqigLZ0tMHdjG8xIe1MniwioWrpIIuSrNWRldKlaqkXXlaf3mWDRppLwZq8309G78MS96tKtEt7v///6AsFrLcskmpsh0HlEtGHd8YC0vVRLVoWelWTEw5qjVDd6W6H61fZvn+S+1G+bWrmHqLY5YqTWEVP+qp2IvfM1CgUjkwysagAEAKVkt3xwLACFrXq9/VlJR+4euUIISjUs2rVfP2wL+P/5mc3ujs/1J4+zx8WW9F6SoqHmVbjWpT/7GVT15lwenoumIID/+nIE/gIAAAINQV5QZxNsRIXLaRnlH4hsuXtADKExFSBvtFSVjsMAqCTkblzqlKwTeyPhstVRQVYpatGlc19lZcoGW/Hrp+jfav9StuHMv173ZXcrsyo33poPRTkW0W+S2PsddI1PVAAABP7W1uC8oITt7Wk3wp7rglE/916XyDqXIOhY6Ii2HHc7KgF6NEQlZlZ/deqWc6/lXnFkgX11nU2ylguq73/p//m0goBGkpY5djvbvtMkaJMUQpZLoplLDHo+ytqQH/RKU39X63TZ/V24k1VOV0iNFIFsLPbTQ5RJDoBFEFg1a0uvWiRevqiABAZZjdjs0igaYjpK+noRmdwwJGoCzO6Do1pdB5SWlA1kTVv/0vbqT9W6kX9/trTT+hiDJBFxoxtLw0r+yjz1yS7HJk0xBTUUAP/6cATJvQAMghQuVxsDLKA9wttKPSNyiFEDaGYIc9EWIG5oZYmOAK2mw/rCG8i1SSxdjsZUg4NeWsN1FSfkDTsb9bEgg6PEQIPBngEPw/KlGUPgF7xAd/5PX7/s7VUcXWK/tT3Ox36O2AAIQi5JbffqmYgactfbk9zKpcbBRW4u2XFsSpMbwbBl29uNxDvSoe/sv+C7oS7sNV2mNa2htblf1+r/7vpBStuwmS8vc+qMyaSptqPzY0CDZ92RqzseisE3/fR4ctlR0Qb/4Prp0T0J4cr7/2ksqVT9hSWICdDir0DnuTjuStiUMQ0pHJO3W4KSyehTulyLXItTV6D9Qg1WkLcgIqPpb5Ov6HPqkmz9WN6GOsrvXpAnmQ0rXPu9qoUGKQZJW80GO2/u/3u6ExBTUUzLjk3AAAD/+nIEP1gAAAIcHV44AzhMQ+OrjQliHQiAc3tBpKyxDI5vKICKViBlttJOaWzuPchQN6UL0eisONZ6vVPuJGyPOLf6s5qORA7ZWOGgM+CAIajiS5/8Ph8ufBAM4fBDE46TqDDKwffhgPk4AAAGUW/pLvWIsRDZaOUZhvAYPcokQAeOuoANtR9733/MJoJ6tGc1HIgG6dDhp8AFMoclz7OmfD63zOHyGJ3+c5fnA+CCEwbrZTn1TScohvfpiaGF4lzXp6So1NXy31DgF0NmFQ6s8P/I8K1BaCvkqFP6ysOrr+tEZQbOuTAz1HlOUDYoFXSNOBCbAtxlObb5VV20bRwGX3JGTe1QALW9d5hOjvQffoLVCwdbhyoWhryUkp+iVg0uv2rxlAdOuBqDTyx5TlA2Igq6RpTEFNRQAP/6cARF/AAAwgci3lAJKEw+w5snPMV0iNjrZmS8qREYoK0Mx5WOBGBMouxS1PYLVk6EqHSNDLEcLejoJMIlHQk1H3v4/87/+wk9Yx4GK5bfZ4d79QFAViojyRUYFTLx6zs8OalX6zsBAE43BPXCGMo7z9aIfdNeY8HUcFlciYSqvlMggKaDt83dDgbx8XErkXqNLnwoj1HrLsOys9qllEdehbv//1hJzbYfSqrIjTPZSDfeiE+hNIXjVMGNq1bKItOOjD3dTI8TCHXER/V7evt/KiRJH1st2buhc6RgxjmkHqYVEohFEfv+5KrdQJbbcE/iG+HI5xdz7GXY8EqXmpJws7IyoyXjAzfEb6IHbewnUij9NlPqO8vvVPVV++uHqoJO6sp00NsWkW/0osIhqihx0UTEFNRQAAD/+nIEZTgAAAINFto4DDBkQMMa02Hidoikr2bnrK5RD6AtKMKKWgAhk278UIz5VFaK0KEzvFHy4Ev02jWx8Z5cv8TnKzXh1tEvb/KKcdsqzrd9wPJFQmMC0yDbzALXGlLvkE0MWJEvQCS3IB96uz6VCISc9eCs5qG5iXtLrRwyCe6fZeh4cd3o1VZmOfwmjD6BjpdIZDImufrP/xLUYd8FEkFDRT3B1X0wACUuu4+1xSEiDPj1ojbN+WSsIhzV0MKCxaWXYuvS4rVXkAnbGkYaqqzn8uiI22ypUWMCAsPv52XvAup/3bjYosvOr9S0AAkCb224m8mLgdGKtua0glJqYO17zLrSedH0VFfajaM3UHbvjb/1YclTRtqJeFZd3cldfarcXRrP9PDWftLNaSiDrSmIKaimZccm4P/6cASR6AAIAgc12rgJOGw/xdr3YSV2iQB1YHT0ABEYHu0qnnACAIEm25AHpuRCkflRPZ2SfXJrHoFiDKyZiNTN4bOZalBF14+bR03bomrJ2/T9tv+XaDKhvACUh8XtRFmiCqp1ltQBACnZaPwutOp092mwqQco6OgoHR1QhxjjuiIK1M161whf8d043XIC/13v/Ttf/lKr3HIiP9Rb4sbmdWpGhfxoRct2HynjohkZJQQ/TGhVmFHqLS+Uh7xQTBAohiJoMQ+sv0ulFCK/YLf/ofngTnpO76XSo8Rj+JW7J5E+1YHpFLDLGllO6U9UAAIgKXb8UzVZ2btnN9fVd3V0CIMUMGuPlBvKot0Hc7Tp3ni7rodnTmp183S/v/NvnTf5O999tMaG1HxSQDsVLKjEDKE/cmIKaij/+nIE4BEAAAIoW+DWFOACQqkcF8OUAAhIP3RcwYABCI9uB56AAAAAAACAAOBwOBwOLWoP7aqsE2r//uuv///5h4+ee//8xhHQbCR9f/jQdB2FwnMOId9//8H5M89FLE9P///8g6nqjMPvDAAAgwMBgMBgMOlH21Q9IVeLM3oZf5pyf9naIen4iNc41G//UofcwcP6//FCgcJg8wp33//w+L1RRx7P/yg8LkAG8CAUN9BwAwaDuIhKkkoeJS9G4VhHLZqGYnI65GGB0g9wVFqCLgETWRCT2HVjCxUsP5HfbfWYiK9OVXZle+mvqRqv+p4zwC6ByKAl50J6OpV94pUU/etofSwoVKOFVS1cyynJi/9pWLu/vnaW52EqPPOxMVEQ/kc9b1uiKPTlVlmZV///v+pMQU1FMy45N//6cATHiQAAAg07WzsPKGBBZzw6HCd7iLDveSOEdzEYoDJ0MR1+BAAmm7bDQicq103oQrmKSeNMweinDtfKKGUIJT0VSjNeW9EMn9/Rq3tfXs/VkRr5dzU/3/8VRxY6h1r4dcv7f99Ai4ttyRpyoFC6WOc6pvgRe83w8YEKXnE4J1ndMFz/2ubVF3//N53/+3K+pbLUUlSz50s19bhYlIiWojYEhVSx5asAUCkqmgBAChcSNuhtqNKgUGLYqEs50pS5g8xxfydhq8WBinyZkQQ3s0EtHPNCfbzWxr/Szyo5zEXPKErmlVStz442nlqwY7gXrJtvdvIZqjHFYX4Z7YUIOa/qR9Otpmmo2iJg5X/Vv7rZG6qrEGdUV/0Tr0qa82tFNUTBgko9w96WKoXJXrbrW49bUmIKaij/+nIEtoUAAAH1DNo7DHhQRGxMTRglFcjse2tMJE5hHK8xtDAVRgQhS+z/qVANYuTxDxYqQZfW1MJ2ArRe2d28NLbXtBkw96nwUKucCNgby1mH+/Sl/k+ucJFNyH0ddrej//dTUEiqAHlJbZN6C6RkVxVvCKehcq5aJ1Zmufetf6AZP/p53/J6FPe/+mlGxj3/5G+v62rerVTpvuZEI2bUixvUp7lavQSnEBAICayW5RMu9btbtYzFv8+7vclRQpp6r/EHz99GTlBrVb1v4m/qbOJBspbrjO3Kl1+BqI9J2lMPXPVc6gmVX7wpsrd4w11BtPAKWW7aXetRZ2b7FGbamq/aqFqt3ebRhj/tgCdtPvKq5W6bk6D99/9tBXiTfpfEx1v+isvmlq66bn3oVvq32Uiqo1wpUOTEEP/6cATIHwAIAhMQ2bovWGhC49sjYMVyCHhjZGwcboEFHO4k9pRuACBI2pKEuFa0byfOVxeDarHTRVifC1UtThabfz9PXIAJbxV7AHPDlz7Pscjmcil3hKJn563GtUY9Q37AJrc3dv9tYKer/5UWTVnn0lMpmp2g7+q+rd0Zi3Sg76Wnjv3RipRjvZj1aX++DfRCoYC/fjUfJRWp2J3Qi5Is3ojdvZt2OMdzukLPWf8qFJyWsbksl0ckW+Zwb9NOBpZQZdQhtV/UpoRUilmwCoNYopEMewqcc0Zg1yz/Xaoc6Z5FFOt5i5V+j9NVe7WSACAZb6rLMbWJVKdFO76EG4Oe2gEqPXSZhtUPZVSisv9FKCNb9DlclrGt6t1MS3+0rsyFqPrd5MhWrIDv6+/T+npTEFNRTMuOTcD/+nIEXGUAAAIRQ99RIyhMQmPLE2DFcgiVAX+khPLxFg9wKJGUPhFwrTksclGw4G+RH3LZA+GXdAE1R1ScrLplVezPfbPhb/oyNctyWo3X50V6/6aeZGvLMa9BnK8bpfmlq4v7rve5lqE2U/460nK3kGUs1Vsy3nMNVdygZSzBQVugQHUu56ajqbbFucogy9SujAKIDG9i03a9H5Zk81uu8VV679zeiqV/ToDRaIiiktjlw2edGiO4Ip7tfbNTajkC2fkrnGFz4zGpsH7vUQf5/TarbevmLf/069kLW960Hj/7BdDGLZpuc6R3yNM9iou4dtS2y3h9zr1Sm7/4SxX0NsTmMNp/eVGEf0sYDK58qUDFweXpNqCd1mCqWwdU99ag+wj0ApOVcKCtVND3gJSA8EEXJIpiCmooAP/6cAQgzgAIAgUeV5svEPBC6CvNMKI3iG0DYOwYTlEPF21c0IpeCbtv/KlLyztInx8jvkQvLqaPU5CYgFCvexIuFM0XlIW87hZj9SK72IX+9RLuyrsrNSrlUd02lm+1VqvX9Xt/00AKJAFJNtxyYNavfaF+isQngGKHNoBrZe5SuzNnUpLsqP/xP//7dM9tD01xusjSX2V0J6V0H1Jv925a0XXyxtOaSpqwhJRyQCjUgpXdsN0ksH2sq9/lyJrIDwruM284zS9XQ6BA2hLKDa7jN/dYH/Z61Wybr6P71r/SVD5B7mZHuSXwXXbv1CABSbKcBKjeXCxlJpWW5irAcgwdGpRS09MRNPQJmbJH79Rz/24tO1lfdjtwzrtfh+Qoe3UKkdOrixa3A5wtX+9zz9taYgpqKZlxybj/+nIENI4AAAH1Hl7QwzPcQggrmigi44h87VxsDO9RFyBsDYKd4iCwrbtslohKN9Rq3bP+UTDOZ9G7T/ltysP6W5VSk7xD4CI64NlRcoSq0Mz+ok8OiShR6Kh8NuWQlmp9Oo9qUBCBRTkjkA8Gw1LuhqHTvQBcxjtxY2MoforMuqWu5+9tnnhSftnNHPvI4vNBNhbm+E1U9mIZn3qxqEHbftSsxp6MiCk45AIuQE+VJDK21FK1vk7bq+2rtgOUlFhcgAS0+2GZT+GaZchDb3y6BCf/VlzXs27K92ObP1Ovf9P2vJ7EC9vtIf6AW5JaBiEadeGPt57lfZ2tnvmQIFkUCoVQvrmRDWdXc1T5B7elnhr/dVcxX1z26PbZL11bWe1UXLFD+6T9kKF0jhmwZp27UxBTUUzLjk3AAP/6cATAIwAIweklWRHiO9w/5vqzZSJ0CMkBaOeErjERD2sNlgnKGqbB6jxnvCZTeWI258ZytFmTAnZWaYX3TKlNbn1UueSd6Z4WN/o3Hmq68kvRrzqUqn8T6me0tDtzRiFRR4CFtoBjkBgKxmlyyllUrlV114BbHnHWwnTCwomDUqVBKEZwtzClpZXqhTVZA397jf7XuVSW99U8z//u+g8Y/5EISTikAncWN5UhZov6fHiMAHklyjSg+1bdGlnFClStCe+1QF/3PnS0btTCh5EyKVCWT9a+rrlqPuVsJnGKfF3MoGsRLoO6ACW3KBHC2FEh9qfCapZ6u7E1M2o9KToWLa1JsOtC2LX5VZyZ5WsJpdf1Ni/SJFMMmlrrwVf9tWhiKFkflq4rVSoXpiqkxBTUUzLjk3AAAAD/+nAE/JwACoIFQNm55xOcQuga42DifIhku2JnjFLxEp0s3POJzgAgtNqQIBLuswGdEusYrj2azYBVoyjsFwSyJtMloL7WBIXMzp4p/9dFVpX3PmR+1b///Y+PK7MzvF7OCucWXuzYJcjtCtAsZwZbzCtKcdRGh+mlM0bUp1IDpng11c5Lnsg49bVvWpn6sxoYS21bGZjqzqtG6+rf/0/7evfCG1aU6lr5ppwCRd7ZjRJV4zqfybsf4DUNS81OZq18ZWZdpc/k68CJN4f1EHv/RinVqN+1uKnaeIVCWNOHCW/ke60lLPQVErBU7MjBUjTgKJySzljF4tO5Yzvew4Hs1JUC7taquxwjsm6Lkm/rRBZVvLo5i2OMhDOduDL1en/0b6YlNTyIo933ONLfQiRbQhMQU1FAAAAA//pyBJf+AAiCG0DaOS0qTDxCOuM/C2CItOtWbLBOmRmdLNyXlZYAJSkrkA3beMKHTWRQnLDSCEEVTIyKfqLo7YnrZoxbZ/yGsIvRb7NZSn3bM2UNMVbFuRu/nJ+YoZLkX46iJPccX6NNIKckoBMzJL4gdNxmtrjVAStbtwjppC7C5sqswAyda5qZ1VDbHHDbTtspGegYyx523CZ5t+2YVe5HBplfSAVJIAyu/TKfkTtLkViyl0R1MWImmQcCYQkWRfbx8KPNRR77wRQxH9aOinU35nnAn37JImdzEyuR4Z11pYuv4RaK2rDIdoZTcrgEwgLb1w+2ZpfLe/gfF/UtexImvHGsVh0Scjy3PsJ2R92sxB7trlrqZXKtiEKsVHJmFiMzLT9bqQm6hpXnizTqT7xiYgpqKZlxybj/+nAErN4AAAHnLNm9POAMPQPbN6eUAYmBd4FYI4ARLC7xNwKgAgAgpuOAK2kSlZUg+zuf71DfAlmpzEMCxfZ1d0dW0OvTowt16PYSS/+qNvb51z5TCrfi1KhRsVPgaHMu9lYAQk5XAFhwW4mKwVuaPG3562EbRCGSoGoxVbobVKoer0N/ogjdpLRDMoU0GbnhcQObtdhk89hJThgipURSAAAAEAIBAGBAIBB+97NGnp/+df//6KpEz/44PGuOGf/2OFZ4nHjP//FwQhMCwHYjsJB///+JIyxEwTFzjC45////jdT2ViiFCA+glggAAAEAOQCgUCgQCAUYu/1daf957+//0VSUz/5ARGuQGf/yxwUZ4YisPP//LkJMPB+exIf///kpezFHOMcz////H6nsrFEKEhGgtpiC//pyBEvuAAACJDlcnzBAAERBG6PmCAAIqCV3J4jE8QSgsTSAiu4AJpuToQAx3HwdnDVK3F5jz2FhaYpWMhpXLKhnbqyO+ireletDWplLSiGlb+j+Xro3yrothRV2SPNcqHNz5Kbr2e35adBTpctohgmO5YJxwpWw0fMaLroIdBUsBlgUqWAtYwRQreeJviKIlnYmCglAPZne7CcVng1rAS0WFgVoTFXKfbLeN088VABCpqUbhMgzmejsSppvYWHSDTFRVTGl1iMMKHguSOi9AQcgw+lxZLYvlnK8s+epvWlpdyTbLKw0iGniNrMNXbHC5hmlyiAUko9bdYpKyCe+0244/DUwsNxxaospZ3MmNeLMzmjm8RJF7MaNqQnfP8XL/ifF/1XK0411gCe0FaLfw1dUzQzJpiCmooD/+nAEmLsACAIQH9YDKRuQQ6IawmkicghU12DtPEPhAKBvNPGUFjCKP9IFfKQX8yFLWX09PIYlG5HddsJCnmzE+SCS0rOYlQqKwY1olyrCjYCVun/BRClOqJFhCCu0k9BWs7TIkY5T6wn/MABDgkgFae1pzDZSvx/dy12nqmsmMExNHhtZFdgBiWddoMFEJGmVg0BHKVQh+4yAbSJ0xRWfcl6VW+vXkv0f//9YUm4VJWnk4J45ZhdJlb+75lj4Kkkslq1yxS/ux89ByF5Uk5RtafG6e+idlf/+u//DpJjT+1j6yrFFPefR9g31O0VkQSSSG4482nLcbbJwOV7I2quNoJgxovW1FXNK4gze35x9QH2uzf/f/6o3//z//9RMd9ZB82aGupRTTqch+VD9skmIKaimZccm4AAA//pyBMlFAACCGEBh6QEUrEFICvNkIp8IzDFYbT8BQQ8b7B2XlDQRuRiyy/7O3DTAnvtT2io7nSriIwv4tTlU9Aq6gjIVLHZsUz06o/pej/VXqslat/ZuT/5nbB19aZFbH94h2f2pKuuoBciBcFETiGb2k3TrZtfb1TU0hwoVVoxjhY78g/22RowEmWhnigciDm9BXy5hlHL+XMLef+XPsv6eFd/2P9GW/orBTbdtGBo29FDHVphlQWseqngsFkcxoKFTz7C6fcFqF7hryBU0BidGNTFjVXiNbtT9SG9blPTxqarmPp2hOghQ9+/7E668mENxxSVfJSydM27p1VWpd7K6zAExju5oQE6GZanojNRSp9urflTv/7z/1fsZna/0PTqK3qyxKaGKWM1Cl12z3+W7Q50JiCmooAD/+nAEq9cAAIIHOWBo5RBsQsIao2njcgiVBV5ssEdBDBysKZWIfAUXEWrLdrJdBl5BkqiRtoGe2DH1vlvGobBqybE3/sy6aWT3CLP8yP7oZyf7voag7O15S79CPmj+5Nxu0hQWcuw8AW0ZIsgn+jQ9x37qtwZNA0JvwpRuWyKOAYMr6MyMNdlPtdGRjOY4TOoSc09kPJ9Mcb7c634flFv3oNqivzLv/u//WHLtv+1obZcq5g8S2LGWKELWPhaAkOLUp4mm2bNuXVqTujvdovX+e31+9n3+pm0GtVf9VrTRP32oCERWzR2yepmrR7dywDSUrcud4jFlml3ipI3WpYi22gAQTd6kMBE4GZkKZ1D6ZlRlqxP02Z/1vbshf6p4r/+RtFg6KNyH/Y7aHenbbusTQlMQU1FAAAAA//pyBMT5AAACGEDd6YMoTEQD2zZhJQ2IpQODo4xUcQqgMLQzigYABEBuKTSOSpxs32FWFzwIKoyuHQ2pm9mZuLlK5uwh1ykKggQrKt/7Zf9W4vT5dNt0TLf6JNxVTVVRrbl+qhaatG+SIC181r7JknnxstBv7qcvQGhNY8YAv0YxYq8qKioq2l0XtceqDUWUYNBY+N0ml3KtXcgUW/pVOClNcDrY9a9inq/qz5gEppJS7b/3YCctarlnVF1QH518QEsuv1gYSceVaL8ztpqNIsrU3NevMzWXlMXUIKeR9/m6cUj+crK9mEJqc+pg9idnWomFHJFZrt/rtwan1DIU8Ki5iRq5ahNudAVJBVlZld3Shh/7TkVK91ZSehxv1RtRCe19VS959l/kV8NY3ft/Umdu/Ie5MQU1FAD/+nAEL7YACAILN9pKDChsPyaq8mHlDYgpAW1GHENxEyAtXMQUfgAAKL/vAklst7LBXRP47TJabAsO7NgMOOjp0ZS6S0kuRG1dpO/92r9f0obx1p1/Ii9Bg966hUq1DHKtmHEmIN99IVSwSffElTHey5ixrRbSb8gmzOhqlBd6IPWcRdV6XjlNvvU+ev6aP7N6SO2gmvXT3FjywyKN7BcU/d7kZQzekLi5JHIBxGT9qCG8uqTYBIqIa7u4Z96FU3qrRMxWFNU5ZksMuv+m6L+yEfju1b/72S6jXt2d6FBnXehyVSJ2u2+so5TkjkASlE0pSrZ8Pw3IZHy8pAX9+9lV68rVYyN9zvUQ9+1FZLupf0b3R3K7V5oqWjFsOSfIs8tQow1rqQohBSyV0pjExBTUUzLjk3AAAAAA//pyBDU2AAyCChlWEwYTvEQKCzc9Qh+IIGNObTBOwQoOa9mHlH4GYUAnhYTNYH+XqKwO5WbuUle/F2jTCIqUr4gNFa/tilYffVt5tWEcwdLk6tWaoA6wyaELSzfdN+46ZYOfPct/WkA5TcicASQ6mGCphj1Oo9QnFpZWjDfV2YgJ22LO6Ndb11fW/92OiZ7etBROGIiVanzPyY129X3hG+v0tPld9EqqMEZkAE7JQDHyAwRDikouz9ZIkDSvki8X/dh32cOGBs9heWjl5ILEX/fY3GIkzD+q2VWbIBq62fqQ9dg2oO0X6Is9/1JCV9WCFTeNPfrMKlI2XDwJfoTVnxPn3RvwPY5UuYINeSc49Kx/XGu5iNqC4GAhjqwmxfiBIhnXJWVVYj+p6PJjbSJBMQU1FMy45NwAAAD/+nAEKYEACIHxOVcTDyhsQearSjxlGYjY5WVGGE6xFxzs6MCWdh+qgLrThJVi0GQ45+zUXu3pMBsOuOEDhIVr0aqI+1TaKwmFOqOeYuNbO9f0vX+Mf3b/pqVJVjUorY9h67u0AgoEXLI5AMNhgOLB73WMTIAyL1H0eyqUUKJte7jHfdwxaq0qLUmzdbWu7Wr9W0MY5f/E88ohQxIWLDBHb2xbb+YtBYtyRyACL5UilhIfvTF9sswIJaityB/8RLLHejUmnMqMW2iZdFL03yuklvo2xmuiq5uvKvqd4yM1nDzJEXMscrijbTLxIJkqJdkkgBEXFBW3WH4Jj1nIpM32LXlaOm2fNKWf8yIfNSGciGJbkRSIT8OUyXEDnZU1OBXVHNYci03vgypxXcIXOqMOxiqUxBTUUAAA//pyBKo9AACCFTpaUego/EEIKuI9hQ+I2PVc5MCssQEMa6T2GG4AQEk5bZICyncJthALLieIUSCIby/IX9j5jn1iVFRXpEA65JnsHbo8lG69rkPW99CcAWJt+lh695UFXZ5BaQnf7d9i/9gCEEyHcdGC8ybJ9+1Lm/hQTL1Z8gT+qjUU7EL19daLQmor6JVZlV7um7caraDaTF0+j93Nd/zWZRQVUNpxsDABKTkTAA2JgBi1nRgZJOg0HMo2AnlCkpx1ijP1ZkOQOGzNLFLFUDfx9CE/MimLuhVIz7RAGKyI7EjP9xb70T49WigsdMX9IJNf3h1xRjIe3YwPjitezSZoBzPNKw8PrVWPvwxpf79UzfbsPqYKg103Kssc3PD2rvUFIEUP1otACvoxRd+Qo3JiCmopmXHJuAD/+nAE8gwAAAIgNdi9MKAMRGPLGqYgAYho9WtYkoAZCJvttxIgAwQlOSyMEvTN3qtJXPvyZ2iGBQqsXCIat/Se1K3dZmHAV61uSce9/Nb7Mjd2mfmIdmKUfa6Shg8XMkiQHtKi9A3si//oADAZOSSQB8dR8KlKoVo5W2ykOhIeXL5YKDOvpBzCw7jhmbqCzw7FK4SZv8wjltTGP52Vz4fEzRpgRiA2JWC62O22LpGgAAAU3yXbbcbAAAATopfunWFrUlajSJwyPDgsLMIi05XK6oKtj9w0xHR9UdscxaD/iz9bU9XX0pK50dbf/dV/IOdOoAAAAAACVHbdf//8AAAQnlYhh7cH4xaQrWYsvDqFEW3x9uDK8VwpEvnqVWqI+32qTdg8qWUs1ncktnQqqvVB6fc4ATEFNRQA//pyBMuvAAACG13hHgSgAEPLvHPAHACIPDVzvHOAERKJbiuGMAIcDj8fj8fjP/8q/////1a7//IrOpP/D54ooTIEA8O/sc4oRjncoiOCRxhxxP+d53+ocNdhGh5f/u3Rjv+Q5EPVCu7CIgFFotFotH/////3//MVnUz/xufIKFzAoNi39jyY0MY89yI1ICMeUPLDn/Pee++o0JXYdofN/+7dGPP/MPHEPqhruw6SSCAXHY2SkzyJclRyxt9sPIdAiIoUBYTTDVho4BHrllkrSK+yJRh98s+W/BvOiYO6h51fM63SXW5bvqaZjwkDRGsEYK5W2Sk2Cwmek5nPPFlfUKI8k5kzOJh4xZbrcss8S6qw0IyOHc9byx+VWDT8GhKpuVfkq3XSUSursFxGh5MSiIGn21JiCmooAAD/+nAECx8AAAIZQNxooR5MQYgb/SAjn4i4u29CjObxFaztqICWViAAQGo5IyCWijONI19qgAsrMg5kOy2IyCbyjrpfwl4Tby/mi0eIR/NeHl/3ycw/6pbbAyR3lix7qV+mRYsZGNHPlcgQyojJZbo03Ymrcmdu5njRm+FC/ozAzmeSk+/81sT1/55P+vDy/4lk5h/1S22CkiueERY91K7KkwaYszCTXvlZYOACo25ZGm4RBThaCDTo1lFXxwsyO+5yoQ2a79aB715/zP3/Mzv06XHyCfBRYoB0OkwGVAQKmHpWtwxRNhedgmhzf18ciW3AghcnIkVJRw0xCO76idceHst7gMnXnrGtegoK+3yr//9/f9CP32+vT1/7MgWrf+k7EKqnIJuhaVlrZ1mI3ZOMepihWxZNMQU0//pwBLadAAACEV7XUeEU6EMsW2okwhuIhQFzo4xScRWgr3RgiG4AAA1JUglJ2JLrGclmookkupXHcIt5t+u/CcN9qdQMHw+6/AH3vv/Mf5nx07L/MN9f7z+ft6f03W/996L//9t4s/WACE6cjJKibxefOjdx3fd4wcRbuJxnWVXj5ed+hhf/mdvsn7VFf/Rf7fXSvX/8O//927nvT13fer+0i3N0S4Yh6n2iiCCCC7JNGm4VvepKz1ZlOIWwoGXshl14Arq+7v0/Rr4yMuqv+SpH+r/H6fS4jzMv7bOuIekjboHjzkjjAtk0ZHtKJq2tAlIN2b6xSUDNMKmV2Uav1KPUaRMzB3r0UgWo4os7b8Zf/sslRv5Ohmvt/vx3vf8rlyDzt4pKhl9yVR8P4vyN0VLij15tMQU1FP/6cgSuAQAAggxA2lDKEkxDY8saMMMhiGxXW0Y9RSEQF2qM9ImQBABtNxIIstEbHYwK2SSrOwcFWfiUeiMfsKQzIrsxHq/+jP4P/+2X8Ef2ZEb/m8u/2ZdaCmQtGa0faj+9alHboclAAASJjJBK5QGJ75VBpF0RY3QjGuap3LhzYKd//LOsrKVpNwx03ehb9b48O+KqesRE7ci9YAYdI8Jn0MT0o0c3bjFs1gAAEJbGU4yPgSpXbEAOpLxkzCl08SBXHUyHzwV0ZnVUerd5SscTt6vdYFiT8dlZryk9e/SihFWp1zk3YE13fdHH6FqEsqctsPwTceToGYX5CUE0vWJMWfMnAPQKJUkF/njvBkeu1G2Z9el7f+bRzmp+vwStb5qJlsG5VBlgrr0LLU/7Xf6LZJMQU1FAAAAA//pwBEVzAACB0S7bUGYpLEMlyvowJXEIzQV5Q4RS8Rsa7GjAim4EAA1JW0lJi2sAmT1J1rD5/UN2TkdV0Z2SW9CfVC+1P/6/ymbUWW32qEE4f3pWSvW+r/i/4BprGsJ0AAA2rfWJdhyDI0kSkS1HG9WCuwB6TBtx4BOlkUzyBJ7vQbq12+hdHtb3/Tb8qeKu+jc3ta0TK+kL7XV0ItkduxSV+9sDr3v7JLkUoa6oz2rHioPjLYhDWvJ6wqiHgGNHoDphiclP9qt/VuMifNoqhaMllDqb8lagxPTpFCBqxSNtx50i9bIaoryQTFONEpwH5MM7ibCy9PW/vwIk/26/HnSbMJWyzzFwmR/O6EZHFr5wzBL3H4J0b2NrZBSXrFFdch+pZp6kWnbhlmbtuDJx/UmIKaimZccm4P/6cgQyVQAAghQuVhnoE5hDqBsaGQJNiKUBb0GYUDEPIGsc8IpUMusUlfIMiFrWnxeSucEPa8UZW0JkvPKsDoJJA3mNdntnVLFb+jNX9G+j+/2N0HahiNTJgTamucs4+lKabbqk+3+isAAFklEikygoJBGCCZY5TrKagQGaxkA14t+UHvR9V7fd/y7Mpf//L+qdBa9MbrFvaXLb1qYlIyopTF+U8JSsj3PqfbYOKnbl1jlGKFiFlV1DSEchm2YIHxm7mHdLJyPr++g6OjtkovbRmozehvNSql9Uuq5mUmnBIfJBhK9q9JoITQFWgk2eEAZCZ7m7bDRxFl9eCRK1TptUvo0u68r1PrH+zG3himTdJvRmsFclt17/0/tZH+j9Sf1+jUY1Et90dCWGUsjl/T4Gd/9/lkxBTUUA//pwBLxXAAACGUDX0SUTnEHIG2oMZQ+IYLlhRISycQ8gbGiUFWYIAAynGi0InwKRt2HyFbRKd+ZIA4nLoAxKPUp9Q90belUQ/81zL++30W6fI/Errr9ZL2xPfOzNUEJIh4bWF1G2a1b1IAJ2ntY5ADVhWHJ3xu2YmWpjQSjtoh6M37WEzCiT3YqqQQZ6tZW/O6x30b2MjqmbQ0R1tRrp2mTuGDUp71zP33rcCACxcjaTFnhwkYgdVTaPpfLTDRSmMuR7aGZpJlc47N1RgG36lda9tLdaW/jUfUScWJE9R9UijtlH53rNiZ7l6iCqkgABOU7GmwmeDgR4KtEb2kdeuDYtk4GhHwPVtBlHb125z7cqYfL21eutKt+7txp0SmvVTGqU1BWR9kKiWuCXDLKf2296YgpqKAAAAP/6cgTJDwAAgg9A1JsIK5RD6CsKMSUtiNkDZUSMovELIGwok4juCbjUgmVeLleGv2CYJfjF1INmpbWpRZIkEp5xwVvf0ojxrMyegkEaLrTf25LaWRDfkL1Kt5f9PNv+VvUQddn1s+rWAAExUjabHHkI/4KBou9s1P9rimsnG7EHS6DjRIrejc7h0Y8yUSjJ/XbW3/KM6DCaW/kLqOpr1WtcoLc5vGuDrno1diwgAHTmkkgg4TrGHrojxWoBkfiQ7c2iTEHxps9uJhLXpPETPVru5vM1f4Sf2dzXmW1ZjFphEUS/dkiTDQgLXsWwirdXnDFJUBi3Y04KZCQowNCAqiiztBcY6cCs4Z0QtwfVCsOnVROvTcOh9O7l0QtxD/r1ZCt/6JRFol/RdMoi+7wdIrGWKOy005MQU1FA//pwBCD0AAiCFUBW0S8TJEQoGxow4kmIZLthRIizcP8PaxzHiYYAAIg5bbsA8NkAVqJfRTEsudY9zzHXFs9iNjjrka6J3nd5OsgXo85t/t/2r/Vn2Dvcz/XKfRXjIydSP1AO3ShPbniAAALFyyKQQNnBf8aWjH6EeqANJofmjNWoi2ZNtSW0MLZmXMqzN/alHt/w4E2oUxEm/wq9/fynuTAxKlqbiY8wArHYwHQnSlscgSPjo9GlXw6GF1+SDXPZEPZ+XxB1DClV9eiht9JEx1+hbDZ5ViH+iNwGc8K6YWQInnz8gULPQqDTq7pVVzouEJRpJhCH4DIPMjoI4HdobZvvbDqRoWsGAVZ0Q6Mh81XLXzflTCqetarcIu2tlimrDoPQiKPZqjzS/ouv7Fns2mIKaimZccm4AP/6cgSt3wAAAfor1DHhFKxBJOr6LCKXiPTXX1SVADEWmqwqklAGACN0oNYtRNyGNj9ZRz+xrM8sHSQAHIL2FH3VPGXbuKJMY2XLZNQNZ0W60GZzN9evt7wgqsN2TuEaFhk9ozSqAAAmLekUYx+BPBFQD8KymJie2KjtEzpbqEv3KNqVjRE7eB5dgByo8z3Mz094IlrPS4qNcnVY8iiuHzgKjGNSzNVACAknHI5AXbBIT2mo3sWMuMUgNDs889Q691ecYsxzyB3eiWSLJpy+2//+pjIv5z9T1Wqp+xC0OvH8eEgnMtIJS5WHui1ZdhEAFN05bJIC7ZoGpkYhRx7NQ27UFhUVAdTAFmTOJsi2bU69Tne/Uczb67tfdW3f9m2Md95E0jhjBcPFiFlzyp0/bXfZtU5KYgpqKAAA//pwBGhAAAACDixdbhyghESkq73EjJCIjJ93vDEAEQgGbneKUAAAgAgAAh1Tzcf5/QBgQ20VCx5iLYegbPchAGZX22lU2Re2wuisZ60U+//4p29OowTnaX09ZEPi0TnzH//f//99DqACAAAAW5Jbd/7ndAIBQqVxCWYlEknvpOlvb0eFxaxBFJ7NGf3/Q+9nnVf0/655kUEI26FopfT7G3F3o//fv//9L0IcYQUjGglJEkAStCBuEmLWfrkxWwop6vl8jGcqUP/6a6HayS3Z63VBn4hrZ+GYrP6kKWZQ9DtB566uM7qTEGYDEzTSD5YqQAQnce+6ZAJck8QR3ndLKAyNxgFk9K0GxIg/6rVB5DO16OIayLTV+GYrCzakKWZQ9DtB566uM7qTEGYDEzTSD5YqQTEFNRQAAP/6cgRM7wAAAhU52ejAE4A9olvdBSOAiIkDaUGUUBEboGxoYIraAANABnyTAJbKHnZpkx5b/WCVEH26mWhU9Hb/zfMb9elDOTT/qAtl6eZzaCqCkDuBlrI5Z2uV91+udrKxCo8FQkWbSQQozJtpIU3KZWVUVOFWfQltvUvYCEi2NzbvLYiPVLqUHTivneyodLQoewMtZHLO1yvuv1ztZWIVHh4SLNpAFDeWyJNyWCYDipJ/xhqbEEibJyDIoyPPUldH+f75Wo+m6aP+X0N00b7l1EtX6ValQQolBrLa3KX/2nkhqBiQ8YGiuTABCdytopMo0CE0JOVX9tn6R/1TFhHfdb9tsRXtGInmVmKzXvDly2WT/K3QVzPRvuXUuv0q1KghyURZa5bq/+1kNSzVqcVyaYgpqKZlxybg//pwBDUFAACB+jXa6QEU7EAIGnI9InIIuN97oJiwsSGxalzzldgAAohOOyNEpxZo96uh0Lzrnv34wGKhBsqmrQpgR438IPQNZf3irxfgpP/YRrRs4dQ+rrOMJ8BseCdHql6w/Tvb0HzuK8CiPpSl0JWQ1c1kbNtD3Jqki3oudAHtvup7BpEfa/Rv/Qv7/339v1Ft3//V+v/4J/qE3J/kGs9ecHt6CQimZPt9I27Y1I0r4pOvhv7prOCwz4rS2qnsZZHuXzvVu/+p3+VeimKx0/aEg6ixw8LPni+cQ6vUuL+17n+LcotYILRFrgm6kguXFqEnJq2J4oXFgtFla4loHO9pWaIN92Jt99l8wvsu93Ocdd5//V/39RI/b/Xydf/wh/b0WfnP19fpqn6duhFsJtrqoqmIKaigAP/6cgR9QwAIgftA2VDhFPREKAraMOJWCHitY6SEUyEUiGpM9iBoAACt2xpRxS4uEtS4iy1uagPTG3QMc8mkHlCG+ZyOwC9FqV/vL8v1/+nz/3rMbFFFzRDG6W+hTa+WfcVnQ1ISwAAJTbVqWoZuG43unOX74/Ato3CA+iICP0YW9pkyMvzb/0ZZG/90k/p1Lb/6voIa5f5XfFXegYZTO2LaQ2toIopKuX6AIrPLYZMnEkQRA87J2zz99PHN/mke/U4WpZXd/+T+f/yqapfwzqTYY8s+8i2pdBUfQ+l0gI7UVpb1LSmxpBIHclzAslWxkpw+WUWhCFChpBUOtIadulSANaVcuEd/8QdAiQ21tuYoGPI+sKAu7E9Qql/OiKKXu1PpVKu4pGba791Bt4CwC4K0rQmIKaigAAAA//pwBNoEAAACE17Y6SEVeEQm+zoMIrWIhQ9zoaRQMQYXbfRwim4AAACO/byGXWSMrtGEbTHrf3WiQzl9Dzv89+dUbLr9/IvL8/nU7+ZfEaNf9Z5M/SjSo3X/Kmr003+i/W3f9F1vBaFgSB25IyCnRAznAxtlL+DCOcYGAfq3BXg1zeK/WPYYVI/NUUz1YbFfq/hd//M3DrfypApK+weMY4YM6q1H3pq1Xm0NQRABKIss1zbbmRzBExEpeehvDg0jrttkhNFZh69G+dbUls5f/r/R24yJ9/sRSqe4RT9rrMiMLo+CdKG3x6/VoQ+Y67iEkAqShJJLYinLF0Zz63VTtFdp4oA/dSYGFzev/wOx4UfEx8o4VP0N47/huWX61KDRD47CI2bF0HZJbWSsULnSI890JiCmooAAAP/6cgRxsgAAAgZBW20MoAxDRbrdpJQBCGiBdbgxABEcni13DCACAAIABk1kRTksXcQ1U/ygM/g71WrLYYVjIVxFmbUW+X6fVPnpK1P24uv7fX11X+lWRBayzSNaS1i1BBc9kt3XXkQAAAAJLNGm6UEIDEslV56ukko/MGWu6AbHnpeUYqEd/9/pq6qnr/6o35+NHvMJdCyVDVhT1ubp4BU77hR6aaU3rscYABBIAASet+32ttt2uwcOT9PuZc02kmdX21aLNcq5NikK725GtK4hxLqnsOHD667DzlKu2IxK1zGCxml0E6G/0N8gmsAAAkEqNNvWS1ySSSW0HuKiO+bub/jsykQ5iDzGs10yroTBmMUi729vVEXX3VqW2vPXIxKskh/odF14oQVmfV9vovs470bNiYgpqKAA//pwBDV6AAACHSjf7jEAAEKkjA3ErACIkDFgXZMAAQ8H64+0gAAAAAAAAAbDYbDUCgAAD6RGZcGPuHNW7dqD30RGaf97b/l3FyADB9/uWfUhwFwHFiJ/7ihmKCgs5NVP/6IYcDEa//ubSAAAQCQyJBIJAwAAAABsMJygR8CWK8s0/m2aMJ3xRxh9dL/klk8dZIAaBC/5TPskkEMB5E5/7zQ5mhoO5Etg1rjAwoBK8a0OPH8OX5aAie9TXno3VkO9VsI5NrjjZDhUmSIA2sDjqEjmFXLqqEMYSFDqDcRgsMApEqdVdvm0IQGo/pR0SU9duASbkgXMMMjsEwgqrQDQ7Rl1LwjNLFa1BUuwQ7tmMJjgjZ95NQsF7QpMNEAhJCobFD2oCLYQte1CCN6/htNTl6fq7U6ExBTUUP/6cgS7IAAIAhMfVyspQbBEQZwNGCY1iIQzZ6wIxWENnCnFt4h4ACBhkQAqkHkoqv877uRVVkydZW6QjCcLLBgqvheLdbbK2ZZr27Nn4WvsZZZkyJHvPO2H546UBnescs+ppBDatVTNQCSTdussjTcBERs1rSkjMn9SrNOPCEQkGgVBtKCI4aoEjZKvYHs4G06Ba8nhn8nFVOdQKqWCakJ3lFW7o8z2/vQPqatQGktdlcvEZBUsqprUlIQVHrxxDRaHBk5eKB06poNhrInalliDStYTDXnRC4WJTq356WU/iJ8UXKv8s+qSo8JO3llnVjA3MngjIYElBGRK4ozNIlWPVVHY0MYMM5k18NnXKJj3fc5xCE3eyM139f2o/syVKv39/fBts/+bxUj1B2LHs79n/Z/JJiCmooAA//pwBB1nAAiCGQzYMy8wDEGCGpZvCQUIaK9UbRhOURAa6w2liHoA6VTYvM/xibLDDApW6hReYXY37j0GorgRG9dAaaKpDmfM0PCzRCG1rLX3qI2RcZEt+KhpCjV4TNvI1RVASUsVLTi1gCBmfAaInjngk53IVSsjh0EROsyIEZDyAina3KkWzjn9PTETmGmTlph+AYvwFIBKXj6grZ1hiSTfSNQsO67+439QBSbkAjyp9Ns79lO69Dtunn901We2uiu9lVAbGSNZa+rzeq6FIRTvoKNzL5dLu29ECoNE5BD7lIba522GXn2zf0qq0glNyUDMd1la6dI2FuWEpFhbnrTHf22Maal31NrooTJ7c6PtuRrpsm41CuQujo8+sxbfyWBtJBAa91ZF4Z/5TFaRzmvSmIKaigAAAP/6cAStNAAAwhYUWtIGKCxD4ipjbekaCECXZUwgo/D1hmpNthhoAcBtNxtyAJ47Axm2RlJEdM4+9SBmyGRRl30ihkOlFuSkRIUAQKcCdQNGwu3UxZUIu5HJVls8hwkWKqvLWJVm5UinqABLewGccmXCYYbuWNJrkrjImPJDjSh+GdQ5DiGrRheofZ7dyZPpFwwfLmLdb3LdzJsPPUXJaA8nkV5xnih4QMaQO9tAAgMlFtpwEN1uTMpoJRDWqmdL2bXgJB0wyc1PkRjf63l5NzMqIQtyhKIURdAhdFSXrLV2Y9DZ1UbrqQI0rciI3Px6Bkt/ANjRZBRRYiqE86CIxUKEN9R2k0QcPuQMQ54mLrbQYHRVFqQu4mH2oStjFmpEVB/mfZiFSuKj7f+z60xBTUUzLjk3AAAAAAD/+nIEwmsAAAIVEdQdbQAAQcBrKqwIAYjIOZG48aQRDJ2sQzCgAAU5P+DCa1LhyUhoMXQvN34En3+5yZfi67qeEwRgYp6a18XA+pgSzkgwbBgNsFV1kCY+bnXxhrSiCq2qbc4q6mrs/2gAAummm3AM5gSbt43BEgJiSFGnwReJBOTPvKg4ZBhN6rkDWgR9ynubQldyEPHAzPhioSpelVx4tz3nrWMcsscUkuAAGBQG3ZZJbdtfqBQMPxwHTTfxqbfxrOcxInCvSjCQC8+VQOeH5aecX2G49inTiXCWlR8pGoNhmT2qkw///2HP//unz6z5qNOgLLuB541R6nLMIlt6rnTs7gdChaIsZvZsWwBAFT/EIBXFt//djxYHn/5h7x+f//uYrse///48Mh///hg5//92fTEFNRQAAP/6cASdNQAAAg4a3J8wQABDg6xt4ZQBiIyBc0YgQVEILm8olYjaRMQKTWj85B+407+2VAXd+fuHZZ6Fbs6GXvN7o04wrESgKHV9jkdGGr5F0MrEg4++lCBwMoeXfrd18X7q9NinmbZYuFEJuNOoglqK4LvF+6jtI1SyqUqEZupTGMYycrULAW00SNCMFg6oO+Z7pV1ym0PnBVPcksddN9v9bdG108pZFD10hUEAAmhYS2xQqUjWCgTXkoCI+74LdbOBlkcj2f0HX1g+5QbmDARgS24aa87ry3QSp2iLwaPETmTywUGB2mo8O7d50lg1cAAbbQsScgw1Gm/RyflygdMjVOZ6EWyuVWAKr6/+bqM+YCpUv1+X8vob+oro/rQ3+l6W//Q1kM/fobN+v3llauYVQmIKaigAAAD/+nIETzcAAAH2QNq55ysUPqgbI2BFZAjE13bkCPHRIDEw9DSdTgAgCCKCBrOsPtySN5eta0lAxqKRLYVq68R6/+/qHoifGlDrkq////ktqKpiTthf+j/9NS0UVFkgWNLFf//VpCGGdGLBJNPzS37m26KvxtYwIOQJjEFJUXiwLUvfjGoM69EgChMeK8b///tt6l6OxRrF/ob/6FbUdlf///p9BI0hd9vzzaQe2NToYNAfX14zSE6dX06Or/anLDPkdPI32o/aocHqUo4VIGXB6Db4srGgV9F0ledL9T5Pi84K7+VHF8MUquLKEOibZu7u7k1H7nxOB1B8y7Ehs3nde7Z3+07vL/9mbMsxyeYT0dJUJHPMF4uaoIt///oXfUgf/6W9FV2SlParL/+jczmfoUTEFNRTMuOTcP/6cAQg0gAAAf4y3FFpEhRD5vwdHQfxiNzLb0MkprEaIG/ocJaOABAZEW124KV7evO0ceWaCD5j4WaB3+z5+j2+9LzQjSBpnPs0nrO++ReNBrW+CfZT5Je/+qL+S2+p+JnjAXorvBILFbSh00dxXUvqdZ4WLCsRnoN6mjbr06tq+nTa9x8WT1a1f/35SNf0wWjnRTLcTCH13k1DffD36MugY/0LZqr6wzyIgADgCNhKAAelAfLMJf7JABQW+JbBjsxV041+9v55aIH2sMbvZb6M8z7UGMbVRbOhsZmZTzHO0d2aVvlZ6viI+l4FeEjdEXBGOtta1SYcer6kLZwc4wNcpxrynF0qJ5NjA2Y+VBU8x6mV/vRjMjVa8S0mV7n4b69X/3yvxVkUe3/fXwgOJqCYKlQ7IoSPTEH/+nIEjaMAAAINH1/oZxtsQSZbajyjfof4y2bnlE7RF5ltqPOV4gUAwU42NY5MxdHnDzCKD7crsJVo++PNYQjOiIqe9eLoWFgxjHl6Vyet2NQ+SljyrNOwteVZ/9L+tTEyc1U9bgicQEAAKQksop/X2vvn1bpq3+SBjVteNgXYPajerZ2oMFtPFrdpX1tT/7e5aP7EFiG7EQNLsS3u1DPSOxzv/+9Ha2mkAJAkjkh6rL1rtatzG/sgxXGnDHNiWUIndxR92eER9Gv8RR8GQEisjFbT73XfbZPszKNoH0SKh/Ts18f92n/+0AIQgja3dl+ul91mkJx99ZBDo261XDWpHUcP/Kk8QOu3zWqgNzMRLoit2dZv0+b/HbJS/YjSj5tZWMfsOT97CqaVjr0awAmIKaimZccm4AAAAP/6cASZHwAAggQz2JmPKbA/xlsHPyVWCSjLdUEcSzEZoC5olpTWgH/3HV4rB1RvF9u9GtJBAjwhn9V2GsjBDiTbCodKHQ3Rr+gfkMtu2y5L3rdte2h24uf9AiP0J2dalkNff///vpACgNtcAr1BcsyIipJFX/zSiko0PSNpW1i3mO9Q98j6BF2go/Ev6g9kPMrCqLKaTZfsv+ibFHF34wTfDcV/f/oQXC9EWxS0Q842o0GQiF2kbOs5hu1Qv0bBD6f6TUkmI0rUsVmO7qhwe6eRmygDLrqCfb2ukxwwAgVVdLliKHEXUjHgMstq8jv+sFwxHDJXO2xakoPqRHCKZBPcDviuR+nGAZZhx2TqPmvRk1M+jf/f8jJxQirKuLP///ncyxAq5pG5B8TIcdYr2rNqRVIqEN6Ygpr/+nIEYNwABAIMMtc54iukQGarCj8HUwjUy2BnnK9RGaAt6IgVehAABSKAMybWFCKiBvCG+dqNMEdHsHtysGoLCnWMPhPmfR44vUso4/E+n2NOx+Ri25TEZ5BWjOJcZ978WAFwh19p0AAAihrJAHyNhaFRfCocs2hQeKMI1Slg1SaBmSL8avUZpEYWPVO+IRyNTWtO5E3P+iW7fp8/ulio0fk3MPlWdtQIljtDeoqnMVuNWjfUNDgwkpsvDp4pxlLRVzXyp/yPoGGPbMYFBmU45UVzMjFszHYx32r0jyNV0xB3U9+tN5RsQ7wt1dyOkAQHg1dr+HXICql0f1JWDPBpLKi8mWrQ1nO3WVXpot86PLH/YdZHS9521t0beylbmWyusgE8i//3kKrnEDlblT7K7JWLEtCYgpqKAP/6cAT8jQAEwiJAWLpGL4RCaBrzZOV2iGj5YmY8qpETICwM/BVKkCAHNMA5JF4YwkVtVTWQUIbL6iBnrkOZZ/eneRnLJ//1EXt10lZ331fdf5dwaDaTY8Ncs5UXANvX6t1o29UEWnCwe0pBCddAzsW2YNWlD13aPeUdcgh0a5WidycDqjNqP6BYzaqlQ9PLFEJx/9dE//rUiOVBYScoij3B26+v+xnMuolAtG9QAdk2DtVgqP+lpF2UggqCugopz/UvUe15eJDlhEN+/qAXLsIJrLvUjIhbO39H0m5+Inf35/+pxGIhCPzjsXay3rML0BByy0TTwyyevlK/c/5GzCth6EvbabUo7FzvQZwm2JqhLjadSXrdRTr9Lsz9/vdhUVM5g663EGiIs3+So6vrR7xjv+/VUmIKaij/+nIEuakACIIHQVlRjyqUQygbJy8HUIh5A2VHnK7RCplsHYOZ+gAQCAEumAQ4oh4Gf4hQ7vswJpk6ExsBpDJFHmwmO1Df/UC83Adtq1d7UeZl/ZBMUQqFGnjhwrwx//v/dW5R/ZVWAUES67BU80WLp0S1NqVO95Qk8EEdCNUHND2zeJxnX/DFPYx8cOaj5jTdtfogyarnjxGqGKyhK3Z+bm39K0WU6P+76gDIEuvAa6x7EX8ZQd7Xggk4NkfjFMoxWawo4QstA3T20S8vDr59EapprN91VoiKulSuRBBpiARP/v9SxjFnYe15JxSsIAc0wG8sXic/k1TvJl+b3lVZEWtJc2cnU0R9yHFbrGgzp/KAjX3P5/7Odo3T8+YGy8vrJVqC+w3ZT63DVEESgoptiYgpqKZlxybgAP/6cAQKcwAAAhNAWVHnK8xDRltKPOV6iETVaUeVD9EUGa008qX6QAAJANxQCurylr8LlHfX0EMZqqxxxVsUnztCZYdUFA/TauMCNr3iD3G1mZr//pzI8WETow9MwHb+i7X9qZIoxkxEYAAphK73gY125pkkkSv+YgpqV6igVHOOTjydY0fExLRu+Cg3jy6xja3QQSZSZ9mbtjBcdiY0ysYYbB3292oJ2BxMp0ABAuALf+BjcVJpb+CyfN8DecPB3cR2CdHEeJ+/9+DfK9aMLrQdXb9PmuyF0KFj1NpccGQiEVvLOjmrB5TH6p66IqSQAQgIAbvuB4knWr4bIvvRxE7j1evcuwnGqU54DNiTf3xgtLbiLbOVBJqIjWrZ21udb/1Itd5sdpMRPrdecRpDpgW+Zm0xBTUUAAD/+nIEDmwAAAITMtjR5yvUQAZbKjzmfoj4zWNU84ARFxlsap5wBgAQWBFuuAknuYcbMrwrf8QROU1hidsw86MJDqwoXEJPlt3R06hilrlCTGRTIosWa/en3ZDiukUREIQlQFfCFf/xbegAIYBl94FMYhpeDtsR9Z5H4Qkh8R5hpLiaYUKFjlA9tW098TEL22Jc7v/b27y8KTZvY3NSmf3Iv/zwqXNddyVoAhCCbbuBZynIGcETJ2L+s7jGBN2ulBqtSTVPahYN4+GG20XKBHW9DxO9zDmZi9EOT9v69F32mD5eVOmT2JhZIkOxTGMErxKAAAQAbbgDZHyws2L4KbOOnjICfj3zUtVCtyZOYYCIbPPIFseLd5gUH3a9VJNrqzIxnT1/vql29Dio9BmJtEoo6LNZRHqQmIKaiv/6cASzugAAAh5lYL4E4ARErKxzwKgAiJlZk1gFABEUqzPPAtICABAAADAoFAoFq5EznHBEXun+n//////n///zxuTADEv//+NDSY3Jng8B4Q///8geeNCFBpY/////93MIHoYNxuTQaEAAAAOBwOB//ynX////////////j8VxbAFiHf//8WDScRY/JwbAvBp///4sEZOIgaSgsMxH////+5OQEhOhIIsRZOgqCwAABgAAgMBgMBgMAv////f/+TGER5r//jwjDFgwC6Gv/+PR6GgUQXQiIkj///8KElcseKxh5IaRf//+Njx8xrEKGlzmF4AoGAwGAwH/5f///////PzJ0X//JQzDqwfBLiD//kqPUPAmwlw5EBpJP//8TUouaG47kC+SiJl///48ky8yLGKBqfNmH0D/+nIEtZwAAAIeGN9PGEAMRCOL6uKUAYidcXlChFy5DSCvtHEOfoQEuFtG2zsNkDePfcgl2t3bAj7e89XfTRsOxNn61rQDIK4vhqdnqQrDU9qeKDzuzSgcDT5lZbyJUYTFQE8NgFi6w1S6JEfwDGQS13YquyCOFDmYeYweaEgO0h39av2/FE3xIYwsK6ZJoCBU7XlXFZbAzSp0tyKiIl1d5lKfZ+sc0OlTQStiV1EAKTAFRpOoWRoicMUPUAzYayBJpufxskr9tNQ1qcd//GoNvL5VxPE/5gLJvVfZNQrL5v/7LSre/TL1NepSlSgF1eN5+qoUAC2mEE23KaMSJZJzS+NgYcXIYRtjOYn0qbm/O31//r2//9De/6KMaqPnZ27KUOUKNXT3RgEYBwFES87J4lKT1vQMTEFNRf/6cAR0jAAAAggl4WkCO7xDqAs3JeVEiLEDbOWUTZEOo+/0UR5+xpBMThLurtzH7ElQ1Y7xvA0F2DzHyJWStE7d6hg/vJ0Pb+sluWVJbi+VSZ8D6+SFHRi2PWiUU4URxiHr/VTWFuuBAAUjklOofMUIBUTqlgrxIxU7mlwBHxMW6cIQHZTPiuj4AA++71/2o9T5DH9bo/A8tTbfT26f08Ij92LGu7937e13dAEAtuW3XkeC/Fg7nqEvwSHQsEwE3JnWsSZ5k5/oAqU6YP/21+f9PeT/7EFVTuvlLHLaHc+Cji0Uqi9ehzjuzftUsEbLR9BAIhkCTkclCnkal43C2MJhuMqNARqqyUZsj0bQfAyb6a/+TVNL/t6ijdv/0C0p/roFSXTa/cufk69FzVHe9FQQmrZJMQU1FAD/+nIEaNAAAAIbQN/ooRWsQyb7AxsHKAfgZWjmFExRDC+vqGKJxoAAUU4iVa5Ko+7xl4Ngw7HY9kRcgyrhZTV8su19//twtaqn0MxtEVTPf7w/hvTsysxaBHFyKFxmgkpecVpc3fvnnWVAA2S7FLoKDSrM8tyumtWmyHrb6vOo5oOxhRqDnTuNw4JmkNB7KF6mLzI7r/7tVvT+nqnf6vPbQm9/Xnkf/87V7/n+iAIAUbksP0QhvK4smzIedcFbxBcBXkEan7RMnQTp3Gy1vlYhQPbrdvNfdcRZr42lvpXZ0Heqw/IyCBPLONAyzdQrwSpHJmXzE4ODZ1fiQ/ONwjoEz+stWbXTkJ9P/5etrP+i+Cpf+1W0er3/ZlqVqUlZK1qZa0/1Sz/emafp+a490WTEFNRTMuOTcAAAAP/6cAR3aAAIAgYl2zjsKTRBw4wdDOZfiJUFYmecT1EalyyMx5yiAEAjZLcrIIQ+hrbQHM+KMo+EMQP/eG0bR+8aBtu3/0j84rFyfCHlfI6n6gEowaRRrHYotT81xag+4yUH3EVkCq4yUiG4i9Y7fWufRigHxwP3xo9j8gnWkqzadsRxvtgo0v+TW09qyv5R88T7gCHBk71IMWsJdfQlZ1AkkyIeE8Vg0eADbck821Wa2TNYViMRV+QtK7d0eC9ooIZnjSBSFyHbK8MNS1cI/7aYNsn+i+My/fvJtJUlvRXq6BxFMOZIn2eQ27XdtQAUblsNNKw3uNNAeUuWZAcQzuxsUwImy2d9xxhOQLVI6tiIPfXR/9kapDn/0R+X/WcW5tvaIrvQfi0gyOURsUKeGXQKLW5hMQU1FAD/+nAEBBkAAgH9QFiZ5yvEQOMrFz4FVIhNKW1DFE9ZGqCvdLSJjgC43IMRcDdjdc3aJTo06C0x6qyHhzoKBfn+NjAdwqSardowLbY6Cljf8r7NZbfozaC7//unTOn3dt406yzQI0ACABbkkzN1KfbWD1jz1I6ZQQREpUEwdEj2cJExFedwHhZB2TVsBxXVI+Xor6nSe0QLKl3CEcWSa5Ms7+n66bf+mEAFNdwVfAaDwlLBMcQvnh6B6gwfk8bJI7bfgz14v/+2jf62OIZp3KdUI8vSq8kwr8zNTOyejUrragI6TtKLP9uSqwAElxklO2W5At33qr8G/AuMtLDM3kr5Aenq4BXyathDb4Pv/y6vkUv5kJqIX8tuKLy0Kr+iKyOsbaHrp9+Q6E2K9NJEopikxBTUUzLjk3AA//pyBDoTAAACCTfb0SUUFEFG+3okRX6I6Odg57ClURclLSiVlKKBRpgU7tuKfzJ7nFoJG9PivdRj2YrlB+A5OSWh2ofV6q/xWn/qkO2/+vxJy1S+6Ho11DD2C1abmtlvQNYsZtI1zUIAKgFbbcJZRWUlQ7loviIOeR/DPcfP6OPty69O+n/82DEb0b2GlXRRDOix96Qwysg8VOM1zBBVOqt59Y21R08tQBACpJcPPsg5WUeSVhJTaomvUMQ8aq9KDw3Dh9gGIe4jxDG40W71DN2/5sRao7/Yz6OXLr/ftKOpkClYqiAOZwjW+eWfJJi8AgCgBy7cHDdDZiYoBKmjTyLwjlKWTUG28Kh2jcZrxN99W2/6cd/9SdNZbozylgw5UOxooheju76R1a319bqws+UnyNLzaYgpqKD/+nAEdn0ABCIIP9kZgivEQOgbSj0CZIitA2LmHE9Q/yBtKLeImgApbsN+o4DaYaV0i1LAWdmDF5ccmHbYGUVI3E/hn7a/++n/4w/QRnR0ehEVJehXRZvZ1ZSjSLXULXDihgtonn3OQAAoAK27jE+3qU0KYqt4f75VrNuGvaoDkzorZmDY3B/gBPr2/2fTn/2Zui/2TVlopsTfy2q1xrkfN1KeKnTrqzMABMs2DMWFUbay8jw/gLJPRaOTNyjKXJ5j7NDMTGNXo0oEeZa3/6kztr/h2atGXXa/Xoar093XY4IcqWFUruFJ6tzGv1LYEAO78SujjFAU5YpmF+Nl/CAOD1GqYj6stRtTacDX6f/2bJlf/dsion+2DNcozdi82hUqJm0hxaEJGjVqZhXpTEFNRTMuOTcAAAAA//pyBDcEAASCC0DZuYcT1D7mq0ol6iCIUOVgZ5xO0RygbNz1iOKAABku/GuaBZT0EaOXrKyTOrBnldzNdbxdPZq/wm04GkO9/7atm/yq+7/390VXSehUd7LLNUCfrKGQIQsFzzpnvWAAARBW23F+ygRowisLRzCSHWPG2VeduR6yGsXlmr0bHh22v/6s9BNVjj292Rm0VGpdkfyR91GYsu2/FM/oYsAKW7DE2CsiyJBrcsuEqFDKiYRZemjRyiC/pWOQxBUO1TatUT+bK//Ttl/XM91BT6bU8mhca64OjVUoeua7LMaELKUAF678Z1ddOOVOMw7nuBYMmuOePHZcifJBs1V0GpXbJ3/4S4ItiI32R+iemnIgYiUTZGBaOrsQ6AxgEku7EzhV96twZFFkEpiCmopmXHJuAAD/+nAE7hIABBHTFVk5gSukQwj7NzziOoiU1WTmHKyZEhqsqJecaoAgFS78QV0Bz+oS2mVhNAnUynWUKJA15mF5Q/oGULYuiR/ITnnJXgBczMJCYFcp7kmHWEYCZ23ii6lQOAMu347j0xHqJ2bQypSgHAkwithsYxVv2WOys1QDQbAxFWyNQf9s7aYL34a74UiTZPaYuhZN/JbMhyuu/+MxO7zlMIAd2/EEegYV626UM9g1BFXUGYrexPP4WpIw+gvpoGfksb/zZx1D/kjWba+aQp/jneq7ttp3K4+dwX6/8N2d27lBO3+gQBIQALJOgIkHgB/bU3qHWwsjlVVP6hVULCvNXlXGu3H/xof8/v/eugIXKs2uyP6ev1PcrEx5ht/IpmxwcdsPq2kVCBykxBTUUzLjk3AAAAAA//pyBN1SAAgCIRlXuedDhEJEqxc+J1CImGNgZ5jtEQwZbNzyieoAIATt2GJ4wpaCgGGpEDudqZwl0LLxUvAR1TgPJ5gUtO+8aV73jAXsuVnPyVmu5AkcgUgFaX3hxaMDHi9Sn+957FGJy8BgAdtgJJ5Goj9gyR/tlKZOBHDQVhIwkFyGkHolXHC4RUZ8afi877f/OqYxQCsMPTCL5I6UgiNWXeKIKRth2Va2/UATLthl88FsVaHkzQ7q9zulh9zQcPzk87+ITaC8JcX46m6vKg6cMOBxXzhRYX8UWkkiBA0EDLnAGTZA+4PLpa9/JbvSxwCgC7vwMWbJTXV58kWeEdvvQvxEbov4SnOLEI6EUeMhGjPn14KXTDCG/8gTRzw9vvz691jGGYZEBE5BYQOYJ++1ZQ2mIKaigAD/+nAE/0MACJH5GNi55hNEQ4XbNz0iPohMmWlHpEfRDBlsnPWVowCAAS7YZ2tlTHYSxpYk7AwtsAnol9PnNR3/GJ5RgHRM0B2BFbJ6KJclxD9qqOtgAnsVA4JcwBJHrX5bv0fap4BgCrvwPCXkJPPRzEmDJsnjAZDntrONuTvDmTqFrNYndBX+r0NXaQav/QpKCFMzvpSvxTsaVHA1DQBaNB8tU0K01VxhEAE7b8fTanrEAXTOcxtIwDwGEKJv1D0keGD/rE1H8Kv0Lz90kLq0F/9ZqKQGIAzpVSGoZUfuTt6TWkltbd6CnxUWQADM4fjdfC6BLi5F/7qlyjSjbI3mvLqKj+pqkAT6Fq0B3pq9zr2oXb/l46n0vZiat2M4RHk4SgzV5ia/3VIZ+t/9O0xBTUUzLjk3AAAA//pyBCuJAAyCASLXGecthD9DmxM9YmiJFMdebD1FERgZLOjzibIABy2jTqgcLwJEDYRZzWklb2MZOFTj4c/i2Wo+6z6Xygc+N5aSYQl6j2VeMBV00f/43W5Nnm9E007UuQ+79H/1AATbgZmGWW1GD6A2UIdp/i4UaGwdrhK5NNIcyYLqjwXyKdYC2fV7gHTj/uVCRemTAjzm9hWmlbytYrF2P4slYAUu4Gr0cUlQkLTOWRAaLKSclQJlZniaY6YLg/UrF4PDiheyE8vQa1KtOC3oUzjpG/+UfSr/Vd1d/+TgAWpags49ejUqXaomHxgALv348Usz3PowyoMlXXhX0fpTRcMO51GdQ1QvGheJLTeW/Efo8GGqn+Cf5jWzzM90VRavtjb/mszFpmzHCxUgaHL+ZYxMQU1FAAD/+nAERpwACYIRM9k55itEQCOq02GFWoh8x2TnlG/ZEIqsHPEaCoAAFT/gSbkKzmKax0HBuOv2UxSvp/9712UHf65II5NbGfH9kGhItRjTilX/7aVf71RXdpt53pIw5A5kTRJpbMW6NgALtoFyysCnkUBRtYsLFKC2mgSjhMG0SQR90rdi+xTPpgR0WG8gkgYzYG/ARZ0SonT7KC+t0s6hcCwj3GXEP/rYCTbOdjLhAPzehinuzL1UWhe1hSIoyo8iBtHiYrCA9zPVMY0oK+jVHf/N3ZU21zM4yS2fm4q/2ELZJimB7HPe7vutbZAAbvwMbRCOG0TAfY6CS2R8e8IJvC+XqJVSxY6Axl9l2AQzV/yNCGyCzyfFXiESTmoAvF1izA6Knkx7A2ScmSBk1cmI0xBTUUzLjk3A//pyBMa9AAyB9TrYmecTdEIGOwM9gnaINHNgbCStERYWLFz0naIIp78Dzwy4TE3HpkNuqoXPQ1uSCLqc16F6Og6RQDDGJvlsnUxH+odv/r/+j5b3U/NKCFMVc+77NUcq1KvqrMAgTbgecbAtxim8JChh5taHOMYmBCNbIZ/J/2Lt7J7W1MYaIoAwugLRrHPVHxv/001X0k7r9jYpCeic0yQNAVmxLUBFTbgb7i3e6SLW276zqklgrjPozYDEd6Q74Hq20t7FcmPKI8aC1WowMW96iv1PKTWVrDpjFk23U5ZIxIcMT8uXUgQJF/4GYxZEogneTF+Q7ppyiDKL/qAQ+Qo243c7rLXFGrCyQ2L5vawQNSSov/zCbbJR+k5WQvKABQhORdbEqGTpB3Nm/FExBTUUzLjk3AAAAAD/+nAEUVoADVIRGNmZihMUQkWLFz0ndohAZWhnmOtREROsDPSdogQr/+N7St4DB3EtZpsh1K/ywdaMKkGRkBVACQYyE76cXvDf7lR6aTSh4viwliMWY44SLI1POJQlp08dfSssYsax18AASb/wJYTEkEepV2c8SAr2WrsWzxjiyhU757fBLE0yTEhZI4vkfwjZvnf/KLoWn/roxZo8cNFwdVI0m3U0Uxu1E2EjvrCdeBtJxVoCqw1LUthZ3xPPCmU7d4Vze7Ppo8qERZZFb/iFNlzyKyuxIFEgKuESzQaLFqtJStprW85xZBgsvcCVczIslharh8tiMXbE9HZRJehHPYO58gnKSmpkIs6hapfT8KF5/Rdv7HH6Hqg5rUFCjhOH0Aq6YQqxETjmJPvdgIAqWmIKaimZccm4//pyBGmBAASCIx1XGwkrtEFDaxc9hyiIfLllR6TqUQQMq8z1nZIEGbYDVnBS6MgkpfxwkzYk+rYZcvp+PlanEoju8KOSFiiWB8ZFtrVXeo7V1UMbTKP/JQzfkiTBg8qtyLgnb6NxXUL1rWAwJC/8DxT5U5arouEosvE0q2fB6N6GoLRmjFSFHjUMtLIr5XR1hc1zXL3/FopPSqiK0xATKnVMFGiohdOTfrazdAAGpf+A1UqbUEEZLcfj+6CbnAud6glxtjlBhcqFWj9BKdQubY2qIvxOLmVv9C+Upb+6PtLlkpDRFa1qeiZuue8ohKaQCZtsMRS6vyKjG4iid6NtJPQN4YNHL8WOzj1QZQugkhzWvq/dXKl3h9Zz6Yh+VfYpJ0MQ8yuy1rZfS4Js7dnbqrTEFNRTMuOTcAD/+nAE0q0AAAIUFNi5jzjAQcMrvRgih4hQY3NEjO5xGAzsDJOJ0IABmbS3Y++Eb4CoWI4CTtqcYXFUIVKE8doOOgcmVCrxCXd8rVINW84v7N6uHy7xBR4YUCAIOxAGMQO//qBD//ygIOYIAEZBRciThV89bt0vD80dSYR+OVuvqTvPl0G0HfQKOYmcRLvD6gwp3KHAQBAMagQdUc/8piAEAxiB1nEHlBgIBhQAqS1EQS/tkkAWZia7KWrHsSP8TkHhbzS8sMqN80npwniqgwwedSz4ry2e/PV3P1pUeTLaAMedr91WszLKKTw+iIgFYlJaeBwG2DqIQi6wpEDy5Pik8K5MkUFp10cPCOgp6/wYHz8edTf4q7LYdj78KxVTn60ix5MtoAwVcHa+11XdLSk8PoiJMQU1FAAA//pyBFaQAAgB+RlY0eJLEEHl270FIg+IwHFQbL2tQRKgbNyXlUIAADBkvrx87iJ+UB7HypUdCR6+7PcyzHHx9Ioae/2qnwceoubgol7vemUdSW/donstiVy/QeniPTOin93//8iQSANCyS5E5bSVdHYs/Z/D6ANCTNIH7I4N9Q8FQz/9Dft/++jLo/5vUTnfAUsp/eVUz9EO0Y36zpYsVcHFB29YaiIACa4COzGhGIzszrTFRTTFiXRVWfpYizBQp7HVXqqa9XOdytYGp2dlolgfSMRSsd63D+ZXw8lGi2c/DtiPXraWf11u+MPKAYCldvxcizw4UlAMTYYwUDNIL5nsOR0tKLuMoLD6r+ERF96H/+j6Hl/0p0Xu8zfUuUrGp61KgeWIMFYljyKpXE111JVKYgpqKZlxybj/+nAE6y8ACAIaPdiYLxBUQkgbNyWiUIgMeWVBMKGRFyCs6JGVogQrt8DTEdL4DY7W0I6VerL4tsK2+YZRnBOCB5xlCPU2uhv0f/5P/7JS+hmuz+2zE3d8vU7OCuErxMJwIJBgGHWe9sXAUAlfvxeSDgcLMKI5TeoKJrgGC0pdGiR3BUd9u2Yj11f/6Po90vqU27LCKJI8yK1rfnelu7OSmCGIXDthFrfse1qyABcuu4jYWvAGEQtJWl6ZsTwieXsZKIq2SMEXqym1+hgDF9SXdBnwd2n31rYPWRYG+t1zPY5SqaOeSx7NgQvYKIIDICXtvxzKaIyRsQLxpnkQsaB8qNo8JZxRRz3ffVtC9tv/ZVzE1W3vToPH6N9PopNrdnYQkqPBd5whAQHYZcfI1qbQVHpiCmooAAAA//pwBOwRAAgB/zhZuek5RD+G+yck4lSI/OdgdPOAEREMrSqSgAaAgSd23GNQUrkTsQHMs1ZpAEMo9nsiVd0lDXo05vvlQm6tKt/83pVtv1voht3X9JRuVhRm50r6/GuJiqy7iVkvAYEO7bhI8VAJEQtsgNNlqoIVKVCPdATIkEoq+Zql16v877f6m0Wm39d0mpez9mR+IUVO/DSbtVDnvpXW4VS5YRN22Ej9jQtgHiU0RCobg/kO5D/Ag46eg8TUuROlCBdTi05ppOitUJG11b/7t6m7bdDk2k7sya7TVo5eha62mnOpkt+L7r2kIABlBSdkgOTm95QnivvVmIw5CRKiAwI+lEPxYw9urx9fHz8h0KLGTiPTSV8ucGCZRxz21HlCgqtzGgGqrUXa1qFL72aUxBTUUAAAAP/6cgQtvQAAAgZLW4YwoABDLKuiwxQACOTlatxjgAEYFy53kCAAbExkTNKi+qthRFEuLm6tMYkjd3c8itk5/bp6Vf/+qozt/8nhZwmIE/9JPoYMExQim///+OHoABhD//8oJ1jCkFAwwQQw7JYY+SpWNzWAnFyajWTX////o6v//URGM7f/bxpwmQn/7fQw4TFCKb///4IPjhwcYdY/////4oonVhhCKxQ5AW1ABfYC2CwCiZ9ZyBHySaP91JG9DVCpdB5vpo/700f/62qb9R1OpS87t0QmOmnKRoXFWZJwieMWY77KV9nTEVDjwmSo9cogAVlkOVpBN9w9lrYzUkkFJVzGHbe/ou6GbX6GTYszpo//1tUv1Cp1BJLUaTtxFnJOETxizHfZIlbRVk80OxFQ48JkqPXKTEFN//pwBN77AAgCEEDYEekR8EQIC0olZUIIcQFlRIh3YRYgLVxjlcKkgazhsL8yicnzkT7qx8NBLVRb9Bt29M/v222ZuF0gg1X05////69vpXqJbL36Ohtnob/qbBdHU9z5aIuhVC6NjugAIEAraVJblRZsZ16OXFuGdBXCL1dR/o1Ho2mj8r9Ppp9/XtN9epeXv0dDbPQ3/KagwXewxng6hOeUe7mTJ2E6ivErrBkKJ6MmvBEKYCgnbIY0noHmPJf9FnjSb//b0qIH6w1f1Ff3/+9aqzUX9/mTf/UvQ3/nVqzBD09rc9V/u5FosRLWT1DAWgqVuWvlAtECWo5fySsxOgQPVlRdEJR/rpxH8wvVF/ZTWytQnt+id3/qXobdfSpVKkIf0KDqVJepCUjlzWIkQaSLVPUNTEFNRf/6cgRagAAAAh5e21AoEFRDyAt6JKd/iDxjYOYM7oEKoG4oNJVGAAFkIyyy2oFsJkcSdRRD1FYPKcGOqVfNUrf8R+o/r+7fbT8i+jozJ7Is4RJTnKYF96f7e/+9yX/ZlvVf+r6VnrUdqK1ASgBMbRT7oGtLKUp5fjBP3lsDDtlboMIyP82nEm+Jgzssn3/sqH9anPoE5M6pjo37z0V0YrT/yhj8w/eR+/021tV5yAAM1q12RMhh5LMLkFo7bqWrfUseE90o8nRKaRqunR4oCsrNoH2U+X6Gi5VQT8NU38sKw40Xfsqe303Cnr5mETNlcAAslKFlJwaidwiC8vRpBYuNoQqs+geKyPfvpwF/Ggjdv//fTonYPTZX/p1FqVf8hODDk0XLMBaAlX9yZ7XRU5TLaExBTUUzLjk3//pwBJ+WAACCEkBdUEEWvEFG+wowxWYH3HNmYwROUQycrJyQiZqBAtjVIi3Eq7lMg10rmAXcXue6pii0xRJxTqLegr95a+c+OkYl8/3/+JZBl/dW8K0+QOUNY0GTqG0DzarBujQGloqAACAE6yXaDV478GqY9LdD+7iYJdtL0MW1V3NbWAxW0Xr+JfoWm9X/3t/6eq/9Fp1GOeP7phFPSiL+y3fscvsf0MCbjck9iyGmLMJWML0RnYfrZaJQdoJlM22udGqjQn96xaJ+MxYVEHWC8VU997Rzzaa8KOMGDu+wf7Oqq83YssgFttySm7FmUJsSgdNKGFAANhxbZyJxWtFbN1qQz/JT//9H/QT4Qiz/0u+Vajol7gzHsF3SbBMhNOoXToVaas1r3Cj0xBTUUzLjk3AAAAAAAP/6cgRExQAIAhtAWVApEFRDCBuaBMIbiJlxZOSMrJEXG++0UQ9WAACABORyS2QTkijIB4Kpa9uUUxBe50ZdDkmGZ2/1F9KEf/1J7US7b5iE1dmqv+v9/pYzqignl4Y0HqshlXYojZo1vUAAdRKkZblx4ZcqZX2wnUUMmzKSjqY9aN174VvoJo9Pq7NRaXN+YnQBrX/l9qv/o5yFBoS+OMUDn91kCNUrUMe8PW1EBSjctuUEVnc4WmEl+yp0b5yyMmh8aww7V/UE/R//+mzX/UrccqfX67Iu1/tkeEj70M3mkfIqbVtShmTv+Zv41iRR6skyAAbaU3bJLlNSi2vhZjOI4uOlMVvrXZtNXwr+L/9081p3/W+g/+97YGvgnKhZ9yVXiVKuBZ8UWMnggOeWtYt8obWgVTpQmIKa//pwBD/NAAAB9i5YuSUTZEOIK90NJXOIgQNvQIyh8RYxrrRwi14AILkbklwB4Z+FionsMKOGEOGb5Ywct0rLGB3XqyjAFFd9DOtPq/10/z+g7FvVUrOiPQGXMLHqiFEKxT//X6WgCRLUo9W7NSYH2w7Pqlz2NzrGxgd6q65Wqur4X/DyKys29/+pv7e/833eltLfkYY9he4YGlh1R5v603L4dRQESo16ggD2KUkTdoFjNFGgm5s4YYzATljGTVsrnFX+rUFvzN/1f121V+oRTleSYro33TnaP/VFvYY76vfYvd9MZmLiTidqS4XQIAEaZblbk0tSOq5OblS7qG8avWUIpz2f9X1fEz/a1//rCH8/r8lygFM3nXuia0HbRC+p6/19ea+/6pR91MVB7yLo/EIhMQU1FAAAAP/6cgTyrAAAAh9M3NBlE6xAKBtaJOVxiKDlaUMEVvELoGtM9JWaQQJNN2xy3LEluSY8OjFH09NHCwrxUKNo/bR/mG/X9d07uv/YSiV7alldcrKn7OZ6IZVvm7PtZ2N4J7D4q5YHATPu2dUgCohSRpuJHoinlJE7XN08Qt5kMQej9Tk9Xq/I4oOAj9BEn/29+qf1+Xfr3jWtJKyr7VqQsXVZoXd+l6F+O+7pQAGQoyNOBuL1UC4BXOP0QF9FLuLzvIF+3cTvN1kt5Zj/EkbP9SORXJr4IztQ3Yr2/JpZhPChlJUVclnRGPY2oSBtB4eVALjcgk25luZiZE8OcflTQa4AuhFYbT/FbvA8kLx3EziElTvX2oI/mDNv9GbNfM/1N6olV/xj76ilO13rmH3J783t2piCmopmXHJu//pwBM+hAAgCFjhaUekpLEIqDA0gJaOIVQFpTBhDUQsgLamDFGYAAcAjI04IhIxD0QoUehaUUHYThCdpwBF9k0qV91EWr/An52s6/vJqdWmL6Tn8vtv9PHstGx8S9zd50ARJZiYDHSZsAEIS6N3/XcZZ8S6dZtceKRjQdfZXOj5S0Y+uOsAw/4wbv/XbJZuvyNxjpr/slI6orL6S1SMH/b/TVzGP3fjDRGwwQAKn23ElFYlklYgEF6OHsFvJpGUl5uqaVR6VX/ggr/6f4xxB2U8ZlMr+FFeQ970aibstVKorbt3qDdu36xR7cxvSkABpgFJJIKEdOXeZMFCyRhHh9cpCNPcIPM8x0qUlWn/thFPqb/9PtX/P0Kx07lmlRxGpBr1z866Yg9N+0UShti43ehMQU1FMy45NwP/6cgQ2VAAIwg85WRsJKMRBhrrjZeIKiLUBYuYwQ1kXGuoNhYmgCL124lhwGzF2jliI2fTEyWlAalH4jyJJai0qLvvroDfo3/nu9H6P+n3fR962lvIhFGbbSQT/ig0i8g1IlDZMkotcAXZLRAh5LgdTQO4ex1Jwh8FOTQjASqih7ATYqzI6CSMDZD/2wT/Iu/9kKZDuait+hPbWf/B5Fbn6luDPflPS+wDECT1v4PiAB5mRoCxOfaMIffInNrMI85866Jsg776tBoN8jf+8zI7pdB3/Nooan/782yLyU0ZBLzqF9v3wNSs9Ic52Xzu9BB22wU2Q4D1kM28C4GaLChFphiMWpF/oiYMhpog+3OtgnuhdRY3oQyxQWNR9G4j9//p6aJ66CWzBEeRvu9xepz/ceT9frTEFNRQA//pwBN1DAAiB3xbWmeEThD/lewowooWI4NVi5hxvsRIV7FzCibIIOSXB66I0nnaHDkNGIvIls6tLndiMVywMQrE96wwI2TCW9CnfGst5Ot1PnNz3yC3jvqzAOoLhu9hE+lQAASBFtlwVROHnis5JPYqro0jrj9f88R1Y4bvMeFjpi1F985/qRv/e38n8asSh497KVdNUGgK71XCsRjcTqK5QJJdjcH8XEBWNdx9NLEa8SEm1Zuueys90NHWdj25+nCRvkNW/tK6BWqOpGmRav+/Mx/LIvMGaKB4MNOINxW9k7swX2l0ddzzBO7X8e463o8nboY1UWohm0Yegx7Qsf6PVqPV9B6uFJthCmrf77sjsQyl28SgRhTvpRbu3l3Co5o4hQ8stghF3JmkPapMQU1FMy45NwAAAAP/6cgQVmQAAAiMZWL0lAAxD5rr6phQAiHl3hVgjgBEQnHB3DlAAAKCnG5BCpsIiUA4sEV0FXMS+QvVtw46R1JcUdcVzft/wEgHm0F7eWMKLGFO1OlNKcs5DAgpmeaLMtQRQZNBQwm98oeIgABCCpttgxYjjkwIAlnCizcOjWRZW2Og1auExC2KD9lIP+j4Rftc3/q1d1ZUO2uMO9VJozNSiaDhgs9iJNabUU91ui4AAACAgACAQCAQCCki//UR+3/73////2zD0//x8eEgBASdf/5AqDsUg7MLjRt///j555ZxoMmM6////5MshiGkzXqg0AAAAAYAAoFAoFAoFAooies1c4gaGcNFF+r/xQCAOb/iw5wOnX+xFuT/+quKCAp1r/8ii4mLkTkd/g+aAhqr/8u4YXxAmIKaA//pwBPvhAAwCEBDclzxgAEGjm5LnjAAIrG9gLRhvAQgbcCjDlN4BUqobg/EYXNWMT9hUtnqegqQnMXdQU5LKqsZiUNDwoSNxYrHmFEgq6xUQiKIqioSz1t+6RU/YSKypVsA0to//+ioAVqoYA4FQdaoVzG9TuHqHQWwQNosDCATpZiQobNYZebUo2tmvmuWKet3sIxFrCWetvrMLIqeRyRWVQSkqW0f//1KYuYwUAvu2BuaIjNlC4VLtyyihmeGncXnJ5ZHEkPE9LTNx7EFfmVSspWudLBIlBOKGkoUIYecpoqqGm0uew3K7HOqWS+oROL002knLCj4LuR+lyDITCXXaZiNWlxmgszoj1/XQaDf/ez5f//L+hb8zlCRRT1EsstGS63Oevqioikk5IY/vVYtMQU1FMy45N//6cgRjOgAIgf4ZV5MGG7BBZttaPCd6CMhzYuyUbgEQoGxNkonwAFYDBgGwQHkx2WrwZdYk1SdguxSPGbEQ7DMsEbM27tHzuTjRQlPLKKsExMXpAJatwu8xGCYnPPktioxszt/QSAAAIiX28cIAHEdRtuK85oFwtqXTU1AB5wyYF2IyLsaZMu8J0G8gPL6mG/oct16zl3/Qz3/84anfqKrQv9///7P0hDrsG3C4R1nz7B7TCm4PbKO0ditUZ4CB6lTB5colZ2RDwYgP0zXPDAe1pqQ2e0Joi/Zihl/pq9pR2o/KJ1Pd6b3+rrqD3QA09dsFwjpJd6vZgqIROv23q22KGRBHng0A3rJ3SVr620vo9dAsUb//b1bua3ggMddK/piqey/pqFG+oBv/oo9VXUziNMQU1FMy45Nw//pwBIL5AAgCB0Ba0eET6EKIC1pEJ4kH/CF3RgmC8RQObaTxIe4AACgjLXbhsgHTElqv2Ct97teV+A1WuVdMXlMpmWj+L5EfS+nftjP85e089f907Nf/OTg1qc+rYqz0fs0vWWfdIgBAUXHK3eIqDbYnk002G0Zon9y4BTJc87OxdiuzaaKOE9fNDH//fbb7JfQ5m1/7WYtTb/4679+dYyuUy3ZdEMU3sfj6jbRboajZJ8GeXWGIkhA6xwAWiCzSgCYfMwzMI09txpXX1/K59mlz2lFvazbJsYhlaG5FSwwmTFjAGKS4wQqEXAVqobCjGYpIGZYKkkzEeVlig54u8NJZm8xHVAo7HZbPu+vQEUt/jE2MCERC5gz3koh+vPLp9jVb6Rc7nsy9M9OnaVpTEFNRTMuOTcAAAP/6cgREdAAAAiY43ukjKMxC5ys3YgU3CExPYGwUTgEHm+8ogJ3uAACILicsjboHhBLMnKaJwMAfvd+YRSldb2o96Jn1O63YtW9d0Hu9n30+pJnJ/RxtFVxJ5xdmc1/vXdp+lD1rPxhOGliUAREcbs5UGUUvSa9us3pYmmYpgyHtqjSF7e4grbt6XZ7X1EAU3/vM+71b+3o1l/tXolR9GFHDZzNSLG6f6NrvLa4DYEnbd+M3K9OE1akUaenOX6x7Vp4+FTdrkhkd0M68zPsPKwvIcl8umyh+BKwI0X8UVeP70ixBdDqu1S+ItRF9/XOpkhVkrSkjTlA8GLqoYXmF8tAFqzq3KRY0VWfLCLlch4OYzX9rPSZZv1Z+an/5x1Jdb92LEUK8C8Y/jrYVZXWtg8Ub1piCmopmXHJu//pwBN0KAAiCC0Dayw8QbEDjivNkZ3IIVE1cbJRugRugbF2CifQABBiuSslGuBvaNRnCd7/XL2AHTNbNI7I9KHTghYdSqRj8Xpo767/Trrt/t2r/9PTa33PZ0icUdLAc6t+zu+R68iC7bdsO0i1LE4Fwjl1e0psT+WD6v2DG68wTDmhSOt6yprS/9F3pqFA0MBg1vSLgGcTKukb6sqdHtL2dO77/1//6QlNZvyTNXq8ZZMqNrQi0BY0ko7EI8TTU9RFWGIxDcZQ6+wx5KnLDs2JQRT0tQeCjTZqvEbr0dznke2RQc0dy1///1iLjlevKmXRi9exapXUkmNrnzE+OopOGswq2/lZna/sktELRyIFP6/71Xy/c4yti2M+lvs277frREdQyrUVsUCv9jKaPx1+hMQU1FAAAAP/6cASCegAIAhBMW9HpEGxBA5s5PCh7iEkBZSeMT3EWIG1owJYmAASopNtOQoUsQ7qpbJ+OxsZEGOaMREmO+kpta1qjstvzAN/+lLyMn9S/et//S8r3/MpOiN8jb/sZcHD0kWqcz9PqABAYbprCEl6a7Oobxl3fF/ChlW4y202mKY7IjX1aAZN9MDiCD0r0FLlbQWwh34PlBBVqWqeyuTLdfgB6P+9z6JcBhqmwggvquXeKKJTz69T8AQaRbnnPVY6oG/7LxT09/lgQEZ3/9GRrH/6X7EWv/RVSd1J+duVQY3fppXhqWWdsEoBANCopRtNgQAzdertkbLaissWGz1HbV0jaARu0E0RjJJ6x+0FADZ6KqXulFzqtvavsNV16dJMbeVv9rlMMKqpMqla39m+tMQU1FMy45Nz/+nIEz1oACIIJJVUbRhOAP+cq02RldwhZBWtFhO8xEaAtKPGJ7gCrHQApGUucBp1xDomWiQ/dyOR5/pcnMAvaFTjuWDWiLQ7hph0sqaS0PRvuYQn19L0DqGiz0qZLM/YfSj2UvdZ1EqWOQCKRMagZ1rCG4lK7kVknGjURmiRiGmkpUxZMrGKcalGpr0kb4QDdJbe1mxROv5t6111+dpTaiiPap2//6gKKUbUgBGJqnaQ8na5neYgs89nlC0czDJGzOaMnyf5AI3Z+6+7bO2n6n+ay7bfXSQ3v5rtVUJl6klj15iIzRLmi2JweSaTTgLkeLZG8krrOnOHncUI7eD752gHr0FSiRXr8PY1F3kMzDp/+1FZv/EJ0Np/3eqC9r91mqsMdudoYhBJpLLtxRKYgpqKZlxybgAAAAP/6cAQvowAIgeAr1zMDE9xDA5qDaSNyCFzfWmfgrBEWoG1o9An2AEC0oDt4rDW76y3ddePW5FvCs/gPTB18GhHo75zrmdKmll25KKT8oYCf/0BTGYU3t0KOShkNdluNf11JAKkewCs95xEKbs0+IPW3NhD1s6fyVNFRsMTMprTmBJK01Dtd1VAEPf+5eYs9G+dDj79AqBQk4h04Kv+1X1VFRT//6Ai5JaAnCHvdccqqGsiaoyBliOYH3vyw7AF0SXK6xtA8VbOvUjfFVVVt+dliLkJGt+i+d2Y1+V9K1GuOcpGG9Hrf/9INJSyOQLC02RJ/l3fzf3gjD1j23vip/h0q4maWvykS3uNlB0VnlVWJnrMLd2ZL9WxDcqr/8IaHKRysv2Q20Iy6F6+pXM6kxBTUUzLjk3AAAAD/+nIEd8UAAAIHLlY7CROUQcgLNzwle4gwr2mnmK8xGY5pzaMV2ABAoySACrGoWaW2m4yp9IcqPzVoa6joUVIb12CuQcq5c2Es5lrotlMz+hn/92Rju7Ns9rKc1rgRVXco6IzqBN8sCMpNtyAgS3Roj+touIdcrvgZI1c4lrwXcmq0yIJfCIEL/9Wd1tp7bdU6N1ukhUUqTNbzGs9Bxlk/uIoVSHGKkplgQCBCUScccgb4s+qLpsaKQp/uZsEx1/BVNsaZ7y0x6jthTvFN9NMT8BQpkZ5vvvW9PJnFq3WeOqW8ZI1rILlnqTUABZvwKhxooMhxYaoUz1KSWsMpIaicdLyt+dkuuB27OIOEW5w2OlDMrLdXZ6OXnbtYM/gw08I7pLF2VQCo+gwAhnUUu7te1MQU1FMy45NwAP/6cASROgAIQhM21rmYEwY95usXMeU3iLjfXTWBADEUG2xOnnAHACCzLcATy0k5aBcwP24U0fpLB8pFcqKZx6MaScLTbbajGNMtll/+qdVZEesjoz+cjsvcrakWIqYcb/Ocpkm3j+jx5AyQo3GHiSD56u0vPTe9PRnBaueGXO9O6IJmR92v+j/CAdb/m53Q7FR/7dTN9GK2y4fHC1KELFj1rpB7nPoAYW+wabP1q19bLc22kf0tipKYNB0JZxCCaCmKFRYIoJ1oREHaYvI9Ng7d0+pzEOquzFNu/ZtSf2+qI7gxhx1C00JQQXaaKTccYWWo7Yc2mJg3eNuSV+ApKCIhhk4yheelj9FcuYVMR7Y+leFAnPrb9rWRdl/KHp0TRr/lXkceGUeGa2RU3gZt/ptdTEFNRTMuOTf/+nIEbjcAAAHjRdyOIKAEP8u8A8KcAAl4339cYQApL5vx64YgB0kkmXQeQln0xgYCoYFxgJ7Vf+r///7+3+x7X9P7Q+oWYmtLf92HhRZTM6mcj1T/4gVJUxF//0ikYoBiPQMBwMBwOBwt3vvZkFUPDf////9Uv/+zHzE//tG6ic4z//yZEXhYax0kZ///igi5zmkSDmDv///+SQw2w5KMeg/gCDRSrTl2SAyaBLEZeHltS+ytdH1axjK21DV7TdWlKQ0peunUq5n/Q3l/o96iS8SwG5fNM3yfG+IU/eucb+Ebj01/4/cadl/6OBf5DU16crSbhV3RiE2lVmEY5Wuj6tmMrehurtM9SlUqsaFE1KVvqVsz/oby/0fqJLxPA3L4oKVYnxmQhT9zfk/+Cu9NfrH7jbZf/nAv8P/6cATE3wAAgfQLWBMmiaBDRdt6YGI/COB9jaQET3EYCarJp4zgARKFzIABfJJOAE2VWOGDJNXJkAR0upooTYgWGgEFiMNDzZooEBTc9Yrba5b1lWCwoEzKXm19J6+hNKLH/p6wAAPa2sUljTaDSnnsQuHaoEbexAYnyY2vCLVXlynXZURCKlGoKMqp+mxtPX8pWxWea7BqIlP1HkFg2msjln9q+39GMBSUk2slsst8Md3NXXbXBQE+hJnKl9HxefxMh9+Vgox6HwafvBlR4KxE+dWNGB5zU+6alSoGnjxV9+SDT+00Bhs0AWXC7ZKgFqzMIQOieNeWVYwZ2UiW3HjZxV06keA+YjJqK/lhqpCvMOHswwI+0WqLJnjyxDwailZajid0/tY9VTGbK3+mHv//p2NrrTEFNRT/+nIE8WAACAIJNdibIRP4QoxbzTRiCYhlA19MBE+hGi4wtGCK5y6607nvH69x6fm6Z+Obqb+mj6m1nC0t14hIXC5ZAZgMiOwLxfrOnmf7r9d3/wYqmvlNv9bmJfUTOuJ7I52hSyPLgoloOVyaNOVwjsgh4Y2qzCQ3tGnUrUPRyXTX2x/2czMjq/+f1J+nqjf/p5+n/8e//S5O1nf69p6qf9G29UYp3r1BoBQUrRbgpU8GyltN8WeWF5S2zUr0RSGx7IrM4CeYwDzNyPUZFG4I+SR5W1+oz/Xrk9v9ujI/21vhHs+uivRf9XXed6w25CbtdtbJeWgeMflMhsV35v+bTTP8V3mG07JAiZrHGRfN7J3/l5r/4Qtuf+jZx3S7fv4Z//9fLSvr19X+czb5IkI99D+i0xBTUUAAAP/6cARHjQAAAg8RVxsskFBCIlqCbwYOCKBzZymkQbEWl21pJBQ2Kc1W2HBRv0DlFIOCWfkeUFcsYB4A9z6b017M1udXIKK9yGEA7T8mZU3QF1wUDhLlXTrNDHy7H3ewcy2sqzIo9nXWBS1CAnG1QDRfWNP8yJnTE4lQwDEmYqPEMHai/bkM5j9luqdo1GLVE6KVocdhaA/o0/QeentREj69edXV/+9//57oEAAorpbYKkJldGhDkcqPkVBbau21C0dVvxnK/1q9bqowxrlo2EnIfg1kSIW8DuEarsUpPpq+96b4sfdXPIOvqXGhy1IiBARtJtIJxYVEvqZR6uFcsF6+l5kUt1U6hbOWlG55Cp3su//2tq36t0EUJfJ1X8sNSQFtGBBwVHF2ZkHedLIFnvcrWhKxCmIKaij/+nIEkIoAAAIVQFlrDxBoQ+ELrTwmA4iZA3OnoEGxD5bw9DCN1gAAEE4ndbbvRj6Y9YjGnaqz1tChtQ1Kb7pfezMjrjZ/3ckiupj67/3y/f6p0GfVf+52pce2ielQb9un3dDtndydblgkJACRyWJSW5WNs3UdNZ/Q3mAUvLDzomaBQoIXEkXGVLe25y03a9P5Zk6m1NgNEwm5gGSoPg7SaMdRO0sjapiZgnVWwAAlBSt2RpyostHWhsj+7lDCgj/fHdQSKd0Z+Mb9iowlkZWO+36ub2pt/VuL39vp9rW+56tBm18djy1r2qfTqDtFj4a6w43Wbtf//dvRmaTdsj5BXnOm30cc36wYsj2KLlsW2MVkh386RmcOcTnl0/oNhUY12YiJR+7W55JfgBTlq51r0Uf593QmIKaigP/6cAQYiAAAAgMrXemDKFxDiBuNNCKJiLkRb0ekQbERKGwZgInuAQBQDkl1rlA1mt0VjuU2BhX92sq1ESPqOzJVVavfMRkJeZaOvu3d/URPLEgdAhtPbMCPU57S79PWgVY7Nzd4TUAgAA41JI3IAxjlaihPrtnFjteyZtQBlKkQJSYfNs5roKCfX9O6eX9fUQnso/oI1l0v+YiqgMYCqET0NFy2URceAdcgAIlyksjkA0Y6NrEi3h7uSagZz7uRGVvUyF6ld2VVuzYN6iG/9OzL0t0Q2rDWdV+qSpeZo/7qIokHXwdSIdLNeHnJqcuwwAMrpsJLyGOWrd3OpZwvd19Ik5az1QYU8opyRwtj0flgh2fJ/1Z/T9nbQOvXTpFvqaqs/oq0wrd0f1f9KHY60MqQ9S4smIKaigD/+nIEbiYAAIIUHdYbARukP8ga9mAie4itQ2VHhE85AqArCYCJ7gnI3AAQEe9cV9eX0m1B0GW4VSVJRLg8cn4nYCde+YTmuQJerYXtkQR1EBICBEwVYsTMk8g+bMpVkgPQNPa6hwJJ+bAGNc0IYjR6mFqxcajQVrd76ktLz3qTZDxpUSOfJj5AYUBXTD/+46ZmkpJ7m+6XW0ujyN2n09rUNBsqnK1W/frBAAIpttOAEYJspGa88BwzuJatnormt6vrUxBKQAFNnTOn3Fa/5rrTqv5ydGXdcnTJp2vbc6UaRlbl/yky0IxU+YKYg/So3IoBPAoUglddsEAOnQ2nznr9HIAMDtKRhsrXJAZ6NI0dQmAKQRBwAWgZivsqNorHfZf39E//f0qSqdUXaH7O+lMQU1FMy45NwAAAAP/6cAQuTQAMghQT1Rs4GGRBaAsqPWINiDj/WGw8Q1EIoGyo9JQ2CTbUgMORghKJ2oHYWp2uGC8JmxagmVCl5DsyF1qeRRYZ84IYJAibFykMCoJqIur2nopwnUeSj8t99YYVYvRuT4u6sAAGk0204AZyNrs8dDqLYrMVgbOcyKh0KM86ItqCZEcj6Xf6jf/8iP/6k8qpl/S6XjVa/tNfCu29zjrYiXLHkRQcE3JLQCdpMpPNMS5UiLuL7T5/ZguAPnPVWstm9T0oIOhXZNUPu2dPf9CVZS7/wQ+85Zuqe6tfx/6raDIXM0qqf7NQVFtxqQAz8azo2qcQW74rMDNPnOw7UcmiM6XHIaamis345v/ZFV5cYS316GMf/41fr+48m1AjWKC15CGDIt8bUhpFMQU1FMy45NwAAAD/+nIEKdQAAIISJ1lR4RPcQOgbOixld4h9A2FHjE85DJzs6PSINgAEbKkkcgJzGgNE8yLjyfVKxm0mk3OCMw1s5oUgMn4iFSKRxHt+uxmPg3LaWdFgmlppTVMakTZYm42JDtXo2SG9uoAEL01JHIAIS7nRcMLdTKl4ZNvSx1oMRrgwoj4o10N/K3xEM1t+q9bs/3qjcyJq36kSkzWt7MjO6BO2Ovp116VVgCAWW5E4AFJDX0a5HpBc2XH3iCvDvjc89iKd6teJwAZMyM50QnqBCXLdvV6LZ3TJ/+X/fyHWgZ1GUj6mrY0LxuidR5EK1JZJICoR0XQKLKlYbLYzB24Zo7ocQ7IwPN0dQbPRnI1EuUpHyVXpbRq3/Bl6FptOmrkCK6IcyDTuIzKw+qrQj17FJiCmopmXHJuAAP/6cATATgAIAf06WFHhE5xAw5rZPEN7iLjfW1WCgDEVlywqmKAGAAAtNuRwBV8z1bZTJxpe6zXyN4YW86pTdASuQsz497aKgc71ZSG/86+C4JkX2FdDJp39R0vR7sNSGjcRz92fQAAAxP9wjxGJVHU7lKHaf1d5fLiMJ5HaMHkVisxMPbQUh1pp/VhZrXNJFWNEdcSbD7bnpeosWc+TUASsXre6Z/3A0C5G2A79Cm3M0sBsAfqt/a35x0mJOaQmQtVvSrq93vXLUJIQcYqjEKxlVXNvrUYzoh1XzP5LVuZr0hw7KYWLBIpSpGqKAAg227I4AuLrP3K4pMlX91GpO2KvIpZXVSpxlwUDcXFzzHehrKc6Nto5yHpZ/26s856+zNSSilovrcKiSTpWfIoYxBNulMQU1FAAAAD/+nIEUm0AAAIMJNvuGKAEQkLrbcMYAMkBX4Z4E4AZGSvwzwJwAwAAAAkHI5Nt9/+BwAAAksAg55PdD9XMDASgJNUU+xZCnOhEPgxDwHVKFehnXuUtHRXZfi0YTWu04cMjgv/4CEB64AAAAIlp2zW///78AABATJ4AcbK2z5GDYu5n5/Qhu3hiW90VV0GbmX0vntcxZbu3WNb7x5v9/S4m7tf/pgz/maVlokEgkEgkEg/37s/////79DDPzOi1MOM/aYw3PsaKyAUGxD/HyY0MY95EdJHiKTjy/8xpjX5gyarHjyGo7//bmGMfv5ERnuC8FkEgkEgkEg/y//////79GM/M6LUw4z/MYbn5orIBQbEP8fJjQxj3kR0keIpOPL/zGmNfmDJqsePIajv/9uYYx+/kRGe4LwVMQf/6cASxdQABAhxb3m8UQAY8Rzu94YgAiJUFZ0QET9EcoK50UYoeJBaQEssjSbisgtQ9b1UVDPzGf3QMZplVuz/+m9Styr+j9/zG83Xr7lXUlHq3qVk2M/u3zPzP/Nzf/0ez1dQsIKe6/8BIVRkszjKTfEKAoYBwzMg/uvarkdVZnM09FRnT/o81Styr+htH/R/T+vmKupKDwV1jP9KSz+env0opKkQAUXUsmYGDqKB3aTuCQmCNOsF5E5GjZjX7CQRrYsOsK1/kJmfu3l6av6Cn5aV9DGetQQq/KzoKy23JfUemmFQiwjgqdUEQiTJZbYgU5Y9hlA67awE2dpn06tVSgUohRm+aStzI5WthW2ZHdNkM6l9y9RPM+/oby/6I71qCHv9xKInsa0iGv9FArTgGpMQU1FAAAAD/+nAE5DwAAAIHLtnRIROsQqgLvQ0CoYh1eVZnzEwBFaBtNGGJ5gBAbUjaJTYgHqwuQxq8+JKg1/rNmjuGVNR2al345G5W+v1d/tVLeT7hyCeoN1LHOsKm5G764s0jk8cn64yFAxTWSgm07rbo25SHqV6IXaEFwMj884ijk2oZHSvLsy632/j2mb2b+1X9/0J77ei+joa6/W+ENt+GcVhZkalY5XyMUina9W2tSWMcBVHA1lS2mW4Ye66vsCqPe9lbyOJ6rda9WX0X2Sras/9T/nXnsa6f5xGjb/TI/at/X+uiW/t7P/7/Zq4tt0mAAEQpFLGiE3Vd0Blopem57DX81y9I9dwoLhZdKudvHKb3H5H3z39LAyfp4IOnbb/cgzo11/8J/RgBC3UbbO9VTAMGbVEUxBTUUAAA//pyBCovAAQCFCfWUZETAD/IC20MwhOItOVg5KhMMRQb7bRgic4AIG1N61biutaBgqWkoMkWe9YukABvm3W1Y1uHdGk/TmNTVG7tsm30aw3bp3cMS9/LIhy53yAqviFThVfa/Ac4BkEAAAgI47K0Eni7Mmx49Xq4FZ0Z+jJc5B2tKx71J3f9Hnd5Cf9GVRP9uhm6/6dH6f0bQCsubERLa1fRkum6K1P7wiokQCjri4tAoWSZ+x5Y6BIu+Vs6GK6ldGQj/SyC3rkd/f627GPQtPIf2ojf6HLqNBfyt7NGgCWkJYsTVcpVNrARofc0MAgFAJxSVspTxBvMpdfZ7aC/MrTItaoDrpVDv0u/qgx3lam/9Hnf85tTgzHzfrR9AK9+kNLMj5fpFL/u7pdLwitphgtTJpiCmooAAAD/+nAEuq0AAAIcE9nRIRucQcvbnSAif4hdA3ehBFpxEBdrdPCJ1AAEvLdaJTuzrtSSY37LFyglzwAtZ2ycOFBD6qXVXgiChkLCjZkcBX4UysCeBolUOfQw3J69b3izynQzSUoMYs6OPySAQC2pI7rGW7znYwiq7bFigHQ+E5qvIDC6BIfTM3Bipev/3MH6t4JVr/z++/2qh3fq3++dv0/6fv6KzWbs6WYa1QkRSKYlru8jkqCgcUdCvRXExeigczHv0Im0Q9v4y+aFTjCmn/9fqZtRS6/9Vv3MnolJaRawkUdL8Ylb9Tnb66ZewnJVgAAAQxVWIuVrT1kq6L+Tw4VitM+A3j705BEKro7KBKZ1t3k7/me3r7ftt+t+Jb8MRJerUhRJHpADKk32C0R3Hq9W9vSmIKaigAAA//pyBCDgAAAB6y5b6GIpvEElu30UI7WJQQVxoKSm8SegbOhgic4gEhByKXRpOxQZla72wwGAbQR2YiH6EfaKP/w19Kipqpebd/75f6txwpp0VtQnS9goe/bT+jZSIjS2KDVVYICIFkktsSdq49Qi8tkpBNaGrbvIHSEqf5KMvkmTknnerTKwzYA5/uPPBDjgAvwzF7/VP/7vxSimQNuJKGhk4b6gUE2nY5tY5LZsYTTFKq4XBQFa6w9RToPrRH2jk/pBatzNWj0MhnX5pr/lN0H8j0bzGfsyD9tx2ptxdDq0IQsGFCt7WbrxYvXXorEPi9yVpNxh1jKIA7GHCRMkwgPmYonNKYIhTYPnrylbWivNQ7dH/8v6p4/6Deij0kyL9kBKZDgxQ9YloQFFnaa2w88fAXsqe5tFSYD/+nAEQdwACAIMOVKbCROgQ0a7TSAihYiJNWVBnEp5GqCtNDEafhG4nILzuMgTPfCGn3ZK1CPOfHrcUXOJ/efKlRmKxv8eu75n2sfR29H9DOv/fT6/6G96VXbXOtlDtH860UH5zo7OsAAghuOWxpyY7dAwTT9xaqT12N3tAczCzZBXtbqpiq60Intt/t2T9H6kb9ekUkjK+uJ1yWqoAJU9wZHgQeoY7+976KUeTtjTggsFBpj3Ox4qAI3ag6K/o5kzN6aqHd2drjqzJo6dOjOmZPRn4lUrr6rS9mUmvLd7KGMqN/qzW1zOFb6n36sFJQAAUG47dYpBX2osY8kpsIAPuE9S2ZqhWdMI9FJ2nY/gZUPiOjmdEpYxpC/Rk5DEcKjTfE60r/eefMsJHacaczBVYJ/JGVR6Ygpo//pyBMQ4AACCDEFa6WEVzEFoKwow4nuIYQVZJLxMMRSga2jDCT4ElJFSOTSOQcKUf2njaaZpkgDtV7ti9RVVfMos4a2vgXyAqJS3+Opn6mDmLiPP9C6CGjL91JuDUZTsK4lLV29BvpAABdu1spjTyXfC0uINprkp7GffrvUt7Dh7td+2hw+FmV0lQ42jr32T2b/oK8GciUb+Z9T1Gm9Z96j0/vLOP5VPJggAUf80TKrCQalEWF6y+yfEad7qWGSjG60SjiaoJKRqOetrF6t91c9SvRE/ZW1KqPLk9U6O09vunK4jXvjmlfFVEdAVlqNFMSS8eiUhNZdGucIuQvcmHzyB5U3/UT2RReryp4n8KjP/nW2clOnlCeCJorU6SNqlS28nbUUpPpLgUM1vOuGy8khMQU1FMy45NwD/+nAEtgsAAIIMQVloxRR8QcgauTDCTYjE5WNElE8xDSBsdGEWtgCAyGopLI5BynRRdpnydxRYAzXY3bUdZZ2dIwULur20TerO/07U9Hn/hQ/2dzE623XpqO/oWZj2Diddi7adlmlhIAAJ1+mhDHKxYfDQShiV9MJOIHiphojJti9l8rTbrXZ/0RFI9J1b/9PR5v9W1aVkt/P4llp92d3TFodfVqJV8dFrAAkrMmskgpCnT/3qbC8XUAXMsZ1Z7rZFSlAVmUn795wECVysNRD1abcjw7Kvu1cOa5n3RysrHwwBBkKXnQ0YLncj6P5xcqA3VXZHIObDrAjUczJ3sJ1+956sDTLxd7BKhHKp3IT29QomTX197pu/4ME2oVyJf/AVrswyfIyLaCjy6UHnyDqfvctaYgpqKAAA//pyBBu7AAECEDnYUWMr3EClavoZBVOIaRFdQyysMRWaq+iRleYEFL1LZHIIPGLTR2xPf52w26Krj6HtlFThYly5rEtp6HqUFau99/0Wzf06gM5lRf7KZNKiqBqlMJhsvJPY5Ola9A4QEG05q04OFOVlgsiqTXaYDk9tkV0RyIinYotabmztbY4KRMtnMV6Fd1I7xJm8bi1TvfcQTqe5j2W96Tboh0EoqACjSjt400cbNBkJOdhREI0vcm1mXQpZtDFVxRUbP+PF31dOjo94iyMMJXeIA3Ut1TdqZxZUyFmR31KonIjipp+P7yIAAfqOyOQULWy2Nqtr/bgcmB/xjTVwpO/GqsnBwsRG/k/QML9uv9KPT1TzorKTe24kwCBYONoSQPER9JhGZNrpZQbbSLpTEFNRTMuOTcD/+nAEwb0AAAICNdQ1PKAMP8XbCqQgAYkw4XG4wRIRI54utxIiQgBhfUEc23i2XqI7enFGi47GXQU9C3I5zysVG2QqsV5/7Kpvzpd1f0d2PrYqP+Z/Ouq12tAplLLHQOkDGBdRtHrABDty21wCz3dg5OEdVS1sUCRpMnSePSOFMrpqiUi2Z2EAM3f6Mr/1/7f33rF3//RtepzvLK0EFiOsxqRESIzoAABAACaqcd31td0AoEMGHi1mM9sU9gblcXQ4lGsLMY9EZ3fVsi9umW0QkJNK6l3V36ayek/7/v9XDHQ2bE5+n/482XAaP//cnoAAAABLbslc392m1AoFcgmU9CbJVXz6P3p6W2IxwwsyH3GLk9+o88piaqpyUVjOW/tVk1lrb39d57Ob9yiz4AT/95tP//0HE8QA//pyBDjIAAxCGQhclyRgAEMoC4XkiAAIpOFyZ4xLAQ6b7szBiOwCQWq52Iw2Ro0ZJi4rJ5uxOfqOA+fxOH9aYYP2i15wwUeMKN1H4kPDKcofFGiMVx5xAsLss4VrtbQluK7N4rSqlwiJQAGWk5rhcNk5GTihxGKxW3n9lOfyEnOc/qc/v37H+qXft3fkX9DtqINZF3+Q9EB0Oi+6kuyghdjWVwrX9Lf9+lVNRIApJSTUcNQdqpVT+NBjSvRNxzdgJoaqVZqpVL38vNM9VbKUqlK1V/Kq7m66P5ev/dNBURCJ9Zg6LCoSjTsFXKDv/Z/DUkCmSEpz4ABdPVS+MK1CibsZxmZqpKZ9Sl/qXm8pW6spS6lL+VWob8xvXr/3TQUo8mgSgqVFJGJcNRE//V0ptAp0q4tpTEFNRQD/+nAEMHAACsIlCdezKzGAQwga4mmCOAhgf1QNYGGBAqBrCaYIoAEAANCg5s1k/sGx9BEuKoG0ESCihYpM/QsaeIYqETqQ4LAmsmKhalkPhghMFRYNzovGoXfsdeVknNe9xpKmRr6Bdz/vSDU/ormjak5lo74ioJV9wD3LUSnjsQfU2/HpjmZyyo5bU0rMuu3TzM32qW337VK3FfQ38upWr/5pg1vO78qrvuusX0GA2nRenQ46pV7dkwou+HM5my80kTxwzYUqpfKH3YCFLQFS6vaqwMa//qTA1PBIOkYdGEioNWAGVDTS0OoBR+9Y/99K21+FJ5uVIGIl9mrwUGS4atxLEuVCdAtMZj3RW20TVS6l/K15aOsMrapo/6/b9Ry9/pr1frT+2GIP+Ku/Zt//5BMQU1FAAAAA//pyBKGuAAwCAzXUk08QYEHoGxpgwgkIsQFSTbBDQSCgLTWAiHxKQqMEtPlqFdCfnEWJ4yrl+5bucfBf3mW5EiYNUwyW1vr9npsZ/Sr6v/r/XyUs//DFojtZVE41lFu8Z7HXev7jAAQFt3NFyognaQSkGJSGPZzGBQ6PvoWXowzfV750b3ftSPZrfb1b/6kft/r5Ov9bYIZOutLoAq2MtU7W9blQCQvlZZSowouOeNRoJiweDImIDMRw2SkISwsowcquOiTEK9yu+ivOv2ad2O3Uy769PsJ/unQqNT/Y3V//fwblkenGPYzeLf/XqAADIm0++il60AkMhtBGRozGaqzaoTZG1Y32YlmYk9lOr27GNK9q6nyKon+3Vbb/6voIbb/8K5l+0jvPalJxRlLVvKCIoTrjEJiCmor/+nAEpk4AAAIOK1jrCRBoQ4CL/SQjEYigaYGkDEXxFAirtZGY7AAACFU4tGI6VcIwk2pgen5blygwO5Z8o5D7vtI2hSXT/3VlSpQ3p9nYs38EfpHBjyz72d9os97W17fqR69FLNNWaCbUcusl0jkwec1jBB2AgFEhJYHARp0iF8roM0YoIhLM96Entb4IC7+tR5SE055pa2EGUCd6Wp1MRpz6EuF0iUSvuUODSlav1u1rkwYpK1fONUcxfkvFXpO5MCs9ddKJszhT5VcHUHRasTgM4EtLJBICO+7I6BjXCCyzNabUa/zBiwgKIJPKOPNAAAAKsUjAUpJwprJrF+t8uF3pPFUR8LS0u0Kz6PfQPdFgw9pktr7g6A1u15f8s60n13Co9zL0xjXSbe3KIsMTdKV7hIeTEFNA//pyBOSnAAyB+kDVE0ET+ELF2v1lAg0IrQFMTbxBgRoT6p2kiDQHQGIfgcAiErnGYOnA8s+zypEK7KMdIhnMJEXRAU8yS8yyNAK4W9QV4fX5TdCon/5FKp7pf/fCdL93V69f+6RAAAASqjsZcpLk0AwSHyoULSoW6IqkIu62DtoC9SFdEqhn5NyK1GPrq+tnSZvyu+opPw3LLr9jkJ6i+gd56R206u7oEoLwTcAKZJitF7C+JYNdGJJD7tiiaA0WSKUPFuVmalAA6CtjFUn08t15jr0e/Vpcv7/FP+319dV/c1cE/T2SP6d2R3yVIQi0CVBHiCJCr0jLAIwJJJstYR1OCsyMzNecWh6PW5fk7SvVJGZN1LdVSWYR01u1uhYyUGwox1K4bJfHLZ94u5FFA6y5rTKYgpqKAAD/+nAE61EADYITOdKTRhOQQ6grHTwiSwgs4UhVsQABC5qsKqIgBgKWgzMIbwKrP6uhiYWBMmeF93WsP8vuVCxOL3OJBjHymRxNwSbfZelG3rsu9Sotv6X/I/UrbIyv+nPQVyiEXfdf/SAAABHZd7LtxHgQCex4Pc4XZI4M1aLVwKDm0WSmzOzUqZ6sVH/fanr+VPG9HV+sDS5A2VG/Z2PQfHMutdo6kWzv6UqqEo0ONN2CwUeKHJerLKoNDD9w84k1GHcaKWQmpMQINBFeaaoh2XLLaff0VtErf7+dte/obyua//feDk+06xX9Pb1BcUoiSkAmgxEMPOxmfMakaM24ieKHDtKzTSfdm/2R1c9ncBKp2IjaJt/0fo6fmtRwQTSdCwF7GCj9+7Y8NdAF1//7UxBTUUzLjk3A//pwBPNrAAACIh9ebhxgBEPCq33HiACIqXdtuNEAERUu8LcKIgIAEgkyWzbaj//bb7/gAEGEmDT1vajJR7x1q4S96qa6kMTIazv/+blKKINigsoInGAvaI6A4+ePSS+ODKExbBy+wAN8+kAEglJKNyW22622yUAAhkQ6cDXtXV8Y/p64Z3d7QrHJW9qWrDiQHTIcPBkVEwUIRjzsElm8XFFrJOOEmofjGnNPZ5fvAAAACSbcQEAgZDRjAgGZEGpYzprm163uu6FtKahqVO92//tRv/nmc7f6nJ6f/kIff2VX+/9XZTsfQvl/////upkO5qLgwAAgEIHaNp+OJBLNwOAph1ftvq23tTX895rN1vo11b+8xzk/7k9L/+Qh1n9lv9/54xxYwt3IAtZyqzub////dTIdzKi4MP/6cgTcmQAAghcIV29gYAhD5vpi7QgACEivRy1gQYD/imuk8xQ2AABIbku0Jboik6UquU8oifL3e6+pyWGVEU3DBEHDhBahRSanSKRLntKBh7Q6R6cZvDq8seOpRowC6V5bV+9qlBV33LUFRiNSA5CNk0Ps/f5toj2UQPNQBLHjjMOUBq51aXQ6NbRtM1l6305f/pbf8rdH/v9PFAY+SwaXPOpdKhqt1dmz6V5YAAAgvrA5ZAXGqvWulUSSCx2nSnsH3ZuPyxG+huhCAThjNK9EcOfTj06TdjuVdSp639Waf+Gd88kjZW6Sv6lKEv0KBurpsCaNQmsOYkOX5l6czZzK6HulRBwsrNmUejRFVSTWaZW4OcvUF3M4uGHkkir5c6WgzbcS3h0Sw4K4DklGVJiCmopmXHJuAAAA//pwBOIzAAASCjnUsyET/EMICx08Yg2IsPNfR6CgcREa6+jBiT4AZUqoMVrAwqMSF/2W0c/zmGpTTuVR+TNEQTEDjiOQwMbCo5HjFRF4c5Vwpqa66t4BS77SpUvUIyjJ17bl+7UvQkAAAhONytpwAhUN3YNkPmdcjhLG6I4ZZgocp2pL9Gdk1LKhbHC5ybWartV/sHN4JUzf4Xqtmv6dsMw/96FsIh3MJUVABCtuRtNgWwm1k2B8HFpVdIY1LWIi0KaLhCV6oz3aUo8QJZHOrOqJy3m5nlkb9EflIl/+r5Wat/HpUFQ6mBqmv07rzju0ERP1JGAwjpP6Oq23QlgwmGcDmRkUJjpgnX2TJpmoYr7vDkry2L3rdLaT+F9FRaN+8O0SKdoSRWYU9XgIy+QY6BDwxZJMQU1FAP/6cgQzLQAAAgQrWWkBE6xCKBqZPYINiKTjXVSRADEiICuqmDAGAIKabjlrbkALmns0WPTplFg6DPU2qOtEwaurq7nzjTspzkU3R0en+nRvwT9qqU4VWSLCY6gJxcSzRINbkJW7/oAAAZroUAzIYNC1WVWSvShrel7iErR6TtsGc0Idn3BEd3ZE0StyTG0ZarXdiJNBH/UU3IpVOu3/iaUb9X4lk/tt+KABBe2422wiFDDoA4RQd4q2nYaroh56SKzIo4grpsiF3ujyGdDomr2Ru1qWVn/PTkREa30gLVKkMdKTIlVOCtVDaCrF9iQAQrbdjbYEw2XVtDKuLGd2Hmb/kFreZFmaBcj+GcvMkX9OnBCHgNtc4uUNVLFsX/wv8Yr7dXKMSGcDLUNt/+ox8FXMP79dvKqTEFNA//pwBHVLAAACEB9c7jRgBELBC03DDACI1D97/IEAIQwH73+MUAAABMAOGU2L9/z4/oBAIoqWq66kAsE7W7kRy8K+Tp3L8zTyz3VjZC0oi58WabcOnT7lIRnKQzrLHdIfd//QT///f5MAAAkFBwWmxbO0WgAgEIRDSBoX/NeX0xrnjkCp9AfIlBGbAinoS4o4IjkrCrFh8yBM/WjOHKfW3Sf//uQTD9v//f5MyMwpFQhL0QBMiqiX0TbCcecf27uB8K/GNllEa3yaHJDbV1W6orxDUDurmViRTR9YKKUSVX3CsZVuFO+Wc47BckFBZRpBYeZEYQamJKeRKStes7NkfIA11Td3Q/GvRAw2WMh2uoyVcLBtq+3VFeIagd/MxIpr6wUUokqvuFYyrcKd9O2NJBQWvlnpiCmooP/6cgRptgAAAgwcW2hoUbBBw4vNIKZqiEEDb0QcT1EEoG6oMonqAAEDAANcJkpjFyvADtcAXMso3jzi9swFHElb+VTi59dSFk7RFcGlHvln5XI50RcVtFXKvzB3o3Feexh4OqcJUEaySRC0AlW0nI0TB7dRphkeyOD0N40foGIZVmc/G/FLryZ8NNf2SGxT/ln5XTnRF12irt+5vQ9Z0FXZ7GHg6pwlQRrEDEQGlty3eOt1D1WcM3mzfJtjvEwp4m/qWoaOM6gRWao39W0Tk/+3y9On7aG/+ZeFZMt8sbiMtUeklu2QpHRj0LqYTJUKqSW6Hu1uH9wo+1bm4CBqqFAvJxHjeMEuF9ffn5m7df5fl6c35dDf6pMrVCjBmIr0XK2K3/ayHXqWMeVKpSmIKaimZccm4AAAAAAA//pwBDnkAABCETtc6QoS7EDoGxololqI5Xto4bSl0RogbJxnnNJEIVAMEEppumJHcPQTXZYMilntzS9EbOD8vi9tPbl6f5uvBF5/9t/zLEv2+N7fFLLNlY/dpID1e5NnpvW8gQOrY4gAGAAACbcjNZMOEJHMDEcodgpMms9qJHUB+OY1PqIu4J9fP06Nqnbq+h/fT26COz0L/0Zkqxn/4P+niv3s/v1J6QREEJuygrFkm4d0EZiU9SPJJVi+WKURRxLUJutP8G5m5OX+gv//6/Xfp8Yfv/lZBE3D7q5lkvZVptRL1/9P/V9e1CRcy8VGAgEJuSWrDjRF2eVs2Kha6W4jcHwIIg8ZqDrZ+jZUSuvKN2/mF+d16f6KzqzUM+vk7sv7H+IS6aXVhI/KJX9ym63qqYozbIpiCP/6cgT9OAAAAhQdV7jPOeBEyPv9DOVRiHWNdUKEXFEOji2og5ViBAAApNsKBDlK6iQSBht6qrEXC3/lM6+iBoGlFKFiWmACNY5tTOEvXlBbnqJbq6tbE+VfOMkUQ7KPFf1q24UK7P9OqMoVNkkhOuzB7DOJV6EECT/AXjW0HcF/o3b+Mb+nGP/T/I+gw7lO/11fV6Vp3Uj6DB7G7UR1Vhzv2XVcXeajiBwMqbJnqQ5SILlu3iqtWwGdUQNupuThZbDAF4f/24t04wH6dW64v94vpHL/mNo0Ey7cnpa9Wf/pYm36WK/9JVdW3Iqp2bmasQ6AYgAEty7VETDpgdb4qLWtx7jvEwZ4P/GNoflfGO7Z6Tr99dNtGsJkUHqA6wqNa07oUA0tcFXCgZdQWU5xpLcvclQqtMQU1FAA//pwBH51AAACFxrY0M9phEJEu3ogR2WIwQNxQKTm8QcXbWiDlWIAIAAAE3JCw5faTjw2XTJunZZGfS1gwkNbKJbiYPX5wo8u8qaWrr3ztvFuNB23EMOqXeOFSqzWt2Yexqb81quo+MbRImpABSRbhURqNZQn6Rgr0bhdwnEBuo515QtoZxBzjeM9uralvyez8OSl5S8FQZB1jXvK3lQkWebdz+t1KHIx85bJIB3AQk2nIaPKIFLlict5QK8/ikZWjNgXb8oTzmfGwdobxz25Du+r/o7cl/b6NfztG7aNHyHdUZeUGGkMioLDHlzO6mt1tAgQgABOS7Twj06AKS0isYqhPirj74KBvl6dG4Pz8LH8n+38v9DPxWn4biFDy3VPNT1G7AsapAiQhF1Cj+0a1Iw/0piCmooAAP/6cgRBLQAAAh9KW1AoEeRD6PwdFCLjiIEFb0QcT9EOIG40hRXyADEgAGS76zbKgXsrDb7xP+t7CIG54fif8fm6tx//y8n//xq/Ft69EcEVWT1NVkBiZmqzavsdSBleHRHekkw4dFPyVFahQ7bZcUlu3WhaLehQsrpH8R4i+g96ePbMfUOduFhvXk9dcv98ua3H0dOrohh0lyqz7IGZnt6vxKmpUXte+WkKlXbXvIDoAAza/jSmr1BaoxQLDeYWVynKDOpfndf8RfbEIRde4xYcHOJbaeXBJ0cyRCa9P7x9GxRyS5xnO3pQo+tcMRZC9oquIgRIkEKa78GrJtMth7VlhLdKNbkXEA/HeX69XygXvP4xDbQzk7ZS4wT5C/onj/0bki5JnGzskJeZjFR45sv6cp515VMQU1FA//pwBHlLAAACHkBa0Gsp9EQoK1ohQmqIWO1YaSxNkQ4gbOjDiXsENQYApbuK8MPcwWXp5FI8TRl+V/lRn6pMH1ypJNYr5+Jf1DO3VtC8vVv/zEz9e7GS1Hj++rLrGoVOXUNGw4mmfUPaZUDQEgJ3biKL5LpgVW5BJV1Lc3hkJiSMbcTG/F76Cu3j8/L0fv3/m9WWj6jcMwAVHKaQxltqyZ1QRb7qQYeaGlkiodRSAAW5AYkOH6NJMzNgH+WEYvRD59RP4AIPeSiG7YDmuDtzsX8gwle0Ptfa315v8f+vrqj8v2lfrLa06aDWPoss2fb/RAEABDUs3Fth/2WvFPddHugt77CWDERUagP/4hfC5N8HzYImorvwQ/Py/7eyOQ/XrX1rwXVfSNrdH34/IKid1/ufaKwmIKaigP/6cgR5twAAEhQ5WBnqEvRDqBs6LaJ4iI0FXueoTdEHoGykk5W6Ahclww9aqqj5dDge25obzJNwtNkY7oO8fNo+Lx5NFgKMO3CqFkzEcvT05uzc23qmor3+/O4PIyzos9tnuW4M9RGylQKEQCXbuJ2l9oJQKD3Mcd6UfeYaxVKOgS+svfyvgw3Xo3bp0fv/v/g0V6VFetqyIFGltsKMiLcZcv9ZoCDmtJHdJkyCIAlObDD6ysa8Z0LEqq7bCm621BZL1Gofrk3ED75ocbEjq2UE3v0bn69G/Rv9vfVmfm30Jd0aeFCC73GqbZWWyMMtcRgSQAC4sJPotUQByZmNu9BnleBEXqqg3i3vxS3GP5QO/n879uT34l3RVGpz9bOmVDNcIdK34qG0K60jHkXPTCqioqmIKaigAAAA//pwBEcuAAACGDvY0S85pDtkWxcZhzKIqQFlRZxVMQcgLOhklGIAIQCAnbuOzaEJbZscs2bJA+9HV0AlwIB61ZsIvfQjx/+Jhvxz21F3P9te5vVuZaj90ovH+hXC0eDSqdFU6kowoUdqBQAQrNw+AXVc4NnbcIT8qJXEGgEA9YrBLQvyvXlAd8/27fyr/4lrGByo9sy9NBcrMKpfpkg1GuwlIqOgBiGQAmpBrptHzlQCkoYsI+yXfJB/AUA6kvgsawEdxx9CXHP6Euf/KizjTr7f59BPT39FhRtCaSsd4RgYFSa00sxhFuxSoaAIFy78OgKhc7fBKVIwPE/q3C+Az4WLFqZ1p0fUPdnxorjAH53wiP5OXq3N66dC9q1oisyxDmIu8TMudWmc6fJJiCmopmXHJuAAAAAAAP/6cgRepAAAAh49WTkqKsREZxsqMOJeiIjlYGY06XkQnayow4l6BCCjLvx2E6IfWAW1GgRE1bcePQHgsI8g6cK6AnF20bRuTp1Xi7af0J5tH4u2hDrdNAVTDBG7iYUMGHBKGUilm+bpv74AgIQSl345/3bV+4K90mh/LcQaiHicgbNFDZ3KF8qJLYWbBi+K5OyaH/q3fsmnRLJh2XPAHGXFBNOmhI8npKrQ8anatJLJagEvsow3+C4guvDRqUnzbjGFoqo81hq2FhiogCqMJYPT6hGWU1An08o2qc3p0IZ3/P5vRTPWVM1RyGl9OwP9eyNjagJiIIvb8ZYvrXMmwWX/xQH4mEXiHiQ+VGDZQAlNeUL8SGxW2o+v+CG79OT30Q9HfQBTR9XU0KyLGSCVojKNWiRv7taExBTQ//pwBOSrAAACICNVHWIAAELnuxqlnACIoZOaeAgAURaxrs8EcAECBXcDbsWXDWEopjaDVepLCqSK36vwMypiish4hcMwXjhDCDm7zNOtM9mQxXTYzK7rI00YvmZEun/nqOpvy0vU/hiIaEECBIKl3Ah5iw66MD7mICwvuFF+FkaFiGOhmp4Fm/KF9BO+3YPeo42bqPAxs/+U/76+Ym7XSqDkmkobdCyFrlIC2lQAAAHAwHA////////////+gfWbhlscaf//54gY4xZAzBoT5Nm////iyyugM2Qc3JYi5ueIH////+OMiFBImCcIORc3L6QAAAHAwHA91z2QoSPNG2Tc/bfbv26f//////+Y6ngBiW///7COE4DAeEBuNyf///g7LsD8bnlhu7CP////+JZDQcIDcbp0wP/6cgRh1wAAAgtWYh4BQAREqswTwKgAiKhha1yCgAEUDm1rjHAAADAoFAoFA///////RWdTP/xYKg+SAYBuEz//FkLkTAlCOMiMaCp//+C4LUnJAuSoyH7D3///xUH4uQ9iqD0u5oqgBgQCAQCAfk7Puv/TX////5is6mf/jQqGZIGAXQ0//xZECNAXheMiMaCpt//wuBak5gqqREbD3///yAfi5D2KoSl3NFUBZWW3GDJeYJIaYjRhCCvMPxPUT6DmUoCG7aNxB5dwqehqz6n/Ku01B10RB3onlPrTW40JQ1W4q7vChVwUeIippTGxKySwBFupyEyX61Kl3E5rUGz/ePqxpEEjVU1lNEQzTo3P7tRC3V+R/DV18RGqgM/uLSO18SJGgq6ShrpLMDRYio8G3CmsJVuTEFNA//pwBJ0GAAACGjfY0YcS8ESIK2kg4lmIiXlk5ZxM2QAgb3RUlkYAIBEGdJN8es4Le3E/bzhzJL8pqNS+JiLUFf9GygOeNejd+u2tK5n/T0M+Z/9G1Eq8sJYdIyv6VNWM+niUFWFo1ddb8E3GZpG5MGbxdA5sqgyymcc4mDS/gegJtE5OrdeH9unTp/+huZ/9OpZqfoZS0DCkfO9toGCrCQNPO3CQ9rBUYoK251owDpALckudlg//BQ+8XkK+qcd4UOwsL+Bf08f+C6p/K/P1/r+W7P+eYSmjs5X6vXzHue2l9lq/v/ZJKf+p9KTuk4Gdc5kIsRQrMtyNy6MZGU8saC0l78YGsbA4pwR/7cX6+Kcn9D9bZ+/+Dy1Nt826/b9fEnd6mmrRGrtcHKOfW5Shpq+STEFNRQAAAP/6cATs3AAAAh9e3lDlHf5DKBu9DCcXiLUDZ0QIrmEUm6vcB6gYUjvdlttN29TqT7RC023P5QWJHg/kW16dSduFeX/f+r/oveSi/8YP7Mn8zlviDXNfLqvvQl6ejvWv/7T+cs5XCLqS7DcQIhgUTkbScqUZUwyRUjK05r5QWI5QX8l7YmHdwl6NiYCrb9G5L+r/r6nHdtvr0LUa3/xR/MMgRKh3mlN1vRUEJq2SACOWKqRW7MlQpw4aWnDI/OiuAcDDEqDMiEZ7PjHxq8fxn+ml/tt+jei3e/8r6nZP3e7vj4oSTgfEYqu+WEB6Ov+BZ50hUAJBqubZriP13WAMqdoMjV0toC82RNkYf0ce9Og7x9z2yg369OrdLJ/09U/89yupQNw0SyLy7z1zZIJs26pHItoar1u4RTD/+nIETtQACAIKGNnIaTmsQAXcHRTlkYjhAWLnnEvRHZytqGOJNgARSBZF97AvwLNP6pvtfjfiFXQqNufynR9AxnZ6Ky9SJA6DvJZtJzwpap3YpYbZAG6tzPI3eKBdyj6nOC6TRcRjM0xHTrpZa5cjjbq9Z4Xqi9VyPiEUBiGD0lG1BsrNhbaG7f6+zWH/ofmGSD/xT6XyK5bqATgnG7BXnkqS5XPEehKSDbkmYGcJveakx1i7B9r8acRQTVVO1Ep89tDMoC/p19/6ty3UrN9k/1L/ouUrIql7OisegV4Ecbw42AWsRjm4qmtGR1brdAWJYCaKUaS7yN4yTonazjPHHxzqHC1J6Jtq3Xr0L3/qKK0t6p+R/CrYqf4ZePQdKFxY+FXP60m9rP5VMXAJSTEYeAbwrBp6YgpqKP/6cAQI+gAAAiBe29ApKaxC5dpQYe1MCIC5WGeo7kELoC5oU5ZGADpZEpJJycwaGNjAYHsf4f4UGrUXfBOX24e/jX1T+rf1/o7cdX5+8jIyoxTSfbJsL7ZX9NuiV1+pHlzlvyN242hox5L9RFqV0qi3Hjv5M5CZgoKHPo7apPWoLd1vhratcqA5aaw4+yx/LMxJ6sKBtF7OFm+avodbf1/0lPpH9nhKLKPK+780AA47/1deQvOu/AF7VfZGHeZq72C84TkwuDdB/v0GNSPM5V/6dW0b/5jNoTPXq16CCMaeYo9p4sXEr6FZG9zrXztvXimyfNshuNu2RzGZQudwi7Y7l4U2PBt8f16v1/hYG6cZ69OqfkUmovbU5E5UK0xT0HopvvTGvf9pL6Vb1VIPLsAJZCOhMQU1FAD/+nIEhOMACAIXQVWbDRLgQea6k2DKRAhhA1tHnElRGiBsNLOKjAELXtvgXCs6+eMiDkQzloMRAmy24wfMzWoR5lzLrfOFmicCG1fk67k3QX7/mJ1rmTb9ejGf5jyX466V4M19Pup91FQADLtuHYMtxVo8tuAR0MnYf9oQfoC0mGQmXKhfLj1teVEzio+V5UUdPb+vm/ont/b5doAjNN8nUUpq6NkX5H+9zKAIICbck7TjbN7vAPyiNYLB3bhIeppbQKgUXEtguCG5+D4IQ1AH3wTc3/1+JPr/WZFztH/aWpnCHZK7lR0sjt7P7LOlgAACxRtS3fxqejeEyc3nr12uph22EoyjlFsui/HAl0I2DfUvX/b+rfqXpJc/fuoA8hjuUxn9qVQwd10ZVU4kt4Fdv/vu5VMQU1FAAP/6cASr6AAAAhA5WlBMKYxBqBsqLCO6iJDfZ0G9RjETIGzoNZT2EDdRIlNuC0o2UCsRzOGWGF4vxnhgoqO2LNmLqHuTjB3f+jdev9TPoYu2vsWJzHRmGTci9ZZj0n2UprZBiSLtUdeXgAsZkKWbXuKZ3IHVr0y16hLJX6d+qRvVPOdJyXT5Pj/mXLlyDy5r8wLjhhuXx+U3Xe/20KOMHIsDI4hMe5jav/9IyKMIptpwb7BZGHtcuovHO/JORNoNSyTmyvKvmBC2/DIUZ7tkTVc3Ui6H+9H6G3Wm/1fjxYx7oBjkfVAaA+Ve1iBric9wl5TBKacFSACgjgqezIMhGbRj+aR5S36EgKdh5v78Ce/DP6dDd/btszcYuxP0WXi8aSq9ENvER95boQYEgFWxyAcXSCyYgpqKAAD/+nIEOnwAAAIPQVpQbCmURGPqc2HnTAjBDWWlnFGxEiBrnDWU+iQ2lYTm340QQPcwL47YItZTcX41sKFSLD/9egv04kO1fjer9v6tnHNolUtRvYr2TY90LldnSMcMiglIK19FzFNuIsApzYbz3OtnZ9FJkCvgJhoeqN+ZGX7lDTcbZrP8nwXVwvWJe1X0bgm274gEXjd6IrK1eehEU+JT08AU4Xaxb327XuN6AoA0mSQk2pBzCaRrMAGIw0pV963LVtRC+UfkP4nbUbPiPqpvLfzBn+/bhS++iP+lPzD66OYE4OGCidYNyR7QvKptirjapeBTIDtuGlUAcagwauys7bnEf8E1ftC/sJkemHSx2/PxXr0fk/oG9/b9hUubK7q5fwEVpxVo9duY9c4/U73BpSmMYVfUSTEFNP/6cATMugABAh462FHlElRDaisaDSI/iFzlYaG8phEQqKvo9RV6GCGVBJ3bjwsxHCndEOfKZCTy6CnCIOtBz4HT4NsGP2fUfRevR+3Xk9E1BTo6UIulhGog4dFFo5SFOC+jvjSq13sFb6YEiERBLbglsUGSAGfmHXfLS/4Wh/P/nUS8LH2a31JzdX0Jz9PN0/2yDtoltMnTEOlFciiPUl7KQev/22kRFSd73cN0AkCElAgK4m0TwOLbFI4wMcl+LXgIPxgJydOrcU/qG9+nMPxg3n6eTxVqpyaIUXVGVHFSiQlMsGl+iikXNveQqFSNKIAhAIKku4xTPaW2fI96V2HqTJuoY8q+Qho2S/7d20fEgP26NqvE+ifp9e/9mOXilGlYnvJUcaj2TR73orLSfrZYyp8PpiCmooD/+nIE1C4ABFIYNVdIb1EcQiWK1xnqMoiU41J1hoAREx7rXpagAgAgARXvMVWFzLMD6ndiMa1vyXiM+MBQSIYMTM9sq2Lglps2gp78/kb9//nlu15rprfUkOODIQQGJ40wNDmyG7u+3Q8FAILt2Hd7AAIlA+INqg+Tz/gOPkJfKCJ4+6vobyHp1E7me2pf+n9C+LMEsRNhd4QJB4iVW8yFkPPpJ1W70k6xVvQAApKBhIPj8qzoYwSxjl7bC4RzeeqzMSpDJKgYBQDlT1LzF9k60xhu7aj//yt/6XW+y+tBq2U1XdSa+uZnTNRUXutvx7IQS5KTFB0kFbQkZ7wpPU3ZuF+WoYO6DbYe9X52eEoxqvhmBULTxObNLZUWGzOnn6k/VjczRtVRX0WrpMTUqZeeFqGMJoTEFNRQAP/6cASmBQAAAiVN3D4c4AY/5ftix7QAiK0dbHzzgBkZIS0PsHACAABAIDAwGAwAreWMIiZxsN2rDe1gkH5TSg9/ybJX/olv/5gl7f/8bj4mCQaBGL///xLEwSDYgTIDQt///5ZCCCOXJkSQAQQQQHjeJS8GLFmjxaeLpDxc1oHmBqJZWtBiwkSSV2bLC6TjVfo2yVNmZ//yUMB7kgTiv//SNkAfBP/mn//5AAJjbj42x5g5S7n2rMu22qsCIOPFz5rVVC74/9FV/bTtpoRROzf///+1R0dIrR6sYyHHE2OB2BVmOOOVlU37nHKKDvSodAguO0fq5M2KHGFTVDLrc3L6WzLChoAswg0ijKbH7IPrdGHD2PPacReid6IKif3U3WdOzTamr1N26///wm6m2btYObLM//WmIID/+nIE6fQABgHvPNo47zlUQShLiiXnY4jhJ2Jn4OxRIqFsnFwcqoAAAua8IqgoXqw+1DRh8F34I9hbZ52XVRhKZN8adshdWr19vRm9OVbX/20/Xjzt9BNfQejoQr/xLkxV73fXAAkIILikHytjYwanaD2bv2D0xWY2XLW1FxfV6ls/Qtq9G3akoS/UzKkP85HsmnVV+/26PgVehrL3a4gArVbN/+u28fEhPYlg+FEf05X7bGn8ZkPjnZ6ansYH5uXegR57UG92aSBDMbZvRvq2a2d7pZFbv/zf2nMfoyR0Xf//08dcQLWypRNVa4AAAO3YFwIDogVJ8aju6+Rbwg0aJbszOcoLbZUWPml8ttaWmirTmN0/zNS2n91m6azmqjL2z8086IAPd7tWhjswWDHvAry3/JpiCmooAP/6cASDTwAKQhJG2JnnFERCSEsaPwdgiBEnYOecT1EJoSzcbByiABdtA9YZcoOgoSUTLahnTDlmhRgS0WaDFyI652z4gyptRtlGUUCPu2LanZ/V9G3TyKqq9U0yv1VJvVmzPRB//b/AYAAAAAcmAxTruFIFCRkLZFbkWvqPg0oFLlImEtWSUCXjz2bG3aYBHvnv0/y7Z+f+vT/+v6e+BZrOqi93R6hYM0Q6kMkwzvQ8XOhfiOXoBDaGxPXAyQXjkSR3HBcOyhJ8RWldRbjzyoBz1aorVtqaOTDtsvJJ+tenm/1k5Mj///xXoOiAAKW7BrcQNZ4moY3XI5co9bqGBAlux9BnWUG/C/473nir8olaNp5rz0281/O7dEt+Z9UqaBTShgz3aJmRqYJW/u1piCmopmXHJuAAAAD/+nIEA3gADMICO1kZ5yxERAg7Si4nYIf1G2JHpE7xDR7szYEqOgSpbOPrmPCwTM1XOEXuyYe/KkCdc7wc1iboL+dmoyF0u6D4IN6hjy6My8SK8IkmP/m/+jVT/z8Vq/90bpYsSLpgABAAKXbjdKJ/Kxi9QeYUX5wAHiM4ULVJZmS401fHu9B3+bXb7I71IX20n6r/5upuzU55aUBVWSaIz/lDfTYo6hWS5NBug++PxolB6srptL925KT4O4FW3IYr3knVSYdqSbc9Rs6OXOJ/IP2s3UERFYhUOlC1WqddFql5f9Ju//9F/xYBUuvGO901NdY3JsNs2+Y7/ysLGr8i9JVrHqNwNqvroPJevOPVrfoGdvZPl6a+m2VY1T1+zKRgqboS9T/rPZKFwJdMpiCmopmXHJuAAAAAAP/6cATjYAAMQfU62BsHK7Q/Z6s6GeUqiLTrZGeY7pEdHqzcso46ABctw/C4u+3dTdb+5xWm9Xfj7DphBmMAHyowJzTYhDWgYztU2mJBmvI9b2f59B2nqdu//zub69TY00j9/KdcAAAABTW8XXCRjj3zbJeNSb+3gbL6q5h7KzVXh3Gv7o9RL8QN9vqdK3ZPd3djFtN1W60b/o1QXxnp/e9r7yfvACd24+cnpGw4lrVuOfa/FrgzSSHIjL7hp9cDjhO8ry2jzBL1fJ96s6dU1bb56o6P/7szrR/U02xotQkb19KyCREpSVrTXAAFd9x1j8ngqQ6pgIe6dpUPBS4eFONY1Scz16XTIEfxNurfIPPKoJdm07oYyU26REDCCHPplJrgqJJYCWwnTq4MvdqGIWjcmIKaimZccm7/+nIEG2cADAIXOtiZ+DsEPgg7NyYlRMiw62BnnFEREyDtHMgJfgC3bcPjBPViwuh5eGWK6ZUuM3gHok1BcZhyOOyos4BmOvQnqXQRwCnlHxs0o8dZl6tq3+r2RtPcxO3+0zQX2fd1ZJcAEQpd8PtCxqoh1BYsT6SiPBlEGAJqxtaBgrkLGtha+cJfs9HX+rduz61//uvfM3xnAJruIlN8o1Lnj/eHYgAzXAetxcmvQJJ5K2i80mYG5RTgb93I7csgwe42xhqAL1L49ldQ7Tlb/1KIwQl3OTzuVEEupdVJV9u376BLCYyYHubUVWAEEN1wCew/cxIg30IOnLlEHB1h9o7HT6pYO8lXmZ7H5bLaxDdXEhKDhw6/hERjvBp0VnR2oQpkoiXk5f31EaDyduDfyiYgpqKZlxybgP/6cARzxAAMUgRG2ZlxE0RBKEsTLgdSiLUdYmecUTECoWxc9Z3SITu3A7xKZwKQ4PKEnVNqNCfBVGqBMJVNdHZqY6sj7WtQBT4UayK113QOSDZ1M/OxuntpZ6PT/PhFuYiv/tenjoBAm3A7wHisA8XImAZxNmqBfEaLRdQiaX0Br4QbcQq5Vs56c9v/We2Q6fVlR89FftNc52/zT6BXKG1NWnHWj6hcgACo4B9YHnA5/mrfZTW0z1kEHLkyOnOLSKJKvgEtOeiSx2NBZpoAtRj7+CRYVql90dlBtt0aR7V/4LQWk7OX2qUaTdFjKqAAqenL+tc9ojZDH9uAr63qDxMXC71Hxb9U2fBf+UWrVCjUfERff9TboMV+qlTq9X7b2879C9QmIMiW/UTkNxcymIKaimZccm4AAAD/+nIEuuMAAAIWPNiZ+CsGQseLNyXnRIho7WlHnE9xDR1s3JgdggApfwPvj8ccjTVMCVDPJD/X1DwGFQ6akJDi4Zo+vWy7gf8X3Zf6F0Nsvp1b/q7qQ6KOd2Z2sKjQAYojw0zXdX5fzDIAAh3fgedjBjC2L2ampG+9pQ7R6TahdK1GOJD1fZixXif8S0q1/scOnSg+dS+6q7P/9tDrb1Rb1BA2Vc5SwqSrXOmYBQVAJdcA9MIfPk9XODIdOsY+T0FtHnFSskbljqDGozq9SWpaPgOeP8M9f95NBEwtOyDs6lLa9zwf/+Xicee1KrWsVkMQHf+B64hV9sQSC+nK2waAK2EVajKUzUoEzb59l4m/YjVv6R4ypC7mPtVFY9WHGNdFOPdHpqttFd0GTJMCoU9ZngZMQU1FMy45N//6cAQcnAACAeo615gvOHRBJ2rTPwdiiNjzYUwkTtEbISvc8ooaACl2AnKOx9ZlLVWQjH3m2sjKApRY1ZGSBrJExvBDKl8MNMaaCOvHH7L/VqkTnT/3/V3ulGq/pKTUDf93agAGS0D1lEKKa8iMfN4m3LIzN4x4a/ErWFuVMVxXQvqC1avgsQvewVaV5Q+rGfucazIDDpjPXXdNPdNKc7/qNnX9CAASbcD8tMhotNEhVPdmPu298fcoeSGERDdUy+PSfn4/vvQDuCwR/wjw9nunj6t/KGBqaKuzPRSLb+uZR2nDWDGIL9a2AIlAQAKbcD2oLChGpHGDUhuj2bHU8MWFDaYqkcrtQVbEnq+Ad0zGevOP0fTw+CLnL5Ve5kppKdj2dmZPW7WATTj+yVghNKIfHJiCmooAAAD/+nAEbG8ACNISOti4LCh0Qcdq8zzFdMhs62DnnE+RAZ9sDPOWIyCABTb8VoBm8DSzvGs6/9g1AZZbe1IybDR2BNHw9d9G15cr/6Po1C/Qzoyf2dW6b/VsgrOj9MdrGmnCy6HDnSpE0kAp7cD64jTDSEsNUJC8LydmouQaSECCs8wuuxMdhD8ANGlGNXjko6f7/qvU+8tDkV9yp7297NQa2F5rjFdJdPFmDhAhzb4feB0uWD2WGaE57gOWtxQ8knE10Y6O1LlsR8eeaRKl23Cf8Nnyh3btfBNm+jZmppdqM89u3UjUHhbjuo/Mu/3gFT75DE3aVNQ8rrwkN34ICot1jwdxM61GS1RHenJ3RsIfzLR/+ZqAnf2O1FedmoVpVza/o2LNO8TBF/vErUve9TEFNRTMuOTcAAAA//pyBEWWAAACBTlXvTygBEOnyxennACItZVyOGOAERqyrosSUAAAQAcuwHrGFVEhs54MPYqJCe8huHuQXA5aD3YSqJjsQ/HKghUaEGpwtJnEmOq9edv+XT/7HKifph1AiDYjd7WoRAAAu7YDdbFjeQ0msOMRlztYn6kBnjQwKs9BM+VIEDDBIenW6NguMa2Ol6Fzn0559xwW5r9apMHf6JMVP19WeOLWo+WBBJLxxhgZWb4nQKrQFk3LuZ29z/1st7e3t//zzjKN/9tkH1FZhn//5w+QCo8YcNzP///GxF3cfc/Kf////lVPZWMYyho4AECEEEndqQPMbf2PKTGlCwq49wbsuiK6bLm9v///5zEo3/22QTUJkJ//+YTFAiJEMHyf//+HhV3cTc+M/////VT1YjEoUQTEFND/+nAEt3QAAAImK93vDEAAQ4V7meSIAAhUV3WgjKChBA5w9GGcbmACgXCXW0W5swHhweqE0IXhQjMCZFgmTs6Am3qbuphfopS9f+ntlb4Vq2X3YliyjrtAGWGi0i/xWtb976ztrFVLPPBqSgCAg/kr8lkcy4y+OOlvb8hGhjMlwTUd2yNQXntrN+UpbL9Ontlb4Vq2X3YliyjrtAGWGi0i/xWtb976ztrFVLPPBqSoADAaZT2Qd1wJgDmFbyiGC4miLQdno8Bh0j46ubrcGV52V8RP9Z27w7KqPazJVZLToKuDt5KBUuXkgVWIkNQw7lQ1myGlJE3YinK0oVABzmZ2hOgRsIzEWJiNHdMVFqF9vv6tYks5O17pH6zv4dlVHtZlayWnlXJ2wKl3QJViJGw7rDSYgpqKAAAA//pyBNNiAAACDEBfUAMofEHoG3csxSqIsNdk5jyqwR2xbugiib4BMdikq25YuCQw+YmkuCWmJvTQ6cIjolzp6j9V0f/3dvVqHX7/Mm/N6lfMLbt9qIbEQl/QLvw6x1V7mMh1sOlu+SBKCpZJb6hJWFxAODtkvxPLVWEduyC/VHoGUH5tXUwi7+FBlS+n9+qfZDehk3fN6l6GopX/RDY0pv/TjPpq1XFq/RAABD+7+a+QtHMwwKGeeTOEttiFrzaS3zpexhxARHwOtG6Pd1Amo3R//VG/u/5FaqEORmN/nH6rpBTu+j3DUcD3C//rNdeAIMhJxpOywziwAcrLw1AVYypmQfsogPx0z4ijK+A3V6AfT+6F21Vxf6+gs71Sn6axHv/8H//u/1b9d2rr/XnaiKrsZqJw4hMQU0D/+nAEyBcAAAIOGNkYDzhwQ6gbzRRllYc4cYOhhPKxFZwuaBMUIgEdZv12SW5dXLRxRpoq+7daSYtU1Ja8LNHpgtGYY7e61OEmuRX4u5Wl2VtKxvhqXHetEmz9bhAhXc2vrquhEM2VvQACAiQSUabl1FYWMpM+K48Y4MKSYrimg+heXGj15gF6tU3/tTtSpv/zX1/0bQ+yfz2oAr971G2TSFbdqyOuioao9bJAJhIyMqWxyb6ZhBT3NqZdEIMTS4ZIsDj/WbIcGumOW+Z0LMa+Ot5d0Ms0dWTf7HbJeR07kSbDBUxKQAiyCprLdTpYQBlH8Y+DVBSxGjPahSEe/b35RL2lBd//eahSnK9Oicilc1/pmbj2s7XFp65m7YnoI6swSSfJCgWEb0XAJMQU1FMy45NwAAAAAAAA//pyBD42AAgCFhHZmeNLpEOl3A0JImOINEFvQCRB0QmObaSQlg4EmNST7bXCIpotDMvIywJ4+2wao5sXTuc6INs685PqGCzBRxj5k9QbPuzuLFRB66LuKGZghVnZu9V1YYdT+9V5uhBaAAovRFy2OXT2grNOmNlhoipCjQ72pYj2fG1VrIhUzZ33/9KSPj/oXwSN9gfl1NultC1J8Y8boURK6jsuWcOftUpCgwGARVlt0B+18wjpzSWy5wRzE02p3W2oJwbU5Ni35Z3qOWKGo1MtMWoyp4PJLg1SG7mOINN6e1vWd+XOXgUWHXqcHYAA0G9K/CxPInFJ5bWp+Qn/Eq+mkrm5NkeMDVQdiVkZBgCmdfFIq1VT7mP5DO/ulFP7g0OIpVRlcut3bzz6Pe8/bUmIKaimZccm4AD/+nAEoTAAAAIIQNzRATa8QUXbjSzCR4isuWrgJKGREQ5uKBMUPoEAxDSaTc7GDLW8zPWMd4RG2fW/N36xWQP3r0in8sU+ZrEv/vl5//1/5aZYWin9dn9YxYzKvlS7lvV6n1JrPdeRgAAASARTaTkp5o0VkHSuqO4R23bMvUG+5uCHmGwsyMigWtqF//T+q/qz6D3PtXKypY2rxlPzM4NUulrHq/RoYzoACIXZLsHitJS5NNZqsJ+dRszZ2Gy0HK0JcVxVAxcgL+b/7FX2erN926D2aai2kQprXJFXaaUDBIL8Uw6o+1+bWSkll69cAA0hltJuyYnqAPsdO+5PLAc0UqrhY/ORWjB8S1CSiiNQV9qj/r3rFujf370i1/Ss6HExaiJVdWw/XRCYCe6AGhoN2RyYgpqKAAAA//pyBPydAAwCGytaMWgsrEOF2zc9YjiIBQNqYCRB8RcgruhhlsYArH7Z7iQ36HmiMTc6US0CRdqVTKyu1FIK1TBqGjLSKIogb+IvVvLsr9sxl/GnJMooN+yTI63HHLd1aB4q0OVkhV11MAAjJJbfXCkhCnKVbPSoWdEFqildFfWDNyqNBDzG1Rk1FaGyJRfvq/T/6o3EPWp1gdl1hPWVawhGi2KK6nddX/e9llQJTacC4l3JuKUjNXAU9IFX87FVGBtIYMEarex9O6aYP/7M+m5f/7CUSv9ta0eP+nucGjHFbHPMsrALnMIuCckQ0EHcxy2Sj6wnFoLnzvZ+4K2BJP/HQvG8hucvxCrwX5BFMzzn2/opdM9/pKvy5+uh4FIhEMqms323RB1q/gmTe1i5INIaSTEFNRQAAAD/+nAEuSoACAIMGNuwCRBsQ0OcPQTmX4iUcWRgsOHA/4xtXBSUMgCFNLZ19Ur52aZ/HvRNQIbZWpMEaEavJa6gxUWt/8pxSkcCAIKqDEuD4EOPwQwwEK+TLv+GBOfD/5Q4cLnycEC5+kGJ1uOX2uXVKZMrSoPUCDKJRlLNmERij7aXuphBOZFgA4dvV8p1UqDBh3wf9QIYICD2Ey7/UGBOfD/5RzC583BAuHw1anbrILjpXSoz1kfFU6DDk4/05+tmE7IkeYtOLU5+tUDH5VzUfVLEtT5X8s+4qS7QSKufO7QDTIsyycthqQcVCKCvgrAwFKJNy+S6SgGl7QNXbNJErxdpHsiSMOq+2/eoam4MfEr5E+FRuR3fkaI3rU8NJleht9XY/PSplQBgoLuO8qdTEFNRTMuOTcAA//pyBJfyAAAB2RjYmA9IZEJoG3csIoaISOdq4KThkSSga0z2CPoIqNySYx47DHcldInVnEIv9pRifGX/4dy0ukq7g+7qZ8q9hvUL8Pf6r/w7O3Ya1HtQ87V9l1/O/pp6j0TAk9btusugWEEs+GtmY3jX5AzvMoqm5LXeQT+7/+u+iNR3+j9DVl5epQFti0Nb0eV6qIPWZFbr7IT8skjLdR7WSrgEGptvxZRaQwc6fF2qsVWoJc5uVolB3FL1LTGV0e4a/FB9Ppx7oqd/2V+76P9UT0OZy7cSoHCVlNcqoa2PB2lj66wAZJKPeCHY6C+E0LRDqBoOkr5PqlCieHBVi96fYTLzKPpug6jYq95xXvIn/5Uo5hdm7pQR6oZTH/oJfDGe6ffbUo2p2vW8oVbQmIKaimZccm4AAAD/+nAELXYAAIIQLte55hO0QWgLJz0iKoio5WVHnFCxC6AsHPSo+gBAkuS4fA9mGY6ieRYDahizuIKTpJDrGrA0neI5bSxHeiHcGSjVDvDiPeR//r///1DWO8hXY6J4+zKfcwmPza60kIAAl23cemTzuMsqLKTMHbRIN6H8tk7dHEHeo9OVfYv4ID/+pNHov+jNwV7NT83RtkddqFtgJk74uRS283ESGTxF0AAAgkNtweq6YIZnwpr4RsbNEH28eU1KVsyOUWazwsXrz4PDr0ej//O3ZWr/VPKvzdagWqnoDW5c9X3e0OkAhLPMLFmMWQIuSbD5yXDk5G+0E8kYxZoL7Rz4D7rzKMftMzf4P6n0qW5xn6htRE/q33zP7dTTf/0fvt+sxFnDGx79oSrFSHczIuTEFNRQAAAA//pyBIaNAAiCDDXWmeYrpEQICwc85WaIoK1m9MKAMRWbLF6YcAIEGSWj9nEZciaEvSlHwv3TXBIRQzQQGIpEuoMUmW9SLYhqWpKcgJ+j0/5X0dW2/L+m/64o8QqYO1CsxcrRlGRvZpgICXLdh7YL/IHwTOVW1hMcBnRCqI6ONdpQOzi5R9B8a7zWVpx/vCYhRrd/+2vpq2c3V9/l6m2t9VYuPY9xPy16MXQm0gJbjkE6Q+cB4uqObOpriW9Ba51LZhB0uovnejUXegwJn+HhRrenEm0qv9IwpMDFM5FhShjYXaxZg4wiSoc83fGIQipJNwBd23FK6PVwbB6bl6VqxCjHle2qac+ggIplFL0evN7Iwkn/QjRlb+2j+/99TSzGoc30NnkEW8RnWbqOdusYSPiTZASb0piCmor/+nAEq2IAAAILW2SeBOAGREisJ8KUAAiwaX9ckQABAA0vW5gwAAAMBgMhkM///L///////++3//ZzDACxL//+g0IAPEtxIIGbTm//xoWPMKkzCGe/3sx///JjRXOGhA4yT/LAGoNowAAQIHA4HA4HUta5SKiQ4KAG2/8lPzs//0b3/1Y4uOP//c+AYH62/+4gKAcXihG2u3/+HAGNBw4X//g5EAZcUxYEIrEwABLNKNkkQUJFDyOMpzq1hvMk2isaqJIdmFKiexrdrYM2bGHQ0aCtUJPPIkRCsYKy3h1k9z35S7FWtucgbWFULuvrbs0WADG+VKHAkLFB/V5u9GfBVVP1lRls+OUO9/yal5lr4YRPGSUfqCTzwlyuKy3h1k8yg9+i7FWtucVG1j0Luvr7NCYgpqKAAAAA//pyBKBtAAACFiHjaMFMrEPmXC0kInmIpMt7Q5haMQKZbA2BnZgAu1OSNztIg8objaW1Fwjw2/Tqen9YdIR2YRKvJ8z+5EAvfKiFhZYgmP0mOtKRITIFz3nZasYeJREr/ER3nv+S//iUAJMRpJrNkg8oDLvsMhkc8T/WJhHPky7QHhzNF/C8E7GQEgoylL/+5XR0MpbzUUBbQU+MPIkVfBbEQa1Hvq4l6j3+JUgijegYkUmCwYDKocOyTMsIubzrOUIu6To4eRf9/4q9EvKSgVI+HdFR1dS95kPuavMrc0GGkhut1WhApUF3GUor3b79P6wBXSwABBGAISorEH3ay5O5bKs7QiEPmEtUoVyUzNSYZTxMutqsbrfAdxsLixscNvrs+v21d3qsqpjJJpUXf/MSaYgpqKAAAAD/+nAE6BkAAAISNl/Qpj6MQwgbR2BHbgite4WkmKHxDhltXYCV4hAxsJekbdCQDiyVK7zuoT5KJmdP2df9/48ceGI/ZKJS2a6nZr7X+1FbdSrsycKn9CDTAozspq/bV9K/oVJn4qxp2kIAA8+/6CILhcaNdjr40tJu9jkKKUZLAnhEzXWj0Rnfqt/4nlC6o9f/9/1vqLGsrKaVCRv8xtf6eeX+so7Z+37vUjpCRUL0ib8jk1iYl/LavLbob2FprNXUu69V2TsNf+NbFxhHGGBYw116/m//ktt35S/R/965HSqv//Tf+23/1RvT1QWLTFB4CgTXU5HjCRTE72pJa+Pct4Eqn8zxpcR6Jd5qgNS/fD3Fh6HiANPf9/U2NPYSuZTh0WHkIN4r6P3wz+hsqn7s73amySYgpqKA//pwBExbAAiCCiHaugwoVEFmW0dF4gyInIlkbBUtkRIZa82RiagAYBrSkoTyo5TykJl0d81sKqYGLbRNn7pn21b+Ij8PjToMhrMYwi3uR2/qz0srr45Ex8wKVvnk697qiwBniEioowBYJjiggT5EIK1I+guc1M7yCiDsn0sYPcgJtk13Ri/b4nmIqKrTvT7bLL9yJnUIRzLLQXkYr9ZGDykp///umO13aAK22IWJpwSmWU8vfCKwvvNR1P2LsIsRn1q7a2nm9Lfwp8nHFpqRPO9RIKbNrWjQneChyGIlYs4QJ+5f///rI59iCFV5IAeJWYsTkwjrvlH4/LHOvY27eDTybAcFs4wqFxkXLbpEadhTbSgE2ACBKuVBq9tG1W/9vPv8f2X+YVLrR3P/+/U/2J1oTEFNRQAAAP/6cgSjewAAgghAXlGBKCxAY6sDZKNgCLUBZmw8oVETGW0dgwmCAGGpJZxNsL0AamwnmVAEfHH9KrSplq7UzMq/iDYKB5Tjjsdii/5r/XmI2inIy0xNuv9/7o58a8VhrMU7Eamf9uog6xbYZjGgYw+8xVgqex7RVKojZGhZAPcNlHmqXteF8OaXLz4FL6460q0qUWT0mYs1+6V3p6yDVOfbkH9n9CP9ACsZkpVjGWX3Ui7Vr7ONyCfODoB0o1au+qzs/9/4MKJCgAdhERNGscVVdS2aqftbi1P1///77qIsqNaDm36Piz7wySYWjkEGSOWlVctgWMbuVv7q7u6SPi5xEGadqrvyoDPP+/k34IrmYyDo2rPns11/KxtRk3RcByNqvvW5P0WBNat4tV6ZZU4LPTEFNRTMuOTc//pwBFdQAAiCFzPb0egTfD3D2sNoZ4YIQM1i54hvURONbB2ECYoAAPQRWkmxOVuaPSM2w7+vyd83bLCwsTN/uN1j7/mW/78mvDgIWcx2dfK9PSzN6/Z/qVB2iOJhwK+U+7tCotV1u7PUAKyaAIIocKQvbDKZqMSeZlsajETAD0St0j9trluhou10lDCAYVD5b9VPn4Q8sucUCzQgu3KdG7pff/qkk2EBMkckEdA9Hg+nKtSqNstuzcJhE1K0jMQS2vnWCNfqiiOpMO6YJpMhy3zzMqxl5+fqFn8XTlSmCP0p7nV//6//o1EBMicESpIlNnf65N77KvvZ6aaU3ExwJOjG6K7Y8wI9WZ9lXdMU86fABEmNBBwSMuF+3frD74QiG12tyiWK6F///rvvppTEFNRTMuOTcAAAAP/6cgQHOQAAAhIg1ZsmK7BDqCwNGGV5iGzNfaSIcbEQoGxdgRWyAGEODAEAtMzykjLZnKZe7diETNoHJFAl97ULA6Dod6klm5XXaoZ62YMTtihlyZSGMJxn/vh215T/llLcZZLd3//6wE0g7Y4/tNQZueXbL/ziA+SMJAgmG2Z9bWLpLuhy/jmxUSR1Zi3mMU+r/f2n9RBGOjYEfr71/vK1lExytGRzDx1eR9IJRYdkblultD2Q9JLTzzlw/tKEaJo45Fq7Sujl22qhUenFSVP0af1aqb6csQFDWqDB8UHOAZZ3RyRFoo49MUbvy//kwBAmyygFRDqQzA3alBVq8yv5kHlJNA0dHpWuMmrKRm51/HTB0FZDCqsZhXejXN/SUSZrCEil3IdOj1tG6eSeV41qk6lJiCmooAAA//pwBJdOAACCDzNb0SMTTEEmWypgZWmIzMlxRSRBsRQZLA2AleYBUbkVJE5BOEbdc4XLZZQAGiYV0iN1ey5joQIj/dX9yueDFVaWuV0Yiy9D6anPOszpsyxbVZ1fbiB39q5LLr6EfDIAAMgGJBNN+0FzpN3sxZqbsY4kNG8rCKZ29PuhT4sSXp+cMbRxosZ7qQtW//+T1L14t6fqw1op1g8tDlhA+B/Z6gIB6SVkcrB2vdHW07+egDZ5n4CjSpfudphkWrUH7vnckOzMZq7JvVUdrrfyuvtP2BhupOR8kKCgT3RmsGhxIkYfIrlAq/2ACJFIEh4NfCH72eorjlZrXhCAiZnhD+EsjliV1VhoP8TbKGoKhyjC1nV1aq2vv7O2syvsw0rVYxfdYtaWM6lDXWOWjPUpiCmooP/6cgSBJgAAgh4y3ekBFKw+JlrTYKN6iOS7faQMTPEFGWwo8wmSBKBCkaj2rtoofMOKH3G/gbfIocbZts6MuNkagFEjB2bEutQ9FsxX1WUoVDVu6enoRkUjviP37WC91/TFXS2qiSi3y7gDJFAAYAmWlg5FvF43thPXnqx9J8jbPSmUNEHudXJjGqjX+/vhBMcXMlGVqV0Y7yTq88vAC+yS7l4239NEPAlpFWVy/a7QrV6KFJeW4BReVMIOdrwnhm84P90LRJXjjkuaqpOW05bkQtt1t5uQGx6oExknKrOPtWqBHwOzaZcXXtll3m/rFgJWWUAP5RKNfvDTMRwyrJFyH2Eyx5PMSQMw0WLX2WyMl37/ClaOcxBohTyM93TtnyNmGXYOqlYqUDvp7vravorTEFNRTMuOTcAA//pwBERCAACCCjNXOeMrxEHGWyo8ZW2IkMtg5jyoUQQZrag3iU4IAJssgA2Xrw6nY72CsuT6moALmTdFQIl+d5DPNQZ23ynfwg2Aom5AmIsFMrDEXRHdcvRomHnWx2kTLAzSm0un+gAAGAVWywDZepts1VdwouHuYIE2eJ4TQLqagx/zM0Yre2Q6dJOgR/AcVGnUcY5xxFR/rs+mreiLRmoLej9VymDUagDBK2yhmZaFTJ6eo6nzz/yjBaEcA0cQud2tvjBl3OVd+57CbEw0REiiZxFzaO93V0crJS8vO7KyGV8RdbR9qtfbs//+o3QlbXIHGGAeBWLHnGityVXeSLDoo/uydXfk9cQ+w73I17UarK3cltDK3IV/ewVqnARnU2Fyrk3PKJuQ9snsjnmUxBTUUzLjk3AAAP/6cgQWKwAAAhkzWDU8QAxCQ9s6p4gBCMF3j7gTgBEYlvJ3BCACAML/VITGTDNIfEPWdsWGsaDhNlkCg3tdGpkOcrkQ7PuvFeeRkVSpt1O5UnoZe95We52q12cK1TWP2S9K/79/PNR/yABgqlvtEAOlhSxrwojNSrq9jdK9/HtOlmS73Ldyy32Rf4ou2dAA4bfU7D5QH35eGPgg6sH5//B/U4MbFAh6gQcIAwAAAAAABABABQKBQKRT//q//6J//2///tz//8fLkAcMZ//54oFhYmYXb///jg0IF1MMWNye2vp//xoNB9xLG5N88mNNKb1NoAACya3a7QWWCxCMRtHVf6dau+qtmo8vP3eZbFnQ5UPW39ufc/qnq7CPqC1zyADI59n8o68uaKPakp/KDUh+s1JAIBzw5MQQ//pwBMQwAAACGgNg7whgAD/A7ArjCAAI9C1zR5hlARwL7zT0DLIAEERtuTWtIwggo4k2TGgM0aIgqo0h4lIusOsue0OpNPKNIlbWsNSEGbgc7CsilT0xE39I1mSM1PZOrGrOKSzobvzLAACecusaa5hB1o1r7l4BDjRE6ONEHiUi6ROkUzTTycSQ6V9FkGcSeilKnpiJv6RrNBmp7IbWNWEVJY3Kt35ljAAi3idlrSYgfBrPrrAUtCSnYRualHxVaAMBGTG5QowUeusXeeGPcUlrKkGlg0PbqKhpp5ky3qe0KnQ1/363VnWHetwlqCrhKAAQEo5LY20oQ7FVbboWZFrgCkoWGNexjtZStEzTJIcyM2BAiKDyYXSLiYsoO3ukRFs9vgq4sr25FZYSgV3/5LJSvkluiKtyYP/6cgQmDQAAgiEZWtHpGMhAI+rSZwIMCBTVb0wgQfEOoGvdoIngAAGNJpJSUMwGETY4nESK4bGXBpxVJuXlTOUvClsaHLPh3CqXlUPcvA3PGhKdOwWzw5p1si2xepRYqEkWblkdqV865HUF//mI8a9B3AGCgVrTEnlgew/cDRAQyJyX6jpmcOVpVEpcSZW73rabuwl6PeUw2QUt9Sj1GlPf+ztqf9HZ///6gBinKUaKTdoOee+NI1h6gC3qhR+mfVzhCmSrux/NTm1butymrdrpVS3a/6W9Nm/aEO/jCPFrHq05vma/7b5R99hAF41fwqAOWPVdLbMM0Mmi1uNXpQ5ZQcv7iSAAmTg4Rxl/r3wyYbKhdf/2v/+P7f/3//6An/OPlDI+hibVM1VceT1CFMQU1FMy45NwAAAA//pwBDrvAAgCDBzXuw8QaELoC+0kInuIgHNYbTxhwQyb7CmDCYAAQIrQLhUuZ7FjVbWwn8sKh1rKYB8RqQFasEVDOttDNPffNTtg+oyxM4i1PNluL6Yzzcpfy2z+o2I0ffV1a9gatqCSIDkjc1rcp0RI78Njvz86e2KERO5lSjaQk+iL3wVBPRwz63Sy3znm70S7JRma7fqr6tSdv08N/NUn3aoHyPZk011BKtq2jjsfAhwFuhF0UxaQo99qQN5KzxybY7Hgtr7EjQEx8Iz3bPz83m4UaSfZ7yXJ+/yNKuPbtoTitd+6w1p7n+4/xOCAYAU9t/5JMn6v2g527VtVI3ybIet6KQBtVI1GXmorUm03W+OrUuci6p/7z/6ec3/tV9RL7tJ1x1VHUyz/v3PT3s6UxBTUUAAAAP/6cgQ55wAIghcfW1IJEFxDgbrXayIMCLxPVm0kTEEIlesNoYnYAAC4JVopMS0KBBHNXcntegFrGUoU7mIzG6U6sl13+R1ghasUWCMAkTVeJAj+kXU7oOTzEbybQOOp5D6dRzd9rNTr1gIBerW2Kh0TCNzwimFNKZPdfq+9IWzGh4xyvcFZajBSOEq0xM9boL20kGuyEob6M5+QlL+ha6ZZahYWXGe/zn1btikgCyOyiFaPuVKpi6o2VQshgi1FaWCiQpBypIF/Zxrspbug1XYHufKz0EDx80hovw34Ecs5Lf4/a9Syanjn6NXxf9+oY56KQFXXbRGnGyKVTXrNSPx+N7cyQS9IUrNzlvJNy1oWQ/dbD/S62U+r7qzSI3/+vR9Rg+oHEi/i8R3/YHk1bDTKrv/9yYgpqKAA//pwBPaeAAACE0DfaOMVPELFywph5QsIhEda7LxhkQAgb/SBid4BEkpyy32uXBcOdrFXe+N2q++j19NTzBsSUjpZgXqT6CK5OGJyp/pdi/StuAov//ptb/XQf8Diy2Kt55S9ZGguZdJAAEWkdbJcO3EuoYyxsvX8bEPcEgQJZgc2zodimrOEzu7+26ttp1HSoRDo9Lu/vP/Kjti1PWk8o8I9OhP96fp7l/t0ABJEabiKvKBpkzvjAL6L+C9aIMIAlVvZ8xA6pXcgXyJhQGUcMyts2LAAKoa/qJK24V77Dsso+6kkub/Q5qEVEHZdSfrCbaNlll2stwZVvenfvgJDpgTVYXk3aZhRdhkl9aVYTkZatek3u1l1Vjt/bqZem/r8+ifyVoFfuRMf3Nrn/lb6ExBTUUzLjk3AAP/6cgSP9AAAAho4V1MPEGhBBqr3YCJ1COVrfaMMQ/EXoK2oxAh+AADQB+120RZGhE9g6jFsIeuITfSQonLxFgqKGqgOvuDRk6baUyIsxruhk0R/9f8qPyXp+r02gqeRayeRZRdp10e/5kBRS+kuopdLJj0gx3QU+uTnZollSZzl0ptmjqbQwIutUW3R92eFWZ6KvR8reVv1Ty/yfEua5ZH4u9lmf81Tea/7AQSw9NLPtbtA7d6EO7P3ycZAZnUs2qQr1SjO6+uvVV0erKe95a0/V0Z+DK9W/XJW7qaq92d7mT9v+j2S7Lul6auTe//Be3KgIFem7I5AaDeLbhnvfwHBRlPetURORA8zAZXr67b5Sbhl5Sqac9VR66090PvM7XbVaLHdGVarpS6KpHgxRzTdmDhPrtVF0xBA//pwBJegAAACDi5Y0wkZrEKoC3o9AhuIUQNtRIxMsRGc6o2BidAAAEilGykhhDrSim5SrasmMdXErcNPIYsskjpDIVkzlnZ/gHzvVNHhd0y72esKf35DPYIxCPfNo9yEOZf+76N5b6ApSvTdkcgIA+ZcU70u34aYXGNmLaXUbKj0Vnnf21NodHRm0pW/r/TN511TR/E9CQZLPul2sYO8wTCSx7tCOkbQwUMgAjcW7I5GCR5T9QaPQ+6kB0rHPNFMat2SiZh0apehdVJIIzWY/W3e/8tX9W0XP6X1I/8ikRqFjSguNqGN+o+lwr/WEPtvwaZEWFTu/MWI3SxLJ7cbAjGTlp6Z7W1GdLkZUbDPrczr99HwjkVFTyqlKNe3+TxBF3/eq0RajX1oycWTV+p/ioXTEFNRQAAAAP/6cgSK5QAMwg8T1RsvEdA/xrqzZMJiCHC5VGwkTIEUCOlNnRhoBO/38C+6LCxoxfWVRlBg4Yy9SCDHFDO9S1fLK7tfiXXdkCbjxV9M+0eeNbTgOvJg052ZyZcP+y2/J3LK3N/9P1/WEf//wF51AU6mTZzEAX4/qH6aDRmVXuggkvn2KPUirVZyaqn+bXkdh3ZdfXVZkdvbdtMna2uCRZZfVyf13X/TUAePx4PRXu7z20Sq5VahYtzljKZLktuSB8FluQLMbtMljC8z71S/8tLGdqEKWn//u7GLZFFxmpS1SYPNR//fzeTtT/YAJJJCNoFOZuFIsuw4aMj+J6QG17FnphLoc3lFRCozzcQwGGDMUWJuQc4H4hrbHuCgu1En6VqVKVN3d9Nzu9Kf7tQv//UmIKaimZccm4AA//pwBLRIAAACGVtc6QET3EJIG70gInmInNVtRKCmsQyOat2BieIABElOuPayShMq1qE8GReMA4zzXIw2TLEwwSvIJFwb4JLsl2TOQlrqekhb/5H9d277+y7r+9Xyr9v/o/Wmv/t/gm8KAkFF6Ryfa2gSMb3MU/VQobRy4maczT7MWntfTKuDIcp1ft5Ud3mRqv3byVfXTudlQ9EZSvVnfR5x01M3hRbE6rn9SAwvpvWyRiOZX0kB5o3eMH1dH1i2k+yZt3tlTjclIQB6WU2hdFSzsVS/dTPyv00spkFVSZMFBpJ+XuY1596upe5TbBX6ACAlySQKr0yEbqTj6O21GdxwsZvQRbvU8GKrpS0p5ECOl4ZtyvOubqN+X3Gp1KNRkisQJPHRpSCG1Q1lG+lGu7foqamIKaigAP/6cATaQgAAAhYc2VGBE5xD45rqPwJTCHSDa1SSgDEVhO62liACAEBpFWRtlejM48XGIMmJvtwOvT76aEjRETyqAvX/w2ae46TyHliDSoWFXWqeSLjxodtYw1Jqu16S0MOc2nLJR2dnrAACgo61yVXoahTXZmHvBUXtroiIk1j2Mmte6MmwaERnX7++TnGc63B/ygP1wG0f8QOKBhXLn05RxyIFg4c7UOosdOdYAQ/tvWRiEO0++FAab89sFviaJ0CAjnccjZ5wGTWlWXctbEij3UrkGFQHHtFg+4WdQu7WLDkrC4huGupUv67//+3EpsAgoKxyWyNptpZ0pq3PbMVfpCywYDZ9qEPMscKlHh9AOOeJ3g+CBeCGs+cBCGEg4J0fB8+IFvl5c2+71ODP1n/yjvIeIHJiCmj/+nIE0UIAAAIiW99uCEACRIcMTcCUAIhcuX28YYARDZct95IgAAAAAAAAAKBQKAwIBQKL1RBVomqkn///7//mVz//PJMT/+zOUdj62/+UwcMUOojmt//wNnRA4sOhhH/v//xYQhzueB5jwSQABbbRLbZa3C2Qw28cEvuGWVa9n+3Z2vu5mfFlFj9n6JD5xCMb6dkyIPFQYVPr+nJlGHEhcNU/5dVtX+z3s2z+uVagsgtO23WNpJPCVdis393CUmqWfa8hvGcy5CdB91+FDVSrGuW1L1///6Tfb7LPrDueqmpk6Rtb+IwW4aDX2d30yLLcYAACLdr/Yi5JMn33BR++/1hZDD5HpY1UXRf6t2dLGU7era/tmsi/VjdB3acSZ0ijUBWK4bw7CZ2sWH1B3bUeeZwrTU9MQU1FAP/6cAQ2jAAAAfA32+ihHVxDCAtdICO7CNlrZ0YYRrERpC/0IJ3+AABAjbbiIAKMHkwkGD6YSSj1RTWj3ndyBOvfvszCiKeFT/8QovF9HhXn81jFB69Z1K2REjzy3Ker/ETdn5EACAT23X1lzDA5IM2Ay2e/D8EWofe39TaL4wdb3nX/xZr+Ff75C/L9YV/h5Pn9qln0ErO8GrZayeXsrc182LNTOnoiAADlMxolNZU8d/rjEP47i7lFEmkRbttzvQrKYn20793D16Nt23p1KnyL/8N3Rzub119EM6eSn0bbR16Eu+lUdeb1g33FK6iCWg7NLvbLddtQTD2wfYcxc5mZ1T2vuJFAsnbFXP/W/Y35pC+Zt9HztDke3ZUdVEA4cc/ZE55cjgOaTHVv0pyEX4vFTKYgpqKAAAD/+nIEyJkAAAIZXl/oYiz+Q8gbKjDFYoi9a29EBEz4+YStaJCkRomEm9pd/Y5M7LzpD5+ATogcmN2Ov2bGufc895DsgVDqI/q3f/U//9Nm/2H77/VJ/J29f6apZvs+1v/V/Z32UNPvnWsACG6czLcmk4npOqfjG9f3AVj+piL2n1U5I0a3kdrq6urcPlyEMkcDMYacn/q//7f/+0/T/8Qb8W0IdpRes/TfUupPJAAh+5K2UoNs+qwyt7eUuF92byd1AmsrECJvdKuyIvIbVu/0bR/h15kC7I/8c3Rr/6Wzsnf+3vev6vRfv673SCQeTY5f82ABD/LUaBTt48rIH0LWVXTNzbJBcuOWqF1tSIRJbcjRYQZdkFhn1l0T31m7O6Xc/2GXpe1l8MZPpa6Joo8UzqYgpqKZlxybgP/6cASvWgAAAg8oXWjCGfxEQ0qTYeI0B/BHdaCgYzEPly30YYmmIJRDkjs1Zbhb/dcSe8bD4Q5XY7a23pB9J9z4f/r2KZ8IN0rIzfFZ8GJZsqfx6fRnWdaZVtrqNJHZQg3sX5NFKK8mEoyI5SspQMYNL5cMdSLZ51C2yhsgiilFtO5LP1dIQG7Nb+hcPSMQSV9hcPdlBL0Lcsj6w61WdEbcXbbVVTq0UN0EWcwQwghLJJpE5DSTLLoz+FX8y6j36/aawI8lbemeYSU4wwZ0jhY/swUJAn67FDs2XUhP8WEIdKLdSC7krSz19/EQABAUjcWaLceEVezR7bf4IvxDdZef5SGwgW89D/9OhRbMcEMifX33/t4/5TYnTDZ4a94qZcssdonMt4qq0NILNlmHcWTEFNRTMuOTcAD/+nIEjeoAAAInXtlRIRN8Qwb7rRgidYiRbW2jDEyxERcsqPSI1gAAeSVSJLgwjRugkTu6V0AyUZdizHxikSmvXg1zkIVoDEv9q3+cI3AydPbmMrUzIT760Bd1r+//r+ifr7v2eFplg86gTAIQiSx3aNOXwvZkpcP34F9g8btn9AwaPbOVe5DUEiHJmPvX/7Vb9WfVv/7vxK39ZUWamvLpVQmg/PPEcidphPbPz1QBAAKrbWjScaUiroq1Yi3Fv4icVTFqK2YEy22av678xzLVoZkRvvVCkdfb4I6/N2Q3S0ZF/0oCf/+nVP/00zfWneCcjQAIVRarRKcB+rmTtQ9r0wzKxEegT6rtrUmidGI707ZuQ4OgIr7WTXuh6fL6CSZFqtblPS1vIr3e4u5jdSA7OXPRXFFoSmIKaP/6cAS/kQAAgeAS0gsGS5RDCCrtPCJ9CRVra0MMTXEUG+10kYneGVbSPZkAQHYlaKqH9IkSwpO3d/g8AtK8zBBVI4HFbCfa/YDJTIqOzY+qco9Tv9uGu7JUDH5dCrP7///qAAAAMVq9atySWykP+NOJEYtZp4PL4bWbR82mjlMiEIRzKb/WvYcn3r/nG/VvX+3yNWXb+zOlUHspu2dGrarfw41K6wQAvUekTcZJE5WNiPQbg2ePxX7wikDhPMjjHQeU5daRAkEQzPJmXL+5vyo90A52RP3tvM19el60RPrVNt81vrpprTb/4dwyv2AyRx6NtztKpRVRjsbqXRC24k+S/TMWvEIiJnDOXZdO/cXUspDIdP/VD/Y3hk/P0hTuiiguVAlCs2UYzb+c3Pr1bC91nKJiCmooAAD/+nIEk1cAAYISWtnRARX8Q0I6QmNGGggFAWejBHdxEKCs6LCKVggBpSdjTbgRhHFS40HMTMge8OdWR/Uf9pN115frKcB6LJyiO6sRmfyNzZF0j+UzKkYMlJrhNhb7RFp9KpP/PH//4MT5zJAjwifwgHKloBV+DiqcLUlCGGrAmFxjSWmdFYNsJhX1jKivh2cwAij1ecpp5JPF+B8Fb3stPUXOy2R+j//q69ziCQAYmm5C7ocl+QjjmYAVjoMRJIf431pjKL3fO2H4tSsXL8jrajoviBzH0W/mX6vldcjodh5Luuch8D9tyUO8yP5csZbasoG1pC0+s57sFT/Jp7FqfqEGN5kryRP0oHRkR9jOr9nU10Jrb83k9P9W+rX7IqFvQdyqGLejMScXJiyLDn5FMQU1FMy45NwAAP/6cAT4VQAAAhBA2VEjFCxCqBvNJMIviL0DY0SETvEXjm50EwimBAD5N1tNpM8ZFjFbCOGL4qd6Z9lWbL5+tNQ49l1UR5amr1S6yJT9dP9Ttr0vt2rrFb/q3w+aPT+H3n0HZEgJ6aPqKIKM212+0t14LGcnO59ni19gD7BtvHPVLl6SzjJqtB9XF6dxt5/1m+S36iHvHfYxP7+2371toPNdXUjZbpcvx17vQEAHQcjabeGFxOmlaUMXgmB/9PjCm3rZyuxX2E2sZ0YyK51vT+f0ev6Ef02X//1d/mOiHRAZReRpUJEpFLRcV0XjCGPlnIAIJDcrt9ttsA3CUvMz80PlJbM6wehyaK0yWJflJl6iToIlHJZQUgTplRKlq0tKBNQqdteUC7hoM3glepOy9i0mADS5XRMpiCD/+nIETuIAAAIaW1lQwRP8Q6a7bSAieYh4uW+hhHgxFAaqDPyMnAgBvClkbbKRHABMm7XtWwjP01kgiBkzOV69ztY45g3DdEd6gzoq5l1IIydn9XItLqRUfq1Goe3ZL7/fb7/brfb/w/oAAJCUbk9skcKcljAxZtKuEgbrH5QAQXmjZnldufbDccSMOajC5E39Hrbu8A6kKu33qBJIoDC61scCRSrqPVLVxlb/1gJEl2t2fWSSVA4ReXUsWUBhNIrngi/wZmQ+5FGsnWcurtDpv1qZNmleKnV/2P4ONXiyYTcHSqNJAhPZCh+csOv9jfyZJ1kkBZGeCsHOdjOKhL8hFaBqPIRxMSDuAB0YYjYI9ABnWpFNUpQ49tZeaSwKNrcwysq9imGDpR5ADilLKnvfxRyF/TiiYgpqKP/6cAQ2DQAIEh5bVTnnEwhBi2r6PCJ5iKVrXPTBADERjS12jDAGACU/dbLAPI4RTl22W6K7tzbwp2fBjza7IkwerDovWqNn9a5O1qV2/L7Koo65ld/+yo+izJ6v2SR+nvt/X//v/6dwhqgABZKdbSTkZkNXoL1jSjL6flE63pnBaHl6J+coCfNwPlwTVkvZf0kK275NUZkfzey6XpU1///X/Xv/9P9qf8Fwwk5TjaSWlotAjG9nXmjlDBBlipKrSrUw7UdZ9HozRGSis0m7XayVZW2Yre93un0Rlrdw1t9nrW1aU1r6vdOv/2/V+z/+GUAQAG3HLW9B53ReZ8/OGEEQB6EcN9TRATeRFlZBe8ESUS8LHxYIg4CDk9TgcOPwQd7R4XlDjUsX9vlBB0wQyFOCDkdvoTEFNRT/+nIEV7YAAAIlJtjWMGAEQ6NbrcEMAIiNd4h4A4AA8aKwiwJQAgAAAOv5JrbXawIACoN4kEl0WxWrBJagJt2oTM8sKIkqXdWyFPnE5o/wxafd8RH1TR/9kgQJFw2lcJYfrOHte6b//qS82QAAAG25LbtbRZZBJbaEHSdGlvA4iBYkIiuZtXkWXR8jbgZWWuII/HLi9dmhYZ383itvT+pWyfX1PbmBZyFW8rc6Kpp/H4/H4/H4/////fzG/fz0OU07+5hg3JzHEdAyYf/mGc893Hi4hGovVjv+e+YyGajQ4w40u1jz//vzG+eflDzmPopzLKeGeeeeeX85X/////f0b/50NKn+QgfPIoTICof/ITnO5hIHCIiLqQn/OfIzkzigUWFBJ+nlIng2KGzxppxMQU1FMy45NwAAAP/6cAS/hgAAAiYK3O8gQARC64ut4YgAiGUDZ0SMVhETIGyocIsiARLSjbraJJOlvCk6xtAKGvE0oVrC2gs4OOLIklrBY8qdERamsJHssmVDPlSoIywGfK4atrMlaQaPWeebK9dblPOhrFRYEpMJ22tolJ6V8oz5XCmcV6VI4dtxlqzlQtLsv/Nvlaif8vX8z9SP+nlJ5nM/95S6t/9zP0fum3K3/6PL0RxIKkqs8AE38zjSSVReSRpVp3y0hNuoFdkpV1lbn35lJHqiOmJa9v2l6X0/9DKX3b1TLoX7GWolkdf1zYNq09Z1ySre9fkf+IgAhqdsiKTQiHjh48UtMxWMyis5/cq3LK6I9lbNJG2YTiFYIVW8iYzRwsyEf2Hw8K19PMMv9WmoDZERe7t7/kaTteMcWrTEFND/+nIEd/oAAAIcGNlQyCo8QugKpzHlUgiNe2NEjE1REDFtdIGJtgBRvUjRBKZyAPFwSVT8AbWx8s1UOjvmVkRoy5lSrUAMkMEpzPscQ7E85pMv8G+OdSsqMBU4FKbR49DSiMhc3+pkab6wQOSRJyDUKwOBKIC/sPoNs3guQn9dN5V3W9GlS9C3/ilm/aa3v25qP7/prk/06u6Pf9bUCw0ylqUkdxxLb6P6ZeL9QAQXuyNJuU0Sh5tUYIo6lOgo3uK8LUIzndgZeW+RIzVsJ6Ivs1G0/8//9e3+UV2/0ets9Le39dkt/f0X/q/vehIglNA8AIAFxqWIgJwPNaVfX/BwTlWfkkPLoK07PBr53eYQLDdi39n//6j/p6Dt+//k2//h//9n5CWr970Q1T/3sla7FOyJ4JUxBTUUAP/6cAS9ywAAAh433GjBK7w96AtKLCJsiLyvW0eET8EcBqqo9AmIAQSSdburRSf3TUOUzbPYPHdWBgNn7NTypIne4DllLKLe69UT9eZ/r7U2v+w/WJjh/UPmaEb3WEUrxMs9HvdvrHWmLagIh+dlrUkkuP1cTCnE4pRMYEYVmshm551Mfk8v50NrqzZmOhFBf29L6f9tG6f2lxntceoIkt729OL9PLVuUAAF7jlitzXHVxcrNhVHQq+7tkusHHdTJs0bZjIaJZfX/k/i/viCph/CHzBkLeGpNXcLrhxEBuswF9wSdVexdzh75q1YsMAADQWtkuVMo8cRe1FCgZPeJNqQNYrJUAj0SLPg3KCz2KLGtQxFq7b/QKkteXmOpblHqeHa3Mu0qYWbW5RHoua9B4UStTdg5MQU1FD/+nIE8CgAAAIJEdKLDxmwRCXbjRRieYjVe32ghE3xHS9r9PCJvHjZivwaQGdbC5S+gig20JHoZagPBDrphcq+WxX6YmlhpD+LM+1k00JPx2bEQr5eSUp+XRIau9961Pz/6HVY1lKlgFlh217VopxWHEFVuSuFDpAtjRzLWHWcFyfYWGZO0xFfdraoya+3bf9W8Q6PmZe/aVQlxHTlyxWVfmxe6fyQzXpUxICIQLSv1/8sk1WRmnPGtnbSYbMRpQYkUVmrHbZNVO0cy/9enCmbUIdXpvd3medT1FJ93WeqiM2b6u/9P+r36+ir80EyFYE0NgAgAyR/esu+CjHss9b4j7n9FsrZCNoSyGuVnm8dclh7+L6fW8tvhb6HWzf8yc2+n9ame/T9ktWzmp1Wh+3M/9EqhFkhKz0IgP/6cASeRAAIghla2+jBEaxBhcp2YMJiCE1ra7RigDEQoGkGsCACAIACtj1sZTjYtCZF4OLTqOzALxMIOu6adVQlbVT1VvXqdZfRmlT86toAtf2+nVdr/pWoMT//Vb31/9/N9HNq0OZ9lABC/q+GFEES3KeePZWHFp4cw0SXKIwUPh61ko757A+r9Pj76aPSb3T9qv/foKQ9pqwXcVaI2+0p/ahFtilov/1rQTHHbWklGeCbHAer+fgTd0BeMNSgOXRKo0yIrX7vrMnX+N/6tr7P4s/1fvUup4ge7aNa3JTp/6f//r/q2uImwtYpiXHR0QmptJirCS+HJxKqAmIytnY5dPmcpoQqhRT4Qd3vglfd6N6G27b0M3X/1b9U6mT6v8jTU2X/dNBqrtS9PucilrvHpiCmooAAAAD/+nAEhysAAAIZJNuuPSAAQgSLZce8AAiwV2DdkwABGAEx94QgBgAAskgIcZOD8G+BieTkHQpfLc8mIPcxnzcAEJlS0JtuFhA3efL2aY601VpQzw5YyYQrRqUc8P9pDsZIn137gbGf+3QAAFkABDudg/BJwDLVgkZpK0fSooQuOWquc4BQOT1TRH8eZNJCNmn97x4apbn284xr08J6yPba+P/T/4e7my+3+gsAIBK8aOOuk7q64mg4nS6smou4Rrt/KljTSw3yar4z49W3lUy8qcDQVnomVYpqkD0zLR4RAZcJGBFciyyJXPNuS7vd669QBKrl2t2223qNKBpwEW8JMBkGw8aEY0uE1FQ2hwo8KGCr19bgjeg2q8WexzMTqLGZofYOS+7sJi7Wmt9xCQadbdYiQ2KXQmII//pyBL0YAACCFyVXCy8Y0EOhu01gwxkIyEV7pgxgMP+IqqWnjGADjEUPbcBul9D3WSgTZxKllTu0xFrVmI3ZmKe0NnIzjkUfLYyUVE9S/cgmmdSy5Bnph9zWHVBLegfMRMgmmrXQxtvUAAAGVE44y3EbDQkSS+gvJJJI5PMH+PD2nGHNhQimaGPGEQVOBJe05lQTQeqTcqsKsX5CKzj62VBZEv0Ks3Wmvq9vuoBIiT1tkkabpWD0ZdG2czosuiCZ/1OkmwgUxcKtPEsCjOsNHnhoKA0qws1JU7K9qnlWzxXqDp1jVG/qw1/yanmbQaBUlUysAlqC8xcg8SFSL+QkMQ5ROEfDlOFqV1iTZ+LL8KDbCt4aTeKDceGlQ1DtflXUflc8V6g6dFTv/kvFM8gnpyJUkmIKaigAAAD/+nAEVD0ACAITEFObjxhgQcb73TxiBYhoe12ssEGhDiBw9FCLHgCWnJACFjhYJCKQDkMgPoCzViNR44iKx8NRNbkDPAnDGJJAwoxSAGHschCXVLatiqBKfdLG9q/TnSPzyDzkf/9/8mEkmppZLrZbrEXiSRGd5ZOLc4V80j5THQ7Xo82l6VK/YsNmfVtxO0qaN16GR5f3o76jJPbQVLKEmJbtrv11Lfr1dwAjKVluyFXwlYD4Y0Trxa5G800KI/gfXQQ71YXK706MyH/zLVTvUS5Yjz0PvuaHjoufTupFGEnah6d1QHd8B1Uo7P1ByVm7+/fff8aLB+eQhSPpqhUFWdXOVqOT6iATezuZinJaWEbgB3p7ddBAESyGBtl/hvbZN7ylcmBlRnX0XJ17Xuxf1JiCmopmXHJu//pyBESlAACCEglVm0w5qEEjSjFzCQoIqW15pYRR8Q6G6mW3jDQENSSUZqjU6aTPnX1WW3o6VJlFYVrKh4KLcoLuBwsiXP95RKqwaSZdgqle1gotAWH2vQiigrKsp06rH+zfp/SHNHoBgCycrT5zkjk+ijcTR4Z01lmEah4zkI8aD45KYLCrznV0w4hIUQpT34kt5v25393stHXvVe6fSafGCrqjKOhWcQCQU5LJLpbbgrMzrOJ287jV+85tmIAiZ8T1xuWlWILYZxo0BjF+Gj8tkz8F/rMhxnrdHZ0qy299rtaXtf2b/r/+2/hy/1gQB3/BeLEksUBdnZPEmaCriMsNjICL28JUzCgNJR8xOgs8sbvBkWXgcGpAkhdpIJhSSbrLB6Xd1gjGu1os1e+1H+760xBTUUAAAAD/+nAEf/oAAAICDlKVbMAARGFKuq0MAQjQYX+40ZIRFYiu9xowAgG/ILcYCz3rMJBWYoPCADdZLicZM/s0h2fqc04uqQvDqFOcOhY/h04wzEpJFaRj2nbAOHdBFLaDfv6P9ln///tAAAJEtuSUUrjzp4Xvi0PRmGJXYz3E4beTlSvAZYsWWhqjrUvYnKtoBCNes4PCBcl0Bqm6PUt8YBbShqw+jN/fp/rr8NgAAABJt2vSa37TabQCh8WA4Swl6mLr66HPUz5QTnXsFKKy7qcK4Sfahzxc8kyxUq9Q8FhqmmN8kNa1KqqHdlFFl3u///6UgAAACIxyy23bfz9/wCDUiG6iMGm1d6Mwp1U8+amUMyFuMUkS73mYhUgmoTg7gMWqOgHM03wBe+F+h3j88LPuRQh3///SlMQQ//pyBOQ7AAACDAzi7wigDEGCHF3hlAGI4J+HoZxtsQ6NsrQwno4JsxOSyuIgl3Q41nPe7WepwdcRNrChBBCp5Kt9b3rIlhdjeU4hwS/D0opzqSipkrXwOdcr4pRdLDXEVNKsMyKwkPDbMSllriIJecYa0zzpqeepwdqB0fq9BwOgxU8lW+NeaWRSJflOIcEvw9KKc6koqZK18Dud+Kd0s5xFTSrDMisJDwoA3HbItGUmC1xDdbKjzhhLA844SwpnP0bXr5nO6haoUByNaUObMzHRJaoGslfOyo2tT+8XWRLEkppLEC2ViLpXcgit1Z3sEcMd2129jksyYaJT+nmrRVNeIzZRoHktizaghEjKF1hNYK1ndQNPO6n1uTK4abaR6VnRZ4Vc/NnnHs6p/nSsY8lgr1JiCmooAAD/+nAEAMkAAAIOQN1o4RasQWgcPQzCcYis12ZnnK2RB6PvNGQIdgAAQgYSVGUoBww5rZUWWUhoJbY36ahss9fbV++gk9eX/U6qRqfwpcQ8P/Y2gqj/p0qCFITnup//7Cw9YKvdPCISqBILU0rkurl0Lg30VlUzo1Y0IzF8Vzc7avrwYnTeTp/Po1uq+pfNy/+bQ1P6dKoOjv6g6VUtbnqqryz1iV6lvLFUkiWSWivl5tGaK3gChteRQ1tKcATnNm8q+rZUv/DXaeDtib66NRnShnt5vymb/3F48ADnWLxHfR1NaQ54g8Vo9CykYGJWoAgESFhiSOSg6tKNnJTrqLo1JKBFMnAevU+Uf9X78D6/b/p9qNwY9FYn/6Wur/deoQXpY7d1MJukKu8g1n3PeMZyaYgpqKZlxybg//pyBAGuAAACFDXgbQjgDEJoHA2hlAGIiXeOeBUAERau8I8EcAIIgssqWP2OTBXUjaLcw/V8/jt5R5gsDZ55ZcTku+os49JKVaw1LmP29fX9V+m7f5U6lXeuh11PVFPT/vqFVKE8TUjAGQhG44rbJcFPlR6N85hKeD8H4ebZ9g3R9ONFGqmoz/v6U4/vv2Gijk2p+nOPa1//IHD/irIIMHdMgtnetSkwzYosABWKhQKBQ+v/////////75h576/88eGGEA8M0b/8xR+Lwvzi5A3//+aK6E5GUBWotj0Fv////G5cqcA2LQ0IyecFwDQABAIRAIBAO5CDFWVkv/0///////vmHn//zyBhg4QM7f/mKTKg/OLjjf//5o3QmPjgJqNxqA//0//4vJKgBY2Fg+TnAsCRMQU1FAD/+nAE6WkAAAIDDN5vCEAAQMMsDeGUAQis2XlBKFERAZsuqFOJ6gEBAW685CAAZqFWoqVhufQDLgaITLOUNDhUtucwmxps9fKsFSy2rdZ/V5EO/qB01sOuiIrQ+wWJUrwVKugXSdGJsS13TX/IABiMELfgOH9G06APuB107tRer9qBQdDShUCGiJZCQFCSiQdF5Kr/Z4Gf+RDbqiqKjzZmpYq6/Rbtx4o1XPS2NpBWYw+pLAgMNgBcA4Xj66q2IwsOaJA/IiZTdwV6ctJnVW1K1e3m5Sob/UrKX5gIBcN/qDoBTiL/lXVKPOriKeADZuN2xpII5g1ph9yvYYBdRVwk4+2aaqgsL4pL6NiYlpyjvM9S5St9vNylQ3O3lZS/MBGZGG/570/8q6pR51csmIKaimZccm4AAAAA//pyBI3wAAACHkDf6KE3LEEjqxo9ZyiIrW15VGKAMRKQrraMUAZkIROOpP2y3D0o+VrHBjsgW9w+2DM8EH/jG7/qBOe0O9R60b/Od/TWRy+vxy9/yHvszOPUHUOlyDowpbtdofdQgc6TpAAAIESNyRXtU1xtpIiWEq1aD/VjNegBLYl1caO1eIDNhnTlCHL6jSWHIYYfWvw7E1HlnKaIvigqM25p2z0f+ygqIlXbL9jlsdnX/GfSxinsBbYvDvCTaNhJu/bQI8QjRcXd1Eh6sMHP/fT/bUWpfv9OibX63StEFt+39Eo+9d2rO3mb/8Y6AACgSqktG5Lmdw9s9doye5DMG1FKDGdpHRA0E0H/QIB16kswdHMMHJQIMVpW/vkuXouLMEdibxIXLbO8nvq9969U8tPemIKaigD/+nAEy4cAAAIJZVwGGKAAQUyrocMUAIi4K3u8UQABHAzwd5AwArJMR4/aLZ4QGojmDNGK7D1yW/+3/9bJ/+vdP/9ipFHb//9Ti4gOMi///n+UeRhgkiuAH//vv/8OCYsBhwohXUXHAktowkRofLJUm0F3ToiLo6f+jf37+T1//690//2KkUdv//1OLiA4yL//+f5R5GGCSK4Af//3/9xQJmDw4UQrqPHAslhy7WSMgC6CJjPeVH3B2w6C8QjgdN3ZWse4wGlCy9kN1sKywTDVa7L80iWFgLr8MoqHBd1mB2NFTOBg1F3BNSQIyUF1oJxpyS1xtFF65aD1Zlx/dhvJgOlTP7wwE3IBFHtlrPorVBVQstlluxcsEw1rsvzSJYWAv8M9QHc4iKwbYLCEBhWBvCeUygumIKaA//pyBEJOAAACEhlbUMUzRELDK4oIxxqILLlzoojwsQ8gao2HnKhBlBjS0kAKT4tBq5j650dQE4h14S4Q4wdB05pFD/uEcu4RSLcqMes7DT3KfyriJZ2dEx78kIfJK8S1DH1hIDHvluSRVG5NxtkmkGo6bb8to0sE3HM4hx7hRseL9tGxOPRQyDY0BSJLKjHrOw1GKfxK4RFudEx78kIfJK8S1KfWEgMe+W4aQBSBKgqlrkoieIhmw1SW0CO4T5OHGwzYIHhBJHZsTAC9f9u+up7LM7c3pKxIxfdPo8697s9rFWvR/PzQUNcM9zGAB7XbQdI5Ki6REW24Kc8XjjUDwKdtSAfZMWYdYTLhXNAHGFlS/bRsTiPoM6j3TrRl9v//HFSzf0+ZVFT7fKv/p//I9SYgpqKZlxybgAD/+nAEGDQAAAIaK9rVIOAEQQPrOqScAIi9lYR4FQARHLKvTwZwAgAgHCM229EtS4MVNCfUh53Bd4m79WVR8tlC3/nBN0vKvq2rv2znn/eUIrUwRidRpiilqGuJ+BNvFL6yL3sFECoqlnqAABoM3W7CL7LhA9Khz6oruUE9odyLYmtHdRwN3Ktpyof81Ra2P0Gxz0pAdueidjD3Tc4lqhoHFhRVzscj2VdYo8AAQCgUCgWd156v/////////MPP///C8HhIhhn//48MsQCwIsRZP///5OQEjSQuYYXC4/////C8JCNwBYFMQ4zC/J2EGDWAAGBAIBAJlitHlJM0OnP//99G6evv//zL///g8GhBDG///GhmOEBLG5f///yY4WaY5hhMRP////wkID7gBgFiWKwfkzBWDtMA//pyBDS6AAACFlZingVABEPKzFPAKACIjGdvvIKAARCMrveMcAIAQCAQCAQDusv///////+eRs5zf/kJp549Vf/8jYjGg+iwMRv//+cTDcejIQAslBwWw2//9fkoXo8EUKAvziEpguj8AQCgUCgUD//////89bnN/+PSEjJxZVf/8ZmCuJg+cQgxE///8fEwhxZGQUAXowFAtge///8eg0iwDcFkGsoQi6wSRXQQDKlaqlJAAyICYauwa3jfkUYsRKWBXz8T6CpSlfV9AXqDsFcseFTsClZZ/5bGjXaSP8GgV1naes7IMg0sKhryQldLVkohBJ0RyJFHwxQSJfr9zj9k/nci5pxOlOpE0376hk6ix4lOhoOllArInWw7/PSpJvHP/LAy7K4/iWWBYXLB0TCV3w1sTEFNRQD/+nAEyE4AAAIdHVc57FEwRCOrzQxFZ4gkuW9UkoAxBxCq6p6wAACADbaur24gyiHyMAsHOvb9H4xOuPuIJsheoMjfIyf9Ds4Fb5vJ63Ft3ypav+ZFl5YJu+Vihr//xQ8sBWEQk8qROtCrKARSfMetkcCDcwnR+RbzcvAsS4MNAdsoPjR+r41dD3YWlXk3d7HDgKmPD23mQkKsqFzPYoCiooazH/sdCj6bJ2si5r4A1VANtkko1Rwf1JIfyP8oeWZeN6ccD6i/9saAnNIwKLvZtnZbVVn/nnPVY5wQsxMeQBM0dWL8/lGIR9zsWov8OAAAGAL/+AXRdwRoGWc0wLsWyOfTUWsdzlial46q2Dpe9hw+gwAEQb9x76P/3yVjoZWv9f6X51yehvJLYMjPd/Z/SmIKaimZccm4//pwBE1wAAACI1Bc1iSgAEDp+5fDFACI3QFxPJEAAQYgLhuMIAAABioAAFAQBgMDgPosHZE5yZZ4fy7rr6i1Zno1GjHNmHZz9fTs1v//t//6EYUJ///ni6Nb///5hSJgODnO5A5//0OnAwAkEAKBsdkAwFa/nLEokHFJ7yWn9dyXpjNHZLqu/L7+Tn///qn9OhGITt+3/PA6NJ///8xJw+LnO5BD//gCCBRyYAAWBoFvkbNcQISSj3uFeZtoPQrSNoCZMf9OHbXLXVts1W6tdnX0L46rZ6/mLzUKjr3lzKGe1zOKNXZpW3Fd+dYICqmlZYfAMmgWfXO3BCQSyD5m914fQbT3ZJR+mjUiW8tdW2zbdWuz/Qvjqtnr+YvNQt/e2ZQz22cRNX6VtxXfnWCAqppWoKpiCmooAP/6cgTe4AAAAh9A31BGEORCZcuaCGIUCMEBaGWcTtEVIC0cZZTaiA7oJORySSDWxyldfusYDBskT6Cd/1oO22jZD16f//KrZf5pVUbu2byiTaiaCkf7mMbBTzSisFdNURWHU55BYs/rctOAAFkH6qbA3hzjAngewLMTCYITunWWEfRe2KPXgplYrdNH0VWy/zdRqXJxK+dEXg0eV+g7ZihY8DSL5aMPB3pJWvsAAjbk1pIE5ksBgSmIidq+qELCjKNs/E54qiC1HwWhxX2//TvWK6eVm6tlb+g65ndH39WloGed9uDSgrc0SqztGGrDpEl6oAAANpyTDCg+gZAZqgas/1nDXAdowX2X3EoUdqDPwQWry//00fT95W8vK39H6PT/VpaCTzvnUQ6o9cJRKrO0VkmvCTCW5MQQ//pwBBZkAAACExxd0QI7REKMS5oJJQ6IgXtu5YirkRgMrfSQCkDADGQlNdt4XGjsMkPiLoZVBOB4Iu/klyLUfvUNfk7esD3IOv09hnSw0KiAYZBZyhhB6aM5yC2v70K+L2GjCSbLwoLwAVICXbbtGpXbOpmrHO1B3k19Cr8G/pG0O1F74OKV5v/76G0T6UvjGY22318lHVv/iTfb+961vT/XbX++tavrbXQZBMEuSW2L3kbFp4jwP+V6gj3EvR8hqw00GF5tXqBD15tn/6YztO/687PWv+cdo9KJ5Z2fJ1RtfplXv+verf9fq3PFC9dAABJQBGtckUd31EbJ2Ehvunnp35Temb8EwTVqH74hdJMj9zN58clQIDmu+GFO4fw+qt8piBj70NE8T2TgjYfP3sPiDcUQmIKaiv/6cgSo3wAAAh5AX1BFEvxEiGu9DSIrCJTfZuecqpEQCa1cw4lSgRy9FSNyW9hpRbqBZe+A3HZy1hMp5V7aaB0N7Ren/tm2eb9P2/6KUI3DUTSllbqHcoecpelYoeCZPCYsmaR6HMQhly6AAJZAAtm2mCfUpb3Wa80/3if8idBiP9j79OPjp8uj9ProtnKSvVv/9i5Zu/dWKj830WgCVPQ0RPVPwrXMrrdaRZvwKiAQAMjkks9lcgediF2qNrUcoKwmqB/HZE5YJ3wfR6Ob6f/29ioq/ixX7TEUjfdFXjlDe1y0oj+lRo5W+SAzqL9ylL321AaABjbkz9fUecGy3Q2qSbFITYh1EZPBsNUd5emJ8t+qRkiTYlfLr+oGr8h+wRW9Iu0lW546lYCAwdDSBEw7DQ4VaJRKmIKa//pwBLhMAAQCFhjXmG84cEPHK7oIwiuIbQFo5LymURWcLEzzleIADS7YLMhUGLQKEnzUPYmdsxqXY1j0NCeUBmq85wD3C5Lj2e9BHdu+KSjwuTftzcj4WVULr0pSZWlXMWVK5n9Gp1+3ACOiA405QYqQaBVrE+q/3QrhAfC5/CubHfJ+Qnwv/9HoScHN+r+IT/8O2pqD2Khp49X51iWgzyjbVIU8xhxpwD0Q0cgAKklttjkV0NGFm5NtwniorgZqN/aHJRfRddQZfn0/82MyEVf0M2ogiff565KmX857VCB9MOvSgP0HWo1Bvbo6GVPWQDG5JL+zJWgUJYVUpbrVbd5XkJPQDkxVkfQuewoDZeh/bA4jR/2/24wshT/0M+ivRv+ZdRWIp7bEqm2bANnSfMtu/ZpFkxBTQP/6cgSoOAAAAgZA2BnqE2A+4yt6JOJECRTldUMcS9Egla9oExSmAE33/1NKNBL+OhCpXIpE0SJOgzpAysXDXM8XGCDhUH7yiatUTGp0srf2rpz/qjWzAK+sn29KNb3dqVBG+3V//68CAqAM/tlixRzFBoWyIZuj4baHbLhk+tHfbtiBWqj1g+GawP1vB9VcnE4fWfUc4IHPM8uCHxO/bl3nA/ievP8AiyFJt/+ZPQX3jWALU3IYmxNz/WlCz0P+JgEiyPGvF//yavp+kEnin1Vzl6sgZ3iFDDzwFIOeUDzLFqUFlrOurGq7UbsNpqqSZrkExIAGpD2Gyk/B354CZg7oO1/GUfQmrZ2+EzqhUqa16fVL1M8w4kDSHMXVcRXixI9oogZxqdLByq2NcLisrENyRZm3CLUpiCmg//pwBGqnAAACHFreUEgSnEBiO80M4kWIvOF/oKSlcRCgbeixFXLAiqgSpZbQTzD1jmHu9YoGsO0E+Tyy1I9Ta6G/N//Lo//95YSab72dV1DVHo/PeqKCQzK13raysxK6Oiondl13R3/4JghImNAlRolghuD4ca0Rrc0MCmBPBC8npD3qPrcVts+5SiLREVlXZ3iV0cDNlQ39YKnud+W4lyR5QdxEJRE/uDaAZSlcScuu2ungl7UuV+hovNs56D8PpwozypoO14u9eGa/++Mfb/GkeqDUdXnX0sSzDoxWsMC4qg6KERSPu6ZEwYnZj5CqQQJAQ9t+FJk6R8K23RbumCATuG4v+CYJV+I15G+vf/cPs4wNytbu8d0Yt3XJ0jucsaejbPdVVRgsRWB2uj0lrsVWm1KYgpqKAP/6cgS/MgAAAhIZWjUk4AxE49v9oZwBiJz1d1gygBEUsq7fElACAIDff3uhse6VNwBm2M3kQTaOn0fM6l2JMiZQnsyisByZWZ8DtfHdAclH6q1NUGzWo2LnRVzIuTsY57g3a1aFqs+HoiEmZIE9dbsEF1xzBmcF8BFwcgjNHxc82ybPU7l23U8Nk6tqe5pee4TUbT1JrNMWjUBoPLCAytWX/VeeNLInhVjwmun1gAAINUkn3f/f/AAUJRP45pagRWIGdzSXZTnU7sOKZAeyM6Vbx3QdzzmRe7XRZ11s5kUrMR8YqFTdDeWZ3Iutoi8WaeoqAAgAFFAgEAoFvMZs0godTJ68MWqdXX11ao6u2V0qT29udP/I7kb//sTO////kaRv///uICjqcCNDjf////nIpwO9CWOLpiCA//pwBDcgAACCFCVbVyUADERjG23mFAAIHLtiZLzsEQ8a68mGFOjEACQVJGxFUFAvSacX2tJuWcqgrPrRI0v62EfJE9xZvszA8A9VU7wb/N9f/yOrIHvOP0tbMLF0qZqh1bu0q3///+GQAACEYCrYklv1T2hMBdbqzvxTqiUOvlZhCiRGlR7/R6Bp3Jax+t0UYsKlHAVhFXqWxNCS+M33TWcMUZdtOMUdSqMymtoBcttmOsoeVA8RqRCNmhu92Gq8JXofzasriyOO+LO2Dp6Pksc//jz639NU1JzlDitcVC6wfnk7Wni7NX81jSi/qA/wbutNVgS4OIUk/Ar+wr72DmbD1M4a/OtCFcYxQ0fiumoqlOTRv+vexUeptHf+j1lIaXGgERXBX+o8Hf/2lutyn/lkxBTUUAAAAP/6cgTRPAAAAigVWBnsKcRCh1sXLgIoiIzVb0KYQ3ELFyyc8xzaAClu0pFwMFASVZwwgb0sRPaKB1+wq6hPqwQg8JEfAeQWf0T3oh/pKqdJc6ezR9IKmQ7eyJngqk3W95gJqREdyziDz3eaAIAFLbbKyRFCEBGMDehkNN6RWaDGoBZdGO4fHeg2jZD0/Rv+TTo31Qztqyq10ZcvdtXazBk9jJLI3Ff+RMpV5dze+ABBwC5ZYxmBlcyg31kSs5Q/D6C8Q1bsSpOBaPBhv+rf8+w+9NDaE+yOzte7Y495MUQ9obCIUAEBQqKOFAR3MToIU3fbCwEqbbCk2IptVE6NRfbWEL8IVFfK5ROJy5DviVo2GxpTn9v+mnX6Jnby5JA7OgJZI8OsAR869CAFA66TzB4wj/SRamIKaigA//pwBLxWAAACGyVYueZRpEMFu0o85TaIHW11QoRdMQcOK8zzKPIAYAkuumN8hqVlNhEG2ugu8ngeZBCPQmy+Ywx5qaDXPfFGnNuY1v75R7vTE+KMqQ0yFA8BA3+et8qJlHTVBq8eba3ycABSAlb7cSV8ia0pi0eUoKtAnwQfiGuth4/I2HfwUvyf/6RgPiukxkuuKiLQuSBAXSwMccPf707ugxSLDYRE58XaXSiCLRJlskt48cQSI50mbGpYPNi57KdGEHX6PjdAO/x7z//ATedkQxuApugbk1faAM5s5e8f1jf+x19Sxm+l//4RwAnLa8WwPgrYAs4NUABemBAIM5YUhAEh+TvZPs9gYGwiZGYWxW1bFom+T0/pn/GSHQxIwW0RefzMX5Fv1PFditvsTEFNRTMuOTcAAP/6cgQn5QAAghcd2RnjLCRDhhsjNSI+iDSNiaGcS/ESGKwdhh0aBDckk+4aEI2FDeg/4rrxsTwn6ziGX+/Qpk1eCAlDbt7u2o8NdZZjIItOyU5f3qc4gdsteoslMkgC6sdONbWKbP/T1ABS7YXYXBeWB/E4CZGfoeYgsbCFUbz8hburmm45Rb2jZdWwpuWRN6/yfs6/5UovCWB4Klg6tbiSMnqatCsQo5i9ifHYwFuOtyf//+puj2isXST2DBHETUxtMtjWxz1f6hUaanRJQqPXhdA8E57lg5ZEeGloEUeG2JPOpkM1yrVLT++pnh4QBTttG+1WeKMXSwWTkR5lYlDeGzxKJv23ahALsx4cJmNZRzU7UtRgoyuXNSfffXp30/zLJu86W97yzWsIo7yHd9KunpTEFNRQAAAA//pwBF6GAAiCESTbUYcR9ELDayM9hUaImMFs9POAERMM7V6YUAKEBUCBdf/KotSRMieNYBlFGhsoD/OA9qk+fljIaonbXs3aSBO8F3zGwx8qjp891H1JWB9QCnRrKj45yFt7t0DzT/WAHLthmmTuNXicj1i1aZZHTp5QM+xn6E6UDEYTqR6m1fHgXjC6jN63Kp9UDxNLOfBUw+XOw45d7ItdBneKtRe1iK4oUATf/2jhRS6kbU+RO3HU2Pdi1UImyGxlWeWr5+vHfWPnqa9f/VzrXbc1a5g6YmPtRF8aQCgqzNZQWva5Sex9iaiZZfzIgArbbZpQaIbHIByZeWDG0yZtYeZov9GxCran75A3a5vya1VROs/WLJa1GIAy84OlHQffrNrfkj9dhQ48zaIFHxt6r/JpiCmooP/6cgSOcgAAAhct4FYwRIREI0wqwYwAiM13mngCgBEHD26fEjACAAACBSAMA2//A4G83hQdwG8AMTRi+u8rmgNGedXKPO+LMR0ZJ56MmgvzdP//8jEumgYjABJcRrTQSJc//+XP///o0AgIK/9tv/7/7/78b6RXQbhup5y9zMncuDmTpmnNynEnwWyee4/NvgrbMSorU08Kxdo3rYtXiMKBawG2MIrs/LjXi9fpAAA+H4/H4//////////a053Qp0N/1QlBRCRQYFh0VUOiQn/1POdw+hBAOFcOCcPjBjsD//ZUYOMdCUEGxphIeCjjIUOGGIAAuUrbtvxwABYvZ6CoN6KSUSF9XleMgjCLRhyzNCuIfI5cQq9v9S/rfBB+XJoQTSVgNtDKgKRY8bDS1wP6ELDZJCYgpqKA//pwBPrSAAAB8hlh7xSgDEOmG2rmFACIvFV/oZRGMQmOrMzzlcJiAFuPyStEhXvIl7XSoMDFKP4vFVFjDEBxUdV8gbvxAOgEJKNEg00O9agpZEv/ivnjvklhoDdX/u6uRz1Yo+AIBQVf/hYI/ZG7Q1F4d7n0CB1pXIKwFtAcsgzveMZqPolBgJritUKxuugyg1DxpyvSX6oa6B5ic00yH1sijf/o///XCwCi3C1tpJaPiCBRQ2t0xmsNqAaIwKXobBDyUjQgqGF1jrwfJOWcDRJqkRIAegGqFXflkLMJ1GjzvldQ48t0WArjsqMUPAE2248/fkfYznERKrhIx7vhQwsB3AmY7Kw20KZsfo2cX7QaN30FTQN2A0YJGnS9Rg4yGbL0J/8vqGuAZpQnqCVDDqYgpqKZlxybgP/6cgS9RgAAAe4hXdCiKnxBBKszMOJUiTz/d1RhADEYDq8qjFAGyASkta620bh+NUBTRWtFaY3D9aMCfBAGGHwTUBCcfRAUPirpxB6pGf7trgmr1Jrm7num+ecxba7XuodX+GQBbbBZ/gdDLS8CIlU24FrwnDsHPH1LjFfBDNv2xATvEIIO+LkCSSUOPLvLpnyDT5fwfPw+Bz9uzs///Ln+cU7wfmARpIzbbR8cQgaLEjLQxT/1lXbEaBKDOxEAxDVTTR1+0UX9OM6o5ZnN6d9fEZLKRa1M2e7iLnrRAqm9pYCw+4Xn8RgVJM+GEr9UQKOlG2NuP3PJGuaM2nQj+Hw9aAJ9Mcj6CjVN+cvariRZTSixR4mGBEgTeaUPTbmLiN8VmaUUKWozlGPVwo9UwuQuYx+/YmIKaigA//pwBNNfAAAB/l3elhigBEOoK5rEiACI7GVtXJKAARoSqw+wsAIAwwwwxkW7UJkawC3M9+D2dH///fv///VFf//kIeLs///uQggJoKI2v//3KPIRAYjCo/T///7C4gOBRZVF3Uw4AAABmZFbbYbDYACIMUX5MCphOAPLO8ZdUOwc7O7qRRQ0Z1afvzgV6jeXuhrKm6Ms/uXV2Z73/4J6vX3X//DUm512YVGABwSsZJNVYigkyAYCWg7r5ek33BtG0JRlfZ//EQZ5JLhK5Tyrs0FTpAsDOeo1Wpuhogn7hGAmtiUq5bdus9uZZDoiLNw0aJDkgA5LZwvuPMsqPoclU36X/TJxyqiutOoGrBkbOSJp35ggh2lhsZ2w9cQd/vhMn378vzh2tDrl/KM1K5Vsxu2ln1XJgpqTAP/6cAQOSAAAAiMbVhMPOtBEY7udGCcBiFBni6CIo/ELDu5ocZ3CAplKncBYLZpWOCCokc3hTEeldN+rnmovFKSvxS28uXu6L5avbj2p7YVIVlkf6qHHrT8EHakVjxAENH8Or5ao99d3/8OwAAEtkMyIpQwZQdKnB6opRWkdRJaUHfo6M1Cr0Kd8qInMkGFeQpIVgw4hxPKBj8nBB3KBigTn/if+sH+77g+sP4nB8H4aAnJtNdY5NdiSpeWNqUbD6Ab0FY1gCHPmaZu1ABG4sfFP9wrxaoX/Ddd2kqtZ1VL61BV3fufUSix+IoDKnArOywVgADoNWNyVHc4Ox8XSMiu9IGKhLRqV6Eeb+OBLz5OOIMNQN2OFeLVEvzMVjNJVazqqX6mO37XPqbCz4igMCnArOywVTEFNRQD/+nIEuo0AAAIUE15tDQAMRMarmqOUAIixgXx4IQAJGQwuawwgAkEAEksWFGpLtiLGUVjwLD0E8lftpANpEuGZ8hnJth/T/6n/LfkXTxXlgaIiUPRUmqBTqgaljoBOtDvoOw1DgKt2opQAaoI6y228UC3HyDnyd1YMOp+L7pxr6Cv/x/1OHgp6qdadP0f9vm/76CwSUeKgYuwswise1TjE0YYlKmuGPU42ijRe2kAgcfj8ej1HmnEkAjzPe//6aP3///f//zzy/358EMQQGT/3znfhA44MwoodP/ujZBbyM7uxgiHwv//OfEEbo3QUcmI0GRQAAIRAElRdvqLhgGB8OYJFoIGgSVW5bQodlS9SNaQdcWbFNqExPnLpHqzYXAIqRTSoEwCDh0uG7P5EFVbzNJn8s0+aBJ4dTP/6cARtGQAAAh0q2+8k4AA1pCuN5KgBiQzZZOWUTZEvoGyc84l6QAASagUkYBFxiIH2H0ItIzFafo3uX49dso5zbkW/NZRFDC77/nTtvOqhqUaxQ+w3u6Fu1ILf2I1NdaubSyy0b47RpGoEEoqMJW0AC0uNE9dYhkz2dj+P460qDeLLFCQ4ZRIOygFpLr2oLydcnlZ95zZFxYjuAu6kq4AgIjKJO2BHHLQpPCVIdeOK1CGUVegd30cRZoWHW307AvaAOFClspjUebluUpWDdDf/0M+hgYCedgy78NEP/O1qPZ4795Gs7LQPBM220rFs8LXemJB9p7lcF4RMwsbCV6nSkLM9CLUTtwKNkKSh8G2CfR6LuqIq26uqA2VXF7pv3aTq7evIYg731rFyw9S7kdpYw76kxBTUUAD/+nIEUtcAAAH1JNcZ6xH0Peb7Az1FRIkUlW9Uk4AxKJdtqpBwBwBJJI6RdDeI9uP0lyqobGBJSlANT/aHqDu+p1Fzaj+Uh9Oj0DrywE7rQG+7aswMYhHfaLV77a3f2dKvVd//UAHbbXhxiHEK3cBnL1UeWvHASeB+A3Wg9wxJz6F/GAH3nouvd3vWtkZv5uyH0+t66oKZoIVmrGJdnG0fZ/6oAFrBUtkkUrBS/qNPt3Pz8h99BuyDgpeSFRgUcTn99OiR8JwGEDEe/01dsxY7LNfHLKyFxVQ2WOMNh49RuKR929kw0rZqin8OQAKuSXLJAQZQchbGFprzD5Qc5Z8U3OKOODJpE9SwTk5T+Kwib+v6/q7T+voz1jppMlfU/luUHIIXPBUPWI9tvM/Qj38/VRn/b/bTEFNRQP/6cAQiNgAAAhJfXRYM4AJDbKvTwZwACNDhcbxSgAEYHC2zjFAAADDDDDjYcGOKs1U06UXnX3o8x+/vzffyF//892MN//7EyAYG///8wdEsXjRVMb///xLMMZyBrqxX////8nmmJNpaQABQKBQKBedop+aHzfr9v/v26//2//7nuxhv//YmQDAln///mDUJxeJB6jjf//+JZhjFyBF1Yr////+TnucahdTzSioEIAwxOIgApTwKZBIqXtoKjjGGNvoRBIwDNlH99MwQArbq/q32/6t+or0H9DbfIbisQkudYJbbNj1otb26nyUq5pBSjy8DAEFAJRlAB94ATqOUbsPG/mnM1qHbSqOOxgOah/6ff1b7ffq340V6Cz6Ge3yCz4rEIabnWCWSWzY9aLW9up8lKuaQUo8vAwD/+nIEa14AAAIXQNnQ4R6wQKgbNygj1gi1cXFBBFyZFqDv9FCblgBgHBViDcV2ADII4tPVEVaB6435fNPZ4XbFBP9OJif32ah/58w/h+ltb9Gu5C/w+0qDOoSZJ70OtiLbVBpwiQ0JN9QIQFWQ3LsEsTLjhNdCVcuKMrz+vEdsXF+nfcRfSUiZqH//D+f0v36NdyF/n1SoM6hJknvQ62IttUOuESGhJpvepAYGgldJblRRhqlFSfR8A1FfB2fDCXo/6G7RoyaXda8nUkI/F8zwqfyEshLXq6lmWtRO3ZKf/e9f0/V70MaiHCsCV2++oLILjLtDesUlVLNVmiiC/IuIPOGvQOulegO2f8oBcseyo7VTrstSXuL6WFfyFy//157PoupRnXuijtxZPpZKvlgOwrW6VmUxBTUUAP/6cASDlgAAAhw12BnmKRBDyCsHPSc4CJDXgaKcVDEGoLA0UR2eAF913rNGN8iKgYlBgyiUAEWwPqK/C4DXgOCvGP3w83zp7/Q///lt5TvJ+RyCrhOLD3Sakd3qvajgBjhf7CySlYcllqAqALtrtXUZFlppMvD3naL+icJZJrKjmpShcwZx59DPx8Jela9//9UX0V75QYY2hHb76Ie+jq3p1Kl/xQx3M9dX28W6oCym9I29I5LHUHVO09VwdnGgx6g2qQtzWSFWeCfvUAf5kRKNun/5/0XtTe38MPJjh+8sYht73lrUqeKrVovd91b7jNG6AlOOSpTWOSxWjVnmgivRLBeF2OyuUfV9PL4QBvvMpz/p+mhf9r6F377fXqS2T+lsYF10wmKAdim2dK6XaLpOrqNpiCmooAD/+nIEfIsAAAIlQNk5IxKUREb7OhkiHIhodVpnsUNBD47sHPMcaIIEiRqOQdAcHf0hazX1aoNiOJ0WDHDDLI+CJq+z9otFdlP1zNyJZTfmb5Vpb7Wflp+6KzJgTtMDM2MpLb7Or3VgY+/RXCAuoBkbchgaVCWaaMX7NI89ELPkSbQA5aubgx9H/f2uyn2qn+RJXRv06Mnb8kHwYQZVrdNrp6UQgl7eez+1yFPa43pSALGpbTbsDWZrYTDnQg6zH4au3FtsGrIyCJI3CfxXPfN1ehM+h9FJmlKm9K263Y8r4iYp5Bn0DdvuKWez7GWW0vXFTICyB7Lv8bjkiW8AMzhekm/Cr30GWqkrMc+Jw96HfqL+fNjh7rWdz2cVrPs9QYhNTX6HUFKhmFwqMV1oe7OaoNK3tpWTTEFNRf/6cASn7wAAgh5A2OgpKDBBY/tqFOZ7iIkBg6CIqfEJDKxoxJxqAAAgYIC/u/98ABv0WCdBc5CoZ5wTh3IeNcUfCxd9/oqcfPHNr0qn6Jf9E8/Q3+miPqns7Jag2uU1Dtq3OyOpGj7idqwAAtINZCUvDBeMhkluxMaNga/qYKJs0Z7/QsAZzI7xuSfrRBTewt2bv30X51SCxAh7QIE8a7v7XLRNLFQisPLS5EIYVldTn1tuuJ43Hj2o+DfFYcRWG5B3/aob+iMul5W39lz0+7cQZfnVXIqMpSCdhJG9C5ioEWVMyICoazw33Uckzet1ArgFxtyb7ouCUg8MEvIau/BEQrG+gmdjppVihbKB/b8XAry7lcCtJLL9RGIZqvEp5TyJL2I/tLiNrNSG2L+xrDSPemIKaigAAAD/+nIEIEIAAAIXW1aZ7SrQRAT7Ci2lNof9bXmjhFtxD6Bu9DKJpgBqndoNm0nAWd4pUlMrRwVKC603qHxp0ldA/VGOeUsNAx4U+rVAgPq9Uq/sl/9P/8v/9emhb/t5V//oqf////6rG9IAATgBxuSNh4WHMpDwFmdFtP6o3lUILidmV1iY5YUG/+OAL7Safv9pqi3ENRtylMqMFETz9TnAQbRRGL6drHFafdff0sAAFxtJWxyY1p5fUxDbctwXtU7U2jEXVyRB5RtWoNU7Xf/fr5/176OS/Pjk/k6rOXl/C5GfXXf/V/8g46VaOjWEAI1cSbm5NRtx7qPpgqcaEMa+52D5YXC+j9v3L95tP3QyNCGoUv9PN+jeh21epVf+xWqNUQ1er7i20VH2FBVxw5QgqmIKaimZccm4AP/6cASv+AAAAhRa3WjJEyw+5rtaJEV5iOUDeaKYUjESnHC0MJTWaBAMkYStjkuKg1fbA3X7bpHd8wjvOsZvRk6jdu1HG7WjP/9Lb5n+t9iH0b9HdLy4/5Ztzleyfvuv9aadf+v/guyyhIBCwVYklO7DCySYytSvdD/D2E1FbJBwo+cfQbvURATllY5dEWtSNq8+Zvr8qfnujRJ1lF9G8kr//u/VU67y0AAAbWTe1luEB0a+gqqX1DXwGbL1AgwoZpbLqNpzif2o6fYtyc9EdNdjO2DX9vz6WUl/QxNYaQln3PRRLVMe4onRmEhvFlaqYgZbonf/d+CG3GlquYlAIA5gDaNHfVkfV9G7YcAXvS3T/Ns6b+lSM1BRSslqdK/jXi9ZtUNadZq46tTT0KqFFf3XinQmIKaigAD/+nIEljIAAAIgWt7oZxNsQ0gbygziiYiRAW9MJELRGJcrTaSdagACYlInNrLtvCRLHQTUqS3K6lM8/LFGeezzF0eVD9PnBI/+vCdy+tH8j9d+2+o7W/9HQzfbv6FdVsi/tcs2d6P/wacXABj9N3WW7eAAAlgnEYty8yoV9tVyxzpE4flDe2NxK/v0+rrKpUctKezdhy6rf1uXjVabyWkdAYhUdzPVdOvJ7flb+kAAFghft/o+z8omw0GXRkHaN8bq2E5dmqy8G7wSadm/kt9LNZ7XVU/DsW9GtejfZ9Vo/TW89AzdyTknJTKnL1PMs+T/vACkkgSXEZsjxmIGhwFzZAqspfWYneUV9A+CHwkf6Szw/ozb18GQS0ftjQj/T/9ul1VdOS7kQYWSvQg8oPiUgKR7Kwl3atyYgv/6cATgagAIAgJa2jnhE/ZDhruqMOUdiJC7bUScSFERFe0okomqAiAJrts9AHAXRvrL2C3o6r6FL/31cfFspoLwvJf1/GevnPoiIn+VfZtfssrqsqM47L9S8yf+/RPJ/X/p+3e4RYBDPiXtZbKggP3qyRmdmnUZ4RNh/6RDqP/jsB/WSHjNJ0dDMrVNsjm22bkSmX+JFWld1oSKrDw9ewiUNPuVTAMqLX+oKwlvv/TpITcOZZdjA6M8QfBvULjHg6cEK42nAi94zBua/d2orXo6l+boLY+++LFGlhpAMNBJJeOeZQbWTGbaGmEPd/qBAhoFXbbgPigNKtVRVFg75pbY+FtgI9cZGNwbVu2E9Ozaf9rI9Gt4Qzi4OuC/gpxjVsJrhqpy3OSgaCptw4e5RUUhsUTEFNRQAAD/+nIENBAAAAIkNd7ooRcMQuV7bSDiX4io12TlnEvQ/5dr6POJMkgQUE4nP/dsVnMO3HVU3V8nNuSt2SJCmo7RsSDfvYb9Wom4A1Nobz1AFwZGUt/rg11tdGiUcLtaiKytErU2rLEbtcu6gBAAluEK2uRrTCzjZUKQf0y5Jo1L1DbKix5y4d37fmBt/1V9tdWc72nVOVYYsqCwZETuSpuflQNDSGnUsv2xNT27vWAcJ3bbSW4sGOUD0JbEFIY4vSw31I7P1tKi3Kn/ULO6X0Yrqv8iOhHJVPwredUsvSqKGI0yMFXxiGjVITrcSSRvYz82v1gAAOABbbRmIlDhtVvUg8bhEt2mjGPPiS9GcowHluzbd8OAf//NLVWttezSPzCrFT1BFYu8F42YcksirmGJ936UxBTUUAAAAP/6cATVNgAAAh41WT0kQAxEJrvdpBwBiE0njPgjgBELJPFrBFAAgMCHI21CCAPEvDwwTxJFEN+zJ51Cakb0e4qzNk/I/a8//WiVJtZdrSpoKJSrsdL3AQ2k57rgKdfcahsVZPq8FY9bPmUGCXrao7/tgid1HD0vc8GAI4s4RJmykUvqJBb/iIV+vf+a66mpRKv46ejmo7O70Rd2eTY1gI2vFc4ZRovtqSLuddD8OAAgCAQCAQCAQfD7uKfzfH0/nf//qfU//7sPkxoIn/8SAdg/JkBoQzf/4OxLee44NCH//++ePkxIY8aHP/4IQwIChwAAAAAwUDgcDgcDj4fnFP5vU7d8Q/Z//i7zv/+Jh8XFBD/+KAcPi4oHBTL//A4vfQQJ///85xcCMcOHP/4ISAgKHOlMQU1FAAD/+nIEKwMACAIgIOBPIOAIRAG8b+GYAQhs12wMBHVA/Ycy9CCkPgACx+lBpEgSszXNSqquzTedoFgyd2zkNZta/Nb/2aRsFBR99B9T155q9lTBiFrcR3+tr2GIYIooas41ZUiJGLqedYSoACQiJoU97ZI5cEF2Y71VUtmubLf0KH8seMGRcnkvmCtgoKP6Hqet081aLKiIxAXW5P9bXsMTBFG1bCS1kRIKD6nnZKisBOBgqOVznIpZy33nOa5+t73Y1YZ0t2UXMfl//3+c+qNneeFPC5LAwy7wnrLPFKW5Z0kBkIi9dMm8+rPB1tspDjLdYLcl27ssbSUhBQokGboNAAVp6wFz/li1HqAiw+PXoJ3rur5w6y8q8JKnrZJbmOr960rsllD6+pyxVdhLa4YmbTEFNRTMuOTcAP/6cATbPQABAh42XEnwE6BEBsv9IOJnCDTVZky8RcEOGq0JkIqgATVDqqqH2BDP6N1O9iRdX1m28XtqqUZhDkjS1N11fzfd7t8zqGqZ82yddsokqlmfozr6RKHbuzF67MtWjf057grR2awgVLKTpLG24HYAMOky0dO1a20SlMThNbMwcRVlX5u10/zPDOpnzbJ122ZpZn5mdV6RKNuyDMXrsyyFiXf0rh3grR2LrGYG2lmqCH52MsaqeVnvDn/vL/evcXwAoQtvZc9gZGrJfytsZ3/9S6E22720UxZabvo9vBM7PlFV9+1AxfTv/6G9YP/+FYQMNH69+rfh6tnu3ew7/45dt71ukZ6NCTVL+8lyvzW3f95hyJVrirSCaXe9WR7eC9M3yiq+/aYGLd5v6PIN416YgpqKAAD/+nAEdOgACAIXQNzrBjhoQmgLzTxqDQiY12LMQEsBFBCtaPWIuAAAI2CmbG5KMJZuKtjLSS/jfL2QqyaBsMJ5h6Uqxi+239tHq3M/6P9z3/5m/lC3/qqNOPSxW7Sq8wldvnF+JH/NyFQQBEtod31s2DFONnInEP6K5bvj25ZSUFL9kptUzzSzt02tTofZylHW369H9v97rKa/v+i8r+Ll3WhMW51e+tnxLXsCJXfmByP0od2QtKfjB+6th40k0lzucVYxBpmLll8Mh+kG/tRqfN2qXtojX+jWZFVWlU1Sf0F6l1v5n0KzCYe/V2P3+ysAQ0A5JJbQFAcSE0nXVD6rShLPdy1StSPaCWBtj/20agV+QS6u6CfShthHIMSs0uw5BMxuuU/s1j5J+MuZd1NzD1M0J+hMQU1F//pyBIOJAACCIxTYswhZ0EIDCvJhkTgIqF+FoJiqcRAV7OjGidABAt3+DUApcNwLDrC3YmTlNAEo/mhXkujrcAoG1dfiWrdfZdkFF2bXsK0NaNtD1Qd5NHYFq7j6hrk3+m7dRZsu6H+p3WBM1GGgOm2OTy/OdZuSHVIIUvXlBpOvURN4JcLhjQnD+izTh7NvnosLPUnLNebf68kREEa3VN9/vSa96tNCuqgvw2CEjrSnJZG3TIamKWMYYvVhSSH4wNVO6JJwy2guqwxSCZwyREdhJ7ZIHQ85rj4u5EOCTaqq9Vleu57G/TqTm33DtEgWFHmAAW5ZaJo1Du8+lSmUD0mbszjDedZJ3FiBOJFx7qgKg23ofWjbbouV9Ge3+mlRI+0gsmgkHCSlK1tb/2HY5X+zj/utSmIKaij/+nAEtKQAAAIgNdtpLRMwQSI7fSRqVQi9M3uhnE6xGZrvdGKJdgAAIUCHb7t+DqAr2Cj7jvMU/F17WeHYpTJta7YH6hp2ZHXp8yIytdyOU2nalfUq/zMlRLqJ645o/j5xYdY3JqsyPfWSAAABgALtdl4yStN1iZhfdQX5i0ke8XApc5vK3JxFezGjzT8ogqzy2/SCrxGAXAypWeY1nfVQlj1vt05rLur1PRSACQ4wklG0nElPZcvMx9VAN/FAZU5rkldWyew1O/5F3Lzp6t5S2lRuKN33bv6b/bPvq3/vz0RUog4UFKA3i9olG+owwmycCTghQLbjaTkWtKIOAzyO6xM/TxgHmarsIj1FcE+JRkZLXmkRD/Kzum1S7dQilZU19248V0uZ7N3OraoWZO5tTRDgEy/tuQmA//pyBKy+AADCF0bj6KksjkMliuI/B3QIhRl3oahPcRaWK8zHldBtuP6jTX227GMd6lplDSEVXs3oaoTO80Y+ihrbw+5O+rDDKet3W37+vzNW3rVUmknvf/ov1/+PshNTskVR3/8fbnfIBX/ochhPn8e1yPbG1gNGFXyvlZ3m9Tc7VLziXqKOUXsVcKPNxt6C+vP1jnou+bR/o6tow8WHiHbIzntd/v2+yr//6gQYUkCE20m5UDCursxyF0l16AwIQgRtafHvqMkR0T6OnJ3kZ0lTrH6W69dHa0te/+pQz9bJ/qKKErbR6+0qX1iF36H1gJtyWrTktWdOW1kGBsJjt81Ec/+uVmMD5VEzJN0GqEhTwj6hyzYymZ00RpLpdtu0fVsdIvXuadCLXua7X/Uyxkkr21cQuTEFNRT/+nAEgVAAAIIKNV1QZxLsQCO7AzIHkAixU2jENE9xApptZGaKVgxZAKTbSc3BGbTvuWbb91AeDERsxz2bipOYB9eqGf00pVrX6hBT2cjLIb1Mt7RT5jdFHV6MlKqS4NjfoWjGfRqkQBHLbUg5Dhyll8sttHZLv8y8r6d1hmwrAbCkXD2Y4rZXxB82i6+ULh6QHx++o81Tr4uAyMstqFyGR0fGevb9/SBgGZbeQwlwUP1zB6cKn3gG6QXUe0pPp878jvt6TOiGSyb/v4JFZVFlNI1bs7eozFRretLVbr+/xrf4Ry9k26VB3bPX2R8yATLYEEhCuV7fpIEofomPzapbhAIy6TapNe+T/m308E7fuiEb/oSjqQZ1t4X+MWkslc//dQkOlAatypa7s7Cu9MQU1FMy45NwAAAA//pyBOv0AADCFRjXGeU1EESDq0cZQmuIoS9nIxxPcQOaK8y3lggBMm3Yw1ozC8yHZaAjrxFpat8Wi78uJY57AOxb5W5o8sUcT4Q2lgjpG0qYIqjv9Toj+X0KUza2Z2Z6sb3F3dvbt/9QEASiCVKHKUpeB4nB8x7rpUuwrAJscWzowdCegLjeMxNPcIOlcBjPDmS+9CjrmOrpQ/dTtbOnXD3jKboXe9oi4Mxs7gmCAQAKKebMhMdQ1fwi29kielcKgkst0Ht8V+YGVMLv00dOn0+3oN3K/v/0BV3t5K9//v6/BNOyhBRKS6ViZQJBqwjfv8uAW5Z+sDgtJhNIrTg+TYx5jhf+pcWPwD9ElddXQBmGPgG3It7o/p7/Uye3oXwi+lf/GD7Xen6cnhivv8pqVTbZ70xBTUUAAAD/+nAEEO8ACIINS9mxJxL8QcXK8zIHdAh5UWlDHLLxEqus5JUJ7gCAyrZQ2HzMSPoXVhYt3TRlLOFwSlWVVcf3aKPHhLONf+ZUv3XGTo3oXwjrkT7dgR/Vvf//0f2+degkBW+oObv/QCJNdsvHMg0qXysxD54FXfvVczs6VEyaBcUSp6fULmo6wWN3JfbZEN/ejFk0T8nq4PHahYwOOuqHt6cpd+EPmP6dGiAlEkuFWLuSKaavYkWVb7f789GSNRPskx0ugV9wm/2q6cn19H9H833+vdB/r8vy/+nzfCq0Me61f1reIuit5LImac9UC0g2HxYjkeUWaN0qArZqfRF8s8kjACFxn6iyOnOkKN+VFqxSZ7B5Ol+L9H1o/m/+vx//L9fv6fDN5N6bNX/0L8f5H70xBTUUAAAA//pyBJvoAAACGlTdUGcrbEMqm0kk4l2IjSNxVDOAMROr73aMUAZQjo0m1GpeBTIFsM+CFlFtjnigbjcDxerOw8Z4jeOhJ6/v5/p9/i01Dl8U6ubWMB/V/N9//RvX43bFn/578Zw7uq7KAAMQFiH1TqbXOKW12gWUzvnULKJgD1H30QVIr4UblEXL94PXX6ff4/hW1Rvv6v9/T7/f0+vwO1VHMzUevZ3wz5z1fUAJ0BTaabgfg6sQi+DFiJlNV0CgbZXzYr9AenvPHAcnEzH10MLnVmP5qdX+/s+rH/+hf7+vqnv9FRcdd3ymUZ6M/i3/qJZIdBLbrjlzVlJp5MP3nPoGCemIA1XndQHZXwt3yNv9TB0Xfk3y+r/tor9n9W9X9H9fk2/oeuImXij+T/19VR6xF/V8smIKaij/+nAEfhIAAAIgSOE+FKAARKkMAsQUAAhUPXK9A4ABEZrxt4xQBgEABAIBAIBAIEqc4Q0Hh4KxCItsqUzo0v//7s5l/+HDOKOWv/wcPAjjziGnb/kFyITEzf//5kZii48oid/+o9eGwyroMMMMMMm7dwcmkOBsGLyMkzZTPaSwsv/rb7s6LX/hwzijlr/8XEgRx5yadv+Q5EJiZvun/8yMxRdxgcsK/+IqSpp7O65QBYANQtuA+FGQOk0Rp53PukkkldTfCAhcvsZbRbHWpzwoJViGsdUCZA207tZvQnW17CQww7rFyS6i73Vdus4eI+5CAEUmo3GkSQFwMG1nqUfu7VVXOpVV4iCmd+UQS+J2217p/5tFLVPX9HRrrL9TGujHHscDWLp0EPxfyaP9okLAZ7gpRc5KYgpo//pyBIGzAAECDjXl6GEVzEJCm908ZR0IkQGPoRxO8RUbLvTxFHwNuXbO2xtJJ4UBZirNqrZPCNjP8MJkH74JKsBCbmadf+zBjFCU0Hlz3/4bGdtwBdBZ5We0fjyK1rIor0NO4cWpP6AAA5aTYyAAE2BaVbYMoWH0SxlSm0PiA7faEhR+HBEbWSeZs0iSBls+1Iv50s+UXQWeue0frIrrSj0NbUHBdRa1fQoOJu6m3SNwgISU1m0CdQPb1II4z3HgyoyxxpM25L23fS50X2VS+hvvr/ab6A2R9r+VnfqDdkKhEtTa3KoxGGkYNPQnZrAIBkjUkbKBSvKhuilR4rDDLRz24mBAer+cFR8HboM5Ee27+50VOVVL2Q331/tMnoNYRXHpVOt2QqLLtrdoxGGkYNPR2a0xBTUUAAD/+nAExWQAAIIjNVpLJxHwQea7SWUKLAf415fjBO5xFJrrhbYoYAABguv+xVkXtf6Hb9StsYNiOZ40a+Te00vZIrDo2Da2Dy7epvR9Feardu7OWct3t5r3NBBU7W1dlCFoX9lBW//p/fqAwAAYP9fYq6P0u04t+gcs5YDMt+Kerw8gPBUxV9YvLImIn3GdNE9SbXe/pt29q1bT9L6UJlr9/MN9au46n/3+p3IkANDZpd1W/62W4vJzX+1sBCaQfMPwsxWtejBc2xqCxa1Ck5EV3TX93oy/9vmGdK+39Cf1zDiB/97WMs+tPW776nnEdCEPKmreHRBqJ5FddAo8pxYk7ow6eB0FB4VU0cPVdmQVjuwuR6LblbqtfQ9bdda72//tXl+l2tyA8/1nz3FP/FXetCYgpqKAAAAA//pyBAaEAAgB/TZXkwlQ8ESoGylhol0IbNdpR5ihIRGm7nTAlGwQaZOUwMldsWHg+2FAb8QDOYjUVP6tMxEPgnEtt2CaO9RW3oPfv6trv2Mr/9D9J6/X/cfeek+V+hlL3g52dz9AAFAkwgJkKpjBdelocs5BluRkq1ce1TTrwxD1k80utjE+vlTO+H/1gxmTKPa6f37XWH1Wr/3pU3vVf7NwvR7fJfX0O+SbAglJJwcwHIilkJDU/okxeI0THSmULbVgEDU4j8G9fr9PvS1kS1tiq1fXv7VFa4DdM3p+q4LCFkOn38sZtkTq2ZREwCSRF6IvZZNh6CKHYBJWFh0FUF0GigiBh9WvQIP43wmBnjp38aO9G87e3y+u5H+7/qO9tlbJ33//0k/nca2u1zMidX2rTEFNRTMuOTf/+nAEqDMAAAIdNlxpYylYQybLuSAnR4g4jWTmDOrBDBrsTMGdWAAENMQpZLLe0Ne9RBwi4Iw0o4F1amx43rCVOA3jQJR9vX4m2jr16albRTafUvboO9qyIBMVa35JOwi91bupaQ5G21S4Flbd9VchWnqIYmYiexWtopBJSvmg6LWwp5oY2qZ4r+69Sl9Pq3OSv+pk9jqsNexB4Xi7yOy8HhQiqwsf11PH8OfvqMoC3JZsKy6WnnOl6nul/G7F5LCjA1R3600En2GNP5nzFtNIOYGVR86Hd9Clxz9DqC7oi+ca2pgXgHdZdKnNDViwwJt2S0Hx6Ih8ho1Dy69BSw/Yuq4IoJEqWfxC/QU+cb5VvbXTzCzp3pmb3V3azzaFWO/L8k2KXBQ/6NzVAOl/2YA/fpTEFNRQAAAA//pyBK9BAAACASxcyQcVnEQmOzok4lsIyI9SB6VUARaZLqgyid4QSrp9lunFlqenjIpfV+PKCNzQSlL97BxI4z4mCWhFFM83w6Lp/9D7IEdBJ17o1zeq43sd76VlRUeAVfHJqTZ1AAagQCm1ceBsEPxtODWuByXy3SmKg6KH0iIX8f0J6/T3X0X+j0foFs39hKWIBzoPW6mV5edFKhOdYzOqtfY9+066g2WEeBRgpDqUxWuTW2sjOATz6i0qqsa9aldIB0BfwyMMns60BZ6jKgEGe5EFt71OfiOa/M2ZidqnyLyJa0vWSZq6/++jd0f+kEDtEki0nGNMrlZhwaLHmVbztGfAKvAG6L6+x29Pv9/V3RnCOm79TDGJLc4WwjSjFNj5zOsKg1MrWgYWsWs29JhLBNY6DCYgpoD/+nAEL8oAAAIJTVxgZRQMQ4ab2gTio4iopWkhmKUxE5TvtCOWxgAAIYCFLP0LCwWc8AA6mqLGq0ChUEN3C/D+plRat4V+x//v6v665PnamSxHRtN+v/0f09japGjT74UxY3LJ8vq1AUXooy43LMCkIwFA+IO9yHL4n3aA816Ibarev/yW791F0KWSqRCch0vYZahAtRHbvz8srYooTDwdjGKOEkRUKVfMOKgKagDIt2VDDkWreFjLnpTtVoaKRg7GRAf4Q+BGvjt6uq3CI+mZDLU3x+3Na6muFHe56itxbL4MipwGAo/jLEtU3NSmhVpJBNTJcsbclmO1LpcWAFPbt5hVG8AHc/CR2wgJspXOCCnCx9aK/O/r+P16nVNipahfe2ITglWQqCyp0woWadtK1ndCPFExBTUU//pwBNptAAACCC9YOYcTVD+l+0oY4pSI4Ul9oxyycRYpL7QjilYDgIASUkKj63B269PlR9fj/DaVmBgdCEaxMHaMCmuLy3gf19f39f/hOjNXKNczPPv+/14ixGXWSFKbyJGig/szrwAIgCKbbtMoH/LmC30a+O1pRX7ocWNJRvE3wXFvH/9Ter+Av7fCIurfGrKakU7NP27MSwkJW3n1X9b7VwK7oJkJtaSJSJty58f6YS2c5yOx9CH8cw/i8ousQFr4AtXAA9sZ6t9/K/n8x+rP9vR/J9f+8nOo/5rat4xfRzbp8XNkjTbnWqjvakQU1S023G7kHA9bNYMN3GOp1tKIqiYn4WLXwq3iX9Pjer/f1+D73vsJ1k+jdF/3ST//7eQzozBmV9NXQXCiBZZ/bvVJTyYgpqKAAP/6cgQ28AAAAf4kXWhoKpxDpev9BMdjiL1JY0ScTVEaJ6zkkoqWBBDrQJjbSbt6pgghTkCApNAvrvV4x9YSDI+gfH+EmphH4n5TbIg/W8BCTRpyFTNPz7AqyVdODZZ3r1aPU3bcRCSKkyTrljc1DcAjKhhK6AWYbl8cL0L9Tg5CTKIxejxGb1+b7HbIeeiI41euRf29ydpDIMkft3V63R251erRKkcnWd2AAYgICUnKVBqaPCcMXkog+JyytQwe6YslgnVxd5QOk7kxKR2h/p6m9W//6p9/p9vT6f/JT/t9vtylX0PyDYhoI1pqDO2GAAEwEQWw8YWn1rF5y6EX//MT1S4OpExVj3q5AxOAr9Qn8nxXyeX6fN6P8nu3givwT+3yU0X7fG1MjaSv0CH4BxDgnSmoM7UxBTUU//pwBMuGAAACBEbaSQcTXEKpK2og4nuIRN9tQwiu0RQqreRSjr4ADIDBCemJPkC4QngHDxesXoi0VEF0r1nByHGIEpdjUk9C+o3q3lf39V+329W83//dNHQz9EL4dp9u/dV8Dan+kIhcSSWUFMxL3U4usSN+wRo5Oh8Li2JztIUGPKegSPbN+I+3QYdNDMtDH+3y+VrYJiam2m///+vweTxDkNksHcjVzQAm4FhORyNLX5TGVnJkTb8jCsCx6CoPrDjSVBvv9/iv28Z9vIBvTyGHejevkHc1/kA+zDvCI4mQ1hZwgGhYMCQv+0ARsFGlpSk1QcbEx2pQEWKZoReN6R4Z4f+P+/oO/5BnoXyA3kbkT5O1fZv3T//yMJnJfQ3n8qV5TRPL64QQo4HxANOP+tMQU1FMy45NwP/6cgQRQgAAAgUkW4BmHJBEBHuZDCKwCMQ9hUGERkEMB7BkMIzocqZAnU64k4iEemiJvZAbhMmnrxemVDizAxZ+iJlELd3w7v/hBEEFg4YCwDE4gA/LlHB9jA+IMmin/v//8m3+J2jEoAAA1ExlDinXKxEI9NE/oAMQcDfqH1GNhxb6EJoTU55zn8IcKOJkBrHhYM+fDBccouOOVjnLf4/Yn6/9zFjy/1lwvgFIAAQKTlIIIrmESbOO0tFuy9wahM89VYDIFhNDxZDWGggI2CG+RcGRY+96HvEFChWl7vmVVdTmTRdy9lPGjx6pafNgPFoAwAARb4gQRXZEmNiUMjtDXYLXHhw5EBkCwmh4BbDTBGwQqfIuDIsfeaQ94g1L3u9xlVXU5jTSRq9jUr1rHqpn0rxZMQU1FAAA//pwBEgvAAACFRreAYYZcEQg3BoBgwAIuAuFgKQgIRIH71gWDAjuC4AVLcaUcMX9p/UdqZjzo00oci6WF+S70ihJ0wIlqCiRiQIkzF41hF4BEpcqZa+ce2MCho1Rvm+uyyn6zJZrv09AAbIIFptuEIYqgkmEClHBkUQWtSLBxRMzl0DEC5ZuqHRkCJMxeNFGHiouA0Gey1ww8aNdj+1i7H0J+NMlmnRbcKUitCAGQHUirMhzSCTDRIiOewWHwZgRK3FVFo8kNOtOEIjDwxaGx8o/GEAcNMAMsbaWWywqH1LO/2UdzLtTTtRpohF5gwfPHmDwMADKzCNA/4iNihC5Hny0+EhmCx7ioxTR50ad0Unwkt+BZQCUAoQDzWAGm0tZoJqWd6Dex6NjiNKWKPnVTUq1znNexhpMQf/6cgRPyAALAhEGXSkoGJBCIbuoCYMACIyPbqMEagEdFe5kkYmoC4AKbkQBmB2DmpCWBY2DTUAFyBaaOFQ+I3A2DQJsyIHOJKhNxtDxdLW41yLnrWdehbbamPYy5qbbCyO1Wht/HbaFAQAAAMWNRsEq8DqAjZzJu3qYwkM1gogNvQlQMDkVCwOYKgpHuW0fO0ErqbUUOa4rhRIsp6Bt61KU/r3Rsj0scvADCc80SeNHJy0pKsgESkOdUpYdTvbFQisMQywksQ9TU0YbvHgYBMNEZdDx10VrKxRbw2PWtsXF/adRWLd+u0laq18jXZEgAEIIArUsGB+b+9n9E7M6qFt9W96pYU+zmvI1cmS7dHatlZGpRkfU8o8KLdFayrCK3ht/i4uMs3IrExeffUXcTJXqtPNI19SYgpqK//pwBNk8AAgCHitaAecZ4ENCS60MI2YIXF9lJKBpgRCL7SSTCWjUHdWsjDM6fB6SrSiohhg1C+K/jJz1XaoSiVXiN7Up57Nsd5w4faRp4MJAI01Li4CAxIJNDtGCqqHZ1zH6qehmvX+3pAQAhgCBKbcmFjOZsfhaS2h6pE58jAn2vQJSvI5ZkqMCGLrDAFmTWwuAizRYXHKJLiUt547FH19WhFJXWhC48VMLVSiABAFZJ6QnmBQMGTOYAwtH2ctLw7A6G5Lpo2DBkHCh9RBiyhGfAy7GKhZ21C8WErib7tLiVdS++9UkyvI9G3W8o7uWABwCAt/1saSUYM7Pg8Sinkf8zlK4Eh3mGriMZ8E/cS568V15Y7rBXU5K1PnciPOhJRvLBBR9RQhsquFYiouivKOyU8lMQU1FAP/6cgSolgAAAe8oV4MMGfBEgvt9DMIdCPSxdaEYbSEdB630NIzg1veE6QjfmfU2fBWl+m6K+5A2MrQEaCIiT3WQjSmRBEsgAc2KJ5e8Oc86bmZQ/2FF5lNy3n+zbbX/v6///9oEDHWAQCKTUwtlrxGJ57DkPIy82UHnGyZ4EMnSuYU1GlLVg1lTqeTi7RhIh0zaEjlIFjlxETiO9z860Yl7Pzlx5vePbagADb4HX5ZJr1gItH3M3yFtE64Qzd+rszYGhclGtyDnP784ZykhkJKN+Wt/RSaLBsVXEjzZcECok4LG9Gm6dqHelytvRZdK6wAGa8AiJLdvj0Y13S+IT7X8g3JNdbbBlIMuQpbS3apb3DiU8QlZUk8CuETRSxTxWYuZQmlQxrA5UtW/uXLyw+YQaaelpN1b0xBA//pwBJH3AAgCGTLXKegVIECmSvU9JVoIaKVjJhhLQRwL7bRhlcQAgH2mSI14AOxVoGPp0dDzWvZGZ9LaUPwm8aKXFMg8KxXZfU8BxK1di112SX0RUfv3Rntr312x+CtYz+zToyOY1XfGAEF9rWanHEdItzUG5iAMP9oAMXXZldnV5jUc+0UYjRxe9BwAORZhl/RlvtqjfvmfmZvbfH5pszsNUf119H/2f64QEX/5ulOEAFjxO44+R89/B+PHZbslhg18/PDayuIfLF00LuiM+6PVkJdrp7iTwTa+E2b7N/VU5WItTSX0/endyEXqAYAiwBBjbUrr6Hq4U1g41/8Aco8mrjDeEhTyWvFzKkdqEuE4NJSeVas7QcSsmlQNAKdPA81fR+xxVzws2eZaZel9FlTb6JVMQU1FAP/6cgR2BgAIgh8mWEmLOlBAI0rRJedKCHhfY0WY6oETDW10MxSMAACAWr/5h2ExIKgZKauBm4vkss4/18wLHCxk1UDTeeHqZcqSP2KOZ0p3Siui3Ri1Bixi/VXHjSW5HIfaVRzdf6Gd394PshSAXaLEBZibkLReL4W0RnUl5WeCZ8a5mWtSBMMpng/Z2UfbsULLEClAI96L1DlYC1o0oKmv5DAu1npr+v/omAgpOS6EclQi0wbDQdINvoLa3tcTEYHM6vGr+MbVGA2wit4Ecxro9NyyGKMqPqf17mpQMpU48hFGMVEi0faR39Nj0bIyAQW4ndvIIgireDCkmA+saPssJdoRY3DPcU8g697UnIlAgWuQMHERQwMWHx6D0nkb6szdK5JtITJhZDBc46ZFZ742uSTEFNRQAAAA//pwBMcXAADCHUJb4EEXLEKjeqA9iJAIHF91RIxHMQ8L7Mz2FGwgACJkgQjdJmDuMO7gIkOGsCGK7qLRand+HfoX5fVsrDM7VdpZ1687RmADr/Mr/1f8t1Qa2C1r+gJkRtqTpEco0653Vs+D8I5ckjKBXFrWIBRcIeNR0lEnhcwYkgylbgA6unwXNrYFMw2Bgd99WH8n5MFjyQtW8eSdL4sA7D64tOJ3r+K7OkABeiSkiXLRkYbky1bHgTkZMS7LNUREH1o/BG8G2Zw6y9RIuZHaXqapmp+1a7uvRo2aA6FmnSazgth5KiFD6OL0klppytZ/p8TMyJputPnmRk/T62dXaVjpDo+7RbSEhfwiTDCJ+PelD5TLAJB1zie92pZLyy2kMCvsPKf1MQq69C2fq9KYgpqKZlxybv/6cgRgPAAIAhcX19NLKsBEKFsqZOJcCDhfX0ysqsERG6508wguAA4AAAOSBJcEhThlhoVFkyNz1nOdaCTZUk4TXX5hVG60A87q7PegPbCJqC2zf6ck17UJonCtqho50VmsSoo2/vXr1gASIAY5d+z9U5yiLW5QSWzX7xzCfmXygzYsgoJeb6BrwbdjLkyJYrpbo2/xlbVWsy//PtV/+5PVfUZPsZpK60eQ9TfowEEJK21TQyJFvvxaZc2aQQdq2uNW19xdHOVD/IvOoDUzE+LK6wsX95Jei6hsrkr+KPQxYBFHSMsNgLBGYOut0f+kAAisAIhlIuPBZA+fZefw8JJUF/eCztmRa6ivX69VCjVzejW2+99W7/mbsis8oSaukNmdr8NknNMPSZGDwFYa3SLybe9MQU1FAAAA//pwBOqOAACCH0Nb0eE5HD9DSzphigqIxRllTBxL0QeQbB2HqCABDoSSiiVBoEtpW57hSw4u562S+815V+43dNA7sJxn1fyl+b6NapT5x7UVj07ejP7eO/f7ereUbdn8//0goxxLTtgYPgARACkknISGDAurPKdY3J8MXMhzjdlLxkWgyb5G66ix2F5O9pC2WfRsRPbK27qnev5LEmDFYbw/Sbq7vKaP94ACyEFpNyEkIpPW/d2D8rf8ZsRtPDRlkE5BBx4XL88gMroM+oIX5DwR1woTWj26ev7+jf+d9FT1bwzci/X3Hw3Sz7YvVdvgEK3ZsQeVYtMNHTFGiXoPIsupfoVHiiOOQdE87li6nrKt1Fj1M7FS7tkRjhQ1/W9KnRGbl4sTyWQoO5/fQ/0f5rSmIKaigAAAAP/6cgRsnQAAAiVD2VU84ABCqIsdp5wACLEvcHiRABEYEG7fCiACAACAMyWb8CSTUCK4CmOcSelCEv7/6cKl6ixuCpLoFC93URXoYeL2830PTPX0Pb1+dr/t6v/5v26V56v1k40jQc/rf69QAAAEACTbdAAfRaODZtefWh4mMl/v/uCqedDpFjwVEptBATnnoJ3dEHwnILmlfVzH7eqef9/nfb1/f1+29F9U7S9CCC22223IBBKNH0SyMcGDBY9KXy3JutWUhXgt2MbBtkfEU39ptXtq/ZmmntZK2SmtJJ97pb06XR03stP8FM7foweUQ7//pAFHINkkkklg2BUF5sZGEvqyu9WyuwTdjOiA2wh8b38vnYhwYhNi5g68/GaErHGAfvHKsI2BYdeNV5G1tS1f+tTT+ulJZCkw//pwBAU1AAACJl3hHgSgAEILvHPAFACIuNd7vGOAAQWa73eKUAAAVCoWC0Wic5r///////+vYii5BA2i/xQQFDgiCjzdvpB0aeYjFZSq7p9vFN5xRTq5TjZQ8okWf/5D87qdyN1rUyspljQBEIhIJBIP//////2IouQQ6f4oIChwRBR5u38HRp5iMIspVcwl/4pvOKKdXKcbKHlEiz//IL87qdyN1YbGiSspljVE8+9HmgSiH6ryiunfmjob9M6yqLKNVt0Cwknbvvr0O3/5rTd3VHSn0NSbMbb6qd5BUcOmiLbW/U9vII/dYMJKeayoq56QnWfdJogSiHdLqJnPlFmTA5rOgLVqApdAkHTZu7T9E//Zpd3W6U+jJaRtvqpvFFRw6aI7W/vbyCP3WDCSnvyoq56YgpqKAP/6cgQmtwAAAhk13nhIFQBDRsttFCKyCFCHc6KM6uD/kO0kM5UQIQNhdZM/kimm7HJBGnaCEdRhKGVC7bd6OCqJUYztpTVunzGPuu3+/oVdn9DUe9YsXUoSu+36rS1J0NP8NoasSNs7NIACEMAjAAAAh0l1O64DM3FFvRqPGNvEgUhaJrARZrVPmMdLllGrX3utBKtY/oam/F2sWGtOX9MMlZ6VQZ8q8bFwjV1EBIedARskkJXO0whTCdf8clTg/NG8q16jb0DM7PZG/erSpJVrusjEFynsHRITcSRWKPqMolcWPM4i68JnX9o11VsiIQOCkFNQ4YjCGksDX7E6ko10jB3hY7qBm6N6/96s41vd5GQfU9g6JCbmlaxQ9UZKyuLHmahF10BM6/U0a6q2RTEFNRTMuOTcAAAA//pwBPI1AAAB7EBXAeoS4EKGuvk84lwJHQFvowDuIR6bLrBhFa6rLDXTO4Mk8TGA4C/NGm6FAaJxyj8TepKXtho06CEm3b9/VOjTf2+YV2p0fN4Mvvp/9hVK9bvLXpZ//WzgUACAIJAas137s+FJBntxoI+emsTFqkVEcjoVHGtUCvmAe5f762uoJqNpt7UH//m6Axv/ftxKitM8W9HRYp21ykxEG6SyNAQU5JI44Vs0moyfyaG/0pBbVrN5r9Br4+Q9/u+90aYTPtsr2N3WVJbKz7Jab4pAoiLbVsy2mo6bQLNRYOvr99j+GvlnTtpQJFaKC6Cu5V+4Tv8Wkc1BO+1HiB/I3QR4mCed/M9Lu6yPVXkLVVKzqhB72X6rr6jrMz2Sr/sQdWMNCcUXAKoOPasNes6NtpTEEP/6cAQd7AAIAh42VqnnEvA/qbuNDSInCDyvVgecS4EJGu28NZUMAIL9FZMfcMHeh7Iz12Nwjyg3uij41ikigRB3QfLruJD8Qr5jUe1K7Fo7s9/2XQh2cjLqun3hbbG2Ox3f0WOH/pdqIaQhMPGAS5I5JYtwa71o8YY3LD7V0zP0DtwQnyr7fs3M2rTpvdtvBFzI//3oWq+TtzpuCfp66UT2vUGXvj6Jh30KRrRFEuDkHXDOLLIBvDbLOCY2igqDhx0PULgj4GC5WqMawovqHdvfeOvMdWvbZiq+GPFKEdhFdSNLPXsXs0fps/qAQAQljICY42ZqXTRxk8XrKtj23hb3xN/P4wvoDfX2b3P0kOj9deqJzKv30oyC6xrh9r//n46HA127mEQKMXvYRtj0xBTUUzLjk3AAAAD/+nIEna0ACAIYNlbLChLwQWb73QzlN4icr2VGNOZBGKvtfDWU3AAIAAFaqkFVJmOEKnsldNqqgQHx2o7rIQspOVjdFG5ddSf1Dfub8z9EXKl939jdFP7enf4qS8JoxmvtRTQ7v3d/xalIojyNJNppKbS4JFbKCsjnh+kIikuIj/CLroDv4H7383k+jt3+bLUfffeqOj1XNUd6jmjtsinmP57SE0WoUCyHIUegMatmuusc6CZQfPXFVuP86ULUaUfxIfqJHlQmerq/lfm7b+v7zJg5WBGpvPExKgCLcTv5LbUh5OdMsJGoiJVHsKP35EAAACUUCZTEbuiQichR3xWfSrVQ22phAW1MBB66gN8Dffz/L9//XeQVHk/tv547Mm6f+8n/ppV1vI7NdPv0syWuMfc/NaEpiCmooP/6cARDAAAAgg4dWWhvOLBExXsaJaVMCFExd6CYpDEMFmtcx50AAAAqhADkdtoOMUMPmezj+VBFjsSD2WUWc8KegWIdBr6A8bp8qRiz4Lm4O5O0O+wWCthxJ1MchevV0kIpLP/frd+8AAYAFLZdkjcmIg+qRTtg0P2LHvUnMmjU3jU3UBfgre+sLZ81+87ZS/eswwe8ZUJRRyCKmadGe3YTcJ2LcbIN63cNJ/kAwUI6QWkkk5QVAOhsaptgHDkxb+nxIMTGB4f4B//bzP6epPt9P/WtmsfR/inT9v/mqjvNtGHjxW4k44Haie6sY+PqgACk1ImoypUCkngwG+ZDbDB/EczisGuSPhEN97gxNBEekqG//Kiz2fslEzPm82S741eCa2pYCn/xT3GiuzQYfn9+BkxBTUUAAAD/+nIEQe4ACAIZKVhR6xHwQwUrfAUnO4h8pVlHsadBCBTq5PYp0AAAwDLbt2xPvVAZTVEP9khf+hsP7fNNJbSGv/sOQ0Eg9Ydn6r5gpKzH6pXZPgnKHq8miHKwzUHbX4+tvTbWx/RX+zYQAFDEVqVuM8mF/0yPsz9IEfznVMg+M+PjHQbNWVAJJ1efdKEervn0c1aFG3lFT305BlNod+/1alY1SrioshPdTsd3pAAAQZI2NDfGjj/Ia3kZekAg7A5doBazY/YJO8UWOhDaUQcUTjThM8xIn1es+7ab9SD+v53E3yex/tjWRr9+T8p9P+wACIAAayVeP2d8KY2Bhp5+wAbHrU6vQTcw2/pSnUvIqEMGX8eihOSm+D3zPl/V7UT6+xG0ke3Kh7AMRZX7fs+cb+tMQU1FMy45N//6cAQ9DgAAAhkd2BsGUrBBJGwNGGUfiFiBbYwg4/EZK/D0g4pmAKbm/zC6TzNOZnLZYJpYams3LhI1AcIucq0uyyEP/BklRsW/QY/XyqZPZu2UnWKrLnsrRlo41S7I1GbXdjcs/xXZioBSMUabUkcu54DpWV42HgoLmjrtW7jfhNvE/if/ib+d/FkSuKD4vnKG7XsdU/dW6rEJ1SHuYtdUi4tYoWqJUH/FgASEogDM1vWMjY7SqSH7VeFDr1UQTzWy7leX8Kv408fCb0+aycg+IdGI1CgfRP19+dqzT2G8lkVFw1Ud3SmL5PJ/7gCyZfZHLLbfIh186V6N60E55oiRnqS8Tv4VLrxp8Rn6fFL/+3yfdOr+f1/FSLUlsIbZEfzt1KvNJytyMpv9G9X6ij1vq+QTEFNRQAD/+nIE8hIAAAHtHVjTDyhEQ2Oqw2oHSAj1UW+nmEWRHSswtFQV1gRFwIBSTkfgNihrlPM0x5s4KYdq4CMPqNerVFtIUGrxPxo70+M+K5DAu6tler6NOcqCe6Lj1K+Rz6Yx139QAKKlqCgudd15pewFxoEb3cFsqWoj9dPG0owJRqLrpAwZtKAFluVTxz44PlcE8hn85W+s7VoyGH92NzsJsqc/sxbdnAgYKmmyE5bsNAp2aH1xS3jhKZR/QNzf5H8t4JOQQK6ivVf/X/wRf/YnQvl+zVuRHaCcnMU3+tP+6eMnVtJfiqtjKGyj5f9EkGUzrrY3nJdwo4lo5j44FrgCtpCHd3AFCJ6MbqD+NP7/GFLdUP0/+3q3q/svttR/f6edNZie7ryN1YsvosPOvYrYgBaai+vamIKaiv/6cATtYgAAgg1WYehFEsxAKstZQOJPiK1nfbSRADEaF2uqsnAAFTq81jc1tl4JwQqGEXxQGPua+MBqvCfzep/BgXifOBjXdV7Hv29BK+R+C+e+jG7l8sv+v1+T7ev0+I9G8EAZr6NoAG6g0rYQQqSkJNImR/BIXumLjJqFGq6iOMeTficNeFJ43xb9X5TG8nglXq3gv16F+T2p/r9f/jerbU+N9vBOoDCp7rTajckw1tZ12M1TuLrO/fkICaR4X1Hbgx/Yfzeoj3P5Xfm9HT29fmV2k3LBovNT/Bu3I5+rc1+v/ovo3nZ+BN67SgwACGSbA2BiVexTtzgWQwqkliLM///VomuwqIGDhNgWkeooL7iEt5hnjZtlFk255P7+/3bqzcfN11fLI9u71fTiM1Roz39SYgpqKAD/+nIE1ZoAAAITL1qeMEAARGXrg8SIAIi0r3pckQABExXwZ4wgBChAZBQAAAlomoIn0dVlIoryiU4pigrdjODciLUm8qpSWse9rGslrz3uv+7POlbS3LSY45xtmv+kh/9yUBE3//+6kgUYy2ZDAZUhiaVnlh5Z64fdz3jt5XRDTg8nbrWae2ZCpa88xHV/vdtUrbR8245xt93XbpIKer/clARNlJX58Ve++jdSQpVWoCAAA4bEhpeKtMNR35qodb23IWZ53ZS3Wl+9jdrpeLrWj39H3c0SSFWLE0OgWnJ3UipO5PzzpKeTotUixJJzMqbc/cAAF38otBgAQTUu8llVP+aqHVjSZiFYx0u1hnLkfnlN29561o9/Tu5okkKsiaHUU5O6kKk7k9UOHZKeTotVsSSczKm79yYggP/6cATKCAAAge0P3IHmEMBC4fuVYMUaCQjZi6MURfEYmy1BhYi4VwGwTPZxjoGVZOR20IuIvCD1BHlX0rOJ2UtTyQKCxtmwVGA2HOcWmpS4DYQFqCbPjlPYEWAmnncNMvK7f1gEsGGhwS7+mBrgN3Tkdsthb+ENQoHix2lbk5GTUHbgoSBQWNs4qMDrOxaalLgOQFsmz45T2BFgJp7swyHSq3N6FyrgVAVUkzGkSVgCTrT0rkj7DAF73prDXwL/bCOisKZdPUqhamrFb+m+gpHIh07MyV8Lba2RRixzoHhEVedh0+BfOjjtYJxpvWut3DvBr7hU+jxxzpHwgBy3if4h8CkZQ0oGXL06tpTN5ralUtTK02/pvoKRyIdOzMlfC22tkUYsc6B5xbzsOnwL7Rx2sE/11piCmor/+nIE2N4ACAISQFmLKRJgQagcKSTiGYiAe2rMpaGhFxGszaaI+AHEDQHZa5F5xkNu01irwbKC/9E2bYp7GT3lQdBafsQz9aUbCk/y2l48qs3y/9X0T8v+K//LbTDu3VjnruX9f1/640nO+qumsYCJht1aswkIH0NwfDjbCtAr6wVef09rT95St+Xb++iH+X/Fbf8qlSmHbRkzwwkgc1HnQmd5J558rsOoJoAWJxi5kibMQONRHmrZWf/auonKRmJTrKIamzMtXXTq1HDPqfqVmURuFjtvNDWtsfGqxP/LIKnEtaXtvP55H/X2bKouAA2nIIoCEF+UxS7fviwdMrDQacz5OUarDCm8jJax1Prlq1sIL99G5v1CHlXv0FWIFi19lb/+UGetJgjaWhritlVsnwynWmIKaigAAP/6cARiugAAAgo14ukCK+xDRqtaYScGCETXi6KMXjEYpvN0M4neCjFdUabbkjpxtqcVHOyDL/Cbqg8g+ABej+nzgunjXr/d//tWg5KL7yqxaagZZRQvIG+SnhVUIOuZMfV6HVuwlIOAAIQAiSy0MEHNaZQsNUF6iQIwdqKuKUuJIvUwg/CR9OnPKdmyjXt/KHfSiXM9v29a+VPQjY3y7vUxaL0M6frR6h7+sSMJyMtOyuSjYtlZEzI/AvJxj7O+Uf7bZBvXhQir3dmcdVzaEb6vNA6GU6USWp/E1+B5+GvU3GiRKGJv0bAYduz/UNNJfrdJt9t7khyEnm43uF4q4meiE6oC9/07N/BDK/qb3Q6N+prpFuxwyMzppVTajaMSz1ZX2XhW/rSl0SnfBP3R9Mo5Xm0xBTUUAAD/+nIEgH0AAAIeHNvRgTiUQIa8PRQiuYjo13+hlEdxGBruKJKJKhAFwAJbbkdAy+7GoOzoindA9jkG3ED5E7ir59NFKPmPoXq6BgUHUj0pVSXmfITy1VOb/SuUO1hEO6bQ6lqDDlDtD5isyQk1MIqORysCj5Bccz5VbUDdVeb34S+5ZF4eIKdcPP/8uA9szsjEN60EmhxgFToKsoR/SPFmrOB32sO4KmFei0EIA1lkpttuAmM+vS6XxoS68KJDiQvB/Sg9ByNgn6Gw/bVW7p3sj5j+3rYjnnggNjniy15ckQ0NPi6jp4XpSyT0JTM91TZqsCAgAC25JRgJn7w7G0OQ8F9gzjFy6jQbiftR6IvBcG4J0PchnqW/T/0S2/0Yju+guE1KXbFSXpErmHa41H1Hu2pzFhVEOpTEEP/6cATsdwAAAewYWrjLEXBDiQtnJKJmiN0fiaGYTDEOEu7oI48OAYAFb9+FCEdcMvhlePKq/63O2fKjKKTM+Gf2qKpD1YXnLKN6hybG07l3jEsaoMvajV6GEUxBvAvtspDv1uAYAA3JI04/XyfBbeFVX+heFrhDKA4tzeg1Hoorr1f/52R6n0dO+rPv6Va/UJBd2pVE2q9a78zamUUUtYPF9FSHKkVJLMCedTSktt1kfiW+1H7oX0KJKO1FJwfS1WpJ06iv6NqvRvR9XtkqvWVA1mqZHLsM/NIm1kL6Li3K47tFRKEyfukW4a+s0xIkBAAFNpOA1HwSHD4FfR+l5moHbv0QvoeGOnKjKv1p6ptwRlnNUFHHXriISys5WXut/PrWUOrCYopcTDEMEFiDe7NSCYgpqKAAAAD/+nIE9dMAAAIKN1iZjSswRGkL6gziV4iU23tEBFcxD5uv9FSKTiA27dmFghVUE81kABbYRVgz6s25PlOVkG5JBYkwV8xB1T6gG340Udp6ltp6v9/V9KfZrt5Yjsi1vd1Ealt7TH+G7oHqQLbblgYNI5Gnqj8v1QvC41w43AG6atVF6+Ou3P01sf7+p/T6MTW9kBrtb59P6/TSog8QQ0YOKqDF5hCnCc5Qijx9mBtMFJtywLFDsaWjXtF+NT9NtR2gsEbcBN/zc65eHr+S9frm/JeN730JBj5sa0U0IT0PtUwoSBUJRCpdtDWHUOIC+ybRJBkabCrkcsj6pfDwDPqIc14RbtVGznSEOf6tVBXJwQokwPXNT5PstNfM2mymZkx4V3B+fYz/GSo1Vw4W30lN0X019KYgpqKAAP/6cAQ/EwAAghEr3NAmEERBxvsqMOJcCFEZeaEYTDERm63ocIrKAQEgFN23aGM0gvROcaCH43cG1GpFc/W9REGydPRc3Tp8352ZIAYLD2LF6Vl4Rxvr1vgF7xrGJi4L0OqvUVi74lVJAAYgADJv+Xz9Z8sWqFLsgdHy/LcxHiIHqKDz9Q18XROMVi+bo3ff0L7t7v5Pt1Vv1bG3hijUyvKrmpmkVb/072/SCGAISQEm25U1ICEQWKBP4gF5V0ahRHEfJjVEH6eM3/dB25V+/23UM3Uqe9KBtPbU+uX0P4bTasiKnj3JKcLEctpyjoEFOWbDqFrpQIniUVfEQWc+8TY8r8MfU1NA5rQZX+ni/b6v4oRegJupPvWQMLPSS8ppz2GDlgsETyn0FsuLNLUJrKZdMQU1FMy45Nz/+nIED+YAAAIhWF7QQSusQgjMPQyiX4jRX31BHLoxF6vuqGEVNhECURMbbmQpUY7T4kF4/I1JSS0AId56Lnb3woOmH1mEHbQT9E8r9Wb1b6f6R/2f/Q3yfdMlqat6/9bdjihAsg3K2F6VORgvJttySXeQSp976q+JAH0VJmxHoLNpeNbgvToNf69V+n39Rvdvb6U7/L1XTN8J7C2QYeSMOGHbD8VikAb62y+LeQGyUo5Jcc5WdUOXMC8A6y3ajew2stBWEN/KB6l5mn3fuvyPyq/KCV1emraRlHzl/2jvRvGOtnf5XfKT1ZrkTWwiyihO3J1oMCQGk3L0hsoZOCPMg+hutqtUKI5xvXfI/Xwxx5ATR+pn9f/l9R32yZFqqU5/+zDvZtlI+ssvKlVt6yaDPKIsghJXs2E0wP/6cAQJLQAAAiBg2hkqEtRDyMszJOJmh81hd0GUR/EQLXA0kJQeICakuOmZzUg1Aa2xSDA04t8xR2RjkopjZMKNZNtS9OiK090+CfRk/+b7ff/7dSev0b0bu+joT5v/trT5HXi/V21jVhsAJKW0oI0768LA3V2Uf8nxQhOblCoMbBtrJj507cGdSIsyL5vk/9Tfb9etKu7N27K25w1HZoncoJ1gElvksMWJz/3Vv1haAlNJx0KAyCYxDc4SDNQA7rq1H8LHavofEm5zaB0KjVQ3m+n/xPp/6/g69dG0WcF8i3rX3+X//+2C53dKRpBGOIlOSW8DkTbNtYinUBOJoJX0cd2/3y+bqPtfp0JexUpy/Hej+reteWta/9SOvOXLOVeavHff1b0a1wg/cV0ZxsmmIKaimZccm4D/+nAEASIAAAINOF3tIOAMQ6hr/aQcAYh5k3A4YQABDDJuRxJQAgSiA4AAW23JtDHw6VWMgd0QDqXHbV0d+V+u+X/1Dym2c7Tx84hzF/85Ogoe2O+pltSXf6tejP3gTPTITUo5ur+nQkoAa02U5JbbWJggezfAytMBvHLVeYJRtXCf771Obr5dPp56meVvs/jhDsUfzfp0PT5/TvnV5VJYhnzlHpCbpjdX9OVABJcCJcMDGL4OoAhLB9y+otGJMLZQslCPOpaInrpIQh1//cAIST//IBgbuEEf//gZ3bkb///z3Jc53J/////YDP74twASchjUoFbTITAZZp9T1Cmi4sQZc1iu2j3ta3r7EIdf/4oRpP/8gfF7kJ//+BzuTkb///zu4ornO5E/////YPn98XdMQU1FAAAA//pyBF6SAAACI1Zk1hSgBEUKzKrClACImTOHvLEAIRCmcb+OIAQBADSDBgQBgQCAQBCTBMpRyVk+UWTq39FMX/Kh2b/ylGCZ0//LCRiGL//iTi5USNL//+LO0aHlFVRP///MhDqLIqj0QaAABhBgwIAwKBQKBJMEy2Tk+UW+z/1M3+yKzf+VUE3T/9YkZjF//w8PFyoMMJl///Bhdo0BlFRqDP///MQhxoGMdR4xAmAK0rrra20nFRehEyoht3zX9rqjaXv0+ly7uTpp/u91uhqo715DPYrzI/N27IlK5/R/ov7dN9qkW4cghM+OP8RUNyyLjAAJISNCv/7ZJMoXSqoh7VVu65+mdm99VehU2JlM9P93ut0M7o715He1+/fdkyolK5/R/ov7dN9q1waMT6j/BqgllkXGEwD/+nAEQGAAAAIhNd7pgRGYPQbMbRgiFYjQ12SssKMBGaBxtGCIjgAqFLlKmSAVCMbrcgfoeg5eM7SOfyq+ggnU6Vrbu/p0MwgykNl3T29ROl26lb+Ak6Ovt96nkBLlFuvesJFjsSnlE3/IhptTSKRtIkp1DwVCHWQGWjl7Lpr6CRWoMVR9qZn/6M2Q2XdPbwYnS7dSt/AVfGdut/SsipbtyPErD2Ev9QCCHGHD3DXiy80DTI1t7F8TX5jGNxplRphpOGt4vR7CQjqKdG70R6slH3+32lmoZ6l16oLN04VdkSMjlr3Vsb7Q193LKz1oTjM0kbkcTcKHlpvDidQb3QncG223hn6r6OupPtnperJR9/t5SyuY6GeUB19BX/5lLosE4is0jnuKj2E08NhpnufqUuHWk0xBTUUA//pyBLeZAACCCTXbUwkQUEMICyJpYi4IpNdrTCThQRsa7FmmHCACAQCC7LbQp4PUotYjSTrbLFX+j/auDVtS/NvS/PwTPvp1KnZ2rIfqUyJ16qsj0oLO5f1JPsaepzJnO+5Xqd99FYDf+YScbMBEIahVO6RD8WB/meDv7Vb1VtMy+N4MNRaIn+M+n/t9c6OVnOhtLZVWT4/9LX9kuHN05oTL2lf/kLOe3LaAAYBBct2wN4nHyxtC3hf8Kxf5T2RpXNduZ9t5R/blGM/6mMy/tRZ80pnLRXm+3hpblrzGoMwoM1XlkzaKb1KXYMdu00vQh3XmLjDRNO0wkeXDhNhSJxnMF+yFXmkVCpqcl6jDRmh69eUK7OzquzWtXrKHO5p/5ypOT+pK6W/M/U9lMeQ7aupyuSM9DUxBTUX/+nAENdcACAIdNdiR5xNQQugLGUGFCgiAYWDMPWEBBZsw9HCLVgG/8B7AEkGKrCyjWUm9jCVib1cneeUITXlBc3A99VEvegGn9KbaPqRWRGLvRnqqHa788im/Ubjpcw3Dv9Q/Tr/iB/9gAAFANf4EcAcNoYWudQbaExVQZv6hECRpYCCwJqEx/QP1NQf6ePTfQ27zls379eqUt07ak0rv01Z9hJ/tFf3enwz+9CD1+5JAuKxIFmcMHXjhaRcP02qSvzihboXaA5+pMRrfsehcfBQtuW8YdS5QDFxUks4DEeS9vuvs3oo/JJVsu3a3ZH7BJCVKlG5I3LKmkyR7Muar8vsZb0+JAbXcyrZQzq3kqyXLea+DIbV3qBt141xO06v/1oDKGCFYSfWiXEFRF5LLXTKYgpqKAAAA//pyBPaWAACCGjZb0ScR+EQFe0os4qIIiQFpRJyqAQ+b7OiRFSACAgEi5JNUAmfCZ1Dy2Bwq8a8+9NBnwoMrTZGoHf+Dfvs3d3366nepSv2r6s/oD2UWLfHv9bUqEphIQMrmBSJUZv66gAD0LDl22GwAYsqm8B88enr9tLNt8G/FaR9fiAvo4jNNqHP/n5tG6o3p89cO3qKhnNxItRVbJxKUq1tSe4Wb+7S+7UaAAIQguzfcRKiZdamsJtwZCjcAuRSbTgy8ID/PRqDE68a5H/5X00bRid0N/5znt0OtGd2m3ZC1ZhtaxlCVqUiM/5Yz8XIAFOTX8FjI61MqrxXKJYfOK5VFbVBycK6RrdQ4O79QY4+Wtum3tbJSaYv/Kc+iqJLiQ1ddC/7WEjUMGvetj3Sp56vySYgpqKD/+nAEW68ADAIRKVkZhxNAQ8UrGjEFSgiM32BnnEuBGZvtKJMVUgA5Nti+JJkwp/JFPaftKJwl5OLZVohObiB+uCahm7dXkV+bmv6/FJYK+4Nqah7A811wetXZdKqCS2uKOr9/O6cV9QAAYICS22jWckT7Vj8sxYC7X6BWXxaZxo7ljWwMyarth43XjRBSVmQzda5RvqBhsSoqiUslSzs1O988dodW3tqr/2/WAE5dtMYbC4P8v7EAh8sBwZ0CbkOZgaZ4WLarlNArczeZik5+pPr8LXQVvUulnN1l4MxxI4ckiyyZGjXO0E0eaGYr+3qEQnBAJuOVlDUcEFaNbwwvf6Dxtr6tUd4KKe3xNf6ipGm7+0irK+8Rq5iG0/cl6VdrxowqzR/qpS5yWOf6nwTCO6KoNkZkYmII//pyBFxzAAACDSlbUOEdpEIjCuo9B1gI9N11QxxO8R8b7igiiQYAAgUQo5LUMaXHGYR0YPKtuEvdX2mkG5QYMncnt8+SusnvSpnmzX6KyW+sP7AyePb7Xd/StACFLXvT81gGh1S66mgAJACCk7bGgnjDfm8hlzDxUh5J0cGLW8VhO+QBAzQDi3iVl6CDuWBrb4BTVlqz/tbseplOqKJj+oKU7XGXeK9DP6KQLSaKTTdKTJzC86H5E/P2Hdl1zyzNUbhyUOd/YnToxN9Plfr+H2Dl5pS5Fb+fBnLcF3IBh92TJljjaU3x+o0osS81MDFhwaBBUJJSaTigDtDjRGCh32fct6vQQfihvB70KnXxCfp001VuQ7ArrcllbKcSXpUlAZQkhm3bJbB8RA2A3g6SM2tT2BUxvk6qBoD/+nAEpPUAAAITLFlR6xJUQkbbNyTifIiZWXWhHEwxDast6FGLwgACAABTckgM0WtSpvo6o2BEj8MGj95i/RXkg1q4VuoraCYvXozt9/Z/b1K7uUVGx8eLdjWbpzCW84o20SGert2dHWAgANuSQqybiyCrUCLeBpPf5GOhVKvHyIfyJbqd6V68oBVC6MZMKPolby9A7eZ+idzsmrraDFddGTJTmZXM+1Nen3AggOMokstOVJsQOcLgIDygT9FeEHjN4d+DarUR+TmKSo/X//ubqBN19/3fgz/9E86/+5V7O9EJWhmNqb+lwYlG9qbHwEIAwACjsu1x7uID4XgCNfQD9VbeQqc/r86CXbjAd0vEm+3/vy8FoZyzu5ffwbf9G89elO6+3q1EO73qGe3Rng4/k9m5MQU1FAAA//pyBE4MAAACDlDf6GUUfERKGvM9QlwIfN1zoySsMRAv7ihjiT4sMCzFRKuSXRHF4IQc9gJFzHWF9Mzc4QHrjRSrVL78a5HUjKOO+n09TNyg9f6f16jr0p39X/fs/q3ttVuo2IHRT6QBJLf0m0rbO2C7OqG5XIRoSRTqPuPI33hkOAk2RtaLxCbQYwXnbBBillxvR7afRfHf//XrBjt0+/q/r///TX42QdT/6iQQJWQSUW26waM5NHBf6KA2dGRF3QXnarej9QWrRo5O3V327e/r9jdQ1ftzez+IAxFgh3YidTSbWp1Gj5rJ55ij9WUQIAwxJRTbsjorLnr8HKl9T+6F/JlB1sd+Eu1W8vRX9WT3/8vqK+7+XuqMkGjUy0/0vdj/fzN117eb7t3PJe7eeS4JqDKYgpqKAAD/+nAEHTAAAAIdNFedPOAAQsOq06eoAAiJh2zYkQABFjEtSxhQAAADtv4ajjwXAJK50H49fi+gxP817HBo8DWiKOD7zBIfVybq0qWGOd7t9/v/8q2wwTXVGTN9Sp+ROVAKIP3IOZ3Z/F9IIAcuyInZMM4usr8tsFnADSGJu929iogiXFJ4zaC6FMG59B663KE9D8cHiaeXN06fm5QN2s216WRbEGj5zO7KtGNywEAAkQYYZf7AxEHAaC4nmIxBnN8e9aOYhHcM5QY46JKywTpTzt1+3/3v//9sv//+pkbU////7oEHZXVyf//yN/9YtSdQAYYY47232Bo+DU6KapN8P0VwM6jDkgIPi+As3ES4x923VqRv//zX///SWn//6mTqf//v/mQOD2K6nJ///b/3dWFxoi+KpiCA//pyBBAIAAACJxdgbwRgAESi7C3giAAIpJF8JiBpQRQSLwAWIBBQAAJIAEgpykNgIjINs3Nnv2KabmjsnKX/C0FnHwfQTy78TqcwTxO9RzXEawsA0S4WXrD5Dz/Lgglb/3dZ8swves+uPAsZKKYAQKKcuMDIEZKxXGx8516uxzM9SfaQWzJlErgd9QnFnME8TvUc1y6wsA8uFl7Q+GPD3WCBe/b3dZ8sES7HrPrjwZA/JKYVtHsC5bkI9JrUNFhyBOpqDXduAJEWTmzFozMdb5/2GxHSI8dGeakNHZ9q5hiBhUetijxZ+j7o6hNel/fQBqOdq1WNYD2lKEqB3xjrUkwxTWSACOtENPE7QxK1XUw11wUUdci1/fdrFxTErWw8lhVrwVOuPtAcAsKuQtco8s/R/Vp/78DfO1D/+nAEl1MABPIYFV4pLROQQsK7oD2CHgigSXanjacBFIyugPSM8AMCfdAeCk0Kqtlr2e5WzXTj+FMljmRroafUFOIDQPCoTmFtYBjiwGORP1DjIV64cdesnATg4tLjLnyWxG65aK9O6yjMhmixRH5VgjVOSj7urnXe3RZHkCSEmtB0pEA0ES4OC4shxdUOUB8UcB1xEQAyWIhss6Xi+HEuOmkBxNerrmrdu++oIEcSpEjpZ1/KOONMtV26JqKbIVG1HnVQ8dCABKkAiUAWDJmsIm2ohh9g0BHnoY/uvpe7em90xFUZcw1VovTSl4xW3a2v2LNQxWlfunNDBbx8D1SXw4GYfe+iXpcPNCpMi5YzHgLBky9YRNnbXHrFljwXQc1OdfS91Dy97picQ6Xc1VraSpZOrJ8mmIKa//pyBBBLAAACFgxeyGFjgEJoC6U8Ip4H8GFwIzxwARuIMPQhlCxAi4VG79jgdXCi2qUFmTrojgbuZdW9Bixwx7daxOuQa9MYFEBYPjTx6WNNNpFIe6aQ2DJ3aPe/1PUptCCTnOV3yKpkBAP5ypYks0GPAq7lxreZG//2uNvetxrGYiqRsoJE5h8zoxzyhfLV6bbWQuxOabiLtV9+6ugVuTNL2Jqq6LervpVOjv5sxSAgejyLRP7lY8sFCVUvLTvKs45SzJnBCYcwFSQBeBmqekDGHLe+fDqK001UlabDNhF2xj06m+h1NOv/Rii5aDZJZHJoek5Q1xERfA3qBPSPrCVBd5YLE0PJIpCgeVESnOYOHgoPNu00j1UlaWTKBUROtkRzkwM3yBhSVFr1ltkynOkUxBTUUzLjk3D/+nAEwV0ABAIGHFmBLzFgQKZLMD0Clgh4YWzDLEWhHJHt6LCKQLguJVgQEGbmLZj724X1RYtiPogRaZ/yFK7aOaqfLjO1bIV/Lt1A6ZUDLWnk2MQ9hZABDwKETxY85BtLlf///f+iuXqsetA0kFbCJlprPa1B6riU7AJ2NipyTJSAeb6lDK+jXIqk+FK16XZF3VW1nlMiPRNW6O+CPb0jKPr/Z//6PRgCszy5Y4kiDxkrJI02eod5ZaQx5qqDdWfy3uwMxFU44tTSqAg2QtDoGeLFhYQoVPKTZHnsSrQWa2iGkRvZ9EM99NT1QCYgFG5LbqsmlhYyWk5TZHBC3nzQQhVcwWuDuZt1qLDEKxDG0d0MtL/BH6DWxLA0NLBx+4m8NV1NS9Z1iEams0OTo9Mo9XoTEFNRQAAA//pwBDJqAAAB/iReyOEczEADKwE/CIAIqGNrQTCjwR0WbdxnigLAlVVqldUsYrljkFsquhPOoHTOS6vgURExkQy5PL8ldBRHR4iHmDYoIMUMFV0dOswpHtsW1V3pdprU9NTTEnUoHvldNxcooyis9CGIuPFxleOOmWFXpTBFZBYsejkc4DLcZU0tWNO1LYLGb4x+1LnkWboq8VUtcwtPRVR+jX/qpwAAAAmWW2zDrHg7QI7D/dKvmbjedS+hC3GiXROH7pkPpjAyTv71oIPIDwLGOAdpdhFzUSu9blPCJeyta3j1s6NXH8z/nkIEhJtuNLFiE9DJsFxcfuO7PjcU+5JbkbFM4Lkd74T9W0ulO8ztvM1UJzILF0nbhfQ4gwyZnYHjqu2zejvZnSxIyPf1HZVSExBTUUAAAP/6cgR5UgAEAhIY2hlvGPBEBJs3PGeUCIxjaOexCQEVEq60ExRkAMkkg7UDRmQoZMHUqepWN9L7N9JizUgZoZSsKLoBnOUV9NEA5KEBRh0QgqPPEBGckjKx6B9QowK6l0OZS3RTrV17gIAFJuWyY8kVNotxrBT0fUD+Vk+myQ7xuXuoatE6ucPxa8mGhHuqUdH1m/y+sE5djgkm+wfamKFu/hLFVWZxaPezSToCASblt+mo/4SPJiefsGLcB13pR8zj9kHDlEzarZ1cCDG1RZnzUuESBg8NO2JeBAmls6o11ebr35U1Cs07O73Rfs+xX1sgIdACN22zeCFNIvDMGylCGC7qUI18e2OM6PYEdlZYQyrr223349AqVcoVsatiFKDYFKk1qIrtqq/b2kVlLQstNjXN+xCYgpqK//pwBFSmAAACES9eSKgUPECm64oFZSyIyFVqYLIB4Q2SbugziWaABAqe6usYVWDaHRDtjtpQE67Zu6XBcydWxfSrVv1Zc1S6kfZHvppKZ6uuEoipE2NGoJ0PdVF3ZqdSipF69Kt/7YpAAoATTckhhNmzHmCe6lfg/6LkAapj6m5ksbDWVR0oWRZnZW//Z3TeZ2JVUq3p916XIzCKDaj5Crz2shQr1EvuqILbUuuSanPA0NtuWn4Gf9CZmybToLzHKOzLWVZ7z6FmHPiE4WdOEGEDla2mCCBsQRymOz5PlCajjnOc76UPe5/NvtQ79RrQBQQS0inLCGClTKtavppQMMnixsLb1WvO++EZa1c3lRWEE1hrt1WJFLVrpfuOikQdHTOhlD3Hw0GXDmpYVElbnz2lSYgpqKAAAP/6cgSBogAMAhsY2ZnhLKBEROu6DEN8iDhlZmeMsgEPjGzc8RpQBLkv/pmVBMyGDYpEgoBwhPLahH3rDfAlBFV6EQDXNZBFRpIGV2iHck+gu9oprS5SnSMwcK1/WiJQ6gqtt6/vZ8ptrRgAwISllu5LR3GzbEpwygfdgQmjVRunbSy0hNeH1fbq7WhQPNyFNNDFKCUQY5Qlnghqbijrrm5/EVSh8gh6VIdEvwFpSAXLf/jFSkVEUzkpGhN7vMLW4aW165iBZfJLGFtur6QTsxgPVLPHcWWee2sDxVYMyFNqnEZL4sNvekOIpXUp+vp9EAAC5d//ap4NThJhvwj4+Gr7ytfTXRnEu57GWgpsOWja2y8g8hYmricRWOSWkzjlVRcZanpScgikg+C15KOvZsU9PTuTEFNRQAAA//pwBJ1tAAACHCTZmY0rlEKny3oNpXKItQ97Q4RcsPYfLqhwjn4AJtySesT2SuVxszo/Oa33IbFUnBgGGWvQ4a2KR6bh/Rqh78Q0/o20EAzQ4MZQ16iJU7Dr1qW6mSIIaRaklSr0/8ph6ABAAU1JbbwDG5hKESQbojZyKE4nWJo210a69HgI+2hVu//u3v6K1bSUbqyO972Tu+1d0EhrTm4WQe3F3vZwNVOU9GAHTAmOOTKdMdyrJag67qR7Fw7dJ5fvdNSWllLflD5qGK9lV0Rx9LidoK9Uqll4dTll4cpnY3xkBV77lrqV9uEXTeJotkMAFBAKTTdUvHHPu83Ks6C3qOAtq2et2WYu8813//IYeYPeQz1NfLH1XXy7KDBacS+jQ6SVf0b0pFnVE17KkxBTUUzLjk3AAP/6cgTcIwAAAiAVWzkDKrRCwqrzPSWSCEz7d0KEfnD6Hy5oM4mSgaCm5Lt1AP0UGXLgmomU9w8UB4iqF22TbDIdAjtAdY2eTLn5CBDaQS2l7otqQm69DBI/citJ0sF7WsEpA1bdQmvKRaoAJS7bzLgjaHJaSMnZiveQk7PFXjrzZtXBAgztKQ+r5i7v7UaTgDIz5hxQ9oelYjfW/9sfkr2KO4eUVfMbkW9K7f+iABgSU23JbxhUK5L03QU0oTZKs2HzNayrvQM149aNT7qruGvsLv1hM/Tv17Dc1nyQigpTAJoJAWLucrDMopTKnf/IYAACEo7dvlgQzTSpxDqLtDxMCNKVJ1TfDXThPx1mahPf39/bqVqqX19e13/+ihnRatxFHlH2WLs7BBjC1xByYgpqKZlxybgAAAAA//pwBNVLAAACH0NavSTgCEPn25einAGIQTV2GGKAARQmrwcQcAACDJst//qAZNuIDb9YzXfKdUuYIgIsVqqlkngsHiK0MdaXODf5prmzXatXpc5+d5ujGed7P16X2/N8avzX5U9+V3V/0QIATTTlkip4xzoKrTCwJqw8TdfLcxProbOEN25t7bdvOOfV+y+nobWvm/fr+t46fJPKhvxKDZYPkgRNST3nBVUcz1ellufJicw0m59coIUeURDQgO31L0/yK/P5Ti5n/k8TlIYb/q/iAiEDjxgj/yf5xVyFdWN/9//M73D48A//9IVnxZBQEknH7yc7MZJeNmSgADLEwJD7pd5Bi/mdlTfr6yZ3/8q46OHFf/8cHRMeXKDv/b/PIuxrqx3///nO9xuPAP//SFXnxZBRMQU1FP/6cgSpGAAAAh0S4m8UoAhDRBvZ5ZQACGkxdyQEV4EOJi/0gIr04jlh210aRJVLRBGOR3VEwfGOJAjTLSroMFEQFgRFVoBYwtegTMGsE5DwuPMRHI1ZQbXPLMFiyOj3aZD7mqhlN+Im2VYBJS6qq8a1IRU0y9lbhOAxjKUTq7ZuRsiob1yvbluiMyiTBrBOQ8LjzCxHI1LlBtc8swWLGG6Ol2WkPuaqGU34ibZVgKAXwAtpB9n1RmnOPgMgu6fGC/NczEXniZiBYSZlvLn/rE9Gq/5jUP9OV8/zLVsvCT/Pxd8rO4YDa+HV1j8PLrCdN1BBYGUrZIAC65t6pPj78NEzIyBG5TnYp6FwQasycqFlX/rELRow6r41D/TMrLP1MnDcvCT/Pxd8rO4YDa+HV4/JaxtKYgpqKAAA//pwBOqMAAACEjXeyGcTKEPmu/0UIscIxTN9QZRL4RUmb/RQi1QAMES+Vf6IQK5N2XSVBB2ZXD6t24Vqtv9FKxW6+YxXxK+j7ZfmeqfRH+kMn2LI7E1vPCJrRfDf1AYS5IiWFtTWJJJwAKQrj0bSSlVIQadjarFMHIacwpUd2YfCx0o6UZQ/JIK/yaeH5PqH4nMvkT/SGT01kdmt7BRrRfM/UBhLkiJZvaxJJIGaIu7Wu7fXZA/SZ7rQFeaQNq1bPibxuZr7Q3VmmH9arKTmRHr38Glyvs1O/qJ/r9SrS4/2f07WPc0a2ZGRJuxQGvMZ0kQ4ACQsJv2zuyKqmIJ10WH8BAekQVkbyPGttQ+rIYLqVkkfMPmmiEAnMn+PYW6139RP/+2lx//9dlu8a2ZVJPu2PfqdtJEEwP/6cgRamwAAAgk121ErErBByAv5DOJViPEBg6OFGrEdIDE0FImWwAAAFuSSSPKiIGTjLymOhD0sQhB7kcqGWqzKYQd4TLvuqooAtlaj/u+hHt63aXSzb/tl8GI6Iu/u2fTv/8Wd83XgMmzTzXvYMGpoRvjmEYkbHF7JdCDwbedrq+CXtv/8j1e8qP2aUv/9vUb/Zt9XbEJeW0iIduNtduuVrISKGQ8aY1dIZSCSSkbbUYhV1YkfPdF5dqIKDq6nIpeP269aGBKZP1A5Z5SJe5GPiwEeaqfja/lQI/PDyMj5evX0KGXOdQ1nd+yuEdPZfbyC0RWzHZG3Qgm2FipZ/qEtKplQx/q5L146ruprL/JRVRymur6X1q+z4N6k305lVqCffLf0bwzvnivqduqZlRC88igwBmugsmII//pwBOcXAAACCTXamSsSsEDpy2oxolYI0NdzQyxMkQ+a7VyXiVgJSS236cBpAu1yVlpyjKoQwoWp+NxG/UwYIPDn9X0ag/5G+7ErZrbMtyOvVWr0a1v0H1tdIW2/L600I/5R92p1NWAAQAOSXa9eScejTAgl2mtVjuoAZZpeVD19TI48l779Mz6POtPifqr9U66zPoy/99H/t1fOXdWX/6oT/qN17LoZAQSANNtyX8oNBVZXVrGXdCViKFSZffQI9ng2hXsiqTemYautm7tg+Xb7K/b7f4/XSG0PTletdg29Gm1THvKHy50fARKkmRAIDK7bb6UEpYEAGNKW0n5ENo3jKZcZsvPt9jLDOPTV5fKxQSvmpz1z1+vt1pI+Rm5VV1dNYp4y5aXXp/tN88322aPzCYgpqKAAAP/6cgRq9QAAQiIk3lBlFAxCw5szAesMCDC7gaCYo7EJEm0o8ZXQwQDURNJJPsMOZGD3LswShx7WBd20CYJ4NAbHDMfUX0wwnX3V03g7N6i2d0OGrFhzShVwTYaijsjx7rJcg6nXxG/fW2REcckkhHkYY0CAWZ3NYbOxli22okqax5BBzUOMVao68w9M97uIWk4SfmK4OmS6DcUQNNnjNLQ4HlfdKdm/Z/J//t1vALJIiRckjbnRkzUXWvk/h0Dg2o260o01PdNFOEFlXM1Sd+U/EQ4zXYjyMdhJkWPzcmn5GQa4X+2p9QTpf3aX2/koAA25Nds46aJgry+TNrk/v3p45SAJQDPGMPwrSQZhTIfOeyEdQG1Tv9b9dhrI6i4O30yaohMsReKYvJbNbNaDP++1MQU1FMy45NwA//pwBEQYAACCEyXZGYkrsEPjG1oF6QwIWJWBQCChsQySrVyWiZIFR2W2akMcwnUZU/LQwPYWA3wNQec5bBgIZR+Hhal+2LV8iUSOAui7ZS/7PoK1NkQK97Ras6tsb+b7PvPHW5Nv0p0AABAJLNd/XVZvlmxLDY5vqdqTem4DmcfF4fU37XzqZzmXO72qyoU4hgs8NxZTUKztsvNNhKmt1PVv69no9jbKZLX+2kFUa23HJJZgxo6h79OgResYGMgaVbhINiJOtdDUf9eZe3N49/WtK9YwOjlpPBp6DSShCKQ5RH7J1Arf7hr6yW6pdeogSSckn2wwGFBOLSoWnejk6G4St1sT6x10CYJr5GR0ZjASvO/0Tp08frGrDNT5h49Z08FYo8cOOB2498hDJH8jRdknpiCmooAAAP/6cgSiIwAAghMzWRnmO6BAR+uJJCWZiQiXiaCY6PEOJu8ocJZmCL12w/opyTSvoimSqiZcx0jDIwSUetXKI4BB6fpDwzSic623peO/5u5RDUHwlL2Ygj069kObVNi1Yf5KXa8Kf1p9uACIC1te50YaA467mG8tdH7DqmUlTWd8gO5C/0c/oX9aXuUAX2bmV66UOurbN1av7JnXixhCaT3EqkVPGfI+71gllE1tSW23fJhtHVveuw8tIVZ4zuq0fWjbFtVY8v01bt/OdLAczxHuYapreMMxI0xPOY2hA90wyt4JJcsIs74XayYd1LQxh5CbEpJtyz6CK4pzTnnpQGCug6gw1hNkQKYr12mH9L/6PoFf6bPvvxt6KllPmSY1K/pzt29q69ivIYa2RfPhjQzpFStFKYgpqKAA//pwBN79AAwCGCVaGeM8hEJGa5olYlSIhLlaTCSyQQwgbEz0idgFJNy39ecDkfLnVUsxxfo8aJMXKuoel8tdZHSvHLRO83MfuxYJM0zVtfbo/Fe+2YVPU0/oXqcXOyztc07U9y+ycmnIwASEJNy3fFOmDwOlX226My/gN0l1YPrRXMZt97i9XYxve66/6PoH//R+j/pHsPdG1zrf6Dhsuu7qP3KIOIKL6npmAK7DmLMpEELVVY3PzRQJC51rrYBodpJ1CGknakvpSJX2A6K1TZRGEbIRchGvUiteQO393/txj6gI/fQuX4x99NPn/2UgBKb8fOzmCHl5Os19lMhau0kqdvLghzvD5O9PG05pETPw/NnqndlLf0N/9PEe//0R+fqOtUWi+fzpVG0BZ7uSrb+1KYgpqKAAAP/6cgShRAAAAgwl1pMPK7BARKvaBSIfiLEBg6GkTPEcHO/0NAmuEn8OdeNQhGlvYbc9kF5Ph+Y/DyXm30G6Q7xnhdCmqvRljLuz9o52NLxReNHVV2Eg38g/U3N18dkY6KvvSmrd/6KsAa5LUbcmihQHK2vUe1tJgki6WRLb7LYNx61idXob8xc8ubn6Db7+nPvYwoxecqUZDZAMKOU652g79/raMQOeaiBBKjilkst+oOtIcJfq84vcldGjHrDdb0aGujyF1KZXsq0R9P9uEE83Xo3VOR31bO3T29ObUGdKH+txR720/tlYSSOOxWEAEmJKySSXEWKHZDhLOlwFRHkuoN5u58Z+dKwoetdLIUttcfb+g2wIT/Vu/VH0eiK1Q7LgugnyMLxV3pBKPPHD11DQD3aKkxBTUUAA//pwBPONAAACCS3cuOEUnEGIWxM8wngIQQN3VGOAMRwhbuqQUAYA4SbabuJ0FpMcyxfkpwNNow+CLUDiWiQ1BuyqLT3R51SZH0dsVrfrfR8GMWHZGjdJUtLhNZiI3J1z3V+mrCaz1Isrd/Htg1Q5Q2yghR0Att+JDz2vAnwy50ohgGkqcplfEoM2KbFcRYBMzXOUCVRaNJ/nJ1//X/Ty+3t7fqTgnp9/VAAOpbabkvZ7SNrPLdtxxYMXIGnlSzW6j/VT3jq3pHQcGGdk1T2081v7J/W+3TsSvtzF5x1DWqpBYWq8xylXt9fZTACHxTbikvOeLGnVJsJ2nIYAM5ooAIuE2LZBNt2R6r2PAYjVdnaqfyLqAI3m7dWTKWyii6e//+9iDc453j0NoRSsx0fu/O9KYgpqKAAAAP/6cATqggAAAgRI4BYgoARECRvyxBQACNyDlbwxgDEMhvK3kDAGAIIIII/OhXq4Be1rSSjWjP5Gf/2/+jp/0IKIHEZl/4oHw+cUUXyq//w4KN5GT9E/+9nQgHcuHt6/9IYhgHy/rpAEMMMMlMZBt1bAvO5QksDXQUYCKux3Rv29Df/VFT/IyEMzL/xQPh84op7FV//ihG9GT9E/++6ED7rD29f/OQwHy+uukEppK2uRpElcEszHZIzNwWr1VCUnGbt/aKVKdzszUMx88z8zx3EzpU1VnxEbQHVCWp8nqHHmE6TF9HLOvm2s23N2Frn3MkVglNJuWSNIkrg1ma1iIZmgkMCRSusIxg66WBkQuNJbBYOqmzxtLiZ0qa8+KPQRUJanyeocxiaTF9HLOvm2/t2FrtzJFMQU1FD/+nIEcsAAAAIRNmfoYRTsQmbMPSgjfQiM12gMIE5BGJsuJPCVwArJZNdtbI27xsKLmXOfRQ9Ijk6lmbCMCfEF/wf92SJlvh/I0ZNMl5lVFZca49PIO7l/VFGNoAv3AZxUQg+SglsClQIZcL020sblhMH1XOdr7rGK3mPXkgHy6fI8H9d80OD8VeefKVhkcL/s7Mr43PQ5buX9UUY0igCti+4DDEFQuSluOqLzmWbhVY9BL/QzS4Yb7OdpSsj9IbLCQK9Msy3IHbZmf9W+/ytCtL2pbddsolpSfVKJ1jlpHiz6w3pxRp5mJHVsqLXf1iAAXOzXjnAP25wXtt2ouafFbZDapR4NGM9DzjGvbe3qza/PbLl3Z0tuv1K23109Y+9VtbMX76hUspMaGADFq2B0x3tRi2pMQU1FAP/6cATAAwAAAgYT2ANMG5BBRrvKPUIbCHUBkaGAqjEYpHH0YZQeMLNNOQay6K/FkQ+ymQUncKSpF1FAYDL8H124isL/0pmUJhL4IXCb5Se7GCK6EmCiUVlUGtbk5P4NP7Po95L//6gAwvU2sklEXH9eGyIzTX1hiOmNaIbbyI1UP2fZGqZ/wjJmlK71RJkX+jWe/qzTNwb9cRPfs+hbVJeGn/f7PWORkQ49QRLL9bJKFNjMwaqtaPbzhuVm5xvOlE5nEABKJCJ2a+itQ2hUnf5Hf3O9VmSy9ew0N/o1mbnSw2n6X4G7kW9ZD6N4TkYMmlutclEptCpiLH+MYo6s+uxXKpJjjGQrIrPentRQHlnJoV6C6ZLZ2yfT/W2v+vtzpYa7No9eiOOepJ4/3acTOXVUQTEFNRQAAAD/+nIEaOEAAMIbNdzR6yhoQaa7EmTCcge4MX9DBSTxF4isCYMlwAAgCJmllmGAdU9mJi87VMnyKGBe7LUN713QztL7eP2Lv0ZyTUQezea3RrIJclqb79FFLaY9jWwzQgjjRptln6tlvr2gtf4EgLlKsnXmpYDnIdq8rcmqTMQr0aQ7F8frNqtgpnKpX62eX/yF5++dsyLZF0mbJ/eY5lqIodfkz8M//k/inS0oYK0pE0mwYmXsIGwTZdCXc3Mx6wMs442fPFSSwqgmgvQNsaymhOImaj79SjtjGztE3+2QMwWPvrzTdYZ+TCGvgK4tG2WNUUoeOWP9PUV2CqavoCYT5LCXjRq13elcprCLw6opceJ8q7nGEZCus6w03r3SaSKykbAz8amsIuRTflqOsMpiCmopmXHJuAAAAP/6cAQkEgAAwh4r31DFGPxDBGsTPwJkCJiLaUS8TKEJle1MwInsECCdNxtJRwK6dEct2TQDjMzjG27Rp6fA+R1by+cy5fFofaSYovjEupH5XBmNK2ArD7nCvvUe7NPuayn9SdR+obpkySwEm45DZHcM+JKzImc9bP4C6g0qBcbFrlHX7g8vN3qzvP10rX3T1xfrWORwEWK6isTLxQKvNKe2m1v/rLMQ2O/Kf9OsAIAAmii3AVMFmwaEYONhhth/PUpx/FxzNWj515GbGzpQwV2RXZpBuX8Zu14Pe/apdbk9sRdWMOrn8tonbbSe3pwq/1ESdFG5R5HFo/I8K2NJtm7ddoCnGGiZFWAyN1DzcnCMSMBPqAbt4Pdd0f9I5lD1qZxNTPxcUhdmryaz8eZs++xb9+7amIKaigD/+nIETnIAAAINK13JARTMQQa7QyWiUgjcl2ZnjE8BGxcuGGYVhgBQpX7qyGSUorLHvXXIQJcI5Ymu1ksxUYN+UzKS8hPlNgouyI1eU8sKWWRCwsfWHHRfpQkHnNXqeuwQRaty/9f8eDLLt+IAaVZFDM3hehaFqYIWTjPYIMiNkELR+lUVp0kVN9sDqnWd+Tqv6ytv67GRlqJbIqwvF7PzyGXi1u9VH/9ILm2375DodEJTDU6TV5N4dWMAQEuaGZA1Ol4K0oZchZQtbm9tqmJr2Bok4HKiwx5VjgVrHRo9yO80lC9u2iFGiuaIe1P/poEON216wewlq1DU9+fgCE1iyhQvsaSqIuggyvKr3e32uRWXZoTJWva+9yCbuWOSYE8Srr2IQBgkyE3VPuYltJZaKNN3f5R60xBTQP/6cAQjqQAAAh5XYNAgEoxDKZv6FCP1iDCNgUWEb7ETkaxM8wnYEiD9Ox2S6TuFFDQWjOZgbd5h1y97qj87KPXtt6+Nob33OegJspdWMiv+l6VTTdW09/Tv0dnM/VkZ12bWrHbwpLNb3l0JUqTccctV3QfuZ02w+aaim/HnaV6I3e+W/y41+etQzk+2RooK/Yu/dznMvNTAa+77mCdKnU6LE4CcI0ighY/Gb6fXAi3bdckl0v5iZ575q0w2JrCtfiDN1LdkOJGwf2f5uDCodz+sNcuxw0dawOssiNMb3UrUC6HlErpSisw1dydSe8Jgpu7/wFgyESSiDK5m7Q4bL7jTYBo4JotchsL3tJTw+bioyPS70/rgl9nuHQn1z/LmxGdzhWw6KS7xQy5/F96q/C6Kf+WTEFNRQAD/+nIEiRYAAIHgHleZ4xOwP0RrIzyneQkk93NFlE7xIBYtXMeVTgWS5sGN0W1TCAkoY7ksMlSVXKyrBdQBgXhoIoYBnDhhamynd6+zr26Y2iqV9sXUiUYvnm9RUo9Z3V03aAXG3IF5Gsq6NHXfG5RB1guttoDmc5BKqA9XpGtVSPM1Dl9KIrWTKtfLNhSCps8/tQSqSs1e5tiUIQ2z+l/3egBACimy03Z6kh0jluzvNSgESffGC6KQ2LcLY0zmR9IrKjv8WnMq4v+vkkBKPN2jszTp7SW3t3hiRc9iOI7Q5P0tT6uG0a2Jc3HiBSKKUtkbozjgs96zLzfhoIZLRw5XRTNibZp0d9SCKFRufGDPbgGeqvr6IkIjxEQl037zllJMjrcGl9l70xi9m99TDtLb+oLJiCmooAAAAP/6cAQrDgAAggw93TlhFiw9hHtSPEh7iOz1XmeMsgEQH+3okorekOm2mnLTeyYmyVc/zVqBuXTceQ/1X0m6Fbljl7Z8wo++9Vt6Lg9fuENRH43au3P/nswWo4/12YZIislL2qRaR6aEtVvGWR+pOvWXNIOYc3wLGxzXIXbiSOcR09rIdDP+Utitqu0XyEm8yryj1PnIZFkIT752yn8zZIMyfc+/6wXHduDMNM3C+DTL6q8nQnzuyvumQqwaSscnHENtGd8jHUcadb6O573R3bfkoUb0fEB3f2+qhJiyG0fM+9tCe3fi0mrv9X3dIDItlJOT0lQBWkoJW1Vu9gAl4/JN/i0LWJ9bTJvtH7zGHe/FWmEb06AVtP/qG6dFyClqZsie2h+Px/ynCFLZKreUfd0piCmopmXHJuD/+nIE0SgAAIIIP2BQJyssRIZbhySnfYig+2ZmHLGREiTtWJeJllNEubmllwJlZRiamsgWfSH/ucepX0b+/TjB1R4lQ6MsKHcr6tq+w4UejJRmZG2VtF/rsQ+Jmkd1y5545IYbDjUpAEU62lAnhtsTUkw13f57A3dMoeq3c714Uy1o0yI6cKWVUmcSawKEHdhb1Nb6FmSfmIjUIXUCvfKV/o0icYKg3qCI4WjQk3JKLnVjQwXOactFu6HF5AAFP8j/QoSr1JK6HcuRZUZdv5Q/IGzZXhQjx7r3M6DAm1xVs3jrHNob1p9+Nb99EQ9Y2b7CFZV5WZqIkVkeLwRwXZ+BSFi9ffaz89Xuj8MJQaVCGZd+hchWVA/Bf/+CXl6F3EsyitzGt9CVAnwfX07/3E1LWA1DuylMQU1FAP/6cARq0AAAAhJJ3tUg4AxC56uHpggBiMkjgFiSgAEWLXErCnABFxCpK2Ru2d6FRtKM25+wiHzJgqa3KkDXlub/U9nse0wX57HVRz1DbVVf6c42evmtmNr5v/1bmf/t/QgYL1tAGu9zATktppyZVRrutxQam+Ht8Z+hkSLlHaDIjmGfC/sSZpH5+XU3FNy9OjYYTOvhUeC3Xy9/bjNOXb0cYAJsSOEAJ0tW9RhhhhhmanVg5uZMPzxib0GrHy/IxT/z/+Ock1P+RnFDiBl/+UOgchEFHldv/igno0YOSy2X/+1Wh8chJ6tv+UCKwswCeLUgAAAAGDAYDAYDAYapCtjjdjuTvN/m/57/8s5BTv/kCDkCYo//kRqE5hiENf/5AfSjKULf//5ZFaNxhFLP////mGLUmkFImIL/+nIE6e0AAAIED2TvDGAMRQWMLeQIAQjA2YGlhE/hEJst5YYIMAEU2pdI2kSFgwLVaUWqtuRRjZlViWAzuFERAmxwBGoVNHhQSj0JVrBxJQKNLBTnHn1tYpjkrVb60+5/+VY9Hv6QAAU5f9ImkXIFgBpVRtRMzU1J+iXMrIlKsxTzJ6EvpaxWW3uxnRFZ9un1OSOUCjSwU5x59bWKY6tU76y3U5/9ZVj0e/kQSZUqt/GkgmoAqifc2t542vvg3kJ1zFmsyTBiEaFxKNf5kwbFim/rSpDW/RWYztuGVzhxKe4p/LjsBgJ88yZKvkQQyXXUkAAE/QABogIeUz9o9Wq3noJtP0DFAUtoqqOl00yd9+Zrt39Ucm5s1X+2jqQ1v0VmR/DK5zyVhXFL+phfmRj/Mr2BjR9SYgpqKP/6cATgWwAAAhE2XNHpEGA+pCxtICKXiOzVd0gYQaEgpjM0MIpGAQAt6NpJw0xlBZyFzWodz1vTApruxGI+rcpc3v0zqVXr7YpDo+WtN5epS6KX0Wn4D+WJauQyK5Gp/yIaJaltZtNVBpGNySaRopNygbGrXc63HwFrhruR2r6LCeNGoj+6PX2xSBZhVarzu5SMV878sSyznSFRErI1AbXiMNEtVvagCMM5drXHgweFGkUzk3K68b8sbaZLU8Qjz8Pqvsdpf7s9Fuheha63s7GKh2Vs1HS9zQTHVM2nCIBWhfdQpJZ7v9CSLupzzUcG2rL97f9rbqM5G05jlC7QudSvxsiSZuv5bcKv7tlW6O1zaz+7wREfL5HRnYxoIVaj9aWL0distq+0p0Ta7uGHu67tCeLSd+OTEED/+nIE8SMAAAIOQGVoYS3MQCgK8WzCdAhE2VwsPKXBHyavcJGV5g5VYtrt9bbcDIiACLulheUy1BU2/gyPTe7rxwux/zWbki5+v2N6vldi+FCaN92yK7uuwk5KuwwGnOALd+1/DfyuTf0gkQhBabIHilLPLqq8oilPFoEtTK2mDTDVYr1ubSmyMyK9e+vftlXq+RND+2j2R/+vL4I/+/3Twzvy///J29gPniIQltHCQG4rpPEkesdWpoemcDLFt7naEuDHjS6xpeZ0NEHNT35U79vfvszVT21qRDc1v/4N7ZDrd/ZcEe7t/+oAAIlKX9tYZQ5rPruyOW7KIc7bBPpCBAcOZbELnf1fZ9G1bU3M16aKmlkUKu5/e/0x/6J+hz7iY6trd6JZu0jxB/oJcJehT60xBTUUzLjk3P/6cAQOeAAAghwR3eljOThAQorRZekoCIDZdyScQ/EOGy209JQ0AAEQUtn+22w9DPYZ8jGx0Y4Pn6nA3vKC5KFa33PYou8VvcUeg0K2FTyUgw9NKCOMD0wF0PWHmO/rU5+Gjmxak8VdfrH4CqD50tmBpuNTrAM5aQU7CQxybgVSZYgkssT1cYeQ99sR5WU+hBSt8PpIGHSIYarVWSnW/X0Pr/9Qx+hX6tRIQFKnu2ux1dMaUlZrSLKRLtU5A/9U1ehB1evRtfT1uxqCl5bZe6oDCCaXTe39RDdkSFmMG4TftN1NQ59UVSdTh6yyt6gEo65JHNBKwmOghdsgf4VZ6HFKj3RQ9GraN4CuqQ3n6e/J7UPodletvpsVstvW096lFYvtaKd39b7UHE/Xw9Ip0uWAUxBTUUAAAAD/+nIEYS0ACAIZK9iZjyogQ2sLMzDicwhwsW7kiG9hFprt5MSUthE3JJAfEkCaYAVj0RgtZrdoftQ8DIZZgEFqDkw1Mj0E6tTU/Umx8pfZpba/tzRriip11rCqs2+9kVZt6j3Yl/+Md8mU0m1KQz8fCAAn26lB2s1QNwQk9FPUGN6irWd535J7jL+frfbo7/p7SCmb/Unpn/Y3l6/6f6shmWtHZabfXb8E3oQusqs92m7CNuYSXvC2uj3VoG2b1BduNwT6iFq/VEy5/Wp2hFmz/b+pTlIyBkRe14oQIOd1w88A6uzSXtJKAyPjkyJy1GsAEGVZltx9AqEXx41uwpj0hS+1lBvabhnCSa+vX21EOqoqNlLy929K7uj+i2otlFubc5iphunXLPWMYLk6qnx0HEOXOHjaYgpqKP/6cAS6kQAAAh0U3tBlEpxB45sXPYUoCGjVcSSIb3EHGu4kYonOACC9uRtt2OhiCvcW1qhU7biKaRbvHZ6DK9yQGdEMRRBVWdVUjErJHW1bzJbUUNaKD1+pwqQRhlRrtMWKORq3MJNJJIggIUmu2P460yxhKK6+K3r+hGvWACJshzIFOMbMB0ZhNtW/lfRtA/Q9UtF9GTBM4PFLWC8miwUJ7uRy2hZzuT/0/uABCprurudUXSX7GJXWVxSLke6lZOqHaXqJomsF07j/WLoQpImW5feESUwQj2hXpK+nOD1r+1orIqXW7U1DLXu3d9RECMGluqt00oBf3VjT0e5a72CAjCuoJGhX1b+bpzBtjcNSjyTe3rMrU/swh33YYs8stdSAk1KKK1LFH1kGBqjt1G60xBTUUzLjk3D/+nAEH40AAAIeEdiZ7ClIQaa72g0iU4hI13tBhG/xCg5sDMeUoCCU3L2tiYpxYnwpPhMePWEyAyUh/KljrJTwyyixle+2Qor3KDdtu8WhtVaT9iCykcupRRjop51yZUk5jdCXyDzIFzWzCOu0nJG5SGIxXFmUSPexg05Ex4LoJJo+rd/6NxtC9NHI6wrNtoyOptUVdufpjl2o8zsyXoFzb2CmaRWd4F3PeR3wBIS245JMDGlK53HsG6LLzUuSEoUzC+WZc1w67rH4v8L1Hy3/9ZWlIjgGJVEiZZwXpJiQt2rYnj2197sRm4vUpGlQS0u/6QclLIMGfdJxL1Xkp5BXdXoEwJCmaJMR6bhe6/bO2NFAwcSCqaRTZFWCzjVaxqoatq5vkU3XLYYUibY5aP/1JiCmopmXHJuA//pyBJzRAAwCDytYGeErhEKkWxM8Q3qH3K1kRJRQcRsV7yhQjf4ElNyRJoQkYDg2tR1HQ9iUY0pRtBuHiw0gZdsKa0zZKZeXkbV//sr09tllQYfK5FIs0UeqvcpmDuo8mXGjk3u/JdIJjckkisK6541a505eNu6a9kOZEaU4e7YISR78nX25OJGQodmxNAoZGEIYn4RG+NYBgQKhw83gvHvI/tNMLpSj6q6QUls7grQKlAeIglS8wPM8gEJPCVtkaN4Vspmzt+gy0bhH/kfMb/5nxzgruC7Fn3n7rs9+/wZVjFX19kbTtd0AxRqo27JchXQXRXPZgsV3YeOKfIZMx8aRmunnyeX+I+sva2sFQE2jkiY41RkYUKPHjxgU2dT1UNtB8e8sBdDxSIjqtCzTNCYgpqKZlxybgAD/+nAElxwAAAH4I1zQwRUMQ2Y7WRkCKYigsVh1hoABER4sTp4gAgBUbTMcbg67cPRUGzXEv2Zs/gCT5ldxSqMHHZNU278Wmb/EFk79QuER8rF3OOJDTWTNLHqX3bJE66weLmnqkgAgBcrvBQQMBQ9JYUV3Ev4eN0lkfccyKNhQKhCDdu/OTRunmXNoZ6dVK2f0UogQmjq6tiA8DYlWwWS9Vi2vxDclNaUgAu7ARCD1YWVo4PS9TiIVU9itRUcvl5FOhSQKhkiyA5lMhoaZvqNLKPe/bWl0G/9SCX61dmZkD8rqWGVCeRsdcYc3/UKgpOS0bmN060RBXCjFyW59RHd+tysjCY7jkwYgm3E+2ozQYbgm1/oIO7o2vt2WdSPtqMlnXBdL7cSfYDowUbSd6cqYqJpiCmooAAAA//pyBI/jAAACJCxe1ghgBEOC65rEjACImW+AeBKAGQWt8Q8CUAMBAARm6N23bbf/gCs0KFi2RnETsE4Q73pynbvPj/b9u/5T8rryZbZcNavnGS9ihXLDiZ4ezScy4jFRdjSyD165isGKHoAAAAVabckttt2AAUQxk2ZEbBczhl6Cm3IDkY3VSjqt+fGLSlgyTQyABMYPpIoQ1Vo9AVUfXWhkVH22s0ut1TzXvaYrAAAjFgtFoQoGwRN5s507f2///+3///+d9v/3IxNyXbZBR//oQBAIwu5yEE1UaQUBHKYX/+2KDBwuV5FPpGjRosKAdBKIAACAbDYbC/KTJz///////////3//9yMTckXHWYgIL//QgCARjuchBNYmwoCOUwv/9sUGDhcryKfSJjRosKB9BIEmIKaigAD/+nAEot0ABgIOK9+vGGAAQyDMTeGIAAhYk3gHmGXBFwfvpBeMCAQAu/MxyYPUU0Qj/vrVjZbsBqJNsXn39kBmQlhva8PvkyTlbpFlzyPuYOGRRxkmMnVNzr62CqEdlOblP+TybvVspIAEJSJBablnELd9GgQA2BsWB5yhdx4+/NEypqFb2kAYYKsJOdQ9o8gOYh8UKxV0qk1FlNd1JvpNQyM3D9631gT7Kt+qnsG7MtliRgpWO7OnfnG2yD0Bwki6VlNVIIaQc6dhqMf/+3mvnVGVj1LHNCYzcJUnUJNJdeQFUpp/rvvOEv3dP0dYAQAAIs1KYZkzshhmUESzenS9xFYPGGUkF2hIokeRFCRvVSdS0A345oTARqZEqWlZW5htgqB0qS/ZuCbn3ub+65yduhWtiYgpqKAA//pyBJbkAAoBpQ7eAWUYgEKj27AwwyIJkGlyobBggTUGcLQxDQ21QoBsNqFakT/RCXiGn7CCZVaFrODGDh9oRpepZ8e0JNW7FV0Sp3mW6HKUlGytttPI9aborao0A1O0A2wYToLpUrW0xJe6Y/SPLuqNyZwdS6SX8d4SQfPiYJHy5yoVW1EqdscT6GKUwlbra5tO8jpHm1OQK9m75ngaMSgNNKWj9VYw7IWpLTkk/607EVZ00PGRSDkCxmRA5yBRh4wWaFHCjA69Y1BUsTHPAMO0BUkWUeAp4K7EEeWCuPSmlsVqTUPn1IepBI2OxJttFKSguDb0ph25oXXxUtrf9krIq711Go/frBn7JptKDEUD+Ljbze5xy9vvt/5RXPCtg2U22vY/nrBVX7969DeHDb2fuU1mxVVMQU3/+nAEjA0AAgIeIFuBhhlAP2R7cDDDIggsYWymGGTBFARuJBYYCMbdSgZWCCYJec/d6xx2zjK02NS81IOTcj4tYgp2cj70yYQgWSJUlVsFUCTNVOQKnFuIJQiSYsSk+LFvXp1aKky3fbcizZIRi3zEwSnoc/AHc8qk2Im1LVlVybY7iyiR2cj7/DG1qbgzLyYkoGnGEzs1UJHlVONUrGRc6HfPW7//79P3AkP1o0IK3hVG7sCdisPzmgMjzlky50nIoZsODRtqyLrwaFIlW5wpalykVqWeGpcy0rgq6hyGsPNdb+SenfYv2V6wA4mRRq43kIX3OWbdYJ+9IGjweHkY0UHVuRHDnsMkjWFBTDbamFZJiXVMEzWigtCam53bGqrElaPrMz1lRokrUJXOehzExBTUUzLjk3AA//pyBBlfAAACHg/cSSEbiD/kqxA9IjwIhNdix4xOgRMSLiiRiaQAMHZ5KutRLiDVsQ1DU92hK57YY37MUooy6kPAErYzYRHCi3Xplra3E3oCoiWCY4i56RQe5SiNNDPYlGgAnZWqxn9kUyxqIf4bEAu1GbcqIXdNAb/2oImp/9caQzBKZHuqCAzqSjtqjs2q64NxMzCjDyDM+SYEwkOWqRLJ+wUff2L9fMQAUFqs5YDeENgNywbkW+EzSjUgrxtQxSsESqzTCK2vdTyOz7S+6esxpXlfiXZtOq670tzqPnB1S/1lY21zko9LzPK/uACnNFOJKW4mSgIwpyUtkdd+JxLl2XQBJ4/7d5WCPfbN0rTchVDAINiZzqmXOcbV3vcTbcr7qMpNpAiDFiwAIEXQ1y1pNMQU1FAAAAD/+nAEowMADMIXO9eLDClwQ4R7Ej0iZgigfWZFhFLhEgosjMWNEANxlkUdkedTEMmD1zVhrsvbHePt2BCH+m9feRkZAViUYrE3ZCpd9F+3SZLlR+907t9NVslrVNui7WjcpZ//0/p6N1EDN3K8VrCDynX01XFtHu0ZSDL62oa+juVPjyqabBNu0ZZEprblvK1HGBnCLDYd3xVzFIZOC2uB23gSerT7/6tr7/7dAmq3Cy4nAYkxYRVm6ssVJS+zmCPzSPkaUoZFxt4J+SDQNPhIJA6Yx1jFPWlyy0i5zH7S6HrqWLt6zQfjzFCLGPQoOU9VAJSaktYhurAYtC112KnCGW+HW5V6/SPcRnpPBDTKRVoacp6lgOq1Zi4mgQucAceupTlH37KHot0ihfk/YlwoVi8g+RfSmIKa//pyBNJyAADCHxPYEekrkENiCwI9gzoIcK1rRARTYQoM6sD0mcgBq/kevz0J7ZEtSgvPld6h6H6rJV1jg9Z3eGM09Y3AyBCu49PZUJKGAEmowhKsaLF4iT96VGWMc2KYTU/FVFndGl1HHAd3mG9qQBbWQq1o8evUVxZoxtk93Ezk5EkG1EFmodcmTMlB18HCQNCVw1DYSeoJuJnmjiA8WHJRbRUh199bVIk2dPrABDBJtROWHEVwtY1jrTEtxBxveoy+uQYTKQl2JUpMAKIXeMEkcDVi7nyg2rN3MV72Q7EPKv2talFjVqRScdYtqKtkWXksE6IEGkbahFEIK+y7B3wrYGuWm7Z9YE9BW07MCPDk3jkcvqHsQfMxYIHDyyKBEAgMeEkOgS22tlrzmG73////+pMQU1FAAAD/+nAEGBoACAIZE9m4ZhKYQsNLmQyiUYgoWVYkvSGBDgesqMSI3AAkFpJywVUHyFbWiQ6PH7ehmgkwQ26XhqnnAwZe+4jueSAhhZZNQZtny1oA6sKQCxfS246KDcNIegUHGpluUfmanscAEsq1S3sZPQTCOqFvUS6VzQnRrSro3ezsjOLC88uUjSaDx0WD7Fih46pZs+XIKSZuAinkaP6tyGuF1KU0znUSOnRSDhcfJMJuEomYFEKtxfQHhFvlyT4h9RcVKiWhMe3AeDm/TNZScfqSyxRIo8A8JuDKVnmy0aOjzrFqHiy5GRR2a/QAAEABJScuvlqEmOBowGkWaq/2RMqS0HDM0EdnJJxEXQ5T18K4FQQeS++Lw+skm2lFRuEnC++vxpKPKpQLUBO7dSSocmIKaimZccm4//pyBIjQAADR9RHZuSUTmEJCOxM8o0kIoOFcZ6BOgRoW7Aj0iSQEJBablycVAcDPrSFnJlD+cMknMrQvghPHAiqo5rBlizL7oBe67KhZ3UoVL0PFn1el0Y9Dr2ajyLGrVcvs/0ghJlSywUMZEJhHsqB8umQQH4Ai1+DQntYxlRccZNiF9jTorrcZKlFuY5CGRZGMPitqVsdT9tXXFoWHZ9w0Vv3/3dAATbtErg0ELCyh2sItPPQjFryHEqe4CFxyCjckH8UdZ3W+Z3W7slW3fWy/BoXLQ3rQvy9e6d+PjppL1Xhh7HJUtHr6SVkC5VCErY84bGZAYkrWgc70Fp/PiZimB9CSA0Y0PezVR9zf0etNMPB1W26PmDLUKkih7bGnrdQ5iWi9X0i4heLbYRzIt9VVSYgpqKAAAAD/+nAEO3EABAIcK1jRiRJAQCNLigTCD4goV1xnpK5BFg1uqBMIZgAgABUl//xw/EYRHcqHdoaNMezSG95/mOWG0IuI0Z8H4rl6H76pquoM7unTwyj4zMuYwqTi15wgLwg51A9P7abmp7awAl2JTSSUpPTOLwH8QyvwV0L1OsEkyDaCeS1dJ2xmF3reUY55JJtj9QY2w2ZSbg4wowa5JKxbejvpW5/T2x7siAk5NtI9fDNJa3YgnRX620bqcha5UTEBay47CgVXQa9zNjYDKzyPPrcfCgVvsHjx99V/OzUkutahA9LbLKevfY/UAHNaLiacpG0ixeq888N4ECfaadkQIIvDvm/UL2FSoamR7bB8ffGzUvqejOHWo6iCzokhBqqSpMyGhQMFT0fIG3Pt19+lCYgpqKZlxybg//pyBA7nAAHCERXc0EERLEKiSvc8JXAIDJVzNDOAMRQcbE6ecAJBMFim005UsOLcF0+gX1Rq8EfmewdsGcFQcIBp77StcLqf0Oa9D1GrEsolY0wuPOKFEpr0lR91yBHQODripm3jn8qAQASW7+SOWi2P9nN06hJlVH57rNpVCmUdjBWEaxh+JP0VqXThEmQUJ0K3qcVyKhHagZUYU666SWKMe9PSmUo2Rn+hANHZvjVQxTXW5eZPnBADsn6Fz2PHrzeralj8/qZzk01UeU1yjr7ShGI6nL9shIWPS/sUzuNLOAI/pPFzFjyzdKBTbkEBmX2suzQrFodutZSbhNmRuLpHUJ18Pl3kM/2uhj5UzYhzvzR0orSTPVOfzex65xuf9WrqU3WzbRWWhStjfEa1dqYgpqKZlxybgAD/+nAEWgAAAAIUDlkePSAAQmAMCsGIAIjda454EoAJG65yzwBQAwQhJXagIBT4H7gUTGXiFZRRnjPFTLmnbIpgQDq7xDVn3CYeBYGEABIDsMNU65HkAOyy0H/e8ab0GGjU///vT//0vsAABru/cls+v2/wHEJZB5gZGtPOcNWolGtAjS9WQeoZhifFHUwIwGYsxu8cDIfIHBjGuL1Ipb3jekzJvT//y5qyYE+A9Hw/H4/F1//////9G//nFFOuriUv5A+LziAoKlDo0WMn8XRuwmLiJWZzjmD//Z7oQeQjkdxsgejTU/+z/I1X5lGowb4SlgEQsFotFo////6N//cUV/p/kA4fcXEAIKlDo0WMn8HRuUPi4iVmFjjmD//Z50IPECOQePGyAaJmp/8j8khKvzKNIUN0FRko//pwBHTnAAACBg/gfwSgAEKhi73iiAAIzNlvgKRBQRibLzRQilhAAxkyeH9iSaTgmWjmA1hbVQIC/qoIsJH1Pc2oqGyHSVcGyLzz+JhZ7anxc9EiTp0JtMiTT+oXdZJp/dg0rdlkrIAFQtMZABAMjCIQd2G3Ya2r9EDgHOIBORQX2CV7rrw0VHB8snrCpFdhGsTVnAKVKi6B5zHfipKpa3v+jLHjyyHPPaAA+giuQACplgM2nikWqTPdHbqbtwPgrb7n5yko/L9EFpcuTdenTFGd1t5n78Gznjg7odF3dKwwJHNPAIDP8GY2JRDaHdesgFZrZLNstt2cwFqzMaNHbtaqh5vj5e7Y1mak+X6ILJcuTdW0/Ml/5n78Gznjg6qYdF3IpSsMCQY0OgIHjXgzGxKIbQ7r1piCmv/6cgTkPQAAAhhA2dnmEWBBaBs5GaIYCO0Bc6QMSOEMIC20k4kUAAHAaFA4rGVoiVxfBtnSw3dNVfwWjkNqbmJ3/qt2df6VE1K226dHvQ/BrrzU+cC/m6Vb4NtG3tiX8SnX0EQHfXelqQAwEiUAWkKGAWzIrUP1CdLMT6S0G1Bc5O/flLdjLobmRRKAylNbdOZ5teDX839RX9Oiq3wbfbfxL+JTr6CPrvS1JAAZHjm0hMViacII3Ly6N6WkTA2YE3GZ4zYIUuXhKEd3/fUmxiTFvzunlb/+bwa//vnodQUsXGPCMg10rd90qknvqFjp05BkAAIvhBykhF1bBGBjx1fVqMUYMFQzXoNoJ5eEH1a1Mk2XoatHOtqb9/epKbe3N4Nv6e/8EtK9YJl2ucVu/rk/UkXsrTEFNRQA//pwBEqxAAACGUBXkekpcEJoCvY9hSYIsQF/oaRKMRemrTSSiPQYGmwqnpNCM2YNghGfLhyVCkTZv23ebq5Qzz6DG4p1vZK/8mh1p/1pIVKpSln18IjE6VV9b8yuNShvRjJb+/KnfofNAlCZSZFUrS+CasOPGO2lONvjx5nWNDkwYkKFu74x+3NPK/7E6aHZlW7L+i21b/rTGtSvRv18b+wpvMlNSVVa22889gIxmJySJlp0czPhttjbsvRu1eCH1V3UDGo62mspj1t67FVDnqnr0epjHQGtun/E/06uc5kogkekRviMSqQuMf+doUjR6gAAAKGVGkEnbR+YvSWmZoTBcYy7qWNXChfguK8l1P0d6+2gDZWZTI11v7YpkZaX/+N7Z/sqNy/r+SqfZDAn1KeLErqPljSYgv/6cgSjNwAAwhE1WrjHEbBDZCqwPSJ0CLDXaOSYoyEFGyuI9hyYhMDrbrq9wMvDnEWWiG9Bh9o0nBD9u3VqHH0Kj05vOyOb30PM6NqmrdUZfVIJhFZIGYhlpn9qS7migU1vaSerBWn3plPp5aGMxLqASyPHyFOgcZU6CrfkSbTJ/2Hg6ahWxPic76N0/Zx6Yq9pt9wqLzMeQeqoTMSKuAyKrv1FegDP+jWCvrADBIm2pbDI2CFFYZ74lcQ4vOjRLjA14wX5uvE3Sxkof2ag+5S33trmqqD2qj+j5tqBoViNrgEwDqazbtH7Zvt1+5U5U2wGWva3RtE5HhLrg7Knrg7H0DAzaPoMQO5QNaH8v/V3s7bq+qMqTqOztdv51Ep/3vbQvb/FfVRER25L/XA3R8mdepMQU1FAAAAA//pwBMNuAAACDSvZMYMTmEMGuwox4jQIyHFroaxDYRSgbyhwj14EK/c1Li8gBTekRzLWMR7j5vTrR8O+Yfj8J04OtXfJe9KkVjOtvTuaecGPAzHkw88O0i1BDTqRt1XWPaRd2zeBv2gAAGA03JJpYTiMHtd0XV1N2C/lH1Mh4GsxgXNwnTUGmq8n516Hdf1N7Xstm9d99RDZYX12BJrE9YeOuvej87cBv+xIAAAAEJmjkmiigodV2EBdalRuLuJQeR2hyapwf9TuY9ni3JafC7Dqc9YUFDq0bBgVfZoRnJK6MbkShQrW795qJTlrMTjV3IJU8422m5Z2L6CifFTaHdleRfltegR89NSVF965VxaD4T8pbOb5UQkVQ79ffp1XBZ1Dcq0IxQOhpLHNACVoVBRexx1vF0xBTf/6cgTxKgAIgh8tV5npElBB41sGJeU3CKCVYuSUUiEMDSxoZ4hsAcckkbpVQtEso/iDveG6eLJ2QjnyjaQdoiz9Mc43BNrwZtxBKs7UPTVbuib7dvswMBqqaSdJ1V1HVnFjPTAttbv/d9YAYtqtYqKAHAbUsC/hRtTrHy/UbUPFIS8E2HNjON40fuO0IVEhlcdujI9YlfUAwG+RKwqh888W9ur3yPGoo5vdmKQsJttO0aVFhKkqoABVKxt0qMM/2oo/l34YzAgPh21fUR3bRkz9ej1BNEKnrWgeqsAGNQCaQM3KPW7rfXwnqorxpF0lrlQwIabbcZIaCFbrExHMsTKTdb7petNBw7cbQL3bBvgxDIwuvfOirELipCskGiosyZfIQoQXOZ6lXdiWPc5Y7vRKbiJxMQU1FAAA//pwBG7JAAgCDzzXmesRcEPGu0ogwpCIgNdpIqRQsRcNqtz5HNACRubbLSm0EOt+ioDZ47Qt2wT39wiVOzVf1BxxXBvq+oV0UP1atKGIqTEcyN0637bf7d86//h1wOSyPee129yHazQAIZINtyS5x8gHaUAGaaDK/JMRfJ2Sv8wJ5uD69QOilfb30S1CZNpuv1zfJOJK18ZrCD0yGr9akrdazMyHXIZ95QtuQA2tK2pyAUBlUwC3jXxYW5rd0he7LCtFgfQzuw+DTiPfV/0tOQt39WRvTav1ZXTuoyK87fDKf6O4NthzhJKh5p1yN+hgBAWU24F9HmUaYI0xnzUCEyXcRE2y4Qaj5UWsOBvEwJnPN1IhvHRZqOVcwlEZ/mpYwbrrGDnmNocSnJVPukaFL+7ceR+5SYgpoP/6cgQBSQAAAgQr15nnE6BBhXsDPWUmCPzlb0SIrTEYnrB0EojWIMVu3lcFlnB0PG98PqPaq7c9ywXXIlS04vwmdojx+vP0fY3Noojvr/2KU4V7zkzMuI9VRxBAqFE6dU1Z/Ut3RpQelv/l04MgNWKVuBR7KF6/IvIovQfwa8Z0H8urtjHypZEKixzo7nt527ajRi3QhdSrvuCrFm0J2VXpQ8Q9NSia7AAkeJbbSd+TSBPY6jxmR1/iG7gxnV+J0DNnfm4TPmfdu7UtU6u5XPWXpseqsK83R3abVPH2Vuvf6d3fbQpJJRtTnB+w9cqgJpBqO6xua2XekIezywt+MrxQMAgn7czZn6cH07nahmawnVj78jaeDfl5kaj3pWnv+YO55p23QUokxKhskcJgsONgYeMfrStMQU1F//pwBDqGAAACF1FWuww5QEMF240VBWOImWFvRaBMsQko7SjyiXoEgCJlv9BLX+YyCRQWcD+lIp+yGo33C2W50w0hvwT5ctkU67K9Wfp19+Zp9+f5qJJdVt1r/U7+vsn5v+j//+W1qduQAACVCUo00lbOUBqOVnhb9gulQYpfqPfCLZScIynCIuJFDzPZG19cyN/9WoNI9g9OkrkLKYq+yS+v2dXu7BwZOojv0gBJ6C2km7E0kGtKWlMOMV/i1TdArMPr6tmG0Jzjrd+v0qndqL5Ovt267fTk3jd9jvVS/z+up7fr0fQvf2ZSDodNWJR4oAGKhEbkm2K+gvawsSVk4XxECc6j6+KcT9ulSi4aqlatevVKV19G/xtC9fTp/t11X/9X/7ZpnHehHuEE5VAMoNrM1JiCmooAAP/6cgQiEwAAAhI9XFUsoAxC5yvtpBQBiNmBf1gxABkOGGxDHlAAgCn4lxNthAunDsKSvbZIrmyjDkIyBmbGg3G9G0XiA+ltE6s2C9tPIXpqJto/I+dlYjS1WowFoRNBxpfup/fffaoEUQk3K3HbLJdDzB/e3LhOZUsLmw4zVfi94cF9B2g3hQ9kS+nP7dnr6LqmdRzOgtyJZW0a/nitkQy/naaECbfz1qSFFYACq1X9btWy2f7AkTD2X8tLYcbV9aKz2RNU6YJ70/kW+qel3969nRdbNYiv3pey3pa932fpddL/o1PdGazf////yCHwjtfg7BMRRF28SKDdXtUGGgJk++QqjUGuo4RFhgTYTFRxkYykYogjOn2/Iv/5x7OdP/8go5BQT/94HQZ/+USFjBR3//MZvcmIKaig//pwBEf6AAACGCveBzBAAEQle/bkiAAIiFmHpARJIRKNMLSQjeSg4AQHQczh8xPlj7RdHi++4gUNWaVUq196zKzze0cv+a6LWh0tS9klKgpZ1yxYOkQLTis1gIom35pzi37mq8nfpdZtADRLVQIAQJCNSarUH8qIj33EVKzOVZaGUtWoZWT9kdevmvPWjp97bFQUu5YsHQ6BacVeawEUTb805xa33NV2Bu/S6zaAGkardrG0lGAwQeKvMYNiTb1Exdf57PRR3R6gJTYNOlQCQIk7ayIlY2hUXfPTMasc3FPesGSqUuLnBlXQql+S2iz2RKAAkbNtrEik5AoVuWe6lHzTPPVizVyzNXmzWRG0UffA3UuYNOioBIEfWRErOqfffM1rU18U63rBkqlIRLnHPV0DKejt6ExBTf/6cgRa3AAAAiQ14GmFEOhCxrxtJCJ5iHTZasw8ocEDGuzVkYmYAAEDl8yjaKvBV8a3YVqeF5Uw10ehoRf1m0U3qanX7rB5u9HXq1tRlqyN0Rkr4kqWQqu/Z9rqmrMIf4bFcsGxwBdQxlCgkjFbK42kSXybW7zbh4z42e03hoDx56ls35r1NEdfutc3ejr1a2oy1v6I1/iSqkKrv2Wdrm2rMIf5MVyxNQBdQxlCgAqStQwob6fjG+mU7Uo4ttNBMHLUut9o0kXlm5bV9lHN4Ldub2uHnESl9H+320LvorUfoLflg7gJf1UHqvZK/Cv9bhoCBHGECOV3G5y2kp7jLdZ8fksIwzQaniyooC7tDlpR8uVvoDU2b6uGdSt6P9vtoX9Wt6D/g0/PN/q2dVp37ftTQmIKaimZccm4//pwBCkwAAwCAUBZky8QcEAGy0lkwmII7NdiTD1BgSEgLej3iDBT87FOCYQ3UN768MeUHDU/Hk4Zs476b9gk0qNK705xkZ+guDYlv6vVU2W/J7m2K+vTp6P/+mz7iff767/+T+v1gADAR/fjFRWJBrgw5rskjNjuFKODrfBNmLBrsDVELeXgqzs5H8XBW1f9tfX7cjzFXt76N8f/TR+K+u/3en4e9YHT+C1BvR1EBKa9zDH9amTiBgw5Zr4JOrEzLdD4u7NyVGv0rI+mQrV2OV0rbt2oil1t6ucb28v1boihzq3D04H03KT0d9R6kAMgkhdbfw3gKKqa3WPCVW4WOoRtzRXmJ4KsVwXt/6j19GRl5+mRr0Lorc/99XQWnUfT1b+74Z3xChukX1Cy5qKLJ2MrOmWLrTEFNP/6cgR2rwAAAhwr42imFoxBxBuaPWUNCJBlaGew6IEVDu1o+BUYBRNDUuk8rko3UmrWLTbvxHkixkYhQcdoyOnU8IGJ5wfNtrNlV3J27084RQyJVsLsXlKPMTiCSfF13pQn2wLse75yRqADMFp5ySYNmFSo+REl/DMKAlR5PH8by8T6cjaNhZIxDUZGddRRVTXX2jSi0bBQmLRaiuqwUQS663M0SXdPd7t9hgMSyW1lDztWmr5KEdi6EyBM1xvKjGYFmuczUbKjFi7mu/kCzpeiGmECKXIYUcZbdHgw5rlOU9Olu37EtKF8rr79YaleaABMAMay3ZxHj6DVzRrgVafCUO4ok0XsiGwVzcnjiMLhjKnUdXzD3gkVCJd4193Psc9qyBRFFa1Mt12/Yf1NJdmdtEp36UxBTUUA//pwBLhyAACCGCFa0W8qkEJCqwI/K0IIVFVvRLCoURIZrSjHlUAAIAQnpbthYHp7xheUFt7ksSFz76LQfsE9lQ9WSNbEzKnwDoXlNaszC49tFVbioZhHEgwi5RO+2y5ntooqfd3fKftqAp/xIBLleZgTqljK0uE+puXDMA8BjBO6A+6XCHNQ13n6jTq8irNRaUKSz1wOsY9BotTW31DCzpvbjq/7VJ6f6vo6AAAmJEbckaUcoDP5xv/CkDh6K5x2wCOiqY0vLxtGpeFkl4NHWRFXXF2VRQY4KbLukyFECjwvXQeXE6Y0zWUtspCv1AGAI5d+rMC0NBQ6GhJt+ui3AlBYA6qEuEthJGlfG9f6kqjal1Htp6b9ev3Tb0GLFkrDm7F4GQRXtY0u5NNRb1XKWe29SYgpqKAAAP/6cgRBewAIQhINW7ElEwxDRruaJWIuiIDJdUKEWLEMmu7cV5VGBOpKNlVMOGP6HoygeE2QZ6CGRh84OYSJTpA5KzconDVqESlUY+jHDBB1O9toy6yguIC7Swo4vvTHpoLNEaMgaWp6gABbLEjltVx4w6p31wdCsIvSvo14kp6tG7guQdZB6G9u9VGapF2+5m5E0//6YZOo0hVnkDxp1ESaClygpKvjn7Ob/pBYItMpxCGChur4xwFeO4vRy1mKSvUtBiovkD8wUa7P5N56A9KPtkMymGiTcr7z8KLfbPJPIEURK7Fhk46XGzeVUOWqkUIxpJxEhQvK5tLlW4oCti3NqErz9KxhjsnGCtUfcczqWp1fLX0Pdvp+nRiEEPHLIKV0001yya1x1DVmXpR5P5tSampiCmooAAAA//pwBB2MAACCETFYGfg6kEAGeuJlB3IIoHtvRATwkRSZ7J2DidoAONwCQym89Ae8OjXJfMrLhhlTJx7DwDGKC5j3iLIoDxzJhna5/QHc4odqzyrPa9Xoqal2sZtW3vuVcJ66v3+t90yBl4ZTj/raJhrsqhunyv06/yD1S+PMvnqcNPoaNxFkoABzULLP28TBuPMT1Nor1ZTt/79q2f9+uePtB+aR6f6FgEAiEm5JtrjA340b1JgBw05C9B/ZN8RYljGNd/KhEqofq2Vx163DXucPDT73F1izo37FMstt30JJE1hwnZmDT2FaP2EVlBBSbkoXfsTTV6Ca29vPmZwcSRDqxa8xjvNJtQH14absstSehpaavr/K9rak//5N0VXEPBadUd5FTEt7NEOUEvgH2EKq0xBTUUAAAP/6cASF4AAEAgsy2Jn4OiBAxmrzYQJ2CJTPYuwoTREQmfA0Y5ZOCETu4bKSrsJvFWFG03yWhYdPg/tpDgkQo5BmkLzRiwVGO9inhLnmZrY6+a/fXq36M+/bs0cbUs9PW3zUjoPx3/1ABpygSmXPxPER60cgBvP+w1FXhOqdU9z7qRODROA9W5AVXyBtH04Jo5y7O0K+t63q+pe2lt+2UmQW+4t9Mvf0yIIIRSck1Ds66avu15Qtz9XJQOCpobDuBfOMHqZ8yGbYVxtbDH6Csh8yYJsnT+vt016NkdqC9933SE/bdbY8tR2fSQT/oRJSDkbjrjl33HS/Ze63AlwTz7TuYC/fS6QjBTTs0/ChexHua0IPo70/q3rSlejaNsPebnPnLrbZ2dQPFkkTzbOfVZTQmIKaimZccm7/+nIEd6kAAAH9M1m5bCm0QabrAzMHNgkVb29EsEcZHi3u6DOJfiABBJN2prULV6VFhF+8EQAZ7c0clAB1YN5rUfERVo25wr13bGru2nRu+r9v/T3ZFEpT+Xrq6vDSoBDHW4xG03hhK7cQxqBp2JewrHjlNPcSob6nf6jsalw/U5Xl3mAaL6ifcpzgbc7q+c2nT31f9LfT0c4qj6LQqxvAdfVVVdUr+4AAJAi3Jd02dLu9jRb38fCWPN6bF3pQDty8G2u5+orB9uDT9G/UftpXbR+bOVtf/+V7WzN+rabfNq3Xpof0lYjKRLhnc1zuKzACRVhKJJzS2jy0VlXChii3mcacW2jnYZMm++oTTCqzEM8P1ANG76v2/+/R5hPX//s+To39mo1t+fbn2bn7paRogZIaLIi5U0mIIP/6cATvAgAIAiAb1hsseyBBg9t3DYJQiMDNZvTygBEZmaxenqACAALkAqyaCF8vL95hwuXGblCr4cOalgWXr5O/XC7T9LYd8ubn6sFKU3N7zT4tyk1PBZ+p69ynXrZe4tdw2HkQTv+R5KkFYRjku4KGDY1oEdfweIiDGz7B+PPFtghT0urder4J8G+hTlnl++66TEgc5L6bKJOv0lljeTlNwBMlgweGilQL3uOAW3JI2M2284NUU46tVb5FfH3DjTlZWBlVgKc0AQd7tr04wA3Kz8fjeQuILx2rWvqib9+jsgDnCzB59efm+5b7/S2T7f6AAgBJKSRm2c74j2lkn9t7eWMJhgj6qMmU8VtBNPHyASk7ybke6eTzuyK8q+i5Q96ty39P78zkblf1DlihQ6tl9lyW06G/0Jj/+nIEru0AAAH/TdyOJKAEREnrksYUAAi0sXg88QABHZXxN4wwBAACZELyZw4whA422aElNxFqrim4xlGMcSH900ZdE6rnc9P/5CNX//cONIhP//xd/////h88lgO8H//+Bw+IDNyAAAAYdUVF/pGq/2XGmRI0+49UFHZxBoy4kLWmZLVzN25xc7J//IRq//7hxpLf//i7/////A54oRgO8XP//6QfAgIHyiHURYatNoxNq17CmhVbn0XV5pgpd3Uxd0rRpqtdfaj/5UPdEqyN7fUKJuKwo+FHqbLsMhJrzxkZf0Fi6Z4svpXVw6720awAAQE47G0knzwGmgucrGdrz/M6XsdZS2PvMm6UO9sXQf//1j5yFShf1f/qiUTqiMsRiPHIHiqUzTE+4895ae+2zLGlN17nSaYggP/6cATdZAAIghhMXSnmEbA84RuAYSsWCGTZbqekQ0ETGu4k9ggwAMl6vA3lyiONnyHBmC6snnwNUuqdOklBui1fVvq7/vuUzmNoxW92ZHdv6btTHptk3qT0Z2t26KmndnhBd/yOkdZ7qX/Ea0wqIOPFRNJIvWLy/NTokiSKSpQscVW8SuW8m+lLwKHQNIBrDww2N7LmJVlLxJoQijVZfFSbfvzf6N6mDYQ0DjZCI0ICx9U5eineWXuGoHL17sLhxHPn1dqsv/KJeQ2Wj/e2g+xUr2b9hrb+x8V9MVLT2fI+JTgM1hlyjvVpAcBFaehGB3PTyPou02mNaN5VPYvUaZT8FWo3N+rVXT+UTk5aP97aD5SpXs37DLpVqTU33izTxbLljS9YKFjsSGkiH2ZtMQU1FMy45NwAAAD/+nIEbqcAAIIjGVmLD0BgQUMraWGFDghU13knpEFxCRrvKPSIegvHOAt6EmUm1MQKg16E1qyFBbthtrT1DmfDdSErkYSx83OTXh5kWo6jxKRUBU+8cdxKAj44Wf9G0C/qerFVdyyrv+3xoABKgW/5AoOOemUSazn1HyLAsDKrsXVPfkDOTrEI0dquvEh9q/DpJhFQFT7xx3EqT8Wf8rtAv44rsFVesr9qO3xoAKFjXKwLgeSSz8lF9CeUSPKmy0OegMHwlaCmq+TBAJKh9dfRdS1dHdDkF2a5jHUp/ypk0gpC5H70z1Kq2Z7/7vu6ztBMckkA2EmgPp14TpesA7VDt3RSVkSPdeSfJq/bBpr6rVbrff9UfZjPNbL69k4IcVXuR7yM9IqDi6nnvo+67KxuhMQU1FMy45NwAP/6cATJ8wAAAgw12csvKHBAqxytFEV/iIjXg0KEb7EXFvF0cI5uAAAIC//MGZK98URr0RZ0tTWaUJ+dM/fqj0U2kUZmEqsB2ejtjs3Azader59SHsnVuTuO/6l/oDj9Wb7f9mj6ldDgk2lrPJLbLcJaPW98KYw8GwJs1DgkLFjDfp+GXbRevXs+njdrIxP/9PH/1/RH2CLt77Yxb/2S/6+b9VHN4iD1lBCLNIqONuCYgrPGlaGXGbCuPrrMMr9/MDxbXB86EPzLwYnyLjBYaxc1sStn36GvVOlAPcsj6AmqACI1D7HfiA7froa5QSKCj0LdlklUx2Lqqva4WGKDGK80RX1zikuPDw8WEDOB4tkzUONdesQtFFUeQQEEoYJlpcFVLes4dIt+TauNp+FGZQ6vySYgpqKAAAD/+nIEBbEAAIIENd7Q4RY8Qya7qiTCYohw13kjFE7xEBXuHMEd4hAROCLSSUNCGSLyrsg3uP5nMqYR0ftNsuVHM9TbwqIc4+XvrDZlWAutK+qdRhssvMZan1765vZargBg0VypifFAABmBMcjliCvef6a9UvciMI+L+hC6EwS9q0bk/U+uCFvu7IzL9e3RdQmhHNTYzVddRCGwaU7/9i9gi9t+CjL9oHhkgEAfKu9XCWzU5s8c/Q2EksWHVF+/dwaYnLkbEc2M1nI0pXevTp4tpHOWS6larWd93AQOyuuQVZsuW3YaMp2buRfX2McEBNSSVIJTDr2avRafa4Fprmo8Fs+d+r6F5/wsUmF+QdJnNd62p06uyXKIGrSqZfHrwPULVM/ezhwb9V1hEd2DQSY9FyYgpqKZlxybgP/6cAShAgAAghAc2RnoOyBCY5tnMeJUiLFhe0GUTJEMmq4oZgkaAKMttP5ZRyj3ZJK5KsI6IKoEiiZCcA75IpVSHCzB05mqdnZQVn461ygxNVVW6+K69IHbAyDlu5zFo+rhekz/xz/sAMAEW5IuPVL7+sK0CRtzos64hyY0xzTh1s3M5IIrafhS65wylGVPVVU69sBV9emY9ZwUBN2jWeY5Zoul+yx+p6/9AYAvBpy671TQXyFFhUrnzYiCpRet6nbF3Jx307tq2nN06F5rA7f2lN61EizIZt10ej/X/+9/1d0RtP6Ja9hE8QQUiSpyFBKCU5JaBm5i3AklUvx0TVc+a/pdvhsx+Vz1Zt8nDPpkVtW0/ofBF5qKmhlPbVv4NzM98t+5IxpETCoVkG7G5lLGoiBMQU1FAAD/+nIEFU0AAAIKKVtRhhKkPiVrVxmCGoj1OXNDFExxFqXuKDSUZgQAgACTkloh7C99kjPjJo9DOsPM3krak4NySH0NpqXpi1bVu/6PoPoJr3cyOehi3ccL1O7teyyj7sUDdLVy1C3ESAAApxyYE2LeYE5It0nQaejfWm8I9W1JwTgFLZ8FwuqZ1bVv0fvx+J2Ckl03QIzB1zT1/d/2JYNbQ6/ybv1rgBKAEUk4VjHD4eE3M66yV2IC8bQPwj6eN316YpBHG6d+I08f3wnbnegTmd1jdu9L6K+f/2pKoyUGZQpKG3TSUDImepYt1AVIQJAAiilGUaqpHGGrFqPh4XsDNJoGaLxkf+TM+5tNIZx2nvo3JzaeTrqz/oL6t/9Myo3t/bSrJGBkucmMUbiEO7jy2SFaYgpqKAAAAP/6cASUTwAAAiI/3FElE8w9ZStaGYUaiN07ZuYMrpEaLG9oMolfSAAUEIIpSnzQoJ3MXwzci/pUxvQy6iHNWJnqNA91fG6bIVddF/n6cK+CfT+r//L/u7K9wBt6d74u7WdF3/gU+vDyLIkAAAgAJNySwSzXNaRG+Fts2ExViVjIyhATbJxjFt1Pq+MbR7kLz6f0H6eNHlPk6s9d0uJpOO9LWzDr1rT/WAYAlNuSB7lRE1YXy1gz0iOR6Whi1pghy+DlJGSw/Gi2otGAOarj9R/JzduXjWnR2aXQ2ua+jdO/9PfGdf/+i81kFmhuzsk1SAfKKcRmueN1TWVgrds+QN1DMjIMah3lJrwb9+P06clYC6QfYfDahn13/7f/T+n//8yzqZUMBM9j6VlbCalH9fk3BtlZ5kxBTUX/+nIE9VEAAAIEKV7QYivMQemLEz1ldgj1aXlClE3xHSwuKJQVioAApCSbTeBNoNxRHMm9LUNR/Lz1gh2sL11Bm1Syk5eXZtAG1EXqM6q3amqfOdrj01+rTRRLfStAMidDnPYwioFQASrt5WlmahqQCNnKZGEJqsjmMSU0FfydXz739GO6QXbOUHeQNarYUmryKmhev9OXp///voft361m5m///vx1FP1AABUCUmnMhJh2wLa43cfMBaR2glyuaJNjE7VHNQ7cM0fvp4Xm4vmT9W/v30b/0Lwt7//2VnzEFqjGg1araeUrRT2ESDiEwisAEIgSVLdk5SOHuH9PSN/rnsa2wKG0xgylnxi4x9eNbX27//Xm4s2nTn603rfRtXSpuW8Itv/9dUeokXRnjbVtr7XHvKUfUlMQQP/6cARTDgAAAd5MXtCjEMRCSYuaGKJ1iQEHc0MYoVEfG6xM85WiQFGoNSW7+OhAbsE72IccegFxfD1p1AcMHwnL10QSlH/+btoI6df99PfTr/vsObJy//BPg2h5Ymt+TkOgEEVwCSincSYCGuakj2fgatHTh5qnwmF7AXcuidulLdu/8/N24np/T+nvp1/o+z9O3/eG1Bot7xMptWoTSBwjRH0LgABoEuW7exfKVwM4f9dQNkqVPUoE1CE9WeVdTb6Duuwm2R//j9CtOD6Nxnf9D83OTbr0Pc58T0xFxDU8XSBqXm+WDwjVl6uMABSbkatZKhV00WKN4eVkNtxUGNKgTNwxsPcBLnSxWzLThA9BrYnSR/1Nq+MVq9G6d+vP0bD9FeeXW6/1HR6BU6k9fP9El3+hMQU1FAD/+nIE6I8AAIIdL13oJigoQkTrqi0iZIiUj3rDBEkBBBIvZFMM0EGAQFYi5u3IT4q2NWG6FC8rVbIBcg7gO5d9W10A/Jy9OnRj6YxdWJkZXOk48gH/t5d5d+jLh8MYPn9b/lC6gxWfD/9KAB2hFSNtuqlF4UPL/DRWfUG2qslfaDIbVo676ifzv+z6f0Y/wTxIxg03D5AmlXby58u/5cPhjB8P6/ylQIVnw//SQwAAra9j2ZY9BYGRJ5IboBoIDmAOk+ewcQuer6n+dXqc7hDAfcUBB4IAgGD6OH6jmUTwgUAH/nBOD7l8/8wcDxQvd1jyAAAKl0nCqaGNho+RH5Cc+AbEBsEs5uf5BxF9+v9f/3r9dzCEAzUEDhQEAQG3Zd8pzj8owMHP/WD4ety/4ATZ+TTEFNRTMuOTcP/6cARKegAGYhoa3wgsGDBBg1vlBSMGCLhTeKGwYIEDjG7AOBgAAsqGFb7hkX3HBtAIszVQZBa55BXeeJiziVVPdVXxg3yIceKA8HEUUOIkUpAK7HUCEucGEugq79j3K2Modb2Hn9VxmsEAHxnQN6YFSrDi4GH2xKuqgzPUK4sy1hThVV+hQZeJM65B7AeDiKKDJEimYXZoK3KbFKCrkdlj3DH7N32Mf1XOrBOo49gBNmCY5odWgMPecaKgYzFDxMXJ07i0hMLDhrOJ0JHJUVF5Qxl3HnoEaOPstLjk32Vmk0w9aUBYhn9Rt6TbFpspZpCssFIIGELoSrMOS28v42Yl0iVZGL8bOvl6lM23KCPicYHwolQFF6kY9wVWVLo67LVqTffWhNPaUCXbzab2U2bNKYgpqKAAAAD/+nIEiqgACoHsLV2pgxLwQKH7xQUjCgkQgXKkjE2BIYPu2GEYQAEAjRkHQwif51FXPFQCNerPpWLJa32dfKrzNTNiXR+ZW8tnp0r2tqUFNFNa1sotnW6ZPff7tBN0qmint67gEJvYy0gHos5jctOS7usu/T2glIwDFQPDYo8PmmkajuGwXUPFC60T5Jibu1W6krYA48u+Zhmtaz3WaQ069X1+1DbyeuOqAuNkrVOutMw3++aA0wtUQeyuV0cl8XBTtG56yhTrUphkrHlYo8mw8NbLGEBOcS2mkBhVK7BiptbUEalJTSZe/3PskVsFRa3mQaBgZHKp/QwQsKHAASTMz62goLmYsIhZVoKvFoxFqSTVMKkBFbhyAZS87fAoPBt6FAFh4dXGPaxj3qQnz7BPAR5QEalMQU1FAP/6cASRkAAAAhERXMmGEaBA5AtgCYMKCOBjeaQEayEbEG4okIogQACAAVala3rygV6X7lA0fZTm9FyVCAU6BWtFyoIiEqNoGwM1RgKrS9VguhKOhgUuQW3MDf3WufFVVdFZUuSsV8j0wsIND8xFbJY1A5EzCxg3y9UxsIUwKw2q9S0lRWGwpU6gkvjAgsBioNrvSNIiUXSY2TgUzRa10NoZ3UOfX++tFGqAAABhBJpFubS1hgSnpnMgFOqeZyfLt3PWz8mGe+Sa8sNK1VrKA6VjRcmNFDd5aBVma3Z8VgWbMFmNobQ5EqAVHq1WN5UZkkAA0CLaccyJ0gHxgR6ZkbkZiSfW/Jj2GrkZsU1Chi2RG39VvpGKiwEBLmiqi5JtSUmkysMnRKdrkVh6HeuzRQ1Y39jdBVxFMQT/+nAEpWcAAIH+LV3owRRoQgJrMjzmYgjEr3kjCHExHJbtXBYIOIAAAIUCWo5NO8j00B88Nm1P3N6s5qDILaN955N92hcovOTn5IzPo9wx1QJAY9z3bgmhwlYjd2RZXe9abtlHEawBqrEMzUoNZPKlCpe1KRjRaZWKsF7+vWeV1yCwPmpSLhQ6HXk4GUq/Bhzq1GhGRW0+HUiHLGlJsOnItF1f/u0Us7MAA0GmGrXyfAxC8aa0vfE6+1104SDaPxSbsZvZVQb2v5NvL9pWlPKKDrWCAgYpoF93bcrW9ci9bgwBwg2JVEGKjdiqM/tQIAlSSWMpjmRiueGyqhZsURNK0qTX7lSamugSpACDqKtdOpvUmVti/s5Gysv9Qd3OOeJ0E1TtKlFrrrKKHs9bm/c/sVUpRilMQU1F//pyBB1RAAgCBDXbUewpsEFCa1ckRnQIoCt05IxGURWMbyQwig7AAQASI7aIc/RqUThInjnjdlxZ2rlcxa30dkZ4W6i/fc2Pb+/97ohG3apk26fp7tvYaIGy4aVUO5Gy3vIstxQywAQALcttTVBOioHGyah+KzI3zRPgdWXuxzIwX3Sk6J3geWlptjDS3ineaFc1Es4seRsJrWq9LH1KbsEK/kc7/Fe8oSTckqaHqjg0cMLQBmNkHaKpnRgRjg7fQFnrHLZOwkLiU2esIvUqmKME7ybhDKSKbSrOt+HXvIumrf8HniqUsKix0hUyAgeRpluSAxEDUTUPYVjBuhCxVNSFg6Cn3SpsCYp6z0607VPUVKExoMura8mZEMk/K0Mt+LsvdfeLH4ucmGiZ7djEmmkUpiCmooAAAAD/+nAE5soADAITGNqYLxBgQuOrEj0iaAjI1WpnjEuBFpRwKGCKHgipLtq6sIk30SjGeiNex1/OGvFRNSoj7IEESOZDfXjG8u7pUsUF2XnVutsmWnVmn3KrWcA+qoUTee8XZptr+tymsQA9+ZiGctBYpI7mgxJDlVqPmOlQjEvmCu+4qHvJmAoDnO60Z4J2pwFXe9AdxGpQsRGhIVJFatesygJFmTAt+d79HpoCblv/9e3GKTEd8y3hBxhSTHG4nrYagPhDzNZMiLlCA9E7vvl5rl2dk69mY1P0fR+MKGEckRxd+OxdqGrEplaSJ6PprFlZLiku0nG03XUcgruhGr3tvrewJ9kiAiAPp5JyIiBlf79yUv9SJZHZwZFirO8Y+dnPGBVpJ+RnD2OrsvEVNd6xVjWPhw+ityYA//pyBG6TAAAB6xdYEekS0ELGW3ckYmkImFtzRJhE0RmUbaiTCJoQbwxuCFERASQGWZZpJk7jBEhOO2eFvSPLQtHDSze4qJJumRim7oORQOyU3dxVrcsNeuAnikUUcHIyCe2xICSU5LN/lHA5aaBPoCrA0EKicMteHJzp5Ab52TbrrnM9Ss7/e57Ud7Lv09OswRSTKoFXjYpV9elLmkltGvhQ/6N6tEAAoBluS3JP6BwiDD5YaypXQbH1o99S0zopmxcvVALyz3LX0Sxx6lsfxPKlFlBvRJlWsFzCqH8WfWtT+8miHUmr2jiLEwCAACS25KtZ0YFrFmVhZQ1psuHz0HU1HEWa9XbO2p9H2//VbEe2ooiiIzlmTiqYvpqgaWFk8lZ0NaxzVuqShD8oG3ihoNoTEFNRTMuOTcD/+nAEniIAAAIdMtq4LChYQmZbVzBFeohco2zGFEyxBZpsjMeU2AChsqOS3CIVQXHAtImifWIpLJFqJY1hg9X1FXCQoqOqlaIljQp3Qc1Gf/zJZrX4onNVF/IrRyBNGK8y8q3RX/Ns+39UCASW3JMfL5cQObgLV1TaK4szkejztHbos7IKq3KyRzP9H176IhJFS+UEM25KaeTjnFnKT4D6KJeW+prKWq+qq5frgAAqV7NR84kh0hK7nzUT8KR9V1BEdUzGgYxRWVsnE/gh371bVD7MsnBnclsFaZWtXmgOEgWVCDN7CVLdrMFtaUqd3AFK3/7GVQUGyS/mXVKH44i7q2StvnIo/tO40cQ2z/gor2odP2+av85VoqJtXnshiyiIuP3aHqMKjKmgWu5+r/3bkxBTUUzLjk3A//pyBBmLAAACBTNaUeMrJEMGa7oMwkKIbNVlRLykwRMarEzzCdgAAIAAU3JM/peIQA/nRxwUpSr1XQ2UKXMTHim4vCJSG76aO2+nv/X+vRtaquvreIvI0s8RpEDRHKPH26bqSPT8vAgEoSks39PDDRXbvbE+7YLVtW5vFb9u/fMZav3Nq6U/hOfk/lfB5H+XsMCslEcWUCgBQNExcRlgvI1sEkVNVrBmzG4AAAAGrv+rVA44CmVDRasNa6nSR4oVcEpdHEGaLglTgMZqPp37bnbK7KrpSlOvQq179G1u40ZWPelPYIOW6q+v+v3LABdv/zRdDuWUW2rsvV2JkSytPOrAodYvPCPwwvGAwTC5JPzbvuuTn5ep8nk1fj2Xpanu8KIbFyfXER2cLKvLKyUr/L+5MQU1FMy45Nz/+nAEHAoAAAIVV9tRaRFEQSZ7AzzCdgjpQWz0kQARGywwtoYgBoAAQBCSduzkK0D4fEIT5rttt63lDshX3HocX1tRte/Tg+C99PbXoT//ty8nX/spW2Lq3Im16f/2p+reVkHOC4Y7lVAANzbZmSRVPztT5dzLqlWdpXkF4Zr5pP/EunqSmBwfAPMdJC6vsO8Fy9Oj6Onwadu38/LwF5aE/uuejWY992r/rgKCU5LtCPWYMBcH0FsU8qxaTqLq1mWh0vMzT9Ozdqs/f8ypQ+pdz9U+l8FoJHoR17/np0m68vf36vS6EtYQ5Tn1i5VbuvQMgJTKrajssu+J4crzcLw6cOUMfW9c5OZsyaOt27XOm4+6emfUvXrZPbUFqgqgY/d/369+vbv99XPmREKsQTa521U+JaV6aKkw//pyBDGSAAACAU5dBhigAD1py6DDFAAIdCuBvBEAARoQcD+GIADgBgTgTmhCe3SIu0P19GWyFZtn2/7czBxk//Fx5Udv/d8XEgsSQgn/2/xcc9KCf//7eOd0RRUgG//5QVUMFQfKOS4QyfkIP9nGgqYAMZSKalEyk+3I9P2XMwcZP/zj1R2//xcSCxiEE///zjnpn///b2vRRUgG//5QVjBUDqqACT2krSTSVzjUN0MRDeBDDQaTFZcYklmwZSq10WYNIC7/CAw2AzsBVArFhU+JAo0sXJFGt0amMvRJ/c1VgVVyRCZ2QICDATmrsaTSTC9zdDLsKceu4YT1SZlrRtLq2yO4YrvRJbZVCERpAXf4wY8BnYCqCdq2iQKNLLQprdGpjL8n+3YFVckQu2JiCmopmXHJuAAAAAD/+nAEA+0AAAIYTF/oQRRgQamLmRQijAhE2WqklE0BB5rt6JOI4OAVEo2rUmm4ry1RG0OVqcHw1DzlMRTTCFxBiuq+6xOYNF/fCOjrtoV6X5afdprV91Trr01VSrNhTD/SDS82eYAlb5RAwADQAC6hEeIhISXGKopNxvRqfyRsTMDCsg/19pCiaMGi/vhHR120K6pfiaV0dpn1919demqyrNhTD/SbXvYxP1QAQC/YDjggIoH/0zQ02hVwrSo6bQIXKztr/dzOtbdHisq5dkrX6iWmczpoj/xVV7a2ZJ+TSoqJeDTlUJl1ncRUdS8AgoEBJBKuURGYUGoB+cLkNkdBEdUSNTm2V2lDo/8ztydsVl5dta/iWmc3oj/xX4trM63vYdO6jXc/Euo8x2tjbFJiCmopmXHJuAAA//pyBP3bAAwCC0DYAekTsD+GqwA8wnYI7NdiR4ywgRogLeg0iCzNMsJDCfRHH6cKrCHwNoJp+Hc6kEz1IenMN5cQ8VqJROIoh27fVakshpq+3gi0m+hnurYMvvp/+Gf6M9v/3f7+ghnD8uoeDCKg4Sn1GzAJGcFlty8BWOwdhtI96B/o0p9Ef5mRI2dutStZ2mvVbfporp6Ge68G7/ln/GS2n//5PxgVNf5mou41TDYYjyFDqqU7WrqT0deJaMwbBuI7uK6NvoImLGlxratU+hF/VFv1u/p05fCPloo6cQd2eYYTQd03KG1ybvJGI7AABQ0mynKJpcOL8lW8KZGUcg2D2VKvUxtvBM1+FfVtfbXRK06k1V3dH6lpv18K39X7cmwV9XFEuPHgCx9VVWsPUJbDJm+lMQU1FAD/+nAEpuMABAIQQNkxiSlwQmgLAjzFaAi8bVgHsLCBFCvu9GCK9IAAV/+RJC8GJJI4HbHi3Gt6ot6f6k+3ZLExhNjtq23Hvo+nZcj0W6+XtzlEkRD12pu/KGV+vk/jH0IrvI6fxnu/khVmqkzAnBQmaTMkp4Wq1Q3p1NJP0VrYFNuZh5QXlwmu47Lwk+R8nZsQ1f+Wlrto1z2b/XUd/R/17jH/ej3v0ZHo/dTDUGiKEUx9dqYR+ONlHNZMMPubfgPxvC6uNYimKkHl6x0lXY6aKCNQOooxNzjTCiMTpXGMe97QlZU3YTremn9tvf/1s/r5ABG7ajttu5kM6oF4r62vbLBu1Z3v/53Y/55Pa1w88r/Cspmj6UXKFQ76uRC4VkViyJYmWL9fnJ26PVB/u70ofbUE3drTEFNA//pyBLoAAAyCEzZaOCYQSENGyzo8wmgIZNlmZLBFAQ6Oa8j0jZCAAQiSKl2OyoswoJFSsjD1ah+y1DBYO1lvT10doI66A4Rztfq7NVVIup7aW0Nzpd0F2zZGn9vSlJai+vSKdx5rMZLgAGiEm3XLje25wSRUvln0+GA4uef2zPa2zMx4CvWqWql+h2ZPm04/q77Mj7edpN/VXU6K8w8YhrSCf/r5VX6u5/pfWEFDLtj1RLzvYMcGeq/vj71R9TK5tXDVUSujZunfi35vdOU76oeX6//yN4a/I4AawbqfxZA1KwTB+Kuk1ZYN2LTFFlQB+/ucxcVhViejqmgNcSOGsw82/0T/lRMoTGVAReEdKVVPP8CbNi8d1myZwwXsMgmPoQTQKIUlaxylujEav+1H7e/9CYgpqKAAAAD/+nAExWEAAAIXHNi56RJQQia7aiRieIhEy2JmDEzBDJlriPSJoICAiKSSZ22lvn1HRBESgJIeOtd8Oqgy7Zt8T/4Mmc67vpoVsRqImQ8VC/qI2lAu8KulInSpZXQue5P1bYo9H9OTf9KBBQBJttyXsXuO4JmcfryzU4GMgL6s9vq83pdOWNKF5F2Pqd62lqypomi95SPb01dXpEr7vs/PFzqgMaD7t98fAQ4IOS23cqh41oaEWAGJy8x1onoXqCKO6cKJmhr1ffj878mE0J1dczTo+9jTUv+XBnRMhei1VanMn7484vQEH9VL/8iA3/y2ZywmIrRIhjICqoXMhgDU7iXzO72QYoXXY+6DL0qatvdWwB8O9VfmyF3Z6dWzvyu/Uj9ng3WXTHXt2e72/o/7ExBTUUzLjk3A//pyBPLzAAACGw9YmY8xID6Gu3okIkSIxNVw4JhBsRQOsDQzCVYAOW3beQgXi3JAm+EPOfQZO0s7Bt5llrwZxA9TqbJyyTnpC8VVBSZUAADNpUdYbsPIb7Ynr3rTMTxZynXXRr6ztutVUAAChFxyW2z2zxQDkTqDiDb051H607dDZP7cK/6I66MZaqX+i/vy1r490rfrP9Pw3DiRHJCMkScjKiPf5Ko9Q8IpJJSjHVDAQcs54LZ8vAtHV0ehBPMI1ZYf+Zsg/bVrs7GnepK3v2z0d8EqMCPcMFyx0S3OCJzu+uEM8GIt+0C0uydd7IQGU42opI5NjaFJFc/BaH1fkfnV0erCOZngj3P/Nx3lJ2TSEoKUqv1PFGls4pyHUWwGt18w1jlUDArvpTL0BlJsnLOukkxBTUUAAAD/+nAE5tUADAINHViZjzkwPcDrygQjAYjMd2BjPOUBIqttnGSIigA5Nv85MEqJps4e5hqwHFkbZp2EPLlXn7DlDB03TtzG/HxmVmYUilrpuSm8WcDD0noXuF2o1UU0JnyK6xHQmlf/ZgAKxKTbbpAsrEPSEDaAJ2PGnUEJKy2+ck/qOFJJwq8tFn58aLHwSFwyfEo2L2WUU0OuXQMXX+trmNgNlOKgAyXf1oXti5DBH8vFzlW0UjtzzYW2UrUUVHT2Q0Tn5R+1DW76kNlyDXINIrSoN8DipYsSii0WPuAx5TmauyaaRczVUlf/qgOEVJLc26IuXmR6iot5xOGzuoxCNKvKrwS69vfYRzdqFDkqfK5VkF7LRj/sLTSlNfTzJdrgl0ZV7Zt20srskj6P/0jlXWN7somIKaig//pwBCS2AAAB/jRXmeYTQEHJ290MI72I8Yt9oZhOeRQoL2gwiIYABS2jxz3JaebCryQikN44nbcd5iOCE+wnGwp/GqcFE6fJ47wTfiuvn5enXVerYR8yPN/kliYs+asskLLP+uqpAAkhxFJNtzZ9HlVNobnw+CfcA/Moo37jyX4ubyPh5dcLs+B/kv4JQ8ehfyney5f/y94alIQJlMoumyomFY6XFuG2AACZGW205PnXOXGxsifp5ofWYjNSstK0Fafqj521Fc3Xp25+n//slH16Cf82t0NzPu31va/NoJ5fye1wTHszd6IRUVUcGSTgCf0km3JqxOOxoa1xI8pqATY11EskI3fRteXq24/frzLs+vBdL0b6oMiI+pOXt0oyzzLV+nXy/xRpC8Sj0AQhI8YAsu5MQU1FAP/6cgRWwAAAAiU1XtBjEPxDBTw9BOV3iJ0ldUMESnELFK2cFIgu1Ab8xSOSZ2occ7ClmzVhHQXGOUH4/MAOp2sXk68G2q6pb0F8zzlbX/6jUcWsKPJmEunh0odWzp3HgGedD5q7lYOkV0SVBIRUtldlt3GKbMyUeTtZXQqtCdDwB6INmxMFxPp11Gvqmvl6dOI4TqsoSOhODkovDMIxJ1WIAzgM1ZNLkRM5U27uXgAHRJabct5xay3OvjuzVJ1D0cdKl43K2r6fy8J0NhXz9OTqlbLR2r7vgibkdUPV0r0XTq3Zv91ETQaAYuTLlXX+Gk2eoAoJJaTlUI9VXGlba3UoUbBPmtJqCdIxMLzMk/ZsnRdeqaLkTUS97wyYAshJH2Hg9N0xWyL/wI0wCJdofH3q6JLo+xMQU1FA//pwBMtRAAACFTbd1QygDEPFKyqnlACIATV4GPKAARCmrwMeUABEBuSi225co7DbD1JoPMWzglDmTJxV3Ur7my/3yFH/3yE14+5zLtavb50EVWaeKlCkrSss4rX8uNLmVLGqbXRjVf9SAAYAFOOQYdVJCqERIbxxzLMGMuH/1OQPmHRqMOk2Jr6dmykzZBp+bf21K8es4SFxwSNhcYGr1Onp2c4cnmDN/to1NRh5JpsNyFnxiSQd4xRLRasqjvznT75Ji/+k//P0d2Hmf/IRmxUwDlVH/5/8SdWZyGHf//JkvvHBNv//WsewsNL+J54BuN0fWySL+/Sh1JnDjKGdmibp7ORimFi/56XI/8+6OZHMb+xCM2KmAcqo//P/iTqzOQzf//JtfeOCbf/+ta2KWXTEFNRTMuOTcP/6cgSKUAAAAichXjc8QABERCyt4YwBh7ghfaeIwqEODq3ViAjYAFSqKwzYCsVW3s1PBbYvs+Q+LgpWv1Q2bN7o9Xpmf67XoFlgyE2uTeBBCG4CoDtu/JNVEtu+7gtvS8f1oXlVjOoK0HouAmk07HG0iSuCPUp1SJqGLpLykSsxzOMdzND8+ex/8f/PgWqMi4s5N4EEIbgKgOzrFvyTVRLbvu4m6Xj/QvKrd1BWg9WAAJDJFGkQAskadUEPKlTlmHg0edhJjwhk1xmdL/efQ0FJWSa1TjrLhWddOMbM29X50XWJaj2rcldJjEXG0BB4GPjrFe1Ixawp6jznmhrXOld7S02IhjWO/0/nXj6Xn0NEkrRapx0hcKzrpxzYCtZq6s6LrEtR4fFCtyVqSYZEXG0JiCmopmXHJuAA//pwBLgSAAACHTXk6MEovEKmy0BgYnAIqCtrLDHhAQ0a7WWQiegRNuq3W2NpKOWmGkxKjc4CAvjnayI2UyujFHtX8rOqoRK+yuTNy0/f05Wl6LX1i+ji2pmnW5ovi3uUWEusNFeBCUwRQIQiL7YZDKIKm3apal7DsF2RAq1ejiPZrNWgVKv/96tRX9lclTctP3m05SS8i19Yvu1sxR2FKyRVba2CmiwKhrW7+oAKADvv8QzBekgMWjLSoc091++YgV88THjUiphQscUDYspug7GdTB08FGMVaRkNR4oWQTqrqeeTP7Uey8NEu5v2gH18iTAIBwfv/EJ4HCuOTla07n1N67u2XfnbyJhChM1kzIqZ9fcIdAx//99O/rQU6pX0Nk7KK+p702/UrS86S86/2u6lsyJNMQU1FP/6cgT0WAAAgh5AYejFEUxCiAudPCV7CLjXhaSUZfEIIK6o9ZQ0BZaZdbkkibgKPerFyGuqREBPLkQltVddxJH98r9n2bttg7SZilRLJdWF6r6E7+o//7Zle4Lzq2om4Nd67eWM6M29LLAAAEDA3dInKFSG/dmteBqN4/zB5ST/DcYKzaGBpXV2QYKKMLI32fWyt1b0d3Mbzf7+r/09tFfGd/LyLga+t3UZo4FAKKZkbkrjbgwiuts99SKFADuRzSd8ksEdOtmuXwv2KIf5PxStI+5xO/57hVe6fKUk7l9CV1MWcS9AePL67zOIfrLee3eSSlbWWWYNgyoWqbXP7t0LgJV0Jpq+iJJdGK9ikdBKqMmNDOXt++ibtR2VBDp7bVpqTp/TV1exx3I4u13U/TUzmbeVTEFNRQAA//pwBL4BAAAB5w/WgxlIYD5jqxJgwnQIxNWBooRR8SgTbWjHiYA06Kr15sneZeDK2PsTonMlEIqxsFzNfgRaY1307VCaHExgFbTpBc0oqNL9ND4EcSbR5Crla1qr2f1a//4qEXfg0SbsRZfesvvNXZTOc3BVkEXm5xuKiWhoJZ0iOjVEt6d2pasb34ukgeapYpSUvd5iydoqrG/vopV9HUSABKQcTjjbbiiU1LpLaIglsRiaCSzMRFYIQZln3zA6Tm8j0ch5adiBw+QpJOwIwhbTCgyMSeH3te+RSWKY7uXChbfszzi9wAMACF26/kECV0KLNQEkxa+00CEjOFQd422rETUFzsia9/EP+nS+iCzKsSHC4cCRNZJE6KuGKda9ptP9gTLLh0UXsbPM138bSJEpiCmooAAAAP/6cgQKYAAMAhkm2RmPEwBAomsjPCJ0CORnYGe8YYEOGS2ol4mEQccdtBVSOD19PPsmVxZcrBH3kTDKRyKy5BTtIDL+rVS6XSxvuktNFZBUWcRWG3LczcOiL3WmVzn1LPXrJpbs+EnetASLktpdUYhS/d8umNajO79dTAC9UNExDFVn3LeDARr7ljqVGPpUgDLDl62SIvF0Gd6TprJSs/7op5Ji/r5DZke4GNOSQoFwPwgzOkT4qw6VUKM8SIJpmewwpCKJhBJgmFroDU+fgraVHCcBu0jkyF100F3DHyUCLcvTpS9MjsZW4/dbZs05D9AQAaItuR3jEkQqakofJ7feSZJJi6Hayrpo7MjhW/TV2dejK/ej/RsuVquvs1/THCCFurk1+L/4FtBWaylnSCpuKI0d6YgpqKAA//pwBEI8AAACChzZOS8TAEEDu4kZIhuI5KFtRYR2oRoI7ViGGKYAgDbt2BpIkAg+qFmMskTYImAaisd5UqMyg8Eiy2N+rSu9F4ggpiX3ohy4VASrQMydalxSvckWQWWx7PF2C+S/SkEUKVtmujqG2n3PVEnxOAmx5dFBiMHmqhLG367pmI1CD4I3rlLFOPRMZaMlIx7imufoYmbJ06YzXWec+l5L/WakgAQKSll13aVZsvS9XqsysqADtf3dytyTviEGH9gs+kRnmByKpLamR9Ew1+iR4op4CMKUdFDBCIBdqOdTQj9FdbgjojV6mf1AHVRW1MkGbfTFrCLS5T0NX2u52bingVHbJxJb0BgSua5so/0dtzyJcbGyDiCjpeJiiouvLp+upzQgJwSNWuGGEahKi9/RKpiCmv/6cgRmrQAIAg8OWLkvEGBBocszMeJhCKj/XmZgrAEKE28ogInuAEAnJd/gzIdDMeiny76DklVJ3gfludlAWsJoJoTFeeW4dSzhy8uMjg88yOY3Uo01lzLOcdPqfoihWmHljOv/7NcaC2XLcwbvBA9iczs0O9vZaB8e7sD42JoJsAvYJwyONiJnReuOkrvcpiJ5bCizIVuFXxI96xWoO1IrHAHOUEff4r7wAk5v16pOJbxkEvL0MkJ2guIY2iM1LEtFKAx2E2OpNtuXXtazP27UZtsm3Z6v/13qupq/+vdYhreJ6nAdb0Gel23TZ/akJRKS1G05YHb3A0+ZaeLB+axdbzlWBol8+rodWyZhnRrltGgEMFWlOy5+Jb/blEssXVMD0AVRBy1FicMLpsXGirm6ExBTUUzLjk3A//pwBIE/AAgB8QlaOShgpEJFa0Il4kWIlKVkZLxMEReUr2iAjfYAgKSclpc2uIMB5qohr3QlwAfkBj1ylrDqZ69BsR1DnJn9XPFzAllr8TPYVaHw5WpL8aWt6ttIuB2TlLOn9QKy3NcQkD6hGOH1T+MNxzcNtWyanOmVd++UXQ7VvbPq22itLOZKiSwxoEiBtCLwqt5NpPJsxTbrpcGQtOZV2fugbaCSm5bBdUCWQqibkWI1rgIQkWk0jnwfE4KqHuTtVtF3Tp/XTq9CbJBtVBjW9q6jzW8xrWVk42TWgqqBnhws5Jxkdce/74ypWKcccm0kk6o6GDWyJDaVvPhyolMHWWXFRG2plh563KKUE+DBZRg8h6zTWnFMQ9nawekfawArakxVInXpdYp6AIyrhBSYgpqKZlxybv/6cgQRlwAAAgUaWRGQEww/xouqGOIpiPVDdUQEezkjk+90gwoGBQGyovbCKg8Llf+ba88uCiVCrgGFeo+crEBGV922kfjmBMyjvQkg8/ft8lOr/1LHlt71Lr1Bgg1QAW1k8pSf+LhCAsS2m3K5ch08SyHaIDl2y6vV2nojZ9eVZlqP1T2771a6Mk/fZpe/sDagemEntyF63qpj9ADdErTXtLsYkZ1KMpc0mWnaRyEFMtSpnlkAv68Q/Q6Ea8mTSCIHf3+/jZ5j4N1vL8suDJyMpOfea3h8pftze7NTeBS+/tlo7EHu48qFbb39yd0hqNCZpuOOXDYg1nLWLLrgcD37dwodHUrnIfEZ2ZD4PXorH4t6NvVEpaIiNQTKLJvNrcLSV2y9cGkn1vKWXTrAqLrk6G1ngRuWmIKa//pwBG2gAAQCCTJXmeYTpENGu+0kolOIbTdm1PEAORmmbSaeUAYAkpuC6mXQ/1SXdlRqxIofFZDvBDOSw3BOxS2oM3MuQ9n/Js+NrLbbtpq1HweGfTTttxmnI4ejtk+fVRptu2t5ZyESaDScjbkouNNuw7HLHpRhQj3Np+hhYY6qB2O2bK9RDM8tsnbJqSj59H//8qo2otQfc48vfYmcuIXoIrBMBLYg/mlBAorzf5T4cGXD/VnPeV0fgmMaKri6IPc+ZmmZ+XqzdFLVaF3/ftQ270f/n651k/9tO6//9s2opUVjo8ATNoc/8hU/6BABYWJchO25QPKVZ3WViudsgwGborXCj51MJSPZ57Nn06rVBrF30L9WzavV9T63Xn3WbV7+2mj/T9e37nQRU8e4JFVNfuSmIKaigP/6cgRdQwAAAhtYYR4IoAJCySwjwxQACLghct2BAAD8hDJ3jCAGEAoFAoFAoRtAN9gAl44CPvI/7m//b6uR2/+Lggm5Btf/gcVAMHOICml//hwUOxGUTF/t//qjTzoKO3y///7ijsCAgeGBQKBQKBQ2V4J6/YEhuyyK95H/dP/2+rkdv/nBDuQbX/4HFQDFziAppf/4cFDsRlExf7f/6o086ChrK/+BHFAgGA+ASgAAlK8yLauX5qWH6u5YZdu7hxIiDN6wm6HVi1ZCxwxScqRFBg4nW/LHjYTZCnQ5ibFMe9Q+35KbfUYr0Ny0OsyzHKTSAS3FZJW0iSu4+Z/02qqrdGRZesFUQ6PFonIWOGKTlSIoMHE635Y8kJsinuZsVe9Q+35K/UY9DctDp7LCjlJpTEFNRTMuOTcA//pwBO14AAACDTZjaGEcfELGu809ggQInNeFoxxI8RcbLrTzCGwAppKWRtEkhQSKt2JivxgfqQYp8fezOvYliU9F+q7kOX6yZiZ/yGRNykQODhdRKSCV5k8bVeLi4tyrv75IDtkurNgAAgOytkppNeJqDJK3jVXKXmArKwJq7F1c92b90bTXZP2t3of/6OpnX+h2Y1FZgZFjRV0As0/F5HUGvoemtIXrd2UgJtEWxxpEAoHCkm3FS8naTuoXDNSKgTdcKt2orM7aXXb/mzHZaJV3/q2qG9F/w2j35HLa5Nh6DJs9PPUDoifLJKjPfIpAASBkicSIAQ+w6jybl5OeMnmaLx0hxL2kIbfLk08bRHfb+jGzOy0Srv+ys0qG9Fp+Ba3N04o/PZJfUdy2Iwkbzzw0zMpqTEFNRf/6cgSuPQAAAhU1XmnhKLhDxsxdJGIJiKzZXC3goYEULCyNpghoAAgAf220jkoMsiGuLNCM1jGAjcRql5NWGsjmPoaqt2kvfVsyqR/K6fvZ6lt/R9dWGnX7FCLanX30KNm7+u7qnendUA26TNbbY23BsxSgVctUpJ4WwZjKndGBWDvQz1E66Nv23UrOiZXTy332tv+lUdWBiVe3URZJJH9ccmw2bQ/lXeqjsu1G+YBTmEA7ER4zqU19nFuPO/qq66xCoXOQWEhZxh7BO7Kbkd9cQvd1H3vlvPO+R/tlbelt3t07eNFF76IxoE/a+6h/2v5oBttSQKW0VXtWde2UW3I8fEpwD/IsFZNgf7NV/kat1dKn9N16UTtr3tK1/3+2o39X5dUW4A9sz/O0z69pG/7T/3Gku8YpMQU0//pwBAm6AAACEjXdaYY4SELpvH0EwkWIqWF7Q4RVcQYHK4mGPGAACsCux62W3DFQpUt0TjwqonUHVp5xtHxUtT33r23dl5B9/pquboy3oq10Q7t/unsEtkVrM89R9g9ETIl9aLbXf2uCsmkstl1slqpAKCYEbJboNmH72Dazxap7ZteTV/KlUeTsTdXZ6WsLazW/Tvje9E/sThv2+up7uTKaJpQlQRYqly/JAnBMi4mkmIwToTorl1FjTVGEbN75HuYq/rtkHITIeaoZdFeQpM8Y0FhBmRICN0oP1qyu0pTLfYv/6Z731ms/+l/1BPTzICs0CZBK2dDwz+x367QxgeEDhzIZr2dT7w7/ZbgyfEzlwGt4hhgSElrCU8FGCU25UXalj6mLqfoPSH/zXatzv6w6mIKaigAAAP/6cATmPAAAAgA2X+hmEwxDJss6MeJhCJSxb0SIb6EarG4kkInGAQSKkUTjablEGhPexfjCysu7wgrfQ8GEr7Z9H330fNRp0XeZWorV0Kf22p1+L9kBGMvaE37JVTUXb1xvDPZFKwAgAICKScIIDEYEHuS2vAn7GpI3CvQNkfH0R1EPX8+j77tvmo/ay9v7sxqmv62Q87XFdlyab/dUWfaJV+VgUjkaffWAIhyMtttwfZaGbXzKo/7XZApbdMFwmZljadtsE1ZpXS+D0RrM6rIMcuVzbBnP1xQqxzG4NRUu5ZV/0EpFrBhjf9Ib06grQSlmSzqbB1TI4tI/+ngHU/CqaBkma40idtNG01Wg+vXfVdnRtStQ/u16qT+I/m6t0//+lq0V0Kt3onoW6W9R0FdjDzKDKYgpqKD/+nIE2gUAAIIUGl5QxRpMQQO7Az3iDAjIrXujBFCxFKataMSIbBVgrabTScjQ4N5q6cZscER8T0a4CeDVmVLS8vvmXRDWXqnMpIaHh4Moc+ECGP63TY7a+roUgkFlHQSfqPDnSQGmuMACcksPwkb0R5zyemoDe+xGbGsM+Ka6w+GGxOquiXXtl7baJjyCqCwMqOiKErTDtTQytoI6zbr9bfqsPLrIM3/S8gEkmEouONO1dI9q22V+UYF6ufi5LfeL/pqJ1ujVNnZL5dDuj/M6sCHC5ihsXACDq8yFHu/6yqg28Wo0rRTE4+Hj6oUHRrwSSctl3Ol9Qpawt945ffgELn2kejY+p1qlX2zPVs+9lN9H1LSqOuyauEV9FX0uVq7zr1+1vr+/efqvqWCAaFJvv2v0JEyYgpqKAP/6cATYpQAMAh8cWRmJKNA/5krzPKJyCHhfYGeIrmEMpnB0YRSuQU2//bdXjLjSFyagbqxGD4Ai5NYQoMHYY1HZyafnao7TiizucVFhQKRI1g9C150YbS5l+dU/j3IOIrKPY0k09f+sl9RATltsNDXYHpVPH8SGvKWBBGm/DKRLFIEiUDMAbHM6ZPweraatVsuiabtdNO81un2YgR5G8ZW3+z+uSYeV0L5IAAtuVnZlyPDL9WwV01mVfl1QoEqwrFhbOGEzgd7yICrGPR+OlaLldqnum9ZsmNDi8y3SebNsrEdKfZvbHMBL6k5vRqRxiIkrkkkl1VIYHpWSPlctqgbOjYHad8qs/NjWy5G10/XvIub+n/6UP+Op7027eV//t6q90QcSOjWIgVK48ay6pGxCYgpqKZlxybj/+nIELBgAAAIYLFWTAywgQiR66mHiHAjRPW9EhFExGqbu9GKIngA/xy6zRy/b3sbbLCp+uixRYIIGLi76r8/tmMXHMW6EeJDIEDIU+L2IGYl20zbcj3f/p8giy2MtsuOWVql9hz+ylhEAACAAFLKHSgiyHPdtZgvYS+oNOQkBTAa1FD+crmkWZArsU1CaPn2Gzdt9P2egq+1UvAcnwAoYpNSlNUtu36rd6g8AkCpJSTTicFUGp63dB/eB2kl7+QPR8GSOJGueSz/BoyadHsd7PYlfKiLp+d0RrGYh29FtR6E8n6P/+5mc4IUyA9+d1IMfWQwSAYki225m3SBom55cKg/BOzolnxj+vXP1/BpTtRHsq7mb+uF3b+lp+9/roqUt71/9OhlOM9x1BEjrUiLAw4Yw3EofXPKTAP/6cATeVQAMAgUcVpsPEyBBxUudGEN/iHSFXGfgrAEejSrNgZYYABb1wcGtSBx5HqLQfjpoWrhKRJAfukZ6FFiuXWGCYI71ZUGWbQfJo+EstvFhxMVax4fX9DCSBZNt2T7v/HpLnlAUIgFpEtNN3CHAdGMu4Q/AsIMrBSxiQzWKaDntp3yfgsI+fO+Z7Ptfjj2po9j2/QB2khZq5HTfTD6zf8udMaLh/cQG7tgslrkXB8fCuZoUryeY4hqjS78twGDHQqQ+2D1FKq2uo+of0fOSn4gbKJlmDbYUhpNqHKQm5SVJJauy9dUitxTtAALsokjFIaMCMHggqtATxPHlZZ0ngmvADt7yhqcuBzo2zejKGWwD+htQfEJVzMo9AoxVdqzdC5IgZCRpKQmfYWY3U/318u5CYgpqKAD/+nIE0b8AAAIkKlcdYKAAQombmqMUAYiUH31YYYAZGJOu6wwwAiCndsKkDMgSrsulLJmRaltWVjpVcoAMaMjBEdg5zhjogOqKy30qNeJiG7Ys87V5tGr0z6CtjdNuxzezOCknzu5egq5+sJUK0U1G4LyBZ6UIvo78vEdXjWx9RE+PPJa/zj6imbQW115tG+jeoq0yWKdui20dddNaaYosnd6/+9V5MCsKOsJqmwEAKZv21bdt//+APt6+P489ShRFLOWzevtanuFOfilcVHCvwugRiXfT6zcL2TfGt3+nnwH+s0r/bae5/7jd+V6bf8uowAABKrUm5LdtttgAaHfyGtCLU0btT1z4TmxmTTP7yXvkRepEmqdkf4R35pLIO4deJBATD4cFVjpRM6RYbMzSSHeiWpAvQGExBP/6cARX7wAAAh9IW5YkQABDqQtyxIgACHgDibwhgAESgnC3hjAAAAJM88MCVYlCys4MRhoKE5omQhIlyjlMzmZxatqnTuvr57/////q/q5lf/syNjH/28hP9XKw6wqz6hgY+wHwYMDu254AApnnjQMoBKHkzUYNSZWQ2oofKwedTM7iUFnJr+in/89/////1formV/+zWxj/7eQn+rlYdYVZ9QwMfYD4MGFcXuejAAREkEFlNyhTh8MCcyoLiJzzTYxYSL9CmBZpZwM3tJKatiqhZJkNpSs0pRyxr49XtS7Z2Koexra3u6UB9r7hOkLawQgABJABAIKbkAKLiJQ5lIeLGHmmwWCoSA6cqOYFjqXL2klNWzULJAQbSmhSjljXx6vaBnJs4qKUD2NbX9KA+19xNJ/WCCYgpr/+nIEgbEAAAIiFObgYRqMOsNbwDzDPAjsOXpGDEMBKQfwpJGInIG5HI390zkmgKMwoKOwnClTsk39lR1SsplmLDZJKaH8qDSmBMAEnN2jZih1Wbt2sBlbSDRRb0xdju78bsJEl31mSBGIdaMMWB9OtO+ilQ68t5147OnkijYJ6bq9898iZTaUY/rQIlMCZQk5uSXMNNO8nbnWGl2N+vVsT8bFdG9AVa+EpANgo2eM7TFavSYtxj7CR04LgMyVQAo26AFmROlrURG/GJcPvce3azp5zCVa5kfW9DggsO3i0IwGHntFhe5yXpSKrVFk0+0yvuKgTE+DKxHZiLa/NmYrhpwYPg7GhgK0IoOFFnwuucUTigcFDSaZbRdFywAGTC7UGz8nABSLPTQhsPmS6xVqCE2g+8+0imIKaP/6cARDtQACwhYK3UEjEbBAhsuxICKkCFghcsGIyAEHki3AwY0oAAwAAbgRBJdZPE1zdRBFg3GCYCFzljjSA8XCYLmA42YCLnhoRYSKrqpIJvpSwiQYMc+y9C7KLO46/Fn933ix57l2oGuBtgELpbizZobsmms6z3FjaApltqqEMMI0mt7HIDa8qu/q8s9N+jX393dKDoMQQTbTGdlSNBIyrI10V9NKTWPKAqxUMA0WSLB8YcJCjzazoLxfOk2rLg2PHnjKFLWtSWjSys4DgxjW3wmGV1FUsYSZaKVYG98tvScrOs0pWt0lMKPqy6EBFx3Hn+fIhwYYEZyR11MyB3mypTvSjx/jsaGZWd3O1NweE1KD46DimBJ6HxcM6itckxDSPQBk81+cStzOld0kmIKaimZccm4AAAD/+nIEJ4UABIIXF9qBKRjQQOGreSUGNAjYIWzEBSRhDaAtaLCKeHKIgNFk0DOTSnxjgakZjJEoAeiZHDX82I5SUSCR4KsERmJTzw6tqQFJpgVgHRZAhppsaMLXrFzo5RvQqp/u17vu+5oAANAArWIZAGqBR0Wc9BhBBT1r4xaHBRh0jGJvMHQMrPamZWOe0cbGwG3aXIjbyRZhnKpFOe+x7BLzqSskepK+ToFEByLKAKQjY56vLBKEXsuLnRElweAxpzg0hptOtZYUItcaFwhrcVcFA0OQo804CrhftyirjwwftWsr3h1PEKxFpTDT1naAwBJSUc13AGtUSWOvORnzIlqL1f8rdxExVrBWaE1jXp9vnMRXw/oGbkzQhQ/NIIYapfNWoStxytg5ibL8j9YipU/HWpiCmooAAP/6cATMWgAOAhEUWJHsEOBDRbuaIEN/CKhdWgwwbMEOjGvE8ZoQABqrUkIrkIrnD8BPnxNoL4GsskWY1Tu7Pslx70GAgE3hpG43hRC8G3QtU5TiApYHhnORGGl25N7b28w7OuMfDvdKh6ik04427rgSsp4uXjJohmwjbZMw7o+706ikIU7/XZV2bRorIyXvCpyXIUAiazvMOn1iNG5cN921a0OI/bvLPVuFQ1Up5UVohFp3W1mM3Cr+rK/YAzS3qktAtLeWEBduCJBuAoZvVoxcRLD1xVQFhoGnpW1JZ8IqKC1xR8pi1Ukmg3p+rR//31g/dH7BHQT/MFVNMVgg4HfHZnj3W2dY8XDWGXYyScypnlj90xgVopRiMabNYxgcDwWPPJ5FJy0QTGzM5b+u5n2NT+5aExBTUUD/+nIE7NQAAIIiDVt5aRjYQ2FrOj2pJgg8O2DGNMqBAxrsjPCKcDAAIAAIIpJMp2tgzdG23dn8ZPiXv8KVQdhO04FGuMFBehiDg3U6w64mbvASWByTFlU6j1HF6P9EJL3jolT3VgtkaiK6gAAIIKTctlgbIfHa1zJiVVFUdQrrXsqyIDynBccLWoYKLeSrrYMutpn7iDHJ2JqSeudaIaql96RResjyGGq2wW04l1ABAi/32fA5SFeFS2VeMQAshSNkHj/jq8hUF3rNMWxDhq1jBzmywu6ZRxU2tylCtRmnG04yUk6rXDnOfpn/TyPW+SQCckku/0OjTBJLarTnuBrJ6FE/a//N7DGPCM7mQdYZ/32dVEaslkttmhSRAS9nyq4yp5JDWUSlCE5OvY/998j7ExBTUUzLjk3AAP/6cAS9jQAEgd0Y1gMJQxBCo4s9MMJiCJg5Y0Y8yIEjjGuM9hRwnrWAse5bg1yHbp5RS5ECGvkEhHlgKLxR7sCmZc55IeCmSVHZw57GGRQZQgkQFhoADjW1MKt9e9xR6+pIAAAChAMctts1QDfQZdzTzuDQ2ExfcSPoHWF7tTR9eXS6cKwiseyHdcP3C5CN0FUlUMFTQJNrlVbn6ibvleps8z6KAwAC3JbbO0AhDbzl/n9sECVhW9N5Qjf19zi4HbHHNgGCeTNrkCBsDgmEFMCZw4DBYpKSe6g8hCHVChMHile3TtT0Vq1IACLTkkX44JxWrDU2q0DWnAs0tjG8+cPLDbBwthZJVpndGR2jiYjgyMOTO8A8qsFEgo4oIXOHIZQnVfFQ7r2a3HhtKWPq9ZjV2piCmooAAAD/+nIEnygACMIUF9WLCDOiQ0MLAz0iWAh44WznmENhGoxrSYYgqAcp+rYEY5dqojXEptp1v2JqsGlt3daVC5jQF9yc0zZYOzBQaC02daWRknZEEyLaVCsHJf/6tNFMqO2s0Wzv/+5/7yIKblt0+oTOI1pl9W5X75fA0VHrgtt4/2Xh8HVha3ar6PBuvW98FsLlRR3qvimKsD9ilMI7EljnOVnKiKalaclb8j+8zD0ku8NIBeEIB0iCyGjWcDNKA+6y0db6Ozi5G1fRoOk5s1b3tMjbO136v1V9P20Ur3UycYjfaqMXv/UlctbL0aN3XuAa/H3EQDx46MXm/wHDYqQiQwhrCmpB0bxujU6yCMuCVsIR1dvBldPWVyNtg8wxNQ+YUIbyySsp2FbhjSsPXqq+tmN/Zuf1JiCmgP/6cATbYAAEAg0YVxsvENBAgfszYWMaiMxhYuywo2EdDG/0kImmACTcgZGI3g+MMZVUE+Cto3KH5a5CsTLKrCGhmhBVRZ/YW9hysUqBEPGLYVTOKOhQacrQSIvcfvtGqrkNltTvTV9ZBSbkkrISBy0+TzDBq6gRjPQgYr0qXPkuHi0+p6jRxPTNgRAhLoUcqSkGjoAZlTTWGfRDq3giM80e96mN3y39dRACablzOATApclHVxdC3AjtLMYflN/D+46gJQUejZmo/ZRcJsk3MsY4Ixi/TMpvkRVz2SFZlbRzX0Xm+2oPOeqG62097/6gLE2VU1JI5dAFpH3QRM6kd+qzGax7CRLTZOaYLzkHNgFpFdMsNA1qFoehkUrVVRl+aasjmc7nEHwT2MBcmkkfWOR1hhY9NyUxBTT/+nIE7p4ABAIIPdqzBhDcQYDrmjzFFYi4yWDsJKNhH5krzYMJ2igJTNa2BJMZdxVlpbK/2cEL3JrqJzCXQXcujag5UZD01sWzW2bd099nRTzvtNTPZSN/9PdwoaMl2EkTDe6r8rRoCUAgkpNN1ICMvVQRPA5ig3GFCU4LuRReVkLireqj2mCOtCQAPsOtC66qvJjnEg6BlnULvVI1HXWasAOgmHnrK9KyCU3HLiCBNS2smBGdccTUzhegqMt5rjcRBsIibTO4t27oFCiX2R7E3VkVEjTS++7NVN6Uq9R0Yj54AuIMAup3rvR/yqWjyCk25ByRSyRIJ6Ou9Nudkrjb+B4TF4J/wSzS3wt+4nbLraFatQ5YKiu/2zpCaNp3yb2XJnsrMkHuuvOtocKfpV7Ebr4qnn31piCmgP/6cATD4AAAAg0y2T084AQ9onwdowgBiPSbeVhhgBEjEq8rECADIAgm3IAOIPNy/PHjY33ws/sLml2+NozLoSaKA2xrKcvbR7izS81jDrm6PV0VV9U7UTvvz4+PzMHmKW1MQCgXKn9YMjQMajltl31XKIBBiWFq/f9+6uNvvoKqhYmvciqjOKhMDidsanEJZlqVn8n+hR8r+zWNTtAITa8hn2JJBkACRWr9t2223b/AAaqsO1PcjvEngmYROT7I5kb3ysNurZTGL8v4R+wIiLtjfKYeGFjS+2LWmlUIGWgChJFQox0KmUuD59NyEgAAIq3yS27XXf8ABUTRJQ+54pUd4qCss93c2+yLdiWviGawjox5qMXmmewXehI6v82SV9d5oB652/9n/uPtgc/dbwlx3OSGsrpiCmj/+nAEtaMAAAIVWuGeBKACRMtcc8CUAMiQ13WcMQABDYXuZ4wgAAhKLRaLRaLv6suT/////97fk5BFzC5jfkD4fOSx2AQSFRgtp3D538jM7rchif8/VyEY58rHECqdP//ncn9FV4pxAdhBsSCQSCQX////////v/k5CzHMb8UD4fFyIQ7AIJAokLfw+d+hCFdzu5BIU/4u9lchGOd5WOHCjTp/+/ncn9Cq7inEB2gggA6vQEFDxPcpRHsM0LGHlTL+7cs6mb5Wf790VrOblkp9XZCsTvzqt2ogtTz4naEUtkGfWFGzjQTR9ZlrQ0hSnYqgRB0j9yAAe9APNPxnQ/bCm3IPRJXpdOnyEoVMspakVcZDk6cZkxgaDF8+00oRqe8XaxLZBn1hRs40EyGR1hlrQ0hXrQRTEFNA//pyBJvpAAACHzZdaMUSMEGGy40kZTYIqNeDooRzsQ2a7fSAjqgBppBfSNAoleHCrLubYTwsMZGwp0DFnBrZum1Cs3qSGa1PViDkvo+3+lD7J+Z0dPCOPIyMi3V9RNews93rXiImyHdXMgUIBLyskAgLx0NPVTx2v4wdCoFOzh08B+2Ul2otvtT09WYeS+htv9KPsn6Ojp4g49lMi3V/XsLP+teDQLkYd1PzLDTRTskaRJKOa0h5ZsbOnEmRA2beEoCgPZ/DMH/WGZ5o6PZC+8i+ZHJnnArolVaYPVmup56wQtWDydVgTO4iatz6mOeSSBAQAnY2kW23dwIwVPPHhPVb+Mrm9DB+60Xk/zwf9YZnmtHsn95F+RyZ5wK6JVWmH1mveywVasHk6rAmdxE1bn1XPalMQU1FAAD/+nAEn4MAAAIQQFep5ROgQugLTSwimgjFA2tFhKYhFhrs9LSIbABzu5qRRBN4STa4QMqmmRIUec1nJpwypMJNU7tXfXRt7L/zPIlJJTWvW3VhXbbXm8Eb/68lbiXJXq0OSU7//5lj6wIAAGhEpGnYulhJzGF8Qa8gqu6T3R4y85wfkmbkeSwZ8XxN2/N0TMz7yiuz217+Cf//64zq+LTTjIEWz6maKOQU+tcRgKNOppprnsNJcjTY/DgOGSY1ts+gatKIlH5atUr5fZlimjFv+vZMOaLLuxml8JB+v1ekzcqILNOsogY8wjKfl073fDkWAgIAAFJbQCdtcDDNW1sgn+4XjZjBtRs+gGiHRDaXo2XXt3XotFKiEtMq6Mmzez/r6D9XUiHPrgrTUjS8ivsct2VIC4o2xMQQ//pyBIgyAAGCEEBfUEMuPENJu0oNgg0IPGl7QwTrMRGQawWHlHDMof45G0k0JOYGtYjBT2yaNgGhMzM42/PTKspbNP2KfYzmVzL1p51Q9J0mfGTI/0Az10ppensNdvoZYuInfS7s0esEAMEY2ik6OwFcetuV043Cla2Ikg9GyaD0eytm0bepkqnRNAbymkPu0qX6QdXTfYvtxXrn/RX4Nvt+eb/nG7livU36IzxrjccI8pEDfcMBfEhEseSmPYc7GpY2rfRcscyCJoibERKdOqcHHqqNCdLCazqiKD0UCiPbyBSdaUOu8kO1A1s2Ae7r2SALEdbessikCJidaaIZmIHG0zPGBlQ2gSeimdh0TXGLXjtm/x9CRtDaFMpGUJriRwuwSx+1NnyZ9mcJfccREX6UxBTUUzLjk3D/+nAEE1kAAAH2Nd9owSzcQea7rCAiuYkJM3VCjF5xHZsqQYSJ4Ia2gXU242kogOwF0SUOql/v8b/l7h07eYZ8bnzzKkry26c13CnhXZTDerdEfdn1DPdJVp/3W/Ro1k32nNkNtIoBSRO3TXVwHJiRfAf+SOoML2l/X383EnbvP8zpbFS7NHkZdPWdydr/dH9Rv2wiR+sKXKQ8VJs3InzEmfzlQmQeSkulSbiaKatAyBAJO6h7KR04cer0JkH1fDjSro0RPJW20lnUqsbf+e6G80H4tvrDpx/0kp+SLfu/0//X9L3FIfKp1raGnqurIww4DRT6x55Qm8+0vdMW1P8Z3FZEok1O2iCViONnCT0Sv1Vudi2+Jr3uUkh9l22f0ZWdEbZNrSltd+yzv9hfY2R2J/KdKf/mUxBA//pyBIyAAAACCyfa0WYQqEIiuzo9IhsI0MdlR5hDYRYT7Kj2CGwEgIHJrJLpuQsiKKy5EZ1MCe4E1e2ZMfBtky4JDR0vrbede1q+CUwUU9g6hZFEVbTydVmzWMcprxMS+kjh1dWojQGCCCSajk0kNtEqehoQ06KM4Hvik/H1fbRqm30H3MYftyS6fVYFGOeOWhZE5Vyu5QsOM6dMmeUeFCDX/w6bdYx22VCCACmVxuWrhgk4dM2eYEt0WGlB8Lq+fBiQUBD70f5izmT1rbOq3udLXrodVVTIajbJDj0sV7pT3W7hZ4VzpazIKNnZn8jsDIASF2NyWTEMN96pfLMujaZMEcED+AvBj14cS4Mp2Lgn7XLm1Z/R7XvcGvw5zC46dqE6xNKJctD91hH5We3u4tRR+7Bq1MQU1FD/+nAEhr0AAAIfIVhR7CjYQ0Kqw2HlHAi013FDDELw84ctZJMkbgAACbELbctfImjhDLqu1hf8idEgtI8Y9R9hDDoO7A52CAtZ3Xqm3d1b4mOY1FVKgZvAUQRAELlkoZT2xb+tPnH/Q+okQEm5Jbm4iCsTWGiOuAkkX8AdsMexr1wmJ40NlGNAUNl6PGPQtyKfJBVqqqY+CQGghMloSnV5zfFtvkabmvVp/JHbNCIKdFJppuJRwehXPoa3G3ORMGPh3mfNm1Jk5dOTasmFSr/tURsdkDzlM0uRK3rjfgUDRQqlhFt4RdxZM4RTUfGU2JdT2FkiAtCmW3tKADazR00l/qoergInc61+T02KkNAaGVeP0UuKTTGlXCtzzq77xZKv/satFY8ygqs84FXd8v9aYgpqKZlxybgA//pyBGljAAACECbYGw8Q6ENmW7okYh+IrMtxR5xF8REZq92XiHQBtMO6VoCzO0medkqSY2B4gXGELu0bivrddxlKiog6yfR6pobH2LddPv2bXg4cuNFEyKXK0/imgl/bsPMKRF693+wAEXklG25bIwTF0s0yXBkal4PdZUZ8lOj0Jp3/Lp32e1nkWDyE8zv9M24I+LCQWQnr7NmlxBZZQq0SKJPITS85nrXdIAI3lFppursc4Gko9QjHdBviFy8nUiMpI0FGq/OlTtL/fCfXtT7YKQg3ZSZa03XBDUW+wTUi4S049jbo08Ooyu0OAm9/sAEAAU3ch3Aq59vFv4r5IGLGwq/xpucXav/mdzlwB6CfobXtr+T/6bbWS0ZJvVU/e4TOi6/WENTP5hgJDxppiDqpBbFoTEFNRQD/+nAEsx0AAAIOMtWbLBMgQoabJmGFG4ihAYOjHEPxGIxqzaeIcAAgpaC1h9eKjIBpfPtIfrKbZNzMcDIgxYXzKuCgjdhvLiGoPyUG+fRO+drdzOrftn0ZLU0/2oGpsy80glMpp9//uIWCqttcMTHzZhZImv+rJkLUcFUmMH4+oSar6Jjql+pHt3/6tdXe2epmSXT9NGsMdY8ou1N6emn6LguJEkNDodwx+gEmNSax6y3bgUsbWzKWzBk1VwscfE2LzvmWjaajwz6Ll/bVt9dXedsfO1f9HCvv/3V9Vn5gcUQcmFrZDdoSSvncpah+m4AoSjYUnGkKywRCXkJIqpzkSf0gRVrux035mRsJQCGg9HLIJqC1yinE8e9a23rpnxM9JomEnqGiGGyelv/+iQhljLU3t/0JiCmg//pyBGePAABCDTLc0ak4bEElCtdp5RwIrSltVPKAOSCZKo6ycAAI8e4W03JQwPqLGay0k74xs73GxahbI5VGKBKpi9tTMtpzv+2r05r11tU169/jg8beUeN3TzlDrnMvxj1G6qSe/0kAQDluwFIDuO+W67Gymp5S9ZkI0hTBhH/rqagZgK0aDoh1Z9VxugrpztJ+0xVmZoqeLx7VptTeODiZz91jnPd/qBFBZFotOAd2qI3MlvvGP0VEpn2EdGkA2x62OdNsgJQVxfRf9FyaNm1Wo7a91ppoPSXv/+pc2T9nWzU9zh4LLiFAntv/eKAQc2Bn6CxCzDOBlcfWwsXGOpmcm2/eSbg11lMKiyUFhjAHGRCNB01SMq8or1ajb+1fzdu2c8q2y1T6dMlDdv0VWMG9yMDT+hMQU0D/+nAEqU0AAAILB2JuGESEQaErR8YMAIjEQXg8kYABFhYvB5IgAAAQgCTEWnI63K7bv8B1Vof7l725+BA8MHAwGBxhEYKA3MJM0Zh1RLRoPMo5i82/t5e53NaXoWSe/cn//3OJnwfEAAAKJJLJkQiAYxG4/huSyMP8KY609OeKSQrtgACFAwYMGKq1gggm9GY1I0JoOZDmL3m33PUqA7v6fv3f//c4m8+Q0dQUBgKGR8ysqXSYbY/mxCO5rCyo6iMLk0hJApnGwIhoq8+TNX2KKgIXGJzI4fuz6aBzT5ldW0WH55C377l1lyJhOonXivrqBQMBQyLnkSq6TDZjfNhGdblRnZR2Q9V3VLUfkRfSy0V61d236aSsGDqCSbwyOH7s+mhTT7l1cWXnkLfv15dLk6ideKpiCmoo//pyBKRKAAACICFcywYowEPELI0UInuIHNmdooRccRaa7hmDiLgAACBgACGhgAQ2GXTRNCH3mLmUuopzCr1A7Ycf0qR5lqVbbs9rKKsVjiRTrPEwMyVVvnYtWg+7G9+AiKSY1b36clonaAElHJVFGkSUggC3dSreIPjS5rCNZsKOH+tqYTqrs9rKJYQxxIpyoNkweYdKq3zqRatB92E++gBESxMIre/N3EtDjtGsORya/O6yNuXHCxbGe1Dzq2jsrBMUJCxLv9MdpVJHvf0dxrKwqGaL1WYrJp8hUzcVfUxVmzzakrHjaaPJPG4b/1AMCBmn0EluY/UhzInFJZgor1LbGVhdsQt26+2hS5Hvf913Ll2+1tTXJZ+hqPMmBJoET2K1fex5Mk1D53eKguhsqard22KTEFNRQAD/+nAEgscAAAIaQN9p4ygoQ8gLZmEnGghw12AtMKlBFqNwqJKJpgCQ2xSprGo6egvGCu0hZ4V1Hcr1VuCcV6bJ7lud1dRVtOaVS0erL7erd/o/9RX/6f3GOSo1Wi1Yl/OnX0khw7W1iGLAQOy9oeBLJU4KsjjRGszweCnuzOeuoQi9YnDGhHRNraI1z3v9u9TTaPVl2/Vud85/8j///lB7rtiLIud+grx7iPapKgvGJgC8uivaGn9lEUSml4RPDU1MXGs6yNWajKilHcLBBIVACzI1kfhjav/EdVbk7s23fKX/pub47/dP7fHJzv/3fd7Qlf/TjbTUA4I3J+V8+9Y87HViG4CBqxo7g+5tlbBALYl9G69tUbv/3qNlv6bm9Bv/3XZaB3W9aOiwbDnO/R23X63m1yILJiCA//pyBCjAAAACFzXcUecpeEQmu8o8xQMISNdmbDClwRMm7V2ElLgMAQIgHXJRMwRM6xeSoRqQlB2tuP7lCxlA2ZlBm7aeL/7dP7tt/0SKDcy1nYYuvwXqrJySAy1i09bc951Rf3b8JWvWECKbqP+v5pkNSGlNY01RHRufjEaND/ADYayr8X/o3f9DDltIZEpOyLZ9LPlWdbaCq7S7uiGflXn9TwHtdWzuVywfsesAJySQKXceRN/a6l8WRFnFxVV5r8z9P3C8UPxOZcCvVejcCfxAF5Wejbvzuzei1Ndpuquv9BfXr898UrpQR/RtV/tIAADLbsIGvPMN/RVU4grF3B8lP5U/11P9e5rh+WgX/R9SdOMHfya2t1b1tUSspq96d9D/1pfsvEn/+j6/JRB/bH0QB+lMQU1FAAD/+nAEnF8AAAIMNd7IwylsQqa8DRQj1Yi42XFEnFERFy9wNFCLpgwGdUvq4COhhtwxn6DrQD6ZUgMM1HLgN/Q/fr0F+vtcyFKiGbo6NjU77IcnTaoviEe5RJtPt3OAT5ZOQ02REz1+oCEIMkgtRtuRSKDQtaXreghqfrxpOb+jzKKpj2yX89m0IwPRk/M91qy+SFMcaoNDDwVdSko3VU08106R992Ij121FIYQwAtuSSDDWE7Yp8wPxafOb6Jd1Q3xWMNrx39YzqNT6hUZ1AuESilcrL29OVTsG50rW1jeDBN2wptX+1iiku99XVlzf9ZMIQZQCccbdGHcPiwpdbVSqCOidvHdv1QN0DnAV8TVsdTXJ5K+WmyAQf5lA55gWPWTN8kXryy5hFy26//F+7ps/1BLPBpSYgpo//pwBEr8AAACGBzb0McpaEQpi+oIIuOImJdzQxxt0QsOq4j2ndAIAAglLZLh8EABR0uCxWoBzUJ5R9wVBSVRsJbH0EwNxTx+IFl6mnjQs1zAxFz7rWsaBmJEul+7WT+u9b3kk9GnMP+SDSJwgNtuR0chAiOSfDcfQ+4/jN/9+fpwY+nrxihJHk/Viu3+ro73QK99WVX/WZUe1+vkcxzMRDxRsvlxg4qMF5nboXWFEIRAVltpbIH2VVB6umOUsloZxOecyBhsr/Vu/+X/pspbMJZIoPQKCrkvlhoia5Cllc+aqk+yPIHx4SPGGM30p+91aNIB/4TFiWwU7q8R8L52ouYBLj6na1Zj8JY9g/nx+h8ClIYvWVE9HKIsqA47P5UZ7zlhVCuGYUIEdgmIKUe1pZ2L/fyKYgpqKP/6cgQGPwAAAh8g22kJKNhDhstnJEVkiLB1e0MUQPEWjm5oYonKAAGAAJAt2u5Rig4/asArtmkNntp2p9giPGxitp76rwL/GgatNBjSweERBiUJpF0satA8xLawC47tkX9eeFtBX47kfyRAiAnLbViLiCaf0U4e6RfMbrsJHrCjtk/245v4b/fo1Eemj70Nbl87K/dlHrCIRIm3RPXrZWZz6b31pnjDs+7yrluisaIvMhuOS0HlIqOFXoF0Xs6MDHNQXoEJROqc39RsOckWusSsa4DvZeKBzJWkrEiJIsdHstgVYsOfedCddp0qyWDVCddOKpgFCgnLbtbcTYn6Ka+06wEjlJsFOkEO1BP+2h+H8VFpWTdtGUC4IgpqLChsZKFbFGFkq1/lqBrUiYMvlWFXnHVja3YforTA//pwBAf0AAACEj/g6CMoXELjW5oY5nmIJH1kZ7DjEReObAz2LGAmIUlRJOSy31RQnm+lhHvoZo7bV8/TxThFv68R/o1EW+P1vKlrt3VUt9P+mzzlxjESKIvDbyV53S+MzTLiuhEmTWdCAGIhlNJ17oKgptF9f5WuUncf7DwYLIUd8Z/oMcKAb90VqIUdDgZyNuOOY4YVxXFOxf+qhhCSY2V+xhNinPQigOKOgNJuQMGZSOf9M6NlqihCNI3i7hDWPqtTOJH1YoLckNQ0x46Xso049nLooxZwZEDR7bTpHt0RGVErlKXW90XT6/oAhlv4ZVpWBYxJkywCBdHToTf9IJzbSfzIpj7eRBAXyafDtXgje8vvkwdbIMqaK3Boo5TTgopxrIDOnTvcAECIn9bqtzP3piCmooAAAP/6cgTLxgABAg5BXdDHE+RDx5v9HCLzyDihXmeob4EYlK1okIrSSRMJESbf+EeFlY+HMrsrcYf06y/bF/9H6f0DPZtPb0bGaLQrTmTXsU2zentaiJYrnpFxL2DojFr0rHhXU2Ktn8+hCkBFJoqSy3IsmLx9We1DOX5lYpUvExza9a7cz+gd7Z3R3OWcZkGsMvZJM0lX6/yLg27py4zzzmv5fGof2R/u5+U/b7ADydb4Yjh47XMcU1sUeOOi2pEusmEW2GItIyhVeooWXTnDbiX/Fw/5H/oF49UpAEgLELgdL3N3exbETwvb836/+oIARUSHbcIJqgA0RnZilRfnEWdNF/rfvWWm7LBt/tFHiTs/GsvXf8E/rszQpcRx1LgI2bWebYtRCeEo1/2C5IJKedW5+4BJiCmooAAA//pwBBWIAAgB8ilauSI8pEOkWwc9YmaIqP9k5hROkQ8gLeiRFaYBQSTtuBWFDJvc0gs1y4l3Umv45taVHE4Tj4gEyZOED5V9CXM9+/ZeXx6wMjWwA5J3+KJHny4XG/1ufanbhMgABSbkB2InhjxILamxzRiP2ehQx9szmo7OF0jY00gaNUGkYFSDH5OHbDl1AOfgn1uTZe5dRUaIUeVi0Li6Fhpun//WIApSXBlxoDx1KFQjXHziGED3QK1nNGOQHXO1RPsmfimyvo3O2j/3kaN3JQzf+38n0fRZGQKiFDHRHTSMuHFCxWYS996VBggQQkmnAlLQWMiBLQ/a3WMPx+Lgo8tRTVEFutI1+nAr40X4q23v6ijELYy+2u+330T7P9DsVGKMHPP/m0GjZ5mZtzyYgpqKZlxybv/6cgRingAAAhBAWhUwoAxCiZtDpggAiOTtdPhhABEbHa9rDCAChSrLPLApPZg4qrD25DEjplB2PRAgPTGhinEkdVZDnFtUXGD+Dv+M6P9e9k//Vd1lbX2MaiGmdFK1RFCb9ldy+uiWABcu4f5IBFfZNESKmuxjhk69kEUYp3g1bX6V8nN4//774OiD5u3/5tibfMiSbuqgxrBTMR6vaezdWIl48Ikgk115cAR9IRmQbG0AKh4dKb6zoZNGHOCnZHpNg2kfW3O+iu5HZ6Uz5kdGbktJkZ/v+xFc+mtmUqNUxGPFhiUcz0q92f//+Ay73pDAAACCtSKkWx2VBAbDus+nXbPM5rV03uisHaQ8mRWZXoZ9K+U7h3bey7o2TIQ6+LKtkyK53/bL1pxYYlDjP/dn///gMu96QwmA//pwBIEjAAACGyDgzyRACETEHI3giAEIaTeDpgxDoRAmsLTBiHwAATuoAHzC5GiZqTO5OL/m5VropERy72fqU0tW7Tfe9XFl5gV2NlQkWnkFklj1CZB5q8qu5NO8NnpbZXts0EfEj72ybBRak2+2kklRjGIMQaGntVZHqiI5d7P1KktW7TL7zTuLLzArip2VCRaeQWSRPUJkDQFQ8qu5NO8NhWW2V5KzQR8SPvbJgAGCX7yNEhK6GBqNJVuRW53YgZd2JV3wyF9UWkp+U1LfSlWlOXZ/z0Rrp+Tf3aXWhNd/V0//XstyZhgllGprYBTe7UAhKLb/bEkW/TgyjRKrXK3axiv+HYJ3qiF6qi0lXUpqW+lKtZS/+eiNdP03bZ2l1oTVL+rp/+vZbkzDEU1zrhUrQdelCYgpoP/6cgSdhgAIAgM128sGEMBCZruJYSIYCIEBaSywQUD9muxBphRgAAE1kEBigwRg7eB8KSrV4LPD1Jq221W5e+1+65Ut+pVLp0qyftUxUmMqdmbVuKUyrX/1vYqtzHeEiOKnJV21skACIfIKtDgwRk75CbEmiP7iZd+Nq3Bi7KIJld/a/Vcv/XLp0q39tCpQy/ZtW3FKZVr/2iryCq3Md1hIjipyVdtbJFiQBEphSRBGAgXqjNy175MJ8bQZWhX63xDZv9+I0Vv7aGq2pat9vR9X15WMesiifK1dFlLmSDG1RSsz1BX1lXdXyLJWNC0IgHo8NSPCeTD8OLmnwlobtD7RESu4RDTMtc13atE5Fon+RMWq11ard/0eqmy8rOvqO+tbsl+1/ETfaGv/9CYgpqKZlxybgAAAAAAA//pwBH44AAACADXZs0wQUENrzJ0YIhWINQFzSAyhYRKa64WknZgAkb3+BJgkaAkKDVEzh5rMpASspyYUfqdaCBO39Xuh0sRtP6dZ73ZWp9DPN09tLrBsJtnzBzfRuDKr21aPV+/rCcdWntt1skwjaZbcOalzgWqnUhR1yNl5c6LeL6dRHf9Ve+ysr9jUHQ2b36eoyp/f/yff9WdfTfb/t/sRdLVXCTbD0qAiPNMkr0oDwouHG7T0wgEW2IeSlGbJ/V+7Z1xJu/fVNNGyeiCYzlujVL/UM/p0QrssqiYxRVTnmU319uxnDvrbmB3NTeLO8VveeIzcIUBirNq8FMGsexzaKDXtygSQYUDBFI217w891Itv0I9nz2sj67LZ6sztbT7f8t1/h762M8h9XFH/SmIKaimZccm4AP/6cgSELAAIAhxY2tHpEFhCBXsDYYJKCMDZYkeMTyERKjG0MIneAACCAtNpsKU+LETdxfGDPBxG6Qaj9gUsEfIyNTdX1P26f6NV2dbpvt3a1zdKqleqB69Htpv8v/zJ0teq09H/X+4//qJhKSZKmUQIHZhSY18FHK4siKgj99FOTh/09iNL0BmTE5KoYGGXHZVBDlIg/6p+n5vqgVQSmauK9fQ2Gv9fIff7/pFlVkJACWIlMneRzmsq7GcbrrtVc4XqLGFcA/iFuxU4xYhLArb6m5US3mR6s21k9+1/3x61sJRY9u28glWXOu3rbTm5LkaAqzHJXZtbJbrrZm+Fkc+DZLTvGMiBX40qh3kI7OQ1psjfdaGM/2qhM6lW9TN/HrV1mm3/b/9p2KRD1xyyovpXg25v9y0xBTUU//pwBEBjAAACHjXauYkQWEPle1cxJRsISNl1IxRMMRGmbiiTFFQBgLTckpAComBo9Atw/Frlj2wT7Lwd6E0XYu6dCvher9u2qvr6v9FC+1Na3PSUCAFQRiUkfWSZs63oIvIMfTr5/X20JILC5JHMQANNB8aWhR6Iv0mn9J1yCVamSoCBsYztWn4olPq2t0NdWVLKQ1fo4icWfbYRLRnL1OyX1Ws3ob6ksd/gXPCgULUq91aTaMPzJMdD0N1QrQOm5XGo+hvpTxX8GPo7bq0jvWnL7qiDrujtZZzr1C3xe5RRaRT9q4nSLKR2NuoDUU4uEALkeu2v7SFkOK28BrNS8YfOtau2zOMOzKtKeBtDeD9P5lKbtcl9ZCoXN6VOemYVf//rev/6NUi86jmc5l0Ujohc/+RTEFNRQP/6cgQCWQAAghEz2JnsENBAZsuZJMUviM0zf6CgQXEerG1YkRWmICk22RayV5Cz7kPQPpiLxfLH4hfcAPwRxMEBvQT3pR9RPXq/8npvTr7FUz7/XpGoO0j0zJigh6R4RY3I3+lGlyvJAgC9N1Vpq0J5pluJxXYr8qv4bYXiaPKBXjESmjI3EfvGga+pVR9r7W7fVdqfXR60FrawpJcJd29GRS9X/NbcvoJaCbcTTkkks3IMqbrfDK8ETclKXoOagv6UfkfTzcXqu63RXIun0T308hnJSh75MvxDI8srFfTVGehSyJ5h2+1ABwE75KneQKVbdFcAGrnXiCmfTToLhtKO9A2gvXRkPuFGoYmNDGeqb873R5a/syv/0S6Ku9NDTdLb1//1fd1e+QYZult+avQUP6wDRUG0xBTQ//pwBDv/AAACGU1cUKEV7EJjivc8xXYIvHNcZ6TswRea7Fz0lGRJAaCEkknCCDhEVRHDzj40a8YW5S0lpUeO0/k8k8SyWZf5AX/ui5/2SLea+tL1hCW8/P01tTMCUV0SpYNvUBbX66sCkAYUktoevTcB9vKUjF7Ykf7lXF7ct+z55XAk47HDGYxsvsobxA6RjY0+FTjHh0Gka9c9xiJAnOKlUN5UpZ7PFHJ+wAmTbZWMqlC+W8HpODNVpYOfJvn3Rw39xB6egaFeAeJjdevR0dQ983jhaV2noEUeSy8fura1stcQak4VpGh5Hucatu/QzSBAAtOy5IpBWi7NqAdYAWUHsqboDY5oxxjg+DlxfY9akvO/bKIc+7Py7abp12yfnQq3U9BFoFE50sduu/GKImkRGhP09HqTAP/6cgTMrgAAAgs120knE+xB45tXJGVLiHkBbUGkQVEejmwM9Z2aDAKBqqrUlAUonrcHKQ/nHxpLJkd0RpVidCCZ9T0IHHKEha9s4YrTpz2UyXooOmgV7L+bQumFdS/npkX0btXT8p1EpIBSScXdYBUqSgD2hbQI+J7J5yCTBgQnt9duRu3DOqHw8cYTt1vbrUQ0i99EvmGfW4uWEzTZ1CSJsYJ3aharb2oCAIQhOS7YFMFTNfAvT/QPbMB4Po7EqyZv9tAvvxtl5+jNtrOojyJv9WfNa3yHX1w1jIgJw+hDrxLGrWbASDkf8sXd6yClJJIyudhxOpcrBM7Kr6RVuDfkilPMRLF2hBsLN3VfjdNOODsAILUGLhqAAFWHCwGAagVcw7KFEiXgCqz3LsF1PEzRBf34smIKaigA//pwBNUnAAACDxpYUeVDFD6kayowwmiJAGtg57DjURonbeiSiZoIAAQEi5IJFZQcLapWuIWB6rPCI0sYNhb+oHknMSSO5av+bx0qMFrrGMUdY9jpDruk3NT1CAcxVSBvYx3dqZqpn9YAAEMEOSUadaBF9DKbqBEomAVOfkvy47zXS0CANTBgzprtye2j//iE0CrQlBYIDNYvY4iO2otf/+pSCIBfrtIQCi3LRBPGESZHsMQMnmZ0eg22Et5UvuEJM5lTU1uhp7h1zxeuK2WH1etgGOmUGoQB91ubqILNLFn6GmzQCZJ1pNVWieHroUCAIwJy3biB+g0Ko30Wt+mnPFdXZw1qUDX117oJvMXBtmEUteTWHvZ52jpRp766+pN0oV6h5vZNW3oiG/6drHCFuDeH3GFJTEFNRf/6cgQCZQAAAgdOWj0koARBxQtWpIgBiPWVcnhjgBEfsq+fBqACIIQlJbhNNUGX14BWFdMRjlW9kAR8gg4nOR8v+11EDbdh8305zys6I9BXRql/+iJV6rxRF/m7f9/p+RMhonHvA9YBhG7xbWAJZSqBW0oKEefvoIDnVkrUM+EyaQQs7zpr0Pf6bKm1COhBM4kNvOn0nihHfOYiosjSS+7KLQGz82s8sNEAAAQCAQCA20HdZlnJNpGBDA7pO6OzMyM3/6tSv//////0PKGCQv//9hvLULj7////Mh4BkSLkzv////yxpUbA8EsYEk6cVcCehBACigYDAVjMUZ6w0zk1qOdtXsYzf/rp+mv//837f2VGFj///YV0HDjB0V3///+PFDoBCF5PFs7////9jSo+FQblhqdQq6YA//pwBBcDAAAB7ABhbwRAAECknArhCAAJNG1/IJhgwR6FMKgRiChKQgIoElFOTCokCwLl3icCNE4neTKDSjviMQVnykhIswQt4IBgoc8Ry6tYfoLtB/6HZcU//xObWQ1h4QRhyOEBAASU5V2HRs50u9XV1O9XkEK5G/6uh0I2p8mjaP+EES+HwACDgQ9R9Yf4nfhcuf+Y4OHP/VUsPnlDcTjDiBAAEAAALUUQRPSNMTuAqxkYApejTNxWqNiYUh8ZlP6pYyqTFSASJCocSeCgUIIe1qXPcgKPWb3pdNWpVsvEpSpDl914gy4w/VUSpgCxAkFNyRMINWVHOAlN2gAliqqXjDwSUlQddEt3FUBIkxCXsBoOIedCaXPcVCj3m95lyjTGm1eeEpSWQZ33TYgy4wPKVFiVKYgpoP/6cAS32AAA8foOXqnoGNBCwxuwBegGCNA9dqCkwAENDK7AxIxoIBI+bxhVZQwZAx+55IBE87HciGBUNHnunXEDo1ovcOOJChyDByo/WpIVPSpejOUxWXWPmn9Oixil9Wne5n9CrKoRuMyKZZoxGQfVNYA82VOc+orKiLTadX6wttRMmF1txBDpCBhBgd8kKgYTKS4+l0oKi49QbKNbSZ/3qsb2b9H/kAKACSD4JQgBBnSrCm/AgsvzmTeS6NPguRDIUEQIiwPA8ASzUgsxCyDWB1oplh5kcqvGrF5ugZsahdyKk8obfQllNZi1WQ6/umBkaHUnTez2rzpjFKjEpkVIEL6XYZHtsi7QUPAKWpEwrWQaeS0UyY8yaGJXWNtvXGNqaUuOoSpKMobeqcZTW75DrTEFNRQAAAD/+nIELucAAoHhCV0pJligQUgLpSxiegkEgWwGDM8BH5Au9GUVgLDAu9FoI3B4yDTmpzDQaGMWfFhEJY9qR45gIhU4e1Fjy6mLVGJSx4BHVUC1GfRpxVT+ds/yOSazV3TKiIBAfe0lBeLTLKJJ7hX0R5U1jGX5MKU97cmPeTQqqZr60Rza9gXpsl9Ee1/TSqNt52t9/RYbwlI2fURyTWau6lRFnxHYyP2RXVeUm5xR8jzBZyOFCm2nJROyoPWkKkbGG5OO83tV5hSFEg4YNHUTK0ETudkQos2TKsQ1IbvDT9T3NqXs71JosT9OkCAohJJOd3SnRatKQ7K2wTgZLUKXlREUj1vfuWTWu6O8rCoFLIAt7kQEVWl21TAot4mK+G44ltShGqRsz4dUmtzElhe1RPhxKYgpqKAAAP/6cATzCQAEAhkY2zGPGwA+IxtmMgNgCNRjaGWMToEYiq+0YJzMgAiq1auX5FcHVdCug4ncm7KViKYP2BeaKUYXewzL4xhgbwMwbc1a3TAiHSJuLhoAqHmINDr3Iq9dmuz92tH5FvT0RwBABFrvbI0dwVfsnO5ZWWmADkTk/DhZ0ayCr/6r6SizY+Sc6KrGIIkcMr2oryIixdDJ7rs/f9td+3PN0lk7QASnHG5iOEXB9C6BGHxov2QNgCNHAWSmOUoSq93Ze970FUXzB1y1qpWgHqod2hoYE3NKtY8XqHanovRVK2JRppZqkT2/fzNFCAFccltu3U/DGOXqQq+A5HFBiXs6mXxq9VuPPEoddqrvPWg5e0Jc9GMDWJSyTpVxhzwXj4iGzsjDopQp633v9qi09kWuTEFNRQD/+nIEVtwABAIJD9mQz0lAQkR7NjzHdAis12tHpEsA+wVv6DCYnga/9VCAkPRMDWel2Mnnu5+BoPWyGunele9VQ+8o5DXjkgMSbNLRy0PSOTNAEY5MmftoEFjxtZ1n1Jsda729/T01wAAL/3V2BOrOoB95UxHaXcLDY9ADNuA+f9F4g8VxAzLzTa3Yxd+/dfzI45o4+NJCNY4XyxJrYhddHXf7xRDf9t79MAAKTktsWr5HwMkAfSLsx9pmukNyrC/he5sc587RZ8lXKZMOG5N/TpWlPTa/Rza669Xe2O6+9B4IlZ2tanOu/oaxfb+6SiATsotJJRChi8nMc2uxS7BRJBcKuQt5J0e8vycXQAFEVGUMITtRHZJE1hsnYYGovlWmKvf/V7lkYu9eUalRVMQU1FMy45NwAAAAAP/6cAREjAAAAhIZXbgIODhEAxwKFCOFiHRxcOCkQaEHmi1clpUQAC0tfbeScuExmpTJNcEMzWlZrqqigyvINV1YYqaUSFwhEoZOgOaTY0MB4BmRouLFrkt66199+wnWYZAuSuqRhq3lslEqSTbTcRlc0ZVjVfcDs4UbkzobDg1filnwA7MhhQGgmtwqpAkFgteduCrAyUiK6t0RPPGlFS7muxxdOTtvYNQtzPogIQ25JdJMIT8g5pYI9QnyxEFRcVrRklu8BdF664IDf4l1aoNMB48RohoLlLF2Qg1xCXMJ+UfbooUKBW9VTHVIb3WW0AEBLk222iEMHuQZEsJBln8ngdDsDh1bjKSmd5HqXEZV3A/6v6vXRd2ZHsj9NK395HIzqMLGJZCvru6jSMV9OnZ/oTEFNRQAAAD/+nIENYAAAMICGODQoSu8QqObUz0lcgjMYWhjvENBEYxsTPMV0NhkeLUcblrKarVA9D6BtnI0U5qosuoGRiti3a66PIlXF10hrPqY5rSzDoqy+9YZjFEeotp3tNNHMdeXZu1VMdqCcmu/v7I1/4USzGo9wGb1iiz1MZglepNlHPnapZgk2KtAlFLja3EUUzi5eOFblV2izD7rXMV1J/NNnBz0c2j92/fQAXLf/eDXDInMtFyZXXnc8AwRahIgE1WRjXzuJT1GRqwd0RnFtVYFScM0Gkhkgci66r5iTRaUek0ONxqE1rU9Lt6vNM/qUoAFN2iPpIHmrtTLMM5SDZPQ4poCVAnHIAAPcoFKfnBN4Kl9hrsrUGGBaQwIKVCw8p6nucm2KOp06knTzk+8WfZDybvrWmIKaigAAP/6cAQwOAAMAgEY2ZnhHDBDijwaFCP1iJxjYGfhDEEMjGyM8xXQAjl2Ax2NLSyj3QcE/CIiphXbaiYgnI0F/gdGgxtlI/osMYmHz4gDyiqTcTjXl45TgMpr7ZEDgpTWp2xy0kUo9OMC9lOSN3O12n0aj0gORQky2skwvzfo+yqB+zztT798WcoijKqEs0pBfPnvLDl/xeffX8vOcl2I/alsNHiqvgGVYq5IIKjtE856p4+7l4FEuC3FXYoFrVNBQLTP36WuZ0NzsXuF3rjpiWuR3QUSSh1V67BpwnpUISQPHGV0IaiLu7Xvodfev/6wA3bv9bXzmV+HtnzSedkwr6tS2CYXovCinL5U2StEWq97vawE7FR4BkGPuZKoAyTMyu11mrnRRzSe7Xfa3ZcQW53/cmIKaigAAAD/+nIEz9MADAIHGNmZ4xOQQYNMLRwph4jMY1xsJQ6BGZ4t6JCKIgBJt+L6ZBUskAbCWjlmQ7Z8TeUfR9uOCZ+3UD3iHquKSjTAdk4VGtqErR4SeaFZNYeAs/cWfVW6ieQ5xKnQs6vRAmCVE05JZLzdjq6pTnBGjDs7NmY1ycx4/lSDedKpzOkFAILl6ZNYmGHkzl4RQczd4i0W/2zwJ+p0so0cDrsjK21AAJ20ZYtKTDeWaLDm8h1YiVl5hTidelS4NGGWMiYvkGp2RoN5AXTLrUFi4ytQDEmWIXRQoVEpt63uYhniIBI1mE06jVn2LQACABNyXbbOFTHr0IEPWY29BQzcmwWTm96nwc2EFF1VWqV2G20arVf/TQu26yc2/e345I+Io6eantDJAXctsJLlT4ajfTWmIKaigP/6cARbbwAMAf4lWbgsEHRBwrs3MMJ0iGzJaGMwRVEVkqxc8wnSAIAEnJADURi3JKGB8Iwe5YsahlcCSlnbD4ZkhyPhFq+rX1L+StmZX75hoMirULClyKSxgA0KQHFTLJoRs/r/tAIASnJRTngqT43j5sRvuVJQ+Cug8GX4Oyn2FNrXh2vVZDLfPDHNHmdinEGuKTQCe5wHRwIlMslPVQfEw8MU5y4ioAlyWhWiwGdI9kWEUlGU7vIZiGz8Tt2w7LcQ+DNvva8oA33+31sQYgNtl3lSCLfuxCNBnM1+GjgCDpq8ygM+RdseqhcAAAU3IHGCuzLPK97vSYqDR7tzhDYTcyCnR+Hql6GHGnNVsbvh/3qv01NQRBuRrWIXHS1hdy79up4bNnjT3pJJ7JjtKVrTEFNRTMuOTcD/+nIEoTsAAAHqNds9MOAEP6MbuqMUAYjdNXIYkoABJKauQxJwAIAApOW0RShiQo0NYmC41yHFMJ4WdL5rpmceaY9VVkePA5PnNt6fpVTFKmWX867epnz3z6EbQwfNUF7l0/uQRBIJNNuB+WQQ5tsYXy7zwARQp2fXiDu1Gk5y0eUBOZE9qLI0kpqJltqzhVAnU9Dz6HkFiVcNLiP9zTD6EeeXQlOEiSMwVAU0PtIIKis2VHsSHWS59nYvfdtEfmfo5BZf/0Bii5BP//QBgQPqoH///D4xpKCh////WeUXBwHWf/6AiPCzCisBk4OFkM7KtpmmAcYEROXEtw2XLnmS7u5quqaPcrUxPOfpMOX/9CRpMwf//0GwsG6qN///x8o1Mw////1nzSYOA6z//QEYWYUUmIKaigAAAP/6cAS63AAAAgkSYG8MYABEJCv94wgACDBJdSMFDkEKiS5UZB2IiAlIs0rSSKR1x9yNwos6adYlI4XBcPO8xkARw7LHSVKWwgUTZtlBQOGRE4VZRU/DYqre1/4uim4d00Me9d6ezdVAC+U8pE0UUu+r2425VT63YJeEgmYI+yHq5bPo/5mq1VNOs8AMDhdTNtSg5IuXoqfhsVVva/8XRTOgTpoY96708R0uUqAAHaAAHZLVhAUuW3XSN7jMVJsf8Ur0D5q2kTLZ1VfeAizw2RH1400imKv3HmomkqHYq7vpj2vQFNWkjxrSKdbnoANwPcDuAQ6kWXvWbsEXslpxfZnPhfelKlNUeW3eAizw2RH1400hiYqP3Hmomkiw7FXbnrTHtNAEKatId406RTrOvQmIKaimZccm4AD/+nIE0+UAAAIVNdy5IRYwQ8a7rSQixgiFAWhEmE7BDRstCPGV0IRqIkktbdn+NDFS3ap5rUjP9+vtPuGZW8hnePu3Xv6Exa/M2LXX+vCinPy+9Ynr6duu1r0tQocWJKF8DEg7KnbvZIgACAplxlJJv3Eewsp1vBSnmum3nO3V7T7r+L+Du3j7t2++MTFr/Ytdb+vNFDD5fesT19O3Xa15OiOTVwM18qdu0NZETBDU/hRo2Ixg9doMOEYQiHgTmkPH09eRDr7FjnBD0dX6dE0f6qXImVVm9ft/0M8/oK/+jqX2GXvx5hWIxvq+pPnsDRjgmqqfLArGc5046l3FmwzoHMUkhkIePh2LjFDYW9nza7AJRESjfWWpH1ZZv+wqzs3zGefxg/8iS0L+ParEY31fp9mqMTEFNRQAAP/6cARTPgAAAgpA38hhE4xDSAt6LSUqCEjXc0MI8cEXJG60YJYMwXS4n5rFNH65WCTmvR1ZqzhR8in0wlO69/0HpsxZ8hb1Zc7G62+vTt4J/6emx5TC3uPJ1AVJNDEdyjC8QP00h2YQABIKWyW1fJCuKJo8vkn1M/6G605S40iYgcfXJy3XYB7WzC+9lZT0Rr16dXfL0f/30H/1f+fi+ntAlj1Tu/9lnPGulAAKA9tv99Y4IwQfhmrZo1gMRmF8uj7TvLpReoyBwBdsjv0RmvZ/ZP9UNVSU6Jp48GtZc6tFcF/qqThH8Z1P3WNsrwAACjQklssyR1IYQJqM+Q3Q9AW89QdLAJR8uxLpq9w5yPm+jEuvOltvdkn5PtzM2gb76P9CKuHTt7t7GiPrIfFOLB/yobTEFNRQAAD/+nIE9SUACAIVGVoZ4zPAQqV8LRRidYg012+khFIBFBssqPCO4Am3JbfmMcMdTPbp+WA9xEjWgjaign2z4asHHzPXlL5L4ExJi1tCj52h0diIqNdeO9yKScSOMRl92znVH/yVaif1qLRgFJysuRxtxiwHjFvc6OD4HmFrzbhkx3p2v7rP1HqXtQhW9kt/uyKMaCqhzMjFh2Wzi3IEf1FDlazZDnlKqiF+6KKArAruu22cbakXzTGHpWyY5wMrn3KJNWnmiR4R5nmswaqbb0Zt6k0h+jO+7V6q+vzc/xHe9Ru+6u3c5jKejHW019QAAQAFNxyUwiT+hE7EdVZPYrI8kqQimkI+IlKT7lnz4ryPv4m7VnRbHX3+nZKLv+zmWSD8PXOXo3Y1rF//LckP/T32+5aYgpqKZlxybv/6cARL1wAIAfkTWThvSGBDBXsjPMVmCJyvaGYY66EckW20lBVsAICC5HINRlsKUTrNEOpXIWwr7pX9TB6pbLSafnlkUiz+BzHS2VOpUZ2x1zOtIzEh/lEw1oI//tRXWO7tut/1hpOSSTUTJ0qkHCRs2x7XVsHDUYbQLZkdKX83DMUL4wMiBVokZVwgm09n9V33da0/vQTNKTW3rvXz0eTuR7dUZ/O/94iTbbnaSQy0pfwcpTJoSUDc8PMX21YFk3NCP1hmER1aZf3Cfc56Ml23q6n6nf9TkUeeEq0S+rXzToFJfXdFJI9/w45XkoAABESm3LJaZfrUyQ0HQvR/fB5jjViKi/HbI5Q6Cg1F1HHaMZVCet2qn33ioFFiUtjD5EYbvicnLaFO1z7G2dzGqi1/vTlXJiCmooD/+nIEHzsAAAIOGNzowjPIP0MbzQUiBIfAY3MgJKHxHZruJBYIPmCAAaEJrrrv4MpLX0CgMa+o8sUGYC3dBkVZsO0M3R0fTwJJ3FjajinIPwo5UNse1/plnS7H06ms+MOi+Sa70s/1IYABAUIjltt0NXIPKPGiC4moo0dEFCwQ1bp2meXgtAVrzNKGFAiKtRIqnQwJJFymLY0kvR9Vlmv9jf+Z5gaHrAAAiaqaw8bhcdXkul5v/Es1z93dAQrHERPAXtFnmR8CsLybStyxUYpT2IsTD2K0vr/NZOf9wQeyPJN+hOKOgACQmqrjsbRPhxY6yubqqdPayX4buCG3oNUaJJFFdwTVC6r65dWlq19s3yTI8/8u6quAiwcHAFy0PaWcyzVVvLimL0OQuoBJiCmopmXHJuAAAAAAAP/6cARppgAAYhg2XtBhHcxDYbtqMQM0iFR1bmAkoRDpDm0MwwjaBwSlNJuOXwSKQZioq+I4Z0TK5wv6Y+G/oBI3Lzz8tdwgN+XfGFyU/A7FN0xmFSQOlXZ9a5n6bpwT/SbPH7nXVMqXFIAAABSckt3IB8hC0LjQM5xe4iXAvSV8EMKPIXHu5gcWtMdYljQF5AOXhEWiiUPl3tmEelCKdV7DaCZBlp1Q5vK73qJhJyS7LBF9FO1Y4pQA6gfJXRqSjHrl3zr10XERRHB5Lg29o8yhzS8Y7n4kuJ7udtCtx0Y6SQ8tPG3qeapgZhb8cMWNckH7AhqkADwYGSEAjHBfkNguMCZ0wJtrFyXWjAkerqwhhdzUChsUQ920TT76RizjRYerxlakGP28khMQU1FMy45NwAAAAAAAAAD/+nAE/RYAAAIBK11Q4R6EQugcHQgiu4iwq2hnjK7RFBvuHJGI1ggRQDbtu3x8sNpSqCKflJULY+y6NTO1eU0dX54z+aPDrchXLp8KqWtBAwUi+puR31WqGAgukbi/pHP+zUx3PNXAAUmk01JZb32grPOdDJYPnazI5UYobJi48+f8Lw63h01+oezIqXWangmsv3PLakY0s61RVuhxQlna1tTLLirYViuVBRclt3ks2V1TbqNk1tS9DfopT0e3DDTKjjaD5GsytygRtKN777HdmsqvfRnGQIqRWL1sJJyt8UNhZZENKUnOqWyn7FckiBgRSbbnuaPvBUzdkhibBNXt0spsM1HsALB7ButEdrJqy9mdrb9VDVJudjeucyqsUxB5Uq9gIFVFmoTTVOi6ZBv7NP7OlMQU1FAA//pyBO/NAAACEBlYGC9IYEMmu7ogQ3+IbKlzRIR2sRuVLejBlO4JKXcCMEKM5+xmelVC2lwW2uOLVc7EbryZi783Wdy12aByXad9y2b9US4qHjdybTBaxd5VDfbOOnD6pNC6DB1NXUrAAlSk3HIJ5IsMhnTp4k+4G6d7VZWeEaZ5z2V7hfZ7fj1ajKmm2/AvTlmjFSMjNOTxi9i3uJoFhoKh+x0som+0L/TJyAAuUk23BPoTWpndlTMPpj0P5as7+p2oh2C+dFnSNlR5POkJryzRMVhKt6d/HUfUqzKPQcapIiLAR0upee98vFD+2UQADEFJNOD+1Lx6HaC8FiIoB4pgoENc61lthIdJV6Do9UM9dkOTajpa7eu/x6hEDQsoI0E3ZJRFnlz51ymeBgULlD79IwXAiYgpqKD/+nAEawUADIIDI1s5IRxUPiMbRz0iOIjk4W50w4AxI5Crzp6wAgChNSXYfg4PsKJnyvH26kR0mRZFSB2bGVBzVKnUJs6Q0xM/5qgyAaAyVnVqNsLuxz8kLSdCQYbBw2XW1FjUYBMgECSclwtmyu0NhtNtm5CtIoM+Jm7VxdRcFYuCGrUZAS9AlyRM2qSF1ylyLBAKXk/leltrrBpI+nTpKucpvoBSTbg6gmPamWI0K7SyD12QonVnVau2xTXWs5G1PDXWURzqvtVDlf117HnJc+2+62VbZxRZNCsmWcKFBBhe5TKAgoiPzumVUASm5A8ohhyqsTYDAG+dWTpNB9AHqePI9efMX0aDOXS0+ztaH81bHfOV331V06O+K1RjRdZJeDwwaali+eSq3aqwg4e0Xe1WUqTEFNRQ//pyBMZyAAACJ2JeBiSgAjqGC9DGFAAJREl7PPGAAReWMXeMIAQ5hxCcPPxVsSCZNfLFlq4UJ+9Bcn+iH/tJdil/ydClKRDf2ISTDqCZSoX/k/3FUvYSL//8+97uokLGdE/////us4utPIGNyn6J+0WmhGZzvAg7pEAtM9yDRn+cyt9vVrf5OhZSIb+xCSZUGlOnfOeEkxD/l95ssDSmf/9a3iqigAAH/KoFUwOCOrWWuZtQnz5e9rZZvRcjLY+wUxtJZB88dCbOgaeQLTWyUEJkeROixWKtGAi9x4qp+LdEdaI0sWY2HbTE8ASvfu6AAglZ//LIklygjZnHztRyQ2KlDZFs7InQMbQ02f5WN77sym2VN29PIhWHHkTosVj6gRzxW98W6L7RGlizGImoaYngCV79yYgpqKD/+nAE8OYAAAIUNd3J4yigRENMfQjCKYiI2XeHmEMhAw0tAYGJoAACvuplZRdhwsgTWLHeBy4to6Fse9y2P7Ub/C/m7NIyObbMuta6E3/s6pe8XlpWRDX89UoUCh5wlrdovsJC7rexAlAKbVtrjaRJUMJpZi6388MuJaRXVkcWfZvS1Wf+PUWocGCJ5EK8+oMmUVoHnnvPz1qgK7V9bBQRCJBUJyXcmqNDVfVGtAABAjf+KCb4Yq5e5k0hmY4BTqzMO6syu7bTM9SI2s2juUe7z8yMHLL33+/RCl2L8lHbRQCeVrSnETvpLWZ3TkQqdwVpFQgTrLYLltidlF7uWVKQnXC1BFlLlZV9VcIfmazTaO4z7JaHitN956KArAOUssOPV0pxE7TpLWZ3IokQqdwVp70xBTUUAAAA//pyBIj+AAjCFkBaEy8oUDsmyzJkInQI3QFm7DxBgRqgLEmTCYADi/C6oHeXFmZTqRXNGPGag7oR6Sq6q16eNbXYt9r4U6y22XdSt03T/p0b/nZWeK1+n0T40YnWp7l0Flb63UdvuCusCRqCq59hOk0DKb5WtZ0taqQpw3Uzt6GFo2i0QGju9GX2vj/b75f7p//M3/L6Cf5HWr48lmEq8q7/2iogBE1JDA0X2wwIKcM6AeN4bi3iPrPwrSoGdUbe1JEyXV1faTM3nSz0FPRnt+n8+qyre2X0Ef/RrGPuC1bmJeYgr6bekM/MOA9bzKPDY2d3ZfJL1Bqxak2xRMmPpxtDVM28ZzhSirJMHKz7S4jynreZtE5L9L37+7fof0b//ou4J3uokno9Nya4ZttjHN0JiCmopmXHJuD/+nAEROcACIH6NlozDBB0QqZbJmRlZAiYd2ZnjExBE5BsaYMVkACAVVQ5YFCNk2LhOJh5aVzIwR959lm8fTP3Me/SujS5+/uWUzmRW1drr9jg0XT69H2QH7Yg6xlvYhPf9rOn+oBwH1+OWAIqXw5Yp9yiT6xi3HkKExsFY5q3MJtQwx2e03o02/uXdEER9bsxF0RXqnjP/4s2KxWrkz8l6VX+T+z3794AjjkgNoQlJOMKXKEPbbYW4cyocKdyqk8G+xSWqwyqNTfAX3cNJHgYWRcNYMEjWkCEVSoxVaWi3W1rEHf9VKezdwZf/1AQAGinIWeLgrVeOfm+ztffu7KhhQ8pTh897ZDceEXzojNaVm01yfR7NUc67AVWVr8lpeumyhiyXqoDzHkZPnbnIgy9XpTEFNRQAAAA//pyBAWsAACCGCxb0YMTGEGlewJlImQI5TOFoRhaMQqWLJz8CUAAAdURZHHUMEoP7i+45WWtB93UAQg0YvKr0z22H239H7Kx2JVlSZu2u9nSgJp0A2wItb+KVDPmeSQsvf/FNL3WsnXqA6/2qlnmVv9SQxa7BVfTtSQYihpN4slctiZ8LR6Oj+wsxNB+wpuR2NuMeXf/3Twi7s2VFbX71KPenxkGNJ3+z/9hSEQejbekblKxbotCQcr4UWLDiUaruR+oW+xKcnhPWltEeSn4/v5JUu2VlbafrwZXLKJt/5667fN61ounJGa+RfWuszrGVEUAhyO2n6fRXQbN1aK+20TQDLkLBQgO6qkbdQQ+6BXVcQ7L/ZupyWsbdGkZMleTzDWYoo/c6/lVj1mryPXbs/r7/o1JiCmooAD/+nAEGN8ACAIgLF1RARWcQQQL7SCik4hMo2JnhE4BE5pu6MGVlgAA5RMSJTEmbSyNOXsZAGV20sUkPzeMvqiBJeLHuQaxLIznHVVFiDK17+/tdHBtY69bbt6+rVltCbU3mF9R6bw05rOPAKIETTaraTgy0uXWq6jsNHKKTrUPMu6PZjAQG2QexlrfduwVhaQrn2r486haSTiOpZtQw/3/Xq6ILUB2vRZM1hoANyXZmnJriE22OJsbXS7uLff3V4FSC4M0M6WMCpxiruP9tFX1PR2X7/B0rcgdXh5K7fQIdn3CjwpWQYKbplOjVlUicDyWmmkp8HdOv3g+99FZFzFoiCMe16PucK2SAMrYtLiraKfui/an+vxrO1DddStQebpi5K5lXObo9sva75Lprc5QZWcTEFNRQAAA//pyBE9YAACCEDTd0MkSjEKGmwNlJWgIwNNxILCjsQEabBzMnRAEVdSjGknHrNcQVVs8WJGrBJQg3V96lV2ZqAW2S+vSDZWWWR2dJ66anH9e381HqEtKX9v+5homxZ5j+SQ3Cjq3e2oASO22Mw820K+Yt5Vfjz+R8lrSBSQgpLJam/74pPrMVazOEvjps/nVj7qavqn1x/jW1/vZ0iChZC1ku2z4y3X/o1DABB0V/qzG3li5nfH+A85riX4IOjmXR6uwTZ+Ivogn/uwi3IhUU8qoWtWuMBiXRKmTaqixYszw6ThjRcSbPZyp9mUA3/9esgAcVlDiUfhhANTFGedWLSkgnUnSbd1XRSpCeyFC93is90uFX8/xz2Ktstr3a/+j+v/z3JPlStCXh7b/vpvUj6UxBTUUzLjk3AD/+nAEjCQAAAH0NNjRODqAQmOrihnnV4j1a3+hlFExEplr3MwdSAAAgAEttwOtETa4HgsGGlxQVmRCV5Ug6lEoR3iYv0HX8pvkv9XVjrorXnPTZtJTep70VvZ2Wzy6F9rbPu3+kBAaEU0ilGGov7RhuPmxQm3qNCpamKuvXTkSg0D1qgU9Sl6KZq4+AnQC4s7fDDbfbIXvbrrRq2aW357+rUrI1sUS0lAohyJt2SS62J0ypFMSZPZoQUQvTRYU7cJ0dYJ7dUClZMHlZE0S1cMN1R7/54C7qab//fyF8Fbo96LtieX9+6JfulsoJ9VuoAgAblu1UAazJaHKdcjoCA0SpDApFZpoizSooGGOaClFoBz1sD/0I0zm8rt/sZ/0LVuu+3nLLRbFNtj85Vq05Xb9n1f1JiCmooAA//pyBMvHAACCF1dc0Cgo/EPliuMzB0QIxWtvQzyqMQgWbCjMFRAAgvkU2UnUqYY9IQPeAHVeQ5c2IUo+EGq7u9MQ8gijZ68tWr7o9Mx+iN6f/szMv0+n2/9Pp9t8//9tF0jhFIgxRpoaAI7JasC0MhLa0ezYzxuLP5gdsRSxvP8rGxUelmBQhtB+18HnsT81OprrRzHvvbR/Q1noIjpJMtedWNerVo+RyGzf/6BABqJLTSUApXuUb/ZUZKOthFIgyOiXCQJWpczQm/hv/qj9W6p8f6F2USHLnXo6GFkdSX0rz9P5/S/OtM/r//9fR2k3Gu/QQAIs135mHYGDLCRkbyG6zoSQqkfqlt3ckSDY3EgzHQiL+A6e/v7M7k1Znqnq/x9JRYcldZou8l8/t9WX09NH25/WmIKaigD/+nAERGEACIIVNVmZhxNoP8abSTxnd4iZbXFUYQAxFaLtpphwBipW7ty9dPVcX0vrZLL1w9N2jz33pqWq5gmRyrKKuaVCz2jrn7EY6OxFrt6/R/VvT9QSpGvLulKy2H9mG2Wbv6tO0NgAFANKNr7c9cdWgR5+sK/AfEWPdsOAQmj99xulAAerQID6yo0rkW6lL+vm+v0fx1v//LJt3f1OofKZR1vzmz1iySW0k7qU7MvrMubAW4zshZ+0ONnOOREoF8o3m+rJz1e4u1QZGZqP6t5fvUlRpdfv9v/t6fX0/t1+dqMwyX2COFW17gwMpPbfC8bR39h++L4rjkHixw9Ssqd4rFjM0eSOSAHseZNLef8oZui/J+pnoSbY596/erU21+d/9/RvKZcIiQxor2NdO6q0xBTUUAAA//pyBEtsAAACJEHfVgxABEQhG+rDCACItYeAWJEACPykb8cSIAIAFDpmpya3bXb7YDiw5gvU1kwuY5pklkCZ5aTVqyT9gVZwtrkldWMxWs5bO19JldqSTpQmv9GpSmk9a2lgwEneOvynttAAAmRVjdk1u01gFjWls7M86L4zTktEB46q+y1rAdeLISdOx73LmbkllKdigYLvJklrS08zL366e/tcXnP/+DgnB95woASUUUVXy0hvxtGTNpdvrQ2h2/In/y/t+n+djuQhTq038QYIR91CoyP/yE10ZJ1WdSf/fqcQrtciZVQs////n/qhGcbjEdJJK/U1Bvp62TNp9Lk2VaDbvkRP/X8jOy6P/Ox3IELXN/EGACPvLZH/5Ca6E3VdW/+/nEKHoQhMgX/+3qYaxOmIKaigAAD/+nAEDrcAAAIbB97PPEAAQqI73eeIAAh02YekBFcRBgqutPSIaAAC++pQILGHWjfGgQ5s1rWkwiKuhqt9VtBgd2i9ck0QCgo+xtQKxVFxG5ZI8dEolKFd/h5m12ylQisQ2owjTi52YF1AAACuWtpEkiCxhXo3woEs2c1ermtLYK3GJwq8gIVlEOHLTa2uSkBRj7NQTiqNly9xIXQtF/h5m12ylQisQ2qjTt0LAKSTldjbTScCIHyzcpfbyIdfbX7hda8bfrRwi0eMhcyPL2uSILdyO195yWj/bTOnEma4eZkeL/09T3+lCywtN3XjalgAEBmywoAgJJCkCBMNvuFbS9g6fymH4YWssG1sUMTgaPvarNVDA294rd1qchulx7BZrnmaeL/09T3+lC1C03djdaYgpqKAAAAA//pwBCOXAAACEyDeUeNY9EMmvD0lQoeInNVgLTzhgQwa7vWElCwAlvpWkiki/kmVVmcoCUkMQZVnP1DUvV/jHsYspmdN2OvQ2yxlzf/9w02Nh2eKsksjTz/LfkXZnLc8d4LflSL5YO3gptKaR1pIEojExqUpquyshsB/EisW0UmJtpUs3F5LMuHDzHyV+//NmfNlTdW7o/P9j/4r8i7MrlueO8Fu7EpF8sHY9rQrlEnAIVDG0oXkjE4YU2RHoloLw5dQwyYnGN4HkbNKbY/6J9vVboy22+vo6Oa/766you1OsNNsA1i9T2LoRd/pTo0dYABAUsl0sjlIRwIMytW/D7LjBpIeRT1FGpGjt4T9A98Hvsb7eradls9X9qGRy/+urDYuqqJXc98ipjYBca9Hvfur8kmIKaigAP/6cgQoxwAAgiNAXOpHKfhEyAu9YUULCIEBd+SMpWEJoCtBlJWYAAAC2S01kkoJeSrG7U63KqIVxaXmMKiqFOW8Kl8kHLXFKcvkah3U/Jde2z7VvoSvvl8LFv5T9/pEfsoCd6TP3v1EvX2AAACWua/WXYgPK0dq2ysGHZQ6slxyJx3hEfao1rY639hBvOfdCJ7VZNuZ/9+1sb/oz75m4ia34uAxrhxO+rcadUSt7vSAgQAKSpTX7W2BBJ0cSvLpiDzFiURkDXTjvHBqrcOvNQP+vNGPkVV1KaiNNtpRRpT579F/RQT+nR/4g+d05B0LHdnO9AR4spYk5KGxQyyaE4yxVR6iHJJh+A1C5Ys30o2kEd3DgaWkLHvxefTpGUfUm01uZF7XuR7r/+tR3//+Mf6ftv/6PqTEFNRQ//pwBPtlAAACDR1WiwZTIEHDqsBlKmYI1NeFoJRL8RWvsTRiie8HzDI8xa+/fH6c2Rtvaflt6IvFeeo155Y7WnHBCzKoODbxK2qPvPfymUdAI3mRJUIlvc6/Ud7Idcqn5NnsO/v4F0mS8bpLMHWpY01h3rMajaY7+AFIXANOFM7V8An1seiDnuaGV3wwZKGmTs8/yj6mwKb10RQjMslOp3O01f6ldin/80EEiZq5JZI5TAPO6qN55CGE5PHcoN6AK3gv7VYE2l90O77+WtqmCNpfpSfKwYDD6UzgnMv31k+57Aso2mhtoFJYxyhRcqTvCbaOtslksc3i414rN8ctSVjqkRQbaor2D7eE28Fvnf0/fsLZv+Vrn5d/berIYfqy0m/9//RqVdds1L/1635TMlNujxq2SYgpoP/6cgSOMAAOAhw11gsJOzBBhAu8DSdFiGRhViwlbEESGu30ZZ1cJ8gKoRTuPdZ6pVk3aPwArFNCBD39GJMjHCsIfYg+Z1XiK+6gHbygwnqfz/mbZ3r2mPtVv+r5vj3u2VNr1rm+lP/JftBAYFTV3VWDBm+s2fH8ZcTCNjePpK5V7s4PS6rguL3VH81/X45cLOiacZXcjFwFetmpci0qWI/PwkHIoJrvarhn6NpyM6GhNamhgTInlaPptUwcyzTuLkwVtcrFEYaFvHk+59wQTtl0EK/MB32PgxKMgd0HduRkQ8wk6N7/Nuo1/ZT7//SAAALIXFG5fxAPMZCUO0jcH5g4tG3VWa3Uv4HPbFjdG++s1+q+aW9e05/T/1b7yNtb7hjFHtMo06WqPAIXbrvcuIn9v0JiCmooAAAA//pwBGgrAAACDzJb6McsmEOGO/0Mo7eIhKVWTDRSgRcTL/QTFDYAIAHtNxy2/HWApwGQQjrJORR/SpHNgb2zC/hH8Dv3Bran8Z8nyv1/elS95vjkhK5Wh7n4/ZtQLBNiQPDPocZpf9YATAkakbcbllL5ohm2FNEItuiP+D9KgZQ1AD9B99H9Pp8tNfqfJXKdOfwhgq5NbLP21EZddw1RZ6U3UsTWl/HqCVDy4CNVM27VSlEY1KYch7r+IYwYSCl3X1pMKuXZqn3NYfCPnkhyltqxJGXYojzv4YbxZHJp6t5R4wjndqb6vs+xlv2Xf/6CnEDYy4pG5LY+q0Aod/PwOgcENPQd6P1EXrgBVv9/Cmrqz0QSn1FJgN5/btkXvFKhTGGChbXgsI1MhAg1DL8e+gm+RtT6UxBTQP/6cgSNCQAIAhQpVxnsUrBDaZwdJKJ5iGSlZUYg7JENl2zclpVCAJJltayxSuqhikreKeZWjKwD4xyws/ba0b800Kw7sgYJ4g26DXzTPt0Ifn/b5fTszmcP3+8MKEr5bEkqQ2/+p+z+lpxg2VOSOSXVVZEPqvj/XSfTSN9QELNo/QIaYP7C/X6Dev1+3x66P6PpRepGJrpf9Plb39acjXtYcRD+ke7EaKY1adwaBEttyIfvueHBnG1yoT9BUt7FmOahIW4ygvpiosjtDi9MXfN9F9X7sX201KDSsGhsjDx7Oss9rrteJc3kl0afv+zuAICk3HIU0GN8WqSk2NwoCEpw1WogbTMGvwod4CvzgNXKX5d2V9HEnXv6CjLzXxjyxS4aNiyUkch87toeWuZdv/8SfamIKaigAAAA//pwBPfUAACCDFre6ScrTEBHu4os4pWI9SVzoxxRMRMjbCj0FagkEJKNKNptyp079KO73QkFJZzuqKJHxnx4OnWARq4df39H9X9P/i/WttB/oZedl69/5/f7v6PrK/pXl++tfRW8brAAHgklJJzQbWpdTxWKjnJMdxvSmNS82rhycTP4P7/f19ie6ff5dcfyJ4Nn0P/1j4k04noCZSBCEWvepeLury+kAAEmImJpJOvGbh+LvvEBT9x8Rz0En5f1DkRh8IDLugm9S3wjpUrPzP/1G6U+no1qs3r/9qUt/1v3CPj9WU22NdDdgQcJXLGhAIFVN+8Mgm63pmixYlXSSoPojroFbUXeXzwGQ+mfGiaL7FTFW4Q+X5qZW6nFu526l+nyfb1+v/uSnL7erj8jtfZulcqmIKaigP/6cgQxQAAAwhYsX+kJLYxDY2saJedWiHVra0SUTVEGKOzIs4omSBJK0r8ajkyD3vmHb7yfxEEHKRAt96Cn+WCMEDkYMMaaVg7eLehft//1EPjcrow1k/vYx6SrqzzxT69ejP0kX2WYcAACQIhFp1YcaSkVGZtSbC8EqyOx0Ie31uCqvV4pFi3sWn4LmZFl26ku7dUer1a8royTLd2/f98U0a/opJRWOYyAj00AAOhgTcsyjan6RZdv4h4KbQAwGmMoE+O8LDJCwMOO+328EL+3gvt6h/m+/lb1+T6bsv1/L0CIlZ//v6f9Ua3BDaMPPUUC1R2jhrUTFdXstwfED5PSc6FDEzUO3YkDE1E5tcHvyPxfo28J9vn+n3/8H8n//r/8I/UDY1iD+pnvCY87K/RkkxBTUUzLjk3A//pwBKf5AAACHS9Y1TDgAEOFKuOsKAAIiSFyeGEAAROkLg8WIAAAAMAwLdv2uid5MQEOlZHrAiLHUWDs9kI+eCBmeKy0wwcEk+1TvT9/tqeXM9eg4NvU3oOPhjZKN9b/RiHJ/dWe9Xb/SABHLbKX+icDZV7Mjll2q5UbFWw5eyhw1ytzZFUnCAkueFGOWMMHnuF8nR/l/TqeTt0foQE2F85nK2VN9f0bMns3b/UUQCGhU4M0Z3xb24o+SwliTgbvq2HyPAma81qs9r57ZP//7/+9zfR1f/nnciq/vKYvnf9KCTQiwr+t4fDHtFCH2AFLxOAAAERQIKiVIoLTRcnqpqiVcMmguq/Ijh8Q8ClvM7VZ7X/ZP////3379f/POciq/9C//pQSZwiwZ9Vbw+GPaKEPkSqXidMQQP/6cgQq6gAAAiMjYc4UwABDxTw5wpgACJyReBzygAENhy+bniAAAQAoAg88wAIAJzmWhAJSdyNOk47wYr42Ntw6fZ3fPIbUKgQiTdm3P+hyA+GlI0SvCdwWND0FwIxl37l0Vf7g+n0///3AAAQAQeeYAEAe4k9CASIXipZ2/5gxXxprbx0+z2+PIbYqBCJN2bcfuhyA+GlIt53zn8e2WZa4PqVR+i3s/oLv+///9FdHqSB9EiyUbMXrnPtTKxjiRFCgsUEykUfVDGEd3ctlmEUf+sy0s4sDJuPQLCMqxEUQtO8hZHte+705KhA97viTuPJ9yBQIKs3mLNTRqZu+gb3NSfGMElw6VBCQ+cPuLHXG3GCx1+dhVSDZ4DXlRYuVYhKkL70WLW95sk79ZLIF3u+La3DpTuKpiCmg//pwBBQVAA+iEB7dgG8YEEJA+8UF6QAICDt0BIxDwReH7oDEDJhwuQfsWV3GYhFC/qxRGikauc18m0wLbjQ3WTfT+DHBIPB4UIAyfWzE65NslIMAjJhJoQyIzSpbrHp6epirSimL/5IBAGmjNErQnalSeKWo84sEgyYsAy0llFiw4KFwFacocDkHpACn1sy60JpJSDCl6izRC5jrEqGuex6en2bSiq2b9uSuDQ3gQOIsF84Z2/DJcELnINiJoGIlBwEL73BEYbGBU28VLK3pkxQcSYPRJqeLlRRx4ehCnpS6eVSGjVKrr7v+vGSuMLKnqiaPODHHt/3JZgQMScWHRQHjaQADAELk0zIRGJUDaQKcLK3pKk1DmsHopS9pUROePQhRpKXEnqw0ilV19zPQ/WmIKaimZccm4P/6cgTvVAAIIiEaXCjFGYBCpWuFMCN2CEitbkMIbUETjS2UYQ2YAJCbSKOAgY3QXhHD3IQj3zpWwtUnlpidI6BUVxLLSgctY9TjKlrEMPgyGnNah6GAVT1pS3ZkO4A+zza0TVph6u61bgMAgE7QrRbQ4bxTaNZ9kX1d6sKOJTwPIamh2WWrlc4vO97ZDh871FLM4JEixY+gPxQC3rSnsqKu0gGnZQ7onrdVd1GkAEmg6EjhZGnfD8m8i9XL2G0cJ0cNo7AVqkpL2Z/qzNMpG5mv5c8ESLH3FqwqhFl2SEpkUS+09grar5l3bOr70tRpIIePMPQSVbsFw6NSAPU7eMvDBQuGCl9g1UjCgEKhmsFgowsDQxbkDWFgkk/TW9GRRFsqZFC22m2Lln+ZcpimxZbukkjSmIKaigAA//pwBH5CAAyCHR/ZkeYTQD7km1kkwigI7IdcB7BHwQuL7OTCjaiBBqkjO1i47W1izJdWYTFPXzfWkK31KsquKDPRy3u5XN2Shn4OTAzHP1kivF4hrPFYGLB3QuMWtt65IZ2zmWN2+l3/UAAWiLNfjx4Ssh8J2HkJ9hu4WiO6gqcRMaCG9b6By309/3y5I65EkuLxPHpHsNSxY8FH1VVtp+321Fn/uXT+3K2To/9D8NtRNOloZqReWdz65kMBZ+914nDCKZlKULIiAKThxQFvUM0uztc2DLBAgCcQgk+dkpAwcdQjOF0s/o366Oizrp/cGCFf/sJ0M6HKprsfKzIPu2AinZ1BL4wVM7ocM7rLRQz6sxoS8RH0rQYD7w7LH0i9hJiKHARg13JJQSvZOZSi36afrTEFNRQAAP/6cgRudwAAgfAa1wHpNQBB4wrgPYZmCSBhc6GY6yEci+ukwxaAg6OgnGQZR7Mzk6SjJDLazeU19b3h8Y9v4A9mXCW2KQ5JmqtzXWThpzAyl5GoiSASjlx2K+gbsK1Hcs7//+hvmK0nGzAL8zbzYv7CEJQdPAn2Qd8ObMJAH/LfXG130qmZgwvB5gy248KGlBoRrKTZI0oOqJrWdGUzt6Zz/b0s/9IIAAdzl0kjmyQBzGj73Ss7hGyfh4E5X0VsXhUWI04epdwlJhoGAofvUZe0BiMi7e4mKzasuwYSfzEksw1X+97F3W1WUlQjYjVWEACI0lHAexW8BYVJmtUh6glgM+8BD19X4o2edQhl/1gQNVYpGnuBQIWLWLacDrYQeihDHgUxdKNP2k5e+jCN6es51d9yeqhMQU1F//pwBPh5AAyCExrXkexRYEJDWuU9iFIIbIFgZizrAQ+L7CSXoQgASqo0Hka+hdbUP7iUGmgnacS5mfync0C2c2E9jNSdc5RadKISHygmbSs6thtsIHYo+riI5THTVFmz1BN+qScr/6lgEB55y1Ls6Q9OMkrk5izafjKuBfNmunUbC4J2/AfhqImxeY6wbLdxRp5zXKJucg8lq5+KCqFIicRCtu7V7v3av+lIIRbUjCVIeZEfMn3EUF8EGuGTcajd0SwZvRYQnzVYOvNUg2yM/W7ZRxBaDyMpN1ovE6r0ooLIYR/F9lGRjFUL1WX/FgwEr/CiYIALYhVQduxV2ORLVQHWLDIGB4v1Qbi0kDV7nBprxRtCIEJPm0lmiqjilisclA54+cJsNLNBfv+tMpd/dbfWmIKaigAAAP/6cgTKvQAJghYYWUmHRDg+QvrRMwhECHR1WAewbUETDSzoZJUMAACBFmrz1N/s/qpg/CTBq/pV3puJ8UB9coMrxmqSAqqTpuUfWQiqDYZgwytq5SXFzS2qDTL5tW91lSTHU9xvJf9vqDwvG4DQDK0Mvpvy8qpGio6MY4Oiu6gYENfADAJVdUKj19wTp/Q15UHE3MLuQoTlFNWp5UkiVwVfa6YXr6vrb0clkq8QxVqVnTp6Ap20T9Ylsy7K8UhZ08wIz8WTZajy+l8+7cmFVKqdKkHAs24E2Dp6prxh97toVFxmhnYeqGV6f2huSUmVNxwWExFnVGgPYDBy4GSWFHpcAhi3QlnhMfatH4iYWnBbY8ee21H3xiyTiS79vfUMr1dqIvMLSKuIHWMPQlkZAwmIKaimZccm4AAA//pwBAwVAAgCE0BZaYkpMEKqy8oUItvIhMVg5hyuoRMhq9zziagAAACIACOS/wxnzzgmOT3koIUSt1DFZLh2rQsf0M/Gl85fRu6s5aqS93S9vnTjLf7PTp6qj/6fHZXQrj0CwoxN++KMUvykmkk6pwZKEM7QgzBlRXS6eN0x/hYxuJjlrMnl7HuUfX2uX/hMy5amutZr68P18y9a7ZxGt3ZgtNJfhVf2S3urcACETLDUmDCQTQIWXiiAtoVZrB6uwrHHNxoG/GGbGgTwmTtP4jXm1r8376mdF7XxKq6TTutztb3q+i9D6Vp2tUh7FfSAggJOS1rimIgbRz8bHOtRF9oeNV3Gh1Z4YrcYIUzK46CLLg28GX2Pu7/k3f7K2idvyV+ZYNn/UtX8qax3qW1NhDRp1RyYgpqKAP/6cARsfwAIAhdC18nnE2hESivNBKJPiKUDWEwZTIETmOtM9onoAACARErqyPDLhg4VXDiXVgslUnWcHrqVBA26BYkp9RzrB6Ytguf29JslKdU8H9vjo/b7d/q1/8r+GvwiZh9urbvr1AAoBxFJxNKWkOZExxCFtINU6LHeMHb19AXwdtiilvFDlZYo3Wj6/+hvpz29drDf+30+T0b1Oj6GRmn+O6TUwOF9qPsOgDX+EHN+yKoWUW9bsUkMip82sWokGfO5AJjsiBY8Yl1PlQ66YxFRuxP5X5utTKrN+c7Np6r9n+vs//O9S2XDM3l9H/3awC004NJoX5fllNJdQKqegE3kCZS+MQlbnCVUppLhlfkU+7zg535NPe5T1YpOZkSiiEd3X6PwRHr+sam1RLbLHs5WckcTpgD/+nIER7YAAAIUPVm55xNkQyhsHQxFhYida32gnEvxCZetNIOKIgCAmNtyUiwHthlRM61GLpI3aqfbQv54x4o9Q71B7552IYGYjTgBHXEoeh16qX0+z9ae2qRHrlwr+wS4ArO4c+2rLPSCZTzSr0jl0WXl74To/vaVQX4Mbwj1qEh6HaK+pvQEYiDAoQV3Ur9f/jPz9F+329fiP/nDtCMF12PQ+Hm1PqP5vchCIACdRUcjblJoUa8bD7YSDdzsv6lvEBa+ESTFUFzdTdaiH1P8K5mlBNWqffzP5T+ZrUP8vyfEdkP0VvK/d//V/V/T4L6QAAAIAEW23JFho7FhVivB+sT+KOURSkvHQx4ma80QGUaKm6G7UEGK1m2ld+EbobzPtQfJ5B/u3boo/dW+z2k/9m3emIKaigAAAP/6cARHbwAAAhJD2lU8oARDw7v9oZwBiLF5flgSgAEUpC9HBlAAAADQottN32vF5MYce/y0R4u/h+JIoyHE5QzwkGXUTFhyagLfm2mE1WolfsburdS+7ff7+n1+novy/H/Z//U6kx6qUgki/Co44nLGdeqb+NhDPAwfCIOC00h8t48WupppZOBH3O8orVs05LbCZxk590V1a8VtI7Y/YpBUTdgcHHVLm3OIZSkSggiigjIpMmT8i9f+n+iV/yLru22NcwnLZ/ITxVwHETsLXV0Vz/7GciMdjZbrMb8532IVjiUhFb7fk//lVXmRXV4h1tjwkto/vnOHGwsCT3W+ule2iV/yLZXv+NcwnLI7chGbFXCYidha6uiud/7O7OxyG2urGf85+zLOJQQbornPwm0WWKvZrJx6Ygj/+nIEM9oAAAInEl3vDKAAPoHbvOEUAAjM2XHChHcBExsuNFCK2IA0Cw3EQAAAuUgIAZvfD83+D+ZrUMCsJHF3tDqHJI1AVAdMEn9RkTLOnSRelymrQLHBVjolv30JQpKksexeSJPosq3HkFIEAaMgABWMIMESbPM2czmjQZ3xh/ddanfjnyLjJoaZ8g88LrXF74qpo0pais7+57mPYOTt1utDQtczLLOyoAAEAjBt4AgyMdTCjnxzinHMp2Gim0jeEx3iJ1B5+vpCK8TnNdZzAQmnPOcZr+BUkOCx6Suij+hSSjhp0qZf4qvjYuS1qliAgAOlYACQCpBVYwZXFLDshVeN+MH+A4ImERNqtz9fSEV4nOa61gyIZ2PLqz3ReBWjhzJK6KfUko4adKuf4qvjYuS1qqTEFNRQAP/6cAS/GgAAAgpAYWjpFgxCxstsGOKGCK0BYkYcTYEGmquA84mwaLhU0TkSIJVnSo0mZ1NCaSqqR9PhC9aCSa2UEtS2Dfvf+Wq66s3l+2rfVP8T/+8vuCe/xEouReIDvTWCvPDjfbqAJQD6HTK34TOzUMZ+H+htw9qP5V/KP4QmdBJ9CTUO6/2y1XXVm8tdRtW+VH/iW+lGr8RJkXiA701grw6BDZDaloyMAZasWtFV4a3nczQR0h9Uk3Khvwifzi/QNvxN/+2kyaHymUht3H6RNH0683gif/TrsQHava5AuKIL8USp7paqR4ECbnrATYnUOQ12Z7XNG3YUOQXapRR7xCXo0Si/UkvKAWfihm3R/9DGe8+V1Y2821BjIajdebqCd2uZoXr511kvb7/V8ndpTEFNRTMuOTf/+nIE08oAAAIYQNcJ5xNgQogMPQRFEYjU12uhMKFhGqbuNDQIXBfkczxNKCT01lTF+xbp0I3PFmkKgW8i/UgX7A+9xh/+zazE0Zq/3poi6mY9TKmXwwmj+nl6cFtZq0Il/ctPW7fhuipp1hT6RyyNSEacKB1XeIvipaQFH1wiP6s3g/uGffyb7kzDSoiWrTWu1VVvRMt6jNH9f/xn8oyEnktZRTwtw1bzxvjAAQAbSiYWknFXU4KI4u6hZ5rqCr1iluFP44f0F/Gt/0XmVDHVDXq/7WQyEdFK6LREb7IDQgNL2ubmkU3imNpDerleEHVv9AAjAnaIVkcj2o2QL2nhDUeCFI+E+j+F9RXqf0f2tqYPYqMRVe57s26gwVCMZbWZszFoP7a+lUJuZqU19Hsie+Qfa2K00O+lMP/6cATCEQAAAiMr3OjFEvg9wvrRPwdCCODXbaYcS+EJmvB0U4sGYAYN0Tllsl0p6vgyPxnDxuMe7RgPTN8JP0H+Fh/qU79/u+7zVn1mZdlESksXeq8mtw1dZLT9NzxZ8HhXZmWPh3frTMVlO2n2RRe4Z/LFLWpw53xFTlhwqvBQnMxHDHg/L6CENeourRn8DTQDFR7vEtQeyfJuc8tuonfqIne8j2XOyKQJCWhpjbblZlEWOB85yv+axx6F2e5wapq+4+C9+hniEC3dH8v27Sdk9zLoxOlPfdqVABp2t6CL6G7dd1lCdmVY1Yfam/bNNmsF9uORttxTkEKgrLtI+PacqD/C2rUBQ0+oEblQnfOT6v3l6NbRfNtlo/6XurJUROxuepR72vFA4tWhv3up+shc1MQU1FAAAAD/+nIEmf8AAAIeF1npiymQQ2WLfRwi0wiss3GknEvhAg6ttIEVbAAAAIwFFLbbrjpbuCI/27OBK7VHtoFi3qP8BG4wD9gi1gEfUYrpRKimC6GgRZNdJkiMGnXOIo55t9e0mVl9w6r8oR+sAIAW2JSSSTKxZY6SpYNQs8q7NxKPXCEX+EHoIvjoo7nfJ8j5ACDrue9wzNtvNsqJlppElUWGfqWqhF3ytKgyK26B1YAKCeha2ts3+PU4OPlKpmZU+o0dsqQ9S/QeFicbfDFzUf5m+PyJbRuivVWKNQh5VZFNEOxqWeR2ecFXNEJFHp6HrW6slpAAIByBTjjm+Y5Wj7w4QwTYh5EiPQfwIboA3xH7+Mw0+DSfTKoehOo1eessenr+WEs7KjRcDShZDFlazJ9TWe6STEFNRQAAAP/6cARxmwAAAhkX32imUqxD47ttGEdhCJSlZaeg6oEMlKuol50QQCQbzCkbabiFrMCGtMxlfiUHPyS/fyMcRpU0tSoEQZQJ9ObpDjVC+dUySVUteW6CkutyCNbYHOhINh7PI0XTZ2R19oAQAPZSUskuaUkdAnbPQC4ErDkGRigtpuM+FRhON26ipvfWhOkTUsDTz7lNigvVg+utqxdFX07clVuenvMSmk36g1RsAAABpAgldv8l3FdNpA3CePpxTqt6A4f0yhP4+LvC4wjSst1Hf/YefqXaiOyKxqv5TAHyedb5ux2r16D1z3V7L5bkah9QAAQAAJJ2nCokCcCIdpbrJZvXaB3gvheViAS5lxCNvEIx1GvqBv1+d7v5jP77RxUlrVTtdbv/9bI9bxoxeZqpfS7/uTEFNRT/+nIENG8AAKIhMlxoxxQsQ2L6/THnRgiox2WmHFEA/5GrqMepCAQACcgCQkUp9sjyO/fr2J/4OEpBSKjlsr8Qt1D+oA//t/zyl0I/gdqodHbfRFHdacszt7YQdU+Vlhmpeun0NSlmdVvIgAAAQAABKXaBUWSxwriebs/Rscjaz8G/SzzBQcpjoIeVb3fwePq0bdGyg/It30mqyNloB06Pk3MYiQYyhJpYafX3UKAAAAxACl2/+o1ROjE5dWO7AmUOPnU3cntI+Ifgq/jT4P/ZvBN0CvnKvo/kJWSaQjlfNDnql2YilXaM25Y+YdW1vFNOnFQwjttyMUV7YpPl2XZhHqF1j4YGy8oLPkYa1qI79BUblQp/t5jef8qZmdA9BYw9Mi3Z6shretODzZq1qvI06x1SYgpqKAAAAP/6cAS59QAIAhMgWGmPOgBBo7tNGWU4iIR3XGY85METKG1wkxWOAAAAoACcmu0OngtRNYleoyjLcRmeD0vIHlH8Qt4kjK6CP5UQeUM851uhGoS2JdI3nEw7WbKXLUyTH+f2/du8pt91pAAAEIQTbkkLTqGEJResLZTv1T0lNIKeNHVeCBq8P+IjvRvDwZNGEsDmfqfVpfD+ISp9GDTS7az1YvRrdR8hV//oACal2ZPCyG6ocGFhtMlgtFz1zQ7SJm8QlugRDPUTPyoZbub46ZcH4TM5+k7J6MT5Jz+Y3UIhZNNp/+IGuKJrdUOf2YuAEQDCQYVu8dHnCRja4Oc633BpgpJAXwtvEn6hx+op5m0hEu1ft9/duo/7eq+rUdF9WXk+z/+/qbqtD6eIHZQ28q6pX9CYgpqKAAD/+nIE954AAAIfWt3oKSlMQ2or3RxFqYfko32jHK5xFatvNIOKVgAmFY0iWm3LTUSFO49fFPhYQ7sJBnkbw58V8avr8SL7+cnp9idTeNt02lPbQxeNZ/8/1T0VNWb/nu+L/OVOZNlxmV8eS2gFrGVXE5bK8bjF8fo+RJqcaXGr6ACFqYz+I8H9fOLFGMuOXOMnv4gnYXJoE29Pv6P6/X1+v/qjee/+Ltgdij1cgpENp2VxqNxytPvliT82QTrzSktY+Eqcw7oYcmgI/G/T2Dj9l9U9fQdpzm56wKIBMJJQr7dm3dU+h8WbTs2J/USYwXo4TG05en8ShKx85GVfAnTVQy0RhEMUcoJIyfoLfVvTygDcr2ml9fQv38R9V6m+3p/6/Sf/jW5mpZ7VCfdOC7F3bqkxBTUUzLjk3P/6cARZ4QAAAhY21x084ABCBzrnp5wACMQjcviRABEWjm1PGCAAACR1ubIapRFB0kvOt5OrjDhom/7m2eKzqtCoc3G/oMeOkvH/Rfc/0TzPQ/7+/3PeyuzK5V0uJvJk57PbqtWV06GWrAIAGnZs2Nrx3AWEPzPaAFDuvM+9UbPEJO9RCOvwXPxB6mM/H26FaZ5f0X09D26H+c3utqvt/Kt9ue+705VFOQOWrABwSMbbcitdtkQYc4iepBLJSq6daRDrSr2d9w+BEEztQNdeARxoehLGD5ho9y7bq39NpG0cNvAHMZ5zWblpodRNhgHEDiBKzEc/f+/77TiFRNiThodKw/UQ/EjXZwUPCYuwm0Wp4K0Bj+S1xKBNI4vr6lLss4uhGLXuW/0II2vcu5UWMTjHN9bk/L6ExBD/+nIEyhgAAAInMuEWJEAARMZcM8KIAAiYK4W8sYAg+4yyt4YgBgNNPPPPuCvB34KGNl+evYnOQyeZ/9f/9f8iuxDaLo7vYjHEIIdAybdqRaGIEKjTKRXdKYOE6WiYsd/Acu95ysaKsceX1sCMQiQSCQbAOwLYODnNgquU8hk8z/6//Jr/iFOxDLRdPYIxxCCHmTb+LQxAhUaZSId0pg4H6SQmLHfwHLj3nKxqxUaeWACEnrbYkiS4HgGal+xty5/HUSAJ4ydU6tr0QLGjDzvKuOkXpr2MOlZbWmhSHjmAQjKhtrqVyihYWbIIZaxF3JXv1sANICZblt0jSJK4J65IXY/rIiDby6WM5xQVdW3K1ogRHX0thoqp8dbULFbva+5hmKXPiUf+cFRUVqc6tVyOt1PFkxBTUUAAAP/6cAT7TgAAAfw2ZuhGEow9Rrw/JCIfCMDZmaEsWDEOmKyBlhUgCbd1n91rbTk4zk0PhQqqu2ahSlZg7F6U7oqPoYs5HqnsZw7K5dq//Qvv81HmRYu0OsOr9f/CiDSE7YFeNwT+oAQCNDeJ31bjeoNu0TkHMR6kTmSCpdqWYnRPdARdv+afcu1f/oX3+ajzJi1llFbb8X+UyzO/6GxIfezszKhHVHrfbtZI6ggciGS+Zs4RML75Weuqdm7n7hl23r1I9SGBlbosrL+qPo370ullEKaWdG2kcQvdCUqIn9R76/WsrpChWp5scQPhSD44taf9sDF6VUdmgr21szVf7WN7AQe89yjGyt1eMNdif9WqVvWVvrKj2GM9tBZWsI/anEP1iLxQ92wCJU9fsWmIKaimZccm4AAAAAD/+nIENpQAAAIdQFy9ROAIRMgbd6wIAAgVlXYZgoABD7KuQzBQAAGJyxJuBboEZHAcMk1llVR5FPZqiN06N3NO8TexzlQk2M206psc160fY1Oc//vndyrW//s6WJLStVbtqC1+i31fFfAoDAlpZbi8wt11KkWt5+93O0mNXv97gG4nmtXQ7Yb10EWluwv1s7rb6PbnagtdUvpzeo3/7fxVvlTVaXHi3o+y7EzrYiJhwZ4ePkxKUymkpN1qfurf5eJsNS7POtEJ+vX+nfp//9//+x7f//6EIJo5EJ//8/u7OQhpzCf////7uQ4azTyFFgjL7DV6FK6G4xqgn5Xyk/uFXKKCcaJj2ceZEUn/8/m8v//b3//7Lb///QhBNHIhP//n93ZyENOYT/////uQ4aw6eQosmIKaigAAAP/6cARTVAAIAhsrXtcoQABC5Ww94wgBCHjZcUSErsEUDK1cwZVIMALuK2MgAJonnqefQl2M5/DcjtRrWzv376Ud9CXmomqX7dWMhNxhKAHUbr2P1pO/l3D8VbU99j1kXKvYskVP1Nw8kiHA3p7/t9USRKeFp33jMzSt+bUM9hnamxysno/TQXelG76F9+3VjIR7jAqMdRmL2PxVJ38u5+Ktqe+x60uVey1epuSMNqWOFFQAh1pvbSR+emY/EDOfYRytUYhj9X7PcHpTu9VSrfsaytmvK379v6sYWBVzAEG/7AW8krgq6seMiLPe49qDqyABGRtElwCDonlsyuhjazK82OA0wzYNEcb/Poj6JwZHiVh8N5Y0ieHhpgxnfUgGhoayfc7BsFuHZbgrrfiLPI1nT2oOlUxBTUX/+nAEWUkAAAIZNleR6CtQQ4KbIz2HDAh0iYFUVAAxEpst3pIgBAJqoSBdmIWZfy2Rnwb0FNqwm6QnoMp2MuTwahhtLEvNDGYN8ipn5T1ACdV0RNM97fTbrRJfZtU704t6Y1H/aind3/WCG3ZcUC/cHSSXnLBDc2WeXqabbg7ewwOauVvWPl8RmK73KfIOUo0hV3F03pWGQyiWQVE+p1iKOqzuMJ9s92r6g3cVUI0/aMrbbj2PIymUhzYmAXSv2au7n/sZf1X40iiALtKWpubnZKTMt87W3URpPZsRPnXSZt4mSty0b8zgjU9376iOR1LCCUrJLcTjTxslPKJZcJwk62MqAdzNV13wQ9XG0V8W+t1v1Lo53PW1Fbrszk6cqPv0GuelV8vQLOY9sTV6KveVcuRyOlKYgpqK//pyBOD+AAACD2LdlhjgBEGsO6LEFAAI8VuLuAOAER+rcKsCcAAAEEIIKQlVnGSbFtmKxKVdM1qHT13/+n///8z///2MMb//+rmDQgef///8mPuTQaBOhAH4P/////xLG5QgDsH5PE4AAQYYZgLSRdQwdJ8IYpe2SpRNhExaEst1fR+3T291//////yE///7oIEP///+c7nQUB0IHw/////+Li6CgHD74nAAAAEAAAAYDAYEAgEANb////RjP/9v/92Knmqv/+OHg8EY4wWFRE///JmiQNjgIMJiDAM//9fjwTA+CQHwD0PCUXsEQlf/6AAEAOCGBgKBwOBwGrglLur/bf/3/mf/1e3/+7Keaq//5h4PBscxAqIn7//JmiQPHBAxQxgcf/+vx4NCMJA2B+Y4tJxULv3f0Jj/+nAEJSgAAAIfGdvvGOAAQcM7WeSUAAicZVpHsO7BAhPt5GKJzgAQAQYVJGQAx8mDGS7vDrt5BPmUxwlpccTZ9zPawOHLBWNBV3ERolSiIqtGV3nvDv6QVBp9bqeDT4bDUqCpU70ITjxEAAFIv6BeJlgITjHbUTPl4xJPQVLPEB63VyOiCvZujVDPcvvLIR4iaWeslleHPI/pEoNPrdTxE+GyUqCpU70U4VEQF//Fis5IiaBqqO4VB4sJ0ON6Eh+PgP5CR2IVMhil4VGk6Yh9Thq2mUA2nDq1aGH6HmiCWgRLYljM8zr1P6hfT1///1EgBBGE5W3JpAQQm/7T4N1gVWfwpIUcO4E2M9Dl0mxPbn+mRiGQ4ekwipHr0lcgrdXkbcv61PJiot8Vd2P49IqZlExBTUUzLjk3//pyBCE2AAACHiTZPTDgAEPF6xemHAAIxCF2+GEAERMYr6sGIAKAQA9e3+QUBROMnJUWH9o9yfEbkMDJ5R2JFqMp4jjEqgvzS/54TdtUTHVR1Q+bQlLnie3W5diUlV74q+R8Te3a9j//qACAY7thEUS4KDUfk69V4/MjFiNoXPzRo6EDRGFjjzlXCqqo++52k1R8yqH62+9NLHW3/arvqVLDLEqva6u2p6+zO/6gAAgC25JbLlQ8C0oYDeHNinR1ZoUIB5osLjhK8ocL7qcI6l1O3IFSTSy3McFiobPqSWS8miEQ/eSSjFpoI6b9f/+cA5N8gAALyt0UpJbbbrBoZlNdCPBaG7ydAbsdZMxnUxZmw4tNvP5P0r0ZL7JodSK+ZaMjQ6XZZOtynGxttxH2ZtJ//6e6w2l9CYD/+nAEuYoAAAIdE9/vFEAARAMrqeGIAAg9I32hhFrBBqQuqCQKyiSSJc87rYiQug9kQ9qM4kB2eAvsruS1dDD4xbT4tO4DUPYLsRCREXCQpFmmfdWxEowW19oftXFTdGXBaKXB5RlF6fywCCr3fAG/H0yiuOhxXz9Qd2ZXPmDCsjZdUG+0i1bVuQyJRRixVH8WuhBbKm1rA712iQm9LrAdPCzEF3EHmTI4/ah9wMpihSJ23/RtAGRUdJT3VUKhP5+yH3h05Aw/P/XwQzHDMpC+GviOJiEIg5P/NeTymv5CMLdW8z5nMarZQvb+R2fyuWQAEIRtEkAIJZB2oNHKiXHTJ0j1ZpOGMWBZ9mvwwa3f5nR9ys330cxjej//6PpVv6GKVurebmcxq8oX/qfV6NZ3CiYgpqKAAAAA//pyBKdsAACCECTd0GcT3EMjO0okJWII4W13VDKAMQeTrA6YoABAbsQlE0itOim7KBCnD8Cnq2qelXohejvz3wTdb2Ko1wT6J8fZqNPOizpLUBXSREk3OUFRFBVSMkj+R1HfAqcRAWwAAMwHX/9UlJ8XQUUHU0Dz0skOhrEQo1HGR7EQw7++FD7GIRFIpp3ajcNq0ah5mSInd5ySKiIkCtuSQP+sidqO/TqKoIdqSom5KEeg2cqNkVE2ivK+h26MUeRF7+hYmABCWxiczURPv7e3Ivnbr6Prf39mZXAMl2GEaomnR1ylfv5ft1q/6v3GfbWALbd+vLKuEjE+IUAzMBagIcMWuwUKvPFYmJNCjBNUuUGJbRu7UMra5E5rTms/yAmuyFqHV7a8jv903sr/lt1O3+xMQU1FAAD/+nAEwHAAAAISDN9WDGAEQ4Obc8SIAAjUhX7cYQABFBCwp4YgBAAKppVClYrTqtkA8Qbkd6gKtq3Y02IBCQUBsoHI+mvDsCoC7EhTJmrdCGqNjjCSePsqQDiJ8YV9DKpTS7//wA0LABgeD8bABALKUKroyg82UG0L7ucujgkcgllR0OwQ1YruyXDm5u4xgXcHT1hN8kOHoKRMkLGCadbmaH//5Ss2c//8AXgCAd1NXmiwdMMOP81JI3+Nodt2lMubQ6OVJbs/eXWvu48+SASaxg+DQTcSi5F4GrtWQYxF5A8v4sbS9texFytZYaQtUHoxMvAAJf+qMR0OOMKBxYrL6HQ/csplzVd9st2Mk7ymrX3cefQZTWMHwaCbiUJkXgatjViFjH3zy/ixsi8lXsRcrWWcjUSqTWmA//pyBMyNAAiiGDXfSeET8EEmu4BhIhYIaNl7p5hDQRKbLZWEiGgADczuv+PBECWqTQdR9b9Mb9nMM+ynnym+qN5PeYteimqO5TXkqd3Jp5N2fiBS4m4VRlgyvyB5ijLg21vqbmCO/QyYi90HuZs0wKSESwLyIthTtCamT1ZaQa6n1fHHp5tvbVlZWd0lZOR1O7kVDeTdvEM21oyxFfsPVJcTa31NzCd+hkwpWgo04pHg9lXRFEu66H7fBYMxAWrqya1rV+mu6mu10N9YXTm//xW12TRLVbMBq86PDTHM1fpUodEUi6tQFCA58FrdHFO4Q2V/IFyWTwhx7xqeOMUFtB1ZFryoFF6k/I920N9XC6czLTp+K2uyaJarZgNXnQuGmOY+JPzJZQ5ZGRclagKEBz4STEFNRTMuOTf/+nAEBp4ACAIhGVmLL1ggQyWsPCAlGYhs1WrsrKfBDYxuKYMUOAtFLgQCJ4ikAkz9J2JDAPs22spCDmp+rmO9lzovKpND/yt6tcl9EqPPCWGllan1xzthY/EYVK7lsI4CX6GesJfW7/klglESNNr+nAwVIacSq0K4jhfZdK9LH/8TJ9f81SspWrLSY1dWuMAaXBUsfaIwqVzthHARd0VLFWFes79fU8WRoJFb5kwJkbkgg0Uk/T6S2jiEXCzg66VvQEt2jcvarcRc0pmukNeo7tUCLTn2eZ76z/6v6qVW/p01jR/+RRSWxrrXTX1t8h9AAEwAHa2SDHH6ClBuVSbE7avjN2BF0HK/skT35ezQgfRPKYBjCT5p7ZEu46SF5ByZSgFHLaj1Jd8X2dJQENGoH4316ExBTUUA//pyBBn8AAgCHDXcaekoYEMDHA0kYg0IlNdq54zwgRYbMrQSicYAABEQgvbbbAHxkt03o0kZTQTnvs4vhVSiDD1WrPe5X39xgApOz0umpndpX3b0fedSCPWnMX+NFOutsUkP9Gz9XU7+kVEz5RKP/yWgGZ3KTPAooIrrhmoqv7IxLq2Tthz2xVKZzz0yBzBYVpe3lHJoYAsdZQdYy82ZkVpWxIqx7E0OWjSikUgAq5baDtQ66kZ4CSUj/CNeT59LJv9P664IefjBacVnt7Kge0dPyc99UW1bVT1/Z7snRK09Ma8JpXrt+O3IUzK/xR+/yIcUWc0duusuOwIiurH3hNphYJ/ZWftQurLBFrSXR+pPs6dPvYoEhXSS6O9H3VhL0WEPXiI91oES63iXGGxRkNBl9mCrsRJiCmj/+nAELlYAAIIdNd3JARXMQ4MrAmEiPggQ13DkhKpRCAxtqMOJyCAERFqmlBacLnpmD4Puau5bxW/iLmfqMd0TkuitS+/VbBIifN5uzIlLuJZ6HghwOsBgobUi3/dDwAQCSahaVGNfLP9YDX/CAhMupvs0DQQYLAHLyMc2XE2cCRfNFQOGk71IIjNnqIWejdjSACx6u5pa/U/n4dwtrUfnGNb7V/WUponumvUDVIAQS3JJCgaakPMnWTSZNkqNYVqdGT8eq+ujOr6cqU0HV6pq/Tnopg8Rkqt6tZ3oyBrq1SwqQV3I138ndb+Tf9lgBBGa//jwETyZU2hminT7Mzu40oIOomIEuCGuaFR83bDtxYnQRlSyWvZxQg6iwylhapRDfLE+jcn0s8xOOpffzOlSYgpqKZlxybgA//pyBFbsAAgCGhjZGekq0EMjK1cxIjgIuHlm5JyowRcM7mhkCKAAOWy1XKRzOUvTaeqRs9F5m5uaXz2CbPTWk6Hz9TBRYjO9XxmoEvcOHyEqYZfampYwaw8Qm1NXo1ZVzPr1Ktb/wCz7AGAhW2yqiStdVPjXE+I5eXkkiHdFbsgYZ1gt0YGZ9fwgr92aAROt4k3xO9tJ+tw5yc4o41K1vKahOGM5lDny73ezxAEBW3bYHTANgHEwSOiswWBWwpkV4SvGqEyq+wuQJkHj3jX7nGAKSW+8RrjUjkBmFs2FAV81YCVjpDuW1RrfqSplbmf9tvUQADISvtkgGb1roMu2/zqwnwHiw7MtOs7vpo+HG4Qu4YFA5E4eKoFnGhX0MLqeQiwDBPb2mijVLEZijUKyqJ5SU3mFVsqUmIL/+nAEmkIADMIYJNoZ5jrQQ8MbEjFidgfo52pklE7RBQusDPSV2AFNt/6aclK1qqk7q5TWmNrBn4C/Lw3Wfu5unFCkexDuyBNj15NfKv1dtyL2nUxcWYzXSqtG9KC8U22Lvd5Ig7/I6OwAvqdUnZBHc+B0M7Qk72re0SS9DB6+ZFr0lN1oWPCh93qXR8I9jxfb5Ld7Edi3Sx7/XrDR3WE3VZV08JcSoLanpLPrqAKbkkSUeC5DBTspgRj0vFIdw61WCDrjXCEoxCcv1R6tVErs/V72E+I9rqi0/elJiU08fDo5ItDP7TnWK/9jqgCFJNrqlIF/juLoi3XLzqK1x3txWbzvu1DB9O8TecwLnJcoZjHcIQK5TPThuQ3QV2KNKYKWOps1svq7O0XPDt/6ExBTUUzLjk3AAAAA//pyBFQdAAACFT7ZHTygAD/nO1OklACIsF94+GEAESGQbt8MMAMAN38dsy2t6ElZAxWAdN596smFezi2pQGFBhyCIuPFGGFJnEtHdTfjj2eb7+3t08xv9VVSqfv6r0V0NiyFyGS///9IJTkki6TJc8OCAf2Y/5x2RAdJGdglrVzEFEUdqZqcTFp21G739q5S9G5Cok4sRbW3JXflRuotpPDx1e3T9qf+sACdAySuW3bAXSLXgFO7lW5kW5p+0KMckq1uDtZxVJxl3SDlmSAMIIExUQGC4ZPyWLHZJzgRpUtDtgyBTinMaLWp9n//6QAt0jG7JbbsAJIuTwQHhrGwxX+FmTouDI0ok5WK4l6cadL+nw8QXrnBinyV/JopbWtX35vNFJSLHGO4/q+GGuu/xha9946V1gmIKaD/+nAECk8AAAIkHVqeMMAARIu7w8GIAAh4+XFcY4ARCiGt55IgBAA29ZrhgMD9fCDdaBCKi2JcucOLEJaPfoK4jAyaQ7wRjMepWt5Q0tysz8rd/hPcc5l1ziBdK3vQgn484jY+pZL9f//+gAAAUej4fhlIPRAuhmGWThGt3R9za1+fv//f////1VCL/06nvOqGeZSf9CWSIzvi0RlVkAf/yEILRl5BGohAASEsVHIIAACEJRybbxCBBOEr/2yv3GMw3I6FEXKd63yWt1CYvNdpMV2ea/O9TfTz/Z+Z6t/39fM7DoXoQk/EjLabE1upObgDnqwABxG/prRUkY1YddprySz4ZM4Z9whlwUruY5tT63UIaR9V//1LlonWZ//9tEqlW8paP1L0SgYUt2Rr89rI1SP6MKITEFNA//pwBGpAAACCFENb0SUVREKny3cZIgSIlW2FoKRMsRKfq8z0iaAAAKQipLrrM6TYLnKP92S33AdlMrOrXETFV0qWlFnzeRS6dNO/+oP1X1fR34L7dPlf/DENjko1uDBvFf07qvVor27gCgluTXQH6TD1xYqiN2mPk8EITBPkQqPtrb06M9qv/qP6+R970p9v+3/QiIocMChQqGQymwqLsaj1VZUaWbJXCXJVsElqStKWW3cwwGyTjGjrDRf6xtWgGxul51Ib+zlINNyq6tV/+3l8IP4/v/o3gnv/flAV7+f7Nqnn6J1T0bZNbUwZfUmkAGu79MbbTObUuH2gJ8H/DybMFRyL9H75QsEN+aE19RpCUE9Q2Dmhyf3t/a8wi0h82/V/f3/6P3fovKJ1Zvd/2bvVF/1piCmooP/6cgSZXAAAAhIf3lBoO5xCZJtqLKJWCHhff0GEcMEZja7UERhAQJT4FRokpPN0cSlYih2DqwTJsSGS3WvOTtqyoGWlexVzdCwpeU1J32iRztoId+s5D+t/E4PvLg/w+s+rUGInPhgH8AB8CW9ttcdowPyK9fSZK41+CvhYTG6j3a18B1rCFr1Z7Yyd9oRIonflHO2hjv1nIf1+D4PvLg/w+s+p1QYic+GBPAAIAAJJcoJ+GQPJC4mdNEiI3gQ2AF3WQEMRQ/KHC46o+t4niB0MQfD5wllFB+s/+84TEfpwf4gd5c/fl0BBRc+mJ6UgIAc9i4GvkJ8GZvsyGjD03kgomTMIOtriM5RDCYRJPY7EEHh8g0E4PrD4WDEQOE4PgQaNA7wwI3xO91WTKVH+nLn9X/8ptTEFNRQA//pwBJ8eAAAB4SRfACkYMEMArAYFJgIJICuT4YRmISWK75gWDAgogAjEZKRCWBVMqpMDpLy44FqjYzb++qsRRm5abf8pJT42DEvtBU6bYFDxI6+rgW06/rkVfm9tX/Wr+rWBABGqsQBi2ww4jgqEHNHlToVLHg0wFnuaJaiNpvFUE1MEvBU6bFAoeJNehRpUCw8dHcXQRVX5trSSBivTsWpPsQrKqCmdszIksssl1eC4PWTU5GSLMBYIkBoNJKlKlDSy5ZmIKVQbARH9zxRJ0xdAXGlHG1H0azXeSF3CMHBQXa6oUNzUiu8NsSVJzAmBFqpMHiu+04IZxsYzJFhL4IeqFVRiPLhEcwRUsIz5biCbVBsBB2rcxxsKGzrk3loQjShQGlH0Oi6Lr2iVxcPMF1uqUb6ed0aEwP/6cgTKnwAK4fUZXimBGiA+YyuwBeMICQRNciMsZIEijK5EkQ0AAQDOUwGX4w0fs2LbwwI+layxv65cIl1rSoJMAdT0hIVOoHOahgve1oSCjluXHKDT0ant99VfzfZr0272V3R7GewsSraGqRUyuduGwjTj2nbkgosq6cJFKm2uMYeOekTCp0kOcJTDBe9p0YOd46do3H1K76q9tj+x8r/sr4++0TULwRHhLLJELFZLifc1PaQj2xOQLAGDwwNEdRYOrFGzS7xQW2jj47IhBJ6GGCJgtsCUSuPA3EqaSzdzCqFBk+6uq5ZGwdXNOIiDo+ZDxQ/QaIpqPFwXmyj5QMJP6NZk6WlmYokRDmk8sUafNLjxATQgJKg0yRGJPTjFW7GuDU8I7dKa5liCpEy3XUp0RY6tMQU1FAAA//pwBPZ1AAACDyRcCMsZQD/h+4UFIwoIvGVxJJhnARcKsTAxDOYZ8iyAlQuBJEFpxTODKIgv6qbPsXlbzn1iaG2drPSYNqgg0PIgqJcwi9pEwnW5DDqpAj7du4Xc9mbcVZk9ctar5FYEAXhRBITUNOCfMwTSzRGI/pofcEOUfLKDsBCrDxYk5hIy54ROSaniMBPgUbEr1FdR1/XYxtFBt+l87rN39dmlACUSGVW5cnSMtABCOS2a+D3vmaWLYqx0kh1eeL7fUEWslWu1wkmGipyVWgkvTJWLe83eurUXeXyaHq5k4KuMpkn2If+0pQSCklLumsSnW4r3j9WyfsLj5yGpszY7EnxXkjBVTaHnTERih5YudO2sSdKz00wUxLOlHqeFNwsdffGjiIqVili01xx6rWmIKaigAP/6cgRfFAAAAhgr4+hpEwxDIWvKDMIbCMynaGekSQEUie0c9hUQrRBli0kjaSRMYKWqWWd9CxuRm2U6gz1DM6MtDPhVdRAuQqXbsv91Jt5E6jZG11ssoh1d6n1hklWsa7GywK2BXb/LasAKCLcccni4erciM8OY+aS3D4RoMwkXkqUCyBLMrz2G3uYetSdWMYYOlVVhks/fmWCLFWbrUTynaaWYwE1nkEyxerUAEo3J94YV0jw+0wEzCBOHj8AlFnI6/HyBzFgnB6Lztr0Fd5fs66JOy9qs1hzY9pFwLD6aSD2uVWO+vthUgWVR9VjfTvtYuAAASbkmN3L6yNJ3sgzYRJYGPdxq9gA+DhzGuMjaDEH3qU8e9sT0tQpRdqXXsoh95DJpmjSVLqbidodQi5reppL2uf/qQmII//pwBJi+AAACGBVjaGEZzEFCazMt6RYIzMlzQLBC0RaJ7agVjHiohFqOuSSOS0XtnYG8sfB9GQGjNlnFbwYzGjDqSVlXCoXSRIPc9pmsXj0am0M3JJypxSnmxG1IotabkiwB6MP7kIKrACSbcU7Bc1VUVZ7LnmtCE880Nwvtb6ZRZBLUmI7dJPMrahggqnKnSEFZEQvbW0e0W0VXrnrVd7rLv0r0adlNvIugAAEItuSQ5qFUsJwz1FWlQw1AnFg3NoychmhSa872drd7tVUKUsrWdFy7LRNXZV8d+RElLhiWBrz6kIpHNqctbas7U74vXAAAIQkt2xCoS2Yk4LtHxpOTvQryivSMbsQIaHwEOShi6ztpdpJj4we6OByVvNJXSt6zwSknvzvYlKr2BkJeXtdu2I+lOhMQQP/6cgSSiwAAgh0PX+hlEUhD43sSPSKECGhlf+MI1GEJDGxc9Yj46QCcWqk9rbi9gOBThtbLYMLi9YgdQdGGHItHm+dacMpfY+pJFggUYKTiuwH/5QMcQJKOlCnXD7AfOS58SQwQa9h9nLgL/kutZEZguBCwunh6wzErbSdn9xZ/7D4pN2r0jy2+iB2zaHMUMK4aQ2tHrhu4fY4XJGSaVs3iV9w9qNirkMyXtd/WtUEAFDMom3sJT8yp1ARqXrzO6vf1b/pQ44ypa7qFK2L3yeCmT20raZCYdxZ7IcUW/0+49+sbypKt2Ku3Yx7kFtTcJEQkBpuQQdZKgkqoSBdwGMDSA8xdj9c4BhnT0hYZq8pHbd2ZWqD8LCycSvad7SOLSpNTxQjztx6e6kSLLfAXqse1dO9MQU1FAAAA//pwBKTRAAyCGxlZkekqsEICW1olqDAIjP1gbDCpgRSfbIzECWgj/+xnpd8ahzBqRaMnBYfUlFEL3Q1XuQ0OnJs9Rqrhy5poiGUpSuoiyk1Aht0tdAvDSZHXPfVDpMllnejUVcorO/6jwAABsB27f/JDCpg5ganBBgtGom/BUnLCQybkq4qSxr5yqzflGWJBBNmVIgSrnpAmxhodUbg0yzuZVWBUXR2rf73doAajcFz7LsOlFUd08St0eFrJ8moqJq6ai5Fm0KSBlNajKXdx8/D/axQWlrK6buq5Vuq9U75Fmuaq96rddejb+i8bV++oAFyXbKuDUM0ReFAGWkrIpGLC/4ZE3KhgTJGMPeqmx3wPtghNHNs//LTWbT29D9111XX693tp1B4cqy9B66i8pe5/7b/WmIKaiv/6cgQdJwAAAg8V2ZnsOiA/xFtdMOVCCMxni6QYtDEgEm1cso3iCDm22xvZzqJRhrmALS+F+mjfLYXJoCWq5QYvQuI7UH34sSNL8b88heQEiDYJCZqUKWIG1PULIKT8CgX1UXa9nt/VAAAAAABdtuIEfnxG6xcCPQ+RsIpwvjhg69B4m+wv27LYN+Vy+7cSSNNPNJWwgDXvMLataatsV6yl5Spz14ibTaglG7ZHZEk5gkFV3MPtmrbde98mDA1HeHAK9ax/BtLYp3j2iB8Jpm4gAAPwQFx9F92awQlKw+/E459agzieH9BTwQ9Yf5SAwBbbkuXvcJmskGkVN86U1cVDaKMHdA0o/IL83WGYTnP0l+dqqsC4JLjQBVUpXs2dCZ6WYy4FGcQohq99VfHSp0QmQ4pFQqhMQU1F//pwBKFhAAACGEPc0GkRIEJFmxo9hT4IDGV9oQkp8RMm7uhQiRLAACgzq0iVlAX2DQdw9x0lfhOc43VwXR+J95BvozryIjzTXLstoJWkWtLf796s1uqUO3a/fgpN1QX+slamxdK6NGkUAAAwASabCs+jlJtmEdqSgxranodH9whW6B55FmT5GIEe4kB2oHfNFRH69K2owslasm/quQ45Clzf6yVbJJ9lO2z7YAAEUGU40QUXPYEcVWKdkJw/rOlXO1ARdDfsQGkKxHOutETCw+DRueg0j8O4l6A5/EqvJLGaj3xEDKbpFZ0Gq3QAqsEZLr+rRpsoYh0UhvD9w7KtXTRB2oT2oT9n6H0TqvzfeWw3/MV3n0W1jkVpm8PT/+/tZkKysgmktvEQhap0JXGjfSmIKaimZccm4P/6cgQpTAAAwiI+2FHmKtBC6csjMOJ6iM1raPTCgBEUHuzOknACgAEEAK13as3OU/DaBxF9XGzihjC3Fi49Ib3BwkOhPxeZgFY56L4vCg2a/879KGUpaTq3V+P/5v/+3Sz1Fnk/R6tj//qAKTbk2K4pMEHDAdfNp+/N0UUB52EUvbLuWlB4voT6DOA+klnX3qiL1fqnl8ctd30/1X1bkZ9TP39PJ/0NegrnV/7EAFBDcctysnYkHZ8TRtdyjuTRduPepgkguiu58ykxhtLzJbqKuyzasb2Jyn6tuS7VK3J/07t3fyr2rrbT/odOby+3p/TjGJBKbcttnRGEBPMKG735fQMszwn6Cp6T0HWjxM7QhznlRAY03I9TvczU1uavXo1/9v9081ufdXI406vYniNUc/qep6HYpoTA//pwBIN9AAACE07chiSgAESJ26HDFAAIlIN5nDEAARWQb/+GIACxBYniQhXH4RNeHb3MZRllIhd152ztu/pzjlHP/+HBYouRv/8PiIHILiYv//+HzE2ib///+PY+HwwXIa//4ePlwPJgkkuM47CJnT229abfO5HEOOouPtqXV0uvm52Vn//DgsUXFG//w+EQOIC4mL///h8xGtD7///+PY+HwwXIHP/+Hj5cDyeQBMIlpQBIfRI4eLCzv311ZLtCIE2WtPRLKaj6s/eIBs4fFHeFyTxorJbpAbFl2vDRY4xFvSxssQD7ZOKEkl4bBu7KosgwBCYUJ3GkkkgTQ17sg2ZHkdB26EQqttYG56M2iWU1HTb3jGzh8Ud4XJDxorJbpAa8WXa8NFjjEW9LMsgn+lMNg3dlSFiYgv/6cgS08AAAAhI13WihFcBDpsucCCLGCGTZb4ScTsEZmy2kFIggQAKQIkYAAAN4wOiw0yIH3ZN2zdlseZgccogEzCqIw8nP0teKJ0Pa36ZAZH/H/j8q8YSbIa+oe2Pgy30gZ+8tneSn4AAQGaQAAoO4IBMDHZA9kztUrcOJvPB6AzriUVeWfpa8UTp7W/TIDI3+OvXH8NoFRtRl1t6yIlpaWCfRNh3TPSvHuEZAAEADeABb3oBCYxrLNfJQuDst2Md2msFdHH4zd6ylu/6lcM9ulH+/mfczt5/fjX/ZkXbciVUySaqlEXFTNYSeLcdrAAQIYBFukjtxP+YdkXhBODfYZ7Vg3VAY+pPeqFLV1b1KYM9t0o6e/o7XM7efo9Yynr2KsyLtuRKqZJNVSiLipmsJPFnY7WmIKaig//pwBLQzAADCIExZKekq0EIoCyElQnoIiQOJooRY8RMgLEj0nWgAIH1xo3k6NpcK7MEmmtvyYv6BUPUBUxn6+ED8oa6BnknqX/pUtScrX/6IP1N+Z5/GN7/qyPTiLff7+/qIDvUv1MbzqgvuogsFzIwE5lQMWavLVP3DDSFYkpGY6YO0PL6kuszE1mZv5stScu+rf3epn9zPP4Iv9OrI9OFd95ZOJB3q+piO1VIadkdUljTcK1LDDOgjldB4bgK2oWWkIzaIKZi6scAyLx9ZheBSV5BZ8iZy/3/BE/r5dWJMBbj2KRhBlv1LXjnV8wpdQErVXW5ELRhGGdkbB06QZ1jTXwLu9NAOehfGTQHP6kH1J6z5QJ3//rdLbf6M2d1/vbSU/09dUMx51XLG9q+vf5D7jLKkpiCmgP/6cgRXlgAAAhJAX0hDL4xCyButGOJJCJkzeaKEWuEUmyxYxJVo0Q2dbertMaivHkBVvgx9AY7rUjNiW5O5kEv/9Mrpb//Y0Bjab5GhhmzeJAdFbJIbQj/jVpazYlrXlxX6GPoZZrtm0wCCIWm7bHN89LUBQphfN1ZqhCT3FO0GQNoJbUJ3JDpT75tCardfWTRHwjMdt9bUboPt6N27cG2nU93UT76uLV30mowAMUmrNtN/ZhAWeplFN2QvCD5VHrxzt24D+iC/8/vxIUZpI8fMwkKYtFZF/FrKDkLdq9t/+/KyUkCkN0HFxcxgw+/0pgCBXfQc4YjsWQuChahjRcXviBuPoFH+8B83GuqEPnouTgP6Z+nP7ofM8iLsW3Z1R2UoxKdk9cWdGdl4Nyv61chf5FlkMpiCmooA//pwBGLyAAQCExvZUWY6kELkG40Yo18IJNdmZoRYwQmOrzQjjX6AAAAFEo5WXkCFw7IvHwwDGJLcnYbCItjYqXdZQWga+c2g557WfLVLoLPG7ux4o7qWXe0SXU7v7a72tkk7LB1u+zK0AAMsIFeSO4pPnkp0YP43iCQ+nAZtBo/oGE43oHPXBOWSRc7HAioa9ueIKbfal7RIac48Vs0/2tXmhTsYw83ffzdjABJJLbpEmMtJaIQjhxPRcoJI5PPZdOn1oVR2Pk9uU/M3/rzEKMRVpv2VuM3Yaytk6iVaXX9jPWvyBPIv+Zeq7ZIoAAJQoFRIpOPVHBsPeLctapMO1KjK5QsjZV9CfdbiPsmUQaUs6Bkm5vpWQelEX86zfENSnqsVSPeXtJhL8rw/SjQtMQU1FMy45NwAAP/6cATxNgAAAhdM3UhHKSxDJrs3PKI8CHx9g6CcTvD/FO80NBUegACpHpaY8K40oGZDS/hHQzD56GAIc8JvxT0k/2V5HlVSr3TpWjVjVZ7y0382lfZ+RL+tl/+9Ue6kYY505SeJLrv/IgCAEdl2rui+8aSzUoxAIgvh5BfC2xAcGrhRTjVDu9CekgFX3vydmbf5PYy2CkdaMuc/vj9O+Lr/F2azARu2aclr2+mAhFqGJuyRyhzJSGR04fFHocpsURDqg+jdioJbTldnggo97Dj5DeNKXgxYDqz8PMiM0AzTuz6kKgEUHfYFH51pIzUJHpAAIwkEttJSy5ODcbfO9ZZEBy+EH4xxvV9G9aiv1b0X3OlBd9Ctxd58QwFgktZ9qttLpiKbKlxni/7dbOl4J0piCmopmXHJuAD/+nIEphIAAAIcQOFoJRLsQmKrmQTFB4f0ZX2jhEixFZNuaDOJ7qCQEpIo5JJMFTA5O9ZvD+jizIkzEeokbGAul0FN+bz+voa8J6/Wu3OYCczs9bl9+sy0amJyONQ4dkTn0JfeZK6nkWklAAWOmq62UdLSGuLWzWxPsAwIiRJxF+Z4wyJEElmYdl321ZGtaA85hKbPPDYFDK1lyJoVPsUoAQM+9ikO3PzH5p8hQAAlCSm025SAdVi9RlQWoXqJH5fWbV+Cb0hjkVXemvfRUwbxI0ZsfPxEihRd3XWKlVnUqRWpF54+VDWHNr9wyACUAW0klCG2VkAlojpGFRcUeVbq5jyttS3KJOEJx8yonqROidX4+Z9EuFyB56xrC6lB1i7DTFmbVDESVdtGO4TMblLR0piCmopmXHJuAP/6cARrcgAEAgUm2lGCOtRDR7uaGKJciHCvXueY6kEWnvD0UI+WgAAAQltyRhT0QehvorJzAxq6TNwDnDeEdsIMWY0ZBwzlUNU479v/2VF4sOXuS1Ta9kq7qgZLSgK+xddtf3UPv16AK6EpLbcUGlB9EElsj3SZw3Cejp5KZI5oTbotTfMI8rX0eutxvHZJC5q+p603it87PQl8VnProrioZ91G9CVvG+RRAAA5bbA/bAMJxUGWC8RQ444y5QEoYe2PDrMkS5JpIcMyo/0aNGv7e307M+XTdleVNnScqbYw+WCW21yJQn/b3GP0/XUgVbI05Zbbxm5zpeL0MUaNCWIPnXz+Y7NGt30FbtoK+X3XXj8Pmqmfto1M4ER/OEjlEEVHyS22UGpN6zn60zhg01YMBKpMQU1FAAD/+nIEddsACAH3FVvRKxEkQoYreiAimIi5bXlBFF4xIZ9vaFCPVkAAlBTcluhulgRYhgReP23M1QBtQXjQeDdsGKwg5KVNp3S2G3FmFOiTap/8TgohYua6zle6C+yrZWEXTIq70IALICnJbdF5hdiEakh9rOihq8ivQZ4PM1mPvMVrj9/erOnCnfR/bS/jxx9/bFw6LB6Les4yYmB/RVUxbGWHEjRe7ZNARakct4IoyAhMRPqTh+gf0Ymq8GL07v+Kft5/P4Z+nMlMhExECMRigDvVlNdyCaXJxk4//X7ksfZVem3trd9G3mBWnfTIjzRTakk151Qw5lFLrcSXP5/Cqajeot3LElV24vXn5rk+Vyh6A8wFCwgU1Ls+L8Ot9GQLJCV9dOXhAN1OlFlnsDzTYWLrsjqExBTUUP/6cATZjgAAAgs+YWihFcxBCBtaFCLkiOxVXGwx6oEQH290YRXegACjkqTslt6vS1HicfOivMCPnAj3jBxmqrzfnuvXJ+BcK9+/+fjS8ES+nUb2PxLz4QZ2S9jZJ++2qkKHIgHHUK1IAQICUnLcppAsMCwIKoBaFPYNwV8o7xjGapeoP+ry+O8/p9cn43/nzrktl9fdkhX0EeLePYhvrU488YEIuRvcrYACXbvd1XYQtZtZXICbUlGjU7nYoKJG2gATnxIO5Ypn7TSR/lYJ/4LBY5qc5W6zbTuksM1uctjXSuqIm+fp8XfbvR8vh65/uaASiJhbbbk05p/UXibB/keLbDeCEcCGO1StkCXZMOTeb19Pdeoe9uv79SWoVLW9fsmEinBbEe/ycrptZTeFiqThJOmhMQU1FAD/+nIElW4AAAIhP1kdMOAERMfrF6YcAIhFN3YYwoABErEvBxRwAAQk3JN7CkDInj2JoGi+AxYimTN3HUIS2PjcMJlRgx6kn0CG/9fOby3/Tof6NRXfzuavlG6ffzXmy9wiwxR85RjN3W6LwAAKSck13kxwEIa6WoTrsmV8CmoGltRsLKWFo7qXbQf82PCc8r0Zuc3t1Tob6/fzupvco3/9S3HibVUGK6yOy/dW78vVq6YtHN05OD4GYw3doZ0WeYODWo6BwGFNOlVEF/9aGf/8jEVjf/bFA6Hhw4TN/3/w8X1dv///a6ooqIwL//xETKXOJJJ6xmSGs4F4hPH5mroDxcxkG4IFNOk1Rz/5xE45//yxEcVjv/8gOjYsWHx7//8bG6Vdv///a9FIoe5v////6HK5hChCYgpqKP/6cARnVAAA8hYsY/8MoAhEpYug55wACEzZlaKEVXD7hy2BgrxwgCJBJFM99rG3HYIpGx9vG2ZTW6jgf3o9DK3V/GtL1zOyIcrOimMjLvrqqiNhGt1OYzpGRAp11e7FFUztfovpu8jVrvcSQvqVYmZmcpVLM3KKNCjQTlbqFQAhJGrXo6pt/1avXd6TzboqHI3/VVHbCNYyZzGdIyIFCbq92KKphOP9F9NyOHatYCbltLlkbSTnFbs7jrKyWvy8QBvFowbEVcy18qSTaNBiHs8yyJkaC/e2sduHyUKqBl2K7XOUaF1izyqOWtIv1MleNbuWvBXdgOyixILLlIIavneCpQNA2RTZRrqQvMGTSDIjhWLRR7Q2i9LgG6fkoVUDLsV9yjQvFnlUPy2Rf2V8amIKaimZccm4AAD/+nIEhgYAAAIcNl5h7RLIQ4bL7TECLwiA2WQtLFJBDJrsyZCKqAAEImD2qrIsW1hrrWqV9bWUkmfbfYNojKZer2Rn15mbyon/Lm6f/0FbM3z121CKe+RxK/e5QFKyNJgtamRCpnPOT3awgAZFQ7U0kodgRMf1tKnl3raX+NtiQg/3lojnU7cxWeRgZCf8ubpovT6CtmL89dtQiv+zW/1AUrTuVamRCqcOuLcbjwnEIkJJNnhGFaHJA29yRy2pvlLuEY8pPm45NkpcInx/X5iEdNmU6VlbutOf6G65eT3+bQrb/TvE/y2pbV90tQn0/r+bAVakJ4n81qCJK9Mqr19wNSZZ65nnjzXPsy9RQMNl12/ywBYmCPzX17+xbzXLf7eJB99O8Tf1jH5EX9ApCm/3of7XdSYgpqKAAP/6cAQ30QAAAgM12ZMvOXBB6BuaPMoNCOzvi6QIsPEYoHC0gQ62BLvygQpDXjcjC89Pz7r30K+d3h+19ZdZCENt1a7w7qJmW8s/UJDOr+r+nU7fm6fS7f7v/kOqr23Ud2h3/r789WABYAjsjbheAeRrGjEx1Ogmru0ma0ZgyAvkz6qi3mljPJGd8/zf01Xc2z+9ma795unPtoW29e69nyr/Ux9KP/rr5YFNqSpySOSOiQ6bVR7lTFaakhZKVrQwhI96HZHJDpsgFu869Ef/VPo/+jiq+ypSno5A6/RbrQSItPLDOihbnp0/WrvOeg7TUEEjVEk3W23AKFh95Eo0VT6T15NxUCQBZO/uXxJ+hdsfkmYyMUD0XXtnqrbM/b6d3wRej1X26aHug/lUzHSX7oQ54Mb6DrExBTT/+nIEVnwACIIUF9gzD0jwQ4WLWj2ldAigi2dHpLIBExXszPeIuACACtRcYSjBTrKETiqGo1sK0l8KbEd6ERvTZ41XoLsEUA0L1lJs5vuZ+/DZIFjyefija9z7svyafLde2i/0r+n/0uAAgAhty3UlwpLZqEvKhGvZHN3PreKXzCYNNgQCSnRRIi187+CLv+v0N9E7/XVBaeJ6q1KevI11OLP94up7bwsO10eaDAFpySRIhXIifRvrL1GyN5fNX9WpJ+ePrFoIgovZBscxhBkaVOMCKLoK+Iv5vHPrN4YUQeVNtOnlSBOzrR9a+9/9P6PqICTktPUWpscWId6whcSrkwW3nG1vXh6brQUgMWzdrJE0vhPRdaP6v7N0/f/oFzhARIrYJnquijG0ennbrmIXa2eyrv+pMQU1FP/6cAT/ewAAAiE13WkGKShEBrwKDEWHh+jTeYGY6LEMGm0Ml4pAABQQoBU9l25MkNhlbIUsW7cGKYVW8Ihqp9UZNV7iSrpfq7VlZC/RT+lvN6N+rLRlHIqvvchZKLpxywrasl2tyGa7tEiQx3CbjjcllLD0VL2H9nUpnhBdPqe9RbPmNm/q5lejaq1+32bUIOlBxTa6tocg6Kl3MnWM0dUMlx4xB1AffaNnStX79IABEKAr6qwVSNDh3iIQS5Zmf4+G1M0Sjozqc9aAgzsYYndWTcu/T6n+7a0bsn7py+Rr59FuzAy2rYpFmd+R/QCrbtsBxBbprIwYb5kHnb+2e3347RcX00jr+t8SV+J2z6uR0SyrR0dH01baisCdaOnXciNHEbrVVvUyyk5zy9T/T8RJiCmopmXHJuD/+nIE1MoAAAIfGFxpjRnoQCV60mCnogiIpWVHhFUBHJSsqMedKAAwMAAna9rv9VHeKssnjHQN9Zro55iKKSzjc40VU8/wQeDWulB7RLszegyAwwaSF7Fthlrj7WrMDdf0pEls05nx3f8iALUQOyp4Zt20XIMazMzGIwON9wm5Q1/Ln6ifx6YB1Mp1s1SrhQyckPGepQN10JPWUf++ttfr7lHT3yifo/8d6QAIAAElu2eu2GK/W1ncaWcXSWtsar/C2plmyBBrN6SWPMkgws/i3+4im2OQ6kUxfFrQaGDBdA9jp1l/i3X6VmLLa/36QBIACbk22dPFN1pZ4Z9SxAsQfplia/zDxtvB+yZccNlDLrCr9RRS6n9SlPPtpdrdcusQkIl33Ch08eey5iL+In7fb+jzLv0JiCmooP/6cAQTsgAAAg004WhgO4xB5puaGOVZiOEveaGcTnEYCm0clonSKaYiAajssl3QeDVdr0p5JxQXoMJuvcfCJnPRF9etHXv9XN1f0JO1LKmu5za8kbo0IYQ37q3WyJUDdnAz5FPinYtoDkQAlpJKNd9wUmUydiyzbssx1woHbaTt4U3Uv07I79/I9tXvobyN2/orXEhCgwJFmLiOS+E+9bWN9TeBuVFGz7b0SiIkCSmo26Y9eqivMIeSBgG+jw0IQw1kg76rfUf1XoZex/L9TehFYqGYyKlama7rKMxK2trPorc/Zr/Gv/gn/vbvfZ88yPAgAUm5bokaesCB8W1NIQN18g3uorPw7Apay8y6QsvB4JZPDLtK2VuqOMUZc+pGhPkt+a9LnkCoCKA6zNkBzyXbZPFnTVbExBD/+nIEFR4AAIIgRt1QxRRMQ4U6wmEKoAh80W1EGFIxEBpsnPaV0gEJgJbJTde31lUnv0XOf0PzZq6gU8a3YY78i8aADO6lEjanemJfm+rejev/z37dE/6IzqbTqgreo268v/UTrAJF7dcVAC/Imqq8VG8AVwuhw8eqhhvuX0b/t/z53JscX274HtIGYPZhTGAYUKkmAoX3tR9+5T7+l+zehbDboppxDkNsZ3b6/pQAhAAohFwg6HMCSj2lAcZPLFqoZGd2hxOs7IiJwadBPr7EH50eejP5fRvX/7awS0ljDVKan6duxSwiL36qDzVXpz3x8ACm45KubVaUk59KquFXmemqyMm8itEKG46yyykGNI8P+x9n/GN7fMj6VVaFthHqx+110EhYXo2Ct+3fW+UxYUp+zR1piCmooP/6cARsUQAAAgRXXVDFFDxDiuwtDAVxiLElg6KgtHEUK65oY5XWAQzQlJNt1u24nisbC09z8Yubx8RHVfz0bCB+Yev+n17uzdPoVqIEdc/6dyH9WTj+tf9kd+3z+jf//6xOTup8NMSCajBTcstu9WTB6daFfJ10EatKsJlbhz4t9+cYVtSv0Yn/n5gE926SVsh1161f5Pn/1iiLscr8t1q/9UZ/jlapEO+dTBJbibkskm6ECiVdmujvOMCNxF+NDtx/q0uVugy30cNVczNyOmzeIn629H8Z9/r6P5/r8v/p83wp4CPlSr67SuXniWz5oQDkCY0kpS0ilOGSneieubwmFZ+WjwfWh6FdeH24DgZ1WQPD1szNx/z+j+j+b/6/H+r3y///+b4hpIpT66frL8Xe9uR+9MQU1FD/+nIEf6EAAAIbVN5RBRPcQ2U7FzzloIjBI3+0U4AxF6uvdoxwBlEGhKbcbtmxlUKHNXc19QCTRm8aD1fSMYtoeXjQJXQ/ofq/oP7fR32M3KT5vQXbv6ff/0b1+Dk1FW6dzs9WCQe91X0AQBTTTkivIThIMhufz7WiURI33pNf63hA0RYvtLm8j0CyPwENwFBGnjH7KOqqlelU9H+LJHuUjTp+zfvq15fT/sznqZARbhLccslwRWSZkdWQV40BD4+beJxdMfeC8gyTBq71HAez3c76fTyhv9ee2x97uf/6F70v1Vuqa/0+a/lLin0Z/DdWr6SiEFEEXI43c1pqj50L9zvYEI6eVGh5r3We0xkEJ5+OBK56fbbqm9CPu6+7a/v8z1f0f1b0/9Pq/jR7Zj9e/6qz8dN14r96YP/6cAQbmQAAAiJiYb4UQAJC7EwjwogACKyDi7xSgCEWhq5DsIAAAABAApFAqFYsqyoKEXUMq8QJ6u2jzt/6ov///Y53U6vT/3Odld5f/5CfO6lavf/5N0Y53Ja7wZkRf//+Rjv+qI0X2HdAApFIqFYs9A4ysQoI94cJ6v10X/6f/3/2Oc6nV6U/3OdlcylV7/8hPndStXv/8m5CHO5LXeDMiL///yMd/1RGi8HlgABuW/a2NpSgDoa6Pd2KyGeYxqKJg8SFjcY6/ulNnQrIiPTZRwsDBom5xPhA2OI2pmJvKhNtSVN6NTHcMM+vkVPdqDm6+q3QQlbo16Fy+AJFq1L6Wls6y7BTXDMDwmBpkiSv2XhoUI40JiIMjUIW/KEQOEHrGPQ6/ErdjxWl92KI3FBn0N7yKOItFID/+nAEY+oAAAHmEmRoQzjsQSKrmWDFKAk9A5WkiKexGprvNPMUNACo7bJG2kSVCCshOi4VifTO8lokBqjvoUNZOVjGqjE4OE7RJfXKuDJM5lnaTzMzYEsKnfVU9ksW+Tq3tmQAOodQVZIIDIVTkQoZt5kauLfAzqXRVB9/GNugcdL2tVUnBwnaUv5VwZJs0uXNhxmZsCWFTvqqHMiYBbMnVWPbMgzNaZ22xtNyb99D6gnbQpHxd0a0MSo7aGBEpMS0yV2Lb9MSeb9/l8pW2/sn4r/+ZTPeoiy3JPHXhMmpCS4GPWuYDoNYlOiXoPZIBAjaESxpAlk/GeP2qX9/Oytko+4yjSC1Xvi4I1IHJZ0GVyt99MSebbuvl7lFS2b87J+K/hV+SXTzrVaTe10kIsSnRLyr8kmIKaig//pyBE6tAAiCIjVbywkR+EQmu0ZlZS4IONdgLTznwQsgLqmBlBQAAYPZlYd2zqUyfFstu9HcO3nyZB83Kb6Fnr782yo5ynIc+0vX9E1ZV3Jmzf6KiuxmL/rVHBBR/u6FMRrdK0DB/3O/5sCBf/+K6hAUpanp6DZvZ4Fdt+l96j4oSZAnt8IsisoFXwH6lb1H6zlRzMWbft5KNKjL79a3GGT928wKeSZdMEW+j7l9QPmI0go2vpr8PpgM2livgRGCWtM83b+u2tBYcXPRUz51TKLiWZXJNogFFfl/lvM+b8vm7annvn/v/KL11ef/+3/1Xg7dbJKMHdcELgYRWsFOasP9rxMVruqKpGUhm6AJfI/QTauU9phHzpl7aP6P0vamMPr6X+r8P6X4oretv7dbLeed0piCmooAAAD/+nAEPtIACIIONdxRhylIPqgbaj0iLwi0jV7MPKXBExXriYYJ0AAIwDRcclAEEJiviqXOuhZ1qGiMldSQv4RfqIHd0EBH1+renU91y/pRZuT6P/MG9FRm6G5jjaiSYeRKsrRdKH/ZsAAaAFNMtwLwhTJaOn2LdVjbv6sg9p+b+QhPq/87n47dD+j+DZfJ2T66bM6bWT//j//9rYC6atzSfkPr6j/yRAAqoCoC7ZdLZsvO2xGDuzWYaV6lC4s9h0qqcmaeOyppSjAAD2RAFLzCT9x/kZ832H6KkSwHutHvpGs7Oun7KU63f+Zp6QAZoxwBIXKxtFULvvpObe6JW6mPIgyW94PIb8BCA1KeLWBRDn1Afk2o/g2fgl86/220ULSEc5TEz2RpJm3/a5+5H78SPTEFNRTMuOTc//pyBFb2AACCHjXfUMUS7EMGuxo9gi4IbNdiTDRLEQ8V7Jz2FLgWTtU020nHFLVqI9HPih64wATvP1eN+EsUKQID6Ft9vMN5uh/J0WUmU5G1b0fV+oj3Lt2Ue9DhxZCNC2mp2h6q3qZOLAADAAFOOQzRFk3Ea2wIzxXpYIlOnGgj61MQ4sMyJyXZbAl6BPRvT6t5Tej/+VtSVNX0800oymVGVM0fW+e9vtu7/mwFqphMIszsoICVrdLcqq+n6y8EdF7lx3H1UxLb5SuR0VHug3q3kLpZLSCPR/J0nfUr+elqmhV9s2Hs15RS3djP+I/2ECIdstMEwYzDHKg+XvsQgz/tlsy6lpq7pDKGHH14ROtRgCp0+r+n09RVm70aj3QgsGZtC0HmNL84fnb7/RS5yr/rsy6YgpqKAAD/+nAENY0AAAH7IFzJATysQ0L7Gj1lWAipNY2hFHXxEpjt9IKKJBCIgDmWyCi/FhMzVYQmt8nKbHD41U/JVt8LBLqeVd+d8viTbkWCp2RduvOq47VkXW72KQWEY42W9kxEKLU4meAAmACKUmxdD5N9/M+ZUvCq5B0Q+86HD6ym7yxPB5jKxB14anGDtmyXbU3exT5X6OButli2qZXpnhR6iYrZ77rXf5tDQUWgLlttt2Mh9WmxQHyjferwT9Am0UKNAQm4kDn6t1G+pPQ7cteZ9SGbavSOSs+/+U/Lfovzr23YpaaPEHprfQCIB3fUAGBTgFLHbdynfUJxpkdAHKH/i6QW/Eh8Ka+bMWBDO5QoHeRRN+wdvT7v0+r9g3t6IMesRRS1DtWojKKoy1m2lnKmfs6UxBTUUAAA//pyBIbqAAACGUXg6EcVnEBmzC0IwlWIXKVpQz1IURwUraSwiwZENjQEJySSWqi7CmXGFCrhS+r109Qr2divmh78dbwvx62KqPV/lH6FbyJ52vUktLa79/e9NH8FPmXCiEk07CpVHd/1tIoeUlSSSS1hB3ldl2TFdrAUYXvV6N6vvA/Qyosr+Df1byS/6vzK3p5jkddo1rmBR9dKc3nmyNgAGFnrZMH3toAAxAFJqSTr6zgEVsvotX0tskR6DEtFTBgaNSj9BUTqPGbZm6uq5r6nnMi0T2L/PZyKNcr4u+AbSxOI6Q5/926nU70gCRAxK1qWaLsChR9NXE6110ZXdRF6LOFvnQX5qx82xNfWqhR60O9sWz9PjrtyGxNp8ROh2gJsgo5IsRkWpIJikxTUnod6gymIKaigAAD/+nAEbAQAAIIaL1eZ6xUQQwl7ihwj2Yica2TkvSgRFaSszPOJcgQQ5P293pROzDizQvHBq7r9wDFt7dtNqRXYwNItN7jgs3TpE9n6gfoX1N7v0X7fCdVL5Rtmebf6X+vEWDekNU30q0OAArAIklFwoYQsIwwXSwceuPCFXOVMoW/ijqYJRalCdO/m/vw+S+D6+vBqIUu7yf3/5/L+wzoIDEr3qe9Og5MjbPLExKBootyh+QCI2yDMluCTJSlZY7e3loap3Igjvuhp0JNqIWLs4qGN3+RFoSoN1hvP1H/uqyiq/lKU/JZzDDLKnr4m17HXgpNOW2u83UNtQ4zZxVR3OEryh7jJNhQTtiotd4XbhmddAXxF84+rrpn9F9f28n0b1/2yev1/J4g+cj6lkxG08iUZtcp+hMQQ//pyBDh5AASCCUna0MwqJEIF6yM84lsIhUlq5CRLcRInrSjxlVIFTIA0k5LZ0Re0jcvw+nq7Z0ZChKnHzDo0usYOQysGubUPeo31Xr9Uf3+Kd0b5/RvT6P/5//t0t0GNQjbhzdVryoQkbd1Ug2UqDNQaUe4vRn2L6xwSoKkoqBN2qgwmUCHyKG9D/ByvAUlZjnJs3xL9m9R6iGQY0j9u6vW6Owm2v6NnzowIIIKhB4xVJRFrC5EvnTAjWZezFnb42//0sLQdEA76hfp6iuytyr/6p9/p9vT6f/J/9vt0tyr9PQTUIZUMnGmczDLbJQKBakthrOdSEgR1ZPGTL3tAboYQaHGnBugcHJeEX8b9PU3/iL9CfN9/k926CRV6P7fJ6Lpt8V1MjaM3oMzCFEMjpyO1MQU1FMy45Nz/+nAE5eoAAAIVYNzQxxQsQ0o7iizFWYiVJWNHrKsBExSsaPOJ7BMNwLJJTVbTkcMBl79QqfygDtn+1Wjr+gcgpnEgW1vQvq3r6L6fr9vt5W6m+b6emlDGqqW8O1Xr/9vT/vDX4J+nzjkAZiUUig4rL5YGTDO/IivvcPGJVSfAlMXr6BAMrQIGtQCP0Xyin21s/Uf6H+329W9GTVOv///6/G+o9uQvjHVYU1fSABCAYJk/+3je0OCieXd/lqx3wM6dVpHMklmLy/rOGIiqMZu/39W926J/6B77ejen1+3/p/9XVJxnxb75l/UZCIEa/76QAIABBSLlbVmD8jfQl9H1Un9dfMEmm2BY9wQQL7NFwYNTB+f4z8l6DereG9fkFYY2Zzbu/9eHtOmsKUn6qjIRAgsa8ppTEFNA//pyBE/QAAryJBhaqSNCoD/Da0AxIz4IsJF8IYR6AQ+SL0BjDPgCgHl5zYZB+bvUQiivKBJTiLaKQBoRkDnYQVr9YotjyKPhELPAaGggA1DQIICbvLkLUa3k3QsZpDBDc/j+//6rc4y74YYfd/QvbRQywpVzpQh831GeuZQDJOo5uRWOflCDsBhCfSi4OMkAsCADAgRAggJhG/LkLUa91ukMI3fX//7ez+QXcOdDgYd7ioJOLumyKb4oBYUqYdyzI4DcMGgzzwj4g+VnKuoJ4ZKgybMgmCI8mGHuAFImAD0tPvfjyf97JAg9ej+vS75T4friwsnb/WnWnT2lJUr9kgllzGl3mZHAbggyCXnhH2X9JmvK6gg48MlQZeGQTBEeD4YeZKUiynpafvx5P+/IX//X/0JiCmooAAD/+nAEYEgAAKHyIN8J4xJQQuMbwDzDLgjkr30lhE/BGorvFLMMUBP+j5KZtpVtewzRjbaalJA9igTmVorNYmrTmbQr/o9gw4VBIDstZiWMYkDVOmJoi9jRVZsmwxRyLmu2/G9iPUqQ9tqoGKAJPTEnpqryr8xNyjTi1kkH0mqMxCCoKnRCby0FgMFQSA7JJciJXAJiQM9+jvY0VXSwxRyLrttfG1WI9YBDAQDtc0I4wRadp8Wu5vUtqVlsiUaUXOHViMinrNnqZsiZS5dizBQgeOHC1OX6itVlk0bTQ4xlQaO3jaAvRAdAiedZPmWFkXlgvFIoNMlnYsU+h6ocrCgYxsg2aHtzXCjRoGCQo0pJn0Dw+WULVYus4wg5T+XuqRVZzRvQ4AHYhBpso57AfcUmaCN22xKYgpqK//pyBBfaAAISAANdwGxIAEAga7UFYgAItOFwoJhgwRSR7hSTDFACDAADoY5ClYZFQeQHmPBUkVLLACIiD5I+ExgaARw+N1h2gUbLwl4GVRpes4MRQo9t29btpZvKpegWYz3Iok1KEgIcIrJoQDFAMVNBILtEpUcLgAxEzzAecMgI4TQ4mAw6WiJtMZapIGGUaazikKRPbVxamt20sS6EvqYr4ekJZQFE0KJBSCWjviiK5DI9NFRpKpH94cINnYY5cVkpHzvCH7wqmbcszlvC+ZGZ/M6RstMvhjFYuxihELo42/bRm/Ov/fQuB7ACAk0IzGpVIVMFEd6Nnz4KFKqw/UzOEG3KGOTdhVM53ZT7ZMEbeNFTzVOFEPi2WO7HOZVFAq52g6qte6WWXoEsV76bnSyYgpqKZlxybgD/+nAEJNcAAOHWGFsBJhkgQwKbZSQiVAkgYW2kjKzBEpvshMMJ2MSkmjDy1w3BaXxrDwihgRYP+Gh20gpFSsHEpUNKAiFEMYIluZe5SEpYVWkRmEHEMejK6HbHzsRfs9GsCAH4YLilUsaRnqcMhvQdRnBooWfcEatgYshB46Gg2l1R3Bo8SLXknHmNfXCweMlGuBpoSTO2ogZSRyKiK1K1PiX3NQBApIJIRTbqWczazGOno0btUjaecdQ4iOl4V81pI7ywkqIlmSNkRPfaISIuDdFrCYldCjWotL5isBYmUrzZJXLFi2hCEPCh5SP8rugcJ1FSKDQc7qzCbNW9hqkLs9C9OcCadAUuwR2epu1PQ1+RVUnYxl52opksisjlXyVhShMaB9JW2hHV9KK3XP79EYmIKaigAAAA//pyBHnWAACB9yhXgesa0EPCS10ZBlYIOF9pIwUGIRUMLGSRmojDzZyqkw1Q6g5oUcs+hw1RzLCTwKkZQpBib8qIzoShuIESLYVRcuHnST32WN5czC5Bqq3uRpPftR+5f/Z/T9AAAEECYKTjk6B06eNMl9AYqb+Hj5Dw+Bm8oNl3AoMcnmEwpcylVQaqBm5hsvUDaBzXWxiTY8vAYx9H3V9Nl/r6xF0wKgAWgEIuzloIgp6TqQ/9ATR3aQ8kbMkhT70E1+q1+hKIhq2vQRJb1TC2WIPpWywm9ddR1+69+JNAsWft/qbofbS9AYAJVeo2iegCxJEeGAZNZ+oCbK1KUpEROSODsXXMY/OrengbsRVunp05N60CxNh5VqntpuJJUU4zZMLX2G1fUi2u2mSdWmIKaimZccm4AAD/+nAEZi0AAIIHF9eR5llAQ2ZLFjECXgjUX3WijK6hCg2rAMeY+ABVr2uVrQrwGKHUyF+m4AfCPAgYK0mIhGruKISK9GpnYgHUlNE6BxyBZphySbxqnxjU35Kt39mnbchltDDbu+vSBQVa/0rScOOQxm/aKhi8KGOaUYsaWExtzEClfFvKVwCi520/slXI/0/nqb2VpnT8etDlDXTyb3NS6KVx/b27atvyWtAFCXGSyy27y2Yjjn3ZtgPEhz6NoJbh8MIzoHOyC4AeKNeGAoRLC51aOSCRSkcXbmalQHZttkJQ9kIxs+2Fxaim6d6nKJMSsIPDSqBmLxecT1BhA5o1cq858eDt9vFkDQezPnOsG8lZGbQA1slIDutow1eTlmIMERYpDhdwiLKEd7EWsfSh1lf9SYgpqKAA//pwBOVJAACCJRTe4EMpPECCqvkw5aAIRGFvoYiyYP6V7ahhFWJgJmOMtd1cXNBI5DmJEhSxQpxm9cZthbWuPpE1AZziWBmYA51LJE0bQfrJTrHiiyIXuNTbF3Yuwu41TS1my1RK59W6d0gAQAANVMFrTryAfQqKrAYKnsmg6T8H0X2OiJ40dWkLi1OAdglCbhlanzKb9MXJPnJhqKNKRG9n5DNYv/X7b2/0AAAdRBJVyS/KpihhyrAQWiPHOPbODwW2JfwSnFMYHVpQ8Hz0LoKojRHWDSqbWsyMkpL07CC7t2Ratgpt6r1oX6vFJABMTbkaXzAIzEO4cQmwfWo8OsM3l+CW3J6cersqyKLD+e/bydVEkMSmVCk/pRrX8ro1hCJkk73IrHuw1Ver0piCmopmXHJuAAAAAP/6cgSgqwAIghUaVgsMQsBChFqgPYtYCIStc0MMp3ENDW4kkpz+B6bt9pI2Orjr+Vaj5JUqsnKqd12qGL7jk9CfK3VVgfP5wwbaTi4rfTiGdMCAtU8NmSTG4fQZv6BsZe0kjs3Va0fbZsp1eI2eYj4sCaOmGogexF7OPhyysFhpSQhKN4gFvvxbB72PxY23RjvN2dPJa1fdVFQ939omypfL4kL7VObaj/b2di1ElFAqaCAAhAKPjRwInSsiZXArBWAhqaD3aMD3Qa3s3mfVX03RsnzmzDWMvif3NR78V9XMxVGlbK3KUAQwmxxlZVd60SWmW9FBIgQCFupChpTkVGJT0JAEe5CubxMTfiFdZAsGr3vPJhWrjBYTnb2pWNUwis1sp0ZrY+PvbIkrznEbN+5r+VTEFNRQAAAA//pwBOjsAAgCDytXsy0qwEGDSrVpjVgILHdbLKTrAQCKrrTDFC4AgBf5NFWI5Iw5GIDhDjRePYy5xTR2c0LFrZyoosPjKFR11oZswP0CI/sZ/Snf071b03KNUYXcUx9JNG2zO7q//9oCAcbISyjwSTMAWoRRUdGw6PSseEFqPI1kWbXzgX1Xp8Cy1UHTR8yDM6siqtYyapQYsS4Yce3c4qeQK6Pk/s9bvVAAA3mkQeKzVGDxgCgJ4SLm4qqJ/rpCee1weAtYFhbhZm6xodW42pYQnpSvytdmZ3yDoKzfFNy4o6nG04B2ut//pBCQgYARaSTiwDRgfgUvW6HS2dz5Eerzt/xMDLGvrrQAmhlMOHnEe+c9WaQSElvIZ6g7W7oWlSHqAWccQfuse9SYgpqKZlxybgAAAAAAAP/6cgTrbgAIAhpDV7snEvBDKRvdKMcNiLx3WmylqsEUoazphhyiAgQAnJs2IXpcWFRERnv7OU/21ukptRKW9wSisOYLrW4Py/QM9hSLl6v6fb0a2GfzfK6f6M/RvC2/7ereCbdh/P0/5IIpCuElpppywSG30g2y/Uga/unrvMeJ/n+PhI2yn+r+c/t6MnT6fP+3o3/mv/sreeQ8eIdSL+TPwPnq848tOPW6x7lgBlNWouEcty5DpZFLKl7umEgnPul1vmcPy0hNVhRlr1OaEN3mQvtXTGQz808yW9Z5agpnszJPqd5d44WyWHNu3fW+O+v6AEGAKDabkmCiuqaq1WkfdwRO02V6r1MirA0Z8WjKqsfZWqQ9W7Hk33HC1aEk0pXX6v6N0Pfz/V/X6N1N9B/Tmcju2r6TiYgg//pwBAYfAAACCUPZuecS9EOrbC0VIsGIxQ+FtGOAMRigbPaecAQDgRaTchbwatpqBLFq+PUnJd9xafOqW8DS/hR64ik0RITsuq60FkSWxuf0P//9vK/35yv5ele6v1i62VnN/3169QSbFnjUtccvOOdpRamwvWwIL3NDPGj/GP4W9KobZ35ErubC2fKROvz/f5vRv/v6/b0X19o/k9Cej/X7+u1l+GZt+MYsMJmzWOyyOTvlf0hmfnCFd7DlzCKqK1H0icxtBBl3HGJqrKLT01KbVIJZTm9U636t9fT9/u/T53VTvUzupJvkP/XWI8v70gAgI1AIxpO4XVGxLwCgZrX/Q6Fr+7Oclig3Yg6iEaNxBufHiarYHJ/Q7aPmIZVDdNPZ+/0N+/r9H8x/T53y2bbZkN2c9etMQf/6cgQ51gAAAgYwXgYcQABCxfuwwYgACPxLd5xSgAEfiW9/hGAAYqonMCpZWWay9G0da6WS/L0p+WlHVZb+5jMxrOj5L8rhUIpjGBOGyPkTLEnTahYS/oUCIQvYKJR/tkJRzKVPHDMFW6VKpSy+No9wdiSAr9elKeWlHVZf3MZmNZ0fJflcKCIpjGBAUc/qNDoFS8Qip36HAgCgolSiJo5/XU6GCm8iRIECAIRO16Cgo91CIs9VO+hgcxkFyAnnboMHYNuILlieNoIuItmrKwYCwkFBFBUXU+JEHhcAMaLVJb+KPTYH2fWRYweVnWYqofKggKYMcGeiJSJVQ54c4yLV34uql2V9f0r5THBvPSBeWayNoIuItvsrBgLCQUEUJi6nxIg8LgC0WqS38UfsD7PrI2DytzMVUPrA//pwBEHeAAACHzXeeKEVwEOmy60EIgYInNdvhZRLgRWa7rRTilQRATCDYy0aJRToQziA+ZogLMlARbqMLR5PjwSUwmFTI7hOvyORWUwyl/PE6/p0NbhVKSItf/2iolGu+oXO5dSxd1zG0gBMVQGNIFFKTcLTwxecRm9Xo3mGftZqkW8q2vfRVBJdKl6fy1FPb9WdDOyYWKkRx606+v6yQqoXDRIWT4qJX59guS60AAMoAhTVtunWFRZXyN7IkSANpXQf4wcmoW3m9Bv/J3WFRXdkejv/3VqP9SP6LAg4d6hLqIpquWd3yvXtW7DS1ajzkkURiIS+FxNIEst1jCnXCx/RaFUZkyjdRWWoeoTd0+r+3dYVDulHoZ9+tFsyO1dSP/FXeJdRFNVyzrJuV4/at1YaWrUeckihMP/6cgQiWgAAggtMW+DhFpg/qYtsDUUzCP0DcaakpaEbIGxkyBzIEAAxxGOI3KKVcXgzaCw+fUbECKGFE8oWtihV0GPKFvRvEvE2yX6/4P/17+D0//94rpX9+pSJZgrqDr1P9CfSKP0gAAWcrOkb2hVxOmDg7kBNHM91iwrmhBk1DPGAv/p83R3o1y+b1b19+/Qa3/7/x/Sv5tqpswdGsQ7f6PUwUeyWQDYx7AbbcjtO6xVKv38VwuHgLE+262NrGDvG9BITfM4mnt4mT0fSltC/9melE52yrNCxKYtEp1R9c4rULs12IRPfr2lu+shXoAH9/tPdqNL7jPBqqbjYf3YQknKl40N8gWTKBjzy3n/syXNMWh7qqoURF2Wrs2iJ9NvKt/X+i7kX05sEg1RPFO6vlq+kh0JiCmoo//pwBEEuAACCA0DhaGYSXERpy00loj4ISIFcrDBOwQem7TjGlOyQGGrRtttpubWaj8fmDdbsFqHG8/Ir84N9AY7cQ+hRXoTyt6P26MZ/buhtH//1E1/63T3BWM2j4xdZHX0bmvr9IAAFRAAjkktsnlnCjUV1goDzWRHltH9Ct5s/lbcEA/E+FEfbqcXlXZLlsluoIdp3X9t+o1ddf9eCf/60T/oJ6am6Tf5kCAX1ITQxGDCCz9WYryNEjbX/g9KXznW05NHG6clGNPy6ZVNTidVMB9VZ+CboLPJJGiIxoxsa/39Ula3/1f3u9HWRkDAIao/ZrWwqcfekDCgcSyA95FPVYWPtMjrjBT0B/VuzN6ekrpI9dfc7at87/kK6sqWN////5+2h3Ud9xwtFj3tSmIKaimZccm4AAP/6cgTWLwAAAg414GjBPJxCRut9LOJfCKxre4SEsDEKlesVhApYUKg5rabiabnttwkM2c5wZMj+GTlptXdWubekiftBL/zhayUMz53/6+T9fX2vQndXUaoRVbqU5xGbRk9BpND1T+yxgACuEptOSTMej0blt3yCxflBTfE4svlX8q/Hgdn+L33Nb0b/78/02oKuWv1pZHoO8Yl1p/qQtHSPVpy/M2Jy4V3NSCIqxLddfeXKxtmt/GTVpc4jm9WaXk+KPVogurhq+LZLbQ+K6GjQK84ALEPa0sXKIeC8yejG+pQwTxw0kQP1IUWIKj/yIEAfmLDo7gklu/r5Z5ZADNrusOquvfz5av3dCPyA38Zg+XRwHTe+EUfcLfY4jkb53bl0rvMFKSXfSUjv6jnq+N/2a0xBTUUzLjk3//pwBIGaAAACCyxd4Ek6PEEDqy08x1QJATNzJRiwcRyWLrRjlX4kAimMzVVanXAR5ugswyt6NRx1CpWvHm8IiHiT5w43T6/OfnPejP0d60QvmsSPUw8ZOoa8yVoXq2IISUVd+O79HeAABRQAQrJdpHtZ46dcntc2CBLemLBNRvB7l5xfws3im9Xb1+VdPvUJpZ66ahHFWan53sevp0ZURLTrUv8/v9fyRAljq1L9HSGA6rWBdLZ4vH/3vdUhSN8KBEug9/A6e/SO6u65BlpjunctDOZKOq/Wvy6e0U7f//m7ucpkZDDhMQiqwiUHm5/9JISBURSSTRUZm3lDh+NgK3QHuOLLuj5etkDknOIhdGNKh/mt41vZ/N6p8p0uNBmIJy58kopNRcQnP/V6CytW6zMOuwWPKfFkwP/6cgSDZQAIQf4X1gsPStQ/JSrjMWdYCNDtYUecVAEhlOvc9Z1YAypKZbqeGZL8vN7QVzCpa+aNacvuTZQikUiVyFDHnPnyMRWSfEkD53g2k+FcKsabyL7Nlra9ZuWGRI+Wf6fsvAAKct9kenqkZsyds+AEZ16g7pvmjak44CcUjcMpaNtVNBy/dfQ37/denyipj5N+9vDv3xfUd9Z4Zyo5adDfIQgACnN/4GL0Wyto4+waLJv+Go6e/gnDqSfmaWuOKwYmCALohQnp9R+qvq9up/g7ao//Uf0n/4XF2Rd++Wkq4kNemwP7Kf1hEgly/+OuomShS54Pe+IQc+ExOz7QHXRw+TCZ+NRhEyDdxB8zzi/RX7p6r8fa1+7D1RiWvK0dvyC0DyEwS0ihoAgN9Rl92zYlMQU1FAAA//pwBMffAAgB+RhYGw9RcEJi/E0YZXOInF9nTBjqkSKkbmjzlX4AoqXaJtgMBEoGdqOoSnobjOF1aLaLHJJr1teJn22pSP1nhmOJxF30FVt1ffdtxlQl2ZPsuz1O/VoYtGj7P/UQYzHWnHJZd+0ZphUTnvZr8Yssg/NfifoE96h/Rk9tD6ztQZaXvEGvFajWRgNuS2UBKKmFqDRIXcGQSfNPsdZkbUbEgBBLkkeNQ4XtTUb+yPtrXU+Vy3KBUlzsj4iAl1CF6s4idFJbMlnanbs+eCr3F6Bc/LLPiUdZmd+ZqfErqG+cr1nk3/0AAdoimkk5DO4RKm26Je32yE2SYEaF9Bf5X1AiMroDz4RP7eEQeyRiJMg5ORPT437//eioreQyrb8/qXzfEXJMIajDFWt9b+/oTEFNRf/6cgQWtQAIgg0i2dMKMvRCA8tKYUI6iOh3Vk09awEFlGw1k4ngAAiAExNuR3B2TbWMI9PWdb0pcSHvh1kKMfpiMTej9CPxeNG5TtASW+SbJJr5gaq0nZQdE2F9m6UMejK6Mn/Vr0d4AEQAJJOWu4Kue7DNmcVvusC/PuMjmdYprhX8E/QW3DiCcjeg+V2bN+6vWd3tRoze7G52JxVShhheEXyaVOvOep1QAP+KNDqGVw3Yyvh1pK8lWMmaF6pfBZkiki31E1JCmwGMsRqc0yPNttiZWpxHIF/5/v7K3eknSyg5IfdUKVASkt7fv3f06NNCAIGcu2IDAOi4trNLWNyG9ucS4qXt7uOqT4iA2xiDhnmPzwn8Gfzt85ZiqYuCN/42Sw3jcypT6dWLYg7YY/ltGxMQU1FAAAAA//pwBDHsAAACEyDZawsS2EMKy1w9ZTuIbHdztJQAMRmrbraSUAYAABCAEBkxzFRShFvmUEVJ6z+adS7PaFj9U4oW5rK1uuCqvBi/BgXlfw/dQ+t+6Q+za9QSpOypX27c5u9WUyGQ3f1gAAQMkBVLCkG1HvZIPqrRIG4zXyOs6FLI9vaA47qLvwsG9W8v3fqbqZ/J6Kvt4z7r0L9PZ0//1/+3q3p8d9vGDaP9oBKELKSLKScCwtV625ScICUVt/pIzLhBIvNZJkclgrM+BK3c+f/t/5jLNm98s6ROUZ/PGla2SyvJ5n6a9OK5DJPnv6gQCImSiWmlKTCym61DcrmB9SHXWVulcreHB64W3o3m+KH5z+V/N926kbh9vden1+a3+v2/9L6KlLnq1A+7bjkbcUIQY9eKJiCmgP/6cgQAVwAAAiccXB4koARDBIv6wQgAiLVrhHhhAAENmS/HEiACSTSbckkkABku2NtB9wPPe2D7BQ9KJdzDwtmYTyVEmZcyNW81vKjDklxERQxkhYxpw6aLFhZjRdk6t5Bcn+/Sumw/6koQCABTK04rpLN9t9gJvM8MqwbW7HcroyXXJ2vPLNF2wyFs1+6blcdiD5G1yngZW1rzh2aNCFzTj9Udp9ht5VSxJK/P3gBsRiQSCQd2k8Ba4XSYr++llCKusjItv//7f3J5aObR+cAABAtCGQXQ3XwM/u+5HacGv/X87td7IJmSn/5/8/mVXQSsLLG22qqiuCWWG0URrbRzRZhFXPIVFVtffa/vbL7k3lo5rI67kABB0IZBdDda3Fn+kMwvrd60JeMOhRX7+nPNEMuxupkcmIKa//pwBBFRAAACCBHdtzxAAEOGu6XoFAAIeNeDpgRHoROa8HTAiFQBAQKtIrQXkaEuLOXrt/Ai6xjSbXgxVSy4LTcmWe7pKuS9qPDiyIGjn56mXYOY1Espny972sKC3chZnJLnn4sLagHUWcICCMLykVHz9InzA1QqUmysUJi1YJZTI3ejUfo/6s95W3NVLf5mre/R/m8fEbBzBZFKv13mibAILdyFhnrsfizQDQA0wJY20qwK0Vcaq6WiWS4rnnXP/RtKLB+/ql2nK81Kdd1qNpb5nRzPaMpCCtYzX+XkxasNXdIDFkxGQUGNRK4WQwABhfUl6ttusA+iqK6yAhtG5OM+g3Xp0bQhjwaXn9UQW05c1Kdd1q3t8zo6PaMpCCtYzX00l4PCzFhq7pBcWTEZBTtSNqYgpqKAAP/6cASZlgAAAgtAZuhBFqxCiAtyYWIuCKDXbGwsSUEWGu908IgsDOTmkkutkksh0qMJXdGwx+W3qhez9URHoDme7Zr81M1NH7hX1m/xaXog6N8q0mN1sDcdxEkSrqlG7ESpX3e1UfNgVf7RgAKNyypKT+ldF8GXwz9+yBY7ooO9n3gqc/o2r2vlaq3VqX6t9rq7X5tL0QdPy+Y34N3EUSleU9EAlfd5JUfeQEpLasoNrSRrN9YXlGMZLYW/M68wg7vvWcEx2QDH1U+9E/5UPTW2b6H++huz/ut9WQfqq3GziHtyMcB6D7JjX927d0gsAEAMSlFdpv24RlCQGg4TVwgXktDCEQI/FvrDsjZCdfI2TdehOT+e0Nyy+3f0H/2mwxi+AjIoBwGYPirjC5hQpZd68UpDaYgpqKD/+nIESNgACAITNdobDzgwQ8gLd0kihQhY13FHnEfBEKbuXPKI+gAFHJALkfYzR3gQhashUZHJB2gDHxHFBOK8QBCA5ajoz3Ib0F5Hq3o9dfN/JfIPybe1LOzfQZo872571py/vr5b/SAFghRNwB9g6C258jEJQuZEq3NuX6ziM9RRSvzoif++9RQXl6Ne387eqe4zqhB30S275fj8nv+vh3fCwHRxd+nbqZZ1hAUDN/+DNOfddHNNUIWiIraCdXkEeVeUGoc2IhfSzI2Q3Xxr+nQ5//roQpt6ppI31BLENbLjud6VHVVEFk6fNbGv3opIKbjkDNNOFDtHm2H9gt9RfkTCKxIQFOBX8m9BIr3wQ10fdtb3zH1qnkeq29UaYupmk8jqUn+X/9fX146WSKwrzb/pTEFNRQAAAP/6cAS2nwAAQiAcYukiOOxDxst6KCLGCBjZc0SYSxEJG63MkoliNEBqsaSkrktJGR+ozpnAxPFCKAT3CpehhHkvvvQ832yhJ8iakSxUhic+MMrPhYSO9yvJhNT3XbzzIARLsfXtfwao5KkhAAAAd//xGBwQpc0PZw0NjMqO6jzk6F5j5AOcWvn1Lya65K/vYUzhRdnc2TjDdmBW74i0rQCIrvq96y0nTPfKcGv3KAAjgApySQoQu/JTek+8KOr9xi0YIqzords3ifm3zBudtBQZ59na1l5PstEKEfdyk0oR/QS4YsTt/7BWW3a/X/6gE245CgldLkJ/DUsGGDdALxS8z4QLxvtUejDcds4kMKUepS3oXun7u0q22+kj2VZXQSfbv/+JVWxRzNbhH0sRxJepMQU1FMy45Nz/+nIETaUACAIWHtrozzCwQGkb6iAie4hUYWzmHElRHqTt3IUI8gAAAKAErLbaAzTJsJA+0dM3LfGflEUqVd5xAd4aP+Fb2zhmFmOdmZ+Jz9olekJ8NAYzTYU1OoY1qssr//dvto/9f6DACQIDbbccoY+MvXy4v2C/Hz5Pj+t9c+DfVp28lXz5FTvmV/6z07KKguVERmUvLKi037NoxxS40wtvsdYuy/7E0ACTkkOV8XMBMROGr4ABQhlBlnjkjEBeFkP4C/RqjUg+o/e1kNYa0LJEpXYEjT3HxLIn1HTN91kuhAvERVTlff3/uUQQC5JaKwloDxmHjwwF3NGHJJeY+RCnp0Qv3F9Or//M/cn3asGhkZA+R5qGI+rfS99976f8+k8gmNOrFmRMEi5xp1hsZ1SFKYgpqKAAAP/6cAQOiQAAAg0227npKTRChcvaFCKViKh1bOWgRdEKG22oJgjyAgAE5JbiZl9SrHdBeqC6Tvm5yC8JNEihvGP0Ha8Z/iz/VOa+R292ok+rmbyUX7RNmunkmcUrYrPCvGILAa0Wd9FpkDIIJNtSo7NEyxPC0bQC8/GZ32uWSgyguD8f+nR6qQW3ulM3hNBRpWcF7EEv16rSTyaKxyEBiww5TjBybRELnG63oAIDbkt02ueKJ8BAuBjDvl/zChPlVuaKFw3Ui+pA/8GAESdLEJFBYxoVPrKbjz3YmbQ2nu3QFWoXnHgrU3MPjA8u9TtdIAESABJyWrE4CPMN8VL1Ozivv1Zk8ZIphu9yNqiwTbF6dB1a/Vv8jfWm/yf3+eDeQX/9GGlJKIAaqRZdElGtvNX8DJiCmooAAAD/+nIE9BwAAAIKK9zQwiukQuOLeg1nGohFF3DklEuRGpdrqPOKkBACAALkt28bbEF4S3ier8WS5r+DGE9fvtVie2pTvNv0m1hIU/tUUx+hCFlYR3OVl9b7nxpAisbTQ6MTU6L3ktTKQIJxALbltrj7gUWzO4HlVvqmzEH2hcWoIhPx/5eUarv35QQjWFnu1BxU3nPWjWeJKYhtmKOhPOglGEXxgXffod+ttagAEnJdqjq2B5ixnOIlHajuIq9MLIL8nyUaohGzv65dPNbWX/sOzrUM1JS/ebTX/X1b0fw2OVEx2W8BoigOZ7fY6gACAAAC4wGAtVrsgdQeLg5WVlBTPsfvRmbrd+3rNw2XPmdFjtUCG0qGIKFopGhPiltBle3XoP6L9+wZugJyhbSiK73K7t8smIKaigAAAP/6cASbrQAAAflYXDjHEgRDyMtXPWIoiOlfcOSUS1EerDA0M4luQAgU3btqCTWtWgnhCHG0G4JUgss2hhHno+c/Xxkf2Tqvo/s/29W9rf0jy9i6/Y3yfPyO1bNZar+mtviL7/L0gYAJblttL7VGWdgMnYeUK9QgeRREQOrjC+zfpwpuh+N+idS6PT2f2G929vZL0j/LVn/FNpAPYXhxpIoOU3bPDazm/y6iUQ5Ldk7luh+MDcuNBbYwP8TKsKHRijmuAwZqpcVkb+CAY8lSG4cQ/OtW26lXyiOy//TbP9/t6N6dnPTlTsT1b01weij6CaYIChCaTck26yaj99kbKBF3tR6sX0GRhLqFpxDdfGv5PQj+v/WXyiNGZ90a1VT6tt+zfbyP2KjssYd1BsSySy5jUyhb5xdvrTD/+nIEtscAAAIdYN5Q4RX8Q6x8DQyiE4hpX4ehnEbxDpwsTPSIunYBAJKSbmKnCggWLDJpuVK9Q/speUaUf1LZwaxv1kzUeXgLmRf+Yvr7X/o+D/yWpLieTIBLiL19YZdGBOfn7XZ9BrSZgQKZSSTckvhBnjA2TLbQB5EaZqK/DtrJXnfp1a1/6E+T5PUV6Ceh7Uqr1d1b2/5v23VPT8f/urJ0bMa/Y+lbb2XODdwYItIuOuXXkm3GHTZeVBxzK8zUfw41mFNUPsXh+iOJQZ1e+ien7fE+n7/6we2vKjO9QS8hC0VVVU3+3//9sI+V0bKgAUpJG6FbUAfRhwFraThc9/IJf8Czhjpu6xG1xLacG2J8/V4mbQ/teQKb6/G9H9fhbcLOLO9okfnZd1OhFOjIZHdu9aYgpqKAAP/6cAT5iwAAAh5JX1UUQAxDJHsnpJwAiKwBg1gBgBEEBm5PDDACZQZAIxuS1DspjtXMNfEAB6rq1H8HyOB1NubryB0ZkoGMfMVuQ+39E6Avg+ykm1Fep279IL6/a+Hrg4mHYDOqnN1f06EAAApSSLubpkDxJgMu0QB9j2uamQkHlmNQbmcV/PUqQY85Ob2J2m6J5PWf3xA6rKnogTFkwH99rYo6tG7wLo0bfvwkCqAwDU45ZI/p9B6VHggJSoqMcPHgSLKPgQCFDIjApQELA8Hyzj5ZECtm0voZjKbxwlDZgZVp0pvfclHvTeb/2oQyx1yLgQ2kjaaEK2Lw08DAqYmmLGSiUukzHMd2DQFOGJtrjqr3sXHvQtZgdcoZo3pSPKodnjTX9BMiQR+m9N/9caMq0XITEFNRQAD/+nIEx0wACPIdLF2HMGAAQYWLsOYMAAhk13cnpEKBBxruAYSIYOLgBBEIZKWkhWqdbOD/vp+wmGRPTCH/w/45Nel+aof8yXlbfu1hfxSO8ajZ4Rx5l73KshFj9yTPfQKqvb/czUSf5ny9CAAAOhDMlpIOz53Tg/76XNgFDpHXQy/Q/45H9Q/NUP+evK2/drC/i53jUbPCOPMve5XFWP3JM9+Kq2/3dRJ/mfWKQqKtHFkB1Mw+ms01MzsPmds6NWtm/+npydC/ZipWVCdf5FZLHT9s/eLF53YLKkCa+iNcLJgmR0rw1myynviwsLHqaMYEUWh4TxDRMRTkTwayYdln4V1O3r6vr05Oi/ZikqQqENIt/kVkedPo2fuwsXndgsyQJr6GjXKTBMixKV7s3VftTEFNRTMuOTcAAP/6cATM3gAAAfY2ZejBE9xEhsxtGKI/iJiHcSwkQGEdi7A8sZQ0DUrvmssjaSfOSSzmzm/KS+SBdMc8KXB5hO5hltdfS4PMutP39B0u7P0N18Rc8qGsNOs/KKceiIca8qKc1V+oJJmaN1tpElQDJJTUyfIEha0Cci6PEe8BQddU3qWm7cvpMDqhdafv6Dpd2fobr4hHPAoaw1sfXwIUceh0ca8qKNzVXXqA0AZTRDHGDRRhhkoQqPOCfjd1NI9yeO3i3X06fXdZzODDUKih6zJyep55CQTY8AopALsijx7ns2/kuSIjQm6gLBRAdc8CNhBHMp8+s0vCgGQiaQWXuCfkvddAaMFG4DD+q+MAzAU8L2CVyVFr+9ZrDpY+ZAgWSVOnaCWp7vJJU2X/AJ3WMNIJZ0swZSmIKaD/+nIEJ1wAAAIfNVrTKShQQubLnWDCCwh5YXmmCOfhFKBs6YWI+AIAAILkclGNGggmKT4J0peWWGN9C9lFIU0T+X1Fq6FbrxjHyd9Ua371ylV5p+83Txiuz2j1pTjnoElCirvb1k9Grao0AgBGAC1JG5SCT2kwC26xX4gQS9G5VemdvCdAYbPRi/6cm3WyFdOrzq5ZRSG/p08EP93aXWlOoVtaaUVd7vTRwztUPADAO6jT0m+4EhjHX2X7JhQTYKvtcaUX5G9q6Hf5RmzVq/f/6e5D/pavjwbWbpXzj9aGDjrZGX59Xp9jPtrpT/L/Er6QACAYBLckNZhbsWnb0xfpR7qBWbV6Bl9lUr7qzFq8cEMt8ao9Dm6+N31+3/pc7KIbRvr/Qfb2f9W2CPozLjnUzu/f7E9KYgpqKP/6cASyVwAAAhw2XmmFEfg/yBs6YYUICPh5ZMecqyERGvB0Y4j+DDAFgbc2224pfXZzD1VxJG3B+a1Wxfwn1gddAN/4Jk+jNW1LLuVUfgzNO9O6kr6gUpFTtIHy30N2OjKn8J6xNSnXXpAA0AAFuSQUfCoZeXU88roqCAEzAekJiJvIdOFn6B+pKOv9FT8nIsvf76RdpV7dK31NpbT9UXiT+9bPs/4PnvpAQBMNOAPByeOZvOsoZ9iKisEcqJuaUIxS9gTbUBQc+oCOrUCn7E0qOZqnMY9FchNIWfjwx1uv9g55r8ilKKgWxZ0NAUVrBWo1MhtIxNxtuYMHDMfKO1Q3UXcxW9LNhICa8j2w3r0dG/7yorKz0WTkVsqe7L01EJEbx5VbGkUtRfrQEjz5w927cSnb+hMQU0D/+nIEzbEABIIfNdo5jSpQP8L7Iz2HCAi0YWBnrOsBE6xtpGOI/0ABB2a/CgRnpVDmgSryHYfz3LvSm+8wI/jB+iEbxNunV++hdVRPSq1E+Y6/X1ufwUeqpYj8n9S5141LH2To0tkHeps6AE5ZaTAeZvrta1C1zgWHm1BnLqP0aPEhbxAX0jzJqPs6CBaFIkIunM+0Oiz3OWRDOVmqPs/fPdzfMRTQ74cvaAA1JIhSYUsRNCcpeMWHNCYgq38dUbySYpal64dvhQvfG7T8VDety3vdqEWp0QWAt8ne08wgh7EIsF318OL0MAlX2VmtOsgAVQdYLuUfnETGpmpQJ+ap3qSfhbpBteoAfm6HvLa3d0VNGokj7FQv9kV/x+b////1rVG7I7ran9NG6ixkDTX+O274maYgpqKAAP/6cATetgAAghEpWzEnEmxCBTscMScnCNzfbMQcR/EWHCwo8p1gAQCqrlSiP2gP4IpS5V9gl5ylqZV34gf33oKDdOr2+3d/r8dLwj4cBZjJ+HjfYxg/umEFECm+vx5GRCtAiwG5Q16nAAAQgBFqoB0qm7rgpF9CbWhs2k/IcVDuVyqC58VFkeatOC4h1P4y/vTpbVV6KNrTzXaiQUbJF0uOepSMxi2zZY31CKAlW0Q6x4HQoKtFil9BdyqtP1Xws3vj0Cl69XRpGdBXoy2qvcuqoauvXd+qpMoNxZdHIvkKXMMmVWtQ9tAcKHlCSzewk2nwAAm7dojc/tQYDlyezaMgwo7oDdSpK6hYKTs3lsnhG/8qYY22d7W6+5u6qnX7buqH23K3h7uufuz885FQcLbtmYfN5Jy0xBD/+nIEwjQAAIIYKWDoYShcQsL62j0HVgiYx2LnnEtREiZuKFCK5m0QFU5E5JJdAzGhKWwOqivRXtsKjuHgxP3qLE7fvfMvXd9/h6PDy1o60LWcPHUvijhZT5V9JZZlzwmNX3ewjT1tA+sACEAACm5GOKZTZCA3HhECkn2PM0tNArPMuJsgvLAi2gULeJUqdQFGyMSsXdn3z2eYJ3V1LVrLCjVvq/4rjtLH+xOoBBAKackizEsskze7IXCuQUIjuNuJyrxxp4cMdxjzvewg/ToyJNp4IP6k+fQx2SpFbUK3RJettQZ0+DFKqyz0n8jY3v6uhIKTScGuOdgHHQ/iou+4bydNBXw8O89SNfzZOWXgb5+QKEQA2X5IpW+bBsy5PqnlfXfSdqIZ7vSOIQzF23JsezuGc0hMQU1FAP/6cASIMgAIAgE22TjPOLRB5ts3PEVeiL1ZdaCYozEbqy1oZIiSAQACm5II5y7jtW5EDF4+hMnI8oVJ3aFxYW463UlTKGkuvRna3bo7/9TbxWllXXNVfXOj/R1FWU4ZHTHVy+rv9oGACm5IG2I4RoZGY/ca9Cij6gHMDSZocSI5RuptoIo/P1BEH+yamK77l6CbPYw/ahvanfaJiFKV+ls5iJUivlE16ZCiCWWnKSXgi8JbyIhWUA3UIg0Lykfn8bo0YQd26BhGk5v/+76CJt6+sv340//t6u37b1rbzEVHmZ9TaNl0Gkg+5NtbFzIgDAApOSbNUToQPwEWyDKD8B4VW9iF4duD+dBuz6Cf/rT/inSiAbJVr8rdX1g2X/l/9Pv9qpcYqtu71DPTnzB5l2JlizMBMTEFNRT/+nAE9G4ACAIMUNrRgjt0Q2rsDRQlxYg83WtGHKwRD6+wdBEUTgAKQIKckuv1OoCyjRT0iUfu/iea0r3EPwj9Wo1S+/sm2xeV+nqcb0DzF1+a//UuvSnf7/v/1b/q3lso6EE24D9I5IQBQwtqSyXzExgQOuZH0TpF66XfO2o0CZJp/yGetebrE/JeMDXvd37Tf6j26fd+r31+3+/qylbxye3kFqwimikphVXIEFp23Qor2qC6l4gbJZUv/UP7DDxmFt7v5aY0nk6uPoL5V9/X7J4t+363VmyAxFZBqdDHdL6hlGj5rbGGmKP1JlDEiFU2U7bbeQ2osTJlToB+dH91N1+K+rF6dEO96suqndqjfQO/Det32jeWjPVGvt1b2S+y/fzN+nbzff3U3vtnpjBswmIKaimZccm4//pyBCujAAACHSvZVTygBECiO3qmCAGI8YlqeMEACRsxLg8ScAEAAgAAS27YrLmFQV1C35px7l6eb929g5xgM4k0RRuHH96alN/u33b3/+vUVP1fbUBMhU7+rKuSLOVZYoqJ6AKzi1TM2IgSBARSTsH3v4011dfycx062fcRfynZiIIfIqejZBENlVv31YifBF9+vbjqyFTsZ5RErhbZQ6g7JGjxV0cD3UbAAAAADAgEPilwoUhcP4SiMYioen9VdHQrlI4gGEYgZnWOhJWPBOkujp6a/////////+jex////5w4G5GnP////++LDmEDNwCUABQBQOB7IZT0gE4yToTaCdmq9GHzjykca7Tb83ZHu+lPfX////7////+hnmH////RiYrB240YwmP/////u5o3FZISBmcBMD/+nAES6sAAAIWMN6WGOAAQ6eLwcMcAAigj3ockwABEw1vl5KAAAQQMMcLAqOUzHZSK/8sjW9htMoYpUJwwv8ZLqJf+8Jx0oUP/7NGgsLgvNGn/+w3cgwQ5T4cAgnPn7P8q2UMr5r/66yQAOVIFRVFNTB9/sjU1zWEamYo6E4YXvxeXUS/94TjooHD/+zRoLC4Dx0af/7DcuQYxP//zix4RB8v/8J4Ye3//ttDlcoBggIWjqKe1jU9VrkZoIum8EGxW9Cqd4yfU7c5WsbNV/ndnm2eNw0VMFhEFI4OioALSMyxbGUwFcq92mi05/9mQM+l0AAP+lAwpFpUs/pZLfHuSuMAmbXSQOq7pmnrmGU51puCQ29C2C4qRAIqYUWPVB0VJFqYLPfISMzcMvdp1NOWv/2ZAz6XJiCA//pyBBU3AA/x9RtdgW8cAEHCu6A8wjwI9G10B7xjwRwMroDGmciZEIAlRWbdR+43Rb4hzUkQ+WzCmZybpTyF95kZyQUb6wdVFVC59KhepZ8GbSxaaN2tTFpNb33O/2rH/r3f6U+53A1aqNxW1GYzsvMuoM0WDItRKDj2MrQTbuDAyCyhFxOG2CjRUTi8K4qA1BtqHvS561zy2Le1M0ju71ybejb/83CiGcM9Xu8uUe2IltYtXGMw6m4Q7ewSDrNSXtcihsimOSkTgyx0QgyacPHOWZaFHY8B1111Mm7XvqcXuaQrbdL2Ooa5s0ev6bNhYuKaPFS61YI4rZx5RmrB4uzO47/H2Z3LrajKuUQAs2dICE06FrizREGseCZ9dbl1MKm7R5qpxe5uttkTuuoa5s0e+T0ITEFNRQD/+nAEO/oAAAIZDF/IYVsYQ2GLxhhpOAg0ZXAktGrBCxWuFJaJyMDGCImEjYwIGYI0IauUwZRYZroH3mSThzHjmsRWYMRguSmJ0NIF2AEXcVAKA2MhOfFspKgQrVQRhryrrkYlb799tgsBARKrfg3FkAKnBpXKcW7xYB1wKEa3AUgOY8RNYixBi5re50S3BdyA1DYxJ02fFislKlCvICO2xFbriWSbRml3pFrBYF+lTIygA6BpINpHc7uoioASowUcMptOjS7kogsjRR2Ni6Cy1LK3HR+hRoOmCKakWNU6hG1tpFXdRofR3S3aW/XKwABesETzrAOMtQQ1cIHqHnljE8EKlVshqgkbaRs9ReZjvd66nRb63WmrblQcaR1GrG3SqO20RbajfMIo3Bqmi0sn2piCmopmXHJu//pyBHRqAAACCE/iSKEWvEKDHA0M43MIvI9/JJRrcRMMbnSRidTRWm6ultTXkRiqarzW0jQxzF2Xuj7IXmuKbdMTyio1mczZTLWYQqnRTxCJhpT+az///5fzPR/wQdU+Cu0/559NzYgaYQIq7I3MTNaTETD1SBHZ6hlpfKfvM9VfntBg06dvCq10iQXFB5BUXJEKlyM7WYFZ3G7frd1csbc+CvPqYhpuztwJRVbpnjL5Rm3JRFoT2tlhYLVq3987J9AnQL5ueOZ2uZW+fg3H4MC4dRDrx7DwsE8s8IeQXWAmQoWtPFZJDP9cT8Wz0lHUAAMAEEMpNzPOTYtM9iZBSrNtc99kFmFsJxb4l/iNS4qfkFiNo5UqHsC01SEs+tTTLg1LcjWZlVXCUUZQSt0OXqp+tHSmIKaigAD/+nAEX3AAAAIhDVxQbFiIQQU7ejGicgh4X22npErBFBatnPYIsMAEAQCUScrQWPAMRXSqhjqLwfUf7spI2maWeGKfBFpYcFBaqiMiiRRzSJYDmwqVLvUEulh9SW4qr+6xSH3aHqk1p/dJIASAAo7bbOXqmDwpmu4WW2+hdgbyqsFhU7tmwL6stUO61NSnRjK+jJd9t+o7bCqGmjTUy2NgFp36d2f0W9N5//RQAAAAAAnJrvnxCqqZ7uFkptnFvui/7udCOscGBPnJ6V45e6Pva5ZZb7tq1ftLym/WcICSFIuhLx0509LPahy42t0N0AQIEdt3xTOEMLQeezxmxe+RebTCPdYAVnCKx9YD3qupzW26Dv0d/1ftM3RlsQecXjXqgIZdeWrOJ6E+jui15oosrMNZ1piCmooA//pyBBA3AAACEBxfSKEUvEKjG8kk4leI0GN3oLEBIRKMbUw3lHjAQRVe6us4SBmHuUUZN1FtY0Mc0pksTaZR50xZPEB1pQ2PAbiFyIHesgZOCgjFi2VZS9T3iO9btiGvrUSFV6f0I+qgCAVZmuoeiw6nu2pqny68ccqE3S6MsNo+ayuWPxSw6x9Y7BGXhYLPvzyzS4iW6WdLMGAFRC88d1gDjab6plzLd45kAAAACSUt23lrSQZpI8w9+FzJM8jIDXNzE0nAi9x9nfO6mc6q+lIwJoPHSAWHexBQYgsVJuPjghK8PRdWBXOrcSntOV09dYJUm21Iw4yjFhtMGiExc6lidZ+09BilJrJ1oRtCIj1ORVngqERPqxVabwk1TZWq4YAA8SARO8qSXnrNOEq3Lu7ukXq/uTEFNRT/+nAEVjMABIIJGNs47yjwP8Mbej0nJAhEZWZlvMPBGhJvKJQUvggAFXX/2g5hi/Kq1rEXjzQlfRK/qPJVHTjT2lbDSU7dkQbfP/ICYO1zEadZVECNiiF7qk2TrZaAYzuRat1uLfX3YAAACZd//LXUxMHdNlNCcqGMh+FUBQAt0fc3qdvkOyISvkyly7mqrHxGTU5KqDwWY4DDiRxa77Orjfdob8r/2ggyW0RbgkSsJ4WYxKr5QP4bBk6l03/KAjg4Ai5ExT+dzXCKxs5hNu2SyW5f5B4oMaERjzp+ckI1UP3yatlR1t/06NlCKTKcx/04Pxc6TBBFbbY+wetG+zuIO3GAfldS6N2s+rejWHOnZQERU8NUcNMFHMRpvvgzpo1y5h4ZLXOQEnjqqjVV62aUxBTUUzLjk3AA//pyBCSRAAACGC9g0KEU3EHiqyNh5iwIUGN04CThsRYlsLRQi17AnA0lJHJk2Gi1jkBWGnK194wfgKgrHXAL7H1tVYfmdiPCyKSJaj+CaMe0CzjCfxZyzIxR19En6V8luWKooGIOyo2NABct21y5AjOho6mclihPZPBgpez3eUxp6DVaF0hZdtn45FTZ2QlWnBCTW76lhaT3LCjUiC60Kqer+667u9fSV//aAEFJJJykYOqB91UJ9u++347BQPqRpburrRzb1kRUUCZVhd3PDgeoCaGpYOOK6BZiQXFww54rnqct+0j2bVLTRev9MIRCSTTckkurPHuQ18qpfPOE7teObFVoSm8pYut+L835TQiLRQMsitZfmGYeZiy7RrwRE29n+bps4KkHpYqXNIvuu5C3OpiCmooAAAD/+nAE+eoAAAIXJODoZjwcQCMbIx3mHgjlD4GjhNrxFh8v9GOI7oACWUWW5I5dZwGoP6BESGV/kFL732pXS90Q7RnYO1fLf/NN0LFZog7JFFJkqD1S9NkqplJKbtXelM6i4a6YsDlW194Bit2FFAkNEUq0U+YoZESVw2oZYH7jYe4KbKR8PisxgZFbY3W/8Q4wVN6zJV2VNb7rWJLOoZePV0XyElouX//ooBQISRTkjtxp9VIjpc00tpe2jBGQmvovW6brpYeEt78S++ZxEMiPCtPQzRX/4/rX5TWwLiGhmf91Cqx7liq3DlDtydV6KfVUUCQCgVXHJmTeRfy0QdqZe3mgjU65ut01bzoLyvsUqiUcpzr6uvt6e3q2iMmXpbmf02UOhYYa8ZtuWg20900WQlhZuhlSYgpo//pyBFgNAA4CERjXmw9BcD9Hy1c8wlaIoLNiZ5hPESAiLVzGlcoAFSbDWFdlSxRpBUB2ocdw72S94o8IEX9n5fRe0MyeHK2Jn4FgxkeWdozvIfUxEu5EesPj7pl89XT8ndrVMMOZXrWAEkFOW34y+ZT68R/spuW0oNf8scA1ggoufsCFI+BNulv7/+/V/Xv/6+7deq+z0z2SJcXcBS7iMirSat2itPKtyT30YrmFYGwql+4lmeWDzLk/4Rt2xJAzIAa/zTobOaGbHcirsm/zafU49aPw11dAaPQeNtauaT6mwpXQajzreGZT1fMwABDclt3pRIhhBUspRE9Dhp2qsnOdDcYvMmLvqlHcoF7tT86bq1+dWVBZNBbmbsrSO1qn9fv1+zGRRKtVL1FafgB9ncR8Qv0piCmooAD/+nAE5PgAAAIcGNedYeAAQCfb6qCUAYjpj4J4JQAJGbHwTwRwAQA3LQNdzXEtJJkcE9j6R1WCMXcNuJcvfDn20WhwRMWWsBvW73V2K+n1Xf99wWA4eiUCLJXVlqz0I2slsNyuZs0YL77cMFWBMkcuG7EiQoZmRmDVoFrVVd+nqbu9/2KZHffZLHGGLjTevYnoVv6t7/9X4ieSes75JASNwC9RJ7XG9SdPWAAMBwOBwOuFsqjoxU/Ps6f///////x4SMPxF//4X4kgFwawpFP///C7C/J3MMOEQSf//+PDScs8Zi25GT/////nse8+PyfIAAKBgOBwPSDkK6s+/JSh4//Yz/av////xoQYbjf//EsdAeE4sU///8F4OybmGFBoQ///8aGkyzxWJbnn/////nse8+NyeRMA//pyBEdSAAACKFVi1gjgAkRKXALEFAAIiTGDvFKAIQ2a7peeIAAADAAAAAQDgcDgcC/dr3dB4NSva3/////85kORb/+WcKKTHRJ//ziZA4aCQPCYz//8splAWnEB2f///5pQcPQYY0qSHQwBDDDDCI29Nraxgf4HuIIM45wRHl2T//37+vRkNWavZFeOcQU5RXSlu3dyGIKIM+/7/srUDqMv/9vT5UR0HRM052L9mvEAEBKuJEgFWoY42eTtUGK2MHUK90XC3pyXXpr1v+5kdaNo/9DGSrdufs3b2yv5vqn9PWlyzKjiYx+6bDo6gAnWFdYykEBA/+zk/g7Y7bx9xKvR8AIkMgIXjPMfg320v3Jrfv2a5kdaNo/9DGSrM1ufs3G3CF6S2tnZWpMfDxH0gbmAVVO5Jl6Ygpr/+nAEgZkAAAIKNd1JIS3AQ0a7mSwitgiVAYeBnEyxBprusICLHMAAB+V6vdJoE1ZssM14LyD4Y+VZxv1lbup7zL9JYxanDuv9lg71/8JUl8+miBpCkUraGdmixbJbQndWWLEpE7/kkAgmqVquVE0DFNcyWYrOm9QVn7MdxKljYty6t9F01n0cg7av+WB4tf/EpZm+v6RyH2hnZoSxbCZbQlt1ZYsSkTvr4lhKKQSb/1XtJUg7IdzbLPWFBdRs79HZWzLtcmW5r/eWpEeiFZPb0XQv94ebDt9b6s6l+Omp+4wWFYmXS5il08sS4jGyt8AAADCJ/q9hjGilnFcxuM8AYWom4N4uo4m/E/1wR+t/JeawzBckaLPfKmQVDy1pg26pIe7fs0C8KVsbqkf3u1W2pTEFNRTMuOTc//pwBMv0AAACDkDe6GYpmENoC50ZYjgIrNdu5LysgRIa8jSBDm6AAMAhmS6y33glihgaqMszjC3SEnpz3xlC7sr0kFVoj0fby6s2rNUy69Da6f/jf9fJpWUduflZpmd96vGv9odWmIqAAQGAErrds3KUKEGLItb/JM4AqLiqw4ffqd8fzrdeRaet6/upEU9nVHyM0z///g2/pa/usoi5LsSlNpW33+mjse3NwACcm2u+T0YJTnPJ4v5bSEV+d5ovW8lUconllWs11ng+e9HrrUtFb/971DyLSu6Pv4iCOyDxQ5ZDk9TXURzf4zpf6zsk4VJxGyKy3SS+zTKaUte3lFZVCCEW9qnO+8rqDItTNF+9B0y6NqXJei+GQ1xSGjax955pPUZWIjOo3Dn1ub3kPinY9d+TTEFNRf/6cgRdogAAAh8ZWzgPMGBAiaw9BSJliMDXc0WEVmEVmu5oFgg6AIG5Lba1ukX40x/pukSG17lXY/oRywsZ2fPsnr9eW+3r+M3TtSwgkxN1mUMvD414kApQ6o4e87sYo1x/7uxj/28/9ddIBRMTTkjbcSNTKKxavhVDy+Uezzo7B3meV97UMYQ7oqOdv0fNUymPnQ2vlzL/9dBLfn09Sbq1O/6f6YLokUWz+CAQluy2XdqrgPCc1pq6rmtyIwOVFGK30vGyTPQp0NUZG6NzzCKd1vl0Xdb6Pd/a2rPneWgvAQigc99fVc8/b02Dka6+o1AAEASbccl7YrHi6KHqVlhG14EmLLfRe9yq9cqSj92perKh3et1NoloN72/u2knurKdbKgsypZ4qIn//DornfsYf0ov6KUxBTUU//pwBKcnAAACBiTZGi85YEADHA0gJpWI3LF5JJR28Rka8GhQi1YIJMyTKJsBNjJsNjeSiPcJT+iqk7A8/d7rlAVvGKDgmD2cikw1lR6pM1XMbXsl78r5FYK+ry3+hHZqu5r9fNO/txARAbSkjbbjVKlldJSx9VJJiRjQWXve5gCamIRfWg5OAxM9YfDWwa/5dF205galb6ziqMpRch1c+392h27DrKYIU3b7q/tWDDdc7FLfstonVauVu9xZn0hh/O5uy0exyADK6ps8lluuN5SaeUKnB0ERzB0Lua09G5FCmK6rCSqSShn91CPrcAAmxkjjcvcBHK131O8OC6mVS7rdyasjK5wbSLGATRXz+v2KZdi6Kwgcn5nVe8Sqovn4pJfU0iG1jA4JX1GF02CNw3uU+xKYgpqKAP/6cgR0dgAAAhYY3UljE6xDgxvqJOIpiKiVaUegrsEYErC0gIsOgACIWar2KKDJ0VVrVzTFRBDJPQ6orpAng3q217tHnluU2mrOqbIqLPQdlheI4AkYEcLqgtOUu3gs1dwYu9LHU7uas0ADQpRttzwqAJnSuXM6MgRhDAEKPv4TBsvcbS5w/BODhE2gk9R1B2m0LQtLJh1hwayLUPUkgA9GlJpi51LbunLJrvowAAAE277/5amUC6eXiu0KQt3hehzpEWiFaXBgeNRZx0PkMq9Zsip/Kzoe/07BqmVrUSUMHLQl6xTf7twp+HVEdf3u7P6sSASGo45LJMz7hTe4tf58YuXxXNkzyEx+JUrXIm7g5F6oWAuumsVKuWAxxOJDyxHNRZ9K44o5YXnt2G9yKtTKOsIsoyUa5MQQ//pwBKUAAAACFCVcUewpZEHkq+okI7OIhJVsZ4zukRoXruj0FO6AAEAk5Jbd2vgfWW9qiXdn5KQY5eHaov1kyx/fzaxRRX6HTZnT3SdHAoXEg2cUhSND0be2XYPeoq6/UJqbQr9N8sHcAA3Lccbd9eg0Ut9Qf/6zl4aztT3uaj7Wf7vTbg6RnaqTqmny7MnTUBfdPPNSgXm4YYs4uixe9CP204xXpZTIt0VgpNy2/LW4gydN++qUpvErjVMG/TR0B4cn2rHXt2I6sahl3Vq9/ff4gqbQtu/3LPXSSSrXmQ2HkIOWoMD0Urv7bH3/ZpgACkFNNN358o3Y7yeDUvinwULiS3FIsd1MbZF5epWP9Bt/o/R+Fb/InOi4/EfWd0ZVgvYDqEcUWsY2ddS/8Gn0y4cOqF4DTEFNRf/6cgTblQAIAgkl3DgPKGxBBLrzZYV2CODNbGA84REbILAocwpOAICkUUpLGJBArO3u5deV3rZNiJIIA8axJlEB0jv57UoHbIrIo56p9aeE9NczYOSjf+6I5AmDt5rqq6fq4wOdu3QACm6BvK6soE5DhKTjfYTrOYFylc1GqwXLdoZKRWIKipZWUulOEXQLFRXnBtbMIZrTjtabdHfD+b63xFKb6fz1FYSjct2pwv3Fsnim2lp5g44XlmxADZs9HPKFpSqtUxGo5wbRVXNRLu+e7UfURPfRtPo/+Sk68lstCpGmLxfYor+/qFq99zE34AT2m45JfaaEbD27vNXEx8Q1dr39cEntDl1Xu0PVLULVP9H4h+/bt4J2/Udqm6tefqVtC6Ast97AkJAqM9ZaxYyVqqWlKSKYgpqK//pwBFZNAAACEyXYGwkckEIIHBoYIpeIsQFs5hhO8RUSrmj0iLYJtyUDmUAP2AmQKo1xhTrM0nKjdpLMPqXQtcxwaJCv3hIo9cYFqLOQVWqS1qFDbYb1P6xaGfmfwYpGO8+lNW7o/RKaA90W7HLf99AAps+5PiIcHrDNZfbuDojUgZuivyIsXZ0M+6JQR7//v79PI+vp6OhHVCMIUUr0Cy8j9MbWQYimq7KQCBJKKUnEKwM1iZEhD4t/Lrm0gGV3XASGv7bucG5+UqULXMonImZ9P9uCG5uvraqcj9WLVunt09GqDOqa/1Hun3W8W3fVAAEgSaabvp5h/zuPJVa1Xt8SZ0WTJNq94TjVsx0rcyt3VCqxE5u1KCdizi2xYHZ62HnFM71FS7ShL6j1ct7ra1qSoxZ1JiCmgP/6cgRHBQAAAgEk2RnsE5RCiEsnYMJ2iQkBbTSzgDEekqvOsLAABJbcg/bmUiI7egV0Rx5UkdJXB7Dzq8zwalB/og240Lcr5NWyJZNH36dHY0VbknurGQo4i1G7paty3RH9wU6vycBAik3IPz3A5RlDm1V7XwYpWjuohU1JGjgayeOjTDFTrUM8xBykWZ6V2bsLqe1P85L1//X36eW+3t/tqTgn39Ps+LwABCFNeS97CcUiOsiPqH67XhtQ4mFWoTZaFXzzeab0QeHTkzKPU7uunmt/NTtzU7WorrJV27L3dUNMWeQaXFRtoUoAEtOWVSoBNs2GHvXbPoEWAuGdi6LM7BWVNMzbtmMAzgk2SRopg+rTcksaKfK0e9m77hnXFd/LP/9Ce3jtValCKsFUCRat/6pWSpZ9vuTA//pwBNYYAAAB8U5dhiRAAEOpy7DGFAAI+K+DXGGAIR2GMbeGMAS0NqJycyrBE1HJDQUorDxYGbWZwAb//9+DcOVv/3FFdTf/4gKKGGQP//+HCLPOjf//fxp8IKUU//5QVkGmSki3AFNnMusqbKnS/AyJBYHQwaGAOtRtBIKHf7uQ/78aYTKIf/3K6m//xwiLMOQ///4mRZ6o3///tPiAsop//ygrY0yoAAG3IygCXPEBJRKjvLVs9iebNwvzpSGx316V+HSVqTFz8jtCNlmuj//83tCgZQsKAJt1ORGsaoqg/28luLEOzGJyQKet1tTAAik7/dpI3JvFZjbpMa7gQYoxsKeAg0pQVedJC8eNPAJzBG6xCToDfqLrDZ4VFCDKM3UaLLsOgPr63UQoZ1aGPesAnG8lXiiYgv/6cgQb+wAAAiEE5GhiGBxDwSwNMMUXCHzXbCwgTkEOGy0BgIpoAbiBkkkaRJdDiY7xBYUxMH4WcKDVOBEPmoqqdKo3vNmFGyp69dRYVQP1E2plFlpR4wlxb6FBRR2DLfzc6IEDRrssKQgAGGRNVakiU2xDD3QPtO6ltMASg24YoODkZeoywaFGu1Je5LwWSnsCjTLcVH32T0MxV2v9wiEQhMlgmdy1pF67kSXfKFWsGaAvzanXrkcxL/7N6rapVQOcyS2FOJZu6cululpW/2qbZ8rqQ/T5hS0X6XTfgyFCtj8N69bD3Wt1zxQDNfS2O/YpLc6Fb2NrYjEPXp7Ggq3LmNUAklnZyh+5b/TZiozI3W61NQ+WdYtPCzXl9BS0Vn6X78GjriNScq/bixb2toSoiFU4+lMQU1FA//pwBD8nAAgCDzXaky8Q8ERJnG0gIomINQN3R6xFoQ+gbyj0CDRZmqXOTpON1qKhD5I2rtm4NF0EhOik9/IqfvBCtgboxj0VqqttW9PqXY3lK6d/vYzfQzz9kFP1x6uR/zt5Fn//+hgRjTLrcsjTcsNs3Fm3VN8Yq9H9EK8uAxzbgJDm4Z6+G6lqR22ViZv72Mzehs/xX/1ZH1wTr0+tZZV3UI7NlHxe/WrlSAOF2yu3GgKJ76KZFy9czO4Gya/5MC/9H3Qm5I5dgYSOqRvBE/fEt5U07Uv2kSyf8X/bafNPYH37ZtqEt03fY/4aqAARxPXeXc5CHE0Oee6tZecBIiySnrpuXoqbMadEQrtvmg/6S55KehWZTLy+7++o/t2/Twb79ZIyhCWp2chkH3qlSCyyYgpqKAAAAP/6cgSGKwAAgh9A5WhhKMxDiAt3YeUMCG1fgUMcR3ENDyyJhgnIFKbjkckt1twJg7xp1IDjQo9mcYG+mrmM2tvitv+3nbrXZRW3a5HFT0MnaRJujgbs2Me6Gdb8QPX9wez/1gJNiX/DtFYBwjLrth1ydSPck8fL+su+xV/2H6pYujiQM6XRuialvZstf9FEvJ33aWlez4po9Kb7+pv6t0q98Yf37S3r6JRmlOjAwiCVpJppuUGr82qfNThzXsUF1ZjLsttE7V21b2+VuE/2/OV9F61/4np7ZnldEobdHs20mQzuj5SL6Hm0KX9A7ruxragH/9fwdWGIEbHDjr09qenWI0EqxsCsH41Qh0Oxn+yKVXFpIiJInkpr/Eyx2caKvIVuaeYJ2dRq6ThNaapb9Rd3X/4eTEFNRQAA//pwBAEcAAjCFDXbOewRaEFCOyM/CWAIsI1qZjysAQ4bLYzAilwAoJLbbi5MZWajlAKmG+8dsr5eAeG9XrxtU+jsRfo1zNssb/wU/TkFpot69OiVS9MzJO/QbjXBcbo19VznUfqt/r6gAm45D1EaK12ZgWrLOiYzmS5ue28UOteJpl4fFF5k4Ic39VbFscIdtjA9DdKXpe3iq361EzUr1C9mj+xl9/9//7QY5dvzFbE2RhKMDx5/Aplzm8ioFkIFhkYzOVlqfUph/u6H2eujk7bxuZQnvDkzeiio/pJUir69XisUiwvrG0tdWGLUt3rRcUklWlRvA/MjS5xew+5FsAPFT+zquziI0gpKCEVrG54QehQTf1fr379UpK3rf2nF54UxVhW7CFNjnZMW0crVIfKurTEFNRQAAP/6cgR19AAMAgsjWhnoE6BBJFvqGCOZiLA9ZGeNDgENke0Mx5WIATd22PhhhStA73rQr9zFi3Ec1yPA8IrcJx21URRhdNSIydyXejdkMvM3Bj7M0hpKjdNvr3n6C2v125WoejX9SP3gIZySm404V6dRea/xaSq4t6TdTK7KdJS5X9efyQdh8Cj3BFolxU6BREdDQ5hMHokPnWGnsFez86g8Prtzf2O9voALlt2nUqgWFSK6N9ofLexSWtxiG4CJJSo8IMvzWweAGHJ55i51NxeJEGhNA4ca9iRoTQdxdC12bCWd/0FU9T1egc/f8k8B7bf99MNkkSl6ymT8bXd2YJIsRYuqjAXaForVVtG7UUjW6GFBrC2VVagTFLWy18HXDR/2o6rgVZWSyFGP+y2mLfv9CYgpqKZlxybg//pwBO/+AAgB+DdcuSEUzEDHO3ch5WCINI1sxLysMRMV7+gTFBYAwJJSKnbZ5OkR9bePXljQFgVW7hU16xpnR1oBlkGsi6138RkMj/9TJT6LjmemWPOC9jEf+lfHfufKCMZTTeoQQRTklqsqgLiA1CzsJ5M8EzZr0vXWPVlXVGvff/stv8oZyI/9ZRJrF8jF0r2Wi1QYxVy2/Tx2Yb3oMI1CSvvoqEgst+1bGsGzaOFhHuWlIcmjsSERZ+FlQccYh2oi7oj+jac6/3GsDoC0CVDQ+H4HFoxTzFhHl93d1V6l1e98Gv+9isENNuOOTWnoilWtmVsKaySClXtQqKilK45Oo03t7tMtHYm4n3vf31GpbjnEn0qseqgPrQf01qem6xrZdqVNtUb3YroKpiCmopmXHJuAAAAAAP/6cgREMQAAwiUQWBnlW4BCJXsTYSJ0CMCDd0MsaXESEeuNhBXYARcuwhqJbdCMjAPwtU4lYgz190mhphqBtBpMrJk5zCs+rqWfHBcaDZp40+x4Ul0QI4SiO+2TU2UaPHXvb1VPWS5zR/9IIat39ds01OL5VT+/AGovC7sV1Nhcbtsk3sDiXpv1bFVZN5mPXt9tmNK3tmCff+mFudZbUhdT7rpT9VKjUjJW7/+gQAJimy25Xu0g9GbXzKatgkT3RWtyzHkzddZ9BG1z7EP6I/0gP4IaVP79qhK8dvir6AAhQdnZ6tmivBm6ECqbNSW565dPpAJTtog1tXFhScatq3rzOtzEOSl7J+yaKI3MFbGA8H2sKUaUTDPFRjnNuh0XVuquZN/KA8XPsbGVvTn3JQSXYMz7n7f6UxBA//pwBA7nAAAB8xBXmfhLAEKkawNhJXQI9MmDQaRM8Ryf72hinP4AFK2hgbGx0C0BJjKmI2/aRE29NKxfRrGHCcaRRDxLUYQEAdMxYFmkTW3dBoBZFZardzllFFjp+xKZWU/avQAE7fxhLWn0rCBQDXqVstFSyXchpKYKpU10+q2MDtU7nWB0tFdZDvtv6+iu3ZOoU0pymGYZaUqfcw/0xDKL7bejb+qliv5LXbNwQzM6MSHlSKYNOtnLKG/85SXU6myXOjZwKq1tlCsy6o+Ebv/pmDSqCfQ+g5gTOUc4ieBv/lmNTWdkaRUtQpm+z1ISBU0445dZ9c04ZJNLYUPZiMFNsizqQgl0vvMObr5FZxtTE5gStj19unFTaH6HrQ7pqifX8u9Sc+mmUyIIWFJKrFSb9lwZrTEFNP/6cAS2gwAAAfQ/WpkvKiRAptsjPQJyiNkne0QEtrEhKC3clhVPATTlwOQlAfAZlzTVWmWtlyOhGozIFA1cSj716U9RVHf6MNubZ2xg7sr1/26dPbdejf15DrGotPuseaHJa2HACk46G1RICEOo30rNCtESLeqJGsEbjU1oUAfytUd9LUt7/Xkn+5uCZ9v69GZNJETQQ9VdSAi0LsKV/o4bbAmp1EDE9JOOSUWfEoWTU3dFrIwAr574YMxvotzbkX2gfF95DCRKFnOMFazXr/RWuUmm6BnEku6JRayd+NPr/V9LZhWUrGpNxrhEIcpItGTGuqwIV3Fc05C7uACFhyqeA1Xer0OMZ49jFkjTs+v1P8/Ge9volBi7suhbOVqm3M/02zdf//7mDDXI7RrPQfQh1rntJiCmooD/+nIELREAAAHHJdsdPKAERCebE6eUAIi5NXgYwQABJiawJxBQAAU5JcKx6yJ1WsfbM0c5fewqozJmDCFeMKTm5a+jez91dtLVgddSjXOVitsrEFed+Dko9Hu5CmuAHgEtuURmImzAROEP0pILYZqTdPoAOc9Q4HmQgXUe0aKXUja0+Smi91f6ZDFyso5E7tozKvlTGdU5f09Rzinsu4Y6IIPvsxFSAJGoatAYvJhi8K/OpGcZDs5Ys6L06q5P8iFZjv/O+RzTOb+zox7hUFMZf/J/qVjo8GX/7fIT5nYSC4t/p/igxrAwXAAAABDBFLLPPq4SAcuHgIHyNw/XS7fnOn+VR4p/7W/5+jvM5v7OjHxEYBiGVP+T/UVY6Og0v/23xQjeZ2FQXB7/To6RQAC7AwXTEFNRQAAAAP/6cAQ+0gABAiIsZW8MQAxDJYyd4YgBh/DZkaKEVXEPGzG0cIn2AKaacsrbRJXAbHa2tq/rytUrVQMnW5SPOVmu+7XfV7dyqhiOJKcjNTN5wrDBdkNUOuYtINSR2dHX3cW5ce/7dSXt4lqARaackjaRJXArCusVqrtvylqJs0xugoxfKzX0drvr+5VQxJVVun84VhguyGnodsWkGnho646Ovu4tdLgX9uo29vEtQBSdclsbUIC1CmV2RiC1BMVva9XiZWjgYM2YnzLPx/aG80YDRnLeJkahv4t9Lj+ekwreKXe0X2lSvw+hWB9f1AEpNV2RokkpAoSpSdo1osMAbUc05i4GrMcTeZZnf9x9ap/9Hq1v6bq6GceLXhVAmBm8UuFuJROPtEIFvdh8kqgD4r9SYgpqKZlxybj/+nIEfi8ADIIQNduTDBBgQIbLVWGCHgisT2xMJEGhGQvtXZYIMKpFYRhFliWdnoSW6GvRwfTQZILbqPI8Gu6U3t9mQ0w/+yuEdzPlo/38xnrN62T8bT3OzWm5bjovo78qItRFT302aABKCFzhJ3HoQanKzxx6p3B90IK0vf/oa766nbom9v6aP/ssE7m5aP83Ywp6ub10/G79CO7fkituY08JHtTyKd6lYVYZ+HCSCRMAIdgRdtyXw6EMKzEBkgsGONmO+DNXC+TwfoKkVxzDnFhMRBbWoaVMkE3ktdYYDTHtRaoVw6SZ9HsV0DPaRCRuOQVnDFiQMyMW2GBo/zCDu9pgGrQpUTYdqQN9b+QG5RJyXoQBS1wUEVOfJkQWfnRtIhTeulfDTPqYuDRI97Eey/X7CaYgpqKAAP/6cAR/bwAIAgY2WZMsKWA/YjsCaYZECK0Bd0YEpqEZrC5c8wg0Br/wJqGJvVD8IQ7s8OdljaaL3BibZmbARJ/KDBmoh8c/UCT6p2UXe1C6v9BXvZ6EZqaUq9m0jB3st5b+iOq/Z6EFWoxlsFT2EP2/kCvnOJz37APYIusgkEbRNLS5yuUOGeZIPFxz5RNLkOXdEjt+T3r4tyHX0uhzN++jJ/9rr8qBskj214SCVMES1/QYCVAFTwoMroLPdTMyIgvY0gd9V7Ef3bt9H/ZUUR5f9E6jAPp1eka5bvxoxx121KGSj3393RRre9aBDIu22XDALkxQSIvEPCFK3hKHWI2obdA3ye6r76MqO3JkO3QxEPbstnnu36IVWUELfMWpf/wbdv6tK321H/+v9xrE0jn10JiCmooAAAD/+nIEXTMAAAIdV93RBxK4Qwm8PQyioYik1XFFpEGhFCQvJGGItgIAsTltu3BUGXxHDDy3ZdsJhY+omJ/d+Xqe1KhE6m89tCqfUjpfSx68pdU+6/W4r6T+ipfJ+n/XVEqh7/+7/qEfd3dYYRJMcklkbkaspxdvPL2K/IN0kOCs4TakUBragj1H5+mGd+5SzkUz+9UNyf/c1R3/T03N+v/3Qy6VOdB+oUeaVFn/SAACRKctlxiGTekBOzauQCnd0zBHdkBD7MRy6HfY7/EoureFfze3y+8uYjLmJepXar7g95EmkIq+1uxKtr31aXFcM9kXrAATqnqvLBndAtTD+lG8YePtA/WpW0Kc6Tgnu6KnvybWhUfQr+//Rdv0TmzlJdSt61Lb/N/nmg3ngIJAsR3JcBQZzlHurTEFNP/6cATYGQAAAhQr3+jBFhxAaxwaCCKdiOCZYmeYroEcmyzM9hSgCCQQiTbjabj4qDRItaG8/o58SU0Qc3+et1paN1pkNPbREPl7NzKEksnU+vbWGyn4l/OMVYj1LFbIsTfZWrir1xm2kjhPm3JI5VKLIVXcha232eoTyiqB8Z2pC9vjPOhcxi+Cf0Zqkvyeicgkja3bzdq/p/pR01oz2/rXN+o9FzHnUzYQTllpuJBxZwuiwEU0op3CDgw0R3wM9Ae7LOGZ8wE6rQptShoqr0ZfJ2VvN4k+uLxs+LIY16qL5FkBNbXjP0JiqFu/ulTf1gtzX/mAxptnEs8akcn4Ro4iThFcrKgFo1wwM4l9md5hVSJKupOt6dlXKbO3uu7rZuu1UTH9hyVshN71MwyZAsksVW+y3rNoTEH/+nIEoaIADMIgElmZ4TuAQiMLEz0idAh4Y2JnrE6BDprtDMMJSgA3t/2JqS8pJAolM5t+Jh90dRJRvGqwqwxSggIE9CbRA6p24s98kes09YjOse5bK7z7fhQ5jXD20LJDTVowi7VGmP8XBSmt2OZ6ejCEEKJcZT60tFT0ZrBjMP8pJCbUvhcWJ3aHbs90UEOzVYW24sPh+Z0mCIvWu1RufsOTfV39Hat/7e/1gJy3bVXT6EYKHEEjQHGOOyAsx3ojJO6XqzAhYU8XDiLGAeWL8ELDZ6mWhRUIR55rUWvEwXgKcemlmuSbo/F3LrR0fi4KTcknHzMAcn6L61cIUDfYKSrLGgngxIa0r0VvmMG6k5gTU27XtMqVb7lTp93UQ+zlEIYTAI1dFNqetbObmKKEp1929MQU1FAAAP/6cATKgwAAAfIX3NCmFAxCptw9DCI1iECXfUGEVPEPh+wM8a3AAACgkpNJyPIoMohNhaNRw8morGK4zav3V9UQfKUOWsUjDrxPbYbBJ4SvFmvssorq9NHYTQ1RY06f90XO0urTDRSjbkttt8gpjouEpksM1kqemtuG6s/qT08Z2rR+hN1U/bSj/Su1Iu68HFH0Ai6bWsmx0W6EMcppH4/csA940zvTIhHqSjjlwMfpWFfBMNcWRmRBHk+jFjC3mC8/meNEHxmsK2jgz2Qqi2obuKt5aNMJLNfRqlwy2Ags5jovFESTvejSAG7v+1IlgL6YguBaulespsvzclL4GKr8ewWk7MIrccDKhi1YI5+SOO0lz6lu2EE31qfmCzfwSWgUfdaqYSuLkcx/770xBTUUzLjk3AAAAAD/+nIE9FgAAMIVQ9w4aSosQ2WLSj0iKoi014NCmEwxCpGsyMSJLgCApFJugzgyqyp0dt1WILtZPhZPN0luVBFCtkNrP6N4kv29y9S7f6oVX7/XZlq6r5acYfUwmEaXkqOvEouGrobUf0gABACY5JbeiVgNAghJ4RYJaPehMJtpLAY12hATJnbJbzP7crBhplcXKkhE6tZpmrYCtDZ5hEWiC7636v8ZjhjruzUY8oqpyW63crBjUcr6DWwC73TXBm8+hyFS+LZdeEdn6mLWGBVcqKjPMTkPys9u5M94OUZe0RByAXJFfek7BZq2FtnkQ1s/WAs11Ji3D8dggeJdGR4u72XCD05rzn082Sk0wb7o3q7Pq+joycrdw4SKpFZBqmoPcCRzA95dQSehK9+TgGGhQIfr5hMQU1FAAP/6cASuEwAMggoPV5njW4A/5GtSPMIpiIzzbGYkRbEFGO5oYwiOACm/4SDOncitNdQqk8NQiNlS14RR6B0DrAAWELnOySJrjV4RrIU0iiq5cChBode9liR5LWpzk3sabkQilH2V//tFqrIczt2tinowM+jRL/AQn0Bkt2fiHtke2ZuO+dCU7PnQMSSJCzx1TLNhwyCLnHmHAlLqahW+zHgPLvIOe+aHElJpwQe/UsDSvGVQKZv0JkP2onG/uCHeziOMy/UuqcjAvO3cBHLzjL0Gskmy/yd30fTr1ELzwVoWpSWvLQAMUGR1xl6EGJTccgLImSYEj1EtjkfDp2KNcjECk09r/VtUfCFBV79BAeiMRl6TTNuqE2dHEioHJFoxKMBpdEBtLlxbe1ZnFakxBTUUzLjk3AAAAAD/+nIED0oAAEIgKV04oRTcQGrLxxQij4jMt3B0kQAhFRbtipggBJBgpKOSGqhQ4H1DrTjNQgDdwoNlchi5dx/rMDxy+L2/S+ILqCqtlyRW2HGol2BhTxmoRGQTSnpcoLJFi4RIAdgEFjIEgGUm5JYrtFQo8RaycjfDPn5cSRc6645ea9izImf+xdfkV2hrytdGSjL7b/25HZ0XR3Z8p3ZKHRXUjORTML4skcVbIiUnsdPiKOnNtKUPiF3ICHpofx2o1PRWfa8lCeReRvr9X6SEAy8QBgQAPUMdQIBAA/xCHBWUJ9hB/paLOCwYKUX5QTher9BpZthUZKfGjbo7OUxl1nVIJAnC+Hba3yt/I7t6ejNJQ/zv0kIBrlDggM6gg6gEBAX/FQ4KyhPsIfJtU4LBgpRflC4XTEFNRf/6cATZygAAAhMi4AYYYABCRWw5wYwACNhJi7yBgCEFja8DnjAAbJez58qAYbObh1G5qJCu7kBATK35taqlgIgv1jPQYkL/0xBAzFnEA6I/MC6AEOB4DeoVQs+wchz/+6omdb/9n2CtYgACEEEEFBBBH5GHLUgMblsjvqQVw7kwplY+m1qkWogvyjPaMv/XEJTcvLJv/L8YVaGCJE98UDzhOqx367ko6//VVAkKk0UkkUlJ5CMmzmalVed72v73suRY7OOvj3Dpr5FSiobGUYGZ54GZ5Jw5IAAscePU/95Kk3U612LUCJoUYTQWCjhY28RZisIgatuzOTc0Vuy0rmHlY/A4uoHDAz0jMWpHu56Vm8dGZ6I1BIqGwBPxUDCrIrPCs9cygAMOPH/9FJvd8Wy1n6kxBTUUAAD/+nIESbUADvIDGV2BhhjQQKLrsDzDLgkQJ3QjPMAJFQeugBYMIL0B8hBYFziZ2qNrbIROEbL7JFKzzK0Z2PHyGYF0lHLA8Nic0k02TqWxtUOujUNSjHnxl/lNt7a3UxfWs+17aP1ZopwrlWlFBrqECMbaE1LRTf99gN6NuVpOCNB0GQ20o5YNqMpNJAjXE6itrlJIujZ9OpZ8Zevu2zeVdTXrWfb/6hyKP0morGQyap9/lGmnioSEx3n4lKJak7K4vn6Dml4Uykrt186h38tblra3Lbl/y/zg3e9a376Xc/o0juXrB7nt41f/+qb/vaJCs7WOwU5D559rQzSHA9zowYSOLjwk5IFmg+aebgMOkRwcDAw1HtYSOvGSQDVc8dL1nmJLnXvDbjSVNsiZ7dqadfzWlLExBTUUAP/6cAR/2QAK8iAgXKkhG7A+AwuAJEM4B+hhbCYYZQEIDK1A8wzYBIC9OekDLqUludfa1ps8FhZAweYt98pSGPxRL0+d4otdBBaaJgBolg6ugUCy3LSlkRHIiJvuWOb/m0JoIjWOV3ySyJWXI0gZLrEg9yB2wASzSw1bIsMflWtKkp1BnGBwNB8CLLBtYshC0iqFGAGiAq8KBZblpI5FkiJn8t/9PXq76C0t1QPnSsHr8l69pBcXw+EM94/DI/CkWbHKJEQdFVhwCyBYVcJb2rtvNm2IrNPTTSywCuaja91+t1Gx6PX9RKkitlGPZjaB60JFu1Bws/NFT/w/HJjtS555qwoBLCpB8QqEJEFRw86JVB8KAQ2TySzT3OSiXdFZVG+Yv7mIzzyu6xMQU1FMy45NwAAAAAAAAAD/+nIEtBIAAIIlDN5oJhCYQ4ULED0FNggUY2JHoE6BEYrsWPSU6CCwkJHHIkpaUMQfcrmC0jIu4cZkkyyhY+8zSWEbgZsVFGnty6zqlEbXtVZgEYHfOBIqpd4FkaEvGiIP6SpgSootO/dTXpXKdYCEJgn9aclgNpXEgJMXCg0Vd8VAxjQqxlbRUFhrHa7OtnT0MljGe121EQkHUhZdAhFFJuOli3stFPyS/9v5XuQALVdmOtUCGqVX6XU1FaufVqONhv1wpUohNLzHr6urwSOFHnaSVZUJkz7ocffKWvAQzMzZwwgbd+mzQ9PVjExkWJABVZjE2VISGg7UuKnD4pGYb0wI35OTWJotQVZCjks2NHBsMpOUxSVrApyvrbLUIMCjXAPACSpazZR13aXku5Vi5b31piCmooAAAP/6cAQPxgAMghM12JHpEXBEIwuNDSIVCJBjYkexBQEMjGyMwwnQRWqsx3aXBrGAdXBK5hYP9aZpq/6DyH/zKtNGNETr3WroKVH3o3r6Ipb3tq29CvpstDafj1HUmhAiuzT93zpb6kdFQAAAEJRbbTetQUEl1T78YwNsYO82euqjus/RuyCEAhhtJ91s8WCRRIB3JQxSVWqrvvZJUrlt8jiJn6RSlciKmemE3qjBFr+WZNIEUgmSgzDu9jFD1lDH5+lyQMC+XVI+15vcR/EfRNTa+2PFkjU6AMlb0xZNrb1qTqeHkQ32OYuwXoR9W4e/7JsFJNORlgilkAhgVCjarK0hqJkXnWN7cPy4rmoLsuCGzvBwblWII2NCOLu1KQ4RmEhSufsKS6teQu01TmA7q76LNY71piCmooD/+nAEZ1oAAIIZIlxQxRK4QiH7Aj2GNgiwk2BnpEXBCowtqJCN5ABAriVkcl8Oo0HVasylvBUrMFBsbmcRzu8/ua7r7zGajo9IWeWQTEg5RJK0j2OUOdGJsvN1kVvhQ8NSS7Z2urV39nUDNeUgIWlx8mELo0iUjQToTVNmx0hefhpLD1YVI1BStyDTwRMB44Gb1gQahx29GtK9KmsIHkMuN71iz3o87Yxf7OaAISbkgMprKI1xdXroqRqh9pVInEtVeDVXFuqkYIsxio2rGZzvI6r9r5ORHiitdDVRG110Vi8koYh60f67jq1jWJrV6x+gMAXE3Jqi0oTCMDsR7Czsf0cJDcM+lV8PNlBCPHrT1hl5WxCWakxtCAAgug0Djko0YVc4WfFD1ttUoSASU5Bt7OJB6YgpqKAA//pyBE+NAAgCIhha0SUTOENB+9oMInOIqD9kZgzOQRSErWiEmIwAAEmm3HJathC0IEUXeqTfK5K0XGEoMDN2RoZsiajPCp31MjHhA80ccckwsXE6jq3jrLLWJFpxh68VQhdcreP2bV1t+pAJtik20nJSpqxTJAf4i1O5cg0pUm8AnEkt1B80XFBgxYqukqBYmSibHOHMDQVPkbop/0NHhJTTxU4FBkWVWKVirLrgCo7t/ePZHwLll23omam/Qk4z5KDLPOUUGkK6p1w5jTtlhQHmCYJC8JVtSjtF2vLPVyRh7pOhiqmG7nAL96XhJvRVa1pYAAMWkpHLoZWFAcRjomkuzV/pDDKEFnCwuFijCTjpQVqr46WHMIBEkb0Xx5YgtpSZpD+GnDXLffatMwbQQcYQwTP7ftscmIL/+nAEUzYADMIOD9kZJROQQoSbIzzFRQik4WZGJEixCJJsjPMJIAFJbv7NCuAZEYtgZtsiBw1+DJEmZWha4AJl6uWPsGyKiZ8ULCpeZuxR1FAjcxDUG1PyKXrLMaUR3EbLWX98u/Z/oQLTKly5IXhGncbqtKGLECFzwg13ugvC9UexgOy7LtczUZrum+6JuiuRBrrkmiCW8y5ne53120dUWKmH2tUPS5//+kBEX270AzC29CB9KgdD0bM8SuTkfgWp3+dn5n1J301o+qo9ls3BTLvld6rsz71j75Cb3Ag9LkCy07NcXS1IADhIwpUXVagnNt/Le0Ixz/JC/IBOjoFG8BD8Zss50Hqg11Ddeuju+/+9N/MwNVxRMAh5CiLQVbaG6SVgqMZLNWrd6/vS+GEfVvTEFNRQAAAA//pyBLC0AAACBRhYGeYqUEMCS1YFggeIfOGDoQRXMRsH7ahkmI4hJ3bjOkATUuCMS2DDoikKGexyv+HPrBWd0IV4I23G5486BXVXqTCyybmUYobFdEkfh7YNBFzY2dmqvfuNqa3XeAEGJrNKCysGlOfiHY9BsBFd1aC1Qa8bOPaWB6apHmXyNpKjuHCps4hZB0BSVD1jUPcAVh81eISobMPpGPfbN7n3O0oiFIxtqWWW+16ECNdraOjKIUarVQq4P+fCXfxbtZ1RELvk2VEZVNQ3drr2XHdc4k1hEM1vFq6hzmWtZ1QmFVhj2lm5sAEGgEiilHQtMMCIjY5HYdW8PW586dE1NzlS0tXBZEOC7mk2ueIXu387FltW6+xrUOqBQhMizyzxY8u0iP0PrnrCuJNlfSTTEFNRQAD/+nAEk/kAAAIVK9gZiRF0Q6R60z0idAg1EYehnKwxDhHuaGOJ1gCk25OREcgBGI4coTPkbs9iQBTmbVqhDlf7ON83DtqTEnZGLzb9nutTqyL16M0wNRpgWP2IyErW7W6zv5Wt11NCn1gBuS0QFkhx2CZgohFEuCmZnFvKtNMDeeI/fcZUJJjMbiWTrDdWXJwtGVt7U5CPdRauNPyGnCD1vTiCVkq91iUz37v6WRk5JJHNdNvL3rj3JKNoHrzDAX1MD2UzZercj68TIzzdHTNKYy/1ZUV6Xprr09enRvd0ml9RkSMIkrkOmT78gX8sACUzKaacrPBZx5nA+7F4+MFdZtdHDxzIQYlfaeR8L5LTZUfUJv65B7LApP7VvFnTjD/PVRlecNElCOomFGiUYaqZtSmIKaigAAAA//pyBD7WAAACAERaPSzgBEDkevOniAAJKXeVuAOAERYu8GsEUAAA4BbktEOOmAWWM4MLtNOuchKywUH3vQ8bc3p79G/Y289q3z11Vv5laPq7Zq1Te9VvOm3rnGVPZ587ldie7kPrAEc34XoyNNgCyQFXNA+IsZfMNwbniGsrXKpqvIoRcPyt30N1J1XPZFdcKqDXVKV52ZQJmjniJywlAcmXS5cZ/igAAAAAAgEAgFAgFAoFH/r/o7Gf9HU1v/iYbEgkQV//xoaJQ6RHxz//xEGSzj80aF///8XMazAsJC8qNf///+XJGuYDxTjRuVQaAAAAAGBgUBgMCAUXiJ7u62////R2T/pUrUT/GCwsCIJ//xAoeESnENb2/5HauR/f//i1mYOGHqX////uLFcgcmKHxqCHWmIKaij/+nAEYwAAAAIeK9/vDEAARKV7ueKIAAhs2XMkhE/BChsuZJCJkKQVEbFqyWinTlBMuv2l5HyJ/bIqaHVVPvykZf/No66f8xTkChUNCw4Si6nxMq43YQ2f7mWhgnsxoZYeAokXPxoLCpGuBAgvgUFTucIGKo3ZWyv9vydE0rU93pKRl928UtHLo9PzFOwUKhoWHCVanxMo043YQ2fj2HWWhgnsxpFjAKJFz9YLMI1oQJwcAjXkHBkPodhnlbvW45mO+V8uT0ayErA9fZgKxQrX+bGmdvy0M7JiUeOWGrDzov+1ThERIzXiolwED+ZUlYtKowBW75BGTxzAE2r4Z203ZYOZDfSL4b3tK2hmu11b7qES5qtt/pjTP/LmduJR6lhqw86L/tU4RESN/ipXUD+ZUlYtWhMQU1FA//pyBEICAAgCAUBawekpQEOoC1I8JWoIEK9gDCRLQRMmbXQmCCgAApHn9OzkOTon1LcIU3ebyNaQvRqDn18o/W6esvrzKg1D0p68rdWbkT7f4/0f9HL8b2ItUvnuiKholhVRHt13M0q2aQXYzqS7br9kzN6HyqSki48kym/1UvS2LKg1D3o/LsVpq2d0Ib3Zzfg3oa3VHL8bZokiy+e6Kw0SwqoOtxKtK7tTsDRVCgRGh6jgm9aa8uHKssHvlSGWOk7lg4e/iKMxQQd24IQTQn1e606B8zVFevhnUHO5OT/1pZt0/Pf//MeHAAQAYAG21HHGV8HiSl720t6imoBu5C0GnGL24Ic9WoROp/XMrJv6dM3vR6Gv/R00Biq9N3s37p9f3Rv3cM679P/rXyIcTEFNRTMuOTcAAAD/+nAEgocACAICQNtJgynIQua7jwyiMwhY12+npEVBDJBrQYYVmAAAwRma82kbITFh4A8TRrjCXOnHbRMEbQDtow7xgr6H9D+j8reraP859V9rZfUN/r0XouN2s8c1M771p5F3ycWAQIAAoIErYmZYsUBDoZcEUbcPhUBCNqk6AmwTv5grlOpD370uUmYpFUqOumnv//L4N62lH/b9czLQmn4n4o6/JJeKAUAFF7bbG4skYmZiS3sfXz02EzFK3auDEP1638IKfp9H9OjN2t9nUhSulpqqqJ91EYTAtRJtBn96zCCgl/RuFvs21UFtjrpKIkhXm5KuVxo7R3tyvYGmdnigdakPbEFJHwm2PKAGKUGH4xurObnF5lxxy7owpFKaL0iTcr6DOjW36fivf+lMQU1FMy45NwAA//pyBOY7AAACHDXfYMIpvEJmu2oZJUEIpNdxpYSw4QmKbiiBDdxkoAmM1d1fHa7kQqFxAPqAIyAh0k1I/bk59C+h9ITdtBqcS9kXv5hLuTqqI9Msg5z3AR8CpFp5PqXLYaTtzNss9V9ZkAAZCVI03OlZGiAfI3QYCLZAK+wUPs+j6F5YntQvkbTfyEbIrKdkLd6+iczJzv/jt6zLz8NTzfk2twwR7bjhaWf7FQAgAQBB2SSZMvdagUGDHeCejfmggT959KWMm8L3foLvtfPKDdEs5EN9+9Hzk9upe7cYr7FRUzFUs2+5j5hOS/GUq1tk64MAhLOkrnk85zAMGZ14fk35T3qCemrvonToJCU5rJrQLnViNF773iVT35IiMkXsCrnixzT/hyuYZ+f1Be7WCJAieSmIKaigAAD/+nAEhUcAAIIVLFxRIhvoQYKLRzEiRQjcl2rnsKUhCxstKMeI2EBMhacmkuvGMJWj2T/AhR+dIfV5jNXZH5tUjWzF87er+uxuZcYi+aRzZGGUJ1WmRVSjzfSjY76ESrVPHVu2OrDeR1gQBTTTl3XDg9HIoXGmKIACDf4hLmMPPq/m4QrziicSHSk3GZw0KKvShBEWTiqWVv265Zh7PcQLVJkWe7ohvTlR1QASHHJJc0qzH2NNmI63wNpu+YwiZoqIZMJr1P1j/Mr03afb3N2e+IO7Am4++XQGTEuFqBE5JiJHklIY1UYcnpH9LHw6u9+SMAEzbb/XHfNxrU4UF4xzrqb2fqTso9noON162Xzv070N3X7MxXUsq+a8zv6bSOnUT5LrIju/ZBWpQ9+hjGm2Fnn/oTEFNRQA//pyBNLCAACCCC3aGYkSwEIDu3kJIhuI1PuHQZRK8ROV7XSAjxQBS7baYlS0wHUd1o50iAZNj8qg+dsY3rZy7bHEvmtXQ777a/UKzYM7IltszVVE2GsHkxVND9Wnt27xZotp/vyf7QABkBlW5BeRDvBOsINd/MhWh1HgsE7dunXu7bfBMoqU45V3GDSXetzBAbmhV++xGnXUueQTOrOuENSWSdT6g1a+GPJv/blltuiofH08DGtlLyBTxuUdvfWer1Hb/rnz2SCd3Sj/1cFuhXTu6iHypIZ1Rrbcz1DpmUgCee01yyNkYsyqKmKe2+lgEASUmluhWUNA4zgiuD4lHfQRJWMG1v8NVfV/dXQ25rxPO0Tw5IpBoTLzt2HCR0pJuS9xcVb6a2a+h9uVTpcp2hZjfv60xBTUUAD/+nAElJYAAAIdNmBQQRy8QWL7WiQicIiA2WJnrEsBEB5v6DKI7pZKpKdlkmR2QXU75AfUnUM9cmuXM3Pz8fdIUwdLmEymUPnkpC9gToYRbl7GpXfwZ+uOOkwvq9VthPVVvSffpqbF1OCQAAQEJOSSVbmhOaZ4Bt0odz9M8SJA8K90L34PrnGPyFzdUUcOe89PiJx8DKl2hpR8SCzBAAiO9b0fVTN2MZyF3+sgxX//uTmk09Jh+hMVxFfMT/JKH2/dJdqL4oD8bmYgJudFLqxOhOdWZLOTn+j/alR9PyVd63UMXapCcpH/K3aVrq09iJIR+bdkkmz1gN6uq+z8LFKPW/XsLSpLH8nOcEnlkNdXu8VX6ZGZl+Wz1zqMVHRN1bcEPWCZDZWWa9WRZ+aqnK3POvfoTEFNRQAA//pyBAmtAACCID3asYcS7ELkavM9ImYITP9iZ6xM0QsV7GmEiLqBgCzXr70AQXz0LLhIVeqD+jKXo8LMS5FtHXVFv0fmW3La5R9WQV/KQgeZ1bkZ7Uo9f+hKKCS8OpeR4QNaxC31D0vs6SBHZsMtKXbgPIzRr5LL5VRsz45wN5/A6O9Z8SrZrOR9B2y3d+C5Zxu5mY1hc4QTIOnxUowoAkb3+hA7Hjft2paOX9gBKbkkumhgEL03ZTObDDX3fpYL1vc133lRGW5GzuuayH6P4J/p0FrmZvsbcHWpeZno/N/v1u4dthScT4F7Ka90f2MCQAJtuS7b5MJ9lUhE2K/o6B7/6D096wy7rb6Px4ZtH6VZejc8amVG3ZSI9ZGo3hYsEo95FBT32oJTP+c5Out172UMTEFNRTMuOTf/+nAEMucAAAIfUd1QwRPMQCc73SAinYiZD29FmEUxGSHu6BSUTlAA0JTTbleJPBqj5mCH38uCRo340+1qDPE37+jV7PcEj0RX30PqXRTdWdqSV28pmolkB+RNOv60Pa2gJdX0GryCEo/QgCQXUUnJJJZqpKYzX7G37A+vUDTPyb4V6zhCfJaIlPCR8jUyFb5rCni5hatc6lm8hRVb5I8YpQt8ZPch7rrLKAEAkBSSTc6s4GpXKAj6Dl7/Q09FUfwreVtxlZAWZ3/qjd1pgy6VHycvRu3X7rSr943e5T7Hpvdwr59b3Ml4o9R1b6JvqAgXpGRtyWpCU6exMo3qLNnIC10cE1EfbpMxx60BXWph1auVHQqqyrbbX47UvXb5JaPSz66jZiOzzrxN/bZG1PUd3sQTWmpMQU1F//pwBM/BAAACHCNYVTzgAD+nKzenlACI/HF5WJKAERYCL6sMMAMAAIAC5d+JG20YEWSk09R3Ocui/i2WQvY0OqIRs05RHIi7cbtoWP11R+2mbXOtUqQsPoO2TUjRL7uIbEEL/bTR7++2gBbCTclGKxpBLsrnqFe6Oa4nuxuPhQKo0fRAOeVhft1qOGP2f26rbQuqxSnpztzdNlaqNeudB3ENftp5P89bDAAACKrMcklt12GAEyDv4xO5q1F0LWfaldDGcRoTOzcxtbQGs5Sdx5UWSLizEqIMJQ9re0eI3PC72PclSIoISJswRH2P+8NXGwEAGav5Jbdbtv+AOQY/lWBvaZPKO69lo8uxYrAQDE34ye3+TMs2EoBuUZ0d1Rv7/tz9N2WQ75NSQgnbm9v7Lv+3ytE/ZXTEEP/6cgQ+5AAAAhIyYLYkQABERlwCxggACJxrm7wRgDEQjW5nniAAAACAooooo/ogXCWQVMJP9hiCf1MG1q2/6oqf+9tu3Iroc2R7WOBn7qIDFQ2ncWfwwm8IHKM/gg7nBfo/dxVpSpv7gJKKKKJrhwnHbqQqYH+lJEKfopgztrN/Wip+fjp/boro5kke1gMDPs6iAxQw+ncWfBAy9JQQbpfKBjlFt7vo6heciv6CHHXNLbY2knMEg2MjMwuzNgM4Vz6ZF/zIytiEUh0MMd1RVwCNQUZw2MDRChmeiPeskxOQZ0VhQWacaXQzatbpIFRL9bFAAANQAARxdQHBRNjBizfJmkNcFNGh5Yy0yoq7eit1M3uUT6oq4BGoaZw2MDRDZL4j3raxOQZ9YUFmnGl2dtbpINEvrqTEFNRQ//pwBIZnAAACEiDg6YEQmEJlfH0YInmIrNdzR6REwRMbLQWBlZAAAQJ75utpKsBepiaZj5jAtkwUEyIkEddX+230qz6J3ZhwifnkacE0DORFDzSiS3UP4p8iOrw7f1RbCp82WfWy0OPBbKMlsaiZJbAaORnrf90rXTjEyBBMqgv6165upvdmHIp+bb7sVJBZIYXi5FJ6USW6h/FPkX14dv6othU+8s+tlp54AAhNBtSOHsDAY82MyBdj4mgC7kh8PTrSkqtVla8vxpfs3qJqurbL0f76J9TUBXagV0S7Q1rNM4UPYq2LdW07liS3ap2SS9mQMk6cYh2zYhtqe7r0yglVKgYkaL8Fizxl9VrVmeXeIisM+32LVdW2XdH9HsifKmS+gj3aOTd0ltTXLFexSxLx9HYhMQU1FP/6cgQlRgAAAf81WhMpEzBDA0uKYeIKCPTXk6SETnEKD3L0NIlOQ7v4aBar6RuWtvFKjFM8KCTMtn91b8Y+vbuFEFRqYMysyO6PQmLk63bpR9vxHt1Ka1vr3bgmf2EgLr+jR/9H0gAEAEvqr9TiWVZGcJYze61qSYaSAKgnbMnIz1pVz0f9Xjm5NBMGjbz40XocOIlX0+6p/9krr6kGpk049m1JV0UdllYgtzqkjltluXdi/snKJzb+kXtAO5za4ZtIf7v0PVKTc7V/R/oFRi5dZNF9y2hfQX1vnoFEAcY6VdHEGEZSkpYJQPw66vWDMWFjTek01+0lwMjCOh1PuSFmsP5914oTpyDsQioueDJamhmcG4ARUYwoOOyDAccRl0nxVkj1dSIc+uNkakfX2O9YYaxMQU1FAAAA//pwBHqWAAACETZbmeUb8D/mu8kxIg2I3ItxRhROYRGGbYzyiYAkSa3YDVL/WDEQxVYVf9sHvXFFXcKqQS4xn7OjbPohtbsuhWekZ0N7Gf+dW6nIfJIyan/iu5jFJ4138iuKO/dV6PWIgaKnSuBUlokqSqBL+lYPjrRPuEq6crZ4Oal+njXtsqm7h9VzqztOj1tQPV03vtFGa0ein3oJfz3o7or4eWvoEAAkIORyUpAtVeiFVIT7sOXCZHkJ1jZWAVczNS9Bl2K1F2hWThm2Q7tcOEjwfmDrOykIiAUQxFlVRm1dHUWdK41GjM8se9RQeu2x7EiitVbRWxK+0GODvVMgEwsGwOWYhQ8AGCii6jrfhnASxzzBUgpljmlECRFRhcHWHmJamzXkz4o24l+cxEe+mlMQU1FAAP/6cgQx+wAAQhg2XFGIEwg/QptCMwVQiOjPdySIb/EYma3cl5VKAABZAOR25gSYoIg7ihc/SV4qc2BHBTwmyMs5TOEajbZs86e43kdbJXa1G31cHp/dkMRuNvordayQqlJAqvf3al/R6wKr8uAZNS8rFZyQD7/J1GVDr/IQBVcAo1hDjb3pJxE3LKeOyETkgsWddYo1Np22EDFH7XgiyR6/jpP9/P+pukQAZRO6ud1CxC3jP5C0Xj0C71s5+Rteg2c7t9JPVhPc6u1vMuq3r8Ih9LZtwEMY4RSSmCQrrYHQtPa6wHepjzaH2214Kv9ZFAEtuSRGKoqHxlNYT5QZ6dGvpzWsFay8/Xo1Ge+tEsw/UY1R9PqzzdW18tN/UYdx6Vn7CIvEU8RIqfa5pQ2uki9OjRVDf0piCmoo//pwBJO4AAACFhVaGewRQEAjq0M84nQI0M2HooTY8RgVryg2FUoERS79hb3sKQTwjMN74TFgAscloZMhdxPFiXo4O41xnSx5UsyVqAxqyhOqKEacYEgcfqd7WsV2SSGVmZ5ei176h344EWK/9OtThZ2wXjpuvOJlBkA8qvHlg3Rk4pL4SsO2GM/ejX7KJeJDpibSiUqq+qDG67ohFhWyzekdLRRf17r/1pmIIttN2SS5GFUUSHGH5WCW3NOwzibZ91K8W33IUifDmghZnETk79jzPlykRYxoEyxVi73Ph2+fhxZEsSiEqrqsAu+3qfGSAC4G5bbsGFE48J28TIWKuddxSlV75+ovjLtWwkB44heV6tT1We410O2esTJighVESAkNuKbuO75+ebaxO6vc52V0YmetyYgpoP/6cgS6GwAAAgs1W5HnEzxAhOw9BMJZiLxneUMI7LEeGu6otIqGAlW+26s7a3V336icyjygl82rG8K8AdHJaTevF6PqjVa/qayotX3J1/9lJUW2DqMp96ut9yzK1OPuDu9VtqiVpj0pEwhSUxWRy40A2OYXFwLOteMf22X1iI5dug5Z7Mvj3TyD1pqlX0rUdNfpjYnrpd96q/OsclTj5sU02vdiVvo3WasBQS2mnKwjvDzObyNBlybAsZ+r4jMko23qRlQUUROW1fE740Xe5xoE07lMLGJj9DIpTbIPFUJKlh0m4jmGuirEPdVtIlkABaSKSSc22rlJ6TvmrhSGiTiHUbF3fBKvoATo8mNk61gzSI2rvX+d7SKykt8n8nM6jgHYedXfkbLbbFUNcpJL0AENIrSh+pMQU1FA//pwBGbTAAACDz3YuTg6IETGfAoE4l2IARV3QKCjUQKZrA2DldgQAARW3CWvaIzuUCMbTTm8EZqmnRhE4IaBkWLg9BLjDxQW1dKWoEt1XUzXS+31r7f3/sWcoV0vnPU6luXzTSPP1daiBftOuOXE2cAktseMSItzWKC8zkGqBEist06KKqtVfZTwr6q8v1ZS7DUSd79tEwQt+es1vnyLpG/ZBpq25/Y/nLcXd0gOAIEY7d/TSI8MmaoP1nLmyF6PjfDP/j9k3NjGlJ0/ZfJsVlXo2lmYXfft/8U5Ofrx0k+JJoDVKU625So661VQIRkoEqid94X0ITPY+kHS7FnDNSGJEXJ5pfcXOgFjeJehFzIgIju2vUHmZtLxB6E6f1bstK1pTpqw96ZT5SiuuhMQU1FMy45NwAAAAP/6cgQ7tAAAwhpT4NChFjxDBvsDYOVoCMFPe0METLD8me0M8xWCYoT4pxyTddGShszij2eorVh/CnbfQd+kEwYg5mrRkXJd5+svzLk2QKBak/+nP1fT30tTSy0edSMqdKUEtQ2RLy9ausANO7CpG5fVW6sOs1sDw8l8ih8gW/KiARppiRklqVvDTj0RgzdtLRMGiKF1O8xFZOnvdX06f06cRM+nG+yft+yzs/oFoDUktNy525knudt+eKyYoEqLgm2fUbrrXfX3eD/nb9X7f278xpxInXv0/lfZjuZubONozMZ7oatn37aB9jgiSMls1ah7wSknLWpk8konLWmGmDGYFsabPg1HFZAfy8E4THJelbQsUuN8711to3fV//+/M9xXb/M3SF9FiLMr0RHTsRV9CYgpqKZlxybg//pwBDtUAAACCTLd6MEsrECGewNhJXYICM9u1JKAMSIZsHaMcAYgAACEgFpty5HZM3WT/qxedgw9Q9JlT9p28ll9MEqr4kr1VVVv1dWVOtp12/lnGEJFZq3keSk/1qjaqYE60qdq94IStoFakxuS51G/gomNXsNzh8cGnncpnP9G6LjzNfm8nwDO+FXSSc5UxoYxSvpeua2nulffv27Uq6oJNZJf9FNYDRdWhKMKJg9JIJu5DRjGEAjY5mQotw4OMgFBxOpXUr6D1oAZzIeqDwZxqsicQP2Tb//+nGtPf0vysnTbRH1v1mBBlRRN5yW98uu2uvuhED7tj1HGW/IzoVVal9Xvdqmz75RiZ4rPVTD0ovo9u//35h5sVuLV/UO1NW+9bLkt9DexIkMokm4hpWmIKaimZccm4P/6cgRJegAAAhIIXr4YYARDgRvaxIwAiKyxejzBAAERhW8HmCAAAAHJLcslr2gFc7YY8E0pITllBuIz484AyAwsReWCCzsUrw5URyIqKpWcUi6sCojzHJDri/pDVmLfSXej//pehE2AwAAAYFkk3JJa7oBar1fQWUXVsYs7jJ4XhwPOIKew1C4TNEXtiYkhT33iw82HsshkTmXE2jOnY/3+VQ73m7v/+budNH//lIBAdFpbUWdho4rPf7XGYUVEVnl2TdHuyKz3szGO3vsRUnrMW3VDUqUELuBaeDZ425Tnrn27526/Km2SPtTb1BL5qnTpdCoAADQxH8iNGsLlFZIjm9LCgkIzIFeAkPeaFTKUGC+mABYutN2eYVEaF0lSyTUgm0BiumVo+dHRZ/XeK+IWNdpdfvTEFNRQ//pwBLNlAACCHhbmaGEcnEPiG5U9IhoIcNl3J4RxwQKa7mWEiDAFpRy6uyNpJ8fOMpY55H0Y2BkjPyrrKPtmQeXc2megq4SBIVc4NYWHpYVqaXpBMrFRtohwm341lhh4OEk26jutN2tGbAFp7kBMBzxGkz7IZ5O/QcY1WLmE7D9OFEymKYNkHaXHoKuFmsc4lhYeli+XpLlYqXtEOEz7vGssMPBwkm3Ud1pu1ozYAD8Lq7asCTMU7gxSPGjxNdip4TLvN9lHz9B+SzU0dHZP7EPLh0XrzR1/EZWw9QTHjqg6LNZr/aq2JRrvCopqByLntRDAqQz+BUQWFQ4i5Rm0Va0HNNPzVUel+Clomhfx2kPo/o5jtRsuye3q238z1KmUEFMRDxVXb9di6zDfULMxGciZMQU1FAAAAP/6cgTh4gAIAgg128sJKGA+xszNDCKriNzXeyeYoXEYIC+k9Ag2AAAYCn/HFDVVEAs86ARNY74g/fLG8QoU2RnaFxKjadW767W+rVK1H23/20T80y87B3I8DPqcjWdCp3JWddhL/50SNJ/fuSySSwkQm4eXgxgYkV0H0BDPoIpz5Fy/A88NfqYVv//WRfIU3nYBap7JYGqiF2JSxVe5XklLO+r5XjkbJ6Vw+EqTzMLhPr8hG1H56KfOd2iEtw4ezrI1Q3rnoauq6p+ypdGmdml6aobS6OMIJrl/c+99LLEXtDtVy+8WbTaLAspMsAAdZPTWKYlRerWcfn9BmkGkYV5WrPyWoR7BcnAya5vXUt1R7bVTkY1Gl3v+ngqm//psw60pmhxp7nGRpZ7v8VteVh5TH3piCmooAAAA//pwBBs7AAACIUDeSWYobELoHJ0UIp2IYWFuZ6ShQRIscbQjFVYEApwdaYHQR2Jo2xzx+djnmWFHQ5shnSPZ41O+moc0bMqPV7L3f+3bsfr9LIrvUJBa5nRb3lZrrKIvOmdb2ozf6up/9AbTS0tUdlsuMzsPVVMqYW5gMNxlWDOXaLJyfWAP5vN/k/6+qN/89XZYY6qrrUzMf/hX+tgNSMeT1NIibiJ5XiVDdJAF67bYMVRtAs9eIXHqEsmwf8oZizXHatqvmbHdKAV8zXYiyUilZjU9RXtxC6obKs0lfMDV6ze39H//pVl9X+n9f1OPDJROrhdsskoOop87W6WtBzu4dUjcvWu+huKsQDgagsSxD2I+O5HtbVu3UZY6TtW2q9x/pr06vuMf/fXXT9X/Tp/nH/kkxBTUUP/6cgSCmwAIwhkfWpnpEOA/ZBtDPMJiCLTXdyMIrfEYGuzM8wmICFl22eD82bh9UaAckWBtl4BPVGemd1QR9DPiXEWO0wHu1B3ytQ6YfDjHrElzwYjeSc6pa0NtfadQhn7K9JD6K9Mj9AAjltrAKRlwjPlIq0rgx3qqJ4KrMGdxboEA9F4zkdn1Pu2c+XMd+8ouk0uVqfbSE8zVCwTRqtZVR8PHOfV+3vEJLarMS52mAThnq+vJFwDvqG0M4HDvmNkaY7WAiRLIeIaZbyv2096qUZp7U2c+4mCITHuKJECyjNjXCl7Do5DdPvo+tk0AE7bsYBY1Wz6glltUwT8U6oB3FM5xb8IWgR87vcG56DtcFo2AJvlbk6vlTTp1eisVCtferPd+4nVxTR+9vYFmeSs0fmUUJiCmooAA//pwBPWKAAAB8BzbmYIrRELDK2ox5VIJFH15QxTp8R8a8Shgib4ApOSSNCdLiM6YoMcNYnRpuhDYtkYfp0hIT2zXUN67jFplEKq3XrAzr+BzceROKIbzEKhptvdipVDdX4u77AAACCCm//V1jSPrBqccoWXXRUWxlNsgeZJth1Yk+FD2kG5+LlnZ6yyyQkHpWTPHpN6jYPSx8o5DbVSxL8hVaGpv/khAArKTTScLZ2Dn8xTHsR77jXQRfINZ5WC4wY21yLUJOUL55ZJVpGyKxDLFAkSGG2uZXKt0dOv9BOJXCziE41TQqZiIC28QDlUN+N+2pLbc/3roCGKrfhWRgxynsfCwsmpF+B5c1msunRcE3NRW20XolEClRQoqfGkh55Y4kaDmGqywjENA8kqUG6mZswO3TkgmAP/6cARoDQAAAh1MXrhlE2xCyYt3JCO8iKk3dUCYQXEJn+9oMI5+imEmm07AhsOrhLRUUfo+mr9Oeag5ogtxJ6AKmmdRtRu+nvoPoJ5q/13M5Owq6t5d7/b+3/qSsEYYj7qVkEplVlnx7kAEABTktxakj51ccM2gqaIOWoJd7try/v/+ut0qlyePlnXX9998OpL74XZSm5H6cn/X9dz+QZCYEaw29jlua/CFPI0AAAwCEkk5LTQ4E5Sboat9lHwdApNF5XLBiWxWr0fpog3G/nvEaaIP7af5snMdmjb+/fq9S9f9tyNcQ7gzjwD0XmdFllliBAcghJuO0mo6h0aLHBjZORmcf5Hyn+eT/LKuv764OKfy5zK5AWQ5mrYO9a3qHNCrzkJmjzgm4sYFzl2KeqqecyyZTEFNRQD/+nIE7kMACIIaP9q5hRO0Q4fLejCicohpY3ThhE8xAyctTBeIMgAgBJuS46wJZaOooxljLp8XOxDgxLLxMjrCuIrqNq+f8ZCprovfn6cLrTR/yv/8v/StYA29O91bl51b+zEx9u1db2pAAAkANuW2G8SGqSDXqDKOGMT3cbNskwmGZyPgmJV9U1fTo9SFtPmXvx9E4Pp/6X08V23/wtoRGp8UvJVtWsnXv1/bqGASkU7PAUDoDFYpxBluNnI+Q+JPfHm7KKfd9R+Tpqq0Evh2qj4T3/9P9/6e+GTX//1yoqIKZ2KdafyXQGLpoYInSRIAFtq0KmX4jEXVR2q8rEKuP37JYPxunMMeo+oHrzj9+P06dnWAvg/fQmn9v9v7dP6f//ujudTKQwEZQfVRVFX/oTEFNRTMuOTcAP/6cATuXAAMAhxO25klE/RCScu6BMIEiEkBZGew5REEn+2cxIiiAKbluSuIjCBWZWFsQZK/kgtjKK2ojxtqPza8MalsaKaF5f0AbVXqnTp/9Oj8R/VCw00cnfb6/m2LxqT3beKKafx3ePQAC4EpO7cm7hj5HiJbBR5BLwVgQhaA3w/i+n7E/VOXr/T+n//+fRe39UoGpDN+nStXe5tBkxkplq5aKoM6lvWkKGACEk5IfocgeD8My6G0BZeVjR2Lk6uIWQ9tB50kS5OUO0XUgxQLtcq3I6f5vGup2h///+/fRv/+O3/6Zoex7XEuimgAYACdluPdQYlUaCQRk/6QC/kfuFwID0BPiXSCF6Pq2cuvt3/t15uP/Tr7piqxSOEaiyKpuXhXJ+uiJgGoyv/PziYgpqKZlxybgAD/+nIEdoIAACIiWt9owxQcQ8gLNzzidogpB3dAmECxA5vsjPQJokgQAImSW23MBovAZp6vv289LROlKm/0OchpL+TTu+uRBno//xXJoI6df99H0fBdevvx0ycv/0fBtnbm1/+rpQaLJudjQRgAJOSrhR5XBWl0GUhzDmJfns05duWI1TwmePruIz5wtpm7bf7d/5+bQbQT///T306/0Px404p1vPW3vbb8nISNHQsAACgQk03TXFDXw4bVgryis1ThtQjpZjyK2ubq+uUP0f/76F1FtQiad/52zc6bdfXOfD5GyiQqeLwNTT1B4Vc6mptyN0a5sGqXYNhSILOYLgLpGUTGcvUuZ9FV2DtzkHbEaM0i69ff+/fBLq70GbRd+vP0Jh91stbJU/ENsqktIy/1piCmopmXHJuAAP/6cATX8wAAAiUYYFBFFAxCoxu5JGVliCQTf6CIYAERhG/0IZhYQJC8ltxptMjLQPoyatUPWC5BFIuSde+rQYdhmdss6orEsqPLnE4HLORuyJxb9GUBAEDkPhiouFwfHh90QAgEDgIOnPy6ABUlLw049LFzjn+qwDnQLer2CejczknF2qD1VLgc8Soq/qes4ppMHL7T57XuUBLfxAGAxLjvE7784GBAUDESSHz7iKBCAAACkVKShiCSD2GrigYSoHxACBfggXBAMfxAsCBEPcHwQcT7Kg/Uc/2g5/LzYnD5SUyhztc8EAGUDFqaCjnIoVmgAAFIuVcEQWDG/OcMhZwoGEqB8QBAvwAAwQIdPBBYEOASmD4IOJ0WVB/Of3CcHP4npE4fEEpqOdrnggAygYt0FLkxBTUUAAD/+nIEp9oAAAIWJGNoIRt4REEMBgRgCghcU3YAvMABD4xztBGJjoABykiiUSkoDBA0PHDuQqCXSoAoD+UyQGYduXyhHtyCp6CtATFH2OYIH1nHn3BNwTteSvXfKpOyKf0q4AD6eAzSdb4QIIVQMCQAAxAGe5hRJ4RFQkeArgAFQTNVDxCCRKHVPQLLPloivgrQCoo9rHMKP3PDx0JuCfReuiVS2lP1pVfcbT3U60NsB+wEVIUSa6MW/Sdy2xI/KJEh16tzcpuyJQSrADg8zA8gMJEgKMc1OUcBmoAleazrC6yyqb1v1rte0VY31XUJv9EDajtjbkbSTTpUG5zRuwmqkMuk26rfYslkdRAYJBXB9xwYSQBRncoYwDNQBJe5G1i1iJVL3i+8Vta+xjUPqVbMJ78lsTEFNRQAAP/6cARSWgAA4hMIXilmKLA/IjuwMMMmCQCBeSSMSUETiS5EZIxgCQDsIC4j0eaB6Xl4LOpDJVwIhUVhJpMOjRMg2FUjqxVoaKAdjEMF7osAh7t+NeWRYthJUmvMuVbzHQ6tlDM9blVTNrwC2AdwVNYHVqFv04ceqfMw7G6hQiNe9YcDIGSbFYkCZVYZeqoqMQqg236E3uUlVjF9BqyvQ5lJiwWZ+vbYhAAigJWvekGliq64Q65EL7fF8Nw40qku8m7tXaymcurUdmYZbFnXJhTbFx7uwfGQwhFEqUYeQ55L2FUY0kmoi1lbI+kyskE40L0kADQfLkZ5zouQtxa2XinBrvSd8GZwWAxdTgVDoLIkUGFiiQ25McbYXQ0e5GKj0OcGELciVUx5K8l9HGtT/10lpIJxqYgpqKD/+nIEFoMADgIIGFwITChgQ+IbzQwiZgf8Y2oAvQFBE4juGJMNSAHgmlxCJDb4QWX67AxHTmhlKszEogtJ1eLmUxA1wRKPCqR4wqhaSPI2Kp1bHLSwi5FDdm5Z5xmofSi5DlLkdH0rgABAAJIRbbmEDDigAxY0EkUFDaUvAqeKgBawehkddJEWra6OfaqJAKevvEZxRXKKNTFcVFga9Dsi290oizeq5jSW+lR2o8S/EqZDmJLlmjRW9QhAS0MCTJlFGwfsIUMWVe8ydU8dHvF2i5uw8EUT4iWoyKx0Am1YFMETSk7f9SKf+v+8AQq1/lDSIGgeSRDkh+SqEpVfAvSYCG370wYRJDCEuJzxpxV1666WVJK2iVFRiBTBFCjbusJgoTJU7xFEJHoqXxFz3m9iYgpqKZlxybgAAP/6cASXlgAAAhcK3skBGZxBAqvdDCJ3CGh3eySEa/ERDq7ckYmiwADUWVWrz7CMgp1einPISMEpIqo88eeQ1YBEzyMOhVJA6LKmidbApa43Y8ZDo8BuNblCp9qw2xu9nlbKDdj6q3/f1MAEAUMxRyWfIE4oWEZbJwUWOiE+YYagam+GOTInkls2dNJZsc/VQduvlQFLNET0RuNiVbWPqZ51XRTx4nvLAwsnACFQMq397KYbWgP9+skJLCWmlbqDffIodJVYbkmAIQJYWWSDRDps0lContcXxS5GuR49gusC2Bmlow62mkyiYPsc1MBwU23JPSSawXSo34/ThjVOhvKQ8o2nCr6eqS4zE1GUCqWzRETsYl9UDi4T73T98jWrGEmOSJlMAMk0TXdJVczfbZY5aYgpqKAAAAD/+nIEdXcAAAIYFVrRgjOAQoJrYz0iOAhkW42hiG5xEJrxdDCPHoAAACBbkks/YVgJAGJwNQiZTOon0Nl514pvTica+W7MKQhesasRhZ5xb5vQqD1CVxRwqdoVmnqWHHEuSTq6vjP//5UEpy23HsaKgK9PMISzaakTk58S7yH3Ph6IgJmGBjzMobEgjRA9Ty06RNJKvRWt5tdpo8PiFcPJYihydnX9fXS+2r6cwUmo465K5MYqMsqzG1UyQ8L3Q6spvmnD7DoWiOrfDcJOSfI5Q0BzA+OFCrXk5X9iH8u+9A2jTqXd4BcggZPFxIcpXUCkk61HHHJW7SH1T7qISH4bcy8s+ETEQ5yaymUt3hFkGU+G5eX6DV/IzPsvBMk/U1D6SVgkcoJhLW5r+p17RDbxa1BFMQU1FAAAAP/6cATPZgAAwgwp4FBhG/xDpquHPMJECFzVaGeEVwEIGu2MF4go0BFsJNNNynmAhQtG3I52tl2OJkhKaN5LOZD3yCuL3lRHl4VSpR+xzrlgT6nHCTaKSSUjWNgI2tneTj4HqrlQ9IkAAhqa//57W8JyRUszR7zOLRNrCtwS3dTDUa+CJd61vvg20etLkcxmKhUOXX17ovtoR6hjwnMQN323OdLG+jKcv2X9FIITdtuaWM4TUFaQM6ilhI5zjR3pxO5v1L/a7XeC4+zFP66mbwDlAMSosl2sewalZOz5Zf96PeMfAiKbDNFf/69n9W0Ey3bc8E/hHVtfYFT7P8otgMONIAZjgliaGXIV9OhtMicbm69HdSI7gjpo8hVeif5b8BVAZLKTe+d7P+y0ZVLak+tMQU1FMy45NwD/+nIEBb0ADIIiFNkbD0nAQ8KbVz2GJggQSWhntSYBBQqszPYUoAAUpbcOafxqAKhGEwmHMyGsjpnI5xi+Q9PfP2L03cz5nUpNV+u4sVJOGyL0UMS91MSnbvygcU5wx3utewZ/qU1L/Rs6ACAhy7/11zOD9RidCVYiWyKSWTifoHv4dkm3S6y6u9znupXGzbyq3Unh+8g1LlfBg00BWDru8W5xp4nnngJjLL1in+8Apy/+TVDSJ4G4J4aQr0Jo8WEAXp3sL/pvk1bVUuY69PkppmKoOw7knK3TSW/asDKbaQ8NTQvS2xf0bbPopu6+gANS7bEyILIXUIg5PgzsT3iJgGkjtKAbGj0DVO7QIrFvibzR4PpPIDr0CLO7s89ULJFp9drWk275zYOvei/93v+lMQU1FMy45NwAAP/6cAQvPAAAAhwc1gMPMmBA5lsjYYUoCHD9cSCwobD8By2cF5QqrX5IW8FNGz5JxrAvuyU6NTQyy+hQDQQmEpx7b0VKoPdHs7O4yNrqpdUu4TTgukblSOKK1/bMgF3OLoNihJpaJKm/oqABcu21x8GwR8mi6foKUdtGLBPGMv0agRFINPVBsKIqOoj2LjEf5H177vV678dbq6N+2wogtSdRzdjCtHX/Z/x1UAAIKTNd4bjRUlFkfty3c83kdRuo668oThwHZXifJe/bcWNS+16Wl6bKtiNVjto/xe8roy0f/K+MsIkgp2r1pSjp9NEgQQU3LScJqXiJpGhSpRq52KoN6vqOGoNQ8mQBYeYkIK6j/2MbIwdnNm1RqE7fnbHM6eE0petyCKDIFSit6NCYgpqKZlxybgAAAAD/+nIEi+MAAAIRM1q55iokQwYb+gwia4ihZ4FBhFH5F6vv6DCLFoCAhtuSav1IXQW5jLijsqQRoMCaOqJNirPFas8KOIu9bU6d8zFaLGeskaxE2c5Homr/714jkWpZ4jhyyqK1fV9Py+kozBJuKTQXwESG8XMEQG8HRnU4pYnvWgZ/0bd8/WjpuvCI06MZEb4V66n/lgupwwYBwEMPmuecM3l9+s4ontUFxTToS1RKjjl+eqCwdX2OuDv6+7h6iV5f11mFYTzZSqXM8jHjoXJad1Tj7Pf+1O7NT30/UvXlz3/VNTKUEIOwUymHZjPdMrPpivRbbjk1OpBdc84bEpJzLal97UKZs3BjjDcY4keawgzPBJkWb49le6cnV8KTMPq//zM10372p219dV6/19LxrRuYomnNTEFNRf/6cASLBQAAAfszWjnpEbRApntTMGV2iQ0RavTBABEhrC6qjFAGgAACk5bnF1c4CvkbT5zVXH3FhET+htTHRBcKwHKxDadfbTr0/os9tehP/+3CxIaK/KutX7vi74042XdEt/0dYITbluzCmiF768XbGnjhQHvZ8D9DY9jehyik5ox9bZtNxq4xboaqPRGSFpycntn5eI6YT+7eS6PpdqUiGq6ZX+oAgRbcts9KlQIR2H6+131SZ0q25yFqrllK6lCbBgTMHJQE1C6Wq4pJBfTTod3KJ3XW3/o7oJFQQQ69/3ZJ9zddRs/J+OuRo10IAhCCUknK+cxwrT9dp0/NpbQg3V6uco6hDE0bufbRt07/RH68r6+//VNRg+glXft3Si9+tbd++6rVLojtQUJkmOiTqdYq39mhMQT/+nIEnpYAAAImTlyOMKAARKnLgMYUAAhVWYZ4E4ARCyswzwJwAgCScK1hCq+bMhRCgba8DyelaqqIy3JORsvJWn35zuKL/+4oRn//8OAAKC6E///A4fPWomL///+RTsouBCTn/+gH4PggU46wVcS3yyJBmeXMXDhouxDSh9TuRhS7lQ9tn2T39WY4u4ov/7ihGf//xAOCguhP//w+Lv1Of///yXZTgQk5//oD8PhhAAICAgEAgHObgpr//////+nsjolP05KJzTnI/+/GweSJicH46KQd///kxuQUbggNRKCymf//+ooHCZ4OFFAqEkkYKQAQEBAIBAJ1M6goyXpT//////ZHRP9OSic05yP/vxsMnExOD8dFIO///3G5ZSYYGolCpTP///UUDhM8HCjgqEkkYKUxBTUUAP/6cAQeUAAAAh4K3mcMQABCgmup4YgACJDZcyMEU0ETGy/0UIo0oABArTKqtGge0CK8hxzh0BuDRBa1rsjl12UMTNhQek4BmpZEoTHC2t8xBQVF4tQVagsmuyVnRQWQGW7MDG8w2ZZ2ziIAAIpAAYcB0BAkB7MqDmx0D89q5Surg2YTYNn1br4GItNJCsdWCpMVZk6HFQgL1suG732qzq1CrCg/VpDr6BtA7roQEDwtmb+tOAiaksU+ltE73XrPByiyKcwZrPIyFCmYVyFBgFRkaz/oKW6L6q30h5GvVyT+qTK+8ZpWCrAq5pJ4bRimpC4QBFPUZI00peViOzzEeZxxqAI+Rr5c0p/l6TMK4BQOAVZrP+gpbp9VbTRA6EXrc0UZiR+YVBYS+8ZpWJWBVzSTzqMU1ITEFND/+nAEXWEAAMISQFmphxOAQigLEDBidgiVA3uhhFYhFi7siPGJoIANff8B0mEoUrS00zqhQ62g9hJSMQZAtIGzOrNZkono6+301ImarG9/V9X+rZ/UV/Lo7I/sGHVPrW5Z2K9WV9TParY3A5AwCQ8jqEVaWNgUO3T8/4P7iAQ0XBgrwd0aR9CtSjxt2/dFJeat/01eiuvq2emor/7sj/DDvr2vFVasC+pntVkAkHHXJ9rr7KYwdaWORNsnwX8WDZ7KWGOnl2mL68K2+gXVWtVqyV9S982q9+FFv+TlPqssBzp7DQ5RpU99XeKN9gaSAAGq/OIfNol5QsM1IlXhRNwpAnC6GEo9Y6EBFHCtgm3tP1bX219rV1ZqreUnmpt/hX/r/Xy/t6rd0lpmzf9+3onXrwaEqrTEFNRQ//pyBBSKAAACGEBbOMEsMENoDC0MIseIWNdsZJxPQQ4bLEjDCdiAgMsu0r44BDA9pjHqnA8l47K8nXIHz60jqHMwx++xe6KzVTRW1TmDuuatdz+IhTS1SnK75auMSWsfSWa6h37OHfsqaITLkijkbSmVqvdN4WSkJ8V1BbGUOo5eZeGE+SBBy5dcGE0iIkQVAE61mX/ORFUKTKnC0/7gv2o9b/syxF9FQdGCXWHLJdt8w2QkSKQ7Rljw18o5Kin82hj1T3alDkCUtYikwzbmzsqkeRLzrm6HvV/70TxNkbvcTvQpGqVcxaG/H8E3/ZYBXf9EyjKo8B2G6BZ4vE/EQusj/G/yOdtzWIA9wcaXdnt25XoU700H3S6t1ZpltgV2SbVvTxrN0nCtP8Y3aVfrpZagmmIKaigAAAD/+nAExxIAAAIUXd9oYRX+Q0OrjSSiZAh813MjBHLxCA6ryPMJ4KQAUWmWmmkog2tkg27bQiPe8p5dOJeTz5FZPvIfh29Pt54KhRLPiYXjeyXkaDyCeWvn/Jc6PL+f2VejnShvwbniL9oAAAbIk13/6TvGI1A+6o2aXPAnQSxMNTV0iuxaqN+9rvRxSRWRmHXkY2UacAOxwPiw4kSNMsEmxyvxsy1TP097PvWuARKSalZtY82eBZaWLlrve+9i6uXQpIHFsXeYuiz/b5mbXpj8sNlpcYNv1H/Q/klsFDND8asXJ3s1rde8y3Ja0Z/v2UADVUkexO0JSYSQfxNLIXhkClby5ucHnE/wMk0sfpRY7AN/OPKL/eZGmbHbdmkPlHBdRYMPI3BGnfs06/6/QW/R/0JiCmooAAAA//pyBDedAAwCGCvYmeYS4ENmyzcF4gwIcSNmZhROQRUOb6gRiC4MJyOTG4TKiSat58Ening5jikRSceVfl5PkP3Nzu6cZ11lRqK4jUR59eXt5HuvYG54EpSBzta6jK7un6+16W/8Qu+RAIC5LbbcJ6jS2kaXDReS71E4YMWw2DGnvc5anOVq6o5lPyNjdVLV8juy2oxZNbIpHvpqrvWiiLb2VSree6KpGT/6g0CZZttP1HlEMJeHaiVT/6lqisTcMXS4Uat+rb6iMh3qVsImi7r3S5Dey2ZP/qz0glXsx+7tT2f1TfkjRRjA+x/2da/TIMU6Zjbbt4MiDIuSHTDs2ftHUvAXVzM1R7l7ddQYdg0KNXUaXXijLNyyLACNwSFUtRnXTWtIxqmPa97aKdI1151AZUyLpiCmooD/+nAERjgAAAH/TVzIoRbMQGObKg3iHAigr1pHpK0BEJuwKCCPjoABgKqq1dVICJNHndZjNh31270RjtYixlkBUxA0zeI9+eTzB8kfPvIyYhzBHo9/t8ysllU25VXmZYLdc92oh9QAAYACW7bWGIJIYDESRayzMbNBVTBa/u6nswcdV5nUU3R8l79uFZyOZOrauQa658PEw1LqOrI33Pb+jz1elH1GQG/zNUmTEmCUMxDAftUIQk6D8ZSUkR/UWpnIB+aSPefEu4sIi43jUFEBjZmqgvydu7o59U3d3iKIw6LESh6eUQ6u3/f6vBh/TVkkmVntVnkKyV6iuxXvsTc3R9f5tBz8RnoS3eEHfh/JnErI32BswTg+HrnOEonmL7mRoYwqDO+kVieQdDSUqd3JiCmopmXHJuAA//pyBGuPAAACIk5eUEUeXD/CqwM9AnYIlKVm4yRE0Rsnb3RQix6UVdg0225bRYJZBFXGe99vXO6OnUThH2tN35w3r36dM9pLvGJv+iNdme4/n21ZZvumZuftbeAn7iCKByiE8ehL3dx3c8ABu3DzVMxKkDCnaN0a1MjSqhKKZ3yFaxh7mZt6lVeK8Fi+k5J19VTmuFR6os/SNJvcdGH05OyyinZcKuJMFZWAABLbktQZIMLEYgAux5QnKsKfvtB1I6HOyqV9PIymbZ9RHT6fbKg1QQ5LiDW4WaeW6yzFMOyF3XVS4YaU/ZvIt+LXPOREEEkotJtyUSHMjsNcl7yspN+9qUddCi7wkO14+T4OX6ebVVgHxKv8WjMlLLVunKpNmOCV6NzPurTM6px5nIONDOsNU3/9SYgpqKD/+nAEppcAAEIYHVYQL0BgPKgLdyAimojdQXlBiK+xHKhuHFMUTgAfwhFEQcF8og22YPSlGIb5pF8Ot8yOrgAvoMXQ+0DsHXks4+sZf3D4eP6xygvKy0Xi9bij11ecSdU+a7LJyz+uuqqAYJLkt0XbBsOiROOzIZEkpfN+SoRPa5vr8Hvnw8unnz5BffT/Jy9+b+jujsjhJpivRWRWpT1e6PIst7NGADVotJuTQ4o7K4+uMcV3wLSrOJzhJHMv9KFHodtRXfr0/n4V06f7JY2u6Cv+z1KkfzPSyV0nkY3FkdBXivOcg0y+SOR8DuBAJKSdvCw6aA5kCe7WH4tqXlq5Nb9ted9W3H9+vN/XjH06N+gYhEF9SOhBWiF1fbzXv/+sjJ3ZEFdhW590yIHKMucAQRi8kmIKaigA//pyBJNEAACCGTXYGekS0ELFKvM9ImgIoHV/oYRK8RYJrJz0jNoAOXf/e+LqRSFoYTuBg87EpCSvaa8SH1SsPhr9MfyqXV6vroZ9dgfrqbXofpqUuqJ6eraHwrzUrwgoQ2076MDc25H0gBGXbXnijDNYgY0C5PanFZpHu0CYEy3iIc8DN7iPb4X9wRdW1TJ1bCvqjV8v9OFirdVJ02wrLs9T1H1+znoNWzV30QkEkpNNx2S9zoMezEcjJpLLvUuo14l3q+Cp8vCLEU7bZORYqPIEXH1AnUxMLH0212W7ukCEkBNLUrQ6/DTABSeKpUSKUFBJTktzjJQ9wHc1ElhC3lVYrDpyHuPjUaVQh8ElOw4xsxORWLQtXCEk9djkZ5p9jw9JsSgVsn/4EFkFBG0Pj59S9T0M/QmIKaD/+nAEnkcAAAIjNtedPKAARKUrqqGUAYhtgXhYY4AJCybuxxJwAAAnNsM5RYnLcbgnysEZgHDhbZ1BaRlw7iKEOQExgK4iJuLZn21NzaFH99+ja97qZdq1fbmniJ1U1YqaUkQyyyxjX/XxsAi8kpuJy0sOICBkWXFh2ycklVJazWJr6dke7ZsYJv036NnK8XWcJC44Jz4CDUOqdPPOzms1DhwZ/JLDxd5Q6hFs5RIMBCCCCCcE+6cfVoBAx2t3d+NCH7H//U//zXMf/+eyECZ3/+NBsAwsQHBI///JEK6n////e5ijcWHoXM/////kPmI0gACSTFBU6QKybZEhj1LA+G+oOC3WphNP9jFJ/+rmGG//k2Qwmn/+NB4HhYwwaf//lzJ+p////3uYpMgFgJ//ykpD4YcmIKaA//pyBIKRAACCJCDeLzBAAERkG8XmCAAIkCGHpJTAIQmObhWAjegAfp8KwNmA7JSSYsvQ0Mo2Taq5DP2uYU+9S6O/1wT/vVtRRi5r60NIg2MnYq5C6clOlYlFkdvSxrkotpfGEciDPiz5KOAF6/CsDZgTkpJMXXmWDqOQkaXMY/apjd0cujmm66P6L245i5r60NYDYydirkLpyU6VgqLIYiS1kWNpRb4wjkQZ8WfRHAN2Zf/bVtJ2YDgvKmHmbZIiMSWAIZDGfS0gtz2WnkQNNoMEnFJni40PGW3CzkjrGttW52r6yIvInUhPom45Sxx4kLbDo2DdsExWbhUo/lnN1c+Vrqx8dgupXOdvNYf+5LBzyaHGJxBrWSE4BAtA0gg2xSV1yRXWz4sPOqAsCt0uS9LGJFK+VTEFNRT/+nAEJU0AAAIeNd7R4yhYQQbLUGHiDAg0T5GijEexEprxNICWzgAI+UbTJLU5aCRTAlszLoYB962N6UR6IdmaZ1r8z2z9DMNa/XZfbuUzlIqPzqy/iKn+x+QvXiwXOiLXZfmy2o9frsiJYMXAaCFJ5ib2l3daeSsFgdC1GVmnRjmk7ldFb69F3fQdehmB79dl9vUzyLfnsv4Wn1J1O21kRKpm1VPeexF9qqgyq4tY7LGm7Bzi1bjOxxA4FkeiUfhkhL9XHFDwOt2Sz7REoPTwkcR5KQ1nVYi/GHqSWnTvrEXK2d7oaflgdmzwlYGykzK25G0lGCMeJ4bZ/dikBtXaJqdUdRS7jH1nf56IUnDTcuah+lWzP+rVMX6lalZUBn/GPyX03vrEX2fhp+oeoOnkEExBTUUzLjk3//pyBN53AADCCDZZEyYToEAoC1NgRXQJJWOToxRN8RIbLQ2SifABGqMSIHnRiPPunK90ufm7SS3VPeIoYIf+YS961dQoMUqQF47ON2Bj+7dgd7fv+++T2b/XrfQXotpoq9/ur/+j6gnG3JAZIM9ORWxQUUU5dooxrtkRDo8bi9qs0ZGqXedNJ3u2jRMGXtTreXVN/yt7f6/H0+n+SkY6rqNyUf9X0fYGm3VrHLbZbitbdMqM+XiIoHpUTQaK/ZelN38aCenyvrP1FNooMzq36L/WtPzCuvXo7rojB23Vm703VFurnNf82rG98o3fWGrKwDJJJAvIiNG4vLexidmvnH/zmag4VKreKuwLqqjhuhHtk87W7YWRaFyi9vTUtV70h3ofbf1XUVQyziSA3b8ij1v9Xo+tMQU1FAD/+nAEut8AAIIBNdsZ4xOgPuKbYzxidwj013NEjE9hFRrupGSVFig5bbULCYblY7P5ggT4yfmmxrHZIhL6dGPzoyVLXRU7qv27mbypqp9HPRVIq6OVqLo3TteNG1VP7v9W3/xb9/UECWm4h4eI6sipHptzhVTm81BhunfG0En5roZxC1wNKeiSdo1i1SbQWEw5ZO0UqknjKYu/hp2jacvuI9irkxYCAOY25JLlQLgZeH+s33iPzgAFIfrHxD4mTJGRIRk98qIp/P8G3u2qa3jaJV7Kr+3LT7oNepTLmU/rc9pNSH1ENKHUMv1IkEByhMtmhcFyRTOv6X1PgRrVmIJAnTNIgixm0+3+oo/qzdfs31fZ95qvRG0XitS7dpJNH2tEKVA6KElFXiAgFi9BFXSBZJqYgpqKAAAA//pyBP/zAAACHjXeSQMTvD7jm+oYYnWIzFNk54huQSAa8KhQieYEBOZqqunlDjX7nEDWwJScletn5XqGoWNxu9U9fVtJfbRaCfRdHBe/7lYqmoDCaklolIhsUKHL+XWyyQJts102P31IoAIj5NtNJzKyvGqqdajaCs570Mndb3hVlqpPKjpYTeIRY+ucnU6qluaqjD6xVerYz05OPWsCybcXu72Lfh1MkAEBJO207lKp0JVwnDWwt2WgWfSQegrEiWmSIuxMBDLJYb0qPQfYk/kscq5i3rM1DNzKQsW1OK6HPJKYja6PbP2OZv+LP+hWyf25JJJUYpYwYNFlqwyAvhgEM7NnAQmYdLyAnZ38Zf75iyNYVVW2VJjF29bC0pRQWkvFGKjGhwtU4XIrctbEHE2XpxM/9mhMQU3/+nAEl9AAAEIaSFsZiRKUQKVraiXiRQi8k3VDLEpw/iNuGJCJ7gU1JJGzHXrIJrebNjRiNQZxnehOwP4Lnt3slKHfIz0qQpi7bStrRHZQS3SVUSxmrS6mlr00R/XTdv+kQpiFDvx3/uvAACApV225eaEJavE++UqSreESK9mgkm7R21I0jopKOjfbsY30fTtl7UfBLNF9wQErHOst6Gghq3HUdyzf7+6n1AKBISWkk5zzVcSjpr0J0XibhGSYR3Bvq1iI6HfVhv+10lnZmODa40ILDCGUM1IJJD5WP9PRpschwGxGASZG1iLIuxSNqKYlpmuLGGNOpunbyvyYX5hok3pk8kdF+czai9Az+z/y6atdkaTJr3erU5bUv+czU7/R9KAmMRcYW2Scgx1r+7qTEFNRTMuOTcAA//pwBLvBAABCIQ/i6GsanD8my0Mx4lMIgD9kZL0IkRMQrNzyjeqGNhuxtzW27wbNHnPmoiMGGRwLYI/Sj8G6NQrnIsLXDjpV4OIDYjcx4u+rgFWsS3Ui6oo/9rWkhGWSKsa9kRbTOe9hNIKJklDRycB+8YNiT1WDqyYwPBuVt1qKyCCUqZ1u35R/a+yf+2fl1aZ91p+ruhC2PUePKkEwrxS9X4jizh+NxUAFNyTWi4UMo0RsmWlIDt8dwkq4Uc8WQZc0+CUCIXbJUpZTKEM/KSVxWZNgBQVA7BSOLME/oltS0zQpSOXQhTfv6LNaggS03JMpBRIZYgTQZcTbKnfO4hPPE3dIWBnRRN6TLstCd0Qns81CklnAQz61JnY808OVUpsW5VGW44e9KkT7Iiu3/6/sTEFNRQAAAP/6cgTADAAAAhQ/2rHhHdxCiAwaFCLliJSlhUGkqnEbiGzYh4w2AKAi1+fDHNJikXbs9tV4orJbX3qTHzSEiFq+vd585HwWXfEZeQC55dZaeL9/bzsL0/+SUriQ0ZWDJVzWnVbnN9VelA3e23JJb1LWdnu5jiJQL61NzHrViSNs3snf3Ev0ab5zFkhBHsn/k7bzCef/s73gIRFs6sap1SwcU1fJWqDT++pdKp3O/kkll3jRhLZFe4kVDEa1oZtglK4ixEMNcidkY2qJ0V9+1CszvXlxB5HXhVtiguxutVw2WaWc+KaRhsyMjnKoqUPfWAQBFbcLu08/D5ss1ljRPuIGNEY7qYMNPEhTtGAJ86KuKJDiyFLLwG4q6KxWZq+Bx5acEgfB61qXd66ZaigGwospRYp5aKl0xBTQ//pwBLZLAAACHRjeUGkSLD4Ca7oY41eIvJ9s5LxMMRwm7qhliU8FRZGm03JoUGILacxLbSGVGTwVLdGQ1RJKURn1Fg62KHMxeu0bTZdtqW4RHD6kbrsPtGzxRhB7WSK6qO5EWSx5tK7MYgIAySaacp+aE7iU5OXWBM8vpKv16aKWJJgijscPknLHVehxJjx0q84l6TKLNl2h1f6qDAoo+wAMQ6hE4v9MFCUiinFGChrBAmba7YzJlGOqEq2MForZjjOQRLYqqHTxDXjPe6PrJ3BUTYZohqP3F3ZOLe/Ihsheui+o4kJa30JvDRY6oAACiU1E6JW4WwFdx8Q22GD2awpxtLuVZcuf3lfZu5tC3TyU1bGocucyHZDvYk6s9dU3/5/s7Ubda6NvnHY07A8aFmM6PhC0xBTUUP/6cgT59wAAwfMd15sGG6A/pPsjMeVgiT0ph7RigDkojqwOnoACAKLm4jsbf9BSiXM9sv3Qy5mVh8Jkm5XWtjGYBAPHOQUNd5ArrnX/XefFbLw1nh1XcXktf3Ms18I3bb1fs7EgkpSUUQhSEFVRJfUNLx3wuHwOd0wrNCwVr2q7oy0q3q6e3ZObtnztVqj4e1+hxEcsatMmw4l3vU5HseasM1WviDRakbkttv5pbz3ic+TaZgXeHibjAd8qihDtEtcj72tnXVhnXJ1yjmYrnuxW1ff66lWj+pbeTNq36ftrqcqCyAJ0ZNHFbtQUt84BJbkimepEOOdrVDGy7VRiR36gCUl7Pvc1xoQnvcvpf3XPz/dtH7zxqMcVr1uRDc6VMJu6wGp5U63a5DxZDCjzCzpVr3Lsrbq6aExB//pwBL6ZAAACGU1erhigAELJq7DElAAIuCOVvGEAMRiN83eGMAYAAEkklNOWLdOKOCxpYcvmopJGvXSwuT/IjH/9WJb+/jDIdif9LYuJAOUpyf//iKOjOcj///+89w+BxL//1HFjyofL28ujmNvUYTFYlUHi5MqZSihYt+KC5P9kY/26qxF/z+MMh2J/uS2LiQDqU7f9v8RR0Zzkf///a8+HwOL//9TFjyofLgEtFyySNIkrsrZzHytec4TKxk8FAVdYoOhQOgYRQdLhIUtSIRZ7WPp3Bo2LIrO3a5IseF1B0iiqS9yHTbPra60Yt2dZO1gFRt2y2xtJPhGpRSbVSIKpUpzz6X/D4zHsxyN7ruFgpwZFntY+ncGjYsQrO3VD4lLHhKoOkTEtJaLkOUbZ9bXWjFuzrJ2tMP/6cgSi5AAAAh1MZehiKjxEYdwfKGILCKDZh6MMRuEGmy2ZBggwCbjb11kjRSeCNq6xTpSPo4GytrCZyPq/TLQtUydCfRT5S2FzPNd76UZ5v6f7U+nuqbSo9v+iSPSXGBTuh9TqTDWiv0AACQoLNHtbRSkETNYcU8zBUNdHDtK0oJzYGFzyFFVHVQgjy8AoB5542meIPPdl1CtWPYgqRR6oZUuImeXeQzQ1am1dyQTcktP/tc5aLAeThpaAr7xUptC7czO1FDGorumnTM/+UrkuZNpW9eqGcqkKWuqU/H/U3VyeI48k8Ang7JJi4CKugrT26gAmIioFcAHFoInpwtcd91FKRx+JVg3Rm0Z14JO7k0b5n/ylMS5vq3/o5bFL6pT8f9TdXJqkVrxp4O6awEjBWntXUmIKaigA//pwBNg5AACCEhJZCyww0ELmq2Zhgg0IwNd/Jhig8ROgLlzzCDQDxn0B3IdGnqwEE5OfYrJS9BFgMYggeYTMRT9SYOrHw/pFQLBVrBFQqqpLyolAfqeAg1krUjfOyWxP9BWkqN9X//rAIlqrDtw+wWH8emOfka7mlJ7/KqOjPqC+BtR7LxJX4bt2m6Gdqqqtb89HsZm9S2XsDQzOyWKpr17SrUlRrur9vp6lgUh13VVhKNzs4pBOHJjYPWYKNO+wU8roAhvHkyBYgjajqaN9tREnlTLt5+teuiVwmPENysdew5ntUeGyCQ4a9S+IH+11VQ25rXJQNULckgsmXVeCOi+Ff6SO+wLZVEvCpOmzJ59MzImz5QfexcZ2XmFdPsdKXwf9V9+9IRLy3jhe4DNd1WvwzbXImUxBTf/6cgRhsgAAAhk2XenmKGhD6wxaDCJbiKCNYkegToEOmy7oYIlEBBAAqFLsu1hMDKNScHGGZ53m2AJSRFWL901Ogknt0gv1pmHbocuuyajv91bpTrrbiIJJVnjhGuAhlrsgoVZEh3Z7tIv1ft22uShBRH12OK0MPnj7LWD5+jhU5dGVFStC6Eu5EKRbc5VMAZLXZ3/So36l9tn2Am+/0er/VlBN06++nIPpra7oAH/wbQCapxSNBDYaMlwjjF2kFEJKyFK/Wo9EoKBNl7h2YrIvODROcjdvlXgqkaY4kHS2tepR1NJKRG6/2pqYdd+/UJvpCoBdx67X8BBMjNMS99cyYSfJn4y7NdrNEdaJz9lvpDO+siNR8mr6WSHo/+qsglooTDIXJyVbvyBE2KnVOfsIl9Qms1JiCmoo//pwBMvEAAACFSZeSMIcXEKmy/0MItWImNlxRISuoQ0HLMzHiYQCBOWaqssBOewjpa/S51B+nNFc69UrS+5kKmaN9fBd5v1ox2DqF71EJO5pAmRGH02BtZVZNgN0HW7SSr0mu+mOz9lgAJKDUSTbacoFvgocrD0zemWxUX9rEn2/8B/nBM/v3vcK4iubGvva3XkJ9wjsYXARpFqE7/1lh7BxYVR0wIrP2dL6QAAwjduluKuIUWmj2vtwl9qiguJlNAPiaI7DTspWQHtK9e2iU7StYrnZWqZ/41jtyfolF0FvwkbdQ9Cu1FUeOR/rNaMUADLbcHEqk8LmQmKC7eGwQVUQjoXaGIwNiu+OHiKwmYUlzn0OIzZSNwP3TQux+8UHMADDyp90jSJiGzrHoW0CM+1PQxMQU1FAAP/6cgR8+QAAAgBMXDGBE/xDSxwNDCJJiNTJgUEEerESry4McIq+GADMtIVnVuhfV3cqWpzDlDUZxXokXB1XdHkut7ivC6uvsdIsGy0Uvejb0VfzLWzSeqP9f3kkqrMDNkfj0yxj6gCSAIkkrI3LPUw6JZcrp73sD+n357L17IeyHQqGeY1EcLZP7tKyP60bNsJ/k8iV6r9PqqUR2qxSutG/qnTxI1XZbMLQYippxyOZ0j2HpcTcoq2m754y7CxFd+0ZXZH1xrQ0J4sslz8QS6b8UELHlTkobeRGX1fqKrIIB9aOphBEYYIvIpSMeoe8QElJJKHHFwmcakiKRkWTywQ+mj8q/jU3DkiP3nRrmK9JFIIe5POVf6Vme5DkQgIvy6zy91n619en0o3W5Ud2XZmcIfuUtMQU1FAA//pwBCo+AAiCCBTZ0ekToEMjqzM8YnUINEdiZ5muQREZbhyEiKYAAIAA7v/5W5IOFBlR494a8WvX45NIDjXk5Ue34oRno87dRnJIE7Lytj4BkQIflCkjQUlljord17PkfTreSs/ijiinJJasGSG2LpHZ40NmPPonlVAQhsOUQf4FHvBmnZrYG7307sDcpCYu2I6IoODyGk5sshqXgWKbNXe/ZTtFgyrnVcgAFJ9xO5mEXvn+wu76IaQzLI1EbXljXB+BhQfJNFU8rnpEg1AZTReOrVAYPEoNUYpZqKKYwDgNCDv2M9Fr5JQTEjyEJJJKNJQE2KIX3bjG1oPqEtwdGfW+SEWn6lS+Up7uydHrrchd3u3T6afQGjZQaKAAaGxNGWJ00N5iGyH7LByTCuHNiUxBTUUzLjk3AP/6cgR05QAAAgghWZnpE6RDIduqJMIriJE1cuMEVXEYJrB0EwiWACTkkjzpBCOndx7SKM49npcbzdD+mNXWf8kB795dHR/Z/9pTNRTuCcfdI1zqz1uQJHVpDg3OPJpfS7dk79Bf+ioBQJkgUm3ZZhJxsFQjunysBGHzoJoJuCBAcYAQ0kBnCpj3exZxjH+U+KCsLAVUZfNi9bcq9YvPpwA1P+xJhA4cJnU0UDIKLbcPTotmre6QxgIYEwH/Kw2M/4kfmZM+L0KLNbqpwahP7+FIYavP5jEdzOxWvZ0VoPYicird6M2tlp99h3int5FCATZKUbdllvYY4R0trEKMebMydyBGZlr8ybss2rZ4ONs8zKoV6/0KVVRBkmr0c3Rd//VKk10F3rTt6+5piC1HlfrBpD3/UmIKaigA//pwBDAHAAwCBQ9aGYIzlEJJrAoYIn+IxTloZLRLERWnLczDFKYEJyW1y8IMPgOunTQj8+UBm2dWOMDZfcwOmbuAtlqpUs6OU5LECRo1CVBpXCCC134CLvFXuoTap7IlURWdVV6whBc1bakst+V45gCH33L/xG5zRPjCrKF6Mjw3PPhyXyyLSrdFYfVrluv+tv121uh2pp/56XvqUTsLAJ30UQpj1jhf2EhKW0EC4yC9B5RXVFw4sO48R3KF55Wx131j4fuqpmLOCbd6fmr7LRpE+X8tpar7UmRrG5UQjrMdzsVxrJ39DdqboumjYSkkJNJyF3ivgLIDnmiuLQBnXqwz1VosWPelZN3ybcfOjatRrNRc77PUUIpWqrp0dOiaN3+uzaPR0Xp29TO1HsFvGP/7KUUpiCmooP/6cgTTTAAAgfIdWRnpE6RC6ZuaGCWdiRxje0GIyfEbGu3cZIhqICTkgbHMoz0mW1muO5I7jJdikrhT/UOnXVvUEVW6yn9F1dp/xKgz6DQ+9cnoJLDvtKtXZ60tU0o9vbqqU8VAACokJOOAt7AuDhkI6jfPLdBubzj+7BQ3N9083v9T+59lyuTKNZ+/srHHsoszUZ0MclFaJrRsj/fr6zUooULqqg92QUBUi27Jc4a3BgyGtrGHisFUZs5GW0lO3XGcdDanNgV7tsOrgBpQ6cM1FCAWYAlJllRYDv0JahGkqBRYBB95pLrhW4BDHBgPaiIJy7bzHE2DqTNUul0ukkOvugtt9R0iE/1V1H1bavrtm/Ca5m16JTpMUhlNj3xx06DjzxRBSlBRfeo6TPoU7FcsMOBg+kYpMQU0//pwBLQ0AAZCERfZAewZ4EPiG2IkI3AIBMt8owhrgP6IMDQwjchdx5R1dKNPUzER8DQnmXj58r8qeqN/CkpjCA2Bb1YDNLDEA8AxUDhF44Ng4TCJpqG+UFjB/Tb/Of40giln/4s5qgHI1/qJ8EpwJjadmi/WcucbzZC0plCcIDDgHxzQILOFTbqiYOJCJoXQ31Cxg/pk7kl5Ccjb/BMgQpIej59QWcTFgGp37oFzrIYBECCG5EJEpQQpiEfEigEFEOtrc8wpQiXivmR+daPwrHEFL6mlpk8RTvnlv4jgIYiaIIV1f263n/X9NvRAAAQAASpvBBBAgRHKaayEokvUdQVDLTmfEQKDVBu6tTSDQcU2BBc0Fgi/dKfS53qZ+vky//jwsNHuNDgw99xFMQU1FMy45NwAAAAAAP/6cgQvhAAGwh4a30mBGhBBw2vAPMMuCABldgeYYoEbh+8E8wxIAgAAAEW2S4CaZ6t3X7piyPYpEQ3YBNMsUxLnW1LXh+OnxIOPAUAJ5h1wCAqHP5t6GKwGuhuj4vrazxppIoZMX8NvO781Jgi2xyJolheN0L2ei0/iIqzkbQJIzEu1hCRtcIK4PvzqwyDSQ0GDa3BxFCA6ddTzcYkVmD+N7tlbcXV6Cd+7/phxI4LYKTWXBpm6UJtCabgRmNrYf96YElIkYwsFx9yhgxouBSQmTNXw9atDmmqmFWqeFbC3p1UOfNLzeTbp2I6w/zQ3hLFjqSaOw+dI2JoRn5FQpYitZYVcEBKIbVSBAasPgEaHDYGDCWAu4VJPMbkqnZEsTHT3eP2TBdyQ02l9YHZv0uftZQmIKaigAAAA//pwBL1QAADyFgDgYAMICEGii6AwwxgIXCVzIYUoQQaQbgAmDBgAFultCQAQMhFg8OjxIYAw9xFAiVtFg8sSIAwulwKjEgBMiS3o9J5GliEirRVy0ElSS8yldneEnYrMRtarowo8Y7anHIAZiQ0puBLei9iCj9dGNEOCv4/cWxBwMgyHwKaMMNBB6gZOS1zGIUBluZ3DmqRat1bhaae8mpPQztQpG34ZQQ1gBJAACDaBaAu1EaOgifWzqUBUTiYdY9LlpBwaHAmmKLBhdBpyFVMBc0xiFOpC09NIy1S2erqZ77kJOtY5SVXSCw6AIZ8B1VsDyyGPjbwciYrG6zMzclbdyNuG6U2JVzM5NWEYlYNMHAmw0lZ8YQDUhg2wtDI0a2eiIWNd7G9/T3I9liPWmIKaimZccm4AAP/6cATofwAKgdkX2wGBHJBDgSuGGEYQCOTJZMeMT4EQGWxA9gzYislD1Z1nHci6sGiRd+a06lH+iz2nNjmtVgJB4OiwjQHTYKyRU6eVIG1JcOXHIHYdHdaiLWzeor1WfdeAQBGqwzQkNMCEehOCurpg6dFnqeLpa9LHFmFiywaDxms7gqrIzY5U5sLsuJnkmhY6kGRVvYwNKfFCx7zUs1TbFu7PSIVqRUiNZqKJhVi1tHtOn6wmYMZdsES8CiXuzk8czVj0cmMuLNqX95xvZt9H2oZ6MbQtsS0Jzw9yhLQjKUGCY5//MEUdH80+t5wLU6mbSsRpDst1HvhclMsg2DBfuocLZhLm9m/iWTL0P8jL9UjcZ5Jn6NctZeHO9NSgW3GwMJRqiWnZR6unQtT7v6UxBTUUzLjk3AD/+nIEbH4AAAIaFFiQLxhgQkR68GGCPgh8OXOkGEUhGQxtXLSUbAAWqBcjwcFGcsFHNqeUEp7s5lXqZB0gq7jwc88ntH2oGwqOYiJBg+fJCVRuae8uhVRhPQdVtixJD++9Xra/e5Pwl8VzgjZWaln2f2za5OJDIcqwpThDfXuQ3H89JCwt/sd+k+wJgZ1DmvNTeVaovPCipkck1WTeEzRtXotqL6+yjZ66f364AmAAmm645MyyEahk8/cG3s2g+4N5noEIonGkUMQMaecpzVFkjGpYl7BV54mpYg1UqdbwiAtOZe3QsUad9Wn7ZpReoxAEApEpS2wJ5lxk+/mU/Z85beF7PiK7Mhvamp1iKUPPVJVDgHMhtkKkXmVCZzdiElXFFvzt1tS1Pt1lbNaYZcUbrtdDBaxCYgpqKP/6cATbVwAMAhcOWZApGGhDwysSPYIcCJg9YEC9IYENHG8kII7mAiVkS4AJhEnmxInU4eCQLpoO0epOhWGzzgM7QNMpeXc1wBHCrD9GBSKCKzAknC0aRJPJNwaXLa6jSkoW6gbsd+a/1AT3+lFkWjxPXvr8LazzAbhOhpularjJUGshWlXLW1hRoKEnpcVODbpKYU2gmLjTQlh4tUl5bnTdJZXuWL26Faf5r6NQAX+NSj/XQUTNHWH6ua5hkOy/R2XOkLxMX7bRcmw822EDIckS+uNJb37CbWJjKEdy2rdLrWfW+ZKZMgtqLlop36XdNjlIihftNVyxcQitRYRyWyDuJO04dIxbN5m7CrXw9aL81++5pi310GNoGd0PaD4wMLxOMWla1mRoxGYbffQejZLXtc5EkmIKaij/+nIEP/8AAMIZCttRIzkoQmL7MzCiYwjUY15nsGxBDIytDIMIbIDABJTccle/hjDQho4mQUYYV8o90IDS9BEqwsGCqUvqQsm0IHjzjyR6RjG0sFLK1OH7ljjyM5XyFnZtwlvIVb2HWsQAEkU61HcDlg0KiuuQ9gPOHg9Ap7D5D1a4fK+nGj0vk9jD6zOeWdIeyO8w4/O7LCrUpTijrd7XCrSYLuFVOo7hx1qCAk3JJ3kIL9bB6tqqW86Z8KIkgT3HzHSt2peLhXtpRsvliOoo3p0jmLImluQXc5pFighcKJrILdMFRLUEXPR4xWas7l+r9gRiacrGIADYeGSxx1YkNPeME0vMtUyPR7H+op3suHln44g4lInjTwyprcsxFw9BhrENJodtoH4sSHOWpbb3NLbDuiRTEFNRQP/6cAQB9gAMQg8TV5noG6BDAfsHPYMoCDw7YGw8xQEHh2zM8wigABSckh3axoWUKWytPFlkyG2dIm2EDGGR2XXRhaHqUlwgxJciPdWhi2XlwIZDVhZ6QdverLwzdJI5yJPctPTYxf/cIAAKRy3TPCHI9GihnFoR0W6NdS5w2j0Y1KUBzDiginEjizw/PaEix1xogDxNy+0upTBa0YxP/Ofk8q4chLGDGpH/1ABuO21pMXPCp23Y0YDe/Vh1rcQcq2Q1scfuPp/3B5oTeDd6jxRKY+hbBQ8OFQGge7mhAKdNdpet3TlWMez+r///oIcl3/xMJODbR70BIhZAFOhFaJSN5XK1h1Asu56sOGRUifM3DUpSUKo60WsSxVa3kocuFrnnutGyvrS1Qs3Sj99/SmIKaimZccm4AAD/+nIEq9QADMIEFVebTDDQQcMK82WCWAi8YWZsJENRGYmsTYSMoAA05IGeNiFwgQqLY1B4mi0ZLICEWjMOna8bzcxg58GLf19zeZrzQGcaegeA0rP4ww+pKHjCuah9xqiVuZRX9P6QAW5aGeZgvwFTSdLStUtVln5wEpXQalORlWeQ52irXmGebBUCMlHMGj6l0tasMXCe1tYjJD+TrRb3FWLX6kJ/V/vQKbctgAxjT5VASguqYxOC/m2GjmpPtoQs6MioTp6uH/TgEBWFlFjb6i749zlP6Nws1ooIuoZRBZCg/GtOpS4FqXGWNo/OEBu7/tkNc0DSIFK0oMxUD4FV3U0HszPhSghEMDGksQKohi/LlXk64TDTxbYG3nrk3HBgsli7kxZsUY5z7EOHXmIoqvum/tTEFNRQAP/6cAT6lgAMwgITWBsLQOBB4msDYeUMCHzJYGwwRQEIGO0NhIhqIEln/VXB7LiYbQOh7RPSMEMowiUEyKKNo/Ck2Z0y8D+DpR6ZVbhZzypgyPJw856iYZLU33/0tYw0vfW/+3zNu0AOXbZEMBkfAt2cRcmN5lyPDfNhxUK9toQaMbDMdobHoD1Kj1yhzxZbXxK5Fug9SwyeA4Qfah760w4R531Uf6Nv9ACd1/6To3mwp1gwh8tr4ErPL3hmUjXBwQpkBgbOBFa+TVtXYeitpfZWVNVZ1JbzvsyGLT0VGg2v/jhdosVZuSp/Hf+aILcktVRIk2WsYFSN+JAy7kcESUMGyGarJNx2tr9zf8M62K6vWpSKqa6La5kdm3argwqWeLardf+zqCrn+gLbTy0pTEFNRTMuOTcAAAD/+nIEwwAACAIZPliaLCjYQOJrdzzCKYjVFWz0woAxDw+tTp4gBiTXZdwToL2KRUCETHmJGa6ar2kJQnQe2GmhEXZXz53bco023557ptapdn39loZNUerMvbn6N6VGo5Re7ylF1J//0tWEJFJNJwaZS7TPXB2yUnvE4aVOtHar6yA1Vxc7MBViGtvS1hUQadz2hR44TmHHStZ/fOLPJu9F1BELB6tb7lfXkQkJpluAqe3MthWdRuDsSYpgd1e7UFEkDWtr+jxrtPzNUtXq9B7qVJn291myt6tU3//rndkJV7E3vUXeKnZBgXWkVFenXX9IBaSThIXs5fqMSkxjLNExAngSR8rztm71Rrd+VVf/Y2JSHxwlEry3rMD+4FuDa3pF20IHkL/dehQHFywVeHq13y4urUmIKaigAP/6cAStRQAAAiNI4O4E4ABAiQviwRQACMBnebwxgAEVhy73iiAAAAAAAAAAgEAoFAoFAoqEtXQnGX///f/7OQvT/jQw8wmPdf+LCIOxwugkd2/+KDXn1ch/X/8yZeWJgh3/8oHEh8CBAp0kGGGGGRkEgg7bs1Gc3T////7SXp/xQhyC4lU6/8UFQDEB6BzuzN/xhXnq5Pkr/+SS8cDjEy1/+CTgObBBhznr1IAQANSNkEkmWtoP1nLci2uEky4k55PD9JOcnKJQ4XQ9JYWYZW6yoaFBxFd61YhWwS4oUw0x1GMGPZahmnGnkQyVuP2CrTpBEALdiQBJJRoGsdiodK2pgmmLQZ5GLUCikkVnTGm88pDyVWQExEIPaRtZUdFlnamHJUYjdQQIqrjOaoEzXFHnUF6iwuVTEED/+nIEXWwAAAIVNdxI4RRwPKbLzRQijgkc13OjDE7BJRIxdDCOnoBBqpAAboBVxArIxxOKeqyRYkPQk+XWVchKP2vjAU3ihET/EzIVD/DQ3xVRsraAU7NmpTVCjwVPehZlqWhAeh+WbNEIIlyOVpFJOaCxh6TLGuLS7jjE8i+WSkJI8hKfv9gLeKmX8Uydfw0/FZF51aDr/9i7CJUFvyQ+soRa7vpIBgACUbjZjjvg7k0fDqIcta3D+DKKfWpIfLdaybm9VLmVPzopj5enUt6lEs05WTRv8T+BXVklS08pTFYqb1VNI4lDSQroPXElwOVKy22RoJOpnuHfcpgzC0V4MpzICck0MLQJITGK3GYQ2f5MmxlRKwNHnaw1C+Q930ncXcyeLarIwmeoYsKjDdYKuAzcFlIGuamIIP/6cAQmmwAAAgZM20nmEUhD6ZtGMSUbCKjXZMesrMEPoCyY9AmQgIABGUWntFQFSrJ7sLJ+R5BzMHS24dtIit0+RvK+1G9OYnui/9y5X/6+Cb+mia9ontL9W6r3MK4zT9r7qR1MiHCAQ6Irz2hh0LR++aEOkodS3Ma0XbUIvZVFXV8nRtcrZGJ18xur6/3oOy2/evUKbdPdzJt8Vb/7d17mFotGaft9Q7kQ4AQBv/jS5H+5ksNfV/InNFAoimBtftXkz62iu1Xw81sWXQgYr8fvQno/Z/QN767neiOfXW3jAxmNzOQPWs+3Df1r7X/MUQEF7/3dGEecYzHkbWUIMLBQLRaH+K2SG74UGOr0BbcukZTa89Gq9fR/X9HRvT8rbWwXb0f+yWFGd9DF7EId9utNvChhMQU1FAD/+nIE+jcAAMIQQN5pIykYRAgbMzCiZAh0X3uDBHAxFRrsTPQJkOBKQLC3Jttu0+0GB4GaLGxjPDSRjx7tRX8a1Gk6CB//VvN1Lqpi206kQ3fpolPQH11lXs6eo2lX1GESerlOKv/kKgDJJJJXrwOcTjXsgOQVwVck2jhMGfRnV6D9p+iPbI+iGK2U4qzXU60PY2lszOc8/Nbu7ILfrp29dQrVdU95D4r3P+jqRBgBiX1dXiuB89W99O95BZDcB9p9SaeipW5RZTYQ2nObaHnu8uIxzw2fEYgUesD8BLAj1izGe/XOA9WLEMhmX5Xv30AhNxxzRrEjlD7e+XZXjeyp3HQmdLAuL4MM98HqzhH0Qb0ffb0Zt3RldU0r1UvK693Si2Zx0rYCZkWdPf6D+t4c+5i5X9KYgpqKAP/6cARQKQAMAfwgVosGE8BDxrsjPWJWCFCLZGeMTsEbGu6kgwjOBylm5eXuthZ7NotdquIAC5Jkzj4xBIUnoJZOB3LHA6QMrsbv7wGvtaB1yB/hhOqBnKfVnShoQkL62pa5ZzsgJANyy2bOhvRyhR1qtqcJzkn0wbrUyKaX3uGJMw8lAfdH9H2o3UqzVt7fS8qTl/VWMqWVRDgVc7f/3i7lrnW/nX7Pg9pQDkktg4sP14LIW7W7OIhWsR2aONtWBEM37MYM+JGV0FG9E7/Rs0uyiHOFpNBxS4zW55mCrqul67PqU+2hn/Iu35GiARJVeqtJgIcg71hpKd4MUzWKI6JVsNnoif93zwTdyJaMjvzsqLNajsTb9XRnSwWoDhBa3QmIXy1lZ95xI4NP/PKpGtv0NpTEFNRQAAD/+nIEWjAAAAHWFNwxYRO8Qoa7Uz0iKIjo0WhkMENRLCqvqBMIJppCzLUvgj4pFlcPDzp2xU2odmtBa7U2MHZA8t+Rz0u6thS4RjVJcsmsE0yDPscuz6nyB4KiE99KMl+khNxyTE0hFc7tdqS4M8q+zo5n2bYEzc7Uzei6Kj5ZxtjK5KN5WNVmqzqsrqZtboteiCrpfHRVRmVI60tzw9bdqvDTgS245MrAJoFxb3a8Ab3PhQzWxYd26DpwfKo5Oir2b1JapWUqKVNSjZ5tWS29OqgzFUaRPQoJSKmt+vQoss5Fls+wzU74oPjonG6jckbllxmEzc2tShHZQuIezcEyMsOdTlhG2Qdej+D7l9DYeqNOfVlUF0OZN1q5WZfR/P/+n7/TSy5luCpz2or0FWsQYpRU+MlExBTUUP/6cAR4mwAAAhEm2jmHE6RBZrvqFCK3iJzzasWgSzEYmS3ckIpWCAQU3JJNWCXUFUs7poBnl0rodzopfnBR1aoV95Pj9C/Rfe1JUZVcTnySUSaZIeHzYZEtTzuIqs7Gc88zl27dbMW0wqPVG443LaLxo16yxJnCxLNQrdGUUxA6hclnm9PKF+EuRUWtHteFZa11Zru78Qmper/3FmsWUIMOkoiFW6hffu6wCAytb4x4tW17g1DawnVVDyOaGrX0GKXu9KF6RVKX97rO7F3vuRtTfVmr+tFIvka1/8GFlCBCJDafO0qYd6br6E1Pq7FxHCTSSUfNcBOQd+Wy1y7r5jbXw5bvMc1vWSkgYiZpusI/R5Wj6qoInd8n1160BpMQstoqBsJC0Ba+3S8UcN7UI403Tgk2tMQU1FD/+nIEJmAAAAIUEloR7DjcPwILMz0mJojtAXdGGKNxGyZtWQMUpwEluOTwHIQwmcfYvqUQY3IKv+PT6oVLNnCAYtQVPHNit12ep2ZCtA4cFwG4q8FizWHztd6usX/SvSKofj5ELXo6fqIKTkkciQA5CaqA5hRRHE+OzEJOdAm+bwLZ+FPvQARm8lINcp6S/GWVsaJfIuXaCzps8m95BL2VO+5VifT1/oAUFJJItyWQ0BU0w4g2PA7ytqTxm2iuGOsx2o6GTRW8z+ny+vvp3atLr9+jjHJNN1fptIpXnE1NKLPUkRrrWDyQOnJpDetL0BDAmbFBOAQilDwpMIiXi7LXNDsKhINPQoI9de87e/p1sP82eqLvdNh7K+ViqdV0Vro9Ut//VEW9H0r+nasrniY7jQrtk0kiYgpqKP/6cASpHgAAAh4y3mlhFVxEZmuqMSINiGBfhaSIo/EUh3A0kaQ2BSgCKSSablBwCI2K2j2cpktO8LnZJ3xT2x7SKtLw9SfufeUa/No19nm9T/+KgJYh/W5paXc0P6XQwHSJQGnSCA+s+BgDCRSLTblWAGWO8QvyG3X76oxoyP1BYrY1GnNsv0fSr9i72L3Vj8xWL+vfRkDDiEJnl+SPXpb+lxkUHBeRTS9bpZ7/9YRkBUrjllu3odiilFs6XgneISOMUQJaoXtQXeme9XFEcHSdZJJXNApTepPgAHnkdtFgdZPNUJU7mUIfYhwenkaqaPSACwXI25ZZb6CJNoPle4I3gHmyPDBt39rXEHlS7Z0hBqEib1hJLN4tI3i6FvYtynkV/mt5BIl3rir7x1Fq9lQSFEiXS/oTEED/+nAEfBEABIIHI1qx7BBsQwgrij0lD4ikyVhsvEfBDZluKMSIfhAArPhUBbaTaCi/fOD7IXKPYLODaIXQr2yNNzazq/bavuvhrQbCLjJAQChr3w37eXDgfCwic025YYFkErz7VmPSAoMxIZbjC4GrQpoRM5lOU63yZNDF3OG8UM1qE0eya99Okabu7N20z9/paqfVmVkZHm9/7nR62MKQUPqjVWzwu5bvIgApW0J9iYsWGhpEnU0CDvlcgvtFODwWT0DfmjMt0kWjTbCvRa3BrjrqbBcZmlZl7l9TdjV3bI6SQqfr/9A5kHLb/7f26FpBTTUgMwhsWuXMO9Nr8vsohCsV9iZ/nfbN/1KQG2blnF9Sy19pyvBmZztl/9lDk0fg8D4gGnEB3GhNeCyxYRFkpt1JiCmooAAA//pyBBVVAAACEyZZVTygBEQE2yenlACI2MF4GMEAARMWL1cYUAAAgEACk5aB2oHZ4coZYvtg+oZZtEa7Udw8QQFR9CDQdXxrVbbRhdf7WPohJvozx8zet23BJgSv+t63n1TB+nIJQZ0gBILTloEVNVwIbdIwpsQ1UelFbHzAVNzvQ+HJxorrro3Yq9uRA4G9qIaXRlUWYqfNv/KA2VSlbPDYSQtrLqxayFCrw9BWBJcJ47GBRNS47c8of/QgcCyXcQxG93D6W+6LdERf5zIUuRyOs7+Zgx3YVKogIg35HKvHICrPldovsNzf+Nl5c9pSHFrGgAJJALW3MbBf9OCOpLlPryP3kOHvO4EYY33E3pb7oNvRF/nMhS5HR8/zRImAkCQaB/I63jkBX8q60L7Dfd+Nl5c9psXWmIL/+nAEhg0ACAIcDV43PEAAQ0HsneGMAYhE2XsnmEBg/QkupYMUWAENKq1SMkiV0Z9Fpuv1ibddb7XgiZYIw4i8g/O/UaUPGCy+VJEjb0El7pxoGYLqU2Jf5AstCyg3WnFlQMOani5wq5bgU03K25GkSVgwKH2TVIWXVOjXk/BHY5brHizAkvLRhpQ8ICy+VDxI29BJci6caBmC6lNiWn5AstCyg3XtFlSw5qeuVFc+FCXg/QCMPFjRF3ibp5rwSICBBeF3s8heS0zPnXeVjNOmX/eyUKuv1uhnLdgOGXHNh7Z11mFvfIrR7BfKgTDv1AADhurNvgIBwzJhMIPLWWoJaPanhTYUG8Uqa0jEyCzr+eLkSt+9Sjtu0ceOz7rNjP+YnhzCIqj2Bd8qBMO91SYgpqKZlxybgAAA//pyBJSGAAACGEBcEeY5QEPmzH0IYsGInQF7p5hBoRUmMPSCic4Or/VwLptfdOjuFXCJYzcYfqOrj1kQC75T+j0q1WZKpfXNMWj33f/o7qqr6o9OqlPtTqhz/KP5UrWoS57prks6tHRqBTTUkTdjbSkMpnQkkxFJzanXFtoPy9uj5Qqk9s1dOuWtN902+qO6g1Xyo7k6qEfOiixY9qf0Lrtz38lWdWAU4FARJVQAgAoaiccmvRYlSkcBCRpuZr5/QN0Vm6BQw5ShW0Vd6EXk6sr16c/1f/o/f69/Blp/7asawdTSq66hSkDK/9c6nQ1z1jQGkTWk042m4IYIwqaU89jZ5KJ0IrQvsKO0+iVp78nWtxaTC7711b9tB/b17+on/9vNYPTrXnIrprZ3DAb79ddyayS3VhNMQU3/+nAEfv4AAAITNdsbDClwQ0gLyjzFCwi02YFDBKaxERstnPSUuEAnbbRnpESYh+01o1oQGm6Ia3/gW7OP0d6n/dSLBzwf2behSdPdHpp7dqin1bd7zXqi2t40E4+OR2XfkSM77q/f/QBAdzsd0u4OMxGD4WXmfFFn9IPxdBWAovVjNgNsesx9gTu2jdP3Efu30a17//b1Hf0/rfGfkC9Typc/7260+sJjhVtQiCyolJtuAE/EOLR47GaeGtt0dVIUmTr+jiftyI9dOjJ6t/spUqtl0kS/gKyQ+KsUwwKQeuIUJaKgIKxU77khmuTalFVqgEAJTbbAXRv7ODKlI8IpNi6pM/plv7YpmAw+acG+Bd9qviQfTZsYO1bn77LZNuzJHOxS7FytuTQV96nTEt+U+t/izeH0xBTQ//pyBBLRAAwB/zXbGek4YEQmvDoM4m+IKNdsZ6BMwRka7Uz1ldAAGXbYwhNmAvMCSdoSoUAIWMVqYgzDp0QUUwPfH/96MOdOhFX9U52t79W5yLr6X0TXJTqlXr/5V8V0dm7iX93WFy9TLlkckFbmEcwvlG6B2Y6Y95d6Am/67qNuvKDD/RN7FRhUplVS20Te+2r0W7oLDoodB1IEfZ/oYw+VDALdlzExL+gALa/+g9MROWGxAsf1npz5XptAC3jFrsCjILVuAe7or7n6dD3ppaxDao70QrJ3Z61TNen4/1k6kftva2Y7P7PWi8AK77eAOlYOih8MEZEblUaSRU3kV/aRYpenTJTGb4E+ka+5eTxX9fs7b+uz1GsX+k6WSkWly45Ys5xZFPVaG+YZ+3inzlzUxBTUUzLjk3D/+nAELcsAAAIWGtuZJROoQua7yhQi8IioY27npEXRFQ5vqCMJTgE5ZLiNVsR4Pjkwfp4IdI90h/RnaEgxiyti+6tKIpV9BpZJe2B4uIHCrmFghTapAUJSra3XvJLp+q9RyRbs/DrlXaAQBkQUkt2O7CcxIniAxGqBmqdHtzj9Fb9fJzNhFS4tIjcQZGXo1eqt6/O6egjvV1xX9rD8cOIt4swiLtA7mOdpBK1QhiClJJYLHdsyPfWhRGgT50ztj2YuxjGae6+7xK++NSANbZRN0PWp3UGkFwDEI8Ryyqyytz10/HLPi2mz7LzoCUM0mGpShAQpKTjcrWCVNJh2fKcN26NQOnfr15gFcraj/LOEpW/ePQZMl0MDoZSK1qENY2swLadA+NFDwNNY6HElRO+gANsniEhKpiCA//pyBM0rAAACEEzg0EEfPEQDm9oYJ3mIvLeDQQxG8RWV8HSBDm7RAmUS5JLgJSA7D3yVwaayNBtV0539qef/HT6pzcAzEvcU5Of+xBb5p/4neeia5/xGXHWnkKFZpDxUsPsAjm8SoSkJAlEk3G3avl0MvpfSnnvyeb48jSIywh6mxwe/7DJDKVxEONqcJoSWXPLSH2OSXUxqBz1220py1e+3e9HSUDG6oS2OF1YH2aVlkulmhhVfeqZA10ZNWco7NsbNPpuN/BhJl44YiQWZWQfRXWpgTq4OFBYRCiaKF+14ybZ9hlGWFW9hIiiEk1u84lbYQCkaRblkueaBVxVr8ynAhgSroyYcMu6RHEf00UR76D2t36i0e6CtO1dhVtV6R9SZBS34xX63IFCEQkz9B5Yq3Umpu/qTEED/+nAE52wAAIIXHNmZaRLEPqNbJz0lWAjk13VAmEFxFQvuqJEdZiAXHILMEgZaoVYUawYDYID1nWK+rFmtO5AoPNjKYIlEU9Bkx/8TXxQ0cAAqWcZCwUAoFKPHBRWn58YwzYn3NbqvZ9IBACnd/9epkxUPQiMNLaVMyxrNKgx8k9Nh5kXZlhnQP76wofsK8Y0iSEde9l5ansqvdiG9dT46lP1/Ip/d/rCQAQFMtuWmHpHVhNrFltuN1QU/BOvN5agj1YTwgjudoZyOr5/o+23O91b3NIdOhxMheKHIDSnu1DxEuIx5q5LxYTw01Af/YiSCSbcsH6fsD5cjq1WO2J0q0EOqhQ7oVC23co9i5yQkGFvPtvQ4MMNb4WAl1KTes0UJpWlHlHxoGIh1Nwh1EtDdj3WaWJiCmooA//pyBFRnAAACEEFbkScS7EQDm1cwxXSIfKVw4wSuURYOrWjzFSoJa8igmGHF18M28QCFtCOyI+ziBjONd5ptBmHKGShi8QuuVPdtlNRctU2/9LJ/rR73IQ6EoYUwhZYVfFXPKRYbb5OBgBTcusFVwMJNRnIiagrsc6dQY8KdDBStctDh3fZxj5h7duGIREEoBLoqKHavvRYr4oAijYza2rJAQDgsHT1tRYIJkCBAxu3cG5oOmRvirgWB1+4/bA0NjIO82T1NEw4AxaM10DEeuhn7+rb4hETWogE5W/0ldR8vMoNj6kTcyoXsLQkFyY06ABAACTlu1NPi/yCmP+tQ2CdPbuEf3rzz1V7Q4ts1KPUt+2oZo5qvNFC5b2w3cNP51D2Bppci/8ukqKnlZa0XM6Y9PUt1SYgpqKD/+nAEjKsACMIKItwYwROEQ+f7lySiYcgA/27klHWRD44tTPSIugS5duJScGoCzQPETANf5ZmlPBsxxgfLnRkq2wXpwR0aE97giMVPiaVFRKa6lGlHaohewFRYoAlg4z4pUTNqc/yqQEEIpuQQY0YwiPaQxc1au4F2ZWr4loMjXSnqxT0L1bt/Vnbj7tfV2/7ud2urI323Z2d3BTFC4HRdadXoW624On//8b2QFJTbjKiKXNsagptGda/sI79l387eK5DiWlre4S6NoZGn19W++2VP/+Wujdej9CSwEVosPHZrG4uVMlmzF7n1AFu24etS+3KskaBZZCZLjG+ZN/JrDP+utZjk+7W5uC4UFgkfLNhqstaPe1JMQi00tpwY9MMUqDBAaH13fNF0Iac2ZGxMQU1FMy45NwAA//pyBCgkAAACHEBbPSRADD7n+2OmFACJBEFw+JGAERoGr2sMMAIRgAJScEk9AqxC1FqZpAqPZqhckhVHhXOdjDVDtqx6pwbZ20La2iPgyVecFm2bp/9+67fXdOzsVqi/oOsjGoHsrRHCMAtW7hvk4HqGKrC/itUyZdOqOKoU78Y/P+1tl522B3aYqlb/yablPmS9vZNr9vnXvYmJpETY79SG0GZa6oNC4AAYJSTjtdsAqaBsGBEURsmzqjHgOJCeeRgjoIHWCGFBoaAi49pxFyy6WlpIV0UPyovabx6Q8+pYdeH/Dfljvr3//+XeljkCcAABWWZJSS27T7AZxnQaPJJzjreJTZGV3tBA8HQ8UhOBoRQwgZmQ2WSBR5EvTA2tuBq3tpRXNh/wk1yd3nTaXm3f/6X2UCdMQU3/+nAEkvgAAAIgWuKeGEACRAtcU8MIAEiE14+8YQAxEYauZ7AgAAAwNBsNhsP2PEBHul3O9sn6/0/////yX//yAYu4QQUoCrm/xaNuxzlsVzsUP/1efMRpBTrYCqlP/s5PJnfmU5FFPwlCAEI0Gw2GwbWPMCPqS7n9vP1//////7kV/p/kAxdwggpQFXN/i0bdjnWxXOyh//efMRpB3VmAqpT/7O3kzvzKpFFaCUIAFNSSRtEkBcQRRzy1fZ+7Xt3BnsxHqZj5yvyZ736mtb+Vqvc97/1Mt59PXO+VxCXHQm8yU0s+prrpNP7qywtOpxMldAAABygIMuHEFpVeybcOyq5V+9Uzv/v9X4RBE3DnWlNbxneGlhQDmUpdWDaS89XD8ycNIW1Cwhjr/FUUVvHfRtCrCvWy1MQQ//pyBBNaAAACFjZl6MEUTELmy+0wYi8IgQF1rBhBoRQgLMWWFGAJN2z6y2NJJ+ouvvzoMcDL/3YgdSJhMQ+Dum3NI0YBnl9UQW1y5N1XT8yX/o/fhyue/ah1vpsKDGscWNeFY2sI4d+sAEITS1tkkFHgHjg/g1jmRzPqnFeeGGVu6szTb3+1Pb1RBbXLk3X/qZLr+Z+/DlOvebtRt9NiBjWHSxrwrG1hHDuvWAGiMIpGkSQ0nCswHGJPsRMlFbbHd2eSqO9kDpsi7c7ZXX/RWqVsqXTmfovVfzTe0N/Toql+DdulSyvO/Wd4x7edLR9XmjDxP4z2QMvrLEGi+IvAUVKpthZHhMMPYx2hNMvUmo3rWrGfoUVqVvv5n/1X8zm9lD39OiqX40ZtlSxbzv1hrqejtLR6YgpqKAD/+nAEEvEAAAIYNePowRHcQgarAWmCZgfZAX0ljKGxF6BwNMKINgnXFpbZrY46BDXd5e3cijIspksiHMeortd13YIVGU+Rla+vYxJmd0Kr08pZv/m8GpDXj1x4Ynj5FDVBNGd/9b/iFZQTxywc4CsmH4HsyzBuFDEp+ONEvfxYnGm4HnRNRdfcfADvLIk9UCF16uufXrb3tfS9Wm29ubwYx39Kv0r0lbv+T+kGY6uuqsfwgwI0OTVJkUfNUlb1Q2Ol+rcv8Y2qJZmz6f/yF6U7O0u0LDn0q+e/NcbK25BKBjy3qbZzuiormrAEgFGok24k2fjZyGLd7TsyhQRwxJoMZ4d3399Xuwd9eojk6aH1pOtMzEO4ir5m3PrTUR26N+vh2+OackbzmwLESysXr5ZKYgpqKZlxybgA//pwBDxpAAwCEzZYkwkTMD3oGzJhgg0IrHNmZ4htASMQbijBGXQG+/BG0b3KcGNPtLJ6n+TyXNkNneL/p+zHVe5RMx01SlWKyI+ThGVrdtHzktnq6PUyOhy6aW/ifZa3EP06NT9vbqMJzKwUmtNgTehhuOND9lsq3zDfWgpwcaoA+E13VRCugAPlbBCUpzF7VRPp+4qjLS/b+N7a9+jcL/1cj/0O+kFRy21SBUQTqdKdWWcavG5JnTPDit2tCOHBFEN+QkOlzM+P+CUkIJZSUMfF7It0FRatxHFwFkr70dC7yVQiPbbDN0qdv1gwAwF9bZawElT6OcwpXesoz1NyxTrfZvppic8U3MPx7OH1/fzreB1KeT0KF2oekSBuwwCYUFRwlLHnXos9zDqqiYdR2OdlTv0JiCmooP/6cgTUsQAMAhQ2WRHsKMhCpswKDKJ1iKivYGeYrMEKq+/oMIp2BqazAbWgrWRSWqGWGpyJrD0LfK90CRBYzDWomo1k/Cm5tXsi6H1ZftVc1VQeWqPTR9/i9uiKJiqu7a2Vpfr7ea+yodG727I23ZaJQVO41oQ+i+imqj0Cb4DyyUPVDHsBtRDdnZUymlPu2T9Eo/+968VFrGSeK/ShQidmzfXlq6Gp4mOjywBDkkhTtiAC7RxcFp0erUzq4s38+ga+56nD/SBB8h6CrUolXaYC8z6v3/uX3apbm1ONXceJoeHfkMy6EiH1LbZan/iL8kEjNybbbcr5j0HctZULp/bW6SxOQOF1Fo+fI+K/nMjvl+6D/O737iP6/7Sov/6nZUZ6uUzqzN95klYlsM0+zY4MxGlMQU1FAAAA//pwBAOeAAkCHhzXmekTMEIGu2MkJXcH3HNoxIxO0RkRsPQynZ4AGOSQqzeKUWxWBLlbggGTbZTtUW8OtfDjSb7D9jsYpI6O9U4MfMO/vBj21ySTY+KqhAhplJZNTPq6PTec5Jn7ck/1oSkt23K02BjWC/FSxu7Wzp1kjzqMWRuXX0iXW2D8rd822WhS2elEvr92QSZHZBFKliEep8VCbNt5VveSSz/MD6rvrRDaxAkD+Bcy2K7Ulg/9Kt+HW/a9ITLBM+9Xzo2nUfqiymPWr3NsURBu0QilLBkVuJkmcw9J0fkkexCsg/5LQi0G5I3Lrbbo0D4ZTuKmHGjVzoqwvSXRN0dd+W6risHSu98cd5a4qTEpjS1rcVmxGAD4uppmlK47uuIJYQTeHNF12l/lXTaYgpqKZlxybv/6cgREHQAAAesx2jklE6RBwjsTPKNiCTSbYmYIdJEapu8oNIlHEEgk3JJjVgM2DsmiSItZxHvTfsZlpMqAmgr9lerWsF6PxHXqb1to+tUuv7EBOBWx8w+4tVQivs6SpL/3/aARJ/+9ooxkPU+qFokDa4XhmDHhZsatpgxbSQ+AesbSHLZKrrcNW7i5X0AYEnSyouDFaqnkEUUzpahImY/xRnNVd4ATbkjhqUgI2JAlYHx0ZphOKSOkOT21zfvLOpnO3fZfGb9hHZdvsNBTMRUSYDbeAhhV9U1p/EY0RCcPJkVXPDjli6TtF1FdH/QkAHakW3G7q8C4Ob6loUaq2SqvHD6Pk/2zy6vqfr/Qfbbda2u6015JztXidObftpob/9e62O5QpAUyLlNoFrldIdfj3oOXuqmIKaig//pwBI3uAACB/xhXmekTMELEe0MkolCI3KGDoxRxcRQObCj0lSoABzf9gZY44ogpy6VI5XpNlyewyHlqx/XNUn46WkFjY/6y9JAN6anX8MrairUd1SQsy8M0a0C0UXr6kOblb/8mCW5ban2ABdATYKYtbCWonOlaOCS77+6vKjuV/w4SsJvNqG2pK4dAyDOWUIB4TTEyXIC6ZY0QR3IYHVqedIJp1s7/Ui2G5Uo5bbd2zz0lY3GUwoN3QbhJPpzOMxEf+TlGfzhm56xCkMz6qXphnwmlPNJMDqpwi1V0sZf8jcoaKhUHSb3qkmNXd67CABBTkkV0FUiG2Gx3EgVxxmyg5FVL9CT/4k/FEx9MReVLixH5v6hjdVILrh57Fxc6vhbFhwqZfYsCtdIfVYWRIIvTZJ60xBTUUP/6cgQGMgAIgiZPWpEoEh5Do5rjPSJmCC1JbUMkQbD4m20YwxSeAn/E07ArQPT4XpVnDWJwT8Eyo4otXf7p2L78YEzPodtev8euMYyoRHR5iOGM40+7bTHYqIyVndpn9tdCrZmqDWj4TW/IAJX7hcOayB+jF7ITFFnwxwpUkfW8d/D1J5azZHAZQFxD2NR2asL1fCBWgNCt9eltNOMCAgTHlLmBBSn5j/uF2HwBbeYICSacAmLHey3hi2t5c8/RPptOM4Kj5PpR6ETlfTRhq/Kbep6YpZJAio+lP6+nft778jtWzf9EVjmdnM5h2rsJrUg1WFxDhD2oWl8ZJllg4Z4H0FmZDT2BdhiX0p6P30Mu39TF5J6O5bLVP/XtuQZnnHWcUeL+vexgLTdqjqQ8mIKaimZccm4AAAAA//pwBN0UAAACIEzb1SCgDD7J22ekiAHIxY1xWGKAGRUxrd8SUAMCAUQUm24FNsToGHJFrqsC63MxJrRtjEO/+kjqaLleZ8r3b196kOca8wUZUZbt/pazotTNiFv5Pb+nrpsy1bFZww9MGQFElFpwLbom5IbmTUhqyt+a2CLZllkUfZpfjq5GLo+RqMb/b69KJVXJp9OT/k6//e1+n37auUxiu4Q3X1+hkAAAQAlIICAQDAQECzCxkBILA1FCRh4NIKxCFY5Gd3M9B6OtV9KPSn9f/+v///P///k3zn////yE//////ZxAUQOCgHF8x4AAhJAASAYCAioKSDDSA69UyymgTnmJEdSHFLvR8ev6/uZldFq7L////v/5///8m+c7////xQn/////7OICgwBAQAxfMemIKaigP/6cgRtrAAAAhon5W4CAARD5hv6wagACHyRhTwxAAEUjXAnjDAAAAAAAEAQEAoG2eowGg8xL36BcR/yfPGZP/+QMhw4Dhr/+JTFKjNBqgWcLI2VkA2UUcaYwJuPGvrB1agW//Ii4f39WoAAAIEIBgUU2igVCaDm5F6M3aAfkI+/JAbCX/FtiMf/+K4yFhDf/wK4NIhgGBBA2f+3MZCSf79wcNPGO4FX/3+wXT/1YAoIAtfwg4tzI09kMs4jrI67nmvIv1ZCvR13u/oru9UeCe0UeCxo6FD7mrmngFZVSnpcswDTEr3vekAIQ93+7AvzVWuAAAAAWqHBws/VLIZjRsPwICOrD/Huexo+WZ1YrcZfHLamm1kSz6g6VDoBAQqYXpDaDtzCM1JDB17aeaOOMmP9Gk67pdYmIKaA//pwBDkRAA8B3BTeAasY0ERDDMwAYgmIXD92B5hjQQeMrsD2DGho4QVBShlJw1P8Jd0MDqVKlUYoG2q5+YIuWAxUXFiLUDEFQg0SCBljYmFxg1NzC6YGx1uTdT/1Iaj87pASdijffTXSJHzGYEzthWYQ9WqXNRc/z7gkJi6uRGhKVCAQq1hUkxtCr99Nccs0FiB860VTFWMacAdz1XuK1pPOcjamYxI+HiqC0fdRbeRALB4RAmldKMVAAREBUmIAATDp8PmoqHHpSTetZZ+GiQXSyimJZN4ohGqcYuadd2GER45OwVV9327wmR0rIkN2CytiO37IVIbLyzrOI9alhZFwEmTCjxs25QPOvB+8k0G3WDQG0dum1nVpVc7YralCjFkUuyYrqU36PrTEFNRTMuOTcAAAAAAAAP/6cgSWbwAA4iEr3ZGBG7BBg/uQPSMaCLiPgaMEb2EHjG4Ex42AAWVvq5G0jLu7SKd/dgGcguC5TJ7lI1+cMyWUvXlzMGbGR9I8terZPOjIMFrw0Ye0RV3vVjUitirm+o9R4T8m5CK4uYbfC+R1D4OrtxS3V52qJRZEjbHKJol/hxCFFeM2bhZkeX0oYMDFGEs6GhZk0iWvOoe0BS957jYcm1XC/gZlHhP/r3QAgpFMtNsJSjvj2+tT9tzCwpRcUIirgqBtSTIpoDtcyh3Fd6jAgKIA6VUgCkBg57hapHu5fF3UrFquQ6Jm0815ax9ddsOntBpZOzOEtxeLes1s8Ejwii+WIVSG3hhl84i9NIM0ymaYoONeOPsIlZACsUnVUVpuM9d4TCdN1x2hD/AVrLFWPprTEFNRQAAA//pwBOUlAAgCHBjbsYE0MEQha/0EaQsIVHFqQLxBwREWr/Q0iYwAQCtVt01xTJwbIbJRaSRyhnIsyTmFxhO5YjEKATsu445+MPPFnLMKK8kmeJCA0tMisOmwqNcoKLi4yKdDE+vzH6PW6BBEiBJKONSoUIKaD6C+S5QRV1jYxzAMMFqkvVPFpUjQu2qMWTGGnrARGTHKaEng0hzi2hrFfsrdO8kZtRrTZeSMFi1YE/+sFBHuQwsncRsQiCuJpXasGbXDuzJCvBIzZnq2NqZlENbjyJg37ryDnNCRsRLQsQtxiDyj0l0upZ6Y7zFfZMf78ACiMY0bdduNMXUBHsGsLugkpRZjZWOaqYSnr2sXbpa/t8M2s8q7FlbBDbnRaki4alrxAp8yMhumgKUjnr3B1zd8r6LkxBTUUP/6cgSZUwAEAhYP2xgvMGBBJrv9GCKPCFDZbOeEU4EYFq7kgIreACcltTliUF4bEWdjgyQolXTASfKAHduI9RiMqG0Kc4gVa+ywUqFEsjJ4MIW1uT9ERGqUHmP6VjolfIvTzlq8nQ7TuwAAYsTb+/36jux+FfvP+enDIqL4xV0EnGj5icwWLfvNp/aCjtfouVJkKUmWnjDo02KIbfb13sCyUpajmBd7dDvXACHLbbN8tBuES1LNZktfc1IDaY1eq9UpmOBQAfLyANEQyXeXydTjpGgJwUhnSFlZxdX3kBmSlwtRcwrt9H9h36P9SACLIzK9RmjQ0UO0E/K0yFHWyps1cJM9irXU1mYj7+3QSeVWbkiazborDxVVS3h1ZJxoi9wsmeNegXR/elVCpslaymB90wmIKaigAAAA//pwBHsMAADCFhxc6SUbyELD6yJh4yoIqGNxpbyogQiOLM2EFdCgAAAAoKSRy5qh4DiVMz0iTrofR0MIwr3LdiSsUGRU76yGB9sLEWLaKhkUE4smols2qQWvLMpNryNqvTZpTM7P4V+oCf83jEIOipFRmMWyHFwgN8GQxVcD1YcIlXGkDmFMSmVAgq0TrGIGfgLVC+H9UcwzZSo7GqSRMbSyVzyjx2/TT2emmAAAAABOXb/7kgTwqTF86bLVvrd3CKW4k930UjkxTVtNULBroaqcSLCoqMERypYYY5+s4prE7x9DFZGL33FnLa9Hs0G/qAKUktr/JGnwYRMeGDsY+7Px+pUgaNlnmUBhasu5WlEWurYtq7MAWO42YR3KU+xL7Q3dWgVYbDak9XXfy2iRT2q7UJiCmooAAP/6cgSnWAAMQfQjWhnmK6BB4xtDBeMOCOi1amC8YcEcjGzo9AnQADkto9IZzDII8zWnDox+er7ZwF2HG9TFaPLctsstActdC7M5Qz8fr1XmHMJUxKSMyO1HQNhNKZJiaZb+nT6QApbaDTGcQ0gyUjWJFK7CZjTQ4YQ2umeIG7B1kA9oDO5ep+UoC7Tm1wAwata30BR4naLnjgWsMTRwJKWj/q9+1f1EJzb/liGquA7Yqby1QspFbhL8MJ7V2u9ounKcJcAlvxvilAtI/3//3vO/Pn3inHypDSwYB1GhsjJp0umWbuoRqrvV+nT31VwAABmzbRrsJHAyDpO2fpg0JUyh73rAi1iUFp5cnMyW5xRoR7NQMnWGQFYGgM2Hje8t1kzNyJyRMNTJLVWQUg19i3U2ou2fvTEFNRQA//pwBGJhAAwCASxZmw8RYEMDCxM8xXYIrGNgbDCugR2JLaj2DKoAqWbDLkqvu+TPLJLbnMXrlN3MoXUt0jw9KSyz0MDZ6C6o/drzzG/X1eyaJYRMfvvc6x32mRedHWyfuFmDv//0AAqWUT4hKkEMM9XrbG7HC8SJpwjwTQDKiKBvJ6WL1naE+LL59gFskWxatrrqpI23rNlhdi73AqaWxUr6GR93bV3aCwAKctFvckkKT5NpPzVC1V++1WTbc1nJPk+MQF6wzH+vNvQqmrztHNyuB0h25b1pnhxhQFYUlCNjfSaU8Xu2j2tvS/p//rQACAEJuS31hRTeGtR9guhnfXnlWgDWUpmJUCbXGswDWfCkhOoq5E+oehYdLMW3fXa8BCLE1Fz7Vzc1pOKKqWypIqIpXfT/1JiCmv/6cgTL4gAMggAcWRnsKVBBBkszPMV0CJRzZmeYrhEeEu5odAi+AKluAvTIzwXKFAWGDAkCVdgKlgjBcBFcqEjjliFhOL2ojWoXRnEQG7Y51OtijIcjQEhoFcy4FlCq+S5axYv6LAUnd+Na4tdASzFPHViIxhPZUzmDldwOT+qkJenjmFrLQ+6DrA1Dk7Iz3+rRjVtJrPoiWf7YyJ0I0ZOtak9N/Z/rACTcg3ddi4CnoQvppnQ5KUXC3EVJciTysL0ljNGuDGepcmGvS8My8WURD3l7YRJHRaeYLkjzj+xdFZjvNLYfaij9ft9YQgBJpO5UIAqGoZWCvByGsDx5xDpHKo971LdUqvtE7+VLqmftIxjiAkdUg4VcKNeXYzKtbovMWMb3argdI9fQtZM0uq5rkwCmIKaigAAA//pwBHZaAAACFhlYGDhIYEQjnC0MJaGIeEl7QwRK8RITLQzEiSoApzYADfRNFZaoGNxWMKzvP1skr237Jw6TFBzDdWd3bRk9P6KWTukyT/dtIjgmq4yAZEiNHkDPW3L3LDxEZofePdWugwJFmJuyy3sK1dBVleRyPD0rP6QoLs3urNASy3ILbcYOEBoYkWNG0iVzy+g0KXkrUVYtrWDcXImW3OXVU9/V8ywSf9mCwNEluOXIS1SiJfSCTFoYKHVlVQ73iSLttntM4i4qBxGswImlgRgMi4lZYmfoO5dg4dP70G8SsBMAMY0MCJ7tmaqxEAU3JRWpSMj0MFnnY8D3kQp2Rgkgxh7tkpJO9Td8TVsXqck6uduezXa2VTs7lGiLKqLuGlOwiprZ3Wx6p8b+VW4xqQzKJiCmgP/6cARNCAAAAhcmWT08oARDpJutpAgBiGV/cjjBABkWMnCrBiACgAAEm4A4WYC/nePFErTKhSMrCic05iGmcEFx5TGsoTEVdwZaviyNeovt2dZEbfTIiqOZGEGnMiiW7JTiV4aGkK3J1kAiEEkEtNOCYQuRMkVay5r0gIscUUfM2dT7t6kepiuI/QysIZnvphB4OjXXMY9Cja7L1YzuKRZAXbf0H5g7e9JlDjaQgSYQrQ9QxOJUTFU78PaU5cDFEMJGla7Tyd+2y+vnV//rxBCT//5yKchG///FoR8hCf//+LO5CEkILP/////Fv1g4eAAABV00oCAgGBAJi69aEfTKb4N+0pe1yEgiv25j+/Of96/yf//uRTkI3//4tGfYhP///AznIEEIEEAZ/////4G9CQAkW6YgpoD/+nIEQgQAAQIaK15PGEAARQVrteSIAAfo13SkmENA/Ilu5BMIMAAQJYQAO9DYkxuye/u8r1pq5Nnwbt0b+jUeVFasqB3OkisjNZbvsUKcchooagZ6nVsTSOecSrfkQCHVP378Uyi6OZqBMoHTsSJLqZlZK5XeGFuqa+vP3ov8zUeVFasqB3OiEVkZrLd7MUKccVaKGoGepzliFLjI4ecSrfkQCHVPx2/FMotiOZqALjbQD+AgrCt3WlJjYwmzKz87QY9+yovP+rvte5qtV7M/TqyI7oX+1W8X1SQtkG9EyEjyhZoMiLTvi9JX6GaAABH6ABZmPBTZVbb6umH7ItgZjsUW2MLh9QRPPE8CmulJ4kt5jyQobZ63S/VbyDfxgjULNBkRaWXxebK7NDNCYgpqKZlxybgAAAAAAP/6cAQxdgAEwfY13CkhE6BCxBtgMSI+CKRRZieZDoEHCqzE9higgB5u6cEhpEkydZYrnjMaFmY+hooYNoAKOvf20crc30KM9Ey0/f0dLfqVk24gqGk95GonlNIzrGlvXI5Zq/5G+icLCiTolIfddJwPZNvMavPhf1jO3tDTXO+/0VK6J9kKMPFJ0i2klIioB1hqiUKtT3kam/S57KxoibfWJiLpZq/5FJ4+oCdOkma+6RiMWZl2wF7cz4OF1olkPIkNmowugGMZI90Ezy48XHaRx4lixrfvU7EttR3tkkZRVWpRVN5Q98r60t8bUN8esiLLySMaTQinLzrK4Da9kgL5uHKdEZDGqF7xAdr38i1CXPJARlJ4KBrAW7S9TsFbYsd+SRlNXoTeUPfNev9dSYgpqKZlxybgAAD/+nIEhHgAAAIZNd5QwROoQ2arUzDCdgi08XlEhLDRDCAv5DCKFsAgpMltkuXhaCWX04Tt5rGHoRMeamkjRV0fqmzk1XI9cpUXBr6d1dehh3q1evR+GJ0dSgMfu5GaQpT0V0qQrF3+gjWEXG3JHwpLg7AdHNwnPY/K8kUqOKfai72htx3FtwJP9QJ3Vrj66oW/XuqvzTX6o+au3+jtgn9vy/1MMWsT/8nRwy/W1AAIQk3LZN5dNhBfEMZe5u9k1wmqeamtQ4ns1bFf0f3s+v/9NDiiU22LzsuEgHe9yOMWowzy0qDxZrzrySNy2fY/t9YlzcAK1HfVZwHQoR8HknA54aW6bIILmpY7FUOUjNjfmHb0YuzZzHVkRNfI3/50ckMLo9cL6aI+JP/BkvuijfkNDbUax/WmIKaigP/6cARWpwAAAgQ2WxkjE7BAiwvpDCJziNTngUQEVzEXpi8kYYl+Bcltt9jIFADi6QLBNhlNddQZCDZSHhVhFQa7EhtHshKC/0M9K2PqnW21fMVFoXVbX/iq5Bsi/AX7ZLFxn7daPrwBCpqurSVgwJTCZ3aJ0MEVUXKD3zNSZvFdWr+jG91b0TrY6A2YyuzpolytuK9Nta5USwY33+rP/4J/372yZ43rkhHopxtpyrqhSUca03xcwGuLWPjeeRvxWusiYxcoyjhcIbUGjQVt2qIsgeR/mg2aklgtbPrs3jA86DrMhSBmnoldPaDVKlwAhS11N5USYBqWn8r7eGi8Uk+xwpZPULjIW0JYTZYoafsIZGRN8v66oy5nTkstVcikdKbmpm/f//SZd+KGP1oUX1mHJ9ykxBTUUAD/+nIE3MwAAAIdNdzpgxQ4QoKsXQgiD4h8aXTgpGDRFJrtXPGV4AAAAKQI5bJL0oSCMqWkkOvdAucs0fxPXgBFzl6DPo0IzR6Ro6r7KjNbUrd74hlT7KlS6L6936iV+L6k/1Oss7NfId9VARZUjcllkmGCWZiaRhyFCDYR7tWcHg3TkDmUuOd0YSU5BmjWkDnA0JSVgAN52pmIc89OulQB2XO+qqYmyeWHOKjWyHDbkstUYYCDXw+eENKpYpd+J2rJ54P2bKXs1CIgVBQaWJk7jg8POU+koTIe8Xc84cmB+igb+ykqREgECuu26sO/W6uAAbk2202GJkC0Ig/p2a8afDahrLSBWyb6Jcli+DH4cIM3DYH1ekcpi53pVGVU+/a7FHO0vqq/oPywpvnnMp97HKHu/8smIKaigP/6cAS5SAAIAhwY2JsPMUBBIxtKPCNyCCBldSAlAbERiSxMHBgwCLckg5p0n9BTnHlhhAXSGqFTH6XppXy1qU9AkNsevYkGW4KPZBL7rrbeYNmVg2xyZFLpB3GtVTQ9TK3/V2t+Fmq0/pwAAgNObbbVmptAxjmMzSRPOJNaYvTNc4wWSVZuQyFgx6Rx7HjGQBMjQGzPJQ673B8dJqtU8q3lt1t+jVNVaf//QEr1VWuYACf+rNvl4OyxzUQO+6H8xHeHBykNlW4+3xgRwDTyL50Q4YQpDNUwpboq40ucZIpvO8GO9CxF493m7qzIAUdts77JCbisa5IObgt6vTYRxikhrroRljzvlD5F45z8SOLoBxLDZ9yGvc217KjBV7FGG+lxpbjhLf7xfu3/Ga/fp+hMQU1FMy45NwD/+nIEfdUACAIgK9gbDBJgRGScDQTDB4hsTXTlhFCxAZXtyPMUpgAXLaO7T/ayFWM6fDa+40BgEa3AerLwhqNrtUapgo0vlnfklWfRbPMp6mRuh36MnujznZ639WQdzOTRSrX1bGnvsZ1IRASJVRbkjk03scb8OXF4LOOfcgJwEfpnD88Rde8hqWTfD3+e37Q4ovNrtToj91YyXuVqFQ9G21t3WMZQ6OddCtbnw/0MCKTTd8ilwvfFMONS5Q3Gc63bs9p1QuDiDo1ti1Gj5WbTPo2lwsSGPNIOLJXmGeXCx5hBRw6uxKHomgj36kSrvijlLBma/22QW1wAYYQpRzQEK1IlHR3Qee8YG50fraVzJkehR7bpLvLbT/lOQaq5hU03S6VttWpZhnrF2iyJFmjp6fUmIKaigAAAAP/6cASqAAAIQgoZV5sJE7A/glsTYeYmCCj/dOAwoREYkCydgxVaAAktA7tnsXBiqrvuY50fULeHDjVdwWuomUas6bfYSkpaIkf6bfyNejMErYKqSdVcLMsNT5RYxI/YpDnmua+YV8UBKl347m8cpFXJSCnujCQCrSMjekwOSKO+bWs8dnStS/DHggaYLmS+MEjFuoPoACacMrYB9L2OajVoYjJEfVaswjcu37CMO2EOrL7GYeqGNPV9/bTEnePOZEUwKzJy1SbXf5KafNZHs963ynteI+3Otlblcaw0pCPtCwVTvk7lyJGAAptyDLGrTCrYurbF5XAtSOUAQalFU6TFyzva47E7G59WWCMkzK87OjoOaJAgTDIoKPYWVrdHW7VWprJuOhf3Yrioq930piCmopmXHJuAAAD/+nIEZoAAAAIiLF7QqBWcQ4mbmjzCK4ikqWzlpEORFJVsDYSJ2sAAqDbckoRxkBHEXuO0HUY503jB5ztJo0hNr6mJYL/zzs5C7+GejNzeomYIyEPSRhoLPNIS82aCUqsKI+uhhBBMDexCIAAVBKTbg96XJAmRIeFyX4nsjrm3sWexCa5MyWXQfTV/595jrr/y2Z5avXe9i2U2XMh38ztV0PJXp62O81EsEae6lmACBJyXYcC54BxCVPnEidL0P0kG37CuCsujQbbKjIVUgw2e6GV6PpU5LsRWZ6vbHzCqN5lYTIjAKZW57RgfOOKsHkL/v9AAKbkGeppkxnSp55lxvs0Jv4Axzgecyb+HD3LvwpsRthiUuXy+iyqlW76T777sjzVV5X8eeFQgRsQm9A55EY1viR9Xp5pMQf/6cATrKQABQg8sW9EjE8xBhssTYSU8iGStbtTBADEZDqzenjACQACIIktODesSGHHDVnjSfVQWmJkUGWZsMavVLBlWLQmK6l96Onp3ZP/6skLPqc/CO5ius4SKpPBCnah7w6Oe3jYuESnIBfrxKTByEOy2GzkIQAgF00IgVTLOtnqvtLcynu0LRQ8aGSMpt7XqZUrp3pJ/9frdJt9mfxRaiQUKUUD3in0AGVqbtoHlwaLUdRQTQ5tCoOUQiXVqYLAXu1iu2iiOlwTlV2bZjucirZat6mgjoTAtzLeKWJXSKUsIuO7bmnjQ70TQ+AJJyWjGcIeBSSQrlkgYitXvEQn1cXp+ZufMUxYU9UREZI27ayco6pICAwBg4KqOlKhcwkTgUQ7juZZcnePSkilx37zVV60xBTUUAAD/+nIEPSgAAAIgZOYeAUAEROnLscYUAAhlWYj4goABCCswyxJQAgIBQMBgMBv/////6K6p/+SERJUp//gVBFAFBMHhUW///wXwa3dT2KCx///43UfjhQLwFY8YeG/////k40WgqEhASMP3BJJ0RZN6AJv/rUp6xNqjmfvqUTVVTD4o3+4cFP/kU7J/+KDBScZ//gQWAQUIov//+Ji/egp///4+dkFAchT//lzkMCAuAJAAABQMBgMBJdGg+qJcTirxL5DeZP/////6f/5VOMHCpdf/nOVRY4iJin//7WKgsGjRgkHP//t8WAQUAAahxixcBAAMMMMOTwMgvBRz6uxx5rFeQ3mRP3////+n/+HSiYUOFS6//GnKosLiImKf//szFQWHjRgkKf///FgEYBBqHGLOEExBTUUAAP/6cAQdmgAAAgUa5W8NAAxEI0wd5YgBCJyDcSewoYESmvE0YJReBcbc0sljRJVDwwy7ty4hsx1R2657hIekd1/++F/uAcOYxswHRwHLNKoK5UJ38+nW5VOP5XJfWZhm3pfVTdy2sSgJlna761ootMC6TqZD3bq0DNc8tq/W7MjNdEbpInWgOOXQAjZForCU7hNqeXWew1PX0t52t2jJPKPX5vFLw1rh2oXOrAADG2RaO8CwcH3ITsLf8xQJnP1ZIrq1c7OeYnRHZ1IfugSb96lcSeGZ4V0Zax+1eWK0L6M19jjwvkwLXdLFssHlN6WpaECUJZHGyiSnCDjCow7jHsLIwk19pmSdTNdEMZxVj93AZm/nZxLTmp+21TLSb5/8PFaF7DWa0OkXHhfJr7pYtljSm6rbUxBTUUD/+nIEW/YACIILQF1R6BBQQIa7EGTFagiY12hsMEGBF40smZMJoAAA4DttltSQIMeWLMZJ3jZAtWic9W+3VP04OiXDjGtmtZSsrPVqW3r4NLGR7c2rpqJ/+/9wbqudooFeurTraxH9Q6QeqbhqVxtLWcm3onbsZqDjzsFiwtXL5mVkcWrIY1aoaSpbN5X8BWfX6qWVO1Lb/rsl/c1RWlFFf/JfR6m/Z9oAKckgE8GuHAnrRLUJUFzzjgCI2c5Dhdm4cY1yB6KijytUX6/T6tqT8btbqRtv6wjtYGIFXxlbywoxiHfYnPaNXl/211EA9bzC0AYy9Yap4dkVLH53CNWRUwrCBrACv9KX2nTVP7g/Na4symq/gToA0h9uKJvFa3UdR3cnb0VI3bDFg8t/8/ZySdSUxBTUUAAAAP/6cASm6QAIAgI12RMpEzBC6Bu6PWILCFjZa0ewo4EVJu+ohJVGAmqALsNJQLhAyjs/DOUtsTcQYNLGlgpt1hZuv8A89VBQR2RIgrDQYaPlMlzBmaiAy6t9D/9Aumu6dNcT9ft/+gBQ7klrnNwhSOTlQodK3o0fDikEvwQ/q2gIkiZvVPK3+7EEXv1m3WrHZIN6Hr1bH12//19gbfz1cwJO9gQ4qQVxEwyEAArltpbwVjH4GF5zHflScFV8wGO/DkWrYItsUy6oWmIrbGc6CY+1Geune/IvRX037o9Wrj9lty00ffm27ujdrNfQAQXRSqSSZYIHNjK6vaxsyaDkQyQ2mX6X4onGr4k2rkQxRhw0PlfpvOn+ol9fupkrQ7TH6NdX78v79+c7ba41ucV4mdu0piCmooAAAAD/+nIEPSMAAAIkNeJooRQ8QgQLiiXiUwhkr2lGJEzBFRiu5MQJphEYCtG3JI5KpwsEUyqItaIPhoEGVNo8fdeTk5QnmI/BP78hesv65Zeki1TneXF29QTIkizaEahzjhBrPwMjKuiwyszSsAAcC1InLioXFCFARu6tY7Ip8uJZCtKo/ZXbq09ANVZlEereT4zLA1AWzP0+ZvPUo0bj2zYoctjlLVqqXw033MUq0AAMQBJLbXHzGryGYnbVpDPQJESR0CFXdLK/3SF8+CTdkW+X3bwwp6+83spfl0QE/qrChMW6n2NVZUypSn2oT+vkP0gIDVL2t81FG+3WlJ9/QdN0oNTKoSd32on9MhW5L6l9+8E6Krqu8nRWq/4YAOlWOj1Cqat5xZ7poV2bFQ1YAL/oru9BV1SYgpqKAP/6cATtJgAIAiEpXDjKEbhDJltHJeVFCCRrXCwwzNEPIe8ogInOAOkdLbeKRvTRepq0TFA3oNUVLs3OLc1Fvsq038L3eYjzh1g5Lcr+oyShAWazHh2ectg7KqkhJp0IykKj3fU6VDuT2PaAgERNNwHhKaEg6u355Q6Nn0CPcI0NHRlFeYYGyNUujht0zeVOgkzJIXyuj6dtvEnLt7LizxYr1bhS47mtz1P3W7vlgeia7WrwtlS7FVn/kErlgkvNDSmwTgn1+KZ12HnTkZ4K1c5nVW02YWEdf11rJEK2VaH7lijrqqkoRbvr/1Wv1/0oSFKUW0m45aS0TemL4GAkrCdarwT9V6RLuXG830G6v62TXUzK3RG9OnQ2Mj/6d6VXSmVBqgEt9RdiDlhwCHk/UdoMzKYgpqKAAAD/+nAElLMAAAH8NN1JJRMsQ8ar6g1iU4hRL31ChFixFhSrzZSdmBBBkGqr+vHLOFozlESQNOw9iCwf4/X3o3QBk2Ob283ZK1ZRSaFP1Rqa/SC4stElzKW6NlT2jgDQHiJ/QRs2vqB4P1NNtuXqoDNT10xUeo6os2zro+XujmAmvn9vnb0J2NV6+6v4Jvf6QBphc21zDtK1ejY2ResNrVhhoac/Bc5TkYrmdBB5TUjblGGZUGIzO4vC3eKrV1BqYwfsgg/QIzujBW30QGe+OFpTVERCuW3/Y8+pFf///7b9r8lN4IqIvy2zu+goQADjko1AzR3fbovuT1YnL7CYcoGbkIH4DMrtrwvs2sfflBAxjGQr1Ye8l7KWRaH3dK0Ux37oSwxnbL5x3Ftb6vp37B6++tMQU1FAAAAA//pyBHRRAAgCHhhbUYY7JD7juyMyBVAIwMtiZ7ROwRuabqjDleYAAJCC1JLdetbl5ypg+AWYAhVND306i0s3CherIPN0O9iPYVGhhgnfP5PFCKmEU4EKvcdOBDUirM6MnsTDgtX0r/btqQDl3/KZUFxkdkQj1d+4eLBMBwPOGpM6AlMoOg5RgMGdwr0X2XyDvSerdUyAnXVQ3FS9ins3bvL6bS1rft+vWCErt/WdXQVOFBHeytsiNEvEBvLMeClrO5W6CaChUS3kiz1h53oL9F72phPT6/QbYwOyp+pozSP9z8/UYrewheV/qbt1/1gcFyUm2m5tWK8UnE+f0enY2j5xUpDq8uy1FLZrALelX93yodjdfFGvY4q3Chxcxi6/lexceWp2Wh/PRzlFRZiTFfv7ez+6tMQU1FD/+nAESbQAAAIePeDoZRQ8QoZbmhmHRYg002VHlHHBDKLuaIWVXkiUArI425LfGZogfkyweiiIgbGhlWMAwJvm3hAliTez+jf9P2+N1B/f6sQzqPv/Xu4lTidQyg9FMTJ7OZkAilYAr5mgQApWS0UnfBlUSG7P3tC01cZfpcoLd44Xoo+JIbP4ifbzG7D5ZOna9v9G9f/tLRMtbNu7ZVqy1ImJSP2YIpWp6x3mgAAwBMl//ajwgrLsczKt+sIr2oEYku1IbBjauUr84Y6Yho6hNbVFfM+tyN2fo3k+grns//yPSo9TPnqdTqvo/+3UggUgJSiTroI1SB5rJtEi2npmdQ9qi3GM+IfD/oW+v1+3sT3+Y8ylBB7UlNYzb6vf//P9v/Ga7UNp6bMmxwCGrW59KkxBTUUzLjk3//pyBF4IAACCDEjdUMgUPEKFeyc9ImiIvNNedPOAAQ8abWaYcAYgANEkkU5Wy1yLjX8q4UptDzRUT/81tkOsMtBIv5hr5Pq3p4VU0/T6f/e7V/+b6e7enz12FHYridFuSpelRdyx3oAIgW03JpNwN2Hyq40vdM6+DjR8JmPGuWZgtlDTuzHmxIpwD6y6NooNPFm8Gm9WRqP4b6SzxZe2/7vL5bst+3frr+gERu3a6fjqw/h4kGVc6rVaDsAyuLWiz8LmqeI40MMZRuHbOKT1aUEVXuhPzzMzINlT9cu/t6P6fN/m5Wp5LO7vXldGn//WEA0y2sfJrkihdHeLUVw5F96LXPPcUE3Rpxa1RFqp5gj3xoGzznMIGervdU1uS9D/l+prevqZN1LKks7926VytCf/u1piCmooAAD/+nAElBAAAAISQOBWDEAEQIFbk8YMAIi9iXYYkoAJGCcwTwZQAAAAFpfo3tdttv+AOIG888mlvIy0MZwqyZ4RzQ6Fck0itVfN8UZ0oKelJm0fSt+261bV1p0Tr13pXjFk7AdOPegsihJNuSWW2gB44Q77kpouUnYKLj+okwsGgeF2hq8iw61jGPKm72VuHxhpTnj60urfUAwCU7GwMsoLypZKc0ce+oql6zk1wXdOEw2HDtqWBdIsgkKS4ydyPa3RCE+9scY//+IMh2N/t8VEQEUejf9v8RQ50Ygmn//29rug0VZDN/////bOLyRCOBAJ0/T4/0j+4H3riAUD7n6N/////FDLL/+HGGHYSvn7fFRoCKPRvO/ZUfJVU+v6f+jLdmu8aKsInDAAnPyTDEDi+5J+cLpiCmoo//pyBNz+AAACCA9lbwxgDESA6/rmDAAIyElzLBhFgQ2KtDwwlg4ApJOSWNpEA8Ea7IaqWZs3KpZHGoVVChM8LiJ9ulqeLkVlmIqxYqZGNCRWJLQ6swKNcz7B69SGVce7eLyt/qZi6QABpsbbSR0uAYXnuQUj2vWOgaDgqGmgaGoUD7IQetqHlmp3i5FZZiKsWKmRjQkV88swKXMbjbB69SGVIx50UmxLAN/+0AArOkADpKR3YtMxwTvRYHkE3JuY3lT41+oquRhw+kFXvRCJ+dEa38qCoeKimUms4yppNArktXYWiVhcJyXNixZS6fF88HQM1UzeWb7WRtzgk7hfLvCqAIUaPW5roEQZeH1ESiB8uLvfhE/OiO/lQVDxUUyk1nGVNoFclq7E2sLhP5sWLV0+L4VNpiCmooD/+nAErSYAAAIcIOToZiqcPkL8LyRlHwiE2WQsnE7BHRru9QSUNAlIpvbbI2knXErsIdV5w+JlgJC6A9L2JSEAY6EKONpWrVbSra4rPPYTbI4s8W5xhGAysjxbWZ14oEzukqjdWAWOlXFgIyUBaJjf2WNwuI2Yk79LmY1g/UkA+nMQES8IC2SCGXURWdDTO0rPdtOLHhZ+cYRgMQyPbzP7TvK/WhjpVyl/FbwX8ntGZ8kFqV99qQPiwm/cqwK9WKhM/HA43UVOjwSGrU3Ure66USkiqWVG/pVrlfbVte0VfskeWHp/LZYd6v//HgAIBRF2WRuUOuC0ImZxnd7i/Gz1W961H10HbwlpQDJZoh6P6dKG31LWm/kq1yu/lbXtFk+p/DrfRYHYaBpmyRO+xT+MJJpJpiCmooAA//pyBBA4AAgCBjZcagkoWEMmuxZlBWYImNdnTCTlARYmsXSQiqYAAAGoGElpuBZ0K2oOSms3cjoxVxcjkdRTxg7oECZYq6ORRf7er/bu3z6duyb39tPUX8/RJQDq80MxK/7H6RX1JAIFVVGc2U5ymDLTRpbF78/K10cKpcVODIcItYvGt+DFPS28q9QHV9CV0H+iaye7r6crP/ryX1H+/0BvZqY+r+n1VdYQAgJ1SjFgdqO2phFwRaesnJtCbqTEiUSGWXHAZvEYsm570lRp6P6v69aep39EOXtbrX6KPNAKhWv2f5FEHv0cYf9SbHhpMl+xuSSOU40rt7Cdb953a55qjU1RbL3934FyXsH19+R5IXpTPS+H1T+qW2BpzNQNY324J/X9MpP3QG/1tsWI7EVKfVWmIKaigAD/+nAEBQEADIIYNlnR4zuQQgPLSTzFaQgsU2RnjS5BC5BrmYSV0AAAgBCbckFyDRgsjCebJM8oyMFC+57YjXi2Be48HJqKy2ij3cqNfT5z9DfNJ1tZNXbnm6/t/yP5Jqf9q0bH/v1GaOgAAIqlykcQxWOJcz2y+NulTsYIq8KAc1OsG76xiNbkg6umBt97cd8fltYiQllAwebkGrF/d30JBuBvtWUfsW/0r5kkBpy09QEa8OEIq5RIOmEzYRAteETcZpIO3uHBb77Q77uJ3J5Hbvjj0NyQ9kXf1o0kOrex3Xrve8hMclYhLN+uvYQAaqTdB2msPvD6xVrP7qQWaeVCJRF7KnVigkg5wGfb4EWakYCnVZwm3Vu7o/v4q2e9bpmCOg9U1aurd/7ed+y7v+YTEFNRTMuOTcAA//pyBDxUAAgCBjZaUewpUELkarBl52gIhK9vpiBPYRKbMHQ0Ca4AAMAFJbbUTM4WXyYFpRzZsD1A7d9OSnKWocfTEwzxmtSeJv6v5WasjI1iNSn7WSFdV9UTShEH/sKa/7fG/+/TrLmiRsRehhgMIV8w1/IEfNdV4VqJmnSLDcng+75r3AdxSZv5MeyKi0aVCBM1Sz0kDPHrbnepY3Wu6phjq9//R9n//3gZmLQ625JRFsAqPetLhBkk/2oB1WSIJ/+XW1SK/+al+D5nUy5azh28/y/+j1sCVBmlidZzJNS9qIbx9apu42Qd/zTvtACYE0Tkcccsz+WquuC3GByXJqY/jxt8VIlr6oISOlBpfN9fl8MVDbDzlo/r/6HZ1eygEveF6/1bS0HYjBpyPby4a0VzaYgpqKAAAAD/+nAEdT8ACAIaGFgZ6FMgQ2Y7iTCibYg4a29EnKrhDRksKMepGAAC7LWY5EJUyZBnjhyu4SRDGkJktuQCm7WCQgpVYHSfqFQzWP/Uc5uVfVr25GG2b4sOhSy+cfI6MK7Eoc1ZT+tG7RrAQHBaWbswjtZOzD36ZaNSBYGYwk9/UN2OXqgu+sMpVV0hXbUV6D+n79Cf/FnjxIAmQ2gX+s7Vq3VJOtZb+PMr722PQOjcVm3+JJZQVO3VtALFb525yB1cLH9g8GER0CvGArPlFsjtrBsUUKVvi9qMsWiq6b20bzLVOpcSXlCLPTRsdXv9QAAYBFqS2rEY2MXQVH9uJ6MDlqdYiFnqEarLEGbryAIupzXSLCaqavRe4wLtqSqf0/9Xq0pun8vtvFterRoyVXrR/0JiCmooAAAA//pyBL4QAACCDi9g0GUT3EMEeslh5WgI1Sds4xxJ0RANbWTEnZa1y/m5JJLo8LamxbYxkwz6jAXyhvhbbKJ+gP0R/T4J9nFoXb6j2qu2Gak6Ir3Nf7KjNi8FKz7HnwpZubdQYdTR8FQAAgAEqIMZ1L1vxsUQ5U3EJRLDKjRUOTJhMxPcw89I51itjMI/4+EnXkLA9qlNbj34U/Qz9BO5He1erRpLWf5/bu+gAgRSkmrrmeDynMEwEwsZKH9Rw30D/KvSoODpqSfT0WlTe37fAuRiMqQYjY6fJfm/8rdf0buW+HEFAwXFETI8Y5BUnRltHQHIKK3hyjpILGl5iqEsg+p+moSpgZXubTg9c8Ql15Z+YGPQhkVJZQGd2QrduWKYkK27cptw5hOVKiyzhG5vvooE1wh9CYgpqKD/+nAERWAACAIJFNq5h0LEQoX8HQzih4h9A2NHrE7hDw2tJPWctgCFgpSXQqzPPdsjpfA6gvI4bNzB4v4oD8bmhuVaCjhQvldOykM52pv3VGooAa/WVpRo2WtfaS9y+BjFRlZd7SCEJBMB2OOySS+1H3E5Ltgz6IHhMPh/j5fqRBNVxM3MHtmCuuYf08yP0+gXSi/GwNqORf6PktlAWwmzqcpLz4CG65nRSGARQKl010jNAYj0aa2ekhoaM9m9JIHb1Cj9wuJ38mA5kGdhr0QS/hvKjJVF6o3v8F8n19G/+//d68bDm6XDfRZtrf6wBB0FUS4LpNMjaFKT6u8twSQ7WnT0pTU6wq/QeGVsOCktWYIr+OdjFH7sZiDVl8OWhxjTP3/1YBeksOq1wCnNOU2s4tMQU1FAAAAA//pyBFlzAAwB+yxb0ScS3D7DCwM85mgIUWtsZJRQsSEwrmiyiscAANCggilEou3cD1M1qKFjKAiP0PKC3yhDxQDVWCECXbN9fRb5Rda//g27h8to2bPnW8o+LPjn3a8t10V9luLhgxWfpNlyuYQzBW6l8UneyAEVHxllaIRvugVDBurjNnoGV/TCvtqO0PgQ3IacR5bRWGHyP3b/V3xmvSQ9GzQEASUolU28IA8wp8Q8Lx6Q+41JYKBzdwzwoN3KBkTr9Pj/fdE6IvqJ+T7edvT6f/fyp83UE/ZG/6v6t6t52phh/l9gCA9JFFIuq0u7FiT480zBfVSSOuhEfrm9g4pPVYR5tCe6qBt3T0H9X9Pv8v0+J9W9G9P/v//6ewL0K3VPVvVtUduUNtResfk0mIKaimZccm4AAAD/+nAEWIMAAAIcRlvQxRQsPiUrehlnJ4kIU3eiiKXhGgvxNBMVVgAB0CBRTM7HL50DH/BmCd7kkMNlDegZ4QHzjnD2vb7ff3fgjMtjPtIP9fRvI3VX6Fb+6KnYvLZm4d4uV+3b6HbJTMGgAAwBJRKUy4nBxZ3WX4oMoGT1KoVIehauNC98afFj+R9X9e0n6nd0L/Z8/t9L5WtWXcsEXWZLJeUM098wiKAAIBVhSRRpxXTCakbCw12GHsdDv0GkVAr1FhUOAmESAcygsTAgskAmwjq1DHA+5Gy8oc4D/wI1NQDe+woT90EQuBAQIqdflzSQkJeackTSTJpzCiVcrOw+SRly+jSHQK7wOymHCguESAcz4sGwILJCJsI/UMcH6KN5Ty8vSX4EFieX5AoT6jK1nwIGE/lzSYgg//pwBMMGAApiDw/fsCMYED2i2/kMI3IJMJV4BiBrARMMr0TzDGABAALVaIg13LagfCtc0On0GwkEJCDBaQHgsJ3lwyK4sKqOAU2Uz5RQLtE4fKF4qQkLAim8+xZTqYUcsD7v/lz/r9cAAgAANVfQl65Z4sZlNDgm+DFKCqZQ7/CMOCwneXIrxZijgFNlKD9Rlq6MVIWYR59g9XUwo5b85/qy5/1+uZDQGpWPCaVVvRrV07ZofKZ7cYDtmENB2ymmbD1mqpUX/vzUn5HpjoYkicAA2s2SW4QraSbh5ZIO3ivXxQC1mAIqtz8n3s885Ol+dFAYopQSEgHNNUWGjGK3w2LzCBcQSRmvDTMczaUSshzqzIslJoxaG4AEazbZ0VKtCTUySxIHWPZ/UVrMFFd+T1P+xyUxBTUUAP/6cgRX2AAA8d8X3gHmGLBCgvuwPSMYCUwZeSQYYkEUB+6AJiAQvRuD0lSPsUsog31afm0O0Otzh68j5Hi1AQLGGBiQYCb1zAmZfJCBdDTAcpjm3IQoqxmiryMtZ7N3Xv/TLMQIddgU1NMiqjKbiPJ8mDCjrK4XOHqc7jOQulgEdYQiE4CdcwJiE/aUXRMBylw5rxqGiIqwipiIA8tVPbZH1e/9IANAAIqytQLjQfFvwGIwgbkRDDZ5kbPFXPNGUAi+k0cSBg896L7TzR0YwWUQGLAA0VoCZ0stk410MLK2RVjx1JKWSk8LjeAqEsaqACcEUJ/0eWbMhnh9OGgnuoZjqg0WmC5cFXgAzmwAKGwbDZu1NalpDhpS4oMYSMCzYBKzwtKLxjmuVUdSlke6+Btuh9w5MQU1FAAA//pwBC7uAArhyiPcASEaoENEe4EwwxgI9K1vIKRiwR2M7UCTDWjoBCQuOrPP/C0uTXHQ68Y2+dSbHtVCGdE09z4UOsnbbAannrDo8j7ICQ9jBZ669gsY1Rb28je6LVpAP2CUMuNqyU8C2R8ox/Ycc2DNG9i0jH3pJtVc9zOJ1WTe9IKOeKNItA56QJWOLIeKsab+wWJcy1jq70yKnuQpVN1hAkFWBZFlBE+t52MVeQ/3b8EXw35tW5DUBlUp3zLh5qvKS7dIr2np4M8bY1Ky4OA2hBNLCT2BVcj2npHvbvPuWmy4BW3kEKklwoaFWjAiRmawb0od3SK3dKmv2OV3awN9kMNt6q1Emkc0FbxLOnSB0qsySAILETS5NonCDUlYpMyzRdj6V3iTTX1okni3ZplkxBTUUAAAAP/6cgSkGwAAwhgYWQmIMrBEIysQMSYWCKhfaSMUcmEMEixI85XIAeBaTB3TEAOIf1wqIBesCIdXDhcmv0FPjWJX3azZz3x8mRLUSrjFnQekLXtWeKkVJcqeS9T+zZZ1mGtSrdnTxb436OaD0jeIIHjN4BCzA+FiPM8omr54IvJ3NTaW0XmfrnWWaOrGgVSAgi0LhhRIKklFB7DqQ0OebvCRFDsRYv67F/7PrX/1ACCwIALekTlEwQaTzQuAy3g/u7gYJLzhTdRANLzJ7QdEZd7UIInVoYYMb89FLHPGb5sapKd1GKLrx1Aazcs1Nb0Pq+ei6ArVVPH+hFc9Z3WxrUKByzERr1qHssiIVaBwNXPc8YQcm1X9l1r7mF3uaMH2Bq9x59j0sbRPGbfMaRqGJmLtPsmejamIKaig//pwBFS/AAkCIjXYsYsS0EHC+vE8aXIINGFgRiyrARIL8DRhJT4AgitVqo3Q3xpfH3dYNGBlW8NmVDFBA90wXP+5IhnSJRzspwxWVqaZdPRCmfauuja/olbn9nUbOoct/6VWaPsK3vt+moDpXMxlLNERJwsvmILQMZGYkNsI8tE142kE9jGho/nvTlx6SgiCRIomQVEkcFz6W9xKpzSxFk4imM/XTuuDuj+9qxJh03F3IY9k1hHVGuD97R9r2mA/uu7FxHu6gIDzzGTuNR5U68eOBaWFwIkQlsB2C60cuh5r8RF3MLXTxn9X/qP/UykgXEm3G0nHfNpMDfPS7VGVjvf48ucfW19URuSE7CKBCStvEx4WnxxkwWe4uwLUAOJ7n3R16PIZ0pNJaFrirgpcXV63JiCmooAAAP/6cgQ76QAIgiAYWTklOsBChArhPYY8CGRrYSYgUIESDWzck4mkAEUEnJJFgCRQkqA6FnWg9AB7CDrjAZ+YN8KOe9Sc6dIQyt1danHVIutJLWTXVao+yOUwCPXMLRtPLw5liKjfu+Y0k+kX3ioMVAokZZM0uYcjNlB6GPtm2S3WL5z7EMXVnOEjn3dJ0jiOkNbdeM+fG+ZznNLFrrsfbujIrPUa2xej5dPYz/7gwAX7+LBDKroWD3c15EFsATTMBJdi7Gb+QyCUb4DjIrGtYx92jOWSDFsUKAtbcHioCaSPJHEBUNMvADgFW7v/96f/QQEsolxRgfNJBh5t2OCsiM9kbTFP7FRm2IH8AuiOOzWKNeVOMNGyvO1kYDfDNRUVMOfMX7tZ7DtbZWp9k8bMOPDr0fmUpiCmooAA//pwBFnlAADCHB1ZUYcS4EBjSuJgy2QI3SFzgxRLsRMNK4jHnGgAAJEQ25LcqbrnhyMY0XKxogqWnCUY9GLVxSH3qI4wlYzpUd9pfBpjmC5mshisUZdmpvWPZTQ/a6zIp6b12cojRs1e0CWgkkRgJs0fISJ6QuQWKFaUrLCaeoit2XCH/ZJDr6vJVX8kPVVDVZnpM7CZeDBkY2xU66M+BiZ9SKwERuZTb9hABAMZJma/eeReVOsfEH1OR2EyS6C3kbjQIqNKb1fViaVR5K6fyu9kjXRLm9u6rarp/sM+pBFrhOh1W2O9SUuT6b1HXcf1KQavEysDBm8JuDAoqZ0sTGUR0a+pKql2BGjuBAlZ0KEXSPhyqtjGtKpWgYeELZNDWyL6mPlpYe0XvTI9w0SAJw7W6nSqhMQU0P/6cgTBDwAIwhEX2VHpKTBBhiriYQdoCISDX0espcEWG2tI9RWoAAGAgOS3/szLC2O0JtMPJQCSFTFgg740EbgOfoEx3UF+L5HB116TyElG2vX3SzhSywc++nRoROamLvUg7yarUa/0gL/nv1NxqVjhWZN5O0cUZF2VKFUUJ/iS5trULCak4Jr1DHmE9XRzn17KdvvpRfZqujGt0OyiKlfTdP7KXdtk16NIQABJt7dVHma8cIABpKHDID+CqvBjC8yYpzNyLSP9SvwS2hvVlrcr3Ye61FrKyWQPZlDdlSn0VaNH8kxCjjC1m5G6/jQNvxrTZFnHs2haxmJpmqGbDL4IlH4dNRYgxa6hiONdx8amQAdvUKF/EybTHMzjXVydXT/+K9K+9b0uVxI31KZK4k3qu/qTEFNRQAAA//pwBKHCAADCCxhbSYUzTD0C6yM86GcJGQ2JoZyxMSOkbEz1CapgQNGVUvbrjrAeUZtaIWsWxaZI0NfkH2Kp+pON3ZcUObfK6ccqL5qWc/mHkhtIjTlrd+6v0ofW9xhbmw05SXnO3QUEmVdLmJCwVB3tETxifUGg5Sp1qif4QN2mq5cME9bjH0NSjS5CEzOOfBdPMfT8/v3V/EI/xJi7nOVexH0Nhwua2222XekMN0Y2nd/pNzHSHQQItoiMi1DSm3Cx7rmV5vtq6NVUCjdxhbKj+n7vv+l+3iPeV33F+GKwsA0n4mFpW0tYDGwOAFJpSQXBGo2qiHK7daoMGg7nCpZ+QklMHhM6EDdBu/CCi8E/RxkohNYRrvBP5Lau9ZKaIf9eZv+z+r+f7fFsUbMT27FSxbV/kExBTf/6cgQXjwAAAiBJW1HlEtRCaRtpJOJfiBDfd6OMWjEYIey09QngAADBARxy30vPcmLnje7JKIvsY6rHeMHbQHZuN8f76IDGKrAzXXD3uj/I+h3ThCbJ/+3p8tunhSehOoZbg5R3SNKXUh4AQZFehvwbfwfIT8IWC04Yy2mMecMboDsslVHPQJ9Xk97DM2HvOCDu9L3z+oV3qie7//b0+X5vUR6N1BOeW0fJ4c1kAAB0gNRtJya+EBfxPM0HWPYxDfQt5V80oExjrhJ6p3xkPq8Oh3MzBF7z9H+Yb1fy+wN8vkayFDaj+f3er69f0AAABQANS27+R6zKiGnWJar7lDmVZvSHQycenKhzxJfVhcWdZwD0DeCZlqXoad9F+/sP6Nsq/V/X0X3H9G5AbKn8/S71fAKYgpqKAAAA//pwBPsMAAAB+BhYVT1AAESE2wmnqAEIrXWEeBOAARuusQ8CoAIAAIAglHN5G9kb5AJI6HUklxdI6uiU8Ind3OG3Zw1RsVRTNqVLMiR1qm5+n6tGYxXJ6ak7M5U6PdZof/Tp05kAAIANhZqb1A/Vg7CRNN/yifkjPCmXznIfG3YgA6i4gRSpuMWoYeFMSNyHehOdZV3zvkXxQ4u2vvsfUmjFclY2PT6v/oDAwGAwGAwszmq6////9f85j2Sv/NFg3SI/X/oJQABoYFASN//xEIk3U9Bov3p/+RcycJA2Ik1T////IDjTiB5p5Msf79QQEAgEAgEEv9VKf/////Onsn/zRMH6KIv/+UEMAUHoUgwBo//8Lgek5xp6CIK///41OJJwiBaJR+pT////JB404kPNJx+WPTEFNP/6cgSUPwAAAiEZ5H8IoAw9IkwP4RgACOTXdeGcUIEdGy/8MomkQwJDRIZJGiQAbvq9i1OEooZCiB0AVum9WDXdUTPpkFkn1LYkWaKmVs63jRowjClWILxj5ddRv+ya7U2taNYiwlurOseSCSgTXLfNFJJXHDz2bWVzOBQ5RRwP2/KT/qZDvJPL2Tz/HiqtvtIEiQo8syzclBFu2x/6mUa9egW1XUa6SERACKCWwkAgG2CnEDlPEGOXgIHUeM+N37hAM1lHBlK08uj6t8jiDb0fdPfKyKxn/RWQ3xass9gGRUz9xDg0d9Cw3WG9X7HogEwIuMvzZBAVeAXXKekYfFXKSMBn5h+0IBqqpTqVioeXR9SfI4g29H3T31orI/9WQz+Lsw6WSedFP0KG5YGfmRKPiV+z6kpiCmoo//pwBB4+AAACIDZe6METiEQGy98EZR0IpTNthZRL4QKmMHRQi1aIbBT2Kxsolvip4ghkv18vtY5M4MC8KLfgtXQBfnFNkR0/tmSY7K1HP7+j6fos2ymURcr2aueSsSqtrZ8uM4KjGJx66jAAQAaWOaxlEs0lpfuB+cRq7xR/qP8KWpyAU0tatIR07dkYWRzOytQy7X9H0/RZtqKKXbNmrsSsSlra2fWM4KjGJ1rqAAAAwEogrsk/A9ELfUiWEHxRSKit8N2MA5GcomG9gtvP9f10Ksu4j2+ahit6Pr8T/XVWpfYHWld9esz7FE9aXtR/5jpIIkgCWOJxIpOS9C+SlkFcspvEB9UQBzeDeJp5n+nKMMRFntevuQl8uvxP/7X7sDrSv1IRVmPZiiVN6/o05Jo7CjkxBTUUAP/6cgR88QAIAgxAWeHmOqBEaBt9MKdXCEjXZ0ecsEESFeuFhomoAAAAgACb/irk8mEXRtrfk2YfG8HN2XKO/cPpUTNypDsOkPX6n7U9H+32TVm1dN0/5Cv/v7Y9SnyLB0t+1+tn77bQACAIwm42knnPdqH3/+hXCQ7cuYKBJt36jz1YLjJ+wpP5yu3X9Vujp772Vlc+/f/a2harVrb+7PKc3coRO7zf18Wq5VIYIAFSSSZiM+wxUfi+cEMmbf2s4nkS2ChnjQPpYoITGi/YhfOX0f3bt9P+o0vRP3p6DQDqzTaTXvXWonIa+jffu8iD9BMuxG6Rs0Wk++1gOCrSNWZx3WWiJoKFeQTbrFg2oZxwI9PoLbq/V02PnzNrreDYcInY9WKrxemnNaNan+Ju6Eel/0piCmooAAAA//pwBG1XAAACHxRc6Mg5aEPGuzk85VkImNmFoaTpMQob7/Rwj8ZAABDsSByOWtswwId/gHsi+QocbyUteUeyKBpZalXiQcyvL4q6SMpMuYfgE+ru95ZDMUauV/M0Nih00lVpA2isBUI0VAABBAyLYjMryOVDVvHwEHVA5TBJXQQBzceIc4WGJhEO+gZ6v7P7s2kyXKdrz9JudF//j3n3ofes7DX1tP6qf2LiQ99TQbFWskkcbco53gjb7GE5j0m2fyhicfZC7ZxXspdaRABXuhrcV+g82if/23MfVL8zXaVJS1jVgVmNp8RFUrpf36+Z+L1IkgBVlJxtpSSPWcW8aVbGTKqIBb9vRuokeULNuz9Cul2fS5bud5h0ToGlziY8crW8eBWq6hH9pCq/+2xA9ZjgjJNUmIKaiv/6cgT+igAIAiQXW2liM8hChrspGecbCDBTa0C8oSEUjy90VBbGAAAYiACTjksvclglll30AHZ1feJhJlinpnfxelV7in0OlcrMl7QvY1wTOXDaElY0NIodokBI/ZewrUwXHjqn6HRMf0awAAwmRVnTDnSidrP/2CruVMXErxGdRUL/E5GpzCOW6CE17hYapR3O8r7mfe+rb13qrrv+zp6FnOrqff1aaW6zf9mXTpEktqU06iykxd63riaRhIWHFZ1KGsuHQfwKPe40DHYNtq+HJZ9b94sOaoDVEQHaUUZyXyPr44WUC36SFh0bVyTCg0Ca2m40k4sMaIgdaw5Gvjh6uzAz8I/CI/dsD1XuYWesq+UHrOVgslp4rDdQd+aPjRjymNZ0L+LFoBsIHiKabOAUP+hCYgpqKAAA//pwBNGfAAwB4xfXkww6sEIDuw09JVYJAOtWLD1LAR4V7vRwi04Ab/xik2/U0hMWdZ7nJDGGJf2sgZ/vw4WV2g4GuhQZPcxQlN6A4ZMadFB6l+cItf1i/L9Yhp55lud+3/0AAAAQgAFuSSCyPXbkbiqvrbwIe4Hxe6DcGmKTRT+E38KDVkUJ9UHej+g+shmmrpnJYfdrQ5Sa1QL/pyd/t2ct/0Ug7MQKp9muiHkES2zackyKundQmxNf62MoZrylMECOidJwDgdIVNJhXeji4GvucncjKNRS5ndW6v1n7UZ0qurV/b/yL/5n//2gFgFRlNNNJyz2HhR5jsXqPG6D4xbKt4gL3xJc0xTgmbqnYhc6ksPj+OyWBOpoBU2+ALaiz1/6C9bo4iTtQ8UGiwSTDVTqX9KYgpqKAP/6cARd1wAIgg062JnqEtBDRTtqGOJtCIClYUe0skEFFKyolZT6BCct2kgsiroT5A+J4xMyguzBsnEoW24zCB7wqF7uDU7qiC//IXzeze6/AXaij9PWo/kY1afDsUQyHzPrP/dV/r9YAAcqNy3btLPwNLM9YwiqmHcVk646MeKnzlGz2UqFfn/G9O8zNnV+oV2kD9bkGD9QIqUw4J6eQpJUiOmYk0sZujOn1hgAS3LtJGVKgyLOcOIlaB9TMc25Dxhz0y1M2NYhxR3cQxpygBfUJL1+h/N6n0jU+JS6FWWg9zvWEaN7q9LJnFvvT7sgEBIKTclaYg2AATO9YNmj0d4H1O3QIbOfMC3/HhnwE8gCepvI/p6n9Segkv9AmQsLPFKFv931EYfvJK1TSRVlRjSmIKaimZccm4D/+nIEtPoAAAIdK1azDzrQQgO7bA2HE4gIdYWjDKJxDxpr6PWdWACADfmcDN+5cyKBdegpc7I5OdOz1hCXpa9l0/xr1BDmLj7oX4+qIa4C7qjCT8z3foQ8eZ+pDzl1HW57DeyrX9GW0btxIBBEIhWW2FOtAud5qg5mGYyDweWITa5QtWYEwMTUf8VBduvynPVBGhFRKXiwiwR1Ksxb4ZrfW6UNKMFGNwxVRZ9UhcDmhUeckue0gTg8njEGY0JCg6QYa2vxreAj6IG/blGBUJB4UaQf5Spu6oJGlvQWxMLezM7q2xmvE2N0YeP/2AABAAW1bbl2tOWRNxYfNVmBvQFAwhoaH2xArLPyYM8DW3gEP3Fz9fhE1UUsjtQ13Wn09G+3obfK0jX1p9WrKZPZ9/9aYgpqKZlxybgAAP/6cARMZwAAAf4i1zmYOiBCiuvdFOJriOFDcaQcUTEbKGvo84ngAIAGnLazELBNsCypq2aJH2JXzuCtBdkkzXW4LQ/aTaylBavQRvKr0Y9bT03HjlWV0vvrD2W3S4pbrxT53+jXoSCYLqLbqacpZGhYW5Vh2dqAvUkbFZb1b32hQMNyAXUO/OMQtlIRWo//r4I32/6/t6/N9fRv+jV0tX1yv7onGvXT+gAkhGIolJJOaSyQjWmBQ8RbxglkTo5Un4gfuJL8od4mHfHPhh/vuU3VRvHL5vV/J6obqidlXr6/Xbb0duC+r+OftHT9LCn6wAAwADKt3lcHV4xcFtp1CbQVXKFo8ppEGSEId4CG6FvEA+3BC/I/K5ecf0/+/p9fsr6o16n8lPbqvr9vR2/fX4+18++cV+tMQU3/+nIEMRQAAAIhHVlQzzokQmrLDTzlhgiIg3FUhQAxGBds6p5wBAAAgBJKkkZEcC3qDXliA0ase5jEEPYdDfjzdBR2ExPuFhw7nv5TLZPIb6sprSoSw0CefYs81b3kHqSQpbZsrOIXlcvIAAAAUAANu3/up4kUn0By11oG72vPqeN4tIahycSjeg38TB6e/n9Qoi6n9X83r9Pk/Xq22/n/9fl/+9/9PijJr8aisBSOSSyUlInbLhLlwVCgqO5MJ1nkYbT5hAf42LUMGF+cf7eYWZEmETHlP8liPRhx8JFlaF0ZNEOk5nZ6nV6Mmdhsv5+oQAioVHJL6VtvvWC1JYYmURcz3ki+OA/fhUm6uKyRA+xQJz+Pizy32LUVX1p7Mf7P0P8uzpDdn5Q368j9nqdL6MTNk1+fqTEFNP/6cAQxYQAAAiE24U4goABBxdw6xBQACMCDdLzxAAENhW7XniAAQAAEAMMMMMMMlcbg7yI6kv7SIqY0zP2PRvo5TvT8qo6a/5hZGIazp/xpgFGEGiWj//jDT0gXFf5qGAUYz/9YFKDFBgoAAgYYQIBAIAwIBB++DvJ6it1tIiphYs3jjkRv5T/8qjHTX/MLIxDf/xpgFGEGiVX6nvgX/zUMAoRZ/+sHQIMUGCgA+AghRgMSqjMUa6qpK+pb2kjUFmORjFczv2n/tusz17LWyCpFqj3aFBzxTCbVPi20wRa2Ikt+bFHjXsD7O+NIyLyvqZfWAfm6hrgxp2lztdp1LG9rbktIODTgqlNa6KCrwEf64ikW39oMDnigihNqnxbFzBFuWS39TwG9gfZ3rGkWEXlZ3UFb5VMQU0D/+nIEm9gACAIBNlyrCRBQRCbMjQwia4ihM3EnsEPBFhrtmYYUMAMuW2QGFB0UIVHEI5Lsm5wQXoAVYy2EbdFen1rWWn8iqjKqZen9avZrfK6Gct4OwOz219f1tYoXOkhZPsF+f/WEjI3JFJEkS4AgypdDMggdukZFueZvkVyQqlRQQuVX/kUoIiqlS9P61ezW+VyGct4WwOqPTrX1/W1ihdyFfYL8+mS65USDaq9mAXmFrXnK2gt99Hau6xk6T/3ucSGZ+gt3mkXVsfly+9C1vR6PXf0Paj66kenwI3/pL/f/LqX0d+JTryQ12S/dYgAjSrNJwCQpil5QtOQwt79kk498gdZEkoZ7PUDu9wm/VHGA1n7NzrWvR6O+/0W1H+pH/i34l1GUsrOhM7vlevat2S+9yaExBTUUAP/6cAR2gwAAAiReZe0MQAxBxrsxrJQAiCWFdBmDgAkUsK5HMFAAELklksr1sszjCBsuXgkZOKeAbzVKyLozPUw3tt+lvNei+j3fddCjVy+rb+DL/63qm6ijIdJfzV27S//v/5b9q0jnn3ll/MuEbpbRrNI6stnFm08npIOmqW3+ncPEGHFgZiOPFmW4dAnElVEVqPKuhV5Fv73e9/Padtv37+Nb/z31e479P/qK3SFbdgOU7USrSuelM7esYXr/jwwKyAXV2Qw+OmI9aOm62P7o/X//////////qff///+YxlCdjCH////7GGGDdIBSSUkaak7Kwyz/W7EG3Y9jM8s3r9EqFOqArSGibMh9x+IPs592fqb///t//7////U7v///+xGJILyEDn////5CEIBwJuTEFNRQAAD/+nIETMUAAAIoVmNuCEAAQyrMGsQIAAikNW7c8YABDZtvN5IgBAAAEAibhcMNqMBgMRgGjKqE63f7pS1xZSe+jqvulE+5mt0s//1RBCqv/+yBRJWEKT//9GOUGZEcgfR///wYiIccrmUgZwAAVAP4nAICwIBAIUKSIIxrLTWa/WZFSmfbTq/dPRHbo7fy//rQi1/7+yKXJJ//+jHKpqOwfR//1+DJEO5ZlIGf2JSA479ASQ4pLmwf80rJmy7cKVVkD8A1UhIqk4ggfLCOKuLiNThdy4rVdJtDb3cXG5LEvk/5YGTvbPYlO4sFIKhsFTvztYSAAAgBba0rAJYNSkQZlH/K9jW/MD7GLWQ56FJzDe1URfE1qt0WrdrbZbFLe1UXauUq7KAuvWe/g0GvltbuWwVWGvlcqmIKaP/6cATD5wAAAhc2WjnmEtA/JNvJHMIFiOyvfVSBADEXHy0OnnAAAIAI5LbxMcTnoE+5TGrhwRABPgiX9sV2cGjMf0p8vD9CD9hGS/opZVQqVm0Z/69jt3br/oL7Sd9Xp4iIIKd29X/2UAIAEz01wzHADw4qRKzm4BzWi2RLpdlmE6WqNFdG60cs7HR78G6iKJcYsfyNfWfYQbJ3pUNFxrUzfxW3vv4PaEJRhBMbbcLm+gtZSxM38tyAPVdX+i6X8uu+gPqacqs6vOxrMRttYV8yigDCSIspcWcFRMMayHjUHgg9IEWSY+pBRRXH43hlwJju2yLZ1g3A4FG5tc2atfixlQ4xlMBeyuNR10Kn2z2H9R9OY2qtSn2Zvbb7Gd+rGUV063T5ibud/5VZBdfEJtlDf/t29aYgpoD/+nIEbn0AAAIkEFzWPGAARGEro8YIAIhINYG8IQABFIVu65YgAAAAAAICTLLbLBUApD8ZCVNZGbR3GKyw41duVMgo1BqChXxqBRWUWbSegGkCTq4hlWrcsiLQLoe83/urA//neTP///m+gktNtuORAJoOlIh4FK3llHmkzeWo+AhEXPGU2FYCvSuNdBh72L1hpUAir3SqdqVuzh4kOctZcjpWLoHClugTn///jTfQyGUAJdppGSapBinV1JbR+27gglCTQeDMLrY5BUup6T4lqo70rCxo0JrVcPCG9w4Ez34x24mndscu5cg9zWHGsbuIAcU0IomQBD4UgZr7fetFvNroIWgCoG4IB0q1SJxzRHN/6bxORMhFi3My6mppICb9BC5CwqbkKFIUTMMaoyhaii1LPUFrUxBTQP/6cASmnQAAAhlIXejjETBCIyt2PGU0CKCVbUWU7wEKjm2c8wigIEgIAJjsKQEa0C5jqkGR+AIhsV52ryWRnqo/vQn2dzOVt/9syPXq3//M/Rf5VcpfLWpWqAsUs3UBr8lRDVNXrh3BpAwSvECXWmpXkfHkp0AcFYZoH66im6WdUGE3H+8YCpERK/8AwpbJfyz4rypMNflQCdxK9a+JZWGoijHnUxdwdqBo0oDAAQZd/9d1IAXCD06g3VLvHJXNwLRoW1GGxjPbsbnu5QI6Mr66N9p9C2GdhUdeRgatyyzyyyLdVsidHD6Dorc36CP/5YQAQ+3/+okFqLeTZbY6Q9ZyiBPKhHvB7OsE1SjVdPamnhwk0Ok0Jg6u+mnnwOjNofWSX2TsrI61EkFavV3fbIqCrjyYgpqKAAD/+nIE5G0AAAIlGVrVPQAERIXrR6ecAIhEHYlYkRBRD4lwKwYwAoEAACLbkkxqsMt4bjJNiGzRnCuWRXY8W48cfcVy47suqQwm7jbcIji1OiICxOB1GoMxlTp9NhlDoZO9KEqq0O2J5j//aAEkMuSRgco74OMnThjVGh7TLxvZvMarqKS5c9D4RlnPItjx76H0Ot5Y6mjmf39vdfvy8y5Ys8mdrVJRlep1rFdy/02VgCIEkGBCZ3S+74AREd5IBsQ7uKKEqXnWMIi65IYs6sfdTujzRQXCTqnPAoLlb30pcs+5LEvsOvFzEihiq99xgDiyAAEZFeozbdttvgBsLgGLcyhUjI1JlyvLTmXHIFh97YNhqHKMsRYtCHECQ8ShYFyZdrFGpO563yhhLwcDWAGutr////QmIKaigP/6cASQkgAAAiFd5x4A4ARDi7wjwRQACHStd1xhAAERjLA/hoAEAAoEFz2Gw////////Vns//9X2//744eTNG0aDg3167K7Z+YpEjPHBQLT2MDP/zzFcbnuhjIY2TJCKJQYEsXDg3jg3ByABgKBjsNhB2vM7K/Z6////f///6saxv/6u9qf/+ii5TQ4Q/67K7b5FYs5BAVOyAv/S5LnO8mRseLB0PBgHHoL0OBfQBJFSckZAD1ywIijiO7kbp8NYRknasrs6kUV2+rI6q3t22mN+xjPV9RMztJbpHg0eI/lgK7WDVfPayND2MSklLGsEmqIQYTJJdbt7CQYncagzoAh2Ac4KFq2so3mnUdqQ4jX9cfWMC79bkuQWes1ETycS+35HiI8It+4sBXaw7X2ayO9jMlU/GJiCmj/+nIEDucAAAIMJ15QYhvcPSMrqhhHcIkA+Wx0woARFpJt6pIwAoJITBMbJBKdAFcE9h1NKWfaqmBs73or7t7Yv/DKb1jRj5hR87RzwTCYaP70bXqCr+dd1ywVU98iRq/W7Ku8lbKgJAAVwlHZG7nCzEOXrDb87Uth/E5BFJkadC+n3w1zyiqUMlX00uDRrNFdqVBV/Ou65Yep+RI1frdlXfkpUBBKOW7Y61IIVf1Vx659uT2/HbSPPZ1d0ZKu15h9QAIQq1MQXqOO0xHfK/VOr6qXxH0Tq+1/bz9RffKbd0QuUfercvQHnQX0X0gAAsIpJbsnDFwobiuwkbUypwzMgnkokXh2GcORQEWoSzV2gOF0+yF7ReEdPgtd35VqyJRYXandTSeptI+iKB6X2fat1HpTEFNRQAAAAP/6cASvfgAAAhgh4FYYQARDI5uXxIwAiKl3ivghAAEUFjALEFAAAA46q6TV1222+4A7dPqzt9siHk58CFdjkNctEkHLu6XIuvcM/L/dRQcLGHWHJseQIkjgwFuwMOrQ/PLAT7LW8ukeBwABEk47JI7aAKIIgPEhIVGCFEfdnZ3mQMlMUYdS04BPczmfdbPHSfA/Li7yISsIAF7zciUODE+52Qf1p7Gu1rLGkoYAAgALRYLRaLRoqAFgO6Z1N0/Zk/////ciuc4U8MZfziCEdCMYCQhjfz77oxbIhHUn/O97Cz+h6Hsv/+Rsjf6KpIuqmLBAUSUUUaQWQAg6ALwwxMO3FvCmk7MZ0/s//2503JzlPMiszrnIRnRmQWIA1JNA+kg1wgHnPPpQD+xQfG/zkh4qgusTNZ0piCD/+nIEkv4AAMImGN1HYEAARCFbkewIAAgA124HpEOBABstgYSIWACMBYm+joDis1oqs472t28/5vKtbzNsrrpRnxm2RlLjPKpS7W4mIxZ9uIRVyr4uXegWW5ggoFcgz73VvcQR+7DqV+V3I8mmGAAaq9YTjGH+o68v7+v7llQHGtqLhtRhbFxKHUE0upWMEwjFntbiEVcq+Ll3oFhdzBBeK5Bn3ure4gj92HUr8ruQTgFcSyMLxwmX3MjfdYmzl/R2dL2VhqjNn1bdK9H9WYU1y6MjzdviVVjNTW/fcKheqWN4p32nGIloaf1NINlnVrsJlLUjcyX4J1w9ZVsxGhtB2f2LGSXi/dAFqpo/qzCmuXRkebt8SVWM1Nb99wt3w6+pjqayirp4qZ8VFBtYcySYgpqKZlxybgAAAP/6cAT9ZgAAAhs2XEsMEGBCCaxcJGIPiHkBkaGEp/EUDOxFoqFgABAMRu/JAD4BSfK3RORoMt7uf7LmI7gp2eyxUGzVFatkb//LUrbUd+rWqT3RHypT8V+LA1eTd9tbHqDnZWGiOMensQCkVZnL/2tIcC9WU8T1ypiUcl3SfTuYKXprxxv/8tStVqObYpVy/MiPlT/Ff/oY29Xa/+y61R30FNyKlhojWMensQEypC5VLrJHQQlTFJNxCvrFxEgN+pi7S+KqMEVo9Ac2yrUjrMl6GWn8xpf60Q0rsNF01anS3nZxUsouq6rlv/p/PPrLD+OMAOVXK16URCPP7DlZe9dhGMI+Fug0GCiMuOIQKFg6Nrpv7eAgCOyM92G27LLXERd3UrJxfoy9zPV7jyYfrTz8f93MJiCmooD/+nAEhjkACAIJNds7CThgQuMsXRglI4i412psMKGBDQzshYMhYgIAGW22oPkWBVILLkQlMckTqPXOK3FO59n0Yen0P/Rmhr87XtbMPW/3fqfPLm0a3U7XypLqqRz1v2tRO/Wr3/f1BJBx1tJSNJMmGSaqpQgjK69BB6tbo1jVtr+PTXTdV3qtekf+2FW8LKbWpRiYHveaEwecqyEzyhV5Gx5tqT7XHHRRigVJZbRANfZ8QVw7lcsjaBahTn40g0EHugmOIekTbEhr76WGgAGSvqnoyIu71T7p6Mve6JRP6jtepFCCXxeteS09fW+77aR/BGU35ihizvt3hyluMl5cWI2/xGNgbhmbgSiGoOwy15HxV4VNw24ZPEBW1YG2L0SdP0O6vkqg6zYdd2qPbXSRX+iVTEFNRQAA//pyBLVmAAACBCveaSESiEOpq/0cI9mIzLFtRJRvwRGa7miQipIQABFFpvb/fpDDJfvTg7vu0VhWP1WpaSyIzPO/tGXfJbpvu9kT9/hlJJkRV9Z0gtYqPx8z/veAqxKdrVY66JXeoMgCFkkqNpNoODATFzyt5e4yONfG7aq3RyrlKNqf2aNk7xKeC3MWXuL3OASZgLgN+cldcp7/eX/ngq5fYQUxqKLK6w0AAAaCt2/4gELhCKSYZRTigS6c3rwx2iDXQ7ZnCZx3tpRBg+nKjePZ9m9+vpSUQzmteRvUoP9NqHPP13ZEmpVDkbtvMfFpUQBIQC3JJSghiKXrHW2oiio3r7hWCF/qk7qPSdnS66zOXLZtB1bZFZ/l+sKDQrdmpe9tB0bfSR/c19Ux/PaDkZqBa9IqmIKaigD/+nAEte0ACIIRK9kZiRLQQaWLtxhiUogxA31BHFgxEYyu9GEWVCAnbbS2qLA7g1ZiH4ZYIqX1RA2Z9CbPSOLfjl5djS6zaHwTqivtvb37X9+m3RASHNZvPREmmmaaLWadXVY9/+bd9gFQSpZLbpK9qU4YJd0NMbBd1HPo6eM+/7Gp/7/1d/Q+97EHNDgmGmm9ZDb62L/XDaBAKwMER0ccNLEZA085VrtINicJRttxbMVkd48xqtwHug50ZqSzl0FZnRQ09ei0pHvR2XR+dOjqrla9O3Nc2Y3ll5qvVKAlystW9ZaRb+0xFm7qgJGE/YimbWz3MSVw9dvbX+FKdkDByrvknGfL5IaK3INB1aMVLMrebWHxZxF3TeQVUsc78v5RuMsB08piC8g9yWn2X7jSYgpqKZlxybgA//pyBMr2AACCFx9ZGSYskEOIa5ogolwINJNq5IirkQ6cbejCicIAyW7ZQYibAmhorh0oqx+NLseg960+NRH5WH4OKJq+L6uqgTfnIvGACh6jMTblKvJWPtu0c8h7EbpEsSX3mvu7turUABCYJv+9sC2WOUCmWRSWoSPwvuMcYlH3Q7Pv3xn+pfT5erGlK7b4VqZv/37MZ+YyMarWWvlCiQEeOrK6/1Pq9HO4UQEQFJuSTwiIEL6i7A5VYUMM6h2ncOx7WLGBI7oDAOcegAj421E929rWHVvrYw/4QcAGChKrJC8fst3nnMbV/4s/7AlBKcstho3EaoQXOfi3Nzl+0wQNucCGX0lRy8fmxWfmT3/7N/3RJjs/5XSmbHREsCCYwphenbrzpM8ISXxjKFDu5TNyYgpqKZlxybj/+nAEzQMAAAIaPl7QJhIsRCcr2hglN4fs+WdGIEfRCR8szPMI+hJ92CSUUlopzDIX7e2iZy+FdLA6aIjzhe9DP9l9f/bq3U1HKGLdlXIb0/6P7QlwgmHGXlH2LLNWKMGHat7hrVhnOfpEEiIJNRyV73ywEpxWJFWEvVq1oxsupeao16PlK/X19Wft7m2Eka5+z1slpEa0YuNEwkB4s6U4SZfFB4TVT+w7ZtbWk2AAEBARbkjDsI1FMiQ4mG3OI1sE1OW5mwct/Eqi9OVPitXz7c4Sunr9vb21MV1/dfdtP36K/Ewmap5FXqd9n7AAS3JK1mSChexxtOWMOYtREy/wPHfEF/OB89QGxjagXNkESenp/7e3UitYt/zP6N09H9XyBanje0igyK73/oTXV+lMQU1FMy45NwAA//pyBMilAAiCIENYmeg7sERDG0I8wj4ISPtq5hjl0Q6VrVz0CRgAtzf+ErW5NPxQSYWJWyaJbUj8xqQis0gFRhCKJXJyIjoMZVZUJnl+cfXR+j+W9vWiTv+y9U9ur+cz481V+VdRV9n/6AL6aSC6XWUJHeai3DvhfS0P+Iz/ALPXLsDwyjhtQH3jtfX+Kow2o1YHyIzqcXQHlNKSjo7kwxOLUNf6iftnC6lAnX1lyglqSSN3uWlwwMhFhrqL+CUGwLz44HNlYa+eiNlD81TFDG15MdflKaP1Le/UpupF1zm0R72+f2MbU3yunJbv+3d+tABkibcC9F0zMg04rupHI11lPAfdGO3UcK9DDPp2aMKt1/b/Vl2c26VGX2fI8qNDVfiMY/EVJXaSWMuoPHgKynPYJNYmIKaigAD/+nAEGZwACIHgKlmZLCh0RAJrRzzHNoi1bW70Y4ARIx9sjp5wAgCm3JBmFRYORzxxti2kf77aUM0q/oER98fiCMV6P5RgWOm5Bj3q3vqNEzc3jcTZKr5zyLlP6nf8zX/VmQAgApSSLiXUiFXE5QsqAXti66sBhejikmmpe9ELPL0HzSqmfrD9W3+F1REDRwsyeZBV9LWO2lWLLPt5JdJUZyJa6VGLsEVJdtOUTBgGeYtznSOjGRwM2jQv76zWe6b0Q5ttv2/2Ebqjc0v79W6J0+7f6E9RSIh/IWo7vn9zF6H+d1NfnO2lNW48cAG25IxMz6hjK8co63COls4XtuOK4AxBnHyIvVZUkjmlCJZ5hr7GVSe29TGyyTOlhGtq1qP5/t3O/9uvoemVKZmBn27XL/s9EsmIKaig//pyBFlqAAACGmNevgxABESMa7fECACIMJF0PDGAARSJLueMMAACAdoySyWwBgVwVIgoK03Iz38zWVja7aJqXvsdrs/Z+fRlW2u97BDc/b1u79G9badL0z1////nf/////8h1OQhAM8EAAAUi2JHBAEBKizXOD5ARk+L6uLVlYGwJ2PUsyMnSbYpWyo6UL/avr/b7GtfW7v7J//V5M53////n//////sdXIQgt5ABYNAdFV2d6od0zrqiIm4AAACF13SIXeuiF5UDO/7u7om/wIIA4PwfA4fBBdTqRGfWD5+JwffIIqP/sb//5RFb/rkAEAAAml+btCB23b3b5+71ERI4AACETcO5wMLg4CBMuH9YPg4cPqBAuH4Pjz4IfiN8H3y4fyCJQP9DpAnmOz/lAxF3/LpiCmooAD/+nAEfYEAAEIZK+BJIxLwQaMsBiQjRAjUg3injEnBFw1uwPMMuIAkAARaqwuG0bahdqW3k+usmrczFm21lsy41ikpN1G0N0W75lxO3ZDLc0YRIHpQoM7GLqToIoe2ntEKo1Px7+wy/sfAABCqqJGK0bYxbTU4cNYmoYps7Ns8v/7WCekKgkp8VS8s0S9wVSeGPMtfED4sq1go96RjzK9+trmIa9PT6kG/T6QQAHTnaPi8eJLtqzVeRfeY4VqFCoKNqJO3xelWV3rASfkTEjA0dARK3qeTl3PXmZ07KMGKo1v66msAZFqr4wwImvSrc22tMnE5JdHgILTJd4RzGe9SjpKKsSXg6hr9NDMyNdadOhQR1RKMJBIBEnt0Cx4PrS568vteKERinIdXuSNapsBsq7jBG/3bdKYA//pyBF3LAACB/RldgeYYsEADK7A9IxQJQH95IJhAwRYQLtiQiWDEJfGaQxtCNxdLblIIejM7DZGpIk4ShpadMhISASmqB4s8ST7Q7AD8KGR756qOcpFtyNkxG1vbw6qkm+tlnf9eLGySXBxYlRJoYmoyJZXpWXQxB0iRjhVmOnmox1SmqB4s8SLa03CT8KGXvQzW9ykWucrZMOG1v8Oqyb62WdVnWGA8qC1XCpFb9Ab1jtUCJYK019zx0i77ELXZjIVFb93B2qFEzxEVWqNJlkvqMpLnRANCIiU2NJo6Hnh6oCBp9ECueqP1WsisXUxZAEVryGTPjZKOxZ7AjawYjYKNC9ESis07WZJSL3y3B1sYQeqOa0igXeeTkHpC8MRZ7KBe58mZUyxAdI3OIhpM4xvWrbaKJiCmooD/+nAEUMoADPIJINuBJhsgQqV7cDzDXgi4ZW4jMGMBDBAtAMMVoOtRsDZ9cQCSbXGbkwa9slfpJNWXQ5hKRAi2liB7tvEZ8yhrR4dFknGhotLrMqfcJssxkcfc9RfWiiR9myinbdt0+E1mSq50mqC0hOfRN2itvnfsx+Nnhf+zk1/bdotzw9X4a58yYUi9j0bvS2LzKjRRs3U6WPY57jak40NdNOx1nT/bpX+ZkEK76GhB/VZdL8uLItYRoFPH5yLb+XS9mGGSlYmYPRylKCh+Jg6oy5KkhM400Iwo11wujovXbV0ww0hN3iQcrc1wvqrapdAYlE0FB7hMrDyz8hOU5F/xwKt99VFF30MdpcpDMGNj1Ut34iqkY5ZgQNYKMCQZk2pDTHDyL2uxKSjN//4aZq6kxBTUUAAA//pyBFbOAADCGBVd4MMRqEQh+2MFiAoImGV3QJiiYRUW7Mj0iSiAAAlFk9AMfBK1H2LKytw3dccEkru+6r46yQwOLp3B9zEFF0E8Fio16/sJPdJCj36cWHCV0ShpdWLPUg0qMfY1u7MsRCbclgw9FrpVOgyZRUJ/+wb/OppJIvli3CQy8oHY5J8ZQ59GJzKhyWZc6QWgcdO7XJqTxdQ7TtRvTFCO+edgfiWE38jgCESckjdpYiAxyYBRanHP3yOyElcsiIfQNWzYDBpeVcoWxwIE5k0VHvD1b1yPYCq9MZ35JnLHZxFQuduDUCjQ0vm2dlgE//ikjOPyaCaYRUJNfK2IMZ7FN/iBrOUiq+cErzgitrZEnm3dFoa9Z8uz297o5ZxIwg6TRAiUnoBXvSLI1fVcz7W6G/QmIID/+nAE17wAAAIjNltpJRPAQQSbUz0iLAhoh141hYABGozt6pJQAIAAAACEZJbak+IlCaEYC5LzO1nkK2sjTB8IxPoRFR+YNVerj+99WfsVQaVuerLdUS5/SvyG1xloeyHEkpX9kVSz2f/yoATdluKZPQUFFAHCP9jhj6K99NS9LlXYejpZLs6P0LZ/07L+1RNdyiNlVmCSzKIeWYW1t0ZUhNnUSc2qyHWId/NA5Un4eVOASNPW00smLUleM9Idx+BoEecJ5w84NAoc4xbKx7M1CY6eF/7vQrpZtS+ePq71DJUNXOxhWpbSN+r9lv2uTgAIoJct28/AFBt5YjBjeomx7LSsKFxQIO6xJ3moPZ0de9R1x0V7lTh24FqVLMdF8MpkjfeptCEpTe0JantimrsTt1Ja0kXTEFNA//pyBHuZAAACHWFcljBAAkFADErACACIWKtsvPKAERgJbiuMUAIAIIMMPAMMB/ALCWxBUcDflvx4sZEQOwsiqrgTyPvSCG2p27f/+uv//2ef///oxzoRT///+zs1B6Gb////9yEUYZA8iAArI1e27bbbrrtsWwsSCJUDMBBDXray9RmNhaBGIO49bQqXYITbitDcdPUOpkgFOM5sqZir0K2NY3ax8v3/3tdeAEcPJGURM1dZSJG1sQce09XcFehAmKjHKrMY5WZttOo//7fXm+nbGnCJPdvLKSnQK/2kDylC+1UsRirLFsS+L6tYoACgRQbkcj7zCNHCEtxDFfjnxz5RMCw9TdjChACiFiVnspHMW6YAdgUSKgMSj0nS45wLpjjQZSA0BrDmz2ZT3nEbGGP63UJiCmooAAD/+nAErUkAAAIMGV1RYjH4QuJbOj0oRgjY+31DjFpxGAzuGIKNlsAQDFdrhUqs6MtVgtkQHqZ8CbgxZ2heZQZGdgL+ldtFmTaiCdqRcmBrQKeiWuv4lI0KhR/4i/X53UeqCu6tR4NeoAIAwQ7Lba2lQ4CPbWhA6Y8Ks9MGH+jW8MBou61FpdybLg+pFqYGIUtmxZRpoOlahKihuFbBeGRzn1VhzX0eV3ez/dAEjpRjabqNKhgsVUlPj77PjvLil+rnWU99CFvUt8ZD1XZD1SH3t7T5zJSuv6tZdvavzKyoEBhjKXw5ucw21lfxQDLl++kPgHEmW+saAapJGVcHrofjiY8LH4rm7taOdojOE3gGyAFvJLnWhwVpsKU3tKOJLPJ9F8YFZnfJUHTww2i4wZGNp9aDDmLQhCYA//pwBKGyAAACHxlabTygAEIDKzekmAAInYV4+MEAARYNLk8YMAJAAAAQAlu7f+ao7BbWVdn+zQ3KXWO1t9qBEHO4kHQ1SuIuF3Yz926lisEXhmrI296JNzl+KLeekLnLyFWM36LVp//d1QCAMdk2pmIhBeJcPiAnSVUY5dbfxd89Zof47rlMuUE977+0vHBaCrXqGQNZsek8sLBdS6LF691dTKre+rbSqunKUgAAlv7f3fAUDyfYLYCPzp4+/Tr1p1COChqOz60et+d0e3QvdC69aX+2hv3/tsn/X9KNzurn///9r87z/////yOjCHE4YADTbbkkkAhQoA9wNxPQFvFKKv1xx1XjgjBVJoFICc7snmueK/xt9ILqsepJ5hnHtqTppWHUEA4465qWAFTiC1WIRS9WitMQQP/6cgSarAAAAf8RXe8cwABD5yxt4pQBiBhlcaeYpcEbnywNhIkwgAABshKtbSV4jgsJlHGEWrSvzkkZ7GDplt52KTLYoDnoi7UlWuSpzXIWlPovMvdHXeiWcN0k9+Mf0y11LduKV4FNSXNOba3AYA9SaOY9ysJ2RwGZCB0EfqgxtD83pGgj+empHPqDLUcV1QYirnWhz01fTTpslLEGZKXHV7K+eLRqN22ygAAFrAv6Rt6t3AdNgIxWpdPOdH/Fz+4g632I3iD8vQ6iQZSVOL28XOgqMxKv89aIudDgdr8SjeV1fUs7yNv86SACllw+jwfUpg8m5pX8yAKfIdIJx3RnNpADkp5pU80LdIPC2xKPKBwiz+Te6dVzlMQtF7s9HVvpq3/b3fq3Doq3RbbVWFt9SYgpqKZlxybg//pwBDGbAAACDBlYGW9IkD6Ja1MkIrqJMPuBtFKAMRufLZ6QcAIENS0VrqACBzoYZxoCuwYKjGcqOu+hS/SwiPQU8cdqiQLGc6iUv+lajJQWKOlK20aaiMTJ6/Xiz2hSU3+S///2/pADbktvcSDJ610WC2Rzzl/zkP9LOyuq47kklnkvBr5Degg98L8fvxL/M3+j82+32Ee6dfVP1sNnFXR/ezbT6WQAw6oUpJJfJIjWeVG0DTsMFzajRzuQQ9DjsY3lo/apOj/dXl8jtovUv+u936E0lZ7jWVVcFS2tEkjl0jxZkAz7lZyJypsmh7KwQhFyS3TDlAUslmwXenVeK+o+WOoYxuiOrFEa9rhutqELpP8w5Md807p1Pb/pf6W0Q131z1QtLmRrXLOVZ9I8z1+rF62NuUmIIP/6cgTjogAAAfwYXb4YYARE4eyKwQyQiKyFf/whAAELDLC/jCAEAGGaERkEgAACgTSbBAyg7JxjbfpuFSnTzJUYn317zJBbCD1HAgGVDKoABBPMaS5Dpa20a9LP//nEf/9Inky5QAadeusl2zZ3T9gVGi7LYqWd5OcOfHainGVpQPVOV5xxFow+wLjAwkmJoDrWXIdJPueByGHxVZRRNZUXYNfzn//pJyaSkCBGQGSs60SSigiCMoNUV78Ty9zXPLTsRXOM8ruZwruDZKmtqguOQd56kGj7RHvgZ9281QVFLu/WYQ46iN5VIpqMP81XrgFRDdYR5WiACWQ7Ip+q3fYafHoTqr0dWXlnc7PK/CvJM7T45B1GexQ/I74GfdvNUFRRLr79ZhDjqI3lUimoAv81XrTEFNRQAAAA//pwBDTvAAACEkxe4EEVcDsBa6gEwwIJISNvJJxEgSWbLWDECVDEAmEqXQtSbBhauRUV2z8N3JZZI8h89hPJ7/huxTU2aX+TM5Bz8lNeczvHlmeULTz60ahmsfChU7XpPLO1uanvemYwABNASTmB1zeymv38CKUfONUTt4iv945biZIbfUPaIZJinErmi6Z9Txfa75JZZB9YVdX2L1uanvDqZiAACyQBGt0VFw7JlUDpc4IbC6xQxkhoV6Bz69eQeZ9W9ZRKUTbb7XoqKh7P5W7cNb6+0+zIdtMjMrIckLK1PJesJEcRHUE/ZabQAAIAM+5kUUGDedGEqbeyeDvmAm5pHggY+r+1D0fVvWUSaCNtt3lvRUVD2fyt2rDXYlYgvcI3KhgaoHCs7EQqdtxKKGqgZjX/WmIKaP/6cgSqyAAIghJAW0gsKGBDSAs1MQVoCDjXZyYkS0EJGyzI8RVgAAiMY07vlAGy0hRrMP5htuIosWbhE6rSFdBfX5cWVL/9DVblZJrf1bVfy/5f/oYzrW4i6pFR1y7hOd7VlfnpjU/jQAl/P4WCebNUGhMX6Le9xxvgcP9Qzcv6BjmHF9TetQpUdP/NVuqabfqOoVWtlFWn+Ct/1I5n+IuqRja7hOd7Vlf/+NAMH6/36iOB9YUqONh98t8DV/ld90Cj57y+Py6D3QbsZC//6PlV5mTqSnUpW3/bR+ghn6xdh609sqetbk09Cu/7esD7/zvSHjrcpJk/GMaMLyAQ+D5QAfgwQJDpEH8npIBf0+/VSvduyt1aoquj/tR2xg70jMTyyqdQhstRfu/TJ8POboTEFNRTMuOTcAAA//pwBOUjAAACGUDaOYcqsEMIC7wUIuOIuNd5oZROoREmrIj0iTAAoAjckmzcxMxs04p0IOTHxsL8VPsFgRNWLZyM9F7Fj3/Tz52ran09HkOtUq2qrfWgF39dFL+w2z1wqfxX6le/xQK0MEAEoQXpGKSU2UpRSbOz4zxggczgKOVqEfQ/ZZg3+ynMdFE9Az5MuQmo3MlSlW6cEXdOEft/B/kiruL/kLVG9vGKxuAJOKsam2nvpYNBqhwHhC4p4QbMJBl6X1CtoTsRSD/36tWp5Z1e6oRNHKZUr+1PvFa9ZRzkhm00yhZJaFEW/H7ROi7iltQFf/5rIsdKVhuJ7SAjbFaKG8/Sz9caPZvSMlc2leCAOxJSbafsps339tLkUE8i1fZE1bQdr9n/v3uv/0ov0wfVSjWf9SYggP/6cgQ9OwAAAiEVWujLOihDprwdHCLHiGyxZ0Swo4EXGyxo85UwgAAIDAQZSTsKk8ZJpSbibit8swXtYaoWTKEyr6NlBH2Heypzgw62rNj9qJ99Q8+nDtCkv7VbJWxAV1PXFS1YKsYzNPXCUhHZGnG2nEah7KynPMY0mmwPy1zAsHJhQ82xgr6E+xTel/JVh3PxLK5sqRQabXW9RTbgXXd/8+1U6S12z9iwld00swAQAAyRySDuCoiG9dH5kIK0pjuIp0k3zIOV4wNATuXQU9UtRt1Xs6tf9mahLTmWMM8ttalSznZPYxWrsQpf79vMfVXAAAAAbUcklaExL80JnBmvRIua4SmtUT3khMDOIA8+dC7PGAnrgl2HzK/npd1KzpZqN+m/n8rO9MUtatlOr/v478pzDbMqmIKa//pwBMZZAAAB/xVa0WkRUENmyyc9Ij4IFSF1IoRcsRMc7ygRiGaADNAFbrtmbzwWXouLrHafuIoZ5MH6ljJoKFNh+g+EmryeweGs1XU0RiEDkGtQRaW/pTfGe00sgpCxoz29aPqeAYANy22m53IUiCvxgSiv1/hFKP5K/2haFq/WFxvbsbd8f8gHX3tx/Klf5eprmOjpVFut13oHoi7vCiFbMK1UEaP9BqAVJxemtnkBSRrQZ6y+EjaCYqNEtwvVR+g73j2t7eNq3e3WDwSKrznOGRGHO/XlWk2pW/7yuM6UKuZ97t7es0PUiF0wSaaTl0JeuKwVauDvgXQOJ8dX1fUR5pw1vI3Vujt18nkWS17q2pxqOpVip/iZR8tZoxTOrODX7nLcCZSWS5TnuMySYgpqKZlxybgAAP/6cgRMhgAIwhoyWLnpEmA+4qsjBScMCJxlXuelaMEcoGxM9YkwACAG3bbrcx/LBSStolKgxkIaIvv5e/bJDtJ0UkKH4msK4jSRSDX927V/RHZsbp967ecQ54LQpmKhO/jfih7RGfxteoANS3YxYCQmHoAmMqBHurlO/Ba+PgRJrmC4UDOr6kc6AdZqhzdT5Co6bY5CZdzyS35w7XThP7bZVqzujr76dJAAErba42J4DcJquT2CwUwE4xXEN+7PGvOCyJjJVDdVlY3R6ZG+LrWOrVdTtl8HanL+J3rceH4dptIqUAn+ovUdt+337gkbLtoNLrD5abFWOhqG+KdRLznilfoB8M5nc4VRp2mHJ6XR1k29/27tu3T1r/V6JLSyou+87Jk8EyVspW01ILadb1jupG/IJiCmooAA//pwBMLLAAQiEidYmesqYEJGu3cJAiaIMTNk56Cq0RGgbuhhFT4gt27aurFEwKOpMj9sBtI3nUl/AfK9Yr+8Q3o1rLNhYroroMX6E/X+3QcWsggMzopJtFwhCHkbA2ka7qsd0i36v2JgsCm5bao0dxyCjHDbMukuJbQV4OZpbYNur69aJ5H7e3jedUvY73bgxQkqnLoOFWFngCEWXho5xQohHB375vf719iYACim5NTaKoSxHrg7h5rk6pRG9VbwB64VH5HSgSHcqdVR6m6N1J7fTv6dP/R6bd3Xsi7Wu/o/21+ptdo0xtxieirQCWm26wbEG0prcly8fF9SeSrQTiNn67GVejJyehXt3g7tkfo1q/F0RWrmc9reOnuLk4kzErtVIUECrtmszkHpWdGEPQmIKaimZccm4P/6cgRajgAAAig0XFElEuxDCAs3PQJYiFk3YmekS0ESnG6oMRo+wAQQIsJJxA64Hxc5wlEIRJ7Y7z+eNxMwphATqq1FMvo/UudDent6l3X1+Ou4lKcxVNHKzca69bcYSF0tbQVnGza2tVLLgAAU3JJAmy/cHJJOjHmLTU1dO3hC/u9zOIdWnDjaP+Oy+j+vv1M+o32+1Wv3LyarbRrbMOaTqF9b2tdco6/6Tjl9KgAFvv6burgjZjLonool4w8BDPqJn8e3+LG5WoXsbhWZMEbXUm3v///DE6plV03ZsGOXne6LbvzFbS+r//9e/gvkP64AEFALTjVQ+GPBIFH1meXt6N5XtUG2j/s/3FeZu7c/gvVdU9a6isqIFvttjZ6SBg8DYcUL1oV6DtezyHYl48AjzzbUKSmIKaig//pwBML5AAACFT7ZOC8oZEMoK5oEwgyIbPt1RJRJ8Qqsb3QRiC4CQIJUkifSIP+inJCoMi1QoNNTPL4cbEgNfCnEp1GpiR9b2f9PX19ft7f9fZJ5DKbTqj0uewgPVYo6mv73EN7e+hdSAEshJu3b2xejzyKtHd2zNnENU4b0deozwRv318nt6+rdG6jf1q+p36I6H+X6rmFqqsILTt6CaB2latKZmFSAFL2KoQACYlNJt37y73HVRywSP98PtlCxdcIRupX1B9XVHHm8jcE3I/t0+vT6ebqY6oRBHVvZ1uGCSrrd/nvTHP73ji1jZjakABCgCk245rUMpgyGqcFpR8Dbr6Qb1K2CF/q9PP6ffo3r6+3p6LuCeiII5WXo2oJOvr//zE0P8/uq+7RbqSwGHymhMQU1FAAAAP/6cgSaWgAEwg4c1ZMPOmBAJ9sjPKJYiHyBaUSwo2EaDKwMxIj4ARvMZ6aGBr9QtIDpjDcVggA6vmVm5ju0d4J6ufo2Iwi4+8EydRLVa7E2icFG850j/j94j9NTKtm6zZXt3V7qHXegAspyTOLvDZVhZqZowhNJ7h1KgUfwFB+JSD1VtAD3qAQfQSRVneZE+/Q3p/09H6Gbr6/bghL3OlXLO2hij+yV04ABKSySrwSGWFtQmKpwDJ12+uw9kHpUJBq6HNqLdAFJdcx7c9j6D7RUoXDYoH3LiokDtS2EmhFzszpQ09FsQdCN//+gAJOSC/IQ9AzGA4gWBoRAU5+0ZPu/ERusTDw9P1RsafPTTVQKTXhSGg+cfWiG7XRYaU1SCA+EbhqkyguQaw411C9CGz/WilMQU1FAAAAA//pwBM5gAA/yEQxUmGZIEkCCSoIZIxpAAAGkAAAAIAAANIAAAAQFt22iiTkwkUjEoBhonHUU2UnoZsrTZlN/2JJZjv5ObKSf1hLlfUpr+sJd7mlmt/nGlEu/3v7XOOY7llrc1jv/+d2A3+SRgBDJIJiGFJwaVYRJ5rDWH3UmBjBnEzFXff/Jf3mO73lNKtsJd5RZRVvNKkmkuWt7va5z2O5Za3NY5z+c6mIKaimZccm4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
diff --git a/media2/media2-player/src/androidTest/res/raw/testvideo.3gp b/media2/media2-player/src/androidTest/res/raw/testvideo.3gp
deleted file mode 100644
index 8329311..0000000
--- a/media2/media2-player/src/androidTest/res/raw/testvideo.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4 b/media2/media2-player/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4
deleted file mode 100755
index b8dce17..0000000
--- a/media2/media2-player/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv b/media2/media2-player/src/androidTest/res/raw/video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv
deleted file mode 100644
index dd6d3ab..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp
deleted file mode 100644
index e6dfdcd..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp
deleted file mode 100644
index 0d73ba2..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp
deleted file mode 100644
index 4d63192..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp
deleted file mode 100644
index c7a04f0..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp
deleted file mode 100644
index 63add5e..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp
deleted file mode 100644
index 25103ee..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp
deleted file mode 100644
index d38a8b6..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
deleted file mode 100644
index c0bef56..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp
deleted file mode 100644
index aee6c61..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp
deleted file mode 100644
index d2a6ddf..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp
deleted file mode 100644
index b7c2bed..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp
deleted file mode 100644
index 4b10c21..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp
deleted file mode 100644
index 9ce8a32..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp
deleted file mode 100644
index b39c665..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp
deleted file mode 100644
index e50f329..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp
deleted file mode 100644
index cc50019..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp b/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp
deleted file mode 100644
index 22a7b8b..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index 601dda1..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index 571ff44..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index 5772810..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index 36cd1b1..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4
deleted file mode 100644
index c321586..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
deleted file mode 100644
index 63e25b8..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index c5bae27..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4
deleted file mode 100644
index 5f7c928..0000000
--- a/media2/media2-player/src/androidTest/res/raw/video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/AudioFocusHandler.java b/media2/media2-player/src/main/java/androidx/media2/player/AudioFocusHandler.java
deleted file mode 100644
index b7f0e3c..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/AudioFocusHandler.java
+++ /dev/null
@@ -1,473 +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.media2.player;
-
-import static androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.media.AudioAttributesCompat;
-
-/**
- * Handles audio focus and noisy intent depending on the {@link AudioAttributesCompat}.
- * <p>
- * This follows our developer's guideline, and does nothing if the audio attribute hasn't set.
- *
- * @see {@docRoot}guide/topics/media-apps/audio-app/mediasession-callbacks.html
- * @see {@docRoot}guide/topics/media-apps/video-app/mediasession-callbacks.html
- * @see {@docRoot}guide/topics/media-apps/audio-focus.html
- * @see {@docRoot}guide/topics/media-apps/volume-and-earphones.html
- */
-/* package */ class AudioFocusHandler {
-    private static final String TAG = "AudioFocusHandler";
-    private static final boolean DEBUG = true;
-
-    interface AudioFocusHandlerImpl {
-        boolean onPlay();
-        void onPause();
-        void onReset();
-        void close();
-        void sendIntent(Intent intent);
-    }
-
-    private final AudioFocusHandlerImpl mImpl;
-
-    AudioFocusHandler(Context context, MediaPlayer player) {
-        mImpl = new AudioFocusHandlerImplBase(context, player);
-    }
-
-    /**
-     * Should be called when the {@link MediaPlayer#play()} is called. Returns whether the play()
-     * can be proceed.
-     *
-     * @return {@code true} if it's OK to start playback because audio focus was successfully
-     * granted or audio focus isn't needed for starting playback. {@code false} otherwise.
-     * (i.e. Audio focus is needed for starting playback but failed)
-     */
-    public boolean onPlay() {
-        return mImpl.onPlay();
-    }
-
-    /**
-     * Called when the {@link MediaPlayer#pause()} is called.
-     */
-    public void onPause() {
-        mImpl.onPause();
-    }
-
-    /**
-     * Called when the {@link MediaPlayer#reset()} is called.
-     */
-    public void onReset() {
-        mImpl.onReset();
-    }
-
-    /**
-     * Closes this resource, relinquishing any underlying resources.
-     */
-    public void close() {
-        mImpl.close();
-    }
-
-    /**
-     * Testing purpose.
-     *
-     * @param intent
-     */
-    public void sendIntent(Intent intent) {
-        mImpl.sendIntent(intent);
-    }
-
-    private static class AudioFocusHandlerImplBase implements AudioFocusHandlerImpl {
-        // Value is from the {@link AudioFocusRequest} as follows
-        // 'A typical attenuation by the “ducked” application is a factor of 0.2f (or -14dB), that
-        // can for instance be applied with MediaPlayer.setVolume(0.2f) when using this class for
-        // playback.'
-        private static final float VOLUME_DUCK_FACTOR = 0.2f;
-        private final BroadcastReceiver mBecomingNoisyReceiver = new BecomingNoisyReceiver();
-        private final IntentFilter mIntentFilter =
-                new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
-        private final OnAudioFocusChangeListener mAudioFocusListener = new AudioFocusListener();
-        final Object mLock = new Object();
-        private final Context mContext;
-        final MediaPlayer mPlayer;
-        private final AudioManager mAudioManager;
-
-        @GuardedBy("mLock")
-        AudioAttributesCompat mAudioAttributes;
-        @GuardedBy("mLock")
-        private int mCurrentFocusGainType;
-        @GuardedBy("mLock")
-        boolean mResumeWhenAudioFocusGain;
-        @GuardedBy("mLock")
-        boolean mBecomingNoisyReceiverRegistered;
-
-        AudioFocusHandlerImplBase(Context context, MediaPlayer player) {
-            mContext = context;
-            mPlayer = player;
-
-            // Cannot use session.getContext() because session's impl isn't initialized at this
-            // moment.
-            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        }
-
-        @Override
-        public boolean onPlay() {
-            final AudioAttributesCompat attrs = mPlayer.getAudioAttributes();
-            boolean result = true;
-            synchronized (mLock) {
-                mAudioAttributes = attrs;
-                // Checks whether the audio attributes is {@code null}, to check indirectly whether
-                // the media item has audio track.
-                if (attrs == null) {
-                    // No sound.
-                    abandonAudioFocusLocked();
-                    unregisterBecomingNoisyReceiverLocked();
-                } else {
-                    result = requestAudioFocusLocked();
-                    if (result) {
-                        registerBecomingNoisyReceiverLocked();
-                    }
-                }
-            }
-            return result;
-        }
-
-        @Override
-        public void onPause() {
-            synchronized (mLock) {
-                mResumeWhenAudioFocusGain = false;
-                unregisterBecomingNoisyReceiverLocked();
-            }
-        }
-
-        @Override
-        public void onReset() {
-            synchronized (mLock) {
-                abandonAudioFocusLocked();
-                unregisterBecomingNoisyReceiverLocked();
-            }
-        }
-
-        @Override
-        public void close() {
-            synchronized (mLock) {
-                unregisterBecomingNoisyReceiverLocked();
-                abandonAudioFocusLocked();
-            }
-        }
-
-        @Override
-        public void sendIntent(Intent intent) {
-            mBecomingNoisyReceiver.onReceive(mContext, intent);
-        }
-
-        /**
-         * Requests audio focus. This may regain audio focus.
-         *
-         * @return {@code true} if audio focus is granted or isn't needed.
-         *         {@code false} only when the attempt to request audio focus was failed.
-         */
-        @GuardedBy("mLock")
-        private boolean requestAudioFocusLocked() {
-            int focusGain = convertAudioAttributesToFocusGain(mAudioAttributes);
-            if (focusGain == AudioManager.AUDIOFOCUS_NONE) {
-                if (mAudioAttributes == null && DEBUG) {
-                    // If audio attributes is null, it should be handled outside to set volume
-                    // to zero without holding an lock.
-                    Log.e(TAG, "requestAudioFocusLocked() shouldn't be called when AudioAttributes"
-                            + " is null");
-                }
-                return true;
-            }
-            // Note: This API is deprecated from the API level 26, but there's not much reason to
-            // use the new API for now.
-            int audioFocusRequestResult = mAudioManager.requestAudioFocus(mAudioFocusListener,
-                    mAudioAttributes.getVolumeControlStream(), focusGain);
-            if (audioFocusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-                mCurrentFocusGainType = focusGain;
-            } else {
-                Log.w(TAG, "requestAudioFocus(" + focusGain + ") failed (return="
-                        + audioFocusRequestResult + ") playback wouldn't start.");
-                mCurrentFocusGainType = AudioManager.AUDIOFOCUS_NONE;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "requestAudioFocus(" + focusGain + "), result="
-                        + (audioFocusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
-            }
-            mResumeWhenAudioFocusGain = false;
-            return mCurrentFocusGainType != AudioManager.AUDIOFOCUS_NONE;
-        }
-
-        /**
-         * Abandons audio focus if it has granted.
-         */
-        @GuardedBy("mLock")
-        private void abandonAudioFocusLocked() {
-            if (mCurrentFocusGainType == AudioManager.AUDIOFOCUS_NONE) {
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "abandoningAudioFocusLocked, currently=" + mCurrentFocusGainType);
-            }
-            mAudioManager.abandonAudioFocus(mAudioFocusListener);
-            mCurrentFocusGainType = AudioManager.AUDIOFOCUS_NONE;
-            mResumeWhenAudioFocusGain = false;
-        }
-
-        @GuardedBy("mLock")
-        private void registerBecomingNoisyReceiverLocked() {
-            if (mBecomingNoisyReceiverRegistered) {
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "registering becoming noisy receiver");
-            }
-            // Registering the receiver multiple-times may not be allowed for newer platform.
-            // Register only when it's not registered.
-            if (Build.VERSION.SDK_INT < 33) {
-                mContext.registerReceiver(mBecomingNoisyReceiver, mIntentFilter);
-            } else {
-                Api33.registerReceiver(mContext, mBecomingNoisyReceiver, mIntentFilter,
-                        Context.RECEIVER_NOT_EXPORTED);
-            }
-            mBecomingNoisyReceiverRegistered = true;
-        }
-
-        @GuardedBy("mLock")
-        private void unregisterBecomingNoisyReceiverLocked() {
-            if (!mBecomingNoisyReceiverRegistered) {
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "unregistering becoming noisy receiver");
-            }
-            mContext.unregisterReceiver(mBecomingNoisyReceiver);
-            mBecomingNoisyReceiverRegistered = false;
-        }
-
-        // Converts {@link AudioAttributesCompat} to one of the audio focus request. This follows
-        // the class Javadoc of {@link AudioFocusRequest}.
-        // Note: Any change here should also reflects public Javadoc of {@link MediaSession}.
-        private static int convertAudioAttributesToFocusGain(
-                final AudioAttributesCompat audioAttributesCompat) {
-
-            if (audioAttributesCompat == null) {
-                // Don't handle audio focus. It may be either video only contents or developers
-                // want to have more finer grained control. (e.g. adding audio focus listener)
-                return AudioManager.AUDIOFOCUS_NONE;
-            }
-            // Javadoc here means 'The different types of focus requests' written in the
-            // {@link AudioFocusRequest}.
-            switch (audioAttributesCompat.getUsage()) {
-                // USAGE_VOICE_COMMUNICATION_SIGNALLING is for DTMF that may happen multiple times
-                // during the phone call when AUDIOFOCUS_GAIN_TRANSIENT is requested for that.
-                // Don't request audio focus here.
-                case AudioAttributesCompat.USAGE_VOICE_COMMUNICATION_SIGNALLING:
-                    return AudioManager.AUDIOFOCUS_NONE;
-
-                // Javadoc says 'AUDIOFOCUS_GAIN: Examples of uses of this focus gain are for music
-                // playback, for a game or a video player'
-                case AudioAttributesCompat.USAGE_GAME:
-                case AudioAttributesCompat.USAGE_MEDIA:
-                    return AudioManager.AUDIOFOCUS_GAIN;
-
-                // Special usages: USAGE_UNKNOWN shouldn't be used. Request audio focus to prevent
-                // multiple media playback happen at the same time.
-                case AudioAttributesCompat.USAGE_UNKNOWN:
-                    Log.w(TAG, "Specify a proper usage in the audio attributes for audio focus"
-                            + " handling. Using AUDIOFOCUS_GAIN by default.");
-                    return AudioManager.AUDIOFOCUS_GAIN;
-
-                // Javadoc says 'AUDIOFOCUS_GAIN_TRANSIENT: An example is for playing an alarm, or
-                // during a VoIP call'
-                case AudioAttributesCompat.USAGE_ALARM:
-                case AudioAttributesCompat.USAGE_VOICE_COMMUNICATION:
-                    return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
-
-                // Javadoc says 'AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: Examples are when playing
-                // driving directions or notifications'
-                case AudioAttributesCompat.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
-                case AudioAttributesCompat.USAGE_ASSISTANCE_SONIFICATION:
-                case AudioAttributesCompat.USAGE_NOTIFICATION:
-                case AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
-                case AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
-                case AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
-                case AudioAttributesCompat.USAGE_NOTIFICATION_EVENT:
-                case AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE:
-                    return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-
-                // Javadoc says 'AUDIOFOCUS_GAIN_EXCLUSIVE: This is typically used if you are doing
-                // audio recording or speech recognition'.
-                // Assistant is considered as both recording and notifying developer
-                case AudioAttributesCompat.USAGE_ASSISTANT:
-                    return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
-
-                // Special usages:
-                case AudioAttributesCompat.USAGE_ASSISTANCE_ACCESSIBILITY:
-                    if (audioAttributesCompat.getContentType()
-                            == AudioAttributesCompat.CONTENT_TYPE_SPEECH) {
-                        // Voice shouldn't be interrupted by other playback.
-                        return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
-                    }
-                    return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-            }
-            Log.w(TAG, "Unidentified AudioAttribute " + audioAttributesCompat);
-            return AudioManager.AUDIOFOCUS_NONE;
-        }
-
-        private class BecomingNoisyReceiver extends BroadcastReceiver {
-            BecomingNoisyReceiver() {
-            }
-
-            // Note: This is always the main thread, except for the test.
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (!AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
-                    return;
-                }
-                final int usage;
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Received noisy intent, intent=" + intent + ", registered="
-                                + mBecomingNoisyReceiverRegistered + ", attr=" + mAudioAttributes);
-                    }
-                    if (!mBecomingNoisyReceiverRegistered || mAudioAttributes == null) {
-                        return;
-                    }
-                    usage = mAudioAttributes.getUsage();
-                }
-                switch (usage) {
-                    case AudioAttributesCompat.USAGE_MEDIA:
-                        // Noisy intent guide says 'In the case of music players, users
-                        // typically expect the playback to be paused.'
-                        mPlayer.pause();
-                        break;
-                    case AudioAttributesCompat.USAGE_GAME:
-                        // Noisy intent guide says 'For gaming apps, you may choose to
-                        // significantly lower the volume instead'.
-                        mPlayer.setPlayerVolume(mPlayer.getPlayerVolume() * VOLUME_DUCK_FACTOR);
-                        break;
-                    default:
-                        // Noisy intent guide didn't say anything more for this. No-op for now.
-                        break;
-                }
-            }
-        }
-
-        private class AudioFocusListener implements OnAudioFocusChangeListener {
-            private float mPlayerVolumeBeforeDucking;
-            private float mPlayerDuckingVolume;
-
-            AudioFocusListener() {
-            }
-
-            // This is the thread where the AudioManager was originally instantiated.
-            // see: b/78617702
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onAudioFocusChange(int focusGain) {
-                switch (focusGain) {
-                    case AudioManager.AUDIOFOCUS_GAIN:
-                        // Regains focus after the LOSS_TRANSIENT or LOSS_TRANSIENT_CAN_DUCK.
-                        if (mPlayer.getPlayerState() == PLAYER_STATE_PAUSED) {
-                            // Note: onPlay() will be called again with this.
-                            synchronized (mLock) {
-                                if (!mResumeWhenAudioFocusGain) {
-                                    break;
-                                }
-                            }
-                            mPlayer.play();
-                        } else {
-                            // Resets the volume if the user didn't change it.
-                            final float currentVolume = mPlayer.getPlayerVolume();
-                            final float volumeBeforeDucking;
-                            synchronized (mLock) {
-                                if (currentVolume != mPlayerDuckingVolume) {
-                                    // User manually changed the volume meanwhile. Don't reset.
-                                    break;
-                                }
-                                volumeBeforeDucking = mPlayerVolumeBeforeDucking;
-                            }
-                            mPlayer.setPlayerVolume(volumeBeforeDucking);
-                        }
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS:
-                        // Audio-focus developer guide says 'Your app should pause playback
-                        // immediately, as it won't ever receive an AUDIOFOCUS_GAIN callback'.
-                        mPlayer.pause();
-                        // Don't resume even after you regain the audio focus.
-                        synchronized (mLock) {
-                            mResumeWhenAudioFocusGain = false;
-                        }
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                        final boolean pause;
-                        synchronized (mLock) {
-                            if (mAudioAttributes == null) {
-                                // This shouldn't happen. Just ignoring for now.
-                                break;
-                            }
-                            pause = (mAudioAttributes.getContentType()
-                                    == AudioAttributesCompat.CONTENT_TYPE_SPEECH);
-                        }
-                        if (pause) {
-                            mPlayer.pause();
-                        } else {
-                            // Lower the volume by the factor
-                            final float currentVolume = mPlayer.getPlayerVolume();
-                            final float duckingVolume = currentVolume * VOLUME_DUCK_FACTOR;
-                            synchronized (mLock) {
-                                mPlayerVolumeBeforeDucking = currentVolume;
-                                mPlayerDuckingVolume = duckingVolume;
-                            }
-                            mPlayer.setPlayerVolume(duckingVolume);
-                        }
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                        mPlayer.pause();
-                        // Resume after regaining the audio focus.
-                        synchronized (mLock) {
-                            mResumeWhenAudioFocusGain = true;
-                        }
-                        break;
-                }
-            }
-        }
-    }
-
-    @RequiresApi(33)
-    private static class Api33 {
-        @DoNotInline
-        static void registerReceiver(@NonNull Context context, @NonNull BroadcastReceiver receiver,
-                @NonNull IntentFilter filter, int flags) {
-            context.registerReceiver(receiver, filter, flags);
-        }
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/ByteArrayFrame.java b/media2/media2-player/src/main/java/androidx/media2/player/ByteArrayFrame.java
deleted file mode 100644
index b3b6488..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/ByteArrayFrame.java
+++ /dev/null
@@ -1,109 +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.media2.player;
-
-import android.os.Parcel;
-
-import androidx.annotation.Nullable;
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.metadata.Metadata;
-import androidx.media2.exoplayer.external.util.Util;
-
-import java.util.Arrays;
-
-/**
- * Metadata entry consisting of an ID3 frame as a byte array.
- */
-/* package */ final class ByteArrayFrame implements Metadata.Entry {
-
-    public final long mTimestamp;
-    public final byte[] mData;
-
-    /** Creates a new byte array frame. */
-    ByteArrayFrame(long timestamp, byte[] data) {
-        mTimestamp = timestamp;
-        mData = data;
-    }
-
-    /* package */ ByteArrayFrame(Parcel in) {
-        mTimestamp = in.readLong();
-        int length = in.readInt();
-        byte[] data = new byte[length];
-        in.readByteArray(data);
-        mData = data;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        ByteArrayFrame other = (ByteArrayFrame) obj;
-        return Util.areEqual(mTimestamp, other.mTimestamp) && Arrays.equals(mData, other.mData);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + (int) mTimestamp;
-        result = 31 * result + Arrays.hashCode(mData);
-        return result;
-    }
-
-    // Parcelable implementation.
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeLong(mTimestamp);
-        dest.writeByteArray(mData);
-    }
-
-    public static final Creator<ByteArrayFrame> CREATOR =
-            new Creator<ByteArrayFrame>() {
-
-        @Override
-        public ByteArrayFrame createFromParcel(Parcel in) {
-            return new ByteArrayFrame(in);
-        }
-
-        @Override
-        public ByteArrayFrame[] newArray(int size) {
-            return new ByteArrayFrame[size];
-        }
-
-    };
-
-    @Nullable
-    @Override
-    public Format getWrappedMetadataFormat() {
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public byte[] getWrappedMetadataBytes() {
-        return null;
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/DataSourceCallbackDataSource.java b/media2/media2-player/src/main/java/androidx/media2/player/DataSourceCallbackDataSource.java
deleted file mode 100644
index 64b4eda..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/DataSourceCallbackDataSource.java
+++ /dev/null
@@ -1,121 +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.media2.player;
-
-import android.net.Uri;
-
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.upstream.BaseDataSource;
-import androidx.media2.exoplayer.external.upstream.DataSource;
-import androidx.media2.exoplayer.external.upstream.DataSpec;
-
-import java.io.EOFException;
-import java.io.IOException;
-
-/**
- * An ExoPayer {@link DataSource} for reading from a {@link DataSourceCallback}.
- */
-@SuppressWarnings("unchecked")
-/* package */ final class DataSourceCallbackDataSource extends BaseDataSource {
-
-    /**
-     * Returns a factory for {@link DataSourceCallbackDataSource}s.
-     *
-     * @return A factory for data sources that read from the data source callback.
-     */
-    static DataSource.Factory getFactory(
-            final DataSourceCallback dataSourceCallback) {
-        return new DataSource.Factory() {
-            @Override
-            public DataSource createDataSource() {
-                return new DataSourceCallbackDataSource(dataSourceCallback);
-            }
-        };
-    }
-
-    private final DataSourceCallback mDataSourceCallback;
-
-    @Nullable
-    private Uri mUri;
-    private long mOffset;
-    private long mBytesRemaining;
-    private boolean mOpened;
-
-    DataSourceCallbackDataSource(DataSourceCallback dataSourceCallback) {
-        super(/* isNetwork= */ false);
-        mDataSourceCallback = Preconditions.checkNotNull(dataSourceCallback);
-    }
-
-    @Override
-    public long open(DataSpec dataSpec) throws IOException {
-        mUri = dataSpec.uri;
-        mOffset = dataSpec.position;
-        transferInitializing(dataSpec);
-        long dataSourceCallback2Size = mDataSourceCallback.getSize();
-        if (dataSpec.length != C.LENGTH_UNSET) {
-            mBytesRemaining = dataSpec.length;
-        } else if (dataSourceCallback2Size != -1) {
-            mBytesRemaining = dataSourceCallback2Size - mOffset;
-        } else {
-            mBytesRemaining = C.LENGTH_UNSET;
-        }
-        mOpened = true;
-        transferStarted(dataSpec);
-        return mBytesRemaining;
-    }
-
-    @Override
-    public int read(byte[] buffer, int offset, int readLength) throws IOException {
-        if (readLength == 0) {
-            return 0;
-        } else if (mBytesRemaining == 0) {
-            return C.RESULT_END_OF_INPUT;
-        }
-        int bytesToRead = mBytesRemaining == C.LENGTH_UNSET
-                ? readLength : (int) Math.min(mBytesRemaining, readLength);
-        int bytesRead = mDataSourceCallback.readAt(mOffset, buffer, offset, bytesToRead);
-        if (bytesRead < 0) {
-            if (mBytesRemaining != C.LENGTH_UNSET) {
-                throw new EOFException();
-            }
-            return C.RESULT_END_OF_INPUT;
-        }
-        mOffset += bytesRead;
-        if (mBytesRemaining != C.LENGTH_UNSET) {
-            mBytesRemaining -= bytesRead;
-        }
-        bytesTransferred(bytesRead);
-        return bytesRead;
-    }
-
-    @Override
-    public Uri getUri() {
-        return mUri;
-    }
-
-    @Override
-    public void close() {
-        mUri = null;
-        if (mOpened) {
-            mOpened = false;
-            transferEnded();
-        }
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerMediaPlayer2Impl.java b/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerMediaPlayer2Impl.java
deleted file mode 100644
index aa89b4e..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerMediaPlayer2Impl.java
+++ /dev/null
@@ -1,956 +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.media2.player;
-
-import android.content.Context;
-import android.media.MediaDrm;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.util.Pair;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.ObjectsCompat;
-import androidx.core.util.Preconditions;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.exoplayer.external.Player;
-
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * An implementation of {@link MediaPlayer2} based on a repackaged version of ExoPlayer.
- */
-/* package */ final class ExoPlayerMediaPlayer2Impl extends MediaPlayer2
-        implements ExoPlayerWrapper.Listener {
-
-    private static final String TAG = "ExoPlayerMediaPlayer2";
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final ExoPlayerWrapper mPlayer;
-
-    private final Handler mTaskHandler;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @GuardedBy("mTaskLock")
-    final ArrayDeque<Task> mPendingTasks;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Object mTaskLock;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @GuardedBy("mTaskLock")
-    Task mCurrentTask;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Object mLock;
-    @GuardedBy("mLock")
-    private Pair<Executor, EventCallback> mExecutorAndEventCallback;
-    @SuppressWarnings("unused")
-    @GuardedBy("mLock")
-    private Pair<Executor, DrmEventCallback> mExecutorAndDrmEventCallback;
-    @GuardedBy("mLock")
-    private HandlerThread mHandlerThread;
-
-    /** Creates a new ExoPlayer wrapper using the specified context. */
-    ExoPlayerMediaPlayer2Impl(@NonNull Context context) {
-        mHandlerThread = new HandlerThread("ExoMediaPlayer2Thread");
-        mHandlerThread.start();
-        mPlayer = new ExoPlayerWrapper(
-                context.getApplicationContext(),
-                /* listener= */ this,
-                mHandlerThread.getLooper());
-        // Player callbacks will be called on the task handler thread.
-        mTaskHandler = new Handler(mPlayer.getLooper());
-        mPendingTasks = new ArrayDeque<>();
-        mTaskLock = new Object();
-        mLock = new Object();
-        resetPlayer();
-    }
-
-    // Command queue and events implementation.
-
-    @Override
-    public Object notifyWhenCommandLabelReached(@NonNull final Object label) {
-        return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
-            @Override
-            void process() {
-                notifyMediaPlayer2Event(new Mp2EventNotifier() {
-                    @Override
-                    public void notify(EventCallback cb) {
-                        cb.onCommandLabelReached(ExoPlayerMediaPlayer2Impl.this, label);
-                    }
-                });
-            }
-        });
-    }
-
-    @Override
-    public void clearPendingCommands() {
-        synchronized (mTaskLock) {
-            mPendingTasks.clear();
-        }
-    }
-
-    @Override
-    public boolean cancel(Object token) {
-        synchronized (mTaskLock) {
-            return mPendingTasks.remove(token);
-        }
-    }
-
-    private Object addTask(Task task) {
-        synchronized (mTaskLock) {
-            mPendingTasks.add(task);
-            processPendingTask();
-        }
-        return task;
-    }
-
-    @GuardedBy("mTaskLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void processPendingTask() {
-        if (mCurrentTask == null && !mPendingTasks.isEmpty()) {
-            Task task = mPendingTasks.removeFirst();
-            mCurrentTask = task;
-            mTaskHandler.post(task);
-        }
-    }
-
-    @Override
-    public void setEventCallback(@NonNull Executor executor, @NonNull EventCallback eventCallback) {
-        Preconditions.checkNotNull(executor);
-        Preconditions.checkNotNull(eventCallback);
-        synchronized (mLock) {
-            mExecutorAndEventCallback = Pair.create(executor, eventCallback);
-        }
-    }
-
-    @Override
-    public void clearEventCallback() {
-        synchronized (mLock) {
-            mExecutorAndEventCallback = null;
-        }
-    }
-
-    @Override
-    public void setDrmEventCallback(@NonNull Executor executor,
-            @NonNull DrmEventCallback eventCallback) {
-        Preconditions.checkNotNull(executor);
-        Preconditions.checkNotNull(eventCallback);
-        synchronized (mLock) {
-            mExecutorAndDrmEventCallback = Pair.create(executor, eventCallback);
-        }
-    }
-
-    @Override
-    public void clearDrmEventCallback() {
-        synchronized (mLock) {
-            mExecutorAndDrmEventCallback = null;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) {
-        final Pair<Executor, EventCallback> executorAndEventCallback;
-        synchronized (mLock) {
-            executorAndEventCallback = mExecutorAndEventCallback;
-        }
-        if (executorAndEventCallback != null) {
-            Executor executor = executorAndEventCallback.first;
-            final EventCallback eventCallback = executorAndEventCallback.second;
-            try {
-                executor.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifier.notify(eventCallback);
-                    }
-                });
-            } catch (RejectedExecutionException e) {
-                // The given executor is shutting down.
-                Log.w(TAG, "The given executor is shutting down. Ignoring the player event.");
-            }
-        }
-    }
-
-    // Player implementation.
-
-    @Override
-    public Object setAudioSessionId(final int sessionId) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
-            @Override
-            void process() {
-                mPlayer.setAudioSessionId(sessionId);
-            }
-        });
-    }
-
-    @Override
-    public Object setMediaItem(@NonNull final MediaItem item) {
-        return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
-            @Override
-            void process() {
-                mPlayer.setMediaItem(item);
-            }
-        });
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return runPlayerCallableBlocking(new Callable<MediaItem>() {
-            @Override
-            public MediaItem call() throws Exception {
-                return mPlayer.getCurrentMediaItem();
-            }
-        });
-    }
-
-    @Override
-    public Object prepare() {
-        return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
-            @Override
-            void process() {
-                mPlayer.prepare();
-            }
-        });
-    }
-
-    @Override
-    public Object play() {
-        return addTask(new Task(CALL_COMPLETED_PLAY, false) {
-            @Override
-            void process() {
-                mPlayer.play();
-            }
-        });
-    }
-
-    @Override
-    public Object pause() {
-        return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
-            @Override
-            void process() {
-                mPlayer.pause();
-            }
-        });
-    }
-
-    @Override
-    public Object seekTo(final long msec, final int mode) {
-        return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
-            @Override
-            void process() {
-                mPlayer.seekTo(msec, mode);
-            }
-        });
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return runPlayerCallableBlocking(new Callable<Long>() {
-            @Override
-            public Long call() throws Exception {
-                return mPlayer.getCurrentPosition();
-            }
-        });
-    }
-
-    @Override
-    public long getDuration() {
-        return runPlayerCallableBlocking(new Callable<Long>() {
-            @Override
-            public Long call() throws Exception {
-                return mPlayer.getDuration();
-            }
-        });
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return runPlayerCallableBlocking(new Callable<Long>() {
-            @Override
-            public Long call() throws Exception {
-                return mPlayer.getBufferedPosition();
-            }
-        });
-    }
-
-    @Override
-    public @MediaPlayer2.MediaPlayer2State int getState() {
-        return runPlayerCallableBlocking(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return mPlayer.getState();
-            }
-        });
-    }
-
-    @Override
-    public Object loopCurrent(final boolean loop) {
-        return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
-            @Override
-            void process() {
-                mPlayer.loopCurrent(loop);
-            }
-        });
-    }
-
-    @Override
-    public Object skipToNext() {
-        return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
-            @Override
-            void process() {
-                mPlayer.skipToNext();
-            }
-        });
-    }
-
-    @Override
-    public Object setNextMediaItem(@NonNull final MediaItem item) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
-            @Override
-            void process() {
-                mPlayer.setNextMediaItem(item);
-            }
-        });
-    }
-
-    @Override
-    public Object setNextMediaItems(@NonNull final List<MediaItem> items) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
-            @Override
-            void process() {
-                mPlayer.setNextMediaItems(items);
-            }
-        });
-    }
-
-    @Override
-    public Object setAudioAttributes(@NonNull final AudioAttributesCompat attributes) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
-            @Override
-            void process() {
-                mPlayer.setAudioAttributes(attributes);
-            }
-        });
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return runPlayerCallableBlocking(new Callable<AudioAttributesCompat>() {
-            @Override
-            public AudioAttributesCompat call() throws Exception {
-                return mPlayer.getAudioAttributes();
-            }
-        });
-    }
-
-    @Override
-    public int getAudioSessionId() {
-        return runPlayerCallableBlocking(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return mPlayer.getAudioSessionId();
-            }
-        });
-    }
-
-    @Override
-    public Object attachAuxEffect(final int effectId) {
-        return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
-            @Override
-            void process() {
-                mPlayer.attachAuxEffect(effectId);
-            }
-        });
-    }
-
-    @Override
-    public Object setAuxEffectSendLevel(final float auxEffectSendLevel) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
-            @Override
-            void process() {
-                mPlayer.setAuxEffectSendLevel(auxEffectSendLevel);
-            }
-        });
-    }
-
-    @Override
-    public Object setPlaybackParams(@NonNull final PlaybackParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
-            @Override
-            void process() {
-                mPlayer.setPlaybackParams(params);
-            }
-        });
-    }
-
-    @Override
-    @NonNull
-    public PlaybackParams getPlaybackParams() {
-        return runPlayerCallableBlocking(new Callable<PlaybackParams>() {
-            @Override
-            public PlaybackParams call() throws Exception {
-                return mPlayer.getPlaybackParams();
-            }
-        });
-    }
-
-    @Override
-    public int getVideoWidth() {
-        return runPlayerCallableBlocking(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return mPlayer.getVideoWidth();
-            }
-        });
-    }
-
-    @Override
-    public int getVideoHeight() {
-        return runPlayerCallableBlocking(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return mPlayer.getVideoHeight();
-            }
-        });
-    }
-
-    @Override
-    public Object setSurface(final Surface surface) {
-        return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
-            @Override
-            void process() {
-                mPlayer.setSurface(surface);
-            }
-        });
-    }
-
-    @Override
-    public Object setPlayerVolume(final float volume) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
-            @Override
-            void process() {
-                mPlayer.setVolume(volume);
-            }
-        });
-    }
-
-    @Override
-    public float getPlayerVolume() {
-        return runPlayerCallableBlocking(new Callable<Float>() {
-            @Override
-            public Float call() throws Exception {
-                return mPlayer.getVolume();
-            }
-        });
-    }
-
-    @Override
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        return runPlayerCallableBlocking(new Callable<List<TrackInfo>>() {
-            @Override
-            public List<TrackInfo> call() throws Exception {
-                return mPlayer.getTracks();
-            }
-        });
-    }
-
-    @Override
-    @Nullable
-    public TrackInfo getSelectedTrack(final int trackType) {
-        return runPlayerCallableBlocking(new Callable<TrackInfo>() {
-            @Override
-            public TrackInfo call() {
-                return mPlayer.getSelectedTrack(trackType);
-            }
-        });
-    }
-
-    @Override
-    @NonNull
-    public Object selectTrack(final int trackId) {
-        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
-            @Override
-            void process() {
-                mPlayer.selectTrack(trackId);
-            }
-        });
-    }
-
-    @Override
-    @NonNull
-    public Object deselectTrack(final int trackId) {
-        return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
-            @Override
-            void process() {
-                mPlayer.deselectTrack(trackId);
-            }
-        });
-    }
-
-    @Override
-    @RequiresApi(21)
-    public PersistableBundle getMetrics() {
-        return runPlayerCallableBlocking(new Callable<PersistableBundle>() {
-            @Override
-            public PersistableBundle call() throws Exception {
-                return mPlayer.getMetricsV21();
-            }
-        });
-    }
-
-    @Override
-    public MediaTimestamp getTimestamp() {
-        return runPlayerCallableBlocking(new Callable<MediaTimestamp>() {
-            @Override
-            public MediaTimestamp call() {
-                return mPlayer.getTimestamp();
-            }
-        });
-    }
-
-    @Override
-    public void reset() {
-        clearPendingCommands();
-
-        // Make sure that the current task finishes.
-        Task currentTask;
-        synchronized (mTaskLock) {
-            currentTask = mCurrentTask;
-        }
-        if (currentTask != null) {
-            synchronized (currentTask) {
-                try {
-                    while (!currentTask.mDone) {
-                        currentTask.wait();
-                    }
-                } catch (InterruptedException e) {
-                    // Suppress interruption.
-                }
-            }
-        }
-        runPlayerCallableBlocking(new Callable<Void>() {
-            @Override
-            public Void call() {
-                mPlayer.reset();
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void close() {
-        clearEventCallback();
-        HandlerThread handlerThread;
-        synchronized (mLock) {
-            handlerThread = mHandlerThread;
-            if (handlerThread == null) {
-                return;
-            }
-            mHandlerThread = null;
-        }
-        final ResolvableFuture<Void> future = ResolvableFuture.create();
-        mTaskHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mPlayer.close();
-                    future.set(null);
-                } catch (Throwable e) {
-                    future.setException(e);
-                }
-            }
-        });
-        getPlayerFuture(future);
-        handlerThread.quit();
-    }
-
-    @Override
-    public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public DrmInfo getDrmInfo() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Object prepareDrm(@NonNull final UUID uuid) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void releaseDrm() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @NonNull
-    public MediaDrm.KeyRequest getDrmKeyRequest(byte[] keySetId, byte[] initData, String mimeType,
-            int keyType, Map<String, String> optionalParameters) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void restoreDrmKeys(@NonNull byte[] keySetId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @NonNull
-    public String getDrmPropertyString(@NonNull String propertyName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setDrmPropertyString(@NonNull String propertyName, @NonNull String value) {
-        throw new UnsupportedOperationException();
-    }
-
-    // ExoPlayerWrapper.Listener implementation.
-
-    @Override
-    public void onPrepared(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_PREPARED);
-        synchronized (mTaskLock) {
-            if (mCurrentTask != null
-                    && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
-                    && ObjectsCompat.equals(mCurrentTask.mDSD, mediaItem)
-                    && mCurrentTask.mNeedToWaitForEventToComplete) {
-                mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                mCurrentTask = null;
-                processPendingTask();
-            }
-        }
-    }
-
-    @Override
-    public void onTracksChanged(@NonNull final List<TrackInfo> tracks) {
-        notifyMediaPlayer2Event(cb -> cb.onTracksChanged(ExoPlayerMediaPlayer2Impl.this,
-                tracks));
-    }
-
-    @Override
-    public void onSeekCompleted() {
-        synchronized (mTaskLock) {
-            if (mCurrentTask != null
-                    && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
-                    && mCurrentTask.mNeedToWaitForEventToComplete) {
-                mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                mCurrentTask = null;
-                processPendingTask();
-            }
-        }
-    }
-
-    @Override
-    public void onBufferingStarted(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_BUFFERING_START);
-    }
-
-    @Override
-    public void onBufferingEnded(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_BUFFERING_END);
-    }
-
-    @Override
-    public void onBufferingUpdate(MediaItem mediaItem, int bufferingPercentage) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_BUFFERING_UPDATE, bufferingPercentage);
-    }
-
-    @Override
-    public void onBandwidthSample(MediaItem mediaItem, int bitrateKbps) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_NETWORK_BANDWIDTH, bitrateKbps);
-    }
-
-    @Override
-    public void onVideoRenderingStart(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_VIDEO_RENDERING_START);
-    }
-
-    @Override
-    public void onVideoSizeChanged(final MediaItem mediaItem, final int width, final int height) {
-        notifyMediaPlayer2Event(new ExoPlayerMediaPlayer2Impl.Mp2EventNotifier() {
-            @Override
-            public void notify(MediaPlayer2.EventCallback callback) {
-                callback.onVideoSizeChanged(
-                        ExoPlayerMediaPlayer2Impl.this,
-                        mediaItem,
-                        width,
-                        height);
-            }
-        });
-    }
-
-    @Override
-    public void onSubtitleData(@NonNull final MediaItem mediaItem, @NonNull final TrackInfo track,
-            @NonNull final SubtitleData subtitleData) {
-        notifyMediaPlayer2Event(new Mp2EventNotifier() {
-            @Override
-            public void notify(EventCallback cb) {
-                cb.onSubtitleData(
-                        ExoPlayerMediaPlayer2Impl.this, mediaItem, track, subtitleData);
-            }
-        });
-    }
-
-    @Override
-    public void onTimedMetadata(final MediaItem mediaItem, final TimedMetaData timedMetaData) {
-        notifyMediaPlayer2Event(new Mp2EventNotifier() {
-            @Override
-            public void notify(EventCallback cb) {
-                cb.onTimedMetaDataAvailable(
-                        ExoPlayerMediaPlayer2Impl.this, mediaItem, timedMetaData);
-            }
-        });
-    }
-
-    @Override
-    public void onMediaItemStartedAsNext(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_START);
-    }
-
-    @Override
-    public void onMediaItemEnded(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_END);
-    }
-
-    @Override
-    public void onLoop(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_REPEAT);
-    }
-
-    @Override
-    public void onMediaTimeDiscontinuity(
-            final MediaItem mediaItem, final MediaTimestamp mediaTimestamp) {
-        notifyMediaPlayer2Event(new Mp2EventNotifier() {
-            @Override
-            public void notify(EventCallback cb) {
-                cb.onMediaTimeDiscontinuity(
-                        ExoPlayerMediaPlayer2Impl.this, mediaItem, mediaTimestamp);
-            }
-        });
-    }
-
-    @Override
-    public void onPlaybackEnded(MediaItem mediaItem) {
-        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_LIST_END);
-    }
-
-    @Override
-    public void onError(final MediaItem mediaItem, final int what) {
-        synchronized (mTaskLock) {
-            if (mCurrentTask != null
-                    && mCurrentTask.mNeedToWaitForEventToComplete) {
-                mCurrentTask.sendCompleteNotification(CALL_STATUS_ERROR_UNKNOWN);
-                mCurrentTask = null;
-                processPendingTask();
-            }
-        }
-        notifyMediaPlayer2Event(new Mp2EventNotifier() {
-            @Override
-            public void notify(EventCallback cb) {
-                cb.onError(ExoPlayerMediaPlayer2Impl.this, mediaItem, what, /* extra= */ 0);
-            }
-        });
-    }
-
-    // Internal functionality.
-
-    private void notifyOnInfo(MediaItem mediaItem, int what) {
-        notifyOnInfo(mediaItem, what, /* extra= */ 0);
-    }
-
-    private void notifyOnInfo(final MediaItem mediaItem, final int what, final int extra) {
-        notifyMediaPlayer2Event(new ExoPlayerMediaPlayer2Impl.Mp2EventNotifier() {
-            @Override
-            public void notify(MediaPlayer2.EventCallback callback) {
-                callback.onInfo(ExoPlayerMediaPlayer2Impl.this, mediaItem, what, extra);
-            }
-        });
-    }
-
-    private void resetPlayer() {
-        runPlayerCallableBlocking(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                mPlayer.reset();
-                return null;
-            }
-        });
-    }
-
-    /**
-     * Runs the specified callable on the player thread, blocking the calling thread until a result
-     * is returned.
-     *
-     * <p>Note: ExoPlayer methods do not block (except {@link Player#release}, which needs to
-     * block until resources have been released) so the caller thread will not be blocked for a
-     * substantial amount of time.
-     */
-    private <T> T runPlayerCallableBlocking(final Callable<T> callable) {
-        final ResolvableFuture<T> future = ResolvableFuture.create();
-        synchronized (mLock) {
-            Preconditions.checkNotNull(mHandlerThread);
-            boolean success = mTaskHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        future.set(callable.call());
-                    } catch (Throwable e) {
-                        future.setException(e);
-                    }
-                }
-            });
-            Preconditions.checkState(success);
-        }
-        return getPlayerFuture(future);
-    }
-
-    private static <T> T getPlayerFuture(ResolvableFuture<T> future) {
-        try {
-            T result;
-            boolean wasInterrupted = false;
-            while (true) {
-                try {
-                    result = future.get();
-                    break;
-                } catch (InterruptedException e) {
-                    // We always wait for player calls to return.
-                    wasInterrupted = true;
-                }
-            }
-            if (wasInterrupted) {
-                Thread.currentThread().interrupt();
-            }
-            return result;
-        } catch (ExecutionException e) {
-            Throwable cause = e.getCause();
-            Log.e(TAG, "Internal player error", new RuntimeException(cause));
-            throw new IllegalStateException(cause);
-        }
-    }
-
-    private interface Mp2EventNotifier {
-        void notify(EventCallback callback);
-    }
-
-    private abstract class Task implements Runnable {
-        final int mMediaCallType;
-        final boolean mNeedToWaitForEventToComplete;
-
-        MediaItem mDSD;
-        @GuardedBy("this")
-        boolean mDone;
-
-        Task(int mediaCallType, boolean needToWaitForEventToComplete) {
-            mMediaCallType = mediaCallType;
-            mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
-        }
-
-        abstract void process() throws IOException, NoDrmSchemeException;
-
-        @Override
-        public void run() {
-            int status = CALL_STATUS_NO_ERROR;
-            boolean skip = false;
-            if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
-                synchronized (mTaskLock) {
-                    Task next = mPendingTasks.peekFirst();
-                    if (next != null && next.mMediaCallType == CALL_COMPLETED_SEEK_TO) {
-                        skip = true;
-                    }
-                }
-            }
-            if (!skip) {
-                try {
-                    if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
-                            && mPlayer.hasError()) {
-                        status = CALL_STATUS_INVALID_OPERATION;
-                    } else {
-                        process();
-                    }
-                } catch (IllegalStateException e) {
-                    status = CALL_STATUS_INVALID_OPERATION;
-                } catch (IllegalArgumentException e) {
-                    status = CALL_STATUS_BAD_VALUE;
-                } catch (SecurityException e) {
-                    status = CALL_STATUS_PERMISSION_DENIED;
-                } catch (IOException e) {
-                    status = CALL_STATUS_ERROR_IO;
-                } catch (Exception e) {
-                    status = CALL_STATUS_ERROR_UNKNOWN;
-                }
-            } else {
-                status = CALL_STATUS_SKIPPED;
-            }
-
-            mDSD = mPlayer.getCurrentMediaItem();
-
-            if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR || skip) {
-                sendCompleteNotification(status);
-
-                synchronized (mTaskLock) {
-                    mCurrentTask = null;
-                    processPendingTask();
-                }
-            }
-            // reset() might be waiting for this task. Notify that the task is done.
-            synchronized (this) {
-                mDone = true;
-                notifyAll();
-            }
-        }
-
-        void sendCompleteNotification(final int status) {
-            if (mMediaCallType >= SEPARATE_CALL_COMPLETE_CALLBACK_START) {
-                // These methods have a separate call complete callback and it should be already
-                // called within process().
-                return;
-            }
-            notifyMediaPlayer2Event(new Mp2EventNotifier() {
-                @Override
-                public void notify(EventCallback callback) {
-                    callback.onCallCompleted(
-                            ExoPlayerMediaPlayer2Impl.this, mDSD, mMediaCallType, status);
-                }
-            });
-        }
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerUtils.java b/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerUtils.java
deleted file mode 100644
index b8b3777..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerUtils.java
+++ /dev/null
@@ -1,269 +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.media2.player;
-
-import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_608;
-import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_708;
-
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO;
-import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_IO;
-import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_MALFORMED;
-import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_TIMED_OUT;
-import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN;
-
-import android.annotation.SuppressLint;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.media.MediaFormat;
-import android.net.Uri;
-
-import androidx.core.util.Preconditions;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.ExoPlaybackException;
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.ParserException;
-import androidx.media2.exoplayer.external.PlaybackParameters;
-import androidx.media2.exoplayer.external.SeekParameters;
-import androidx.media2.exoplayer.external.audio.AudioAttributes;
-import androidx.media2.exoplayer.external.extractor.DefaultExtractorsFactory;
-import androidx.media2.exoplayer.external.extractor.ExtractorsFactory;
-import androidx.media2.exoplayer.external.extractor.ts.AdtsExtractor;
-import androidx.media2.exoplayer.external.mediacodec.MediaFormatUtil;
-import androidx.media2.exoplayer.external.source.ExtractorMediaSource;
-import androidx.media2.exoplayer.external.source.MediaSource;
-import androidx.media2.exoplayer.external.source.hls.HlsMediaSource;
-import androidx.media2.exoplayer.external.upstream.DataSource;
-import androidx.media2.exoplayer.external.upstream.HttpDataSource;
-import androidx.media2.exoplayer.external.upstream.RawResourceDataSource;
-import androidx.media2.exoplayer.external.util.MimeTypes;
-import androidx.media2.exoplayer.external.util.Util;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-
-/**
- * Utility methods for translating between the MediaPlayer2 and ExoPlayer APIs.
- */
-/* package */ class ExoPlayerUtils {
-
-    private static final ExtractorsFactory sExtractorsFactory = new DefaultExtractorsFactory()
-            .setAdtsExtractorFlags(AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING);
-
-    /**
-     * Returns an ExoPlayer media source for the given media item. The given {@link MediaItem} is
-     * set as the tag of the source.
-     */
-    public static MediaSource createUnclippedMediaSource(
-            Context context, DataSource.Factory dataSourceFactory, MediaItem mediaItem) {
-        if (mediaItem instanceof UriMediaItem) {
-            Uri uri = ((UriMediaItem) mediaItem).getUri();
-            if (Util.inferContentType(uri) == C.TYPE_HLS) {
-                return new HlsMediaSource.Factory(dataSourceFactory)
-                        .setTag(mediaItem)
-                        .createMediaSource(uri);
-            } else {
-                if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
-                    String path = Preconditions.checkNotNull(uri.getPath());
-                    int resourceIdentifier;
-                    if (uri.getPathSegments().size() == 1
-                            && uri.getPathSegments().get(0).matches("\\d+")) {
-                        resourceIdentifier = Integer.parseInt(uri.getPathSegments().get(0));
-                    } else {
-                        path = path.replaceAll("^/", "");
-                        String host = uri.getHost();
-                        String resourceName = (host != null ? host + ":" : "") + path;
-                        resourceIdentifier = context.getResources()
-                                .getIdentifier(resourceName, "raw", context.getPackageName());
-                    }
-                    Preconditions.checkState(resourceIdentifier != 0);
-                    uri = RawResourceDataSource.buildRawResourceUri(resourceIdentifier);
-                }
-                return new ExtractorMediaSource.Factory(dataSourceFactory)
-                        .setExtractorsFactory(sExtractorsFactory)
-                        .setTag(mediaItem)
-                        .createMediaSource(uri);
-            }
-        } else if (mediaItem instanceof FileMediaItem) {
-            return new ExtractorMediaSource.Factory(dataSourceFactory)
-                    .setExtractorsFactory(sExtractorsFactory)
-                    .setTag(mediaItem)
-                    .createMediaSource(Uri.EMPTY);
-        } else if (mediaItem instanceof CallbackMediaItem) {
-            CallbackMediaItem callbackMediaItem = (CallbackMediaItem) mediaItem;
-            dataSourceFactory = DataSourceCallbackDataSource.getFactory(
-                    callbackMediaItem.getDataSourceCallback());
-            return new ExtractorMediaSource.Factory(dataSourceFactory)
-                    .setExtractorsFactory(sExtractorsFactory)
-                    .setTag(mediaItem)
-                    .createMediaSource(Uri.EMPTY);
-        } else {
-            throw new IllegalStateException();
-        }
-    }
-
-    /** Returns ExoPlayer audio attributes for the given audio attributes. */
-    public static AudioAttributes getAudioAttributes(AudioAttributesCompat audioAttributesCompat) {
-        return new AudioAttributes.Builder()
-                .setContentType(audioAttributesCompat.getContentType())
-                .setFlags(audioAttributesCompat.getFlags())
-                .setUsage(audioAttributesCompat.getUsage())
-                .build();
-    }
-
-    /** Returns audio attributes for the given ExoPlayer audio attributes. */
-    public static AudioAttributesCompat getAudioAttributesCompat(AudioAttributes audioAttributes) {
-        return new AudioAttributesCompat.Builder()
-                .setContentType(audioAttributes.contentType)
-                .setFlags(audioAttributes.flags)
-                .setUsage(audioAttributes.usage)
-                .build();
-    }
-
-    /** Returns ExoPlayer playback parameters for the given playback params. */
-    public static PlaybackParameters getPlaybackParameters(PlaybackParams playbackParams2) {
-        Float speed = playbackParams2.getSpeed();
-        Float pitch = playbackParams2.getPitch();
-        return new PlaybackParameters(speed != null ? speed : 1f, pitch != null ? pitch : 1f);
-    }
-
-    /** Returns the ExoPlayer seek parameters corresponding to the given seek mode. */
-    public static SeekParameters getSeekParameters(int seekMode) {
-        switch (seekMode) {
-            case MediaPlayer2.SEEK_CLOSEST:
-                return SeekParameters.EXACT;
-            case MediaPlayer2.SEEK_CLOSEST_SYNC:
-                return SeekParameters.CLOSEST_SYNC;
-            case MediaPlayer2.SEEK_NEXT_SYNC:
-                return SeekParameters.NEXT_SYNC;
-            case MediaPlayer2.SEEK_PREVIOUS_SYNC:
-                return SeekParameters.PREVIOUS_SYNC;
-            default:
-                throw new IllegalArgumentException();
-        }
-    }
-
-    /** Returns the MEDIA_ERROR_* constant for an ExoPlayer player exception. */
-    public static int getError(ExoPlaybackException exception) {
-        if (exception.type == ExoPlaybackException.TYPE_SOURCE) {
-            IOException sourceException = exception.getSourceException();
-            if (sourceException instanceof ParserException) {
-                return MEDIA_ERROR_MALFORMED;
-            } else {
-                if (sourceException instanceof HttpDataSource.HttpDataSourceException
-                        && sourceException.getCause() instanceof SocketTimeoutException) {
-                    return MEDIA_ERROR_TIMED_OUT;
-                }
-                return MEDIA_ERROR_IO;
-            }
-        }
-        return MEDIA_ERROR_UNKNOWN;
-    }
-
-    /** Returns the ExoPlayer track type for the given track type. */
-    public static int getExoPlayerTrackType(int trackType) {
-        switch (trackType) {
-            case MEDIA_TRACK_TYPE_AUDIO:
-                return C.TRACK_TYPE_AUDIO;
-            case MEDIA_TRACK_TYPE_VIDEO:
-                return C.TRACK_TYPE_VIDEO;
-            case MEDIA_TRACK_TYPE_SUBTITLE:
-                return C.TRACK_TYPE_TEXT;
-            case MEDIA_TRACK_TYPE_METADATA:
-                return C.TRACK_TYPE_METADATA;
-            case MEDIA_TRACK_TYPE_UNKNOWN:
-            default:
-                return C.TRACK_TYPE_UNKNOWN;
-        }
-    }
-
-    /** Returns the track type corresponding to the given ExoPlayer track type. */
-    public static int getTrackType(int exoPlayerTrackType) {
-        switch (exoPlayerTrackType) {
-            case C.TRACK_TYPE_AUDIO:
-                return MEDIA_TRACK_TYPE_AUDIO;
-            case C.TRACK_TYPE_VIDEO:
-                return MEDIA_TRACK_TYPE_VIDEO;
-            case C.TRACK_TYPE_TEXT:
-                return MEDIA_TRACK_TYPE_SUBTITLE;
-            case C.TRACK_TYPE_METADATA:
-                return MEDIA_TRACK_TYPE_METADATA;
-            case C.TRACK_TYPE_NONE:
-            case C.TRACK_TYPE_CAMERA_MOTION:
-            case C.TRACK_TYPE_DEFAULT:
-            case C.TRACK_TYPE_UNKNOWN:
-            default:
-                return MEDIA_TRACK_TYPE_UNKNOWN;
-        }
-    }
-
-    /** Returns the media format corresponding to an ExoPlayer format. */
-    @SuppressLint("InlinedApi")
-    public static MediaFormat getMediaFormat(Format format) {
-        MediaFormat mediaFormat = new MediaFormat();
-        String mimeType = format.sampleMimeType;
-        mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
-        int trackType = MimeTypes.getTrackType(mimeType);
-        if (trackType == C.TRACK_TYPE_AUDIO) {
-            mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
-            mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
-            if (format.language != null) {
-                mediaFormat.setString(MediaFormat.KEY_LANGUAGE, format.language);
-            }
-        } else if (trackType == C.TRACK_TYPE_VIDEO) {
-            MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_WIDTH, format.width);
-            MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_HEIGHT, format.height);
-            MediaFormatUtil.maybeSetFloat(
-                    mediaFormat, MediaFormat.KEY_FRAME_RATE, format.frameRate);
-            MediaFormatUtil.maybeSetInteger(
-                    mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
-            MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo);
-        } else if (trackType == C.TRACK_TYPE_TEXT) {
-            boolean isAutoselect = format.selectionFlags == C.SELECTION_FLAG_AUTOSELECT;
-            boolean isDefault = format.selectionFlags == C.SELECTION_FLAG_DEFAULT;
-            boolean isForced = format.selectionFlags == C.SELECTION_FLAG_FORCED;
-            mediaFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, isAutoselect ? 1 : 0);
-            mediaFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, isDefault ? 1 : 0);
-            mediaFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, isForced ? 1 : 0);
-            if (format.language == null) {
-                mediaFormat.setString(MediaFormat.KEY_LANGUAGE, C.LANGUAGE_UNDETERMINED);
-            } else {
-                mediaFormat.setString(MediaFormat.KEY_LANGUAGE, format.language);
-            }
-            // MediaPlayer2 uses text/* instead of application/* MIME types.
-            if (MimeTypes.APPLICATION_CEA608.equals(mimeType)) {
-                mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
-            } else if (MimeTypes.APPLICATION_CEA708.equals(mimeType)) {
-                mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_708);
-            }
-        }
-        return mediaFormat;
-    }
-
-    private ExoPlayerUtils() {
-        // Prevent instantiation.
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerWrapper.java b/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerWrapper.java
deleted file mode 100644
index bb31761..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/ExoPlayerWrapper.java
+++ /dev/null
@@ -1,1058 +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.media2.player;
-
-import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.util.Preconditions;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.ExoPlaybackException;
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.Player;
-import androidx.media2.exoplayer.external.SimpleExoPlayer;
-import androidx.media2.exoplayer.external.audio.AudioAttributes;
-import androidx.media2.exoplayer.external.audio.AudioCapabilities;
-import androidx.media2.exoplayer.external.audio.AudioListener;
-import androidx.media2.exoplayer.external.audio.AudioProcessor;
-import androidx.media2.exoplayer.external.audio.AuxEffectInfo;
-import androidx.media2.exoplayer.external.audio.DefaultAudioSink;
-import androidx.media2.exoplayer.external.decoder.DecoderCounters;
-import androidx.media2.exoplayer.external.metadata.Metadata;
-import androidx.media2.exoplayer.external.metadata.MetadataOutput;
-import androidx.media2.exoplayer.external.source.ClippingMediaSource;
-import androidx.media2.exoplayer.external.source.ConcatenatingMediaSource;
-import androidx.media2.exoplayer.external.source.MediaSource;
-import androidx.media2.exoplayer.external.source.TrackGroup;
-import androidx.media2.exoplayer.external.source.TrackGroupArray;
-import androidx.media2.exoplayer.external.trackselection.TrackSelectionArray;
-import androidx.media2.exoplayer.external.upstream.DataSource;
-import androidx.media2.exoplayer.external.upstream.DefaultBandwidthMeter;
-import androidx.media2.exoplayer.external.upstream.DefaultDataSourceFactory;
-import androidx.media2.exoplayer.external.util.MimeTypes;
-import androidx.media2.exoplayer.external.util.Util;
-import androidx.media2.exoplayer.external.video.VideoRendererEventListener;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Wraps an ExoPlayer instance and provides methods and notifies events like those in the
- * {@link MediaPlayer2} API. {@link #getLooper()} returns the looper on which all other method calls
- * must be made.
- */
-/* package */ final class ExoPlayerWrapper {
-
-    private static final String TAG = "ExoPlayerWrapper";
-
-    /** Listener for player wrapper events. */
-    public interface Listener {
-
-        /** Called when the player is prepared. */
-        void onPrepared(MediaItem mediaItem);
-
-        /** Called when the list of available tracks changes. */
-        void onTracksChanged(@NonNull List<TrackInfo> tracks);
-
-        /** Called when a seek request has completed. */
-        void onSeekCompleted();
-
-        /** Called when the player rebuffers. */
-        void onBufferingStarted(MediaItem mediaItem);
-
-        /** Called when the player becomes ready again after rebuffering. */
-        void onBufferingEnded(MediaItem mediaItem);
-
-        /** Called periodically with the player's buffered position as a percentage. */
-        void onBufferingUpdate(MediaItem mediaItem, int bufferingPercentage);
-
-        /** Called when a sample of the available bandwidth is known. */
-        void onBandwidthSample(MediaItem mediaItem2, int bitrateKbps);
-
-        /** Called when video rendering of the specified media item has started. */
-        void onVideoRenderingStart(MediaItem mediaItem);
-
-        /** Called when the video size of the specified media item has changed. */
-        void onVideoSizeChanged(MediaItem mediaItem, int width, int height);
-
-        /** Called when subtitle data is handled. */
-        void onSubtitleData(@NonNull MediaItem mediaItem, @NonNull TrackInfo track,
-                @NonNull SubtitleData subtitleData);
-
-        /** Called when timed metadata is handled. */
-        void onTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData);
-
-        /** Called when playback transitions to the next media item. */
-        void onMediaItemStartedAsNext(MediaItem mediaItem);
-
-        /** Called when playback of a media item ends. */
-        void onMediaItemEnded(MediaItem mediaItem);
-
-        /** Called when playback of the specified item loops back to its start. */
-        void onLoop(MediaItem mediaItem);
-
-        /** Called when a change in the progression of media time is detected. */
-        void onMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp);
-
-        /** Called when playback of the item list has ended. */
-        void onPlaybackEnded(MediaItem mediaItem);
-
-        /** Called when the player encounters an error. */
-        void onError(MediaItem mediaItem, int what);
-
-    }
-
-    private static final String USER_AGENT_NAME = "MediaPlayer2";
-
-    private static final int POLL_BUFFER_INTERVAL_MS = 1000;
-
-    private final Context mContext;
-    private final Listener mListener;
-    private final Looper mLooper;
-    private final Handler mHandler;
-    private final DefaultBandwidthMeter mBandwidthMeter;
-    private final Runnable mPollBufferRunnable;
-
-    private SimpleExoPlayer mPlayer;
-    private Handler mPlayerHandler;
-    private DefaultAudioSink mAudioSink;
-    private TrackSelector mTrackSelector;
-    private MediaItemQueue mMediaItemQueue;
-
-    private boolean mHasAudioAttributes;
-    private int mAudioSessionId;
-    private int mAuxEffectId;
-    private float mAuxEffectSendLevel;
-    private boolean mPrepared;
-    private boolean mNewlyPrepared;
-    private boolean mRebuffering;
-    private boolean mPendingSeek;
-    private int mVideoWidth;
-    private int mVideoHeight;
-    private PlaybackParams mPlaybackParams;
-
-    /**
-     * Creates a new ExoPlayer wrapper.
-     *
-     * @param context The context for accessing system components.
-     * @param listener A listener for player wrapper events.
-     * @param looper The looper that will be used for player events.
-     */
-    ExoPlayerWrapper(Context context, Listener listener, Looper looper) {
-        mContext = context.getApplicationContext();
-        mListener = listener;
-        mLooper = looper;
-        mHandler = new Handler(looper);
-        // Use the same bandwidth meter for all playbacks via this wrapper.
-        mBandwidthMeter = new DefaultBandwidthMeter();
-        mPollBufferRunnable = new PollBufferRunnable();
-    }
-
-    public Looper getLooper() {
-        return mLooper;
-    }
-
-    public void setMediaItem(MediaItem mediaItem) {
-        mMediaItemQueue.setMediaItem(Preconditions.checkNotNull(mediaItem));
-    }
-
-    public MediaItem getCurrentMediaItem() {
-        return mMediaItemQueue.getCurrentMediaItem();
-    }
-
-    public void prepare() {
-        Preconditions.checkState(!mPrepared);
-        mMediaItemQueue.preparePlayer();
-    }
-
-    public void play() {
-        mNewlyPrepared = false;
-        if (mPlayer.getPlaybackState() == Player.STATE_ENDED) {
-            mPlayer.seekTo(0);
-        }
-        mPlayer.setPlayWhenReady(true);
-    }
-
-    public void pause() {
-        mNewlyPrepared = false;
-        mPlayer.setPlayWhenReady(false);
-    }
-
-    public void seekTo(long position, @MediaPlayer2.SeekMode int mode) {
-        mPlayer.setSeekParameters(ExoPlayerUtils.getSeekParameters(mode));
-        mPlayer.seekTo(position);
-    }
-
-    public long getCurrentPosition() {
-        Preconditions.checkState(getState() != MediaPlayer2.PLAYER_STATE_IDLE);
-        return Math.max(0, mPlayer.getCurrentPosition());
-    }
-
-    public long getDuration() {
-        Preconditions.checkState(getState() != MediaPlayer2.PLAYER_STATE_IDLE);
-        long duration = mPlayer.getDuration();
-        return duration == C.TIME_UNSET ? -1 : duration;
-    }
-
-    public long getBufferedPosition() {
-        Preconditions.checkState(getState() != MediaPlayer2.PLAYER_STATE_IDLE);
-        return mPlayer.getBufferedPosition();
-    }
-
-    public @MediaPlayer2.MediaPlayer2State int getState() {
-        if (hasError()) {
-            return MediaPlayer2.PLAYER_STATE_ERROR;
-        }
-        if (mNewlyPrepared) {
-            return MediaPlayer2.PLAYER_STATE_PREPARED;
-        }
-        int state = mPlayer.getPlaybackState();
-        boolean playWhenReady = mPlayer.getPlayWhenReady();
-        // TODO(b/80232248): Return PLAYER_STATE_PREPARED before playback when we have track
-        // groups.
-        switch (state) {
-            case Player.STATE_IDLE:
-                return MediaPlayer2.PLAYER_STATE_IDLE;
-            case Player.STATE_ENDED:
-            case Player.STATE_BUFFERING:
-                return MediaPlayer2.PLAYER_STATE_PAUSED;
-            case Player.STATE_READY:
-                return playWhenReady ? MediaPlayer2.PLAYER_STATE_PLAYING
-                        : MediaPlayer2.PLAYER_STATE_PAUSED;
-            default:
-                throw new IllegalStateException();
-        }
-    }
-
-    public void loopCurrent(boolean loop) {
-        mPlayer.setRepeatMode(loop ? Player.REPEAT_MODE_ONE : Player.REPEAT_MODE_OFF);
-    }
-
-    public void skipToNext() {
-        mMediaItemQueue.skipToNext();
-    }
-
-    public void setNextMediaItem(MediaItem mediaItem) {
-        if (mMediaItemQueue.isEmpty()) {
-            if (mediaItem instanceof FileMediaItem) {
-                ((FileMediaItem) mediaItem).increaseRefCount();
-                ((FileMediaItem) mediaItem).decreaseRefCount();
-            }
-            throw new IllegalStateException();
-        }
-        mMediaItemQueue.setNextMediaItems(Collections.singletonList(mediaItem));
-    }
-
-    public void setNextMediaItems(List<MediaItem> mediaItems) {
-        if (mMediaItemQueue.isEmpty()) {
-            for (MediaItem item: mediaItems) {
-                ((FileMediaItem) item).increaseRefCount();
-                ((FileMediaItem) item).decreaseRefCount();
-            }
-            throw new IllegalStateException();
-        }
-        mMediaItemQueue.setNextMediaItems(Preconditions.checkNotNull(mediaItems));
-    }
-
-    public void setAudioAttributes(AudioAttributesCompat audioAttributes) {
-        mHasAudioAttributes = true;
-        mPlayer.setAudioAttributes(ExoPlayerUtils.getAudioAttributes(audioAttributes));
-
-        // Reset the audio session ID, as it gets cleared by setting audio attributes.
-        if (mAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
-            updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
-        }
-    }
-
-    public AudioAttributesCompat getAudioAttributes() {
-        return mHasAudioAttributes
-                ? ExoPlayerUtils.getAudioAttributesCompat(mPlayer.getAudioAttributes()) : null;
-    }
-
-    public void setAudioSessionId(int audioSessionId) {
-        mAudioSessionId = audioSessionId;
-        if (mPlayer != null) {
-            updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
-        }
-    }
-
-    public int getAudioSessionId() {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioSessionId == C.AUDIO_SESSION_ID_UNSET) {
-            setAudioSessionId(C.generateAudioSessionIdV21(mContext));
-        }
-        return mAudioSessionId == C.AUDIO_SESSION_ID_UNSET ? 0 : mAudioSessionId;
-    }
-
-    public void attachAuxEffect(int auxEffectId) {
-        mAuxEffectId = auxEffectId;
-        mPlayer.setAuxEffectInfo(new AuxEffectInfo(auxEffectId, mAuxEffectSendLevel));
-    }
-
-    public void setAuxEffectSendLevel(float auxEffectSendLevel) {
-        mAuxEffectSendLevel = auxEffectSendLevel;
-        mPlayer.setAuxEffectInfo(new AuxEffectInfo(mAuxEffectId, auxEffectSendLevel));
-    }
-
-    public void setPlaybackParams(PlaybackParams playbackParams2) {
-        // TODO(b/80232248): Decide how to handle fallback modes, which ExoPlayer doesn't support.
-        mPlaybackParams = playbackParams2;
-        mPlayer.setPlaybackParameters(ExoPlayerUtils.getPlaybackParameters(mPlaybackParams));
-        if (getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
-            mListener.onMediaTimeDiscontinuity(getCurrentMediaItem(), getTimestamp());
-        }
-    }
-
-    public PlaybackParams getPlaybackParams() {
-        return mPlaybackParams;
-    }
-
-    public int getVideoWidth() {
-        return mVideoWidth;
-    }
-
-    public int getVideoHeight() {
-        return mVideoHeight;
-    }
-
-    public void setSurface(Surface surface) {
-        mPlayer.setVideoSurface(surface);
-    }
-
-    public void setVolume(float volume) {
-        mPlayer.setVolume(volume);
-    }
-
-    public float getVolume() {
-        return mPlayer.getVolume();
-    }
-
-    public List<TrackInfo> getTracks() {
-        return mTrackSelector.getTracks();
-    }
-
-    public TrackInfo getSelectedTrack(int trackType) {
-        return mTrackSelector.getSelectedTrack(trackType);
-    }
-
-    public void selectTrack(int trackId) {
-        mTrackSelector.selectTrack(trackId);
-    }
-
-    public void deselectTrack(int trackId) {
-        mTrackSelector.deselectTrack(trackId);
-    }
-
-    @RequiresApi(21)
-    public PersistableBundle getMetricsV21() {
-        TrackGroupArray trackGroupArray = mPlayer.getCurrentTrackGroups();
-        long durationMs = mPlayer.getDuration();
-        long playingTimeMs = mMediaItemQueue.getCurrentMediaItemPlayingTimeMs();
-        @Nullable String primaryAudioMimeType = null;
-        @Nullable String primaryVideoMimeType = null;
-        for (int i = 0; i < trackGroupArray.length; i++) {
-            TrackGroup trackGroup = trackGroupArray.get(i);
-            String mimeType = trackGroup.getFormat(0).sampleMimeType;
-            if (primaryVideoMimeType == null && MimeTypes.isVideo(mimeType)) {
-                primaryVideoMimeType = mimeType;
-            } else if (primaryAudioMimeType == null && MimeTypes.isAudio(mimeType)) {
-                primaryAudioMimeType = mimeType;
-            }
-        }
-        PersistableBundle bundle = PersistableBundleHelper.Api21Impl.createInstance();
-        if (primaryVideoMimeType != null) {
-            PersistableBundleHelper.Api21Impl.putString(bundle,
-                    MediaPlayer2.MetricsConstants.MIME_TYPE_VIDEO, primaryVideoMimeType);
-        }
-        if (primaryAudioMimeType != null) {
-            PersistableBundleHelper.Api21Impl.putString(bundle,
-                    MediaPlayer2.MetricsConstants.MIME_TYPE_AUDIO, primaryAudioMimeType);
-        }
-        PersistableBundleHelper.Api21Impl.putLong(bundle, MediaPlayer2.MetricsConstants.DURATION,
-                durationMs == C.TIME_UNSET ? -1 : durationMs);
-        PersistableBundleHelper.Api21Impl.putLong(bundle, MediaPlayer2.MetricsConstants.PLAYING,
-                playingTimeMs);
-        return bundle;
-    }
-
-    public MediaTimestamp getTimestamp() {
-        long positionUs = mPlayer.getPlaybackState() == Player.STATE_IDLE
-                ? 0L : C.msToUs(getCurrentPosition());
-        float speed = mPlayer.getPlaybackState() == Player.STATE_READY && mPlayer.getPlayWhenReady()
-                ? mPlaybackParams.getSpeed() : 0f;
-        return new MediaTimestamp(positionUs, System.nanoTime(), speed);
-    }
-
-    public void reset() {
-        if (mPlayer != null) {
-            mPlayer.setPlayWhenReady(false);
-            if (getState() != MediaPlayer2.PLAYER_STATE_IDLE) {
-                mListener.onMediaTimeDiscontinuity(getCurrentMediaItem(), getTimestamp());
-            }
-            mPlayer.release();
-            mMediaItemQueue.clear();
-        }
-        ComponentListener listener = new ComponentListener();
-        mAudioSink = new DefaultAudioSink(
-                AudioCapabilities.getCapabilities(mContext), new AudioProcessor[0]);
-        TextRenderer textRenderer = new TextRenderer(listener);
-        RenderersFactory renderersFactory = new RenderersFactory(mContext, mAudioSink,
-                textRenderer);
-        mTrackSelector = new TrackSelector(textRenderer);
-        mPlayer = new SimpleExoPlayer.Builder(mContext, renderersFactory)
-                .setTrackSelector(mTrackSelector.getPlayerTrackSelector())
-                .setBandwidthMeter(mBandwidthMeter)
-                .setLooper(mLooper)
-                .build();
-        mPlayerHandler = new Handler(mPlayer.getPlaybackLooper());
-        mMediaItemQueue = new MediaItemQueue(mContext, mPlayer, mListener);
-        mPlayer.addListener(listener);
-        // TODO(b/80232248): Switch to AnalyticsListener once default methods work.
-        mPlayer.setVideoDebugListener(listener);
-        mPlayer.addMetadataOutput(listener);
-        mVideoWidth = 0;
-        mVideoHeight = 0;
-        mPrepared = false;
-        mNewlyPrepared = false;
-        mRebuffering = false;
-        mPendingSeek = false;
-        mHasAudioAttributes = false;
-        mAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
-        mAuxEffectId = AuxEffectInfo.NO_AUX_EFFECT_ID;
-        mAuxEffectSendLevel = 0f;
-        mPlaybackParams = new PlaybackParams.Builder()
-                .setSpeed(1f)
-                .setPitch(1f)
-                .setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT)
-                .build();
-    }
-
-    public void close() {
-        if (mPlayer != null) {
-            mHandler.removeCallbacks(mPollBufferRunnable);
-            mPlayer.release();
-            mPlayer = null;
-            mMediaItemQueue.clear();
-            mHasAudioAttributes = false;
-        }
-    }
-
-    public boolean hasError() {
-        return mPlayer.getPlaybackError() != null;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
-        int scaledWidth;
-        if (pixelWidthHeightRatio != 1f) {
-            scaledWidth = (int) (pixelWidthHeightRatio * width);
-        } else {
-            scaledWidth = width;
-        }
-        if (mVideoWidth == scaledWidth && mVideoHeight == height) {
-            return;
-        }
-        mVideoWidth = scaledWidth;
-        mVideoHeight = height;
-        mListener.onVideoSizeChanged(mMediaItemQueue.getCurrentMediaItem(), scaledWidth, height);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleRenderedFirstFrame() {
-        mListener.onVideoRenderingStart(mMediaItemQueue.getCurrentMediaItem());
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handlePlayerStateChanged(boolean playWhenReady, int state) {
-        mListener.onMediaTimeDiscontinuity(getCurrentMediaItem(), getTimestamp());
-
-        if (state == Player.STATE_READY && playWhenReady) {
-            maybeUpdateTimerForPlaying();
-        } else {
-            maybeUpdateTimerForStopped();
-        }
-
-        if (state == Player.STATE_READY || state == Player.STATE_BUFFERING) {
-            mHandler.post(mPollBufferRunnable);
-        } else {
-            mHandler.removeCallbacks(mPollBufferRunnable);
-        }
-
-        switch (state) {
-            case Player.STATE_BUFFERING:
-                maybeNotifyBufferingEvents();
-                break;
-            case Player.STATE_READY:
-                maybeNotifyReadyEvents();
-                break;
-            case Player.STATE_ENDED:
-                maybeNotifyEndedEvents();
-                break;
-            case Player.STATE_IDLE:
-                // Do nothing.
-                break;
-            default:
-                throw new IllegalStateException();
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleTextRendererChannelAvailable(int type, int channel) {
-        mTrackSelector.handleTextRendererChannelAvailable(type, channel);
-        if (mTrackSelector.hasPendingTracksUpdate()) {
-            mListener.onTracksChanged(getTracks());
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handlePlayerTracksChanged(TrackSelectionArray trackSelections) {
-        MediaItem currentMediaItem = getCurrentMediaItem();
-        mTrackSelector.handlePlayerTracksChanged(currentMediaItem, trackSelections);
-        if (mTrackSelector.hasPendingTracksUpdate()) {
-            mListener.onTracksChanged(getTracks());
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleSeekProcessed() {
-        if (getCurrentMediaItem() == null) {
-            mListener.onSeekCompleted();
-            return;
-        }
-        mPendingSeek = true;
-        if (mPlayer.getPlaybackState() == Player.STATE_READY) {
-            // The player doesn't need to buffer to seek, so handle being ready now.
-            maybeNotifyReadyEvents();
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handlePositionDiscontinuity(@Player.DiscontinuityReason int reason) {
-        mListener.onMediaTimeDiscontinuity(getCurrentMediaItem(), getTimestamp());
-        mMediaItemQueue.onPositionDiscontinuity(
-                reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handlePlayerError(ExoPlaybackException exception) {
-        mListener.onMediaTimeDiscontinuity(getCurrentMediaItem(), getTimestamp());
-        mListener.onError(getCurrentMediaItem(), ExoPlayerUtils.getError(exception));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleAudioSessionId(int audioSessionId) {
-        mAudioSessionId = audioSessionId;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleSubtitleData(byte[] data, long timeUs) {
-        TrackInfo track = mTrackSelector.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        final MediaItem currentMediaItem = getCurrentMediaItem();
-        mListener.onSubtitleData(currentMediaItem, track,
-                new SubtitleData(timeUs, /* durationUs= */ 0L, data));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleMetadata(Metadata metadata) {
-        int length = metadata.length();
-        for (int i = 0; i < length; i++) {
-            ByteArrayFrame byteArrayFrame = (ByteArrayFrame) metadata.get(i);
-            mListener.onTimedMetadata(
-                    getCurrentMediaItem(),
-                    new TimedMetaData(byteArrayFrame.mTimestamp, byteArrayFrame.mData));
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void updateBufferingAndScheduleNextPollBuffer() {
-        if (mMediaItemQueue.getCurrentMediaItemIsRemote()) {
-            mListener.onBufferingUpdate(getCurrentMediaItem(), mPlayer.getBufferedPercentage());
-        }
-        mHandler.removeCallbacks(mPollBufferRunnable);
-        mHandler.postDelayed(mPollBufferRunnable, POLL_BUFFER_INTERVAL_MS);
-    }
-
-    private void maybeUpdateTimerForPlaying() {
-        mMediaItemQueue.onPlaying();
-    }
-
-    private void maybeUpdateTimerForStopped() {
-        mMediaItemQueue.onStopped();
-    }
-
-    private void maybeNotifyBufferingEvents() {
-        if (mPrepared && !mRebuffering) {
-            mRebuffering = true;
-            if (mMediaItemQueue.getCurrentMediaItemIsRemote()) {
-                mListener.onBandwidthSample(
-                        getCurrentMediaItem(), (int) (mBandwidthMeter.getBitrateEstimate() / 1000));
-            }
-            mListener.onBufferingStarted(getCurrentMediaItem());
-        }
-    }
-
-    private void maybeNotifyReadyEvents() {
-        MediaItem mediaItem = mMediaItemQueue.getCurrentMediaItem();
-        boolean prepareComplete = !mPrepared;
-        boolean seekComplete = mPendingSeek;
-        if (prepareComplete) {
-            mPrepared = true;
-            mNewlyPrepared = true;
-            mMediaItemQueue.onPositionDiscontinuity(/* isPeriodTransition= */ false);
-            // TODO(b/80232248): Trigger onInfo with MEDIA_INFO_PREPARED for any item in the data
-            // source queue for which the duration is now known, even if this is not the initial
-            // preparation.
-            mListener.onPrepared(mediaItem);
-        } else if (seekComplete) {
-            // TODO(b/80232248): Suppress notification if this is an initial seek for a non-zero
-            // start position.
-            mPendingSeek = false;
-            mListener.onSeekCompleted();
-        }
-        if (mRebuffering) {
-            mRebuffering = false;
-            if (mMediaItemQueue.getCurrentMediaItemIsRemote()) {
-                mListener.onBandwidthSample(
-                        getCurrentMediaItem(), (int) (mBandwidthMeter.getBitrateEstimate() / 1000));
-            }
-            mListener.onBufferingEnded(getCurrentMediaItem());
-        }
-    }
-
-    private void maybeNotifyEndedEvents() {
-        if (mPendingSeek) {
-            // The seek operation resulted in transitioning to the ended state.
-            mPendingSeek = false;
-            mListener.onSeekCompleted();
-        }
-        if (mPlayer.getPlayWhenReady()) {
-            mMediaItemQueue.onPlayerEnded();
-            mPlayer.setPlayWhenReady(false);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final class ComponentListener extends Player.DefaultEventListener
-            implements VideoRendererEventListener, AudioListener,
-            TextRenderer.Output, MetadataOutput {
-
-        // DefaultEventListener implementation.
-
-        @Override
-        public void onPlayerStateChanged(boolean playWhenReady, int state) {
-            handlePlayerStateChanged(playWhenReady, state);
-        }
-
-        @Override
-        public void onTracksChanged(
-                TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
-            handlePlayerTracksChanged(trackSelections);
-        }
-
-        @Override
-        public void onSeekProcessed() {
-            handleSeekProcessed();
-        }
-
-        @Override
-        public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
-            handlePositionDiscontinuity(reason);
-        }
-
-        @Override
-        public void onPlayerError(ExoPlaybackException error) {
-            handlePlayerError(error);
-        }
-
-        // VideoRendererEventListener implementation.
-
-        @Override
-        public void onVideoSizeChanged(
-                final int width,
-                final int height,
-                int unappliedRotationDegrees,
-                float pixelWidthHeightRatio) {
-            handleVideoSizeChanged(width, height, pixelWidthHeightRatio);
-        }
-
-        @Override
-        public void onVideoInputFormatChanged(Format format) {
-            if (MimeTypes.isVideo(format.sampleMimeType)) {
-                handleVideoSizeChanged(format.width, format.height, format.pixelWidthHeightRatio);
-            }
-        }
-
-        @Override
-        public void onRenderedFirstFrame(@Nullable Surface surface) {
-            handleRenderedFirstFrame();
-        }
-
-        @Override
-        public void onVideoEnabled(DecoderCounters counters) {}
-
-        @Override
-        public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
-                long initializationDurationMs) {}
-
-        @Override
-        public void onDroppedFrames(int count, long elapsedMs) {}
-
-        @Override
-        public void onVideoDisabled(DecoderCounters counters) {
-            handleVideoSizeChanged(
-                    /* width= */ 0, /* height= */ 0, /* pixelWidthHeightRatio= */ 1f);
-        }
-
-        // AudioListener implementation.
-
-        @Override
-        public void onAudioSessionId(int audioSessionId) {
-            handleAudioSessionId(audioSessionId);
-        }
-
-        @Override
-        public void onAudioAttributesChanged(AudioAttributes audioAttributes) {}
-
-        @Override
-        public void onVolumeChanged(float volume) {}
-
-        // TextRenderer.Output implementation.
-
-        @Override
-        public void onCcData(byte[] data, long timeUs) {
-            handleSubtitleData(data, timeUs);
-        }
-
-        @Override
-        public void onChannelAvailable(int type, int channel) {
-            handleTextRendererChannelAvailable(type, channel);
-        }
-
-        // MetadataOutput implementation.
-
-        @Override
-        public void onMetadata(Metadata metadata) {
-            handleMetadata(metadata);
-        }
-
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final class PollBufferRunnable implements Runnable {
-        @Override
-        public void run() {
-            updateBufferingAndScheduleNextPollBuffer();
-        }
-    }
-
-    private static void updatePlayerAudioSessionId(
-            Handler playerHandler,
-            final DefaultAudioSink audioSink,
-            final int audioSessionId) {
-        // DefaultAudioSink is not thread-safe, so post the update to the playback thread.
-        playerHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                audioSink.setAudioSessionId(audioSessionId);
-            }
-        });
-    }
-
-    private static final class MediaItemInfo {
-
-        final MediaItem mMediaItem;
-        final boolean mIsRemote;
-
-        MediaItemInfo(MediaItem mediaItem, boolean isRemote) {
-            mMediaItem = mediaItem;
-            mIsRemote = isRemote;
-        }
-
-    }
-
-    private static final class FileDescriptorRegistry {
-
-        private static final class Entry {
-            public final Object mLock;
-
-            public int mMediaItemCount;
-
-            Entry() {
-                mLock = new Object();
-            }
-        }
-
-        private final Map<FileDescriptor, Entry> mEntries;
-
-        FileDescriptorRegistry() {
-            mEntries = new HashMap<>();
-        }
-
-        public Object registerMediaItemAndGetLock(FileDescriptor fileDescriptor) {
-            if (!mEntries.containsKey(fileDescriptor)) {
-                mEntries.put(fileDescriptor, new Entry());
-            }
-            Entry entry = Preconditions.checkNotNull(mEntries.get(fileDescriptor));
-            entry.mMediaItemCount++;
-            return entry.mLock;
-        }
-
-        public void unregisterMediaItem(FileDescriptor fileDescriptor) {
-            Entry entry = Preconditions.checkNotNull(mEntries.get(fileDescriptor));
-            if (--entry.mMediaItemCount == 0) {
-                mEntries.remove(fileDescriptor);
-            }
-        }
-
-    }
-
-    private static final class MediaItemQueue {
-
-        private final Context mContext;
-        private final Listener mListener;
-        private final SimpleExoPlayer mPlayer;
-        private final DataSource.Factory mDataSourceFactory;
-        private final ConcatenatingMediaSource mConcatenatingMediaSource;
-        private final ArrayDeque<MediaItemInfo> mMediaItemInfos;
-        private final FileDescriptorRegistry mFileDescriptorRegistry;
-
-        private long mStartPlayingTimeNs;
-        private long mCurrentMediaItemPlayingTimeUs;
-
-        MediaItemQueue(Context context, SimpleExoPlayer player, Listener listener) {
-            mContext = context;
-            mPlayer = player;
-            mListener = listener;
-            String userAgent = Util.getUserAgent(context, USER_AGENT_NAME);
-            mDataSourceFactory = new DefaultDataSourceFactory(context, userAgent);
-            mConcatenatingMediaSource = new ConcatenatingMediaSource();
-            mMediaItemInfos = new ArrayDeque<>();
-            mFileDescriptorRegistry = new FileDescriptorRegistry();
-            mStartPlayingTimeNs = -1;
-        }
-
-        public void clear() {
-            while (!mMediaItemInfos.isEmpty()) {
-                releaseMediaItem(mMediaItemInfos.remove());
-            }
-        }
-
-        public boolean isEmpty() {
-            return mConcatenatingMediaSource.getSize() == 0;
-        }
-
-        public void setMediaItem(MediaItem mediaItem) {
-            clear();
-            mConcatenatingMediaSource.clear();
-            setNextMediaItems(Collections.singletonList(mediaItem));
-        }
-
-        public void setNextMediaItems(List<MediaItem> mediaItems) {
-            int size = mConcatenatingMediaSource.getSize();
-            List<MediaItemInfo> oldMediaItemInfos = new ArrayList<>(size > 1 ? size - 1 : 0);
-            if (size > 1) {
-                mConcatenatingMediaSource.removeMediaSourceRange(
-                        /* fromIndex= */ 1, /* toIndex= */ size);
-                while (mMediaItemInfos.size() > 1) {
-                    oldMediaItemInfos.add(mMediaItemInfos.removeLast());
-                }
-            }
-
-            List<MediaSource> mediaSources = new ArrayList<>(mediaItems.size());
-            for (MediaItem mediaItem : mediaItems) {
-                if (mediaItem == null) {
-                    mListener.onError(/* mediaItem= */ null, MEDIA_ERROR_UNKNOWN);
-                    return;
-                }
-                appendMediaItem(
-                        mediaItem,
-                        mMediaItemInfos,
-                        mediaSources);
-            }
-            mConcatenatingMediaSource.addMediaSources(mediaSources);
-
-            // Release old media items after appending new ones, so that any items that are present
-            // both before and after this call have their reference counts incremented before they
-            // are decremented.
-            for (MediaItemInfo mediaItemInfo : oldMediaItemInfos) {
-                releaseMediaItem(mediaItemInfo);
-            }
-        }
-
-        public void preparePlayer() {
-            mPlayer.prepare(mConcatenatingMediaSource);
-        }
-
-        @Nullable
-        public MediaItem getCurrentMediaItem() {
-            return mMediaItemInfos.isEmpty() ? null : mMediaItemInfos.peekFirst().mMediaItem;
-        }
-
-        public long getCurrentMediaItemPlayingTimeMs() {
-            return C.usToMs(mCurrentMediaItemPlayingTimeUs);
-        }
-
-        public boolean getCurrentMediaItemIsRemote() {
-            return !mMediaItemInfos.isEmpty() && mMediaItemInfos.peekFirst().mIsRemote;
-        }
-
-        public void skipToNext() {
-            // TODO(b/68398926): Play the start position of the next media item.
-            releaseMediaItem(mMediaItemInfos.removeFirst());
-            mConcatenatingMediaSource.removeMediaSource(0);
-        }
-
-        public void onPlaying() {
-            if (mStartPlayingTimeNs != -1) {
-                return;
-            }
-            mStartPlayingTimeNs = System.nanoTime();
-        }
-
-        public void onStopped() {
-            if (mStartPlayingTimeNs == -1) {
-                return;
-            }
-            long nowNs = System.nanoTime();
-            mCurrentMediaItemPlayingTimeUs += (nowNs - mStartPlayingTimeNs + 500) / 1000;
-            mStartPlayingTimeNs = -1;
-        }
-
-        public void onPlayerEnded() {
-            MediaItem mediaItem = getCurrentMediaItem();
-            mListener.onMediaItemEnded(mediaItem);
-            mListener.onPlaybackEnded(mediaItem);
-        }
-
-        public void onPositionDiscontinuity(boolean isPeriodTransition) {
-            MediaItem currentMediaItem = getCurrentMediaItem();
-            if (isPeriodTransition && mPlayer.getRepeatMode() != Player.REPEAT_MODE_OFF) {
-                mListener.onLoop(currentMediaItem);
-            }
-            int windowIndex = mPlayer.getCurrentWindowIndex();
-            if (windowIndex > 0) {
-                // We're no longer playing the first item in the queue.
-                if (isPeriodTransition) {
-                    mListener.onMediaItemEnded(getCurrentMediaItem());
-                }
-                for (int i = 0; i < windowIndex; i++) {
-                    releaseMediaItem(mMediaItemInfos.removeFirst());
-                }
-                if (isPeriodTransition) {
-                    mListener.onMediaItemStartedAsNext(getCurrentMediaItem());
-                }
-                mConcatenatingMediaSource.removeMediaSourceRange(0, windowIndex);
-                mCurrentMediaItemPlayingTimeUs = 0;
-                mStartPlayingTimeNs = -1;
-                if (mPlayer.getPlaybackState() == Player.STATE_READY) {
-                    onPlaying();
-                }
-            }
-        }
-
-        /**
-         * Appends a media source and associated information for the given media item to the
-         * collections provided.
-         */
-        private void appendMediaItem(
-                MediaItem mediaItem,
-                Collection<MediaItemInfo> mediaItemInfos,
-                Collection<MediaSource> mediaSources) {
-            DataSource.Factory dataSourceFactory = mDataSourceFactory;
-            // Create a data source for reading from the file descriptor, if needed.
-            if (mediaItem instanceof FileMediaItem) {
-                FileMediaItem fileMediaItem = (FileMediaItem) mediaItem;
-                fileMediaItem.increaseRefCount();
-                FileDescriptor fileDescriptor =
-                        fileMediaItem.getParcelFileDescriptor().getFileDescriptor();
-                long offset = fileMediaItem.getFileDescriptorOffset();
-                long length = fileMediaItem.getFileDescriptorLength();
-                Object lock = mFileDescriptorRegistry.registerMediaItemAndGetLock(fileDescriptor);
-                dataSourceFactory =
-                        FileDescriptorDataSource.getFactory(fileDescriptor, offset, length, lock);
-            }
-
-            // Create a source for the item.
-            MediaSource mediaSource = ExoPlayerUtils.createUnclippedMediaSource(
-                    mContext, dataSourceFactory, mediaItem);
-
-            // Apply clipping if needed.
-            long startPosition = mediaItem.getStartPosition();
-            long endPosition = mediaItem.getEndPosition();
-            if (startPosition != 0L || endPosition != MediaItem.POSITION_UNKNOWN) {
-                if (endPosition == MediaItem.POSITION_UNKNOWN) {
-                    endPosition = C.TIME_END_OF_SOURCE;
-                }
-                // Disable the initial discontinuity to give seamless transitions to clips.
-                mediaSource = new ClippingMediaSource(
-                        mediaSource,
-                        C.msToUs(startPosition),
-                        C.msToUs(endPosition),
-                        /* enableInitialDiscontinuity= */ false,
-                        /* allowDynamicClippingUpdates= */ false,
-                        /* relativeToDefaultPosition= */ true);
-            }
-
-            boolean isRemote = mediaItem instanceof UriMediaItem
-                    && !Util.isLocalFileUri(((UriMediaItem) mediaItem).getUri());
-            mediaSources.add(mediaSource);
-            mediaItemInfos.add(new MediaItemInfo(mediaItem, isRemote));
-        }
-
-        private void releaseMediaItem(MediaItemInfo mediaItemInfo) {
-            MediaItem mediaItem = mediaItemInfo.mMediaItem;
-            try {
-                if (mediaItem instanceof FileMediaItem) {
-                    FileDescriptor fileDescriptor =
-                            ((FileMediaItem) mediaItem).getParcelFileDescriptor()
-                                    .getFileDescriptor();
-                    mFileDescriptorRegistry.unregisterMediaItem(fileDescriptor);
-                    ((FileMediaItem) mediaItem).decreaseRefCount();
-                } else if (mediaItem instanceof CallbackMediaItem) {
-                    ((CallbackMediaItem) mediaItem).getDataSourceCallback().close();
-                }
-            } catch (IOException e) {
-                Log.w(TAG, "Error releasing media item " + mediaItem, e);
-            }
-        }
-
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorDataSource.java b/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorDataSource.java
deleted file mode 100644
index 317338a..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorDataSource.java
+++ /dev/null
@@ -1,152 +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.media2.player;
-
-import android.net.Uri;
-
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.upstream.BaseDataSource;
-import androidx.media2.exoplayer.external.upstream.DataSource;
-import androidx.media2.exoplayer.external.upstream.DataSpec;
-
-import java.io.EOFException;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * An ExoPayer {@link DataSource} for reading from a file descriptor.
- */
-@SuppressWarnings("unchecked")
-/* package */ class FileDescriptorDataSource extends BaseDataSource {
-
-    /**
-     * Returns a factory for {@link FileDescriptorDataSource}s.
-     *
-     * @param fileDescriptor The file descriptor to read from.
-     * @param offset The start offset in the file descriptor.
-     * @param length The length of the range to read from the file descriptor.
-     * @param lock An object to synchronize on when using the file descriptor.
-     * @return A factory for data sources that read from the file descriptor.
-     */
-    static DataSource.Factory getFactory(
-            final FileDescriptor fileDescriptor,
-            final long offset,
-            final long length,
-            final Object lock) {
-        return new DataSource.Factory() {
-            @Override
-            public DataSource createDataSource() {
-                return new FileDescriptorDataSource(fileDescriptor, offset, length, lock);
-            }
-        };
-    }
-
-    // TODO(b/80232248): Move into core ExoPlayer library and delete this class.
-
-    private final FileDescriptor mFileDescriptor;
-    private final long mOffset;
-    private final long mLength;
-    private final Object mLock;
-
-    private @Nullable Uri mUri;
-    private @Nullable InputStream mInputStream;
-    private long mBytesRemaining;
-    private boolean mOpened;
-    private long mPosition;
-
-    FileDescriptorDataSource(
-            FileDescriptor fileDescriptor, long offset, long length, Object lock) {
-        super(/* isNetwork= */ false);
-        mFileDescriptor = fileDescriptor;
-        mOffset = offset;
-        mLength = length;
-        mLock = lock;
-    }
-
-    @Override
-    public long open(DataSpec dataSpec) {
-        mUri = dataSpec.uri;
-        transferInitializing(dataSpec);
-        mInputStream = new FileInputStream(mFileDescriptor);
-        if (dataSpec.length != C.LENGTH_UNSET) {
-            mBytesRemaining = dataSpec.length;
-        } else if (mLength != C.LENGTH_UNSET) {
-            mBytesRemaining = mLength - dataSpec.position;
-        } else {
-            mBytesRemaining = C.LENGTH_UNSET;
-        }
-        mPosition = mOffset + dataSpec.position;
-        mOpened = true;
-        transferStarted(dataSpec);
-        return mBytesRemaining;
-    }
-
-    @Override
-    public int read(byte[] buffer, int offset, int readLength) throws IOException {
-        if (readLength == 0) {
-            return 0;
-        } else if (mBytesRemaining == 0) {
-            return C.RESULT_END_OF_INPUT;
-        }
-        int bytesToRead = mBytesRemaining == C.LENGTH_UNSET
-                ? readLength : (int) Math.min(mBytesRemaining, readLength);
-        int bytesRead;
-        synchronized (mLock) {
-            // The file descriptor position is shared across all users, so seek before reading.
-            FileDescriptorUtil.seek(mFileDescriptor, mPosition);
-            bytesRead = Preconditions.checkNotNull(mInputStream).read(buffer, offset, bytesToRead);
-            if (bytesRead == -1) {
-                if (mBytesRemaining != C.LENGTH_UNSET) {
-                    throw new EOFException();
-                }
-                return C.RESULT_END_OF_INPUT;
-            }
-            mPosition += bytesRead;
-        }
-        if (mBytesRemaining != C.LENGTH_UNSET) {
-            mBytesRemaining -= bytesRead;
-        }
-        bytesTransferred(bytesRead);
-        return bytesRead;
-    }
-
-    @Override
-    public Uri getUri() {
-        return Preconditions.checkNotNull(mUri);
-    }
-
-    @Override
-    public void close() throws IOException {
-        mUri = null;
-        try {
-            if (mInputStream != null) {
-                mInputStream.close();
-            }
-        } finally {
-            mInputStream = null;
-            if (mOpened) {
-                mOpened = false;
-                transferEnded();
-            }
-        }
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorUtil.java b/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorUtil.java
deleted file mode 100644
index 9d342b8..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/FileDescriptorUtil.java
+++ /dev/null
@@ -1,195 +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.media2.player;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-/**
- * Utility methods for handling file descriptors.
- */
-/* package */ final class FileDescriptorUtil {
-
-    /**
-     * {@link OsConstants} was added in API 21 and initializes its fields lazily, so we directly
-     * specify the constant for the {@code lseek} {@code whence} argument for earlier API versions.
-     */
-    private static final int SEEK_SET = 0;
-
-    // Before API 21 we access the hidden Posix.lseek API using reflection.
-    private static final Object sPosixLockV14 = new Object();
-    @GuardedBy("sPosixLockV14")
-    @Nullable
-    private static Object sPosixObjectV14;
-    @GuardedBy("sPosixLockV14")
-    private static @Nullable Method sLseekMethodV14;
-    @GuardedBy("sPosixLockV14")
-    private static @Nullable Method sDupMethodV14;
-    @GuardedBy("sPosixLockV14")
-    private static @Nullable Method sCloseMethodV14;
-
-    public static FileDescriptor dup(FileDescriptor fileDescriptor) throws IOException {
-        if (Build.VERSION.SDK_INT >= 21) {
-            return dupV21(fileDescriptor);
-        } else {
-            return dupV14(fileDescriptor);
-        }
-    }
-
-    public static void seek(FileDescriptor fileDescriptor, long position) throws IOException {
-        if (Build.VERSION.SDK_INT >= 21) {
-            seekV21(fileDescriptor, position);
-        } else {
-            seekV14(fileDescriptor, position);
-        }
-    }
-
-    public static void close(FileDescriptor fileDescriptor) throws IOException {
-        if (Build.VERSION.SDK_INT >= 21) {
-            closeV21(fileDescriptor);
-        } else {
-            closeV14(fileDescriptor);
-        }
-    }
-
-    @RequiresApi(21)
-    private static FileDescriptor dupV21(FileDescriptor fileDescriptor) throws IOException {
-        try {
-            return Api21Impl.dup(fileDescriptor);
-        } catch (Exception e) {
-            throw new IOException("Failed to dup the file descriptor", e);
-        }
-    }
-
-    @SuppressLint("PrivateApi")
-    private static FileDescriptor dupV14(FileDescriptor fileDescriptor) throws IOException {
-        try {
-            Method method;
-            Object object;
-            synchronized (sPosixLockV14) {
-                ensurePosixObjectsInitialized();
-                object = sPosixObjectV14;
-                method = sDupMethodV14;
-            }
-            return (FileDescriptor) method.invoke(object, fileDescriptor);
-        } catch (Exception e) {
-            throw new IOException("Failed to dup the file descriptor", e);
-        }
-    }
-
-    @RequiresApi(21)
-    private static void seekV21(FileDescriptor fileDescriptor, long position) throws IOException {
-        try {
-            Api21Impl.lseek(fileDescriptor, position, /* whence= */ OsConstants.SEEK_SET);
-        } catch (Exception e) {
-            throw new IOException("Failed to seek the file descriptor", e);
-        }
-    }
-
-    @SuppressLint("PrivateApi")
-    private static void seekV14(FileDescriptor fileDescriptor, long position) throws IOException {
-        try {
-            Method method;
-            Object object;
-            synchronized (sPosixLockV14) {
-                ensurePosixObjectsInitialized();
-                object = sPosixObjectV14;
-                method = sLseekMethodV14;
-            }
-            method.invoke(object, fileDescriptor, position, /* whence= */ SEEK_SET);
-        } catch (Exception e) {
-            throw new IOException("Failed to seek the file descriptor", e);
-        }
-    }
-
-    @RequiresApi(21)
-    private static void closeV21(FileDescriptor fileDescriptor) throws IOException {
-        try {
-            Api21Impl.close(fileDescriptor);
-        } catch (Exception e) {
-            throw new IOException("Failed to close the file descriptor", e);
-        }
-    }
-
-    @SuppressLint("PrivateApi")
-    private static FileDescriptor closeV14(FileDescriptor fileDescriptor) throws IOException {
-        try {
-            Method method;
-            Object object;
-            synchronized (sPosixLockV14) {
-                ensurePosixObjectsInitialized();
-                object = sPosixObjectV14;
-                method = sCloseMethodV14;
-            }
-            return (FileDescriptor) method.invoke(object, fileDescriptor);
-        } catch (Exception e) {
-            throw new IOException("Failed to close the file descriptor", e);
-        }
-    }
-
-    private static void ensurePosixObjectsInitialized() throws Exception {
-        synchronized (sPosixLockV14) {
-            if (sPosixObjectV14 != null) {
-                return;
-            }
-            Class<?> posixClass = Class.forName("libcore.io.Posix");
-            Constructor<?> constructor = posixClass.getDeclaredConstructor();
-            constructor.setAccessible(true);
-            sLseekMethodV14 = posixClass.getMethod(
-                    "lseek", FileDescriptor.class, Long.TYPE, Integer.TYPE);
-            sDupMethodV14 = posixClass.getMethod("dup", FileDescriptor.class);
-            sCloseMethodV14 = posixClass.getMethod("close", FileDescriptor.class);
-            sPosixObjectV14 = constructor.newInstance();
-        }
-    }
-
-    @RequiresApi(21)
-    static class Api21Impl {
-
-        @DoNotInline
-        static FileDescriptor dup(FileDescriptor fileDescriptor) throws ErrnoException {
-            return Os.dup(fileDescriptor);
-        }
-
-        @DoNotInline
-        static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
-            return Os.lseek(fd, offset, whence);
-        }
-
-        @DoNotInline
-        static void close(FileDescriptor fd) throws ErrnoException {
-            Os.close(fd);
-        }
-
-        private Api21Impl() {}
-    }
-
-    private FileDescriptorUtil() {}
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/Id3MetadataDecoderFactory.java b/media2/media2-player/src/main/java/androidx/media2/player/Id3MetadataDecoderFactory.java
deleted file mode 100644
index 7605110..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/Id3MetadataDecoderFactory.java
+++ /dev/null
@@ -1,53 +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.media2.player;
-
-
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.metadata.Metadata;
-import androidx.media2.exoplayer.external.metadata.MetadataDecoder;
-import androidx.media2.exoplayer.external.metadata.MetadataDecoderFactory;
-import androidx.media2.exoplayer.external.metadata.MetadataInputBuffer;
-import androidx.media2.exoplayer.external.util.MimeTypes;
-
-import java.util.Arrays;
-
-/**
- * Factory for metadata decoders that provide raw ID3 data in {@link ByteArrayFrame}s.
- */
-/* package */ final class Id3MetadataDecoderFactory implements MetadataDecoderFactory {
-
-    @Override
-    public boolean supportsFormat(Format format) {
-        return MimeTypes.APPLICATION_ID3.equals(format.sampleMimeType);
-    }
-
-    @Override
-    public MetadataDecoder createDecoder(Format format) {
-        return new MetadataDecoder() {
-            @Override
-            public Metadata decode(MetadataInputBuffer inputBuffer) {
-                long timestamp = inputBuffer.timeUs;
-                byte[] bufferData = inputBuffer.data.array();
-                Metadata.Entry entry =
-                        new ByteArrayFrame(timestamp, Arrays.copyOf(bufferData, bufferData.length));
-                return new Metadata(entry);
-            }
-        };
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer.java b/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer.java
deleted file mode 100644
index aaf3953..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ /dev/null
@@ -1,3923 +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.media2.player;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_IO;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_PERMISSION_DENIED;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.media.AudioManager;
-import android.media.DeniedByServerException;
-import android.media.MediaDrm;
-import android.media.MediaFormat;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.annotation.FloatRange;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntDef;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.collection.ArrayMap;
-import androidx.concurrent.futures.AbstractResolvableFuture;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.ObjectsCompat;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.UriMediaItem;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * A media player which plays {@link MediaItem}s. The details on playback control and player states
- * can be found in the documentation of the base class, {@link SessionPlayer}.
- *
- * <p>Topic covered here:
- *
- * <ol>
- *   <li><a href="#AudioFocusAndNoisyIntent">Audio focus and noisy intent</a>
- * </ol>
- *
- * <h3 id="AudioFocusAndNoisyIntent">Audio focus and noisy intent</h3>
- *
- * <p>By default, {@link MediaPlayer} handles audio focus and noisy intent with {@link
- * AudioAttributesCompat} set to this player. You need to call {@link
- * #setAudioAttributes(AudioAttributesCompat)} set the audio attribute while in the {@link
- * #PLAYER_STATE_IDLE}.
- *
- * <p>Here's the table of automatic audio focus behavior with audio attributes.
- *
- * <table summary="Audio focus handling overview">
- * <tr><th>Audio Attributes</th><th>Audio Focus Gain Type</th><th>Misc</th></tr>
- * <tr><td>{@link AudioAttributesCompat#USAGE_VOICE_COMMUNICATION_SIGNALLING}</td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_NONE}</td>
- *     <td></td></tr>
- * <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_GAME}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_MEDIA}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_UNKNOWN}</li></ul></td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN}</td>
- *     <td>Developers should specific a proper usage instead of
- *         {@link AudioAttributesCompat#USAGE_UNKNOWN}</td></tr>
- * <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ALARM}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_VOICE_COMMUNICATION}</li></ul></td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}</td>
- *     <td></td></tr>
- * <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_ASSISTANCE_SONIFICATION}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_DELAYED}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_INSTANT}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_REQUEST}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_EVENT}</li>
- *             <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_RINGTONE}</li></ul></td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}</td>
- *     <td></td></tr>
- * <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ASSISTANT}</li></ul></td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}</td>
- *     <td></td></tr>
- * <tr><td>{@link AudioAttributesCompat#USAGE_ASSISTANCE_ACCESSIBILITY}</td>
- *     <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} if
- *         {@link AudioAttributesCompat#CONTENT_TYPE_SPEECH},
- *         {@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} otherwise</td>
- *     <td></td></tr>
- * <tr><td>{@code null}</td>
- *     <td>No audio focus handling, and sets the player volume to {@code 0}</td>
- *     <td>Only valid if your media contents don't have audio</td></tr>
- * <tr><td>Any other AudioAttributes</td>
- *     <td>No audio focus handling, and sets the player volume to {@code 0}</td>
- *     <td>This is to handle error</td></tr>
- * </table>
- *
- * <p>If an {@link AudioAttributesCompat} is not specified by {@link #setAudioAttributes}, {@link
- * #getAudioAttributes} will return {@code null} and the default audio focus behavior will follow
- * the {@code null} case on the table above.
- *
- * <p>For more information about the audio focus, take a look at <a
- * href="{@docRoot}guide/topics/media-apps/audio-focus.html">Managing audio focus</a>
- *
- * <p>
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public final class MediaPlayer extends SessionPlayer {
-    private static final String TAG = "MediaPlayer";
-
-    /**
-     * Unspecified player error.
-     * @see PlayerCallback#onError
-     */
-    public static final int PLAYER_ERROR_UNKNOWN = 1;
-    /**
-     * File or network related operation errors.
-     * @see PlayerCallback#onError
-     */
-    public static final int PLAYER_ERROR_IO = -1004;
-    /**
-     * Bitstream is not conforming to the related coding standard or file spec.
-     * @see PlayerCallback#onError
-     */
-    public static final int PLAYER_ERROR_MALFORMED = -1007;
-    /**
-     * Bitstream is conforming to the related coding standard or file spec, but
-     * the media framework does not support the feature.
-     * @see PlayerCallback#onError
-     */
-    public static final int PLAYER_ERROR_UNSUPPORTED = -1010;
-    /**
-     * Some operation takes too long to complete, usually more than 3-5 seconds.
-     * @see PlayerCallback#onError
-     */
-    public static final int PLAYER_ERROR_TIMED_OUT = -110;
-
-    /**
-     */
-    @IntDef(flag = false, /*prefix = "PLAYER_ERROR",*/ value = {
-            PLAYER_ERROR_UNKNOWN,
-            PLAYER_ERROR_IO,
-            PLAYER_ERROR_MALFORMED,
-            PLAYER_ERROR_UNSUPPORTED,
-            PLAYER_ERROR_TIMED_OUT,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface MediaError {}
-
-    /**
-     * The player just started the playback of this media item.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_MEDIA_ITEM_START = 2;
-
-    /**
-     * The player just pushed the very first video frame for rendering.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
-
-    /**
-     * The player just completed the playback of this media item.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_MEDIA_ITEM_END = 5;
-
-    /**
-     * The player just completed the playback of all the media items set by {@link #setPlaylist}
-     * and {@link #setMediaItem}.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_MEDIA_ITEM_LIST_END = 6;
-
-    /**
-     * The player just completed an iteration of playback loop. This event is sent only when
-     * looping is enabled by {@link #setRepeatMode(int)}.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_MEDIA_ITEM_REPEAT = 7;
-
-    /**
-     * The player just finished preparing a media item for playback.
-     * @see #prepare()
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_PREPARED = 100;
-
-    /**
-     * The video is too complex for the decoder: it can't decode frames fast
-     * enough. Possibly only the audio plays fine at this stage.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
-
-    /**
-     * The player is temporarily pausing playback internally in order to
-     * buffer more data.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_BUFFERING_START = 701;
-
-    /**
-     * The player is resuming playback after filling buffers.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_BUFFERING_END = 702;
-
-    /**
-     * Estimated network bandwidth information (kbps) is available; currently this event fires
-     * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
-     * when playing network files.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
-
-    /**
-     * Update status in buffering a media source received through progressive downloading.
-     * The received buffering percentage indicates how much of the content has been buffered
-     * or played. For example a buffering update of 80 percent when half the content
-     * has already been played indicates that the next 30 percent of the
-     * content to play has been buffered.
-     *
-     * <p>The {@code extra} parameter in {@link PlayerCallback#onInfo} is the
-     * percentage (0-100) of the content that has been buffered or played thus far.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
-
-    /**
-     * Bad interleaving means that a media has been improperly interleaved or
-     * not interleaved at all, e.g has all the video samples first then all the
-     * audio ones. Video is playing but a lot of disk seeks may be happening.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
-
-    /**
-     * The media cannot be seeked (e.g live stream)
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
-
-    /**
-     * A new set of metadata is available.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_METADATA_UPDATE = 802;
-
-    /**
-     * A new set of external-only metadata is available.  Used by
-     * JAVA framework to avoid triggering track scanning.
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803;
-
-    /**
-     * Informs that audio is not playing. Note that playback of the video
-     * is not interrupted.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
-
-    /**
-     * Informs that video is not playing. Note that playback of the audio
-     * is not interrupted.
-     * @see PlayerCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
-
-    /**
-     * Subtitle track was not supported by the media framework.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
-
-    /**
-     * Reading the subtitle track takes too long.
-     * @see PlayerCallback#onInfo
-     */
-    @RestrictTo(LIBRARY)
-    public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
-
-    /**
-     */
-    @IntDef(flag = false, /*prefix = "MEDIA_INFO",*/ value = {
-            MEDIA_INFO_MEDIA_ITEM_START,
-            MEDIA_INFO_VIDEO_RENDERING_START,
-            MEDIA_INFO_MEDIA_ITEM_END,
-            MEDIA_INFO_MEDIA_ITEM_LIST_END,
-            MEDIA_INFO_MEDIA_ITEM_REPEAT,
-            MEDIA_INFO_PREPARED,
-            MEDIA_INFO_VIDEO_TRACK_LAGGING,
-            MEDIA_INFO_BUFFERING_START,
-            MEDIA_INFO_BUFFERING_END,
-            MEDIA_INFO_NETWORK_BANDWIDTH,
-            MEDIA_INFO_BUFFERING_UPDATE,
-            MEDIA_INFO_BAD_INTERLEAVING,
-            MEDIA_INFO_NOT_SEEKABLE,
-            MEDIA_INFO_METADATA_UPDATE,
-            MEDIA_INFO_EXTERNAL_METADATA_UPDATE,
-            MEDIA_INFO_AUDIO_NOT_PLAYING,
-            MEDIA_INFO_VIDEO_NOT_PLAYING,
-            MEDIA_INFO_UNSUPPORTED_SUBTITLE,
-            MEDIA_INFO_SUBTITLE_TIMED_OUT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface MediaInfo {}
-
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * right before or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_PREVIOUS_SYNC    = 0x00;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * right after or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_NEXT_SYNC        = 0x01;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * closest to (in time) or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST_SYNC     = 0x02;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a frame (not necessarily a key frame) associated with a media item that
-     * is located closest to or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST          = 0x03;
-
-    @IntDef(flag = false, /*prefix = "SEEK",*/ value = {
-            SEEK_PREVIOUS_SYNC,
-            SEEK_NEXT_SYNC,
-            SEEK_CLOSEST_SYNC,
-            SEEK_CLOSEST,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface SeekMode {}
-
-    /**
-     * The return value of {@link #getSelectedTrack(int)} when there is no selected track
-     * for the given type.
-     *
-     * @see #getSelectedTrack(int)
-     * @deprecated {@link #getSelectedTrack(int)} returns {@code null} instead of this value.
-     */
-    @Deprecated
-    public static final int NO_TRACK_SELECTED = Integer.MIN_VALUE;
-
-    static final PlaybackParams DEFAULT_PLAYBACK_PARAMS = new PlaybackParams.Builder()
-            .setSpeed(1f)
-            .setPitch(1f)
-            .setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT)
-            .build();
-
-    private static final int END_OF_PLAYLIST = -1;
-    private static final int NO_MEDIA_ITEM = -2;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ArrayMap<Integer, Integer> sResultCodeMap;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ArrayMap<Integer, Integer> sErrorCodeMap;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ArrayMap<Integer, Integer> sInfoCodeMap;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ArrayMap<Integer, Integer> sSeekModeMap;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ArrayMap<Integer, Integer> sPrepareDrmStatusMap;
-
-    static {
-        sResultCodeMap = new ArrayMap<>();
-        sResultCodeMap.put(MediaPlayer2.CALL_STATUS_NO_ERROR, RESULT_SUCCESS);
-        sResultCodeMap.put(MediaPlayer2.CALL_STATUS_ERROR_UNKNOWN, RESULT_ERROR_UNKNOWN);
-        sResultCodeMap.put(
-                MediaPlayer2.CALL_STATUS_INVALID_OPERATION, RESULT_ERROR_INVALID_STATE);
-        sResultCodeMap.put(MediaPlayer2.CALL_STATUS_BAD_VALUE, RESULT_ERROR_BAD_VALUE);
-        sResultCodeMap.put(
-                MediaPlayer2.CALL_STATUS_PERMISSION_DENIED, RESULT_ERROR_PERMISSION_DENIED);
-        sResultCodeMap.put(MediaPlayer2.CALL_STATUS_ERROR_IO, RESULT_ERROR_IO);
-        sResultCodeMap.put(MediaPlayer2.CALL_STATUS_SKIPPED, RESULT_INFO_SKIPPED);
-
-        sErrorCodeMap = new ArrayMap<>();
-        sErrorCodeMap.put(MediaPlayer2.MEDIA_ERROR_UNKNOWN, PLAYER_ERROR_UNKNOWN);
-        sErrorCodeMap.put(MediaPlayer2.MEDIA_ERROR_IO, PLAYER_ERROR_IO);
-        sErrorCodeMap.put(MediaPlayer2.MEDIA_ERROR_MALFORMED, PLAYER_ERROR_MALFORMED);
-        sErrorCodeMap.put(MediaPlayer2.MEDIA_ERROR_UNSUPPORTED, PLAYER_ERROR_UNSUPPORTED);
-        sErrorCodeMap.put(MediaPlayer2.MEDIA_ERROR_TIMED_OUT, PLAYER_ERROR_TIMED_OUT);
-
-        sInfoCodeMap = new ArrayMap<>();
-        sInfoCodeMap.put(
-                MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START, MEDIA_INFO_VIDEO_RENDERING_START);
-        sInfoCodeMap.put(
-                MediaPlayer2.MEDIA_INFO_VIDEO_TRACK_LAGGING, MEDIA_INFO_VIDEO_TRACK_LAGGING);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE, MEDIA_INFO_BUFFERING_UPDATE);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_BAD_INTERLEAVING, MEDIA_INFO_BAD_INTERLEAVING);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_NOT_SEEKABLE, MEDIA_INFO_NOT_SEEKABLE);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_METADATA_UPDATE, MEDIA_INFO_METADATA_UPDATE);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_AUDIO_NOT_PLAYING, MEDIA_INFO_AUDIO_NOT_PLAYING);
-        sInfoCodeMap.put(MediaPlayer2.MEDIA_INFO_VIDEO_NOT_PLAYING, MEDIA_INFO_VIDEO_NOT_PLAYING);
-
-        sSeekModeMap = new ArrayMap<>();
-        sSeekModeMap.put(SEEK_PREVIOUS_SYNC, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        sSeekModeMap.put(SEEK_NEXT_SYNC, MediaPlayer2.SEEK_NEXT_SYNC);
-        sSeekModeMap.put(SEEK_CLOSEST_SYNC, MediaPlayer2.SEEK_CLOSEST_SYNC);
-        sSeekModeMap.put(SEEK_CLOSEST, MediaPlayer2.SEEK_CLOSEST);
-
-        sPrepareDrmStatusMap = new ArrayMap<>();
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS,
-                DrmResult.RESULT_SUCCESS);
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
-                DrmResult.RESULT_ERROR_PROVISIONING_NETWORK_ERROR);
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
-                DrmResult.RESULT_ERROR_PREPARATION_ERROR);
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_PREPARATION_ERROR,
-                DrmResult.RESULT_ERROR_PREPARATION_ERROR);
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
-                DrmResult.RESULT_ERROR_UNSUPPORTED_SCHEME);
-        sPrepareDrmStatusMap.put(MediaPlayer2.PREPARE_DRM_STATUS_RESOURCE_BUSY,
-                DrmResult.RESULT_ERROR_RESOURCE_BUSY);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaPlayer2 mPlayer;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ExecutorService mExecutor;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static final class PendingCommand {
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        @MediaPlayer2.CallCompleted final int mCallType;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        final ResolvableFuture<? extends PlayerResult> mFuture;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        final SessionPlayer.TrackInfo mTrackInfo;
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        PendingCommand(int callType, ResolvableFuture<? extends PlayerResult> future) {
-            this(callType, future, null);
-        }
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        PendingCommand(int callType, ResolvableFuture<? extends PlayerResult> future,
-                SessionPlayer.TrackInfo trackInfo) {
-            mCallType = callType;
-            mFuture = future;
-            mTrackInfo = trackInfo;
-        }
-
-        @SuppressWarnings("unchecked")
-        <V extends PlayerResult> void setResult(V value) {
-            ((ResolvableFuture<V>) mFuture).set(value);
-        }
-    }
-
-    /* A list for tracking the commands submitted to MediaPlayer2.*/
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @GuardedBy("mPendingCommands")
-    final ArrayDeque<PendingCommand> mPendingCommands = new ArrayDeque<>();
-
-    /**
-     * PendingFuture is a future for the result of execution which will be executed later via
-     * the onExecute() method.
-     */
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    abstract static class PendingFuture<V extends PlayerResult>
-            extends AbstractResolvableFuture<V> {
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        final boolean mIsSeekTo;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        boolean mExecuteCalled = false;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        List<ResolvableFuture<V>> mFutures;
-
-        PendingFuture(Executor executor) {
-            this(executor, false);
-        }
-
-        PendingFuture(Executor executor, boolean isSeekTo) {
-            mIsSeekTo = isSeekTo;
-            addListener(new Runnable() {
-                @Override
-                public void run() {
-                    if (isCancelled() && mExecuteCalled) {
-                        cancelFutures();
-                    }
-                }
-            }, executor);
-        }
-
-        @Override
-        public boolean set(@Nullable V value) {
-            return super.set(value);
-        }
-
-        @Override
-        public boolean setException(Throwable throwable) {
-            return super.setException(throwable);
-        }
-
-        public boolean execute() {
-            if (!mExecuteCalled && !isCancelled()) {
-                mExecuteCalled = true;
-                mFutures = onExecute();
-            }
-            if (!isCancelled() && !isDone()) {
-                setResultIfFinished();
-            }
-            return isCancelled() || isDone();
-        }
-
-        private void setResultIfFinished() {
-            V result = null;
-            for (int i = 0; i < mFutures.size(); ++i) {
-                ResolvableFuture<V> future = mFutures.get(i);
-                if (!future.isDone() && !future.isCancelled()) {
-                    return;
-                }
-                try {
-                    result = future.get();
-                    int resultCode = result.getResultCode();
-                    if (resultCode != RESULT_SUCCESS && resultCode != RESULT_INFO_SKIPPED) {
-                        cancelFutures();
-                        set(result);
-                        return;
-                    }
-                } catch (Exception e) {
-                    cancelFutures();
-                    setException(e);
-                    return;
-                }
-            }
-            try {
-                set(result);
-            } catch (Exception e) {
-                setException(e);
-            }
-        }
-
-        abstract List<ResolvableFuture<V>> onExecute();
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        void cancelFutures() {
-            if (mFutures != null) {
-                for (ResolvableFuture<V> future : mFutures) {
-                    if (!future.isCancelled() && !future.isDone()) {
-                        future.cancel(true);
-                    }
-                }
-            }
-        }
-    }
-
-    /* A list of pending operations within this MediaPlayer that will be executed sequentially. */
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @GuardedBy("mPendingFutures")
-    final ArrayDeque<PendingFuture<? extends PlayerResult>> mPendingFutures = new ArrayDeque<>();
-
-    private final Object mStateLock = new Object();
-    @GuardedBy("mStateLock")
-    @PlayerState
-    private int mState;
-    @GuardedBy("mStateLock")
-    private Map<MediaItem, Integer> mMediaItemToBuffState = new HashMap<>();
-    @GuardedBy("mStateLock")
-    private boolean mClosed;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final AudioFocusHandler mAudioFocusHandler;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Object mPlaylistLock = new Object();
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItemList mPlaylist = new MediaItemList();
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ArrayList<MediaItem> mShuffledList = new ArrayList<>();
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaMetadata mPlaylistMetadata;
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mRepeatMode;
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mShuffleMode;
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mCurrentShuffleIdx;
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem mCurPlaylistItem;
-    @GuardedBy("mPlaylistLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem mNextPlaylistItem;
-    @GuardedBy("mPlaylistLock")
-    private boolean mSetMediaItemCalled;
-
-    /**
-     * Constructor to create a MediaPlayer instance.
-     *
-     * @param context A {@link Context} that will be used to resolve {@link UriMediaItem}.
-     */
-    public MediaPlayer(@NonNull Context context) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        mState = PLAYER_STATE_IDLE;
-        mPlayer = MediaPlayer2.create(context);
-        mExecutor = Executors.newFixedThreadPool(1);
-        mPlayer.setEventCallback(mExecutor, new Mp2Callback());
-        mPlayer.setDrmEventCallback(mExecutor, new Mp2DrmCallback());
-        mCurrentShuffleIdx = NO_MEDIA_ITEM;
-        mAudioFocusHandler = new AudioFocusHandler(context, this);
-    }
-
-    @GuardedBy("mPendingCommands")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void addPendingCommandLocked(
-            int callType, final ResolvableFuture<? extends PlayerResult> future,
-            final Object token) {
-        final PendingCommand pendingCommand = new PendingCommand(callType, future);
-        mPendingCommands.add(pendingCommand);
-        addFutureListener(pendingCommand, future, token);
-    }
-
-    @GuardedBy("mPendingCommands")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void addPendingCommandWithTrackInfoLocked(
-            int callType, final ResolvableFuture<? extends PlayerResult> future,
-            final SessionPlayer.TrackInfo trackInfo, final Object token) {
-        final PendingCommand pendingCommand = new PendingCommand(callType, future, trackInfo);
-        mPendingCommands.add(pendingCommand);
-        addFutureListener(pendingCommand, future, token);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void addFutureListener(final PendingCommand pendingCommand,
-            final ResolvableFuture<? extends PlayerResult> future, final Object token) {
-        future.addListener(new Runnable() {
-            @Override
-            public void run() {
-                // Propagate the cancellation to the MediaPlayer2 implementation.
-                if (future.isCancelled()) {
-                    synchronized (mPendingCommands) {
-                        if (mPlayer.cancel(token)) {
-                            mPendingCommands.remove(pendingCommand);
-                        }
-                    }
-                }
-            }
-        }, mExecutor);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void addPendingFuture(final PendingFuture<? extends PlayerResult> pendingFuture) {
-        synchronized (mPendingFutures) {
-            mPendingFutures.add(pendingFuture);
-            executePendingFutures();
-        }
-    }
-
-    /**
-     * Starts or resumes playback.
-     * <p>
-     * On success, this transfers the player state to {@link #PLAYER_STATE_PLAYING} and
-     * a {@link SessionPlayer.PlayerResult} would be returned with the current media item when
-     * the command was completed. If it is called in {@link #PLAYER_STATE_IDLE} or
-     * {@link #PLAYER_STATE_ERROR}, it would be ignored and a {@link SessionPlayer.PlayerResult}
-     * would be returned with {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                final ResolvableFuture<PlayerResult> future;
-                if (mAudioFocusHandler.onPlay()) {
-                    if (mPlayer.getAudioAttributes() == null) {
-                        futures.add(setPlayerVolumeInternal(0f));
-                    }
-                    future = ResolvableFuture.create();
-                    synchronized (mPendingCommands) {
-                        Object token = mPlayer.play();
-                        addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PLAY, future, token);
-                    }
-                } else {
-                    future = createFutureForResultCode(RESULT_ERROR_UNKNOWN);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Pauses playback.
-     * <p>
-     * On success, this transfers the player state to {@link #PLAYER_STATE_PAUSED} and
-     * a {@link SessionPlayer.PlayerResult} would be returned with the current media item when the
-     * command was completed. If it is called in {@link #PLAYER_STATE_IDLE} or
-     * {@link #PLAYER_STATE_ERROR}, it would be ignored and a {@link SessionPlayer.PlayerResult}
-     * would be returned with {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                mAudioFocusHandler.onPause();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.pause();
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PAUSE, future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Prepares the media items for playback. Before calling this API, set media item(s) through
-     * either {@link #setMediaItem} or {@link #setPlaylist}, and set a display surface through
-     * {@link #setSurface} when needed.
-     * <p>
-     * On success, this transfers the player state from {@link #PLAYER_STATE_IDLE} to
-     * {@link #PLAYER_STATE_PAUSED} and a {@link SessionPlayer.PlayerResult} would be returned with
-     * the prepared media item when the command completed. If it's not called in
-     * {@link #PLAYER_STATE_IDLE}, it would be ignored and {@link SessionPlayer.PlayerResult} would
-     * be returned with {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.prepare();
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE, future, token);
-                }
-                // TODO: Changing buffering state is not correct. Think about changing MP2 event
-                // APIs for the initial buffering for prepare case.
-                setBufferingState(mPlayer.getCurrentMediaItem(),
-                        BUFFERING_STATE_BUFFERING_AND_STARVED);
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Seeks to the specified position.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
-     * calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored
-     * and a {@link SessionPlayer.PlayerResult} would be returned with
-     * {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @param position the new playback position in ms. The value would be in the range of start
-     * and end positions defined in {@link MediaItem}.
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(final long position) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture =
-                new PendingFuture<PlayerResult>(mExecutor, true) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.seekTo(position);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets the playback speed. The default playback speed is {@code 1.0f}, and values less than
-     * or equals to {@code 0.0f} is not allowed.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @param playbackSpeed the requested playback speed
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #getPlaybackSpeed()
-     * @see PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(
-            @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
-            final float playbackSpeed) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                if (playbackSpeed <= 0.0f) {
-                    return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                }
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setPlaybackParams(new PlaybackParams.Builder(
-                            mPlayer.getPlaybackParams())
-                            .setSpeed(playbackSpeed).build());
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets the {@link AudioAttributesCompat} to be used during the playback of the media.
-     * <p>
-     * You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
-     * become effective thereafter. Otherwise, the call would be ignored and
-     * {@link SessionPlayer.PlayerResult} would be returned with
-     * {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE}.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @param attributes non-null <code>AudioAttributes</code>.
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull final AudioAttributesCompat attributes) {
-        if (attributes == null) {
-            throw new NullPointerException("attr shouldn't be null");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setAudioAttributes(attributes);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    @Override
-    @PlayerState
-    public int getPlayerState() {
-        synchronized (mStateLock) {
-            return mState;
-        }
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return UNKNOWN_TIME;
-            }
-        }
-        try {
-            final long pos = mPlayer.getCurrentPosition();
-            if (pos >= 0) {
-                return pos;
-            }
-        } catch (IllegalStateException e) {
-            // fall-through.
-        }
-        return UNKNOWN_TIME;
-    }
-
-    @Override
-    public long getDuration() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return UNKNOWN_TIME;
-            }
-        }
-        try {
-            final long duration = mPlayer.getDuration();
-            if (duration >= 0) {
-                return duration;
-            }
-        } catch (IllegalStateException e) {
-            // fall-through.
-        }
-        return UNKNOWN_TIME;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return UNKNOWN_TIME;
-            }
-        }
-        try {
-            final long pos = mPlayer.getBufferedPosition();
-            if (pos >= 0) {
-                return pos;
-            }
-        } catch (IllegalStateException e) {
-            // fall-through.
-        }
-        return UNKNOWN_TIME;
-    }
-
-    @Override
-    @BuffState
-    public int getBufferingState() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return BUFFERING_STATE_UNKNOWN;
-            }
-        }
-        Integer buffState;
-        synchronized (mStateLock) {
-            buffState = mMediaItemToBuffState.get(mPlayer.getCurrentMediaItem());
-        }
-        return buffState == null ? BUFFERING_STATE_UNKNOWN : buffState;
-    }
-
-    @Override
-    @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
-    public float getPlaybackSpeed() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return 1.0f;
-            }
-        }
-        try {
-            return mPlayer.getPlaybackParams().getSpeed();
-        } catch (IllegalStateException e) {
-            return 1.0f;
-        }
-    }
-
-    @Override
-    @Nullable
-    public AudioAttributesCompat getAudioAttributes() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        try {
-            return mPlayer.getAudioAttributes();
-        } catch (IllegalStateException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Sets a {@link MediaItem} for playback. Use this or {@link #setPlaylist} to specify which
-     * items to play. If you want to change current item in the playlist, use one of
-     * {@link #skipToPlaylistItem}, {@link #skipToNextPlaylistItem}, or
-     * {@link #skipToPreviousPlaylistItem} instead of this method.
-     * <p>
-     * When this is called multiple times in any states other than {@link #PLAYER_STATE_ERROR}, it
-     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
-     * <p>
-     * It's recommended to fill {@link MediaMetadata} in {@link MediaItem} especially for the
-     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
-     * duration information in the metadata, session will do extra work to get the duration and send
-     * it to the controller.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with {@code item} set.
-     *
-     * @param item the descriptor of media item you want to play
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see #setPlaylist
-     * @see PlayerCallback#onPlaylistChanged
-     * @see PlayerCallback#onCurrentMediaItemChanged
-     * @throws IllegalArgumentException if the given item is {@code null}.
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull final MediaItem item) {
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        if (item instanceof FileMediaItem) {
-            if (((FileMediaItem) item).isClosed()) {
-                throw new IllegalArgumentException("File descriptor is closed. " + item);
-            }
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                synchronized (mPlaylistLock) {
-                    mPlaylist.clear();
-                    mPlaylistMetadata = null;
-                    mShuffledList.clear();
-                    mCurPlaylistItem = item;
-                    mNextPlaylistItem = null;
-                    mCurrentShuffleIdx = END_OF_PLAYLIST;
-                }
-                notifySessionPlayerCallback(
-                        callback -> callback.onPlaylistChanged(MediaPlayer.this, null, null));
-                futures.addAll(setMediaItemsInternal(item, null));
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets a list of {@link MediaItem} with metadata. Use this or {@link #setMediaItem} to specify
-     * which items to play.
-     * <p>
-     * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
-     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
-     * <p>
-     * Ensure uniqueness of each {@link MediaItem} in the playlist so the session can uniquely
-     * identity individual items. All {@link MediaItem}s wouldn't be {@code null} as well.
-     * <p>
-     * It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
-     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
-     * duration information in the metadata, session will do extra work to get the duration and send
-     * it to the controller.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the first media item
-     * of the playlist when the command completed.
-     *
-     * @param list a list of {@link MediaItem} objects to set as a play list
-     * @throws IllegalArgumentException if the given list is {@code null} or empty, or has
-     *         duplicated media items.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see #setMediaItem
-     * @see PlayerCallback#onPlaylistChanged
-     * @see PlayerCallback#onCurrentMediaItemChanged
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(
-            @NonNull final List<MediaItem> list, @Nullable final MediaMetadata metadata) {
-        if (list == null) {
-            throw new NullPointerException("list shouldn't be null");
-        } else if (list.isEmpty()) {
-            throw new IllegalArgumentException("list shouldn't be empty");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        String errorString = null;
-        for (MediaItem item : list) {
-            if (item == null) {
-                errorString = "list shouldn't contain null item";
-                break;
-            }
-            if (item instanceof FileMediaItem) {
-                if (((FileMediaItem) item).isClosed()) {
-                    errorString = "File descriptor is closed. " + item;
-                    break;
-                }
-            }
-        }
-        if (errorString != null) {
-            // Close all the given FileMediaItems on error case.
-            for (MediaItem item : list) {
-                if (item instanceof FileMediaItem) {
-                    ((FileMediaItem) item).increaseRefCount();
-                    ((FileMediaItem) item).decreaseRefCount();
-                }
-            }
-            throw new IllegalArgumentException(errorString);
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                synchronized (mPlaylistLock) {
-                    mPlaylistMetadata = metadata;
-                    mPlaylist.replaceAll(list);
-                    applyShuffleModeLocked();
-                    mCurrentShuffleIdx = 0;
-                    updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                notifySessionPlayerCallback(callback -> {
-                    callback.onPlaylistChanged(MediaPlayer.this, list, metadata);
-                });
-                if (curItem != null) {
-                    return setMediaItemsInternal(curItem, nextItem);
-                }
-                return createFuturesForResultCode(RESULT_SUCCESS);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Adds the media item to the playlist at the index. Index equals to or greater than
-     * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
-     * the playlist.
-     * <p>
-     * If index is less than or equal to the current index of the playlist,
-     * the current index of the playlist would be increased correspondingly.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with {@code item} added.
-     *
-     * @param index the index of the item you want to add in the playlist
-     * @param item the media item you want to add
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(
-            final int index, @NonNull final MediaItem item) {
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        if (item instanceof FileMediaItem) {
-            if (((FileMediaItem) item).isClosed()) {
-                throw new IllegalArgumentException("File descriptor is closed. " + item);
-            }
-        }
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                Pair<MediaItem, MediaItem> updatedCurNextItem;
-                synchronized (mPlaylistLock) {
-                    if (mPlaylist.contains(item)) {
-                        return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE, item);
-                    }
-                    int clampedIndex = clamp(index, mPlaylist.size());
-                    int addedShuffleIdx = clampedIndex;
-                    mPlaylist.add(clampedIndex, item);
-                    if (mShuffleMode == SessionPlayer.SHUFFLE_MODE_NONE) {
-                        mShuffledList.add(clampedIndex, item);
-                    } else {
-                        // Add the item in random position of mShuffledList.
-                        addedShuffleIdx = (int) (Math.random() * (mShuffledList.size() + 1));
-                        mShuffledList.add(addedShuffleIdx, item);
-                    }
-                    if (addedShuffleIdx <= mCurrentShuffleIdx) {
-                        mCurrentShuffleIdx++;
-                    }
-                    updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
-                }
-                final List<MediaItem> playlist = getPlaylist();
-                final MediaMetadata metadata = getPlaylistMetadata();
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(
-                            SessionPlayer.PlayerCallback callback) {
-                        callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
-                    }
-                });
-
-                if (updatedCurNextItem == null || updatedCurNextItem.second == null) {
-                    return createFuturesForResultCode(RESULT_SUCCESS);
-                }
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                futures.add(setNextMediaItemInternal(updatedCurNextItem.second));
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Removes the media item from the playlist
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with {@code item} removed.
-     *
-     * @param index the index of the item you want to remove in the playlist
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(@IntRange(from = 0) final int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                int removedItemShuffleIdx;
-                MediaItem curItem;
-                MediaItem nextItem;
-                Pair<MediaItem, MediaItem> updatedCurNextItem = null;
-                synchronized (mPlaylistLock) {
-                    if (index >= mPlaylist.size()) {
-                        return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                    }
-                    MediaItem item = mPlaylist.remove(index);
-                    removedItemShuffleIdx = mShuffledList.indexOf(item);
-                    mShuffledList.remove(removedItemShuffleIdx);
-                    if (removedItemShuffleIdx < mCurrentShuffleIdx) {
-                        mCurrentShuffleIdx--;
-                    }
-
-                    updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                final List<MediaItem> playlist = getPlaylist();
-                final MediaMetadata metadata = getPlaylistMetadata();
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(
-                            SessionPlayer.PlayerCallback callback) {
-                        callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
-                    }
-                });
-
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                if (curItem == null) {
-                    resetInternal();
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(SessionPlayer.PlayerCallback callback) {
-                            callback.onPlayerStateChanged(MediaPlayer.this, PLAYER_STATE_IDLE);
-                        }
-                    });
-                    futures.add(createFutureForResultCode(RESULT_SUCCESS));
-                } else if (updatedCurNextItem != null) {
-                    if (updatedCurNextItem.first != null) {
-                        futures.addAll(setMediaItemsInternal(curItem, nextItem));
-                    } else if (updatedCurNextItem.second != null) {
-                        futures.add(setNextMediaItemInternal(nextItem));
-                    }
-                } else {
-                    futures.add(createFutureForResultCode(RESULT_SUCCESS));
-                }
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Replaces the media item at index in the playlist. This can be also used to update metadata of
-     * an item.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with {@code item} set.
-     *
-     * @param index the index of the item to replace in the playlist
-     * @param item the new item
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(
-            final int index, @NonNull final MediaItem item) {
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        if (item instanceof FileMediaItem) {
-            if (((FileMediaItem) item).isClosed()) {
-                throw new IllegalArgumentException("File descriptor is closed. " + item);
-            }
-        }
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                Pair<MediaItem, MediaItem> updatedCurNextItem = null;
-                synchronized (mPlaylistLock) {
-                    if (index >= mPlaylist.size() || mPlaylist.contains(item)) {
-                        return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE, item);
-                    }
-
-                    int shuffleIdx = mShuffledList.indexOf(mPlaylist.get(index));
-                    mShuffledList.set(shuffleIdx, item);
-                    mPlaylist.set(index, item);
-                    updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                // TODO: Should we notify current media item changed if it is replaced?
-                final List<MediaItem> playlist = getPlaylist();
-                final MediaMetadata metadata = getPlaylistMetadata();
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(
-                            SessionPlayer.PlayerCallback callback) {
-                        callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
-                    }
-                });
-
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                if (updatedCurNextItem != null) {
-                    if (updatedCurNextItem.first != null) {
-                        futures.addAll(setMediaItemsInternal(curItem, nextItem));
-                    } else if (updatedCurNextItem.second != null) {
-                        futures.add(setNextMediaItemInternal(nextItem));
-                    }
-                } else {
-                    futures.add(createFutureForResultCode(RESULT_SUCCESS));
-                }
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Moves the media item at {@code fromIdx} to {@code toIdx} in the playlist.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with {@code item} set.
-     *
-     * @param fromIndex the media item's initial index in the playlist
-     * @param toIndex the media item's target index in the playlist
-     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> movePlaylistItem(final int fromIndex, final int toIndex) {
-        if (fromIndex < 0 || toIndex < 0) {
-            throw new IllegalArgumentException("indices shouldn't be negative");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                Pair<MediaItem, MediaItem> updatedCurNextItem = null;
-                synchronized (mPlaylistLock) {
-                    if (fromIndex >= mPlaylist.size() || toIndex >= mPlaylist.size()) {
-                        return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                    }
-
-                    MediaItem item = mPlaylist.remove(fromIndex);
-                    mPlaylist.add(toIndex, item);
-                    if (mShuffleMode == SessionPlayer.SHUFFLE_MODE_NONE) {
-                        mShuffledList.remove(fromIndex);
-                        mShuffledList.add(toIndex, item);
-                        if (item == mCurPlaylistItem) {
-                            mCurrentShuffleIdx = toIndex;
-                        }
-                    }
-                    updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-
-                final List<MediaItem> playlist = getPlaylist();
-                final MediaMetadata metadata = getPlaylistMetadata();
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(
-                            SessionPlayer.PlayerCallback callback) {
-                        callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
-                    }
-                });
-
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                if (updatedCurNextItem != null) {
-                    if (updatedCurNextItem.first != null) {
-                        futures.addAll(setMediaItemsInternal(curItem, nextItem));
-                    } else if (updatedCurNextItem.second != null) {
-                        futures.add(setNextMediaItemInternal(nextItem));
-                    }
-                } else {
-                    futures.add(createFutureForResultCode(RESULT_SUCCESS));
-                }
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Skips to the previous item in the playlist.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                synchronized (mPlaylistLock) {
-                    if (mCurrentShuffleIdx < 0) {
-                        return createFuturesForResultCode(RESULT_ERROR_INVALID_STATE);
-                    }
-                    int prevShuffleIdx = mCurrentShuffleIdx - 1;
-                    if (prevShuffleIdx < 0) {
-                        if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
-                            prevShuffleIdx = mShuffledList.size() - 1;
-                        } else {
-                            return createFuturesForResultCode(RESULT_ERROR_INVALID_STATE);
-                        }
-                    }
-                    mCurrentShuffleIdx = prevShuffleIdx;
-                    updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                return setMediaItemsInternal(curItem, nextItem);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Skips to the next item in the playlist.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                synchronized (mPlaylistLock) {
-                    if (mCurrentShuffleIdx < 0) {
-                        return createFuturesForResultCode(RESULT_ERROR_INVALID_STATE);
-                    }
-                    int nextShuffleIdx = mCurrentShuffleIdx + 1;
-                    if (nextShuffleIdx >= mShuffledList.size()) {
-                        if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
-                            nextShuffleIdx = 0;
-                        } else {
-                            return createFuturesForResultCode(RESULT_ERROR_INVALID_STATE);
-                        }
-                    }
-                    mCurrentShuffleIdx = nextShuffleIdx;
-                    updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                if (curItem != null) {
-                    return setMediaItemsInternal(curItem, nextItem);
-                }
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                futures.add(skipToNextInternal());
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Skips to the item in the playlist at the index.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @param index the index of the item you want to play in the playlist
-     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(@IntRange(from = 0) final int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                MediaItem curItem;
-                MediaItem nextItem;
-                synchronized (mPlaylistLock) {
-                    if (index >= mPlaylist.size()) {
-                        return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                    }
-                    mCurrentShuffleIdx = mShuffledList.indexOf(mPlaylist.get(index));
-                    updateAndGetCurrentNextItemIfNeededLocked();
-                    curItem = mCurPlaylistItem;
-                    nextItem = mNextPlaylistItem;
-                }
-                return setMediaItemsInternal(curItem, nextItem);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Updates the playlist metadata while keeping the playlist as-is.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} swuld be returned with the current media
-     * item when the command completed.
-     *
-     * @param metadata metadata of the playlist
-     * @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(
-            @Nullable final MediaMetadata metadata) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                synchronized (mPlaylistLock) {
-                    mPlaylistMetadata = metadata;
-                }
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(
-                            SessionPlayer.PlayerCallback callback) {
-                        callback.onPlaylistMetadataChanged(MediaPlayer.this, metadata);
-                    }
-                });
-                return createFuturesForResultCode(RESULT_SUCCESS);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets the repeat mode.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @param repeatMode repeat mode
-     * @see #REPEAT_MODE_NONE
-     * @see #REPEAT_MODE_ONE
-     * @see #REPEAT_MODE_ALL
-     * @see #REPEAT_MODE_GROUP
-     * @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(final int repeatMode) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                if (repeatMode < SessionPlayer.REPEAT_MODE_NONE
-                        || repeatMode > SessionPlayer.REPEAT_MODE_GROUP) {
-                    return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                }
-
-                boolean changed;
-                synchronized (mPlaylistLock) {
-                    changed = mRepeatMode != repeatMode;
-                    mRepeatMode = repeatMode;
-                }
-                if (changed) {
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(
-                                SessionPlayer.PlayerCallback callback) {
-                            callback.onRepeatModeChanged(MediaPlayer.this, repeatMode);
-                        }
-                    });
-                }
-                return createFuturesForResultCode(RESULT_SUCCESS);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets the shuffle mode.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} would be returned with the current media
-     * item when the command completed.
-     *
-     * @param shuffleMode the shuffle mode
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #SHUFFLE_MODE_NONE
-     * @see #SHUFFLE_MODE_ALL
-     * @see #SHUFFLE_MODE_GROUP
-     * @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(final int shuffleMode) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                if (shuffleMode < SessionPlayer.SHUFFLE_MODE_NONE
-                        || shuffleMode > SessionPlayer.SHUFFLE_MODE_GROUP) {
-                    return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
-                }
-
-                boolean changed;
-                synchronized (mPlaylistLock) {
-                    changed = mShuffleMode != shuffleMode;
-                    mShuffleMode = shuffleMode;
-                    applyShuffleModeLocked();
-                }
-                if (changed) {
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(
-                                SessionPlayer.PlayerCallback callback) {
-                            callback.onShuffleModeChanged(MediaPlayer.this, shuffleMode);
-                        }
-                    });
-                }
-                return createFuturesForResultCode(RESULT_SUCCESS);
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    @Override
-    @Nullable
-    public List<MediaItem> getPlaylist() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            return mPlaylist.isEmpty() ? null : new ArrayList<>(mPlaylist.getCollection());
-        }
-    }
-
-    @Override
-    @Nullable
-    public MediaMetadata getPlaylistMetadata() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            return mPlaylistMetadata;
-        }
-    }
-
-    @Override
-    public int getRepeatMode() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return REPEAT_MODE_NONE;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            return mRepeatMode;
-        }
-    }
-
-    @Override
-    public int getShuffleMode() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return SHUFFLE_MODE_NONE;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            return mShuffleMode;
-        }
-    }
-
-    @Override
-    @Nullable
-    public MediaItem getCurrentMediaItem() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        return mPlayer.getCurrentMediaItem();
-    }
-
-    /**
-     * Gets the index of current media item in playlist. This value would be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of current media item. Can be {@link #INVALID_ITEM_INDEX} when current
-     *         media item is null or not in the playlist, and when the playlist hasn't been set.
-     */
-    @Override
-    public int getCurrentMediaItemIndex() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return END_OF_PLAYLIST;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            if (mCurrentShuffleIdx < 0) {
-                return END_OF_PLAYLIST;
-            }
-            return mPlaylist.indexOf(mShuffledList.get(mCurrentShuffleIdx));
-        }
-    }
-
-    /**
-     * Gets the previous item index in the playlist. This value would be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of previous media item. Can be {@link #INVALID_ITEM_INDEX} only when
-     *         previous media item does not exist or playlist hasn't been set.
-     */
-    @Override
-    public int getPreviousMediaItemIndex() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return END_OF_PLAYLIST;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            if (mCurrentShuffleIdx < 0) {
-                return END_OF_PLAYLIST;
-            }
-            int prevShuffleIdx = mCurrentShuffleIdx - 1;
-            if (prevShuffleIdx < 0) {
-                if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
-                    return mPlaylist.indexOf(mShuffledList.get(mShuffledList.size() - 1));
-                } else {
-                    return END_OF_PLAYLIST;
-                }
-            }
-            return mPlaylist.indexOf(mShuffledList.get(prevShuffleIdx));
-        }
-    }
-
-    /**
-     * Gets the next item index in the playlist. This value would be updated when
-     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
-     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
-     *
-     * @return the index of next media item. Can be {@link #INVALID_ITEM_INDEX} only when next media
-     *         item does not exist or playlist hasn't been set.
-     */
-    @Override
-    public int getNextMediaItemIndex() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return END_OF_PLAYLIST;
-            }
-        }
-        synchronized (mPlaylistLock) {
-            if (mCurrentShuffleIdx < 0) {
-                return END_OF_PLAYLIST;
-            }
-            int nextShuffleIdx = mCurrentShuffleIdx + 1;
-            if (nextShuffleIdx >= mShuffledList.size()) {
-                if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
-                    return mPlaylist.indexOf(mShuffledList.get(0));
-                } else {
-                    return END_OF_PLAYLIST;
-                }
-            }
-            return mPlaylist.indexOf(mShuffledList.get(nextShuffleIdx));
-        }
-    }
-
-    /**
-     * Closes the player and relinquish underlying resources.
-     */
-    @Override
-    public void close() {
-        super.close();
-
-        synchronized (mStateLock) {
-            if (!mClosed) {
-                mClosed = true;
-                reset();
-                mAudioFocusHandler.close();
-                mPlayer.close();
-                mExecutor.shutdown();
-            }
-        }
-    }
-
-    /**
-     * Resets {@link MediaPlayer} to its uninitialized state if not closed. After calling
-     * this method, you will have to initialize it again by setting the media item and
-     * calling {@link #prepare()}.
-     * <p> Note that if the player is closed, there is no way to reuse the instance.
-     */
-    public void reset() {
-        // Cancel the pending commands.
-        synchronized (mPendingCommands) {
-            for (PendingCommand c : mPendingCommands) {
-                c.mFuture.cancel(true);
-            }
-            mPendingCommands.clear();
-        }
-        // Cancel the pending futures.
-        synchronized (mPendingFutures) {
-            for (PendingFuture<? extends PlayerResult> f : mPendingFutures) {
-                if (f.mExecuteCalled && !f.isDone() && !f.isCancelled()) {
-                    f.cancel(true);
-                }
-            }
-            mPendingFutures.clear();
-        }
-        resetInternal();
-    }
-
-    /**
-     * Sets the {@link Surface} to be used as the sink for the video portion of
-     * the media.
-     * <p>
-     * A null surface will result in only the audio track being played.
-     * <p>
-     * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
-     * returned from {@link SurfaceTexture#getTimestamp()} will have an
-     * unspecified zero point.  These timestamps cannot be directly compared
-     * between different media sources, different instances of the same media
-     * source, or multiple runs of the same program.  The timestamp is normally
-     * monotonically increasing and is unaffected by time-of-day adjustments,
-     * but it is reset when the position is set.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param surface The {@link Surface} to be used for the video portion of
-     * the media.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     */
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> setSurface(@Nullable final Surface surface) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setSurface(surface);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_SURFACE, future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Sets the volume of the audio of the media to play, expressed as a linear multiplier
-     * on the audio samples.
-     * <p>
-     * Note that this volume is specific to the player, and is separate from stream volume
-     * used across the platform.
-     * <p>
-     * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
-     * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
-     * <p>
-     * The default player volume is 1.0f.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlayerVolume(
-            @FloatRange(from = 0, to = 1) final float volume) {
-        if (volume < 0 || volume > 1) {
-            throw new IllegalArgumentException("volume should be between 0.0 and 1.0");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                futures.add(setPlayerVolumeInternal(volume));
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * @return the current volume of this player to this player. Note that it does not take into
-     * account the associated stream volume.
-     */
-    public float getPlayerVolume() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return 1.0f;
-            }
-        }
-        return mPlayer.getPlayerVolume();
-    }
-
-    /**
-     * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
-     */
-    public float getMaxPlayerVolume() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return 1.0f;
-            }
-        }
-        return mPlayer.getMaxPlayerVolume();
-    }
-
-    /**
-     * Returns the size of the video.
-     *
-     * @return the size of the video. The width and height of size could be 0 if there is no video
-     * or the size has not been determined yet.
-     * The {@link PlayerCallback} can be registered via {@link #registerPlayerCallback} to
-     * receive a notification {@link PlayerCallback#onVideoSizeChanged} when the size
-     * is available.
-     */
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return new VideoSize(0, 0);
-            }
-        }
-        return new VideoSize(mPlayer.getVideoWidth(), mPlayer.getVideoHeight());
-    }
-
-    /**
-     * @return a {@link PersistableBundle} containing the set of attributes and values
-     * available for the media being handled by this player instance.
-     * The attributes are described in {@link MetricsConstants}.
-     *
-     * Additional vendor-specific fields may also be present in the return value.
-     */
-    @RestrictTo(LIBRARY)
-    @RequiresApi(21)
-    public PersistableBundle getMetrics() {
-        return mPlayer.getMetrics();
-    }
-
-    /**
-     * Sets playback params using {@link PlaybackParams}.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param params the playback params.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackParams(@NonNull final PlaybackParams params) {
-        if (params == null) {
-            throw new NullPointerException("params shouldn't be null");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setPlaybackParams(params);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Gets the playback params, containing the current playback rate.
-     *
-     * @return the playback params.
-     */
-    @NonNull
-    public PlaybackParams getPlaybackParams() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return DEFAULT_PLAYBACK_PARAMS;
-            }
-        }
-        return mPlayer.getPlaybackParams();
-    }
-
-    /**
-     * Moves the media to specified time position by considering the given mode.
-     * <p>
-     * There is at most one active seekTo processed at any time. If there is a to-be-completed
-     * seekTo, new seekTo requests will be queued in such a way that only the last request
-     * is kept. When current seekTo is completed, the queued request will be processed if
-     * that request is different from just-finished seekTo operation, i.e., the requested
-     * position or mode is different.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param position the offset in milliseconds from the start to seek to.
-     * When seeking to the given time position, there is no guarantee that the media item
-     * has a frame located at the position. When this happens, a frame nearby will be rendered.
-     * The value should be in the range of start and end positions defined in {@link MediaItem}.
-     * @param mode the mode indicating where exactly to seek to.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(final long position, @SeekMode final int mode) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture =
-                new PendingFuture<PlayerResult>(mExecutor, true) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                int mp2SeekMode = sSeekModeMap.containsKey(mode)
-                        ? sSeekModeMap.get(mode) : MediaPlayer2.SEEK_NEXT_SYNC;
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.seekTo(position, mp2SeekMode);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Gets current playback position as a {@link MediaTimestamp}.
-     * <p>
-     * The MediaTimestamp represents how the media time correlates to the system time in
-     * a linear fashion using an anchor and a clock rate. During regular playback, the media
-     * time moves fairly constantly (though the anchor frame may be rebased to a current
-     * system time, the linear correlation stays steady). Therefore, this method does not
-     * need to be called often.
-     * <p>
-     * To help users get current playback position, this method always anchors the timestamp
-     * to the current {@link System#nanoTime system time}, so
-     * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
-     *
-     * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
-     *         is available, e.g. because the media player has not been initialized.
-     *
-     * @see MediaTimestamp
-     */
-    @Nullable
-    public MediaTimestamp getTimestamp() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        return mPlayer.getTimestamp();
-    }
-
-    /**
-     * Sets the audio session ID.
-     *
-     * @param sessionId the audio session ID.
-     * The audio session ID is a system wide unique identifier for the audio stream played by
-     * this MediaPlayer2 instance.
-     * The primary use of the audio session ID  is to associate audio effects to a particular
-     * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
-     * this effect will be applied only to the audio content of media players within the same
-     * audio session and not to the output mix.
-     * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
-     * However, it is possible to force this player to be part of an already existing audio session
-     * by calling this method.
-     * <p>This method must be called before {@link #setMediaItem} and {@link #setPlaylist} methods.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @see AudioManager#generateAudioSessionId
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioSessionId(final int sessionId) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setAudioSessionId(sessionId);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Returns the audio session ID.
-     *
-     * @return the audio session ID. See {@link #setAudioSessionId(int)}. Note that the audio
-     *     session ID is 0 if a problem occurred when the MediaPlayer was constructed or it is
-     *     closed.
-     */
-    public int getAudioSessionId() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return 0;
-            }
-        }
-        return mPlayer.getAudioSessionId();
-    }
-
-    /**
-     * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
-     * effect which can be applied on any sound source that directs a certain amount of its
-     * energy to this effect. This amount is defined by setAuxEffectSendLevel().
-     * See {@link #setAuxEffectSendLevel(float)}.
-     * <p>After creating an auxiliary effect (e.g.
-     * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
-     * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
-     * to attach the player to the effect.
-     * <p>To detach the effect from the player, call this method with a null effect id.
-     * <p>This method must be called before {@link #setMediaItem} and {@link #setPlaylist} methods.
-     * @param effectId system wide unique id of the effect to attach
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> attachAuxEffect(final int effectId) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.attachAuxEffect(effectId);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-
-    /**
-     * Sets the send level of the player to the attached auxiliary effect.
-     * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
-     * <p>By default the send level is 0, so even if an effect is attached to the player
-     * this method must be called for the effect to be applied.
-     * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
-     * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, so an
-     * appropriate conversion from linear UI input x to level is: x == 0 -&gt; level = 0, 0 &lt; x
-     * &lt;= R -&gt; level = 10^(72*(x-R)/20/R)
-     * <p>
-     * On success, a {@link SessionPlayer.PlayerResult} is returned with
-     * the current media item when the command completed.
-     *
-     * @param level send level scalar
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command
-     * completed.
-     */
-    @NonNull
-    public ListenableFuture<PlayerResult> setAuxEffectSendLevel(
-            @FloatRange(from = 0, to = 1) final float level) {
-        if (level < 0 || level > 1) {
-            // Returns ListenableFuture instead of throwing exception, not to newly throw an
-            // exception in existing code.
-            return createFutureForResultCode(RESULT_ERROR_BAD_VALUE);
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.setAuxEffectSendLevel(level);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL,
-                            future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Gets the full list of selected and unselected tracks that the media contains. The order of
-     * the list is irrelevant as different players expose tracks in different ways, but the tracks
-     * will generally be ordered based on track type.
-     *
-     * @return list of tracks. The total number of tracks is the size of the list. If empty,
-     *         an empty list would be returned.
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     */
-    @Override
-    @NonNull
-    public List<SessionPlayer.TrackInfo> getTracks() {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return Collections.emptyList();
-            }
-        }
-        return mPlayer.getTracks();
-    }
-
-
-    /**
-     * @deprecated Use {@link #getTracks()} instead.
-     */
-    @Deprecated
-    @NonNull
-    public List<TrackInfo> getTrackInfo() {
-        List<SessionPlayer.TrackInfo> infoInternals = getTracks();
-        List<TrackInfo> infos = new ArrayList<>();
-        for (SessionPlayer.TrackInfo infoInternal : infoInternals) {
-            infos.add(new TrackInfo(infoInternal));
-        }
-        return infos;
-    }
-
-    /**
-     * Returns the selected track for the given track type.
-     * The return value is an element in the list returned by {@link #getTracks()}.
-     *
-     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE},
-     * or {@link TrackInfo#MEDIA_TRACK_TYPE_METADATA}.
-     * @return metadata corresponding to the  track currently selected for
-     * playback; {@code null} is returned when there is no selected track for {@code trackType} or
-     * when {@code trackType} is not one of audio or video.
-     * @throws IllegalStateException if called after {@link #close()}
-     *
-     * @see #getTracks()
-     */
-    @Override
-    @Nullable
-    public TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return null;
-            }
-        }
-        SessionPlayer.TrackInfo infoInternal = mPlayer.getSelectedTrack(trackType);
-        return infoInternal == null ? null : new TrackInfo(infoInternal);
-    }
-
-    /**
-     * Selects the {@link TrackInfo} for the current media item.
-     * <p>
-     * If the player is in invalid state,
-     * {@link SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE} will be reported with
-     * {@link SessionPlayer.PlayerResult}.
-     * If a player is in <em>Playing</em> state, the selected track is presented immediately.
-     * If a player is not in Playing state, it just marks the track to be played.
-     * <p>
-     * In any valid state, if it is called multiple times on the same type of track (ie. Video,
-     * Audio), the most recent one will be chosen.
-     * <p>
-     * The first audio and video tracks are selected by default if available, even though
-     * this method is not called.
-     * <p>
-     * Currently, tracks that return true for {@link TrackInfo#isSelectable()} can be selected via
-     * this method.
-     *
-     * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
-     * object can be obtained from {@link #getTracks()}.
-     *
-     * @see #getTracks
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link SessionPlayer.PlayerResult} will be delivered when the command completed.
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> selectTrack(
-            @NonNull final SessionPlayer.TrackInfo trackInfo) {
-        if (trackInfo == null) {
-            throw new NullPointerException("trackInfo shouldn't be null");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.selectTrack(trackInfo.getId());
-                    addPendingCommandWithTrackInfoLocked(MediaPlayer2.CALL_COMPLETED_SELECT_TRACK,
-                            future, trackInfo, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * @deprecated Use {@link #selectTrack(SessionPlayer.TrackInfo)} instead.
-     */
-    @Deprecated
-    @NonNull
-    public ListenableFuture<PlayerResult> selectTrack(@NonNull final TrackInfo trackInfo) {
-        return selectTrack((SessionPlayer.TrackInfo) trackInfo);
-    }
-
-    /**
-     * Deselects the {@link TrackInfo} for the current media item.
-     * <p>
-     * The track must be a subtitle track, and no audio or video tracks can be deselected.
-     * <p>
-     * Note: {@link #getSelectedTrack(int)} returns the currently selected track per track type that
-     * can be deselected, but the list may be invalidated when
-     * {@link PlayerCallback#onTracksChanged(SessionPlayer, List)} is called.
-     *
-     * @param trackInfo the track to be selected
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     * @see PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)
-     */
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> deselectTrack(
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        if (trackInfo == null) {
-            throw new NullPointerException("trackInfo shouldn't be null");
-        }
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return createFutureForClosed();
-            }
-        }
-        PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<PlayerResult>> onExecute() {
-                ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-                ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.deselectTrack(trackInfo.getId());
-                    addPendingCommandWithTrackInfoLocked(MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK,
-                            future, trackInfo, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Register {@link PlayerCallback} to listen changes.
-     *
-     * @param executor a callback Executor
-     * @param callback a PlayerCallback
-     * @throws IllegalArgumentException if executor or callback is {@code null}.
-     */
-    public void registerPlayerCallback(
-            @NonNull /*@CallbackExecutor*/ Executor executor,
-            @NonNull PlayerCallback callback) {
-        super.registerPlayerCallback(executor, callback);
-    }
-
-    /**
-     * Unregister the previously registered {@link PlayerCallback}.
-     *
-     * @param callback the callback to be removed
-     * @throws IllegalArgumentException if the callback is {@code null}.
-     */
-    public void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
-        super.unregisterPlayerCallback(callback);
-    }
-
-    /**
-     * Retrieves the DRM Info associated with the current media item.
-     *
-     * @throws IllegalStateException if called before being prepared
-     */
-    @Nullable
-    @RestrictTo(LIBRARY)
-    public DrmInfo getDrmInfo() {
-        MediaPlayer2.DrmInfo info = mPlayer.getDrmInfo();
-        return info == null ? null : new DrmInfo(info);
-    }
-
-    /**
-     * Prepares the DRM for the current media item.
-     * <p>
-     * If {@link OnDrmConfigHelper} is registered, it will be called during
-     * preparation to allow configuration of the DRM properties before opening the
-     * DRM session. Note that the callback is called synchronously in the thread that called
-     * {@link #prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
-     * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
-     * <p>
-     * If the device has not been provisioned before, this call also provisions the device
-     * which involves accessing the provisioning server and can take a variable time to
-     * complete depending on the network connectivity.
-     * prepareDrm() runs in non-blocking mode by launching the provisioning in the background and
-     * returning. The application should check the {@link DrmResult#getResultCode()} returned with
-     * {@link ListenableFuture} to proceed.
-     * <p>
-     *
-     * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
-     * from the source through {#link getDrmInfo} or registering
-     * {@link PlayerCallback#onDrmInfo}.
-     * @return a {@link ListenableFuture} which represents the pending completion of the command.
-     * {@link DrmResult} will be delivered when the command completed.
-     */
-    @RestrictTo(LIBRARY)
-    // This is an asynchronous call.
-    @NonNull
-    public ListenableFuture<DrmResult> prepareDrm(@NonNull final UUID uuid) {
-        if (uuid == null) {
-            throw new NullPointerException("uuid shouldn't be null");
-        }
-        PendingFuture<DrmResult> pendingFuture = new PendingFuture<DrmResult>(mExecutor) {
-            @Override
-            List<ResolvableFuture<DrmResult>> onExecute() {
-                ArrayList<ResolvableFuture<DrmResult>> futures = new ArrayList<>();
-                ResolvableFuture<DrmResult> future = ResolvableFuture.create();
-                synchronized (mPendingCommands) {
-                    Object token = mPlayer.prepareDrm(uuid);
-                    addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE_DRM, future, token);
-                }
-                futures.add(future);
-                return futures;
-            }
-        };
-
-        addPendingFuture(pendingFuture);
-        return pendingFuture;
-    }
-
-    /**
-     * Releases the DRM session
-     * <p>
-     * The player has to have an active DRM session and be in stopped, or prepared
-     * state before this call is made.
-     * A {@code reset()} call will release the DRM session implicitly.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session to release
-     */
-    @RestrictTo(LIBRARY)
-    public void releaseDrm() throws NoDrmSchemeException {
-        try {
-            mPlayer.releaseDrm();
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * A key request/response exchange occurs between the app and a license server
-     * to obtain or release keys used to decrypt encrypted content.
-     * <p>
-     * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
-     * delivered to the license server.  The opaque key request byte array is returned
-     * in KeyRequest.data.  The recommended URL to deliver the key request to is
-     * returned in KeyRequest.defaultUrl.
-     * <p>
-     * After the app has received the key request response from the server,
-     * it should deliver to the response to the DRM engine plugin using the method
-     * {@link #provideDrmKeyResponse}.
-     *
-     * @param keySetId is the key-set identifier of the offline keys being released when keyType is
-     * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
-     * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
-     *
-     * @param initData is the container-specific initialization data when the keyType is
-     * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
-     * interpreted based on the mime type provided in the mimeType parameter.  It could
-     * contain, for example, the content ID, key ID or other data obtained from the content
-     * metadata that is required in generating the key request.
-     * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
-     *
-     * @param mimeType identifies the mime type of the content
-     *
-     * @param keyType specifies the type of the request. The request may be to acquire
-     * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
-     * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
-     * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
-     *
-     * @param optionalParameters are included in the key request message to
-     * allow a client application to provide additional message parameters to the server.
-     * This may be {@code null} if no additional parameters are to be sent.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     */
-    @RestrictTo(LIBRARY)
-    @NonNull
-    public MediaDrm.KeyRequest getDrmKeyRequest(
-            @Nullable byte[] keySetId, @Nullable byte[] initData,
-            @Nullable String mimeType, int keyType,
-            @Nullable Map<String, String> optionalParameters)
-            throws NoDrmSchemeException {
-        try {
-            return mPlayer.getDrmKeyRequest(
-                    keySetId, initData, mimeType, keyType, optionalParameters);
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * A key response is received from the license server by the app, then it is
-     * provided to the DRM engine plugin using provideDrmKeyResponse. When the
-     * response is for an offline key request, a key-set identifier is returned that
-     * can be used to later restore the keys to a new session with the method
-     * {@link #restoreDrmKeys}.
-     * <p>
-     * When the response is for a streaming or release request, null is returned.
-     *
-     * @param keySetId When the response is for a release request, keySetId identifies
-     * the saved key associated with the release request (i.e., the same keySetId
-     * passed to the earlier {@link #getDrmKeyRequest} call. It MUST be null when the
-     * response is for either streaming or offline key requests.
-     *
-     * @param response the byte array response from the server
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @throws DeniedByServerException if the response indicates that the
-     * server rejected the request
-     */
-    @Nullable
-    @RestrictTo(LIBRARY)
-    public byte[] provideDrmKeyResponse(
-            @Nullable byte[] keySetId, @NonNull byte[] response)
-            throws NoDrmSchemeException, DeniedByServerException {
-        try {
-            return mPlayer.provideDrmKeyResponse(keySetId, response);
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * Restore persisted offline keys into a new session.  keySetId identifies the
-     * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
-     *
-     * @param keySetId identifies the saved key set to restore
-     */
-    @RestrictTo(LIBRARY)
-    public void restoreDrmKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException {
-        if (keySetId == null) {
-            throw new NullPointerException("keySetId shouldn't be null");
-        }
-        try {
-            mPlayer.restoreDrmKeys(keySetId);
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * Read a DRM engine plugin String property value, given the property name string.
-     * <p>
-     * @param propertyName the property name
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    @RestrictTo(LIBRARY)
-    @NonNull
-    public String getDrmPropertyString(@NonNull String propertyName) throws NoDrmSchemeException {
-        if (propertyName == null) {
-            throw new NullPointerException("propertyName shouldn't be null");
-        }
-        try {
-            return mPlayer.getDrmPropertyString(propertyName);
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * Set a DRM engine plugin String property value.
-     * <p>
-     * @param propertyName the property name
-     * @param value the property value
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    @RestrictTo(LIBRARY)
-    public void setDrmPropertyString(@NonNull String propertyName, @NonNull String value)
-            throws NoDrmSchemeException {
-        if (propertyName == null) {
-            throw new NullPointerException("propertyName shouldn't be null");
-        }
-        if (value == null) {
-            throw new NullPointerException("value shouldn't be null");
-        }
-        try {
-            mPlayer.setDrmPropertyString(propertyName, value);
-        } catch (MediaPlayer2.NoDrmSchemeException e) {
-            throw new NoDrmSchemeException(e.getMessage());
-        }
-    }
-
-    /**
-     * Register a callback to be invoked for configuration of the DRM object before
-     * the session is created.
-     * <p>
-     * The callback will be invoked synchronously during the execution
-     * of {@link #prepareDrm(UUID uuid)}.
-     *
-     * @param listener the callback that will be run
-     */
-    @RestrictTo(LIBRARY)
-    public void setOnDrmConfigHelper(@Nullable final OnDrmConfigHelper listener) {
-        mPlayer.setOnDrmConfigHelper(listener == null ? null :
-                new MediaPlayer2.OnDrmConfigHelper() {
-                    @Override
-                    public void onDrmConfig(MediaPlayer2 mp, MediaItem item) {
-                        listener.onDrmConfig(MediaPlayer.this, item);
-                    }
-                });
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void setState(@PlayerState final int state) {
-        boolean needToNotify = false;
-        synchronized (mStateLock) {
-            if (mState != state) {
-                mState = state;
-                needToNotify = true;
-            }
-        }
-        if (needToNotify) {
-            notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(SessionPlayer.PlayerCallback callback) {
-                    callback.onPlayerStateChanged(MediaPlayer.this, state);
-                }
-            });
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void setBufferingState(final MediaItem item, @BuffState final int state) {
-        Integer previousState;
-        synchronized (mStateLock) {
-            previousState = mMediaItemToBuffState.put(item, state);
-        }
-        if (previousState == null || previousState.intValue() != state) {
-            notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(SessionPlayer.PlayerCallback callback) {
-                    callback.onBufferingStateChanged(MediaPlayer.this, item, state);
-                }
-            });
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void notifySessionPlayerCallback(final SessionPlayerCallbackNotifier notifier) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return;
-            }
-        }
-        List<Pair<SessionPlayer.PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<SessionPlayer.PlayerCallback, Executor> pair : callbacks) {
-            final SessionPlayer.PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    notifier.callCallback(callback);
-                }
-            });
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void notifyMediaPlayerCallback(final MediaPlayerCallbackNotifier notifier) {
-        synchronized (mStateLock) {
-            if (mClosed) {
-                return;
-            }
-        }
-        List<Pair<SessionPlayer.PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<SessionPlayer.PlayerCallback, Executor> pair : callbacks) {
-            if (pair.first instanceof PlayerCallback) {
-                final PlayerCallback callback = (PlayerCallback) pair.first;
-                pair.second.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifier.callCallback(callback);
-                    }
-                });
-            }
-        }
-    }
-
-    private interface SessionPlayerCallbackNotifier {
-        void callCallback(SessionPlayer.PlayerCallback callback);
-    }
-
-    private interface MediaPlayerCallbackNotifier {
-        void callCallback(PlayerCallback callback);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<ResolvableFuture<PlayerResult>> setMediaItemsInternal(
-            @NonNull MediaItem curItem, @Nullable MediaItem nextItem) {
-        if (curItem == null) {
-            throw new NullPointerException("curItem shouldn't be null");
-        }
-        boolean setMediaItemCalled;
-        synchronized (mPlaylistLock) {
-            setMediaItemCalled = mSetMediaItemCalled;
-        }
-
-        ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-        if (setMediaItemCalled) {
-            futures.add(setNextMediaItemInternal(curItem));
-            futures.add(skipToNextInternal());
-        } else {
-            futures.add(setMediaItemInternal(curItem));
-        }
-
-        if (nextItem != null) {
-            futures.add(setNextMediaItemInternal(nextItem));
-        }
-        return futures;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void resetInternal() {
-        synchronized (mStateLock) {
-            mState = PLAYER_STATE_IDLE;
-            mMediaItemToBuffState.clear();
-        }
-        synchronized (mPlaylistLock) {
-            mPlaylist.clear();
-            mShuffledList.clear();
-            mCurPlaylistItem = null;
-            mNextPlaylistItem = null;
-            mCurrentShuffleIdx = END_OF_PLAYLIST;
-            mSetMediaItemCalled = false;
-        }
-        mAudioFocusHandler.onReset();
-        mPlayer.reset();
-    }
-
-    private ResolvableFuture<PlayerResult> setMediaItemInternal(MediaItem item) {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        synchronized (mPendingCommands) {
-            Object token = mPlayer.setMediaItem(item);
-            addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, future, token);
-        }
-        synchronized (mPlaylistLock) {
-            mSetMediaItemCalled = true;
-        }
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> setNextMediaItemInternal(MediaItem item) {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        synchronized (mPendingCommands) {
-            Object token = mPlayer.setNextMediaItem(item);
-            addPendingCommandLocked(
-                    MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE, future, token);
-        }
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> skipToNextInternal() {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        synchronized (mPendingCommands) {
-            Object token = mPlayer.skipToNext();
-            addPendingCommandLocked(
-                    MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT, future, token);
-        }
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> setPlayerVolumeInternal(float volume) {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        synchronized (mPendingCommands) {
-            Object token = mPlayer.setPlayerVolume(volume);
-            addPendingCommandLocked(
-                    MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, future, token);
-        }
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> createFutureForClosed() {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        future.set(new PlayerResult(RESULT_ERROR_INVALID_STATE, null));
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> createFutureForResultCode(int resultCode) {
-        return createFutureForResultCode(resultCode, null);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ResolvableFuture<PlayerResult> createFutureForResultCode(int resultCode, MediaItem item) {
-        ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
-        future.set(new PlayerResult(resultCode,
-                item == null ? mPlayer.getCurrentMediaItem() : item));
-        return future;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<ResolvableFuture<PlayerResult>> createFuturesForResultCode(int resultCode) {
-        return createFuturesForResultCode(resultCode, null);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<ResolvableFuture<PlayerResult>> createFuturesForResultCode(int resultCode,
-            MediaItem item) {
-        ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
-        futures.add(createFutureForResultCode(resultCode, item));
-        return futures;
-    }
-
-    @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* synthetic access */
-    void applyShuffleModeLocked() {
-        mShuffledList.clear();
-        mShuffledList.addAll(mPlaylist.getCollection());
-        if (mShuffleMode == SessionPlayer.SHUFFLE_MODE_ALL
-                || mShuffleMode == SessionPlayer.SHUFFLE_MODE_GROUP) {
-            Collections.shuffle(mShuffledList);
-        }
-    }
-
-    /**
-     * Update mCurPlaylistItem and mNextPlaylistItem based on mCurrentShuffleIdx value.
-     *
-     * @return A pair contains the changed current item and next item. If current item or next item
-     * is not changed, Pair.first or Pair.second will be null. If current item and next item are the
-     * same, it will return null Pair. If non null Pair which contains two nulls, that means one of
-     * current and next item or both are changed to null.
-     */
-    @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* synthetic access */
-    Pair<MediaItem, MediaItem> updateAndGetCurrentNextItemIfNeededLocked() {
-        MediaItem changedCurItem = null;
-        MediaItem changedNextItem = null;
-        if (mCurrentShuffleIdx < 0 || mPlaylist.isEmpty()) {
-            if (mCurPlaylistItem == null && mNextPlaylistItem == null) {
-                return null;
-            }
-            mCurPlaylistItem = null;
-            mNextPlaylistItem = null;
-            return new Pair<>(null, null);
-        }
-        if (!ObjectsCompat.equals(mCurPlaylistItem, mShuffledList.get(mCurrentShuffleIdx))) {
-            changedCurItem = mCurPlaylistItem = mShuffledList.get(mCurrentShuffleIdx);
-        }
-        int nextShuffleIdx = mCurrentShuffleIdx + 1;
-        if (nextShuffleIdx >= mShuffledList.size()) {
-            if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
-                nextShuffleIdx = 0;
-            } else {
-                nextShuffleIdx = END_OF_PLAYLIST;
-            }
-        }
-
-        if (nextShuffleIdx == END_OF_PLAYLIST) {
-            mNextPlaylistItem = null;
-        } else if (!ObjectsCompat.equals(mNextPlaylistItem, mShuffledList.get(nextShuffleIdx))) {
-            changedNextItem = mNextPlaylistItem = mShuffledList.get(nextShuffleIdx);
-        }
-
-        return (changedCurItem == null && changedNextItem == null)
-                ? null : new Pair<>(changedCurItem, changedNextItem);
-    }
-
-    // Clamps value to [0, maxValue]
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static int clamp(int value, int maxValue) {
-        if (value < 0) {
-            return 0;
-        }
-        return (value > maxValue) ? maxValue : value;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void handleCallComplete(MediaPlayer2 mp, final MediaItem item, int what, int status) {
-        PendingCommand expected;
-        synchronized (mPendingCommands) {
-            expected = mPendingCommands.pollFirst();
-        }
-        if (expected == null) {
-            Log.i(TAG, "No matching call type for " + what + ". Possibly because of reset().");
-            return;
-        }
-
-        if (what != expected.mCallType) {
-            Log.w(TAG, "Call type does not match. expected:" + expected.mCallType
-                    + " actual:" + what);
-            status = MediaPlayer2.CALL_STATUS_ERROR_UNKNOWN;
-        }
-        if (status == MediaPlayer2.CALL_STATUS_NO_ERROR) {
-            switch (what) {
-                case MediaPlayer2.CALL_COMPLETED_PREPARE:
-                case MediaPlayer2.CALL_COMPLETED_PAUSE:
-                    setState(PLAYER_STATE_PAUSED);
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_PLAY:
-                    setState(PLAYER_STATE_PLAYING);
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_SEEK_TO:
-                    final long pos = getCurrentPosition();
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(
-                                SessionPlayer.PlayerCallback callback) {
-                            callback.onSeekCompleted(MediaPlayer.this, pos);
-                        }
-                    });
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE:
-                case MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT:
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(
-                                SessionPlayer.PlayerCallback callback) {
-                            callback.onCurrentMediaItemChanged(MediaPlayer.this, item);
-                        }
-                    });
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS:
-                    // TODO: Need to check if the speed value is really changed.
-                    final float speed = mPlayer.getPlaybackParams().getSpeed();
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(
-                                SessionPlayer.PlayerCallback callback) {
-                            callback.onPlaybackSpeedChanged(MediaPlayer.this, speed);
-                        }
-                    });
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES:
-                    final AudioAttributesCompat attr = mPlayer.getAudioAttributes();
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(SessionPlayer.PlayerCallback callback) {
-                            callback.onAudioAttributesChanged(MediaPlayer.this, attr);
-                        }
-                    });
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_SELECT_TRACK:
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(SessionPlayer.PlayerCallback callback) {
-                            callback.onTrackSelected(MediaPlayer.this, expected.mTrackInfo);
-                        }
-                    });
-                    break;
-                case MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK:
-                    notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                        @Override
-                        public void callCallback(SessionPlayer.PlayerCallback callback) {
-                            callback.onTrackDeselected(MediaPlayer.this, expected.mTrackInfo);
-                        }
-                    });
-                    break;
-            }
-        }
-        if (what != MediaPlayer2.CALL_COMPLETED_PREPARE_DRM) {
-            Integer resultCode = sResultCodeMap.containsKey(status)
-                    ? sResultCodeMap.get(status) : RESULT_ERROR_UNKNOWN;
-            expected.setResult(new PlayerResult(resultCode, item));
-        } else {
-            Integer resultCode = sPrepareDrmStatusMap.containsKey(status)
-                    ? sPrepareDrmStatusMap.get(status) : DrmResult.RESULT_ERROR_PREPARATION_ERROR;
-            expected.setResult(new DrmResult(resultCode, item));
-        }
-        executePendingFutures();
-    }
-
-    private void executePendingFutures() {
-        synchronized (mPendingFutures) {
-            Iterator<PendingFuture<? extends PlayerResult>> it = mPendingFutures.iterator();
-            while (it.hasNext()) {
-                PendingFuture<? extends PlayerResult> f = it.next();
-                if (f.isCancelled() || f.execute()) {
-                    mPendingFutures.removeFirst();
-                } else {
-                    break;
-                }
-            }
-            // Execute skip futures earlier for making them be skipped.
-            while (it.hasNext()) {
-                PendingFuture<? extends PlayerResult> f = it.next();
-                if (!f.mIsSeekTo) {
-                    break;
-                }
-                f.execute();
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    class Mp2DrmCallback extends MediaPlayer2.DrmEventCallback {
-        @Override
-        public void onDrmInfo(
-                MediaPlayer2 mp, final MediaItem item, final MediaPlayer2.DrmInfo drmInfo) {
-            notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(PlayerCallback callback) {
-                    callback.onDrmInfo(MediaPlayer.this, item,
-                            drmInfo == null ? null : new DrmInfo(drmInfo));
-                }
-            });
-        }
-
-        @Override
-        public void onDrmPrepared(MediaPlayer2 mp, final MediaItem item, final int status) {
-            handleCallComplete(mp, item, MediaPlayer2.CALL_COMPLETED_PREPARE_DRM, status);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    class Mp2Callback extends MediaPlayer2.EventCallback {
-        @Override
-        public void onVideoSizeChanged(
-                MediaPlayer2 mp, final MediaItem item, final int width, final int height) {
-            MediaItem currentItem = getCurrentMediaItem();
-            if (currentItem != null && currentItem == item) {
-                final androidx.media2.common.VideoSize commonSize =
-                        new androidx.media2.common.VideoSize(width, height);
-                notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(SessionPlayer.PlayerCallback callback) {
-                        callback.onVideoSizeChanged(MediaPlayer.this, commonSize);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onTimedMetaDataAvailable(
-                MediaPlayer2 mp, final MediaItem item, final TimedMetaData data) {
-            notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(PlayerCallback callback) {
-                    callback.onTimedMetaDataAvailable(MediaPlayer.this, item, data);
-                }
-            });
-        }
-
-        @Override
-        public void onError(
-                MediaPlayer2 mp, final MediaItem item, final int what, final int extra) {
-            setState(PLAYER_STATE_ERROR);
-            setBufferingState(item, BUFFERING_STATE_UNKNOWN);
-            notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(PlayerCallback callback) {
-                    callback.onError(MediaPlayer.this, item, what, extra);
-                }
-            });
-        }
-
-        @Override
-        public void onInfo(
-                MediaPlayer2 mp, final MediaItem item, final int mp2What, final int extra) {
-            switch (mp2What) {
-                case MediaPlayer2.MEDIA_INFO_BUFFERING_START:
-                    setBufferingState(item, BUFFERING_STATE_BUFFERING_AND_STARVED);
-                    break;
-                case MediaPlayer2.MEDIA_INFO_PREPARED:
-                case MediaPlayer2.MEDIA_INFO_BUFFERING_END:
-                    setBufferingState(item, BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
-                    break;
-                case MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE:
-                    if (extra /* percent */ >= 100) {
-                        setBufferingState(item, BUFFERING_STATE_COMPLETE);
-                    }
-                    break;
-                case MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START:
-                    boolean shouldNotifyCurrentMediaItemChanged;
-                    MediaItem nextPlaylistItem;
-                    synchronized (mPlaylistLock) {
-                        if (mCurPlaylistItem == item) {
-                            // Playback is started for the media item that the MediaPlayer has set
-                            // as the current media item via MediaPlayer2.setMediaItem() or
-                            // MediaPlayer2.skipToNext(). In that case, the current media item is
-                            // already notified in the MediaPlayer2.EventCallback#onCallCompleted(),
-                            // so don't need to notify again.
-                            shouldNotifyCurrentMediaItemChanged = false;
-                            nextPlaylistItem = null;
-                        } else {
-                            // Playback is advanced to the next item by MediaPlayer2 itself after
-                            // the playback of the mCurPlaylistItem is completed.
-                            // In that case, update the mCurPlaylistItem and also notify about the
-                            // current media item changes.
-                            shouldNotifyCurrentMediaItemChanged = true;
-                            mCurrentShuffleIdx = mShuffledList.indexOf(item);
-                            updateAndGetCurrentNextItemIfNeededLocked();
-                            nextPlaylistItem = mNextPlaylistItem;
-                        }
-                    }
-                    if (shouldNotifyCurrentMediaItemChanged) {
-                        notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                            @Override
-                            public void callCallback(SessionPlayer.PlayerCallback callback) {
-                                callback.onCurrentMediaItemChanged(MediaPlayer.this, item);
-                            }
-                        });
-                        // If the playback is advanced to the next item by itself, then the next
-                        // media item may be emptied. If so, sets the next media item so the
-                        // playback continues.
-                        if (nextPlaylistItem != null) {
-                            PendingFuture<PlayerResult> pendingFuture =
-                                    new PendingFuture<PlayerResult>(mExecutor) {
-                                        @Override
-                                        List<ResolvableFuture<PlayerResult>> onExecute() {
-                                            ArrayList<ResolvableFuture<PlayerResult>> futures =
-                                                    new ArrayList<>();
-                                            futures.add(setNextMediaItemInternal(nextPlaylistItem));
-                                            return futures;
-                                        }
-                                    };
-                            addPendingFuture(pendingFuture);
-                        }
-                    }
-                    break;
-                case MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END:
-                    MediaItem nextItemToPlay;
-                    synchronized (mPlaylistLock) {
-                        mCurrentShuffleIdx = mShuffledList.indexOf(item);
-                        nextItemToPlay = mNextPlaylistItem;
-                    }
-                    if (nextItemToPlay != null) {
-                        // Although the MediaPlayer2's playback is completed, but there's still
-                        // remaining items to play in the playlist. It happens if the MediaPlayer2's
-                        // playback is completed before the MediaPlayer has set the next item to
-                        // play.
-                        // Forcefully call skipToNextPlaylistItem to resume playback.
-                        ListenableFuture<PlayerResult> future = skipToNextPlaylistItem();
-                        if (future == null) {
-                            Log.e(TAG, "Cannot play next media item", new IllegalStateException());
-                            setState(PLAYER_STATE_ERROR);
-                        }
-                    } else {
-                        // The playback for the playlist is completed for real. Notify accordingly.
-                        setState(PLAYER_STATE_PAUSED);
-                        notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                            @Override
-                            public void callCallback(SessionPlayer.PlayerCallback callback) {
-                                callback.onPlaybackCompleted(MediaPlayer.this);
-                            }
-                        });
-                    }
-                    break;
-            }
-            if (sInfoCodeMap.containsKey(mp2What)) {
-                final int what = sInfoCodeMap.get(mp2What);
-                notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
-                    @Override
-                    public void callCallback(PlayerCallback callback) {
-                        callback.onInfo(MediaPlayer.this, item, what, extra);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onCallCompleted(
-                MediaPlayer2 mp, final MediaItem item, int what, int status) {
-            handleCallComplete(mp, item, what, status);
-        }
-
-        @Override
-        public void onMediaTimeDiscontinuity(
-                MediaPlayer2 mp, final MediaItem item, final MediaTimestamp timestamp) {
-            notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(PlayerCallback callback) {
-                    callback.onMediaTimeDiscontinuity(MediaPlayer.this, item, timestamp);
-                }
-            });
-        }
-
-        @Override
-        public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
-            // Ignore. MediaPlayer does not use MediaPlayer2.notifyWhenCommandLabelReached().
-        }
-
-        @Override
-        public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull final MediaItem item,
-                @NonNull final SessionPlayer.TrackInfo track, @NonNull final SubtitleData data) {
-            notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
-                @Override
-                public void callCallback(SessionPlayer.PlayerCallback callback) {
-                    callback.onSubtitleData(MediaPlayer.this, item, track, data);
-                }
-            });
-        }
-
-        @Override
-        public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                @NonNull List<SessionPlayer.TrackInfo> tracks) {
-            notifySessionPlayerCallback(callback -> callback.onTracksChanged(MediaPlayer.this,
-                    tracks));
-        }
-    }
-
-    /**
-     * Interface definition for callbacks to be invoked when the player has the corresponding
-     * events.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public abstract static class PlayerCallback extends SessionPlayer.PlayerCallback {
-        /**
-         * @deprecated Use
-         * {@link #onVideoSizeChanged(SessionPlayer,androidx.media2.common.VideoSize)} instead.
-         */
-        @Deprecated
-        public void onVideoSizeChanged(
-                @NonNull MediaPlayer mp, @NonNull MediaItem item, @NonNull VideoSize size) { }
-
-        /**
-         * Called to indicate the video size
-         * <p>
-         * The video size (width and height) could be 0 if there was no video,
-         * no display surface was set, or the value was not determined yet.
-         *
-         * @param player the player associated with this callback
-         * @param size the size of the video
-         */
-        @Override
-        public void onVideoSizeChanged(@NonNull SessionPlayer player,
-                @NonNull androidx.media2.common.VideoSize size) {
-            if (!(player instanceof MediaPlayer)) {
-                throw new IllegalArgumentException("player must be MediaPlayer");
-            }
-            onVideoSizeChanged((MediaPlayer) player, player.getCurrentMediaItem(),
-                    new VideoSize(size));
-        }
-
-        /**
-         * Called to indicate available timed metadata
-         * <p>
-         * This method will be called as timed metadata is extracted from the media,
-         * in the same order as it occurs in the media. The timing of this event is
-         * not controlled by the associated timestamp.
-         * <p>
-         * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates
-         * {@link TimedMetaData}.
-         *
-         * @see TimedMetaData
-         *
-         * @param mp the player associated with this callback
-         * @param item the MediaItem of this media item
-         * @param data the timed metadata sample associated with this event
-         */
-        public void onTimedMetaDataAvailable(@NonNull MediaPlayer mp,
-                @NonNull MediaItem item, @NonNull TimedMetaData data) { }
-
-        /**
-         * Called to indicate an error.
-         *
-         * @param mp the MediaPlayer2 the error pertains to
-         * @param item the MediaItem of this media item
-         * @param what the type of error that has occurred.
-         * @param extra an extra code, specific to the error. Typically implementation dependent.
-         */
-        public void onError(@NonNull MediaPlayer mp,
-                @NonNull MediaItem item, @MediaError int what, int extra) { }
-
-        /**
-         * Called to indicate an info or a warning.
-         *
-         * @param mp the player the info pertains to.
-         * @param item the MediaItem of this media item
-         * @param what the type of info or warning.
-         * @param extra an extra code, specific to the info. Typically implementation dependent.
-         */
-        public void onInfo(@NonNull MediaPlayer mp,
-                @NonNull MediaItem item, @MediaInfo int what, int extra) { }
-
-        /**
-         * Called when a discontinuity in the normal progression of the media time is detected.
-         * <p>
-         * The "normal progression" of media time is defined as the expected increase of the
-         * playback position when playing media, relative to the playback speed (for instance every
-         * second, media time increases by two seconds when playing at 2x).<br>
-         * Discontinuities are encountered in the following cases:
-         * <ul>
-         * <li>when the player is starved for data and cannot play anymore</li>
-         * <li>when the player encounters a playback error</li>
-         * <li>when the a seek operation starts, and when it's completed</li>
-         * <li>when the playback speed changes</li>
-         * <li>when the playback state changes</li>
-         * <li>when the player is reset</li>
-         * </ul>
-         *
-         * @param mp the player the media time pertains to.
-         * @param item the MediaItem of this media item
-         * @param timestamp the timestamp that correlates media time, system time and clock rate,
-         *     or {@link MediaTimestamp#TIMESTAMP_UNKNOWN} in an error case.
-         */
-        public void onMediaTimeDiscontinuity(@NonNull MediaPlayer mp,
-                @NonNull MediaItem item, @NonNull MediaTimestamp timestamp) { }
-
-        /**
-         * Called to indicate DRM info is available
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param item the MediaItem of this media item
-         * @param drmInfo DRM info of the source including PSSH, and subset
-         *                of crypto schemes supported by this device
-         */
-        @RestrictTo(LIBRARY)
-        public void onDrmInfo(@NonNull MediaPlayer mp,
-                @NonNull MediaItem item, @NonNull DrmInfo drmInfo) { }
-    }
-
-    /**
-     * Class for the player to return each audio/video/subtitle track's metadata.
-     *
-     * @see #getTracks
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class TrackInfo extends SessionPlayer.TrackInfo {
-        TrackInfo(SessionPlayer.TrackInfo infoInternal) {
-            super(infoInternal.getId(), infoInternal.getTrackType(), infoInternal.getFormat(),
-                    (infoInternal.getTrackType() != MEDIA_TRACK_TYPE_VIDEO));
-        }
-
-        @Nullable
-        @Override
-        public MediaFormat getFormat() {
-            if (getTrackType() == MEDIA_TRACK_TYPE_SUBTITLE) {
-                return super.getFormat();
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Encapsulates the DRM properties of the source.
-     */
-    @RestrictTo(LIBRARY)
-    public static final class DrmInfo {
-        private final MediaPlayer2.DrmInfo mMp2DrmInfo;
-
-        /**
-         * Returns the PSSH info of the media item for each supported DRM scheme.
-         */
-        @NonNull
-        public Map<UUID, byte[]> getPssh() {
-            return mMp2DrmInfo.getPssh();
-        }
-
-        /**
-         * Returns the intersection of the media item and the device DRM schemes.
-         * It effectively identifies the subset of the source's DRM schemes which
-         * are supported by the device too.
-         */
-        @NonNull
-        public List<UUID> getSupportedSchemes() {
-            return mMp2DrmInfo.getSupportedSchemes();
-        }
-
-        DrmInfo(MediaPlayer2.DrmInfo info) {
-            mMp2DrmInfo = info;
-        }
-    }
-
-    /**
-     * Interface definition of a callback to be invoked when the app
-     * can do DRM configuration (get/set properties) before the session
-     * is opened. This facilitates configuration of the properties, like
-     * 'securityLevel', which has to be set after DRM scheme creation but
-     * before the DRM session is opened.
-     * <p>
-     * The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
-     * and {@link #setDrmPropertyString}.
-     */
-    @RestrictTo(LIBRARY)
-    public interface OnDrmConfigHelper {
-        /**
-         * Called to give the app the opportunity to configure DRM before the session is created
-         *
-         * @param mp the {@code MediaPlayer} associated with this callback
-         * @param item the MediaItem of this media item
-         */
-        void onDrmConfig(@NonNull MediaPlayer mp, @NonNull MediaItem item);
-    }
-
-    /**
-     * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
-     * Extends MediaDrm.MediaDrmException
-     */
-    @RestrictTo(LIBRARY)
-    public static class NoDrmSchemeException extends Exception {
-        public NoDrmSchemeException(@Nullable String detailMessage) {
-            super(detailMessage);
-        }
-    }
-
-    /**
-     * Definitions for the metrics that are reported via the {@link #getMetrics} call.
-     */
-    @RestrictTo(LIBRARY)
-    public static final class MetricsConstants {
-        private MetricsConstants() {}
-
-        /**
-         * Key to extract the MIME type of the video track
-         * from the {@link #getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-
-        /**
-         * Key to extract the codec being used to decode the video track
-         * from the {@link #getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-
-        /**
-         * Key to extract the width (in pixels) of the video track
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String WIDTH = "android.media.mediaplayer.width";
-
-        /**
-         * Key to extract the height (in pixels) of the video track
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String HEIGHT = "android.media.mediaplayer.height";
-
-        /**
-         * Key to extract the count of video frames played
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES = "android.media.mediaplayer.frames";
-
-        /**
-         * Key to extract the count of video frames dropped
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-
-        /**
-         * Key to extract the MIME type of the audio track
-         * from the {@link #getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-
-        /**
-         * Key to extract the codec being used to decode the audio track
-         * from the {@link #getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-
-        /**
-         * Key to extract the duration (in milliseconds) of the
-         * media being played
-         * from the {@link #getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String DURATION = "android.media.mediaplayer.durationMs";
-
-        /**
-         * Key to extract the playing time (in milliseconds) of the
-         * media being played
-         * from the {@link #getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String PLAYING = "android.media.mediaplayer.playingMs";
-
-        /**
-         * Key to extract the count of errors encountered while
-         * playing the media
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERRORS = "android.media.mediaplayer.err";
-
-        /**
-         * Key to extract an (optional) error code detected while
-         * playing the media
-         * from the {@link #getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-    }
-
-    /**
-     * Result class of the asynchronous DRM APIs.
-     */
-    @RestrictTo(LIBRARY)
-    public static class DrmResult extends PlayerResult {
-        /**
-         * The device required DRM provisioning but couldn't reach the provisioning server.
-         */
-        public static final int RESULT_ERROR_PROVISIONING_NETWORK_ERROR = -1001;
-
-        /**
-         * The device required DRM provisioning but the provisioning server denied the request.
-         */
-        public static final int RESULT_ERROR_PROVISIONING_SERVER_ERROR = -1002;
-
-        /**
-         * The DRM preparation has failed.
-         */
-        public static final int RESULT_ERROR_PREPARATION_ERROR = -1003;
-
-        /**
-         * The crypto scheme UUID that is not supported by the device.
-         */
-        public static final int RESULT_ERROR_UNSUPPORTED_SCHEME = -1004;
-
-        /**
-         * The hardware resources are not available, due to being in use.
-         */
-        public static final int RESULT_ERROR_RESOURCE_BUSY = -1005;
-
-        @IntDef(flag = false, /*prefix = "PREPARE_DRM_STATUS",*/ value = {
-                RESULT_SUCCESS,
-                RESULT_ERROR_PROVISIONING_NETWORK_ERROR,
-                RESULT_ERROR_PROVISIONING_SERVER_ERROR,
-                RESULT_ERROR_PREPARATION_ERROR,
-                RESULT_ERROR_UNSUPPORTED_SCHEME,
-                RESULT_ERROR_RESOURCE_BUSY,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY)
-        public @interface DrmResultCode {}
-
-        /**
-         * Constructor that uses the current system clock as the completion time.
-         *
-         * @param resultCode result code. Recommends to use the standard code defined here.
-         * @param item media item when the operation is completed
-         */
-        public DrmResult(@DrmResultCode int resultCode, @NonNull MediaItem item) {
-            super(resultCode, item);
-        }
-
-        /**
-         * Gets the result code.
-         *
-         * @return result code.
-         */
-        @Override
-        @DrmResultCode
-        public int getResultCode() {
-            return super.getResultCode();
-        }
-    }
-
-    /**
-     * List for {@link MediaItem} which manages the resource life cycle of
-     * {@link android.os.ParcelFileDescriptor} in {@link FileMediaItem}.
-     */
-    static class MediaItemList {
-        private ArrayList<MediaItem> mList = new ArrayList<>();
-
-        void add(int index, MediaItem item) {
-            if (item instanceof FileMediaItem) {
-                ((FileMediaItem) item).increaseRefCount();
-            }
-            mList.add(index, item);
-        }
-
-        boolean replaceAll(Collection<MediaItem> c) {
-            for (MediaItem item : c) {
-                if (item instanceof FileMediaItem) {
-                    ((FileMediaItem) item).increaseRefCount();
-                }
-            }
-            clear();
-            return mList.addAll(c);
-        }
-
-        MediaItem remove(int index) {
-            MediaItem item = mList.remove(index);
-            if (item instanceof FileMediaItem) {
-                ((FileMediaItem) item).decreaseRefCount();
-            }
-            return item;
-        }
-
-        MediaItem get(int index) {
-            return mList.get(index);
-        }
-
-        MediaItem set(int index, MediaItem item) {
-            if (item instanceof FileMediaItem) {
-                ((FileMediaItem) item).increaseRefCount();
-            }
-            MediaItem removed = mList.set(index, item);
-            if (removed instanceof FileMediaItem) {
-                ((FileMediaItem) removed).decreaseRefCount();
-            }
-            return removed;
-        }
-
-        void clear() {
-            for (MediaItem item : mList) {
-                if (item instanceof FileMediaItem) {
-                    ((FileMediaItem) item).decreaseRefCount();
-                }
-            }
-            mList.clear();
-        }
-
-        int size() {
-            return mList.size();
-        }
-
-        boolean contains(Object o) {
-            return mList.contains(o);
-        }
-
-        int indexOf(Object o) {
-            return mList.indexOf(o);
-        }
-
-        boolean isEmpty() {
-            return mList.isEmpty();
-        }
-
-        Collection<MediaItem> getCollection() {
-            return mList;
-        }
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer2.java b/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer2.java
deleted file mode 100644
index 1aa56ef..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/MediaPlayer2.java
+++ /dev/null
@@ -1,1822 +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.media2.player;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.media.DeniedByServerException;
-import android.media.MediaDrm;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.view.Surface;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Executor;
-
-/**
- * MediaPlayer2 class can be used to control playback of audio/video files and streams.
- *
- * <p>Topics covered here are:
- * <ol>
- * <li><a href="#PlayerStates">Player states</a>
- * <li><a href="#Invalid_States">Invalid method calls</a>
- * <li><a href="#Permissions">Permissions</a>
- * <li><a href="#callbacks">Callbacks</a>
- * </ol>
- *
- *
- * <h3 id="PlayerStates">Player states</h3>
- *
- * <p>The playback control of audio/video files is managed as a state machine.</p>
- * <p><div style="text-align:center;"><img src="../../../images/mediaplayer2_state_diagram.png"
- *         alt="MediaPlayer2 State diagram"
- *         border="0" /></div></p>
- * <p>The MediaPlayer2 object has five states:</p>
- * <ol>
- *     <li><p>{@link #PLAYER_STATE_IDLE}: MediaPlayer2 is in the <strong>Idle</strong>
- *         state after you create it using
- *         {@link #create(Context)}, or after calling {@link #reset()}.</p>
- *
- *         <p>While in this state, you should call
- *         {@link #setMediaItem(MediaItem) setMediaItem()}. It is a good
- *         programming practice to register an {@link EventCallback#onCallCompleted onCallCompleted}
- *         <a href="#callback">callback</a> and watch for {@link #CALL_STATUS_BAD_VALUE} and
- *         {@link #CALL_STATUS_ERROR_IO}, which might be caused by <code>setMediaItem</code>.
- *         </p>
- *
- *         <p>Calling {@link #prepare()} transfers a MediaPlayer2 object to
- *         the <strong>Prepared</strong> state. Note
- *         that {@link #prepare()} is asynchronous. When the preparation completes,
- *         If you register a {@link EventCallback#onInfo} <a href="#callback">callback</a>
- *         the player executes the callback
- *         with {@link #MEDIA_INFO_PREPARED} before transitioning to the
- *         <strong>Prepared</strong> state.</p>
- *         </li>
- *
- *     <li>{@link #PLAYER_STATE_PREPARED}: A MediaPlayer object must be in the
- *         <strong>Prepared</strong> state before playback can be started for the first time.
- *         While in this state, you can set player properties
- *         such as audio/sound volume and looping by invoking the corresponding set methods.
- *         Calling {@link #play()} transfers a MediaPlayer2 object to
- *         the <strong>Playing</strong> state.
- *      </li>
- *
- *     <li>{@link #PLAYER_STATE_PLAYING}:
- *         <p>The player plays the media item while in this state.
- *         If you register an {@link EventCallback#onInfo} <a href="#callback">callback</a>
- *         the player regularly executes the callback with
- *         {@link #MEDIA_INFO_BUFFERING_UPDATE}.
- *         This allows applications to keep track of the buffering status
- *         while streaming audio/video.</p>
- *
- *         <p> When the playback reaches the end of stream, the behavior depends on whether or
- *         not you've enabled looping by calling {@link #loopCurrent(boolean)}:</p>
- *         <ul>
- *         <li>If the looping mode was set to <code>false</code> the player will transfer
- *         to the <strong>Paused</strong> state. If you registered an {@link EventCallback#onInfo}
- *         <a href="#callback">callback</a>
- *         the player calls the callback with {@link #MEDIA_INFO_DATA_SOURCE_END} before entering
- *         the <strong>Paused</strong> state.
- *         </li>
- *         <li>If the looping mode was set to <code>true</code>,
- *         the MediaPlayer2 object remains in the <strong>Playing</strong> state and replays its
- *         media item from the beginning.</li>
- *         </ul>
- *         </li>
- *
- *     <li>{@link #PLAYER_STATE_PAUSED}: Audio/video playback pauses while in this state.
- *         Call {@link #play()} to resume playback from the position where it paused.</li>
- *
- *     <li>{@link #PLAYER_STATE_ERROR}: <p>In general, playback might fail due to various
- *          reasons such as unsupported audio/video format, poorly interleaved
- *          audio/video, resolution too high, streaming timeout, and others.
- *          In addition, due to programming errors, a playback
- *          control operation might be performed from an <a href="#invalid_state">invalid state</a>.
- *          In these cases the player transitions to the <strong>Error</strong> state.</p>
- *
- *          <p>If you register an {@link EventCallback#onError}} <a href="#callback">callback</a>
- *          the callback will be performed when entering the state. When programming errors happen,
- *          such as calling {@link #prepare()} and {@link #setMediaItem(MediaItem)} methods
- *          from an <a href="#invalid_state">invalid state</a>, The callback is called with
- *          {@link #CALL_STATUS_INVALID_OPERATION} . The MediaPlayer2 object enters the
- *          <strong>Error</strong> whether or not a callback exists. </p>
- *
- *          <p>To recover from an error and reuse a MediaPlayer2 object that is in the <strong>
- *          Error</strong> state,
- *          call {@link #reset()}. The object will return to the <strong>Idle</strong>
- *          state and all state information will be lost.</p>
- *          </li>
- * </ol>
- *
- * <p>You should follow these best practices when coding an app that uses MediaPlayer2:</p>
- *
- * <ul>
- *
- * <li>Use <a href="#callback">callbacks</a> to respond to state changes and errors.</li>
- *
- * <li>When  a MediaPlayer2 object is no longer being used, call {@link #close()} as soon as
- * possible to release the resources used by the internal player engine associated with the
- * MediaPlayer2. Failure to call {@link #close()} may cause subsequent instances of MediaPlayer2
- * objects to fallback to software implementations or fail altogether. You cannot use MediaPlayer2
- * after you call {@link #close()}. There is no way to bring it back to any other state.</li>
- *
- * <li>The current playback position can be retrieved with a call to {@link #getCurrentPosition()},
- * which is helpful for applications such as a Music player that need to keep track of the playback
- * progress.</li>
- *
- * <li>The playback position can be adjusted with a call to {@link #seekTo}. Although the
- * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
- * while to finish, especially for audio/video being streamed. If you register an
- * {@link EventCallback#onCallCompleted} <a href="#callback">callback</a>, the callback is
- * called When the seek operation completes with {@link #CALL_COMPLETED_SEEK_TO}.</li>
- *
- * <li>You can call {@link #seekTo(long, int)} from the <strong>Prepared</strong> and
- * <strong>Paused</strong> states. In these cases, if you are playing a video stream and
- * the requested position is valid  one video frame is displayed.</li>
- *
- * </ul>
- *
- * <h3 id="Invalid_States">Invalid method calls</h3>
- *
- * <p>The only methods you safely call from the <strong>Error</strong> state are {@link #close()},
- * {@link #reset()}, {@link #notifyWhenCommandLabelReached}, {@link #clearPendingCommands()},
- * {@link #setEventCallback}, {@link #clearEventCallback()} and {@link #getState()}.
- * Any other methods might throw an exception, return meaningless data, or invoke a
- * {@link EventCallback#onCallCompleted} with an error code.</p>
- *
- * <p>Most methods can be called from any non-Error state. They will either perform their work or
- * silently have no effect. The following table lists the methods that will invoke a
- * {@link EventCallback#onCallCompleted} with an error code or throw an exception when they are
- * called from the associated invalid states.</p>
- *
- * <table border="0" cellspacing="0" cellpadding="0">
- * <tr><th>Method Name</th>
- * <th>Invalid States</th></tr>
- *
- * <tr><td>setMediaItem</td> <td>{Prepared, Paused, Playing}</td></tr>
- * <tr><td>prepare</td> <td>{Prepared, Paused, Playing}</td></tr>
- * <tr><td>play</td> <td>{Idle}</td></tr>
- * <tr><td>pause</td> <td>{Idle}</td></tr>
- * <tr><td>seekTo</td> <td>{Idle}</td></tr>
- * <tr><td>getCurrentPosition</td> <td>{Idle}</td></tr>
- * <tr><td>getDuration</td> <td>{Idle}</td></tr>
- * <tr><td>getBufferedPosition</td> <td>{Idle}</td></tr>
- * <tr><td>getTracks</td> <td>{Idle}</td></tr>
- * <tr><td>getSelectedTrack</td> <td>{Idle}</td></tr>
- * <tr><td>selectTrack</td> <td>{Idle}</td></tr>
- * <tr><td>deselectTrack</td> <td>{Idle}</td></tr>
- * </table>
- *
- * <h3 id="Permissions">Permissions</h3>
- * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
- * when used with network-based content.
- *
- * <h3 id="callback">Callbacks</h3>
- * <p>Many errors do not result in a transition to the  <strong>Error</strong> state.
- * It is good programming practice to register callback listeners using
- * {@link #setEventCallback(Executor, EventCallback)} and
- * {@link #setDrmEventCallback(Executor, DrmEventCallback)}).
- * You can receive a callback at any time and from any state.</p>
- *
- * <p>If it's important for your app to respond to state changes (for instance, to update the
- * controls on a transport UI), you should register an {@link EventCallback#onCallCompleted} and
- * detect state change commands by testing the <code>what</code> parameter for a callback from one
- * of the state transition methods: {@link #CALL_COMPLETED_PREPARE}, {@link #CALL_COMPLETED_PLAY},
- * and {@link #CALL_COMPLETED_PAUSE}.
- * Then check the <code>status</code> parameter. The value {@link #CALL_STATUS_NO_ERROR} indicates a
- * successful transition. Any other value will be an error. Call {@link #getState()} to
- * determine the current state.</p>
- *
- * <p>In order for callbacks to work, your app must create
- * MediaPlayer2 objects on a thread that has its own running Looper. This can be done on the main UI
- * thread, which has a Looper.</p>
- */
-/* package */ abstract class MediaPlayer2 {
-
-    /**
-     * Create a MediaPlayer2 object.
-     *
-     * @param context The context the player is running in
-     * @return A MediaPlayer2 object created
-     */
-    @NonNull
-    public static MediaPlayer2 create(@NonNull Context context) {
-        return new ExoPlayerMediaPlayer2Impl(context);
-    }
-
-    protected MediaPlayer2() { }
-
-    /**
-     * Cancels the asynchronous call previously submitted.
-     *
-     * @param token the token which is returned from the asynchronous call.
-     * @return {@code false} if the task could not be cancelled; {@code true} otherwise.
-     */
-    public abstract boolean cancel(Object token);
-
-    /**
-     * Releases the resources held by this {@code MediaPlayer2} object.
-     *
-     * It is considered good practice to call this method when you're
-     * done using the MediaPlayer2. In particular, whenever an Activity
-     * of an application is paused (its onPause() method is called),
-     * or stopped (its onStop() method is called), this method should be
-     * invoked to release the MediaPlayer2 object, unless the application
-     * has a special need to keep the object around. In addition to
-     * unnecessary resources (such as memory and instances of codecs)
-     * being held, failure to call this method immediately if a
-     * MediaPlayer2 object is no longer needed may also lead to
-     * continuous battery consumption for mobile devices, and playback
-     * failure for other applications if no multiple instances of the
-     * same codec are supported on a device. Even if multiple instances
-     * of the same codec are supported, some performance degradation
-     * may be expected when unnecessary multiple instances are used
-     * at the same time.
-     */
-    // This is a synchronous call.
-    public abstract void close();
-
-    /**
-     * Starts or resumes playback. If playback had previously been paused,
-     * playback will continue from where it was paused. If playback had
-     * reached end of stream and been paused, or never started before,
-     * playback will start at the beginning.
-     *
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object play();
-
-    /**
-     * Prepares the player for playback, asynchronously.
-     *
-     * After setting the datasource and the display surface, you need to
-     * call prepare().
-     *
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object prepare();
-
-    /**
-     * Pauses playback. Call play() to resume.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object pause();
-
-    /**
-     * Tries to play next media item if applicable.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object skipToNext();
-
-    /**
-     * Moves the media to specified time position.
-     * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}.
-     *
-     * @param msec the offset in milliseconds from the start to seek to
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public Object seekTo(long msec) {
-        return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
-    }
-
-    /**
-     * Gets the current playback position.
-     *
-     * @return the current position in milliseconds
-     */
-    public abstract long getCurrentPosition();
-
-    /**
-     * Gets the duration of the file.
-     *
-     * @return the duration in milliseconds, if no duration is available
-     *         (for example, if streaming live content), -1 is returned.
-     */
-    public abstract long getDuration();
-
-    /**
-     * Gets the current buffered media source position received through progressive downloading.
-     * The received buffering percentage indicates how much of the content has been buffered
-     * or played. For example a buffering update of 80 percent when half the content
-     * has already been played indicates that the next 30 percent of the
-     * content to play has been buffered.
-     *
-     * @return the current buffered media source position in milliseconds
-     */
-    public abstract long getBufferedPosition();
-
-    /**
-     * Gets the current MediaPlayer2 state.
-     *
-     * @return the current MediaPlayer2 state.
-     */
-    public abstract @MediaPlayer2State int getState();
-
-    /**
-     * Sets the audio attributes for this MediaPlayer2.
-     * See {@link AudioAttributesCompat} for how to build and configure an instance of this class.
-     * You must call this method before {@link #prepare()} in order
-     * for the audio attributes to become effective thereafter.
-     * @param attributes a non-null set of audio attributes
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setAudioAttributes(@NonNull AudioAttributesCompat attributes);
-
-    /**
-     * Gets the audio attributes for this MediaPlayer2.
-     * @return attributes a set of audio attributes
-     */
-    public abstract @Nullable AudioAttributesCompat getAudioAttributes();
-
-    /**
-     * Sets the media item as described by a MediaItem.
-     * <p>
-     * When the media item is a {@link FileMediaItem}, the {@link ParcelFileDescriptor}
-     * in the {@link FileMediaItem} will be closed by the player.
-     *
-     * @param item the descriptor of media item you want to play
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setMediaItem(@NonNull MediaItem item);
-
-    /**
-     * Sets a single media item as described by a MediaItem which will be played
-     * after current media item is finished.
-     * <p>
-     * When the media item is a {@link FileMediaItem}, the {@link ParcelFileDescriptor}
-     * in the {@link FileMediaItem} will be closed by the player.
-     *
-     * @param item the descriptor of media item you want to play after current one
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setNextMediaItem(@NonNull MediaItem item);
-
-    /**
-     * Sets a list of media items to be played sequentially after current media item is done.
-     * <p>
-     * If a media item in the list is a {@link FileMediaItem}, the {@link ParcelFileDescriptor}
-     * in the {@link FileMediaItem} will be closed by the player.
-     *
-     * @param items the list of media items you want to play after current one
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setNextMediaItems(@NonNull List<MediaItem> items);
-
-    /**
-     * Gets the current media item as described by a MediaItem.
-     *
-     * @return the current MediaItem
-     */
-    public abstract @Nullable MediaItem getCurrentMediaItem();
-
-    /**
-     * Configures the player to loop on the current media item.
-     * @param loop true if the current media item is meant to loop.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object loopCurrent(boolean loop);
-
-    /**
-     * Sets the volume of the audio of the media to play, expressed as a linear multiplier
-     * on the audio samples.
-     * Note that this volume is specific to the player, and is separate from stream volume
-     * used across the platform.<br>
-     * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
-     * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
-     * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setPlayerVolume(float volume);
-
-    /**
-     * Returns the current volume of this player to this player.
-     * Note that it does not take into account the associated stream volume.
-     * @return the player volume.
-     */
-    public abstract float getPlayerVolume();
-
-    /**
-     * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
-     */
-    public float getMaxPlayerVolume() {
-        return 1.0f;
-    }
-
-    /**
-     * Insert a task in the command queue to help the client to identify whether a batch
-     * of commands has been finished. When this command is processed, a notification
-     * {@link EventCallback#onCommandLabelReached} will be fired with the
-     * given {@code label}.
-     *
-     * @see EventCallback#onCommandLabelReached
-     *
-     * @param label An application specific Object used to help to identify the completeness
-     * of a batch of commands.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object notifyWhenCommandLabelReached(@NonNull Object label);
-
-    /**
-     * Sets the {@link Surface} to be used as the sink for the video portion of
-     * the media.  Setting a
-     * Surface will un-set any Surface or SurfaceHolder that was previously set.
-     * A null surface will result in only the audio track being played.
-     *
-     * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
-     * returned from {@link SurfaceTexture#getTimestamp()} will have an
-     * unspecified zero point.  These timestamps cannot be directly compared
-     * between different media sources, different instances of the same media
-     * source, or multiple runs of the same program.  The timestamp is normally
-     * monotonically increasing and is unaffected by time-of-day adjustments,
-     * but it is reset when the position is set.
-     *
-     * @param surface The {@link Surface} to be used for the video portion of
-     * the media.
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized or has been released.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setSurface(@Nullable Surface surface);
-
-    /* Do not change these video scaling mode values below without updating
-     * their counterparts in system/window.h! Please do not forget to update
-     * {@link #isVideoScalingModeSupported} when new video scaling modes
-     * are added.
-     */
-    /**
-     * Specifies a video scaling mode. The content is stretched to the
-     * surface rendering area. When the surface has the same aspect ratio
-     * as the content, the aspect ratio of the content is maintained;
-     * otherwise, the aspect ratio of the content is not maintained when video
-     * is being rendered.
-     * There is no content cropping with this video scaling mode.
-     */
-    public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1;
-
-    /**
-     * Discards all pending commands.
-     */
-    // This is a synchronous call.
-    public abstract void clearPendingCommands();
-
-    /**
-     * Returns the width of the video.
-     *
-     * @return the width of the video, or 0 if there is no video or the width has not been
-     * determined yet. The {@link EventCallback} can be registered via
-     * {@link #setEventCallback(Executor, EventCallback)} to provide a
-     * notification {@link EventCallback#onVideoSizeChanged} when the width
-     * is available.
-     */
-    public abstract int getVideoWidth();
-
-    /**
-     * Returns the height of the video.
-     *
-     * @return the height of the video, or 0 if there is no video or the height has not been
-     * determined yet. The {@link EventCallback} can be registered via
-     * {@link #setEventCallback(Executor, EventCallback)} to provide a
-     * notification {@link EventCallback#onVideoSizeChanged} when the height is
-     * available.
-     */
-    public abstract int getVideoHeight();
-
-    /**
-     * Return Metrics data about the current player.
-     *
-     * @return a {@link PersistableBundle} containing the set of attributes and values
-     * available for the media being handled by this instance of MediaPlayer2
-     * The attributes are descibed in {@link MetricsConstants}.
-     *
-     *  Additional vendor-specific fields may also be present in
-     *  the return value.
-     */
-    @RequiresApi(21)
-    public abstract PersistableBundle getMetrics();
-
-    /**
-     * Sets playback rate using {@link PlaybackParams}. The player sets its internal
-     * PlaybackParams to the given input. This does not change the player state. For example,
-     * if this is called with the speed of 2.0f in {@link #PLAYER_STATE_PAUSED}, the player will
-     * just update internal property and stay paused. Once the client calls {@link #play()}
-     * afterwards, the player will start playback with the given speed. Calling this with zero
-     * speed is not allowed.
-     *
-     * @param params the playback params.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setPlaybackParams(@NonNull PlaybackParams params);
-
-    /**
-     * Gets the playback params, containing the current playback rate.
-     *
-     * @return the playback params.
-     */
-    @NonNull
-    public abstract PlaybackParams getPlaybackParams();
-
-    /**
-     * Seek modes used in method seekTo(long, int) to move media position
-     * to a specified location.
-     *
-     * Do not change these mode values without updating their counterparts
-     * in include/media/IMediaSource.h!
-     */
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * right before or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_PREVIOUS_SYNC    = 0x00;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * right after or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_NEXT_SYNC        = 0x01;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a media item that is located
-     * closest to (in time) or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST_SYNC     = 0x02;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a frame (not necessarily a key frame) associated with a media item that
-     * is located closest to or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST          = 0x03;
-
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "SEEK",*/ value = {
-            SEEK_PREVIOUS_SYNC,
-            SEEK_NEXT_SYNC,
-            SEEK_CLOSEST_SYNC,
-            SEEK_CLOSEST,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SeekMode {}
-
-    /**
-     * Moves the media to specified time position by considering the given mode.
-     * <p>
-     * When seekTo is finished, the user will be notified via
-     * {@link EventCallback#onInfo} with {@link #CALL_COMPLETED_SEEK_TO}.
-     * There is at most one active seekTo processed at any time. If there is a to-be-completed
-     * seekTo, new seekTo requests will be queued in such a way that only the last request
-     * is kept. When current seekTo is completed, the queued request will be processed if
-     * that request is different from just-finished seekTo operation, i.e., the requested
-     * position or mode is different.
-     *
-     * @param msec the offset in milliseconds from the start to seek to.
-     * When seeking to the given time position, there is no guarantee that the media item
-     * has a frame located at the position. When this happens, a frame nearby will be rendered.
-     * If msec is negative, time position zero will be used.
-     * If msec is larger than duration, duration will be used.
-     * @param mode the mode indicating where exactly to seek to.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object seekTo(long msec, @SeekMode int mode);
-
-    /**
-     * Gets current playback position as a {@link MediaTimestamp}.
-     * <p>
-     * The MediaTimestamp represents how the media time correlates to the system time in
-     * a linear fashion using an anchor and a clock rate. During regular playback, the media
-     * time moves fairly constantly (though the anchor frame may be rebased to a current
-     * system time, the linear correlation stays steady). Therefore, this method does not
-     * need to be called often.
-     * <p>
-     * To help users get current playback position, this method always anchors the timestamp
-     * to the current {@link System#nanoTime system time}, so
-     * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
-     *
-     * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
-     *         is available, e.g. because the media player has not been initialized.
-     *
-     * @see MediaTimestamp
-     */
-    @Nullable
-    public abstract MediaTimestamp getTimestamp();
-
-    /**
-     * Resets the MediaPlayer2 to its uninitialized state. After calling
-     * this method, you will have to initialize it again by setting the
-     * media item and calling prepare().
-     */
-    // This is a synchronous call.
-    public abstract void reset();
-
-    /**
-     * Sets the audio session ID.
-     *
-     * @param sessionId the audio session ID.
-     * The audio session ID is a system wide unique identifier for the audio stream played by
-     * this MediaPlayer2 instance.
-     * The primary use of the audio session ID  is to associate audio effects to a particular
-     * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
-     * this effect will be applied only to the audio content of media players within the same
-     * audio session and not to the output mix.
-     * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
-     * However, it is possible to force this player to be part of an already existing audio session
-     * by calling this method.
-     * This method must be called before one of the overloaded <code> setMediaItem </code> methods.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setAudioSessionId(int sessionId);
-
-    /**
-     * Returns the audio session ID.
-     *
-     * @return the audio session ID. {@see #setAudioSessionId(int)}
-     * Note that the audio session ID is 0 only if a problem occurred when the MediaPlayer2 was
-     * constructed.
-     */
-    public abstract int getAudioSessionId();
-
-    /**
-     * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
-     * effect which can be applied on any sound source that directs a certain amount of its
-     * energy to this effect. This amount is defined by setAuxEffectSendLevel().
-     * See {@link #setAuxEffectSendLevel(float)}.
-     * <p>After creating an auxiliary effect (e.g.
-     * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
-     * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
-     * to attach the player to the effect.
-     * <p>To detach the effect from the player, call this method with a null effect id.
-     * <p>This method must be called after one of the overloaded <code> setMediaItem </code>
-     * methods.
-     * @param effectId system wide unique id of the effect to attach
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object attachAuxEffect(int effectId);
-
-
-    /**
-     * Sets the send level of the player to the attached auxiliary effect.
-     * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
-     * <p>By default the send level is 0, so even if an effect is attached to the player
-     * this method must be called for the effect to be applied.
-     * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
-     * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
-     * so an appropriate conversion from linear UI input x to level is:
-     * x == 0 -> level = 0
-     * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
-     * @param level send level scalar
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object setAuxEffectSendLevel(float level);
-
-    /**
-     * Returns a List of track information.
-     *
-     * @return List of track info. The total number of tracks is the array length.
-     */
-    @NonNull
-    public abstract List<TrackInfo> getTracks();
-
-    /**
-     * Returns the metadata of the audio, video, or subtitle track currently selected for playback,
-     * The return value is an item of the array returned by {@link #getTracks()}.
-     *
-     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
-     * @return metadata of the audio, video, or subtitle track currently selected for playback;
-     * {@code null} is returned when there is no selected track for {@code trackType} or
-     * when {@code trackType} is not one of audio, video, or subtitle.
-     * @throws IllegalStateException if called after {@link #close()}
-     *
-     * @see #getTracks()
-     */
-    @Nullable
-    public abstract TrackInfo getSelectedTrack(int trackType);
-
-    /**
-     * Selects a track.
-     * <p>
-     * If a MediaPlayer2 is in invalid state, {@link #CALL_STATUS_INVALID_OPERATION} will be
-     * reported with {@link EventCallback#onCallCompleted}.
-     * If a MediaPlayer2 is in <em>Playing</em> state, the selected track is presented immediately.
-     * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
-     * </p>
-     * <p>
-     * In any valid state, if it is called multiple times on the same type of track (ie. Video,
-     * Audio, Subtitle), the most recent one will be chosen.
-     * </p>
-     * <p>
-     * The first audio and video tracks are selected by default if available, even though
-     * this method is not called. However, no subtitle track will be selected until
-     * this function is called.
-     * </p>
-     * <p>
-     * Currently, only subtitle tracks or audio tracks can be selected via this method.
-     * </p>
-     * @param trackId the id of the track to be selected. The id can be obtained by calling
-     * {@link TrackInfo#getId()} to an {@link TrackInfo} returned by {@link #getTracks()}.
-     * Note that the {@link TrackInfo}s may become invalid when
-     * {@link EventCallback#onTracksChanged} is called.
-     *
-     * @see TrackInfo#getId()
-     * @see #getTracks
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    @NonNull
-    public abstract Object selectTrack(int trackId);
-
-    /**
-     * Deselects a track.
-     * <p>
-     * Currently, the track must be a subtitle track and no audio or video tracks can be
-     * deselected. If the subtitle track identified by index has not been
-     * selected before, it throws an exception.
-     * </p>
-     * @param trackId the id of the track to be deselected. The id can be obtained by calling
-     * {@link TrackInfo#getId()} to an {@link TrackInfo} returned by {@link #getTracks()} or
-     * {@link #getSelectedTrack(int)}. Note that the {@link TrackInfo}s may become invalid when
-     * {@link EventCallback#onTracksChanged} is called.
-     *
-     * @see TrackInfo#getId()
-     * @see #getTracks
-     * @see #getSelectedTrack(int)
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    @NonNull
-    public abstract Object deselectTrack(int trackId);
-
-    /**
-     * Interface definition for callbacks to be invoked when the player has the corresponding
-     * events.
-     */
-    public abstract static class EventCallback {
-        /**
-         * Called to indicate the video size
-         *
-         * The video size (width and height) could be 0 if there was no video,
-         * no display surface was set, or the value was not determined yet.
-         *
-         * @param mp the MediaPlayer2 associated with this callback
-         * @param item the MediaItem of this media item
-         * @param width the width of the video
-         * @param height the height of the video
-         */
-        public void onVideoSizeChanged(
-                MediaPlayer2 mp, MediaItem item, int width, int height) { }
-
-        /**
-         * Called to indicate available timed metadata
-         * <p>
-         * This method will be called as timed metadata is extracted from the media,
-         * in the same order as it occurs in the media. The timing of this event is
-         * not controlled by the associated timestamp.
-         * <p>
-         * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates
-         * {@link TimedMetaData}.
-         *
-         * @see MediaPlayer2#selectTrack(int)
-         * @see TimedMetaData
-         *
-         * @param mp the MediaPlayer2 associated with this callback
-         * @param item the MediaItem of this media item
-         * @param data the timed metadata sample associated with this event
-         */
-        public void onTimedMetaDataAvailable(
-                MediaPlayer2 mp, MediaItem item, TimedMetaData data) { }
-
-        /**
-         * Called to indicate an error.
-         *
-         * @param mp the MediaPlayer2 the error pertains to
-         * @param item the MediaItem of this media item
-         * @param what the type of error that has occurred.
-         * @param extra an extra code, specific to the error. Typically
-         * implementation dependent.
-         */
-        public void onError(
-                MediaPlayer2 mp, MediaItem item, @MediaError int what, int extra) { }
-
-        /**
-         * Called to indicate an info or a warning.
-         *
-         * @param mp the MediaPlayer2 the info pertains to.
-         * @param item the MediaItem of this media item
-         * @param what the type of info or warning.
-         * @param extra an extra code, specific to the info. Typically
-         * implementation dependent.
-         */
-        public void onInfo(MediaPlayer2 mp, MediaItem item, @MediaInfo int what, int extra) { }
-
-        /**
-         * Called to acknowledge an API call.
-         *
-         * @param mp the MediaPlayer2 the call was made on.
-         * @param item the MediaItem of this media item
-         * @param what the enum for the API call.
-         * @param status the returned status code for the call.
-         */
-        public void onCallCompleted(
-                MediaPlayer2 mp, MediaItem item, @CallCompleted int what,
-                @CallStatus int status) { }
-
-        /**
-         * Called when a discontinuity in the normal progression of the media time is detected.
-         * The "normal progression" of media time is defined as the expected increase of the
-         * playback position when playing media, relative to the playback speed (for instance every
-         * second, media time increases by two seconds when playing at 2x).<br>
-         * Discontinuities are encountered in the following cases:
-         * <ul>
-         * <li>when the player is starved for data and cannot play anymore</li>
-         * <li>when the player encounters a playback error</li>
-         * <li>when the a seek operation starts, and when it's completed</li>
-         * <li>when the playback speed changes</li>
-         * <li>when the playback state changes</li>
-         * <li>when the player is reset</li>
-         * </ul>
-         *
-         * @param mp the MediaPlayer2 the media time pertains to.
-         * @param item the MediaItem of this media item
-         * @param timestamp the timestamp that correlates media time, system time and clock rate,
-         *     or {@link MediaTimestamp#TIMESTAMP_UNKNOWN} in an error case.
-         */
-        public void onMediaTimeDiscontinuity(
-                MediaPlayer2 mp, MediaItem item, MediaTimestamp timestamp) { }
-
-        /**
-         * Called to indicate {@link #notifyWhenCommandLabelReached(Object)} has been processed.
-         *
-         * @param mp the MediaPlayer2 {@link #notifyWhenCommandLabelReached(Object)} was called on.
-         * @param label the application specific Object given by
-         *        {@link #notifyWhenCommandLabelReached(Object)}.
-         */
-        public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) { }
-
-        /**
-         * Called when when a player subtitle track has new subtitle data available.
-         * @param mp the player that reports the new subtitle data
-         * @param item the MediaItem of this media item
-         * @param track the track that has the subtitle data
-         * @param data the subtitle data
-         */
-        public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) { }
-
-        /**
-         * Called when the tracks of the current media item is changed such as
-         * 1) when tracks of a media item become available, or
-         * 2) when new tracks are found during playback.
-         * <p>
-         * When it's called, the previous tracks may be invalidated so it's recommended to use the
-         * most recent tracks to call {@link #selectTrack} or {@link #deselectTrack}.
-         *
-         * @param mp the player associated with this callback
-         * @param tracks the list of tracks. It can be empty.
-         */
-        public void onTracksChanged(@NonNull MediaPlayer2 mp,
-                @NonNull List<TrackInfo> tracks) { }
-    }
-
-    /**
-     * Sets the callback to be invoked when the media source is ready for playback.
-     *
-     * @param eventCallback the callback that will be run
-     * @param executor the executor through which the callback should be invoked
-     */
-    // This is a synchronous call.
-    public abstract void setEventCallback(
-            @NonNull Executor executor, @NonNull EventCallback eventCallback);
-
-    /**
-     * Clears the {@link EventCallback}.
-     */
-    // This is a synchronous call.
-    public abstract void clearEventCallback();
-
-    /**
-     * MediaPlayer2 has not been prepared or just has been reset.
-     * In this state, MediaPlayer2 doesn't fetch data.
-     */
-    public static final int PLAYER_STATE_IDLE = 1001;
-
-    /**
-     * MediaPlayer2 has been just prepared.
-     * In this state, MediaPlayer2 just fetches data from media source,
-     * but doesn't actively render data.
-     */
-    public static final int PLAYER_STATE_PREPARED = 1002;
-
-    /**
-     * MediaPlayer2 is paused.
-     * In this state, MediaPlayer2 doesn't actively render data.
-     */
-    public static final int PLAYER_STATE_PAUSED = 1003;
-
-    /**
-     * MediaPlayer2 is actively playing back data.
-     */
-    public static final int PLAYER_STATE_PLAYING = 1004;
-
-    /**
-     * MediaPlayer2 has hit some fatal error and cannot continue playback.
-     */
-    public static final int PLAYER_STATE_ERROR = 1005;
-
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, value = {
-            PLAYER_STATE_IDLE,
-            PLAYER_STATE_PREPARED,
-            PLAYER_STATE_PAUSED,
-            PLAYER_STATE_PLAYING,
-            PLAYER_STATE_ERROR})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaPlayer2State {}
-
-    /**
-     * Unspecified media player error.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_UNKNOWN = 1;
-
-    /**
-     * File or network related operation errors.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_IO = -1004;
-
-    /**
-     * Bitstream is not conforming to the related coding standard or file spec.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_MALFORMED = -1007;
-    /**
-     * Bitstream is conforming to the related coding standard or file spec, but
-     * the media framework does not support the feature.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_UNSUPPORTED = -1010;
-    /**
-     * Some operation takes too long to complete, usually more than 3-5 seconds.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_TIMED_OUT = -110;
-
-    /**
-     * Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
-     * system/core/include/utils/Errors.h
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_SYSTEM = -2147483648;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "MEDIA_ERROR",*/ value = {
-            MEDIA_ERROR_UNKNOWN,
-            MEDIA_ERROR_IO,
-            MEDIA_ERROR_MALFORMED,
-            MEDIA_ERROR_UNSUPPORTED,
-            MEDIA_ERROR_TIMED_OUT,
-            MEDIA_ERROR_SYSTEM
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaError {}
-
-    /**
-     * Unspecified media player info.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_UNKNOWN = 1;
-
-    /**
-     * The player just started the playback of this media item.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_START = 2;
-
-    /**
-     * The player just pushed the very first video frame for rendering.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
-
-    /**
-     * The player just rendered the very first audio sample.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
-
-    /**
-     * The player just completed the playback of this media item.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_END = 5;
-
-    /**
-     * The player just completed the playback of all the media items set by {@link #setMediaItem},
-     * {@link #setNextMediaItem} and {@link #setNextMediaItems}.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6;
-
-    /**
-     * The player just completed an iteration of playback loop. This event is sent only when
-     * looping is enabled by {@link #loopCurrent}.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7;
-
-    /**
-     * The player just prepared a media item.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_PREPARED = 100;
-
-    /**
-     * The video is too complex for the decoder: it can't decode frames fast
-     * enough. Possibly only the audio plays fine at this stage.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
-
-    /**
-     * MediaPlayer2 is temporarily pausing playback internally in order to
-     * buffer more data.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_START = 701;
-
-    /**
-     * MediaPlayer2 is resuming playback after filling buffers.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_END = 702;
-
-    /**
-     * Estimated network bandwidth information (kbps) is available; currently this event fires
-     * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
-     * when playing network files.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
-
-    /**
-     * Update status in buffering a media source received through progressive downloading.
-     * The received buffering percentage indicates how much of the content has been buffered
-     * or played. For example a buffering update of 80 percent when half the content
-     * has already been played indicates that the next 30 percent of the
-     * content to play has been buffered.
-     *
-     * The {@code extra} parameter in {@link EventCallback#onInfo} is the
-     * percentage (0-100) of the content that has been buffered or played thus far.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
-
-    /**
-     * Bad interleaving means that a media has been improperly interleaved or
-     * not interleaved at all, e.g has all the video samples first then all the
-     * audio ones. Video is playing but a lot of disk seeks may be happening.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
-
-    /**
-     * The media cannot be seeked (e.g live stream)
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
-
-    /**
-     * A new set of metadata is available.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_METADATA_UPDATE = 802;
-
-    /**
-     * A new set of external-only metadata is available.  Used by
-     * JAVA framework to avoid triggering track scanning.
-     */
-    public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803;
-
-    /**
-     * Informs that audio is not playing. Note that playback of the video
-     * is not interrupted.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
-
-    /**
-     * Informs that video is not playing. Note that playback of the audio
-     * is not interrupted.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
-
-    /**
-     * Subtitle track was not supported by the media framework.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
-
-    /**
-     * Reading the subtitle track takes too long.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "MEDIA_INFO",*/ value = {
-            MEDIA_INFO_UNKNOWN,
-            MEDIA_INFO_DATA_SOURCE_START,
-            MEDIA_INFO_VIDEO_RENDERING_START,
-            MEDIA_INFO_AUDIO_RENDERING_START,
-            MEDIA_INFO_DATA_SOURCE_END,
-            MEDIA_INFO_DATA_SOURCE_LIST_END,
-            MEDIA_INFO_DATA_SOURCE_REPEAT,
-            MEDIA_INFO_PREPARED,
-            MEDIA_INFO_VIDEO_TRACK_LAGGING,
-            MEDIA_INFO_BUFFERING_START,
-            MEDIA_INFO_BUFFERING_END,
-            MEDIA_INFO_NETWORK_BANDWIDTH,
-            MEDIA_INFO_BUFFERING_UPDATE,
-            MEDIA_INFO_BAD_INTERLEAVING,
-            MEDIA_INFO_NOT_SEEKABLE,
-            MEDIA_INFO_METADATA_UPDATE,
-            MEDIA_INFO_EXTERNAL_METADATA_UPDATE,
-            MEDIA_INFO_AUDIO_NOT_PLAYING,
-            MEDIA_INFO_VIDEO_NOT_PLAYING,
-            MEDIA_INFO_UNSUPPORTED_SUBTITLE,
-            MEDIA_INFO_SUBTITLE_TIMED_OUT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaInfo {}
-
-    //--------------------------------------------------------------------------
-    /**
-     * The player just completed a call {@link #attachAuxEffect}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1;
-
-    /**
-     * The player just completed a call {@link #deselectTrack}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_DESELECT_TRACK = 2;
-
-    /**
-     * The player just completed a call {@link #loopCurrent}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_LOOP_CURRENT = 3;
-
-    /**
-     * The player just completed a call {@link #pause}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PAUSE = 4;
-
-    /**
-     * The player just completed a call {@link #play}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PLAY = 5;
-
-    /**
-     * The player just completed a call {@link #prepare}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PREPARE = 6;
-
-    /**
-     * The player just completed a call {@link #seekTo}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SEEK_TO = 14;
-
-    /**
-     * The player just completed a call {@link #selectTrack}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SELECT_TRACK = 15;
-
-    /**
-     * The player just completed a call {@link #setAudioAttributes}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16;
-
-    /**
-     * The player just completed a call {@link #setAudioSessionId}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17;
-
-    /**
-     * The player just completed a call {@link #setAuxEffectSendLevel}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18;
-
-    /**
-     * The player just completed a call {@link #setMediaItem}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19;
-
-    /**
-     * The player just completed a call {@link #setNextMediaItem}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22;
-
-    /**
-     * The player just completed a call {@link #setNextMediaItems}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23;
-
-    /**
-     * The player just completed a call {@link #setPlaybackParams}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24;
-
-    /**
-     * The player just completed a call {@link #setPlayerVolume}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26;
-
-    /**
-     * The player just completed a call {@link #setSurface}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_SURFACE = 27;
-
-    /**
-     * The player just completed a call {@link #skipToNext}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
-
-    /**
-     * The start of the methods which have separate call complete callback.
-     */
-    public static final int SEPARATE_CALL_COMPLETE_CALLBACK_START = 1000;
-
-    /**
-     * The player just completed a call {@code notifyWhenCommandLabelReached}.
-     * @see EventCallback#onCommandLabelReached
-     */
-    public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
-            SEPARATE_CALL_COMPLETE_CALLBACK_START;
-
-    /**
-     * The player just completed a call {@link #prepareDrm}.
-     * @see EventCallback#onCommandLabelReached
-     */
-    public static final int CALL_COMPLETED_PREPARE_DRM =
-            SEPARATE_CALL_COMPLETE_CALLBACK_START + 1;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "CALL_COMPLETED",*/ value = {
-            CALL_COMPLETED_ATTACH_AUX_EFFECT,
-            CALL_COMPLETED_DESELECT_TRACK,
-            CALL_COMPLETED_LOOP_CURRENT,
-            CALL_COMPLETED_PAUSE,
-            CALL_COMPLETED_PLAY,
-            CALL_COMPLETED_PREPARE,
-            CALL_COMPLETED_SEEK_TO,
-            CALL_COMPLETED_SELECT_TRACK,
-            CALL_COMPLETED_SET_AUDIO_ATTRIBUTES,
-            CALL_COMPLETED_SET_AUDIO_SESSION_ID,
-            CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL,
-            CALL_COMPLETED_SET_DATA_SOURCE,
-            CALL_COMPLETED_SET_NEXT_DATA_SOURCE,
-            CALL_COMPLETED_SET_NEXT_DATA_SOURCES,
-            CALL_COMPLETED_SET_PLAYBACK_PARAMS,
-            CALL_COMPLETED_SET_PLAYER_VOLUME,
-            CALL_COMPLETED_SET_SURFACE,
-            CALL_COMPLETED_SKIP_TO_NEXT,
-            CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED,
-            CALL_COMPLETED_PREPARE_DRM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CallCompleted {}
-
-    /**
-     * Status code represents that call is completed without an error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_NO_ERROR = 0;
-
-    /**
-     * Status code represents that call is ended with an unknown error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE;
-
-    /**
-     * Status code represents that the player is not in valid state for the operation.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_INVALID_OPERATION = 1;
-
-    /**
-     * Status code represents that the argument is illegal.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_BAD_VALUE = 2;
-
-    /**
-     * Status code represents that the operation is not allowed.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_PERMISSION_DENIED = 3;
-
-    /**
-     * Status code represents a file or network related operation error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_ERROR_IO = 4;
-
-    /**
-     * Status code represents that the player skipped the call. For example, a {@link #seekTo}
-     * request may be skipped if it is followed by another {@link #seekTo} request.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_SKIPPED = 5;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "CALL_STATUS",*/ value = {
-            CALL_STATUS_NO_ERROR,
-            CALL_STATUS_ERROR_UNKNOWN,
-            CALL_STATUS_INVALID_OPERATION,
-            CALL_STATUS_BAD_VALUE,
-            CALL_STATUS_PERMISSION_DENIED,
-            CALL_STATUS_ERROR_IO,
-            CALL_STATUS_SKIPPED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CallStatus {}
-
-    // Modular DRM begin
-
-    /**
-     * Interface definition of a callback to be invoked when the app
-     * can do DRM configuration (get/set properties) before the session
-     * is opened. This facilitates configuration of the properties, like
-     * 'securityLevel', which has to be set after DRM scheme creation but
-     * before the DRM session is opened.
-     *
-     * The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
-     * and {@link #setDrmPropertyString}.
-     */
-    public interface OnDrmConfigHelper {
-        /**
-         * Called to give the app the opportunity to configure DRM before the session is created
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param item the MediaItem of this media item
-         */
-        void onDrmConfig(MediaPlayer2 mp, MediaItem item);
-    }
-
-    /**
-     * Register a callback to be invoked for configuration of the DRM object before
-     * the session is created.
-     * The callback will be invoked synchronously during the execution
-     * of {@link #prepareDrm(UUID uuid)}.
-     *
-     * @param listener the callback that will be run
-     */
-    // This is a synchronous call.
-    public abstract void setOnDrmConfigHelper(OnDrmConfigHelper listener);
-
-    /**
-     * Interface definition for callbacks to be invoked when the player has the corresponding
-     * DRM events.
-     */
-    public abstract static class DrmEventCallback {
-        /**
-         * Called to indicate DRM info is available
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param item the MediaItem of this media item
-         * @param drmInfo DRM info of the source including PSSH, and subset
-         *                of crypto schemes supported by this device
-         */
-        public void onDrmInfo(MediaPlayer2 mp, MediaItem item, DrmInfo drmInfo) { }
-
-        /**
-         * Called to notify the client that {@link #prepareDrm} is finished and ready for
-         * key request/response.
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param item the MediaItem of this media item
-         * @param status the result of DRM preparation.
-         */
-        public void onDrmPrepared(
-                MediaPlayer2 mp, MediaItem item, @PrepareDrmStatusCode int status) { }
-    }
-
-    /**
-     * Sets the callback to be invoked when the media source is ready for playback.
-     *
-     * @param eventCallback the callback that will be run
-     * @param executor the executor through which the callback should be invoked
-     */
-    // This is a synchronous call.
-    public abstract void setDrmEventCallback(@NonNull Executor executor,
-                                             @NonNull DrmEventCallback eventCallback);
-
-    /**
-     * Clears the {@link DrmEventCallback}.
-     */
-    // This is a synchronous call.
-    public abstract void clearDrmEventCallback();
-
-    /**
-     * The status codes for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * DRM preparation has succeeded.
-     */
-    public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
-
-    /**
-     * The device required DRM provisioning but couldn't reach the provisioning server.
-     */
-    public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
-
-    /**
-     * The device required DRM provisioning but the provisioning server denied the request.
-     */
-    public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
-
-    /**
-     * The DRM preparation has failed.
-     */
-    public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
-
-    /**
-     * The crypto scheme UUID that is not supported by the device.
-     */
-    public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4;
-
-    /**
-     * The hardware resources are not available, due to being in use.
-     */
-    public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
-
-    @RestrictTo(LIBRARY)
-    @IntDef(flag = false, /*prefix = "PREPARE_DRM_STATUS",*/ value = {
-            PREPARE_DRM_STATUS_SUCCESS,
-            PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
-            PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
-            PREPARE_DRM_STATUS_PREPARATION_ERROR,
-            PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
-            PREPARE_DRM_STATUS_RESOURCE_BUSY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PrepareDrmStatusCode {}
-
-    /**
-     * Retrieves the DRM Info associated with the current source
-     *
-     * @throws IllegalStateException if called before being prepared
-     */
-    public abstract DrmInfo getDrmInfo();
-
-    /**
-     * Prepares the DRM for the current source
-     * <p>
-     * If {@link OnDrmConfigHelper} is registered, it will be called during
-     * preparation to allow configuration of the DRM properties before opening the
-     * DRM session. Note that the callback is called synchronously in the thread that called
-     * {@link #prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
-     * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
-     * <p>
-     * If the device has not been provisioned before, this call also provisions the device
-     * which involves accessing the provisioning server and can take a variable time to
-     * complete depending on the network connectivity.
-     * prepareDrm() runs in non-blocking mode by launching the provisioning in the background and
-     * returning. {@link DrmEventCallback#onDrmPrepared} will be called when provisioning and
-     * preparation has finished. The application should check the status code returned with
-     * {@link DrmEventCallback#onDrmPrepared} to proceed.
-     * <p>
-     *
-     * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
-     * from the source through {@link #getDrmInfo} or registering
-     * {@link DrmEventCallback#onDrmInfo}.
-     * @return a token which can be used to cancel the operation later with {@link #cancel}.
-     */
-    // This is an asynchronous call.
-    public abstract Object prepareDrm(@NonNull UUID uuid);
-
-    /**
-     * Releases the DRM session
-     * <p>
-     * The player has to have an active DRM session and be in stopped, or prepared
-     * state before this call is made.
-     * A {@code reset()} call will release the DRM session implicitly.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session to release
-     */
-    // This is an asynchronous call.
-    public abstract void releaseDrm() throws NoDrmSchemeException;
-
-    /**
-     * A key request/response exchange occurs between the app and a license server
-     * to obtain or release keys used to decrypt encrypted content.
-     * <p>
-     * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
-     * delivered to the license server.  The opaque key request byte array is returned
-     * in KeyRequest.data.  The recommended URL to deliver the key request to is
-     * returned in KeyRequest.defaultUrl.
-     * <p>
-     * After the app has received the key request response from the server,
-     * it should deliver to the response to the DRM engine plugin using the method
-     * {@link #provideDrmKeyResponse}.
-     *
-     * @param keySetId is the key-set identifier of the offline keys being released when keyType is
-     * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
-     * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
-     *
-     * @param initData is the container-specific initialization data when the keyType is
-     * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
-     * interpreted based on the mime type provided in the mimeType parameter.  It could
-     * contain, for example, the content ID, key ID or other data obtained from the content
-     * metadata that is required in generating the key request.
-     * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
-     *
-     * @param mimeType identifies the mime type of the content
-     *
-     * @param keyType specifies the type of the request. The request may be to acquire
-     * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
-     * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
-     * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
-     *
-     * @param optionalParameters are included in the key request message to
-     * allow a client application to provide additional message parameters to the server.
-     * This may be {@code null} if no additional parameters are to be sent.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     */
-    @NonNull
-    public abstract MediaDrm.KeyRequest getDrmKeyRequest(
-            @Nullable byte[] keySetId, @Nullable byte[] initData,
-            @Nullable String mimeType, int keyType,
-            @Nullable Map<String, String> optionalParameters)
-            throws NoDrmSchemeException;
-
-    /**
-     * A key response is received from the license server by the app, then it is
-     * provided to the DRM engine plugin using provideDrmKeyResponse. When the
-     * response is for an offline key request, a key-set identifier is returned that
-     * can be used to later restore the keys to a new session with the method
-     * {@link #restoreDrmKeys}.
-     * When the response is for a streaming or release request, null is returned.
-     *
-     * @param keySetId When the response is for a release request, keySetId identifies
-     * the saved key associated with the release request (i.e., the same keySetId
-     * passed to the earlier {@link #getDrmKeyRequest} call. It MUST be null when the
-     * response is for either streaming or offline key requests.
-     *
-     * @param response the byte array response from the server
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @throws DeniedByServerException if the response indicates that the
-     * server rejected the request
-     */
-    // This is a synchronous call.
-    public abstract byte[] provideDrmKeyResponse(
-            @Nullable byte[] keySetId, @NonNull byte[] response)
-            throws NoDrmSchemeException, DeniedByServerException;
-
-    /**
-     * Restore persisted offline keys into a new session.  keySetId identifies the
-     * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
-     *
-     * @param keySetId identifies the saved key set to restore
-     */
-    // This is an asynchronous call.
-    public abstract void restoreDrmKeys(@NonNull byte[] keySetId)
-            throws NoDrmSchemeException;
-
-    /**
-     * Read a DRM engine plugin String property value, given the property name string.
-     * <p>
-     * @param propertyName the property name
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    @NonNull
-    public abstract String getDrmPropertyString(
-            @NonNull String propertyName)
-            throws NoDrmSchemeException;
-
-    /**
-     * Set a DRM engine plugin String property value.
-     * <p>
-     * @param propertyName the property name
-     * @param value the property value
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    // This is a synchronous call.
-    public abstract void setDrmPropertyString(
-            @NonNull String propertyName, @NonNull String value)
-            throws NoDrmSchemeException;
-
-    /**
-     * Encapsulates the DRM properties of the source.
-     */
-    public abstract static class DrmInfo {
-        /**
-         * Returns the PSSH info of the media item for each supported DRM scheme.
-         */
-        public abstract Map<UUID, byte[]> getPssh();
-
-        /**
-         * Returns the intersection of the media item and the device DRM schemes.
-         * It effectively identifies the subset of the source's DRM schemes which
-         * are supported by the device too.
-         */
-        public abstract List<UUID> getSupportedSchemes();
-    };  // DrmInfo
-
-    /**
-     * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
-     * Extends MediaDrm.MediaDrmException
-     */
-    /* package */ static class NoDrmSchemeException extends Exception {
-        /* package */ NoDrmSchemeException(String detailMessage) {
-            super(detailMessage);
-        }
-    }
-
-    /**
-     * Definitions for the metrics that are reported via the {@link #getMetrics} call.
-     */
-    public static final class MetricsConstants {
-        private MetricsConstants() {}
-
-        /**
-         * Key to extract the MIME type of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-
-        /**
-         * Key to extract the codec being used to decode the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-
-        /**
-         * Key to extract the width (in pixels) of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String WIDTH = "android.media.mediaplayer.width";
-
-        /**
-         * Key to extract the height (in pixels) of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String HEIGHT = "android.media.mediaplayer.height";
-
-        /**
-         * Key to extract the count of video frames played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES = "android.media.mediaplayer.frames";
-
-        /**
-         * Key to extract the count of video frames dropped
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-
-        /**
-         * Key to extract the MIME type of the audio track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-
-        /**
-         * Key to extract the codec being used to decode the audio track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-
-        /**
-         * Key to extract the duration (in milliseconds) of the
-         * media being played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String DURATION = "android.media.mediaplayer.durationMs";
-
-        /**
-         * Key to extract the playing time (in milliseconds) of the
-         * media being played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String PLAYING = "android.media.mediaplayer.playingMs";
-
-        /**
-         * Key to extract the count of errors encountered while
-         * playing the media
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERRORS = "android.media.mediaplayer.err";
-
-        /**
-         * Key to extract an (optional) error code detected while
-         * playing the media
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/MediaTimestamp.java b/media2/media2-player/src/main/java/androidx/media2/player/MediaTimestamp.java
deleted file mode 100644
index f3485fd..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/MediaTimestamp.java
+++ /dev/null
@@ -1,118 +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.media2.player;
-
-import androidx.annotation.NonNull;
-
-/**
- * An immutable object that represents the linear correlation between the media time and the system
- * time. It contains the media clock rate, together with the media timestamp of an anchor frame and
- * the system time when that frame was presented or is committed to be presented.
- *
- * <p>The phrase "present" means that audio/video produced on device is detectable by an external
- * observer off device. The time is based on the implementation's best effort, using whatever
- * knowledge is available to the system, but cannot account for any delay unknown to the
- * implementation. The anchor frame could be any frame, including a just-rendered frame, or even a
- * theoretical or in-between frame, based on the source of the MediaTimestamp. When the anchor frame
- * is a just-rendered one, the media time stands for current position of the playback or recording.
- *
- * @see MediaPlayer#getTimestamp
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public final class MediaTimestamp {
-    /**
-     * An unknown media timestamp value
-     */
-    public static @NonNull final MediaTimestamp TIMESTAMP_UNKNOWN =
-            new MediaTimestamp(-1, -1, 0.0f);
-
-    /**
-     * Get the media time of the anchor in microseconds.
-     */
-    public long getAnchorMediaTimeUs() {
-        return mMediaTimeUs;
-    }
-
-    /**
-     * Get the {@link java.lang.System#nanoTime system time} corresponding to the media time
-     * in nanoseconds.
-     */
-    public long getAnchorSystemNanoTime() {
-        return mNanoTime;
-    }
-
-    /**
-     * Get the rate of the media clock in relation to the system time.
-     * <p>
-     * It is 1.0 if media clock advances in sync with the system clock;
-     * greater than 1.0 if media clock is faster than the system clock;
-     * less than 1.0 if media clock is slower than the system clock.
-     */
-    public float getMediaClockRate() {
-        return mClockRate;
-    }
-
-    private final long mMediaTimeUs;
-    private final long mNanoTime;
-    private final float mClockRate;
-
-    /* package */ MediaTimestamp(long mediaUs, long systemNs, float rate) {
-        mMediaTimeUs = mediaUs;
-        mNanoTime = systemNs;
-        mClockRate = rate;
-    }
-
-    /* package */ MediaTimestamp() {
-        mMediaTimeUs = 0;
-        mNanoTime = 0;
-        mClockRate = 1.0f;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        final MediaTimestamp that = (MediaTimestamp) obj;
-        return (this.mMediaTimeUs == that.mMediaTimeUs)
-                && (this.mNanoTime == that.mNanoTime)
-                && (this.mClockRate == that.mClockRate);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = Long.valueOf(mMediaTimeUs).hashCode();
-        result = (int) (31 * result + mNanoTime);
-        result = (int) (31 * result + mClockRate);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName()
-                + "{AnchorMediaTimeUs=" + mMediaTimeUs
-                + " AnchorSystemNanoTime=" + mNanoTime
-                + " ClockRate=" + mClockRate
-                + "}";
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/PersistableBundleHelper.java b/media2/media2-player/src/main/java/androidx/media2/player/PersistableBundleHelper.java
deleted file mode 100644
index ad28c6f..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/PersistableBundleHelper.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 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.media2.player;
-
-import android.os.PersistableBundle;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.RequiresApi;
-
-final class PersistableBundleHelper {
-
-    @RequiresApi(21)
-    static final class Api21Impl {
-
-        @DoNotInline
-        static PersistableBundle createInstance() {
-            return new PersistableBundle();
-        }
-
-        @DoNotInline
-        static void putLong(PersistableBundle bundle, String key, Long value) {
-            bundle.putLong(key, value);
-        }
-
-        @DoNotInline
-        static void putString(PersistableBundle bundle, String key, String value) {
-            bundle.putString(key, value);
-        }
-
-        private Api21Impl() {}
-    }
-
-    private PersistableBundleHelper() {}
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/PlaybackParams.java b/media2/media2-player/src/main/java/androidx/media2/player/PlaybackParams.java
deleted file mode 100644
index b17f688..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/PlaybackParams.java
+++ /dev/null
@@ -1,339 +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.media2.player;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.media.AudioTrack;
-import android.os.Build;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.FloatRange;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Structure for common playback params.
- *
- * <p>Used by {@link MediaPlayer} {@link MediaPlayer#getPlaybackParams()} and {@link
- * MediaPlayer#setPlaybackParams(PlaybackParams)} to control playback behavior.
- *
- * <p>PlaybackParams returned by {@link MediaPlayer#getPlaybackParams()} will always have values. In
- * case of {@link MediaPlayer#setPlaybackParams}, the player will not update the param if the value
- * is not set. For example, if pitch is set while speed is not set, only pitch will be updated.
- *
- * <p>Note that the speed value does not change the player state. For example, if {@link
- * MediaPlayer#getPlaybackParams()} is called with the speed of 2.0f in {@link
- * MediaPlayer#PLAYER_STATE_PAUSED}, the player will just update internal property and stay paused.
- * Once {@link MediaPlayer#play()} is called afterwards, the player will start playback with the
- * given speed. Calling this with zero speed is not allowed.
- *
- * <p><strong>audio fallback mode:</strong> select out-of-range parameter handling.
- *
- * <ul>
- *   <li>{@link PlaybackParams#AUDIO_FALLBACK_MODE_DEFAULT}: System will determine best handling.
- *   <li>{@link PlaybackParams#AUDIO_FALLBACK_MODE_MUTE}: Play silence for params normally out of
- *       range.
- *   <li>{@link PlaybackParams#AUDIO_FALLBACK_MODE_FAIL}: Return {@link
- *       java.lang.IllegalArgumentException} from <code>AudioTrack.setPlaybackParams(PlaybackParams)
- *       </code>.
- * </ul>
- *
- * <p><strong>pitch:</strong> increases or decreases the tonal frequency of the audio content. It is
- * expressed as a multiplicative factor, where normal pitch is 1.0f.
- *
- * <p><strong>speed:</strong> increases or decreases the time to play back a set of audio or video
- * frames. It is expressed as a multiplicative factor, where normal speed is 1.0f.
- *
- * <p>Different combinations of speed and pitch may be used for audio playback; some common ones:
- *
- * <ul>
- *   <li><em>Pitch equals 1.0f.</em> Speed change will be done with pitch preserved, often called
- *       <em>timestretching</em>.
- *   <li><em>Pitch equals speed.</em> Speed change will be done by <em>resampling</em>, similar to
- *       {@link AudioTrack#setPlaybackRate(int)}.
- * </ul>
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public final class PlaybackParams {
-    @RestrictTo(LIBRARY)
-    @IntDef(
-            value = {
-                    AUDIO_FALLBACK_MODE_DEFAULT,
-                    AUDIO_FALLBACK_MODE_MUTE,
-                    AUDIO_FALLBACK_MODE_FAIL,
-            }
-    )
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AudioFallbackMode {}
-    public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0;
-    public static final int AUDIO_FALLBACK_MODE_MUTE = 1;
-    public static final int AUDIO_FALLBACK_MODE_FAIL = 2;
-
-    // params
-    private Integer mAudioFallbackMode;
-    private Float mPitch;
-    private Float mSpeed;
-    private android.media.PlaybackParams mPlaybackParams;
-
-    PlaybackParams(Integer audioFallbackMode, Float pitch, Float speed) {
-        mAudioFallbackMode = audioFallbackMode;
-        mPitch = pitch;
-        mSpeed = speed;
-    }
-
-    @RequiresApi(23)
-    PlaybackParams(android.media.PlaybackParams playbackParams) {
-        mPlaybackParams = playbackParams;
-    }
-
-    /**
-     * Returns the audio fallback mode. {@code null} if a value is not set.
-     */
-    public @AudioFallbackMode @Nullable Integer getAudioFallbackMode() {
-        if (Build.VERSION.SDK_INT >= 23) {
-            try {
-                return Api23Impl.getAudioFallbackMode(mPlaybackParams);
-            } catch (IllegalStateException e) {
-                return null;
-            }
-        } else {
-            return mAudioFallbackMode;
-        }
-    }
-
-    /**
-     * Returns the pitch factor. {@code null} if a value is not set.
-     */
-    public @Nullable Float getPitch() {
-        if (Build.VERSION.SDK_INT >= 23) {
-            try {
-                return Api23Impl.getPitch(mPlaybackParams);
-            } catch (IllegalStateException e) {
-                return null;
-            }
-        } else {
-            return mPitch;
-        }
-    }
-
-    /**
-     * Returns the speed factor. {@code null} if a value is not set.
-     */
-    public @Nullable Float getSpeed() {
-        if (Build.VERSION.SDK_INT >= 23) {
-            try {
-                return Api23Impl.getSpeed(mPlaybackParams);
-            } catch (IllegalStateException e) {
-                return null;
-            }
-        } else {
-            return mSpeed;
-        }
-    }
-
-    /**
-     * Returns the underlying framework {@link android.media.PlaybackParams} object. {@code null}
-     * if it is not available.
-     * <p>
-     * This method is only supported on {@link android.os.Build.VERSION_CODES#M} and later.
-     * </p>
-     *
-     */
-    @RestrictTo(LIBRARY)
-    @RequiresApi(23)
-    public android.media.PlaybackParams getPlaybackParams() {
-        if (Build.VERSION.SDK_INT >= 23) {
-            return mPlaybackParams;
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * The builder class that makes it easy to chain setters to create a {@link PlaybackParams}
-     * object.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder {
-        private Integer mAudioFallbackMode;
-        private Float mPitch;
-        private Float mSpeed;
-        private android.media.PlaybackParams mPlaybackParams;
-
-        /**
-         * Default constructor
-         */
-        public Builder() {
-            if (Build.VERSION.SDK_INT >= 23) {
-                mPlaybackParams = Api23Impl.createPlaybackParams();
-            }
-        }
-
-        @RestrictTo(LIBRARY)
-        @RequiresApi(23)
-        public Builder(android.media.PlaybackParams playbackParams) {
-            mPlaybackParams = playbackParams;
-        }
-
-        /**
-         * Constructs a new PlaybackParams builder using data from {@code playbackParams}.
-         *
-         * @param playbackParams the non-null instance to initialize from.
-         */
-        public Builder(@NonNull PlaybackParams playbackParams) {
-            if (playbackParams == null) {
-                throw new NullPointerException("playbakcParams shouldn't be null");
-            }
-            if (Build.VERSION.SDK_INT >= 23) {
-                mPlaybackParams = playbackParams.getPlaybackParams();
-            } else {
-                mAudioFallbackMode = playbackParams.getAudioFallbackMode();
-                mPitch = playbackParams.getPitch();
-                mSpeed = playbackParams.getSpeed();
-            }
-        }
-
-        /**
-         * Sets the audio fallback mode.
-         *
-         * @return this <code>Builder</code> instance.
-         */
-        public @NonNull Builder setAudioFallbackMode(@AudioFallbackMode int audioFallbackMode) {
-            if (Build.VERSION.SDK_INT >= 23) {
-                Api23Impl.setAudioFallbackMode(mPlaybackParams, audioFallbackMode);
-            } else {
-                mAudioFallbackMode = audioFallbackMode;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the pitch factor.
-         *
-         * @return this <code>Builder</code> instance.
-         * @throws IllegalArgumentException if the pitch is negative or zero.
-         */
-        public @NonNull Builder setPitch(
-                @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false) float pitch) {
-            if (pitch == 0.f) {
-                throw new IllegalArgumentException("0 pitch is not allowed");
-            }
-            if (pitch < 0.f) {
-                throw new IllegalArgumentException("pitch must not be negative");
-            }
-            if (Build.VERSION.SDK_INT >= 23) {
-                Api23Impl.setPitch(mPlaybackParams, pitch);
-            } else {
-                mPitch = pitch;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the speed factor.
-         *
-         * @return this <code>Builder</code> instance.
-         * @throws IllegalArgumentException if the speed is negative or zero.
-         */
-        public @NonNull Builder setSpeed(
-                @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false) float speed) {
-            if (speed == 0.f) {
-                throw new IllegalArgumentException("0 speed is not allowed");
-            }
-            if (speed < 0.f) {
-                throw new IllegalArgumentException("negative speed is not supported");
-            }
-            if (Build.VERSION.SDK_INT >= 23) {
-                Api23Impl.setSpeed(mPlaybackParams, speed);
-            } else {
-                mSpeed = speed;
-            }
-            return this;
-        }
-
-        /**
-         * Takes the values of the Builder object and creates a PlaybackParams object.
-         *
-         * @return PlaybackParams object with values from the Builder.
-         */
-        public @NonNull PlaybackParams build() {
-            if (Build.VERSION.SDK_INT >= 23) {
-                return new PlaybackParams(mPlaybackParams);
-            } else {
-                return new PlaybackParams(mAudioFallbackMode, mPitch, mSpeed);
-            }
-        }
-    }
-
-    @RequiresApi(23)
-    static class Api23Impl {
-
-        @DoNotInline
-        static android.media.PlaybackParams createPlaybackParams() {
-            return new android.media.PlaybackParams();
-        }
-
-        @DoNotInline
-        static int getAudioFallbackMode(android.media.PlaybackParams playbackParams) {
-            return playbackParams.getAudioFallbackMode();
-        }
-
-        @DoNotInline
-        static float getPitch(android.media.PlaybackParams playbackParams) {
-            return playbackParams.getPitch();
-        }
-
-        @DoNotInline
-        static float getSpeed(android.media.PlaybackParams playbackParams) {
-            return playbackParams.getSpeed();
-        }
-
-        @DoNotInline
-        static android.media.PlaybackParams setAudioFallbackMode(
-                android.media.PlaybackParams playbackParams, int audioFallbackMode) {
-            return playbackParams.setAudioFallbackMode(audioFallbackMode);
-        }
-
-
-        @DoNotInline
-        static android.media.PlaybackParams setPitch(android.media.PlaybackParams playbackParams,
-                float pitch) {
-            return playbackParams.setPitch(pitch);
-        }
-
-        @DoNotInline
-        static android.media.PlaybackParams setSpeed(android.media.PlaybackParams playbackParams,
-                float speed) {
-            return playbackParams.setSpeed(speed);
-        }
-
-        private Api23Impl() {}
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/RenderersFactory.java b/media2/media2-player/src/main/java/androidx/media2/player/RenderersFactory.java
deleted file mode 100644
index 8a7e976..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/RenderersFactory.java
+++ /dev/null
@@ -1,98 +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.media2.player;
-
-import android.content.Context;
-import android.os.Handler;
-
-import androidx.annotation.Nullable;
-import androidx.media2.exoplayer.external.Renderer;
-import androidx.media2.exoplayer.external.audio.AudioRendererEventListener;
-import androidx.media2.exoplayer.external.audio.AudioSink;
-import androidx.media2.exoplayer.external.audio.MediaCodecAudioRenderer;
-import androidx.media2.exoplayer.external.drm.DrmSessionManager;
-import androidx.media2.exoplayer.external.drm.FrameworkMediaCrypto;
-import androidx.media2.exoplayer.external.mediacodec.MediaCodecSelector;
-import androidx.media2.exoplayer.external.metadata.MetadataOutput;
-import androidx.media2.exoplayer.external.metadata.MetadataRenderer;
-import androidx.media2.exoplayer.external.text.TextOutput;
-import androidx.media2.exoplayer.external.video.MediaCodecVideoRenderer;
-import androidx.media2.exoplayer.external.video.VideoRendererEventListener;
-
-/**
- * Factory for renderers for {@link ExoPlayerWrapper}.
- */
-/* package */ final class RenderersFactory
-        implements androidx.media2.exoplayer.external.RenderersFactory {
-
-    public static final int VIDEO_RENDERER_INDEX = 0;
-    public static final int AUDIO_RENDERER_INDEX = 1;
-    public static final int TEXT_RENDERER_INDEX = 2;
-    public static final int METADATA_RENDERER_INDEX = 3;
-
-    private static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000;
-    private static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
-
-    private final Context mContext;
-    private final AudioSink mAudioSink;
-    private final TextRenderer mTextRenderer;
-
-    RenderersFactory(
-            Context context,
-            AudioSink audioSink,
-            TextRenderer textRenderer) {
-        mContext = context;
-        mAudioSink = audioSink;
-        mTextRenderer = textRenderer;
-    }
-
-    @Override
-    public Renderer[] createRenderers(
-            Handler eventHandler,
-            VideoRendererEventListener videoRendererEventListener,
-            AudioRendererEventListener audioRendererEventListener,
-            TextOutput textRendererOutput,
-            MetadataOutput metadataRendererOutput,
-            @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
-        return new Renderer[] {
-                new MediaCodecVideoRenderer(
-                        mContext,
-                        MediaCodecSelector.DEFAULT,
-                        DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS,
-                        drmSessionManager,
-                        /* playClearSamplesWithoutKeys= */ false,
-                        eventHandler,
-                        videoRendererEventListener,
-                        MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY),
-                new MediaCodecAudioRenderer(
-                        mContext,
-                        MediaCodecSelector.DEFAULT,
-                        drmSessionManager,
-                        /* playClearSamplesWithoutKeys= */ false,
-                        eventHandler,
-                        audioRendererEventListener,
-                        mAudioSink),
-                mTextRenderer,
-                new MetadataRenderer(
-                        metadataRendererOutput,
-                        eventHandler.getLooper(),
-                        new Id3MetadataDecoderFactory())
-        };
-    }
-
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/TextRenderer.java b/media2/media2-player/src/main/java/androidx/media2/player/TextRenderer.java
deleted file mode 100644
index 91cb1b0..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/TextRenderer.java
+++ /dev/null
@@ -1,360 +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.media2.player;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.annotation.IntDef;
-import androidx.core.util.Preconditions;
-import androidx.media2.exoplayer.external.BaseRenderer;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.ExoPlaybackException;
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.FormatHolder;
-import androidx.media2.exoplayer.external.text.SubtitleInputBuffer;
-import androidx.media2.exoplayer.external.util.MimeTypes;
-import androidx.media2.exoplayer.external.util.ParsableByteArray;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Outputs encoded text data from a selected text type and channel.
- *
- * <p>The decoding process implemented here should match NuPlayer2CCDecoder.cpp in the framework.
- */
-/* package */ class TextRenderer extends BaseRenderer {
-
-    /** Interface for text renderer outputs. */
-    public interface Output {
-        /** Called when a channel becomes available for selection via {@link #select(int, int)}. */
-        void onChannelAvailable(int type, int channel);
-        /** Called when a buffer of text data is output. */
-        void onCcData(byte[] data, long timeUs);
-    }
-
-    @IntDef(/*prefix = "TRACK_TYPE",*/ value = {
-            TRACK_TYPE_CEA608,
-            TRACK_TYPE_CEA708,
-            TRACK_TYPE_WEBVTT,
-            TRACK_TYPE_UNSET,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TextTrackType {}
-    public static final int TRACK_TYPE_CEA608 = 0;
-    public static final int TRACK_TYPE_CEA708 = 1;
-    public static final int TRACK_TYPE_WEBVTT = 2;
-    public static final int TRACK_TYPE_UNSET = -1;
-
-    /**
-     * The maximum time read ahead distance in microseconds. This matches the early buffer threshold
-     * in ExoPlayer's video renderer.
-     */
-    private static final int READ_AHEAD_THRESHOLD_US = 110000;
-    private static final int PACKET_LENGTH = 3;
-    private static final int CHANNEL_UNSET = -1;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Output mOutput;
-    private final Handler mHandler;
-    private final ParsableByteArray mCcData;
-    private final SortedMap<Long, byte[]> mCcMap;
-    private final FormatHolder mFormatHolder;
-    private final SubtitleInputBuffer mInputBuffer;
-    private final DataBuilder mLine21DataBuilder;
-    private final DataBuilder mDtvDataBuilder;
-    private final int[] mLine21Channels;
-    private final ParsableByteArray mScratch;
-
-    private boolean mHasPendingInputBuffer;
-    private boolean mInputStreamEnded;
-    private boolean[] mIsTypeAndChannelAdvertised;
-    @TextTrackType
-    private int mSelectedType;
-    private int mSelectedChannel;
-
-    TextRenderer(Output output) {
-        super(C.TRACK_TYPE_TEXT);
-        mOutput = output;
-        mHandler = new Handler(Looper.myLooper());
-        mCcData = new ParsableByteArray();
-        mCcMap = new TreeMap<>();
-        mFormatHolder = new FormatHolder();
-        mInputBuffer = new SubtitleInputBuffer();
-        mLine21DataBuilder = new DataBuilder();
-        mDtvDataBuilder = new DataBuilder();
-        mLine21Channels = new int[2];
-        mScratch = new ParsableByteArray();
-        mSelectedType = TRACK_TYPE_UNSET;
-        mSelectedChannel = CHANNEL_UNSET;
-    }
-
-    // BaseRenderer implementation
-
-    @Override
-    public int supportsFormat(Format format) {
-        String mimeType = format.sampleMimeType;
-        if (MimeTypes.APPLICATION_CEA608.equals(mimeType)
-                || MimeTypes.APPLICATION_CEA708.equals(mimeType)
-                || MimeTypes.TEXT_VTT.equals(mimeType)) {
-            return FORMAT_HANDLED;
-        } else {
-            return FORMAT_UNSUPPORTED_TYPE;
-        }
-    }
-
-    @Override
-    protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
-        super.onStreamChanged(formats, offsetUs);
-        mIsTypeAndChannelAdvertised = new boolean[128];
-    }
-
-    @Override
-    protected synchronized void onPositionReset(long positionUs, boolean joining) {
-        flush();
-    }
-
-    @Override
-    public synchronized void render(long positionUs, long elapsedRealtimeUs) {
-        if (getState() != STATE_STARTED) {
-            return;
-        }
-
-        // Display any pending subtitles.
-        display(positionUs);
-
-        // Get an input buffer to parse.
-        if (!mHasPendingInputBuffer) {
-            // Try to read more subtitles from the source.
-            mInputBuffer.clear();
-            int result = readSource(mFormatHolder, mInputBuffer, /* formatRequired= */ false);
-            if (result == C.RESULT_NOTHING_READ || result == C.RESULT_FORMAT_READ) {
-                return;
-            }
-            if (mInputBuffer.isEndOfStream()) {
-                mInputStreamEnded = true;
-                return;
-            }
-            mHasPendingInputBuffer = true;
-            mInputBuffer.flip();
-        }
-        if (mInputBuffer.timeUs - positionUs > READ_AHEAD_THRESHOLD_US) {
-            // We aren't ready to parse this buffer yet.
-            return;
-        }
-        mHasPendingInputBuffer = false;
-        mCcData.reset(mInputBuffer.data.array(), mInputBuffer.data.limit());
-        mLine21DataBuilder.clear();
-        while (mCcData.bytesLeft() >= PACKET_LENGTH) {
-            byte ccDataHeader = (byte) mCcData.readUnsignedByte();
-            byte ccData1 = (byte) mCcData.readUnsignedByte();
-            byte ccData2 = (byte) mCcData.readUnsignedByte();
-
-            boolean ccValid = (ccDataHeader & 0x04) != 0;
-            int ccType = ccDataHeader & 0x03;
-            if (ccValid) {
-                if (ccType == 3) {
-                    if (mDtvDataBuilder.hasData()) {
-                        handleDtvPacket(mDtvDataBuilder, mInputBuffer.timeUs);
-                    }
-                    mDtvDataBuilder.append(ccData1, ccData2);
-                } else if (mDtvDataBuilder.mLength > 0 && ccType == 2) {
-                    mDtvDataBuilder.append(ccData1, ccData2);
-                } else if (ccType == 0 || ccType == 1) {
-                    ccData1 = (byte) (ccData1 & 0x7F);
-                    ccData2 = (byte) (ccData2 & 0x7F);
-                    if (ccData1 < 0x10 && ccData2 < 0x10) {
-                        // Null padding.
-                        continue;
-                    }
-                    if (ccData1 >= 0x10 && ccData1 <= 0x1F) {
-                        int channel = (ccData1 >= 0x18 ? 1 : 0) + (ccDataHeader != 0 ? 2 : 0);
-                        mLine21Channels[ccType] = channel;
-                        maybeAdvertiseChannel(TRACK_TYPE_CEA608, channel);
-                    }
-                    if (mSelectedType == TRACK_TYPE_CEA608
-                            && mSelectedChannel == mLine21Channels[ccType]) {
-                        mLine21DataBuilder.append((byte) ccType, ccData1, ccData2);
-                    }
-                }
-            } else if ((ccType == 3 || ccType == 2) && mDtvDataBuilder.hasData()) {
-                handleDtvPacket(mDtvDataBuilder, mInputBuffer.timeUs);
-            }
-        }
-
-        if (mSelectedType == TRACK_TYPE_CEA608 && mLine21DataBuilder.hasData()) {
-            handleLine21Packet(mLine21DataBuilder, mInputBuffer.timeUs);
-        }
-    }
-
-    @Override
-    public boolean isEnded() {
-        return mInputStreamEnded && mCcMap.isEmpty();
-    }
-
-    @Override
-    public boolean isReady() {
-        // Don't block playback whilst subtitles are loading.
-        // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941].
-        return true;
-    }
-
-    // Track selection.
-
-    /** Clears any previous selection. */
-    public synchronized void clearSelection() {
-        select(TRACK_TYPE_UNSET, CHANNEL_UNSET);
-    }
-
-    /** Selects the specified track type/channel for extraction and rendering. */
-    public synchronized void select(@TextTrackType int type, int channel) {
-        mSelectedType = type;
-        mSelectedChannel = channel;
-        flush();
-    }
-
-    // Internal methods.
-
-    private void flush() {
-        mCcMap.clear();
-        mLine21DataBuilder.clear();
-        mDtvDataBuilder.clear();
-        mInputStreamEnded = false;
-        mHasPendingInputBuffer = false;
-    }
-
-    private void maybeAdvertiseChannel(final int type, final int channel) {
-        int typeAndChannel = (type << 6) + channel;
-        if (!mIsTypeAndChannelAdvertised[typeAndChannel]) {
-            mIsTypeAndChannelAdvertised[typeAndChannel] = true;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mOutput.onChannelAvailable(type, channel);
-                }
-            });
-        }
-    }
-
-    private void handleDtvPacket(DataBuilder dataBuilder, long timeUs) {
-        mScratch.reset(dataBuilder.mData, dataBuilder.mLength);
-        dataBuilder.clear();
-        int size = mScratch.readUnsignedByte() & 0x1F;
-        if (size == 0) {
-            size = 64;
-        }
-        if (mScratch.limit() != size * 2) {
-            return;
-        }
-        while (mScratch.bytesLeft() >= 2) {
-            int value = mScratch.readUnsignedByte();
-            int serviceNumber = (value & 0xE0) >> 5;
-            int blockSize = (value & 0x1F);
-            if (serviceNumber == 7) {
-                serviceNumber = mScratch.readUnsignedByte() & 0x3F;
-                if (serviceNumber < 7) {
-                    return;
-                }
-            }
-            if (mScratch.bytesLeft() < blockSize) {
-                return;
-            }
-            if (blockSize > 0) {
-                maybeAdvertiseChannel(TRACK_TYPE_CEA708, serviceNumber);
-                if (mSelectedType == TRACK_TYPE_CEA708 && mSelectedChannel == serviceNumber) {
-                    byte[] data = new byte[blockSize];
-                    mScratch.readBytes(data, 0, blockSize);
-                    mCcMap.put(timeUs, data);
-                    continue;
-                }
-                mScratch.skipBytes(blockSize);
-            }
-        }
-    }
-
-    private void handleLine21Packet(DataBuilder dataBuilder, long timeUs) {
-        mCcMap.put(timeUs, Arrays.copyOf(dataBuilder.mData, dataBuilder.mLength));
-        dataBuilder.clear();
-    }
-
-    private void display(long timeUs) {
-        if (mSelectedType == TRACK_TYPE_UNSET || mSelectedChannel == CHANNEL_UNSET) {
-            // Nothing is selected for output.
-            return;
-        }
-        byte[] data = new byte[0];
-        long displayTimeUs = C.TIME_UNSET;
-        while (!mCcMap.isEmpty()) {
-            long ccTimeUs = mCcMap.firstKey();
-            if (timeUs < ccTimeUs) {
-                break;
-            }
-            byte[] ccData = Preconditions.checkNotNull(mCcMap.get(ccTimeUs));
-            displayTimeUs = ccTimeUs;
-            int offset = data.length;
-            data = Arrays.copyOf(data, offset + ccData.length);
-            System.arraycopy(ccData, 0, data, offset, ccData.length);
-            mCcMap.remove(mCcMap.firstKey());
-        }
-        if (data.length > 0) {
-            mOutput.onCcData(data, displayTimeUs);
-        }
-    }
-
-    /** Utility for building a byte array by appending. */
-    private static final class DataBuilder {
-
-        public byte[] mData;
-        public int mLength;
-
-        DataBuilder() {
-            mData = new byte[PACKET_LENGTH];
-        }
-
-        public void append(byte cc0, byte cc1) {
-            if (mLength + 2 > mData.length) {
-                // Double the size each time we run out of space to avoid frequent size increases.
-                mData = Arrays.copyOf(mData, mData.length * 2);
-            }
-            mData[mLength++] = cc0;
-            mData[mLength++] = cc1;
-        }
-
-        public void append(byte cc0, byte cc1, byte cc2) {
-            if (mLength + 3 > mData.length) {
-                mData = Arrays.copyOf(mData, mData.length * 2);
-            }
-            mData[mLength++] = cc0;
-            mData[mLength++] = cc1;
-            mData[mLength++] = cc2;
-        }
-
-        public boolean hasData() {
-            return mLength > 0;
-        }
-
-        public void clear() {
-            mLength = 0;
-        }
-
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/TimedMetaData.java b/media2/media2-player/src/main/java/androidx/media2/player/TimedMetaData.java
deleted file mode 100644
index fbf3ffd..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/TimedMetaData.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2018 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.media2.player;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import androidx.annotation.RestrictTo;
-
-/**
- * Class that embodies one timed metadata access unit, including
- *
- * <ul>
- *   <li>a time stamp, and
- *   <li>raw uninterpreted byte-array extracted directly from the container.
- * </ul>
- *
- * @see MediaPlayer.PlayerCallback#onTimedMetaDataAvailable
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class TimedMetaData {
-    private static final String TAG = "TimedMetaData";
-
-    private long mTimestampUs;
-    private byte[] mMetaData;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public TimedMetaData(long timestampUs, byte[] metaData) {
-        mTimestampUs = timestampUs;
-        mMetaData = metaData;
-    }
-
-    /**
-     * @return the timestamp associated with this metadata access unit in microseconds;
-     * 0 denotes playback start.
-     */
-    public long getTimestamp() {
-        return mTimestampUs;
-    }
-
-    /**
-     * @return raw, uninterpreted content of this metadata access unit; for ID3 tags this includes
-     * everything starting from the 3 byte signature "ID3".
-     */
-    public byte[] getMetaData() {
-        return mMetaData;
-    }
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/TrackSelector.java b/media2/media2-player/src/main/java/androidx/media2/player/TrackSelector.java
deleted file mode 100644
index 99f7bd6..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/TrackSelector.java
+++ /dev/null
@@ -1,441 +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.media2.player;
-
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO;
-import static androidx.media2.player.RenderersFactory.AUDIO_RENDERER_INDEX;
-import static androidx.media2.player.RenderersFactory.METADATA_RENDERER_INDEX;
-import static androidx.media2.player.RenderersFactory.TEXT_RENDERER_INDEX;
-import static androidx.media2.player.RenderersFactory.VIDEO_RENDERER_INDEX;
-import static androidx.media2.player.TextRenderer.TRACK_TYPE_CEA608;
-import static androidx.media2.player.TextRenderer.TRACK_TYPE_CEA708;
-import static androidx.media2.player.TextRenderer.TRACK_TYPE_WEBVTT;
-import static androidx.media2.player.TrackSelector.InternalTextTrackInfo.UNSET;
-
-import android.media.MediaFormat;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.exoplayer.external.C;
-import androidx.media2.exoplayer.external.Format;
-import androidx.media2.exoplayer.external.source.TrackGroup;
-import androidx.media2.exoplayer.external.source.TrackGroupArray;
-import androidx.media2.exoplayer.external.trackselection.DefaultTrackSelector;
-import androidx.media2.exoplayer.external.trackselection.MappingTrackSelector;
-import androidx.media2.exoplayer.external.trackselection.TrackSelection;
-import androidx.media2.exoplayer.external.trackselection.TrackSelectionArray;
-import androidx.media2.exoplayer.external.util.MimeTypes;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Manages track selection for {@link ExoPlayerWrapper}.
- */
-/* package */ final class TrackSelector {
-
-    private static final int TRACK_INDEX_UNSET = -1;
-
-    private int mNextTrackId;
-    private MediaItem mCurrentMediaItem;
-    private final TextRenderer mTextRenderer;
-    private final DefaultTrackSelector mDefaultTrackSelector;
-    private final SparseArray<InternalTrackInfo> mAudioTracks;
-    private final SparseArray<InternalTrackInfo> mVideoTracks;
-    private final SparseArray<InternalTrackInfo> mMetadataTracks;
-    private final SparseArray<InternalTextTrackInfo> mTextTracks;
-
-    private boolean mPendingTracksUpdate;
-    private InternalTrackInfo mSelectedAudioTrack;
-    private InternalTrackInfo mSelectedVideoTrack;
-    private InternalTrackInfo mSelectedMetadataTrack;
-    private InternalTextTrackInfo mSelectedTextTrack;
-    private int mPlayerTextTrackIndex;
-
-    TrackSelector(TextRenderer textRenderer) {
-        mTextRenderer = textRenderer;
-        mDefaultTrackSelector = new DefaultTrackSelector();
-        mAudioTracks = new SparseArray<>();
-        mVideoTracks = new SparseArray<>();
-        mMetadataTracks = new SparseArray<>();
-        mTextTracks = new SparseArray<>();
-        mSelectedAudioTrack = null;
-        mSelectedVideoTrack = null;
-        mSelectedMetadataTrack = null;
-        mSelectedTextTrack = null;
-        mPlayerTextTrackIndex = TRACK_INDEX_UNSET;
-        // Ensure undetermined text tracks are selected so that CEA-608/708 streams are sent to the
-        // text renderer. By default, metadata tracks are not selected.
-        mDefaultTrackSelector.setParameters(
-                new DefaultTrackSelector.ParametersBuilder()
-                        .setSelectUndeterminedTextLanguage(true)
-                        .setRendererDisabled(METADATA_RENDERER_INDEX, /* disabled= */ true));
-    }
-
-    public DefaultTrackSelector getPlayerTrackSelector() {
-        return mDefaultTrackSelector;
-    }
-
-    public void handlePlayerTracksChanged(MediaItem item, TrackSelectionArray trackSelections) {
-        final boolean itemChanged = mCurrentMediaItem != item;
-        mCurrentMediaItem = item;
-
-        mPendingTracksUpdate = true;
-
-        // Clear all selection state.
-        mDefaultTrackSelector.setParameters(
-                mDefaultTrackSelector.buildUponParameters().clearSelectionOverrides());
-        mSelectedAudioTrack = null;
-        mSelectedVideoTrack = null;
-        mSelectedMetadataTrack = null;
-        mSelectedTextTrack = null;
-        mPlayerTextTrackIndex = TRACK_INDEX_UNSET;
-        mTextRenderer.clearSelection();
-
-        if (itemChanged) {
-            mAudioTracks.clear();
-            mVideoTracks.clear();
-            mMetadataTracks.clear();
-            mTextTracks.clear();
-        }
-
-        MappingTrackSelector.MappedTrackInfo mappedTrackInfo =
-                mDefaultTrackSelector.getCurrentMappedTrackInfo();
-        if (mappedTrackInfo == null) {
-            return;
-        }
-
-        // Get track selections to determine selected track.
-        TrackSelection audioTrackSelection = trackSelections.get(AUDIO_RENDERER_INDEX);
-        TrackGroup selectedAudioTrackGroup = audioTrackSelection == null ? null
-                : audioTrackSelection.getTrackGroup();
-        TrackSelection videoTrackSelection = trackSelections.get(VIDEO_RENDERER_INDEX);
-        TrackGroup selectedVideoTrackGroup = videoTrackSelection == null ? null
-                : videoTrackSelection.getTrackGroup();
-        TrackSelection metadataTrackSelection = trackSelections.get(METADATA_RENDERER_INDEX);
-        TrackGroup selectedMetadataTrackGroup = metadataTrackSelection == null ? null
-                : metadataTrackSelection.getTrackGroup();
-        TrackSelection textTrackSelection = trackSelections.get(TEXT_RENDERER_INDEX);
-        TrackGroup selectedTextTrackGroup = textTrackSelection == null ? null
-                : textTrackSelection.getTrackGroup();
-
-        // Enumerate track information.
-        TrackGroupArray audioTrackGroups = mappedTrackInfo.getTrackGroups(AUDIO_RENDERER_INDEX);
-        for (int i = mAudioTracks.size(); i < audioTrackGroups.length; i++) {
-            TrackGroup trackGroup = audioTrackGroups.get(i);
-            InternalTrackInfo track = new InternalTrackInfo(
-                    i,
-                    MEDIA_TRACK_TYPE_AUDIO,
-                    ExoPlayerUtils.getMediaFormat(trackGroup.getFormat(0)),
-                    mNextTrackId++);
-            mAudioTracks.put(track.mExternalTrackInfo.getId(), track);
-            if (trackGroup.equals(selectedAudioTrackGroup)) {
-                mSelectedAudioTrack = track;
-            }
-        }
-        TrackGroupArray videoTrackGroups = mappedTrackInfo.getTrackGroups(VIDEO_RENDERER_INDEX);
-        for (int i = mVideoTracks.size(); i < videoTrackGroups.length; i++) {
-            TrackGroup trackGroup = videoTrackGroups.get(i);
-            InternalTrackInfo track = new InternalTrackInfo(
-                    i,
-                    MEDIA_TRACK_TYPE_VIDEO,
-                    ExoPlayerUtils.getMediaFormat(trackGroup.getFormat(0)),
-                    mNextTrackId++);
-            mVideoTracks.put(track.mExternalTrackInfo.getId(), track);
-            if (trackGroup.equals(selectedVideoTrackGroup)) {
-                mSelectedVideoTrack = track;
-            }
-        }
-        TrackGroupArray metadataTrackGroups =
-                mappedTrackInfo.getTrackGroups(METADATA_RENDERER_INDEX);
-        for (int i = mMetadataTracks.size(); i < metadataTrackGroups.length; i++) {
-            TrackGroup trackGroup = metadataTrackGroups.get(i);
-            InternalTrackInfo track = new InternalTrackInfo(
-                    i,
-                    MEDIA_TRACK_TYPE_METADATA,
-                    ExoPlayerUtils.getMediaFormat(trackGroup.getFormat(0)),
-                    mNextTrackId++);
-            mMetadataTracks.put(track.mExternalTrackInfo.getId(), track);
-            if (trackGroup.equals(selectedMetadataTrackGroup)) {
-                mSelectedMetadataTrack = track;
-            }
-        }
-
-        // The text renderer exposes information about text tracks, but we may have preliminary
-        // information from the player.
-        TrackGroupArray textTrackGroups = mappedTrackInfo.getTrackGroups(TEXT_RENDERER_INDEX);
-        for (int i = mTextTracks.size(); i < textTrackGroups.length; i++) {
-            TrackGroup trackGroup = textTrackGroups.get(i);
-            Format format = Preconditions.checkNotNull(trackGroup.getFormat(0));
-            int type = getTextTrackType(format.sampleMimeType);
-            InternalTextTrackInfo textTrack = new InternalTextTrackInfo(
-                    i, type, format, UNSET, mNextTrackId++);
-            mTextTracks.put(textTrack.mExternalTrackInfo.getId(), textTrack);
-            if (trackGroup.equals(selectedTextTrackGroup)) {
-                mPlayerTextTrackIndex = i;
-            }
-        }
-    }
-
-    public void handleTextRendererChannelAvailable(int type, int channel) {
-        // We may already be advertising a track for this type. If so, associate the existing text
-        // track with the channel. Otherwise create a new text track info.
-        boolean populatedExistingTrack = false;
-        for (int i = 0; i < mTextTracks.size(); i++) {
-            InternalTextTrackInfo textTrack = mTextTracks.valueAt(i);
-            if (textTrack.mType == type && textTrack.mChannel == UNSET) {
-                int trackId = textTrack.mExternalTrackInfo.getId();
-                // Associate the existing text track with this channel.
-                InternalTextTrackInfo replacementTextTrack = new InternalTextTrackInfo(
-                        textTrack.mPlayerTrackIndex,
-                        type,
-                        textTrack.mFormat,
-                        channel,
-                        trackId);
-                mTextTracks.put(trackId, replacementTextTrack);
-                if (mSelectedTextTrack != null && mSelectedTextTrack.mPlayerTrackIndex == i) {
-                    mTextRenderer.select(type, channel);
-                }
-                populatedExistingTrack = true;
-                break;
-            }
-        }
-        if (!populatedExistingTrack) {
-            InternalTextTrackInfo textTrack = new InternalTextTrackInfo(
-                    mPlayerTextTrackIndex, type, /* format= */ null, channel, mNextTrackId++);
-            mTextTracks.put(textTrack.mExternalTrackInfo.getId(), textTrack);
-            mPendingTracksUpdate = true;
-        }
-    }
-
-    public boolean hasPendingTracksUpdate() {
-        boolean pendingTracksUpdate = mPendingTracksUpdate;
-        mPendingTracksUpdate = false;
-        return pendingTracksUpdate;
-    }
-
-    public TrackInfo getSelectedTrack(int trackType) {
-        switch (trackType) {
-            case MEDIA_TRACK_TYPE_AUDIO:
-                return mSelectedAudioTrack == null ? null
-                        : mSelectedAudioTrack.mExternalTrackInfo;
-            case MEDIA_TRACK_TYPE_VIDEO:
-                return mSelectedVideoTrack == null ? null
-                        : mSelectedVideoTrack.mExternalTrackInfo;
-            case MEDIA_TRACK_TYPE_METADATA:
-                return mSelectedMetadataTrack == null ? null
-                        : mSelectedMetadataTrack.mExternalTrackInfo;
-            case MEDIA_TRACK_TYPE_SUBTITLE:
-                return mSelectedTextTrack == null ? null
-                        : mSelectedTextTrack.mExternalTrackInfo;
-            case MEDIA_TRACK_TYPE_UNKNOWN:
-            default:
-                return null;
-        }
-    }
-
-    public List<TrackInfo> getTracks() {
-        ArrayList<TrackInfo> externalTracks = new ArrayList<>();
-        for (SparseArray<? extends InternalTrackInfo> tracks : Arrays.asList(
-                mAudioTracks, mVideoTracks, mMetadataTracks, mTextTracks)) {
-            for (int i = 0; i < tracks.size(); i++) {
-                externalTracks.add(tracks.valueAt(i).mExternalTrackInfo);
-            }
-        }
-        return externalTracks;
-    }
-
-    public void selectTrack(int trackId) {
-        InternalTrackInfo videoTrack = mVideoTracks.get(trackId);
-        Preconditions.checkArgument(videoTrack == null, "Video track selection is not supported");
-
-        InternalTrackInfo audioTrack = mAudioTracks.get(trackId);
-        if (audioTrack != null) {
-            mSelectedAudioTrack = audioTrack;
-            MappingTrackSelector.MappedTrackInfo mappedTrackInfo =
-                    Preconditions.checkNotNull(mDefaultTrackSelector.getCurrentMappedTrackInfo());
-            TrackGroupArray audioTrackGroups = mappedTrackInfo.getTrackGroups(AUDIO_RENDERER_INDEX);
-            TrackGroup selectedTrackGroup = audioTrackGroups.get(audioTrack.mPlayerTrackIndex);
-            // Selected all adaptive tracks.
-            int[] trackIndices = new int[selectedTrackGroup.length];
-            for (int i = 0; i < trackIndices.length; i++) {
-                trackIndices[i] = i;
-            }
-            DefaultTrackSelector.SelectionOverride selectionOverride =
-                    new DefaultTrackSelector.SelectionOverride(audioTrack.mPlayerTrackIndex,
-                            trackIndices);
-            mDefaultTrackSelector.setParameters(mDefaultTrackSelector.buildUponParameters()
-                    .setSelectionOverride(AUDIO_RENDERER_INDEX, audioTrackGroups, selectionOverride)
-                    .build());
-            return;
-        }
-
-        InternalTrackInfo metadataTrack = mMetadataTracks.get(trackId);
-        if (metadataTrack != null) {
-            mSelectedMetadataTrack = metadataTrack;
-            MappingTrackSelector.MappedTrackInfo mappedTrackInfo =
-                    Preconditions.checkNotNull(mDefaultTrackSelector.getCurrentMappedTrackInfo());
-            TrackGroupArray metadataTrackGroups =
-                    mappedTrackInfo.getTrackGroups(METADATA_RENDERER_INDEX);
-            DefaultTrackSelector.SelectionOverride selectionOverride =
-                    new DefaultTrackSelector.SelectionOverride(metadataTrack.mPlayerTrackIndex,
-                            /* tracks...= */ 0);
-            mDefaultTrackSelector.setParameters(mDefaultTrackSelector.buildUponParameters()
-                    .setRendererDisabled(METADATA_RENDERER_INDEX, /* disabled= */ false)
-                    .setSelectionOverride(
-                            METADATA_RENDERER_INDEX, metadataTrackGroups, selectionOverride)
-                    .build());
-            return;
-        }
-
-        InternalTextTrackInfo textTrack = mTextTracks.get(trackId);
-        Preconditions.checkArgument(textTrack != null);
-        if (mPlayerTextTrackIndex != textTrack.mPlayerTrackIndex) {
-            // We need to do a player-level track selection.
-            mTextRenderer.clearSelection();
-            mPlayerTextTrackIndex = textTrack.mPlayerTrackIndex;
-            MappingTrackSelector.MappedTrackInfo mappedTrackInfo =
-                    Preconditions.checkNotNull(mDefaultTrackSelector.getCurrentMappedTrackInfo());
-            TrackGroupArray textTrackGroups = mappedTrackInfo.getTrackGroups(TEXT_RENDERER_INDEX);
-            DefaultTrackSelector.SelectionOverride selectionOverride =
-                    new DefaultTrackSelector.SelectionOverride(mPlayerTextTrackIndex, 0);
-            mDefaultTrackSelector.setParameters(mDefaultTrackSelector.buildUponParameters()
-                    .setSelectionOverride(TEXT_RENDERER_INDEX, textTrackGroups, selectionOverride)
-                    .build());
-        }
-        if (textTrack.mChannel != UNSET) {
-            mTextRenderer.select(textTrack.mType, textTrack.mChannel);
-        }
-        mSelectedTextTrack = textTrack;
-    }
-
-    public void deselectTrack(int trackId) {
-        InternalTrackInfo videoTrack = mVideoTracks.get(trackId);
-        Preconditions.checkArgument(videoTrack == null, "Video track deselection is not supported");
-        InternalTrackInfo audioTrack = mAudioTracks.get(trackId);
-        Preconditions.checkArgument(audioTrack == null, "Audio track deselection is not supported");
-
-        InternalTrackInfo metadataTrack = mMetadataTracks.get(trackId);
-        if (metadataTrack != null) {
-            mSelectedMetadataTrack = null;
-            mDefaultTrackSelector.setParameters(mDefaultTrackSelector.buildUponParameters()
-                    .setRendererDisabled(METADATA_RENDERER_INDEX, /* disabled= */ true));
-            return;
-        }
-
-        Preconditions.checkArgument(mSelectedTextTrack != null
-                && mSelectedTextTrack.mExternalTrackInfo.getId() == trackId);
-        mTextRenderer.clearSelection();
-        mSelectedTextTrack = null;
-    }
-
-    private static int getTextTrackType(String sampleMimeType) {
-        switch (sampleMimeType) {
-            case MimeTypes.APPLICATION_CEA608:
-                return TRACK_TYPE_CEA608;
-            case MimeTypes.APPLICATION_CEA708:
-                return TRACK_TYPE_CEA708;
-            case MimeTypes.TEXT_VTT:
-                return TRACK_TYPE_WEBVTT;
-            default:
-                throw new IllegalArgumentException("Unexpected text MIME type " + sampleMimeType);
-        }
-    }
-
-    static class InternalTrackInfo {
-        final int mPlayerTrackIndex;
-        final TrackInfo mExternalTrackInfo;
-
-        InternalTrackInfo(int playerTrackIndex, int trackInfoType, @Nullable MediaFormat format,
-                int trackId) {
-            mPlayerTrackIndex = playerTrackIndex;
-            mExternalTrackInfo = new TrackInfo(trackId, trackInfoType, format,
-                    trackInfoType != MEDIA_TRACK_TYPE_VIDEO);
-        }
-    }
-
-    static final class InternalTextTrackInfo extends InternalTrackInfo {
-
-        static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
-        static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
-
-        static final int UNSET = -1;
-
-        final int mType;
-        final int mChannel;
-        @Nullable final Format mFormat;
-
-        InternalTextTrackInfo(int playerTrackIndex, @TextRenderer.TextTrackType int type,
-                @Nullable Format format, int channel, int trackId) {
-            super(playerTrackIndex, getTrackInfoType(type), getMediaFormat(type, format, channel),
-                    trackId);
-
-            mType = type;
-            mChannel = channel;
-            mFormat = format;
-        }
-
-        private static int getTrackInfoType(@TextRenderer.TextTrackType int type) {
-            // Hide WebVTT tracks, like the NuPlayer-based implementation
-            // (see [internal: b/120081663]).
-            return type == TRACK_TYPE_WEBVTT ? TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN
-                    : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
-        }
-
-        private static MediaFormat getMediaFormat(@TextRenderer.TextTrackType int type,
-                @Nullable Format format, int channel) {
-            @C.SelectionFlags int selectionFlags;
-            if (type == TRACK_TYPE_CEA608 && channel == 0) {
-                selectionFlags = C.SELECTION_FLAG_AUTOSELECT | C.SELECTION_FLAG_DEFAULT;
-            } else if (type == TRACK_TYPE_CEA708 && channel == 1) {
-                selectionFlags = C.SELECTION_FLAG_DEFAULT;
-            } else {
-                selectionFlags = format == null ? 0 : format.selectionFlags;
-            }
-            String language = format == null ? C.LANGUAGE_UNDETERMINED : format.language;
-            MediaFormat mediaFormat = new MediaFormat();
-            if (type == TRACK_TYPE_CEA608) {
-                mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
-            } else if (type == TRACK_TYPE_CEA708) {
-                mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_708);
-            } else if (type == TRACK_TYPE_WEBVTT) {
-                mediaFormat.setString(MediaFormat.KEY_MIME, MimeTypes.TEXT_VTT);
-            } else {
-                // Unexpected.
-                throw new IllegalStateException();
-            }
-            mediaFormat.setString(MediaFormat.KEY_LANGUAGE, language);
-            mediaFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE,
-                    (selectionFlags & C.SELECTION_FLAG_FORCED) != 0 ? 1 : 0);
-            mediaFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT,
-                    (selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0 ? 1 : 0);
-            mediaFormat.setInteger(MediaFormat.KEY_IS_DEFAULT,
-                    (selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0 ? 1 : 0);
-            return mediaFormat;
-        }
-
-    }
-
-}
diff --git a/media2/media2-player/src/main/java/androidx/media2/player/VideoSize.java b/media2/media2-player/src/main/java/androidx/media2/player/VideoSize.java
deleted file mode 100644
index 5aa2b31..0000000
--- a/media2/media2-player/src/main/java/androidx/media2/player/VideoSize.java
+++ /dev/null
@@ -1,36 +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.media2.player;
-
-import androidx.annotation.NonNull;
-
-/**
- * Immutable class for describing video size.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public final class VideoSize extends androidx.media2.common.VideoSize {
-    VideoSize(@NonNull androidx.media2.common.VideoSize internal) {
-        super(internal.getWidth(), internal.getHeight());
-    }
-
-    public VideoSize(int width, int height) {
-        super(width, height);
-    }
-}
diff --git a/media2/media2-session/api/1.0.0-beta01.txt b/media2/media2-session/api/1.0.0-beta01.txt
deleted file mode 100644
index 41f525d..0000000
--- a/media2/media2-session/api/1.0.0-beta01.txt
+++ /dev/null
@@ -1,414 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method public int getShuffleMode();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-  }
-
-  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-  }
-
-  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/1.0.0-beta02.txt b/media2/media2-session/api/1.0.0-beta02.txt
deleted file mode 100644
index fe7e0ccb..0000000
--- a/media2/media2-session/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,414 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method public int getShuffleMode();
-    method public boolean isConnected();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-  }
-
-  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public androidx.media2.session.SessionToken getToken();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-  }
-
-  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/1.0.0-rc01.txt b/media2/media2-session/api/1.0.0-rc01.txt
deleted file mode 100644
index 41f525d..0000000
--- a/media2/media2-session/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,414 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method public int getShuffleMode();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-  }
-
-  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-  }
-
-  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/1.1.0-beta01.txt b/media2/media2-session/api/1.1.0-beta01.txt
deleted file mode 100644
index 2375fb0..0000000
--- a/media2/media2-session/api/1.1.0-beta01.txt
+++ /dev/null
@@ -1,447 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaConstants {
-    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field public static final String MEDIA_URI_QUERY_ID = "id";
-    field public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  public class MediaController implements java.io.Closeable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public android.app.PendingIntent? getSessionActivity();
-    method public int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.io.Closeable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/1.2.0-beta01.txt b/media2/media2-session/api/1.2.0-beta01.txt
deleted file mode 100644
index 95e39e3..0000000
--- a/media2/media2-session/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1,449 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaConstants {
-    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field public static final String MEDIA_URI_QUERY_ID = "id";
-    field public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field public static final String MEDIA_URI_QUERY_URI = "uri";
-    field public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  public class MediaController implements java.io.Closeable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method public long getBufferedPosition();
-    method public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method public int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method public android.app.PendingIntent? getSessionActivity();
-    method public int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.io.Closeable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/1.3.0-beta01.txt b/media2/media2-session/api/1.3.0-beta01.txt
deleted file mode 100644
index 2855a88..0000000
--- a/media2/media2-session/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,449 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @Deprecated public final class HeartRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public HeartRating();
-    ctor @Deprecated public HeartRating(boolean);
-    method @Deprecated public boolean hasHeart();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public LibraryResult(int);
-    ctor @Deprecated public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor @Deprecated public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public class MediaBrowser extends androidx.media2.session.MediaController {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  @Deprecated public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor @Deprecated public MediaBrowser.BrowserCallback();
-    method @Deprecated public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaBrowser.Builder {
-    ctor @Deprecated public MediaBrowser.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaBrowser build();
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public class MediaConstants {
-    field @Deprecated public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field @Deprecated public static final String MEDIA_URI_QUERY_ID = "id";
-    field @Deprecated public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field @Deprecated public static final String MEDIA_URI_QUERY_URI = "uri";
-    field @Deprecated public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  @Deprecated public class MediaController implements java.io.Closeable {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method @Deprecated public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.session.SessionToken? getConnectedToken();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method @Deprecated public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public android.app.PendingIntent? getSessionActivity();
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public boolean isConnected();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  @Deprecated public static final class MediaController.Builder {
-    ctor @Deprecated public MediaController.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaController build();
-    method @Deprecated public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public abstract static class MediaController.ControllerCallback {
-    ctor @Deprecated public MediaController.ControllerCallback();
-    method @Deprecated public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method @Deprecated public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @Deprecated public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getControlType();
-    method @Deprecated public int getCurrentVolume();
-    method @Deprecated public int getMaxVolume();
-    method @Deprecated public int getPlaybackType();
-    field @Deprecated public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field @Deprecated public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @Deprecated public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor @Deprecated public MediaLibraryService();
-    method @Deprecated public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public boolean isOffline();
-    method @Deprecated public boolean isRecent();
-    method @Deprecated public boolean isSuggested();
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor @Deprecated public MediaLibraryService.LibraryParams.Builder();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method @Deprecated public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  @Deprecated public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method @Deprecated public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  @Deprecated public class MediaSession implements java.io.Closeable {
-    method @Deprecated public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void close();
-    method @Deprecated public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method @Deprecated public String getId();
-    method @Deprecated public androidx.media2.common.SessionPlayer getPlayer();
-    method @Deprecated public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method @Deprecated public androidx.media2.session.SessionToken getToken();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static final class MediaSession.Builder {
-    ctor @Deprecated public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method @Deprecated public androidx.media2.session.MediaSession build();
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.session.SessionCommand? getCommand();
-    method @Deprecated public CharSequence? getDisplayName();
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public int getIconResId();
-    method @Deprecated public boolean isEnabled();
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton.Builder {
-    ctor @Deprecated public MediaSession.CommandButton.Builder();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton build();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  @Deprecated public static final class MediaSession.ControllerInfo {
-    method @Deprecated public android.os.Bundle getConnectionHints();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public int getUid();
-  }
-
-  @Deprecated public abstract static class MediaSession.SessionCallback {
-    ctor @Deprecated public MediaSession.SessionCallback();
-    method @Deprecated public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @Deprecated public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @Deprecated public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @Deprecated public final class MediaSessionManager {
-    method @Deprecated public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  @Deprecated public abstract class MediaSessionService extends android.app.Service {
-    ctor @Deprecated public MediaSessionService();
-    method @Deprecated public final void addSession(androidx.media2.session.MediaSession);
-    method @Deprecated public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @Deprecated @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method @Deprecated public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method @Deprecated public final void removeSession(androidx.media2.session.MediaSession);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  @Deprecated public static class MediaSessionService.MediaNotification {
-    ctor @Deprecated public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method @Deprecated public android.app.Notification getNotification();
-    method @Deprecated public int getNotificationId();
-  }
-
-  @Deprecated public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public PercentageRating();
-    ctor @Deprecated public PercentageRating(float);
-    method @Deprecated public float getPercentRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public RemoteSessionPlayer();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method @Deprecated public abstract int getMaxVolume();
-    method @Deprecated public abstract int getVolume();
-    method @Deprecated public abstract int getVolumeControlType();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field @Deprecated public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field @Deprecated public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field @Deprecated public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  @Deprecated public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public RemoteSessionPlayer.Callback();
-    method @Deprecated public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @Deprecated public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommand(int);
-    ctor @Deprecated public SessionCommand(String, android.os.Bundle?);
-    method @Deprecated public int getCommandCode();
-    method @Deprecated public String? getCustomAction();
-    method @Deprecated public android.os.Bundle? getCustomExtras();
-    field @Deprecated public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field @Deprecated public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @Deprecated public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field @Deprecated public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @Deprecated public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @Deprecated public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommandGroup();
-    ctor @Deprecated public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method @Deprecated public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public boolean hasCommand(int);
-  }
-
-  @Deprecated public static final class SessionCommandGroup.Builder {
-    ctor @Deprecated public SessionCommandGroup.Builder();
-    ctor @Deprecated public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup build();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @Deprecated public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionResult(int, android.os.Bundle?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public android.os.Bundle? getCustomCommandResult();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field @Deprecated public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionToken(android.content.Context, android.content.ComponentName);
-    method @Deprecated public android.os.Bundle getExtras();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public String? getServiceName();
-    method @Deprecated public int getType();
-    method @Deprecated public int getUid();
-    field @Deprecated public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field @Deprecated public static final int TYPE_SESSION = 0; // 0x0
-    field @Deprecated public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @Deprecated public final class StarRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public StarRating(@IntRange(from=1) int);
-    ctor @Deprecated public StarRating(@IntRange(from=1) int, float);
-    method @Deprecated public int getMaxStars();
-    method @Deprecated public float getStarRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public ThumbRating();
-    ctor @Deprecated public ThumbRating(boolean);
-    method @Deprecated public boolean isRated();
-    method @Deprecated public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl
deleted file mode 100644
index c437a9d..0000000
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 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 IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package androidx.media2.session;
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-interface IMediaController {
-  oneway void onCurrentMediaItemChanged(int seq, in androidx.versionedparcelable.ParcelImpl item, int currentIdx, int previousIdx, int nextIdx) = 0;
-  oneway void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int state) = 1;
-  oneway void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed) = 2;
-  oneway void onBufferingStateChanged(int seq, in androidx.versionedparcelable.ParcelImpl item, int state, long bufferedPositionMs, long eventTimeMs, long positionMs) = 3;
-  oneway void onPlaylistChanged(int seq, in androidx.media2.common.ParcelImplListSlice listSlice, in androidx.versionedparcelable.ParcelImpl metadata, int currentIdx, int previousIdx, int nextIdx) = 4;
-  oneway void onPlaylistMetadataChanged(int seq, in androidx.versionedparcelable.ParcelImpl metadata) = 5;
-  oneway void onPlaybackInfoChanged(int seq, in androidx.versionedparcelable.ParcelImpl playbackInfo) = 6;
-  oneway void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx, int nextIdx) = 7;
-  oneway void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx, int nextIdx) = 8;
-  oneway void onPlaybackCompleted(int seq) = 9;
-  oneway void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long seekPositionMs) = 10;
-  oneway void onVideoSizeChanged(int seq, in androidx.versionedparcelable.ParcelImpl item, in androidx.versionedparcelable.ParcelImpl videoSize) = 20;
-  oneway void onSubtitleData(int seq, in androidx.versionedparcelable.ParcelImpl item, in androidx.versionedparcelable.ParcelImpl track, in androidx.versionedparcelable.ParcelImpl data) = 24;
-  oneway void onConnected(int seq, in androidx.versionedparcelable.ParcelImpl connectionResult) = 11;
-  oneway void onDisconnected(int seq) = 12;
-  oneway void onSetCustomLayout(int seq, in List<androidx.versionedparcelable.ParcelImpl> commandButtonlist) = 13;
-  oneway void onAllowedCommandsChanged(int seq, in androidx.versionedparcelable.ParcelImpl commandGroup) = 14;
-  oneway void onCustomCommand(int seq, in androidx.versionedparcelable.ParcelImpl command, in android.os.Bundle args) = 15;
-  oneway void onSessionResult(int seq, in androidx.versionedparcelable.ParcelImpl sessionResult) = 16;
-  oneway void onLibraryResult(int seq, in androidx.versionedparcelable.ParcelImpl libraryResult) = 17;
-  oneway void onTrackInfoChanged(int seq, in List<androidx.versionedparcelable.ParcelImpl> trackInfos, in androidx.versionedparcelable.ParcelImpl selectedVideoTrack, in androidx.versionedparcelable.ParcelImpl selectedAudioTrack, in androidx.versionedparcelable.ParcelImpl selectedSubtitleTrack, in androidx.versionedparcelable.ParcelImpl selectedMetadataTrack) = 21;
-  oneway void onTrackSelected(int seq, in androidx.versionedparcelable.ParcelImpl trackInfo) = 22;
-  oneway void onTrackDeselected(int seq, in androidx.versionedparcelable.ParcelImpl trackInfo) = 23;
-  oneway void onChildrenChanged(int seq, String parentId, int itemCount, in androidx.versionedparcelable.ParcelImpl libraryParams) = 18;
-  oneway void onSearchResultChanged(int seq, String query, int itemCount, in androidx.versionedparcelable.ParcelImpl libraryParams) = 19;
-}
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl
deleted file mode 100644
index 6abb4dc..0000000
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2018 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 IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package androidx.media2.session;
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-interface IMediaSession {
-  oneway void connect(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl connectionRequest) = 0;
-  oneway void release(androidx.media2.session.IMediaController caller, int seq) = 1;
-  oneway void setVolumeTo(androidx.media2.session.IMediaController caller, int seq, int value, int flags) = 2;
-  oneway void adjustVolume(androidx.media2.session.IMediaController caller, int seq, int direction, int flags) = 3;
-  oneway void play(androidx.media2.session.IMediaController caller, int seq) = 4;
-  oneway void pause(androidx.media2.session.IMediaController caller, int seq) = 5;
-  oneway void prepare(androidx.media2.session.IMediaController caller, int seq) = 6;
-  oneway void fastForward(androidx.media2.session.IMediaController caller, int seq) = 7;
-  oneway void rewind(androidx.media2.session.IMediaController caller, int seq) = 8;
-  oneway void skipForward(androidx.media2.session.IMediaController caller, int seq) = 9;
-  oneway void skipBackward(androidx.media2.session.IMediaController caller, int seq) = 10;
-  oneway void seekTo(androidx.media2.session.IMediaController caller, int seq, long pos) = 11;
-  oneway void onCustomCommand(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl sessionCommand, in android.os.Bundle args) = 12;
-  oneway void setRating(androidx.media2.session.IMediaController caller, int seq, String mediaId, in androidx.versionedparcelable.ParcelImpl rating) = 19;
-  oneway void setPlaybackSpeed(androidx.media2.session.IMediaController caller, int seq, float speed) = 20;
-  oneway void setPlaylist(androidx.media2.session.IMediaController caller, int seq, in List<String> list, in androidx.versionedparcelable.ParcelImpl metadata) = 21;
-  oneway void setMediaItem(androidx.media2.session.IMediaController caller, int seq, String mediaId) = 22;
-  oneway void setMediaUri(androidx.media2.session.IMediaController caller, int seq, in android.net.Uri uri, in android.os.Bundle extras) = 44;
-  oneway void updatePlaylistMetadata(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl metadata) = 23;
-  oneway void addPlaylistItem(androidx.media2.session.IMediaController caller, int seq, int index, String mediaId) = 24;
-  oneway void removePlaylistItem(androidx.media2.session.IMediaController caller, int seq, int index) = 25;
-  oneway void replacePlaylistItem(androidx.media2.session.IMediaController caller, int seq, int index, String mediaId) = 26;
-  oneway void movePlaylistItem(androidx.media2.session.IMediaController caller, int seq, int fromIndex, int toIndex) = 43;
-  oneway void skipToPlaylistItem(androidx.media2.session.IMediaController caller, int seq, int index) = 27;
-  oneway void skipToPreviousItem(androidx.media2.session.IMediaController caller, int seq) = 28;
-  oneway void skipToNextItem(androidx.media2.session.IMediaController caller, int seq) = 29;
-  oneway void setRepeatMode(androidx.media2.session.IMediaController caller, int seq, int repeatMode) = 30;
-  oneway void setShuffleMode(androidx.media2.session.IMediaController caller, int seq, int shuffleMode) = 31;
-  oneway void setSurface(androidx.media2.session.IMediaController caller, int seq, in android.view.Surface surface) = 40;
-  oneway void selectTrack(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl trackInfo) = 41;
-  oneway void deselectTrack(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl trackInfo) = 42;
-  oneway void onControllerResult(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl controllerResult) = 32;
-  oneway void getLibraryRoot(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl libraryParams) = 33;
-  oneway void getItem(androidx.media2.session.IMediaController caller, int seq, String mediaId) = 34;
-  oneway void getChildren(androidx.media2.session.IMediaController caller, int seq, String parentId, int page, int pageSize, in androidx.versionedparcelable.ParcelImpl libraryParams) = 35;
-  oneway void search(androidx.media2.session.IMediaController caller, int seq, String query, in androidx.versionedparcelable.ParcelImpl libraryParams) = 36;
-  oneway void getSearchResult(androidx.media2.session.IMediaController caller, int seq, String query, int page, int pageSize, in androidx.versionedparcelable.ParcelImpl libraryParams) = 37;
-  oneway void subscribe(androidx.media2.session.IMediaController caller, int seq, String parentId, in androidx.versionedparcelable.ParcelImpl libraryParams) = 38;
-  oneway void unsubscribe(androidx.media2.session.IMediaController caller, int seq, String parentId) = 39;
-}
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl
deleted file mode 100644
index 6a093d7..0000000
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 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 IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package androidx.media2.session;
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-interface IMediaSessionService {
-  oneway void connect(androidx.media2.session.IMediaController caller, in androidx.versionedparcelable.ParcelImpl connectionRequest) = 0;
-}
diff --git a/media2/media2-session/api/api_lint.ignore b/media2/media2-session/api/api_lint.ignore
deleted file mode 100644
index 375dd88..0000000
--- a/media2/media2-session/api/api_lint.ignore
+++ /dev/null
@@ -1,151 +0,0 @@
-// Baseline format: 1.0
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#getChildren(String, int, int, androidx.media2.session.MediaLibraryService.LibraryParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#getItem(String):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#getSearchResult(String, int, int, androidx.media2.session.MediaLibraryService.LibraryParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#search(String, androidx.media2.session.MediaLibraryService.LibraryParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaBrowser#unsubscribe(String):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#addPlaylistItem(int, String):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#adjustVolume(int, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#fastForward():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#movePlaylistItem(int, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#pause():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#play():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#prepare():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#removePlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#replacePlaylistItem(int, String):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#rewind():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#seekTo(long):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#selectTrack(androidx.media2.common.SessionPlayer.TrackInfo):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setMediaItem(String):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setMediaUri(android.net.Uri, android.os.Bundle):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setPlaybackSpeed(float):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setPlaylist(java.util.List<java.lang.String>, androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setRating(String, androidx.media2.common.Rating):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setRepeatMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setShuffleMode(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setSurface(android.view.Surface):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#setVolumeTo(int, int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#skipBackward():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#skipForward():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#skipToNextPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#skipToPlaylistItem(int):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#skipToPreviousPlaylistItem():
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaController#updatePlaylistMetadata(androidx.media2.common.MediaMetadata):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaSession#sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-AsyncSuffixFuture: androidx.media2.session.MediaSession#setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton>):
-    Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-
-
-BadFuture: androidx.media2.session.RemoteSessionPlayer#adjustVolume(int):
-    Use ListenableFuture (library), or a combination of OutcomeReceiver<R,E>, Executor, and CancellationSignal (platform) instead of java.util.concurrent.Future (method androidx.media2.session.RemoteSessionPlayer.adjustVolume(int))
-BadFuture: androidx.media2.session.RemoteSessionPlayer#setVolume(int):
-    Use ListenableFuture (library), or a combination of OutcomeReceiver<R,E>, Executor, and CancellationSignal (platform) instead of java.util.concurrent.Future (method androidx.media2.session.RemoteSessionPlayer.setVolume(int))
-
-
-BuilderSetStyle: androidx.media2.session.SessionCommandGroup.Builder#removeCommand(androidx.media2.session.SessionCommand):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.session.SessionCommandGroup.Builder.removeCommand(androidx.media2.session.SessionCommand)
-
-
-InvalidNullabilityOverride: androidx.media2.session.MediaSessionService#onBind(android.content.Intent):
-    Invalid nullability on method `onBind` return. Overrides of unannotated super method cannot be Nullable.
-InvalidNullabilityOverride: androidx.media2.session.MediaSessionService#onBind(android.content.Intent) parameter #0:
-    Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-
-
-MissingGetterMatchingBuilder: androidx.media2.session.MediaBrowser.Builder#setConnectionHints(android.os.Bundle):
-    androidx.media2.session.MediaBrowser does not declare a `getConnectionHints()` method matching method androidx.media2.session.MediaBrowser.Builder.setConnectionHints(android.os.Bundle)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaBrowser.Builder#setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback):
-    androidx.media2.session.MediaBrowser does not declare a `getControllerCallback()` method matching method androidx.media2.session.MediaBrowser.Builder.setControllerCallback(java.util.concurrent.Executor,androidx.media2.session.MediaBrowser.BrowserCallback)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaBrowser.Builder#setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token):
-    androidx.media2.session.MediaBrowser does not declare a `getSessionCompatToken()` method matching method androidx.media2.session.MediaBrowser.Builder.setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaBrowser.Builder#setSessionToken(androidx.media2.session.SessionToken):
-    androidx.media2.session.MediaBrowser does not declare a `getSessionToken()` method matching method androidx.media2.session.MediaBrowser.Builder.setSessionToken(androidx.media2.session.SessionToken)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaController.Builder#setConnectionHints(android.os.Bundle):
-    androidx.media2.session.MediaController does not declare a `getConnectionHints()` method matching method androidx.media2.session.MediaController.Builder.setConnectionHints(android.os.Bundle)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaController.Builder#setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback):
-    androidx.media2.session.MediaController does not declare a `getControllerCallback()` method matching method androidx.media2.session.MediaController.Builder.setControllerCallback(java.util.concurrent.Executor,androidx.media2.session.MediaController.ControllerCallback)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaController.Builder#setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token):
-    androidx.media2.session.MediaController does not declare a `getSessionCompatToken()` method matching method androidx.media2.session.MediaController.Builder.setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaController.Builder#setSessionToken(androidx.media2.session.SessionToken):
-    androidx.media2.session.MediaController does not declare a `getSessionToken()` method matching method androidx.media2.session.MediaController.Builder.setSessionToken(androidx.media2.session.SessionToken)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder#setExtras(android.os.Bundle):
-    androidx.media2.session.MediaLibraryService.MediaLibrarySession does not declare a `getExtras()` method matching method androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder.setExtras(android.os.Bundle)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder#setSessionActivity(android.app.PendingIntent):
-    androidx.media2.session.MediaLibraryService.MediaLibrarySession does not declare a `getSessionActivity()` method matching method androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder.setSessionActivity(android.app.PendingIntent)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaSession.Builder#setExtras(android.os.Bundle):
-    androidx.media2.session.MediaSession does not declare a `getExtras()` method matching method androidx.media2.session.MediaSession.Builder.setExtras(android.os.Bundle)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaSession.Builder#setSessionActivity(android.app.PendingIntent):
-    androidx.media2.session.MediaSession does not declare a `getSessionActivity()` method matching method androidx.media2.session.MediaSession.Builder.setSessionActivity(android.app.PendingIntent)
-MissingGetterMatchingBuilder: androidx.media2.session.MediaSession.Builder#setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback):
-    androidx.media2.session.MediaSession does not declare a `getSessionCallback()` method matching method androidx.media2.session.MediaSession.Builder.setSessionCallback(java.util.concurrent.Executor,androidx.media2.session.MediaSession.SessionCallback)
-MissingGetterMatchingBuilder: androidx.media2.session.SessionCommandGroup.Builder#addAllPredefinedCommands(int):
-    androidx.media2.session.SessionCommandGroup does not declare a getter method matching method androidx.media2.session.SessionCommandGroup.Builder.addAllPredefinedCommands(int) (expected one of: [getAllPredefinedCommands(), getAllPredefinedCommandses()])
-
-
-MissingNullability: androidx.media2.session.MediaLibraryService#onBind(android.content.Intent):
-    Missing nullability on method `onBind` return
-MissingNullability: androidx.media2.session.MediaSessionService#onStartCommand(android.content.Intent, int, int) parameter #0:
-    Missing nullability on parameter `intent` in method `onStartCommand`
-
-
-NullableCollection: androidx.media2.session.LibraryResult#getMediaItems():
-    Return type of method androidx.media2.session.LibraryResult.getMediaItems() is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.session.MediaController#getPlaylist():
-    Return type of method androidx.media2.session.MediaController.getPlaylist() is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.session.MediaController.ControllerCallback#onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle) parameter #2:
-    Type of parameter args in androidx.media2.session.MediaController.ControllerCallback.onCustomCommand(androidx.media2.session.MediaController controller, androidx.media2.session.SessionCommand command, android.os.Bundle args) is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.MediaController.ControllerCallback#onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem>, androidx.media2.common.MediaMetadata) parameter #1:
-    Type of parameter list in androidx.media2.session.MediaController.ControllerCallback.onPlaylistChanged(androidx.media2.session.MediaController controller, java.util.List<androidx.media2.common.MediaItem> list, androidx.media2.common.MediaMetadata metadata) is a nullable collection (`java.util.List`); must be non-null
-NullableCollection: androidx.media2.session.MediaLibraryService.LibraryParams#getExtras():
-    Return type of method androidx.media2.session.MediaLibraryService.LibraryParams.getExtras() is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.MediaSession.CommandButton#getExtras():
-    Return type of method androidx.media2.session.MediaSession.CommandButton.getExtras() is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.MediaSession.SessionCallback#onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle) parameter #3:
-    Type of parameter args in androidx.media2.session.MediaSession.SessionCallback.onCustomCommand(androidx.media2.session.MediaSession session, androidx.media2.session.MediaSession.ControllerInfo controller, androidx.media2.session.SessionCommand customCommand, android.os.Bundle args) is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.MediaSession.SessionCallback#onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle) parameter #3:
-    Type of parameter extras in androidx.media2.session.MediaSession.SessionCallback.onSetMediaUri(androidx.media2.session.MediaSession session, androidx.media2.session.MediaSession.ControllerInfo controller, android.net.Uri uri, android.os.Bundle extras) is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.SessionCommand#getCustomExtras():
-    Return type of method androidx.media2.session.SessionCommand.getCustomExtras() is a nullable collection (`android.os.Bundle`); must be non-null
-NullableCollection: androidx.media2.session.SessionResult#getCustomCommandResult():
-    Return type of method androidx.media2.session.SessionResult.getCustomCommandResult() is a nullable collection (`android.os.Bundle`); must be non-null
diff --git a/media2/media2-session/api/current.txt b/media2/media2-session/api/current.txt
deleted file mode 100644
index 2855a88..0000000
--- a/media2/media2-session/api/current.txt
+++ /dev/null
@@ -1,449 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @Deprecated public final class HeartRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public HeartRating();
-    ctor @Deprecated public HeartRating(boolean);
-    method @Deprecated public boolean hasHeart();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public LibraryResult(int);
-    ctor @Deprecated public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor @Deprecated public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public class MediaBrowser extends androidx.media2.session.MediaController {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  @Deprecated public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor @Deprecated public MediaBrowser.BrowserCallback();
-    method @Deprecated public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaBrowser.Builder {
-    ctor @Deprecated public MediaBrowser.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaBrowser build();
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public class MediaConstants {
-    field @Deprecated public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field @Deprecated public static final String MEDIA_URI_QUERY_ID = "id";
-    field @Deprecated public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field @Deprecated public static final String MEDIA_URI_QUERY_URI = "uri";
-    field @Deprecated public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  @Deprecated public class MediaController implements java.io.Closeable {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method @Deprecated public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.session.SessionToken? getConnectedToken();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method @Deprecated public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public android.app.PendingIntent? getSessionActivity();
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public boolean isConnected();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  @Deprecated public static final class MediaController.Builder {
-    ctor @Deprecated public MediaController.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaController build();
-    method @Deprecated public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public abstract static class MediaController.ControllerCallback {
-    ctor @Deprecated public MediaController.ControllerCallback();
-    method @Deprecated public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method @Deprecated public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @Deprecated public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getControlType();
-    method @Deprecated public int getCurrentVolume();
-    method @Deprecated public int getMaxVolume();
-    method @Deprecated public int getPlaybackType();
-    field @Deprecated public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field @Deprecated public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @Deprecated public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor @Deprecated public MediaLibraryService();
-    method @Deprecated public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public boolean isOffline();
-    method @Deprecated public boolean isRecent();
-    method @Deprecated public boolean isSuggested();
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor @Deprecated public MediaLibraryService.LibraryParams.Builder();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method @Deprecated public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  @Deprecated public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method @Deprecated public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  @Deprecated public class MediaSession implements java.io.Closeable {
-    method @Deprecated public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void close();
-    method @Deprecated public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method @Deprecated public String getId();
-    method @Deprecated public androidx.media2.common.SessionPlayer getPlayer();
-    method @Deprecated public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method @Deprecated public androidx.media2.session.SessionToken getToken();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static final class MediaSession.Builder {
-    ctor @Deprecated public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method @Deprecated public androidx.media2.session.MediaSession build();
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.session.SessionCommand? getCommand();
-    method @Deprecated public CharSequence? getDisplayName();
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public int getIconResId();
-    method @Deprecated public boolean isEnabled();
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton.Builder {
-    ctor @Deprecated public MediaSession.CommandButton.Builder();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton build();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  @Deprecated public static final class MediaSession.ControllerInfo {
-    method @Deprecated public android.os.Bundle getConnectionHints();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public int getUid();
-  }
-
-  @Deprecated public abstract static class MediaSession.SessionCallback {
-    ctor @Deprecated public MediaSession.SessionCallback();
-    method @Deprecated public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @Deprecated public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @Deprecated public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @Deprecated public final class MediaSessionManager {
-    method @Deprecated public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  @Deprecated public abstract class MediaSessionService extends android.app.Service {
-    ctor @Deprecated public MediaSessionService();
-    method @Deprecated public final void addSession(androidx.media2.session.MediaSession);
-    method @Deprecated public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @Deprecated @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method @Deprecated public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method @Deprecated public final void removeSession(androidx.media2.session.MediaSession);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  @Deprecated public static class MediaSessionService.MediaNotification {
-    ctor @Deprecated public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method @Deprecated public android.app.Notification getNotification();
-    method @Deprecated public int getNotificationId();
-  }
-
-  @Deprecated public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public PercentageRating();
-    ctor @Deprecated public PercentageRating(float);
-    method @Deprecated public float getPercentRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public RemoteSessionPlayer();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method @Deprecated public abstract int getMaxVolume();
-    method @Deprecated public abstract int getVolume();
-    method @Deprecated public abstract int getVolumeControlType();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field @Deprecated public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field @Deprecated public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field @Deprecated public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  @Deprecated public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public RemoteSessionPlayer.Callback();
-    method @Deprecated public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @Deprecated public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommand(int);
-    ctor @Deprecated public SessionCommand(String, android.os.Bundle?);
-    method @Deprecated public int getCommandCode();
-    method @Deprecated public String? getCustomAction();
-    method @Deprecated public android.os.Bundle? getCustomExtras();
-    field @Deprecated public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field @Deprecated public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @Deprecated public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field @Deprecated public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @Deprecated public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @Deprecated public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommandGroup();
-    ctor @Deprecated public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method @Deprecated public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public boolean hasCommand(int);
-  }
-
-  @Deprecated public static final class SessionCommandGroup.Builder {
-    ctor @Deprecated public SessionCommandGroup.Builder();
-    ctor @Deprecated public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup build();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @Deprecated public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionResult(int, android.os.Bundle?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public android.os.Bundle? getCustomCommandResult();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field @Deprecated public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionToken(android.content.Context, android.content.ComponentName);
-    method @Deprecated public android.os.Bundle getExtras();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public String? getServiceName();
-    method @Deprecated public int getType();
-    method @Deprecated public int getUid();
-    field @Deprecated public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field @Deprecated public static final int TYPE_SESSION = 0; // 0x0
-    field @Deprecated public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @Deprecated public final class StarRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public StarRating(@IntRange(from=1) int);
-    ctor @Deprecated public StarRating(@IntRange(from=1) int, float);
-    method @Deprecated public int getMaxStars();
-    method @Deprecated public float getStarRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public ThumbRating();
-    ctor @Deprecated public ThumbRating(boolean);
-    method @Deprecated public boolean isRated();
-    method @Deprecated public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/res-1.0.0-beta01.txt b/media2/media2-session/api/res-1.0.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.0.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-1.0.0-beta02.txt b/media2/media2-session/api/res-1.0.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.0.0-beta02.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-1.0.0-rc01.txt b/media2/media2-session/api/res-1.0.0-rc01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.0.0-rc01.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-1.1.0-beta01.txt b/media2/media2-session/api/res-1.1.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.1.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-1.2.0-beta01.txt b/media2/media2-session/api/res-1.2.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.2.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-1.3.0-beta01.txt b/media2/media2-session/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/media2/media2-session/api/res-current.txt b/media2/media2-session/api/res-current.txt
deleted file mode 100644
index e69de29..0000000
--- a/media2/media2-session/api/res-current.txt
+++ /dev/null
diff --git a/media2/media2-session/api/restricted_1.0.0-beta01.txt b/media2/media2-session/api/restricted_1.0.0-beta01.txt
deleted file mode 100644
index d5dc63d..0000000
--- a/media2/media2-session/api/restricted_1.0.0-beta01.txt
+++ /dev/null
@@ -1,455 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @androidx.media2.session.LibraryResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface LibraryResult.ResultCode {
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(@androidx.media2.session.MediaController.VolumeDirection int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setTimeDiff(Long!);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @IntDef({android.media.AudioManager.ADJUST_LOWER, android.media.AudioManager.ADJUST_RAISE, android.media.AudioManager.ADJUST_SAME, android.media.AudioManager.ADJUST_MUTE, android.media.AudioManager.ADJUST_UNMUTE, android.media.AudioManager.ADJUST_TOGGLE_MUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeDirection {
-  }
-
-  @IntDef(value={android.media.AudioManager.FLAG_SHOW_UI, android.media.AudioManager.FLAG_ALLOW_RINGER_MODES, android.media.AudioManager.FLAG_PLAY_SOUND, android.media.AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE, android.media.AudioManager.FLAG_VIBRATE}, flag=true) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeFlags {
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.MediaSessionCompat! getSessionCompat();
-    method public androidx.media2.session.SessionToken getToken();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isClosed();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media.MediaSessionManager.RemoteUserInfo getRemoteUserInfo();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isTrusted();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method @androidx.media2.session.SessionResult.ResultCode public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @androidx.media2.session.SessionResult.ResultCode public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method @androidx.media2.session.RemoteSessionPlayer.VolumeControlType public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @IntDef({androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_FIXED, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_RELATIVE, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteSessionPlayer.VolumeControlType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method @androidx.media2.session.SessionCommand.CommandCode public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID = 40004; // 0x9c44
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_SEARCH = 40005; // 0x9c45
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_URI = 40006; // 0x9c46
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID = 40007; // 0x9c47
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH = 40008; // 0x9c48
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_URI = 40009; // 0x9c49
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_VERSION_CURRENT = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PREPARE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandCode {
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_VERSION_1}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandVersion {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(@androidx.media2.session.SessionCommand.CommandVersion int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class SessionResult implements androidx.media2.common.BaseResult androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(@androidx.media2.session.SessionResult.ResultCode int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.session.SessionResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.session.SessionResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionResult.ResultCode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void createSessionToken(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token, java.util.concurrent.Executor, androidx.media2.session.SessionToken.OnSessionTokenCreatedListener);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object! getBinder();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.ComponentName! getComponentName();
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method @androidx.media2.session.SessionToken.TokenType public int getType();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isLegacySession();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SessionToken.OnSessionTokenCreatedListener {
-    method public void onSessionTokenCreated(android.support.v4.media.session.MediaSessionCompat.Token!, androidx.media2.session.SessionToken!);
-  }
-
-  @IntDef({androidx.media2.session.SessionToken.TYPE_SESSION, androidx.media2.session.SessionToken.TYPE_SESSION_SERVICE, androidx.media2.session.SessionToken.TYPE_LIBRARY_SERVICE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionToken.TokenType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_1.0.0-beta02.txt b/media2/media2-session/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index 1b3f110..0000000
--- a/media2/media2-session/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,455 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @androidx.media2.session.LibraryResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface LibraryResult.ResultCode {
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(@androidx.media2.session.MediaController.VolumeDirection int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public void close();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
-    method public boolean isConnected();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromUri(android.net.Uri, android.os.Bundle?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromUri(android.net.Uri, android.os.Bundle?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setTimeDiff(Long!);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @IntDef({android.media.AudioManager.ADJUST_LOWER, android.media.AudioManager.ADJUST_RAISE, android.media.AudioManager.ADJUST_SAME, android.media.AudioManager.ADJUST_MUTE, android.media.AudioManager.ADJUST_UNMUTE, android.media.AudioManager.ADJUST_TOGGLE_MUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeDirection {
-  }
-
-  @IntDef(value={android.media.AudioManager.FLAG_SHOW_UI, android.media.AudioManager.FLAG_ALLOW_RINGER_MODES, android.media.AudioManager.FLAG_PLAY_SOUND, android.media.AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE, android.media.AudioManager.FLAG_VIBRATE}, flag=true) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeFlags {
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.MediaSessionCompat! getSessionCompat();
-    method public androidx.media2.session.SessionToken getToken();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isClosed();
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public androidx.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media.MediaSessionManager.RemoteUserInfo getRemoteUserInfo();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isTrusted();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method @androidx.media2.session.SessionResult.ResultCode public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @androidx.media2.session.SessionResult.ResultCode public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method @androidx.media2.session.RemoteSessionPlayer.VolumeControlType public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @IntDef({androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_FIXED, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_RELATIVE, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteSessionPlayer.VolumeControlType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method @androidx.media2.session.SessionCommand.CommandCode public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID = 40004; // 0x9c44
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_SEARCH = 40005; // 0x9c45
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_URI = 40006; // 0x9c46
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID = 40007; // 0x9c47
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH = 40008; // 0x9c48
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_URI = 40009; // 0x9c49
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_VERSION_CURRENT = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PREPARE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, 0x2af8, 0x2af9, 0x2afa, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandCode {
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_VERSION_1}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandVersion {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(@androidx.media2.session.SessionCommand.CommandVersion int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class SessionResult implements androidx.media2.common.BaseResult androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(@androidx.media2.session.SessionResult.ResultCode int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.session.SessionResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.session.SessionResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionResult.ResultCode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void createSessionToken(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token, java.util.concurrent.Executor, androidx.media2.session.SessionToken.OnSessionTokenCreatedListener);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object! getBinder();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.ComponentName! getComponentName();
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method @androidx.media2.session.SessionToken.TokenType public int getType();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isLegacySession();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SessionToken.OnSessionTokenCreatedListener {
-    method public void onSessionTokenCreated(android.support.v4.media.session.MediaSessionCompat.Token!, androidx.media2.session.SessionToken!);
-  }
-
-  @IntDef({androidx.media2.session.SessionToken.TYPE_SESSION, androidx.media2.session.SessionToken.TYPE_SESSION_SERVICE, androidx.media2.session.SessionToken.TYPE_LIBRARY_SERVICE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionToken.TokenType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_1.0.0-rc01.txt b/media2/media2-session/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index 47a8e48..0000000
--- a/media2/media2-session/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1,455 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.session {
-
-  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(@androidx.media2.session.LibraryResult.ResultCode int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @androidx.media2.session.LibraryResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-  }
-
-  @IntDef(flag=false, value={androidx.media2.common.BaseResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface LibraryResult.ResultCode {
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaController implements java.lang.AutoCloseable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(@androidx.media2.session.MediaController.VolumeDirection int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
-    method public android.app.PendingIntent? getSessionActivity();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> playFromUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromMediaId(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromSearch(String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepareFromUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setTimeDiff(Long!);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, @androidx.media2.session.MediaController.VolumeFlags int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @IntDef({android.media.AudioManager.ADJUST_LOWER, android.media.AudioManager.ADJUST_RAISE, android.media.AudioManager.ADJUST_SAME, android.media.AudioManager.ADJUST_MUTE, android.media.AudioManager.ADJUST_UNMUTE, android.media.AudioManager.ADJUST_TOGGLE_MUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeDirection {
-  }
-
-  @IntDef(value={android.media.AudioManager.FLAG_SHOW_UI, android.media.AudioManager.FLAG_ALLOW_RINGER_MODES, android.media.AudioManager.FLAG_PLAY_SOUND, android.media.AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE, android.media.AudioManager.FLAG_VIBRATE}, flag=true) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaController.VolumeFlags {
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @androidx.media2.session.LibraryResult.ResultCode public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.lang.AutoCloseable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.MediaSessionCompat! getSessionCompat();
-    method public androidx.media2.session.SessionToken getToken();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isClosed();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.media.MediaSessionManager.RemoteUserInfo getRemoteUserInfo();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isTrusted();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method @androidx.media2.session.SessionResult.ResultCode public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPlayFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromMediaId(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromSearch(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, android.os.Bundle?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.media2.session.SessionResult.ResultCode public int onPrepareFromUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @androidx.media2.session.SessionResult.ResultCode public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @androidx.media2.session.SessionResult.ResultCode public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @RequiresApi(28) public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method @androidx.media2.session.RemoteSessionPlayer.VolumeControlType public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @IntDef({androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_FIXED, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_RELATIVE, androidx.media2.session.RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteSessionPlayer.VolumeControlType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method @androidx.media2.session.SessionCommand.CommandCode public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID = 40004; // 0x9c44
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_SEARCH = 40005; // 0x9c45
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PLAY_FROM_URI = 40006; // 0x9c46
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID = 40007; // 0x9c47
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH = 40008; // 0x9c48
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_CODE_SESSION_PREPARE_FROM_URI = 40009; // 0x9c49
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int COMMAND_VERSION_CURRENT = 1; // 0x1
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PREPARE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA, androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, 0x2af8, 0x2af9, 0x2afa, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_URI, androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH, androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandCode {
-  }
-
-  @IntDef({androidx.media2.session.SessionCommand.COMMAND_VERSION_1}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionCommand.CommandVersion {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(@androidx.media2.session.SessionCommand.CommandCode int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(@androidx.media2.session.SessionCommand.CommandVersion int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class SessionResult implements androidx.media2.common.BaseResult androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionResult(@androidx.media2.session.SessionResult.ResultCode int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method @androidx.media2.session.SessionResult.ResultCode public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @IntDef(flag=false, value={androidx.media2.session.SessionResult.RESULT_SUCCESS, androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN, androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE, androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE, androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media2.common.BaseResult.RESULT_ERROR_IO, androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media2.common.BaseResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media2.session.RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionResult.ResultCode {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void createSessionToken(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token, java.util.concurrent.Executor, androidx.media2.session.SessionToken.OnSessionTokenCreatedListener);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object! getBinder();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.ComponentName! getComponentName();
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method @androidx.media2.session.SessionToken.TokenType public int getType();
-    method public int getUid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isLegacySession();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SessionToken.OnSessionTokenCreatedListener {
-    method public void onSessionTokenCreated(android.support.v4.media.session.MediaSessionCompat.Token!, androidx.media2.session.SessionToken!);
-  }
-
-  @IntDef({androidx.media2.session.SessionToken.TYPE_SESSION, androidx.media2.session.SessionToken.TYPE_SESSION_SERVICE, androidx.media2.session.SessionToken.TYPE_LIBRARY_SERVICE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SessionToken.TokenType {
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_1.1.0-beta01.txt b/media2/media2-session/api/restricted_1.1.0-beta01.txt
deleted file mode 100644
index dcc76f8..0000000
--- a/media2/media2-session/api/restricted_1.1.0-beta01.txt
+++ /dev/null
@@ -1,432 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaConstants {
-    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field public static final String MEDIA_URI_QUERY_ID = "id";
-    field public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  public class MediaController implements java.io.Closeable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(@androidx.media2.common.SessionPlayer.TrackInfo.MediaTrackType int);
-    method public android.app.PendingIntent? getSessionActivity();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.io.Closeable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_1.2.0-beta01.txt b/media2/media2-session/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index e75539b..0000000
--- a/media2/media2-session/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1,434 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor public HeartRating();
-    ctor public HeartRating(boolean);
-    method public boolean hasHeart();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public LibraryResult(int);
-    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public long getCompletionTime();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-  }
-
-  public class MediaBrowser extends androidx.media2.session.MediaController {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor public MediaBrowser.BrowserCallback();
-    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaBrowser.Builder {
-    ctor public MediaBrowser.Builder(android.content.Context);
-    method public androidx.media2.session.MediaBrowser build();
-    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public class MediaConstants {
-    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field public static final String MEDIA_URI_QUERY_ID = "id";
-    field public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field public static final String MEDIA_URI_QUERY_URI = "uri";
-    field public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  public class MediaController implements java.io.Closeable {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method public void close();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method public long getBufferedPosition();
-    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
-    method public androidx.media2.session.SessionToken? getConnectedToken();
-    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method public int getCurrentMediaItemIndex();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public int getNextMediaItemIndex();
-    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method public float getPlaybackSpeed();
-    method public int getPlayerState();
-    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method public int getPreviousMediaItemIndex();
-    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
-    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(@androidx.media2.common.SessionPlayer.TrackInfo.MediaTrackType int);
-    method public android.app.PendingIntent? getSessionActivity();
-    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
-    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method public androidx.media2.common.VideoSize getVideoSize();
-    method public boolean isConnected();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  public static final class MediaController.Builder {
-    ctor public MediaController.Builder(android.content.Context);
-    method public androidx.media2.session.MediaController build();
-    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  public abstract static class MediaController.ControllerCallback {
-    ctor public MediaController.ControllerCallback();
-    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
-    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaController);
-    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
-    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
-    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
-    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method public int getControlType();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor public MediaLibraryService();
-    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method public android.os.Bundle? getExtras();
-    method public boolean isOffline();
-    method public boolean isRecent();
-    method public boolean isSuggested();
-  }
-
-  public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor public MediaLibraryService.LibraryParams.Builder();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  public class MediaSession implements java.io.Closeable {
-    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void close();
-    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method public String getId();
-    method public androidx.media2.common.SessionPlayer getPlayer();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method public androidx.media2.session.SessionToken getToken();
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static final class MediaSession.Builder {
-    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method public androidx.media2.session.MediaSession build();
-    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method public androidx.media2.session.MediaSession.Builder setId(String);
-    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method public androidx.media2.session.SessionCommand? getCommand();
-    method public CharSequence? getDisplayName();
-    method public android.os.Bundle? getExtras();
-    method public int getIconResId();
-    method public boolean isEnabled();
-  }
-
-  public static final class MediaSession.CommandButton.Builder {
-    ctor public MediaSession.CommandButton.Builder();
-    method public androidx.media2.session.MediaSession.CommandButton build();
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  public static final class MediaSession.ControllerInfo {
-    method public android.os.Bundle getConnectionHints();
-    method public String getPackageName();
-    method public int getUid();
-  }
-
-  public abstract static class MediaSession.SessionCallback {
-    ctor public MediaSession.SessionCallback();
-    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  public final class MediaSessionManager {
-    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  public abstract class MediaSessionService extends android.app.Service {
-    ctor public MediaSessionService();
-    method public final void addSession(androidx.media2.session.MediaSession);
-    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method public final void removeSession(androidx.media2.session.MediaSession);
-    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  public static class MediaSessionService.MediaNotification {
-    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method public android.app.Notification getNotification();
-    method public int getNotificationId();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor public PercentageRating();
-    ctor public PercentageRating(float);
-    method public float getPercentRating();
-    method public boolean isRated();
-  }
-
-  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor public RemoteSessionPlayer();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method public abstract int getMaxVolume();
-    method public abstract int getVolume();
-    method public abstract int getVolumeControlType();
-    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor public RemoteSessionPlayer.Callback();
-    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommand(int);
-    ctor public SessionCommand(String, android.os.Bundle?);
-    method public int getCommandCode();
-    method public String? getCustomAction();
-    method public android.os.Bundle? getCustomExtras();
-    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionCommandGroup();
-    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method public boolean hasCommand(int);
-  }
-
-  public static final class SessionCommandGroup.Builder {
-    ctor public SessionCommandGroup.Builder();
-    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method public androidx.media2.session.SessionCommandGroup build();
-    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
-    ctor public SessionResult(int, android.os.Bundle?);
-    method public long getCompletionTime();
-    method public android.os.Bundle? getCustomCommandResult();
-    method public androidx.media2.common.MediaItem? getMediaItem();
-    method public int getResultCode();
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SessionToken(android.content.Context, android.content.ComponentName);
-    method public android.os.Bundle getExtras();
-    method public String getPackageName();
-    method public String? getServiceName();
-    method public int getType();
-    method public int getUid();
-    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field public static final int TYPE_SESSION = 0; // 0x0
-    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor public StarRating(@IntRange(from=1) int);
-    ctor public StarRating(@IntRange(from=1) int, float);
-    method public int getMaxStars();
-    method public float getStarRating();
-    method public boolean isRated();
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor public ThumbRating();
-    ctor public ThumbRating(boolean);
-    method public boolean isRated();
-    method public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_1.3.0-beta01.txt b/media2/media2-session/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index a7e3a862..0000000
--- a/media2/media2-session/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,449 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public HeartRating();
-    ctor @Deprecated public HeartRating(boolean);
-    method @Deprecated public boolean hasHeart();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public LibraryResult(int);
-    ctor @Deprecated public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor @Deprecated public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public class MediaBrowser extends androidx.media2.session.MediaController {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  @Deprecated public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor @Deprecated public MediaBrowser.BrowserCallback();
-    method @Deprecated public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaBrowser.Builder {
-    ctor @Deprecated public MediaBrowser.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaBrowser build();
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public class MediaConstants {
-    field @Deprecated public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field @Deprecated public static final String MEDIA_URI_QUERY_ID = "id";
-    field @Deprecated public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field @Deprecated public static final String MEDIA_URI_QUERY_URI = "uri";
-    field @Deprecated public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  @Deprecated public class MediaController implements java.io.Closeable {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method @Deprecated public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.session.SessionToken? getConnectedToken();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method @Deprecated public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public android.app.PendingIntent? getSessionActivity();
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public boolean isConnected();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  @Deprecated public static final class MediaController.Builder {
-    ctor @Deprecated public MediaController.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaController build();
-    method @Deprecated public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public abstract static class MediaController.ControllerCallback {
-    ctor @Deprecated public MediaController.ControllerCallback();
-    method @Deprecated public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method @Deprecated public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @Deprecated public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getControlType();
-    method @Deprecated public int getCurrentVolume();
-    method @Deprecated public int getMaxVolume();
-    method @Deprecated public int getPlaybackType();
-    field @Deprecated public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field @Deprecated public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @Deprecated public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor @Deprecated public MediaLibraryService();
-    method @Deprecated public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public boolean isOffline();
-    method @Deprecated public boolean isRecent();
-    method @Deprecated public boolean isSuggested();
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor @Deprecated public MediaLibraryService.LibraryParams.Builder();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method @Deprecated public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  @Deprecated public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method @Deprecated public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  @Deprecated public class MediaSession implements java.io.Closeable {
-    method @Deprecated public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void close();
-    method @Deprecated public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method @Deprecated public String getId();
-    method @Deprecated public androidx.media2.common.SessionPlayer getPlayer();
-    method @Deprecated public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method @Deprecated public androidx.media2.session.SessionToken getToken();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static final class MediaSession.Builder {
-    ctor @Deprecated public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method @Deprecated public androidx.media2.session.MediaSession build();
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.session.SessionCommand? getCommand();
-    method @Deprecated public CharSequence? getDisplayName();
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public int getIconResId();
-    method @Deprecated public boolean isEnabled();
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton.Builder {
-    ctor @Deprecated public MediaSession.CommandButton.Builder();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton build();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  @Deprecated public static final class MediaSession.ControllerInfo {
-    method @Deprecated public android.os.Bundle getConnectionHints();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public int getUid();
-  }
-
-  @Deprecated public abstract static class MediaSession.SessionCallback {
-    ctor @Deprecated public MediaSession.SessionCallback();
-    method @Deprecated public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @Deprecated public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @Deprecated public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @Deprecated public final class MediaSessionManager {
-    method @Deprecated public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  @Deprecated public abstract class MediaSessionService extends android.app.Service {
-    ctor @Deprecated public MediaSessionService();
-    method @Deprecated public final void addSession(androidx.media2.session.MediaSession);
-    method @Deprecated public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @Deprecated @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method @Deprecated public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method @Deprecated public final void removeSession(androidx.media2.session.MediaSession);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  @Deprecated public static class MediaSessionService.MediaNotification {
-    ctor @Deprecated public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method @Deprecated public android.app.Notification getNotification();
-    method @Deprecated public int getNotificationId();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public PercentageRating();
-    ctor @Deprecated public PercentageRating(float);
-    method @Deprecated public float getPercentRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public RemoteSessionPlayer();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method @Deprecated public abstract int getMaxVolume();
-    method @Deprecated public abstract int getVolume();
-    method @Deprecated public abstract int getVolumeControlType();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field @Deprecated public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field @Deprecated public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field @Deprecated public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  @Deprecated public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public RemoteSessionPlayer.Callback();
-    method @Deprecated public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommand(int);
-    ctor @Deprecated public SessionCommand(String, android.os.Bundle?);
-    method @Deprecated public int getCommandCode();
-    method @Deprecated public String? getCustomAction();
-    method @Deprecated public android.os.Bundle? getCustomExtras();
-    field @Deprecated public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field @Deprecated public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @Deprecated public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field @Deprecated public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @Deprecated public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommandGroup();
-    ctor @Deprecated public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method @Deprecated public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public boolean hasCommand(int);
-  }
-
-  @Deprecated public static final class SessionCommandGroup.Builder {
-    ctor @Deprecated public SessionCommandGroup.Builder();
-    ctor @Deprecated public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup build();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public SessionResult(int, android.os.Bundle?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public android.os.Bundle? getCustomCommandResult();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field @Deprecated public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionToken(android.content.Context, android.content.ComponentName);
-    method @Deprecated public android.os.Bundle getExtras();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public String? getServiceName();
-    method @Deprecated public int getType();
-    method @Deprecated public int getUid();
-    field @Deprecated public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field @Deprecated public static final int TYPE_SESSION = 0; // 0x0
-    field @Deprecated public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public StarRating(@IntRange(from=1) int);
-    ctor @Deprecated public StarRating(@IntRange(from=1) int, float);
-    method @Deprecated public int getMaxStars();
-    method @Deprecated public float getStarRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public ThumbRating();
-    ctor @Deprecated public ThumbRating(boolean);
-    method @Deprecated public boolean isRated();
-    method @Deprecated public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/api/restricted_current.ignore b/media2/media2-session/api/restricted_current.ignore
deleted file mode 100644
index e3a354e..0000000
--- a/media2/media2-session/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedInterface: androidx.media2.session.LibraryResult:
-    Class androidx.media2.session.LibraryResult no longer implements androidx.media2.common.BaseResult
-RemovedInterface: androidx.media2.session.SessionResult:
-    Class androidx.media2.session.SessionResult no longer implements androidx.media2.common.BaseResult
diff --git a/media2/media2-session/api/restricted_current.txt b/media2/media2-session/api/restricted_current.txt
deleted file mode 100644
index a7e3a862..0000000
--- a/media2/media2-session/api/restricted_current.txt
+++ /dev/null
@@ -1,449 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.session {
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public HeartRating();
-    ctor @Deprecated public HeartRating(boolean);
-    method @Deprecated public boolean hasHeart();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public LibraryResult(int);
-    ctor @Deprecated public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    ctor @Deprecated public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated public class MediaBrowser extends androidx.media2.session.MediaController {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
-  }
-
-  @Deprecated public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
-    ctor @Deprecated public MediaBrowser.BrowserCallback();
-    method @Deprecated public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaBrowser.Builder {
-    ctor @Deprecated public MediaBrowser.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaBrowser build();
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public class MediaConstants {
-    field @Deprecated public static final String MEDIA_URI_AUTHORITY = "media2-session";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-    field @Deprecated public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-    field @Deprecated public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-    field @Deprecated public static final String MEDIA_URI_QUERY_ID = "id";
-    field @Deprecated public static final String MEDIA_URI_QUERY_QUERY = "query";
-    field @Deprecated public static final String MEDIA_URI_QUERY_URI = "uri";
-    field @Deprecated public static final String MEDIA_URI_SCHEME = "androidx";
-  }
-
-  @Deprecated public class MediaController implements java.io.Closeable {
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
-    method @Deprecated public void close();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
-    method @Deprecated public long getBufferedPosition();
-    method @Deprecated public int getBufferingState();
-    method @Deprecated public androidx.media2.session.SessionToken? getConnectedToken();
-    method @Deprecated public androidx.media2.common.MediaItem? getCurrentMediaItem();
-    method @Deprecated public int getCurrentMediaItemIndex();
-    method @Deprecated public long getCurrentPosition();
-    method @Deprecated public long getDuration();
-    method @Deprecated public int getNextMediaItemIndex();
-    method @Deprecated public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
-    method @Deprecated public float getPlaybackSpeed();
-    method @Deprecated public int getPlayerState();
-    method @Deprecated public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
-    method @Deprecated public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
-    method @Deprecated public int getPreviousMediaItemIndex();
-    method @Deprecated public int getRepeatMode();
-    method @Deprecated public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
-    method @Deprecated public android.app.PendingIntent? getSessionActivity();
-    method @Deprecated public int getShuffleMode();
-    method @Deprecated public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
-    method @Deprecated public androidx.media2.common.VideoSize getVideoSize();
-    method @Deprecated public boolean isConnected();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
-  }
-
-  @Deprecated public static final class MediaController.Builder {
-    ctor @Deprecated public MediaController.Builder(android.content.Context);
-    method @Deprecated public androidx.media2.session.MediaController build();
-    method @Deprecated public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
-    method @Deprecated public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
-  }
-
-  @Deprecated public abstract static class MediaController.ControllerCallback {
-    ctor @Deprecated public MediaController.ControllerCallback();
-    method @Deprecated public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
-    method @Deprecated public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackCompleted(androidx.media2.session.MediaController);
-    method @Deprecated public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
-    method @Deprecated public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
-    method @Deprecated public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
-    method @Deprecated public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSeekCompleted(androidx.media2.session.MediaController, long);
-    method @Deprecated public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
-    method @Deprecated public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
-    method @Deprecated public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
-    method @Deprecated public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
-    method @Deprecated public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media.AudioAttributesCompat? getAudioAttributes();
-    method @Deprecated public int getControlType();
-    method @Deprecated public int getCurrentVolume();
-    method @Deprecated public int getMaxVolume();
-    method @Deprecated public int getPlaybackType();
-    field @Deprecated public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field @Deprecated public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  @Deprecated public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
-    ctor @Deprecated public MediaLibraryService();
-    method @Deprecated public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public boolean isOffline();
-    method @Deprecated public boolean isRecent();
-    method @Deprecated public boolean isSuggested();
-  }
-
-  @Deprecated public static final class MediaLibraryService.LibraryParams.Builder {
-    ctor @Deprecated public MediaLibraryService.LibraryParams.Builder();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
-    method @Deprecated public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-  }
-
-  @Deprecated public static final class MediaLibraryService.MediaLibrarySession.Builder {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
-  }
-
-  @Deprecated public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
-    ctor @Deprecated public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
-    method @Deprecated public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
-    method @Deprecated public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
-  }
-
-  @Deprecated public class MediaSession implements java.io.Closeable {
-    method @Deprecated public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void close();
-    method @Deprecated public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
-    method @Deprecated public String getId();
-    method @Deprecated public androidx.media2.common.SessionPlayer getPlayer();
-    method @Deprecated public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
-    method @Deprecated public androidx.media2.session.SessionToken getToken();
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
-    method @Deprecated public void updatePlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static final class MediaSession.Builder {
-    ctor @Deprecated public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
-    method @Deprecated public androidx.media2.session.MediaSession build();
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setId(String);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
-    method @Deprecated public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
-    method @Deprecated public androidx.media2.session.SessionCommand? getCommand();
-    method @Deprecated public CharSequence? getDisplayName();
-    method @Deprecated public android.os.Bundle? getExtras();
-    method @Deprecated public int getIconResId();
-    method @Deprecated public boolean isEnabled();
-  }
-
-  @Deprecated public static final class MediaSession.CommandButton.Builder {
-    ctor @Deprecated public MediaSession.CommandButton.Builder();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton build();
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
-    method @Deprecated public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
-  }
-
-  @Deprecated public static final class MediaSession.ControllerInfo {
-    method @Deprecated public android.os.Bundle getConnectionHints();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public int getUid();
-  }
-
-  @Deprecated public abstract static class MediaSession.SessionCallback {
-    ctor @Deprecated public MediaSession.SessionCallback();
-    method @Deprecated public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
-    method @Deprecated public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
-    method @Deprecated public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
-    method @Deprecated public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
-    method @Deprecated public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
-  }
-
-  @Deprecated public final class MediaSessionManager {
-    method @Deprecated public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
-  }
-
-  @Deprecated public abstract class MediaSessionService extends android.app.Service {
-    ctor @Deprecated public MediaSessionService();
-    method @Deprecated public final void addSession(androidx.media2.session.MediaSession);
-    method @Deprecated public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
-    method @Deprecated @CallSuper public android.os.IBinder? onBind(android.content.Intent);
-    method @Deprecated public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
-    method @Deprecated public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
-    method @Deprecated public final void removeSession(androidx.media2.session.MediaSession);
-    field @Deprecated public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-  }
-
-  @Deprecated public static class MediaSessionService.MediaNotification {
-    ctor @Deprecated public MediaSessionService.MediaNotification(int, android.app.Notification);
-    method @Deprecated public android.app.Notification getNotification();
-    method @Deprecated public int getNotificationId();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public PercentageRating();
-    ctor @Deprecated public PercentageRating(float);
-    method @Deprecated public float getPercentRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
-    ctor @Deprecated public RemoteSessionPlayer();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
-    method @Deprecated public abstract int getMaxVolume();
-    method @Deprecated public abstract int getVolume();
-    method @Deprecated public abstract int getVolumeControlType();
-    method @Deprecated public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
-    field @Deprecated public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field @Deprecated public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field @Deprecated public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
-  @Deprecated public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
-    ctor @Deprecated public RemoteSessionPlayer.Callback();
-    method @Deprecated public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommand(int);
-    ctor @Deprecated public SessionCommand(String, android.os.Bundle?);
-    method @Deprecated public int getCommandCode();
-    method @Deprecated public String? getCustomAction();
-    method @Deprecated public android.os.Bundle? getCustomExtras();
-    field @Deprecated public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
-    field @Deprecated public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
-    field @Deprecated public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
-    field @Deprecated public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
-    field @Deprecated public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
-    field @Deprecated public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
-    field @Deprecated public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
-    field @Deprecated public static final int COMMAND_VERSION_1 = 1; // 0x1
-    field @Deprecated public static final int COMMAND_VERSION_2 = 2; // 0x2
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionCommandGroup();
-    ctor @Deprecated public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
-    method @Deprecated public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
-    method @Deprecated public boolean hasCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public boolean hasCommand(int);
-  }
-
-  @Deprecated public static final class SessionCommandGroup.Builder {
-    ctor @Deprecated public SessionCommandGroup.Builder();
-    ctor @Deprecated public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
-    method @Deprecated public androidx.media2.session.SessionCommandGroup build();
-    method @Deprecated public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @Deprecated public SessionResult(int, android.os.Bundle?);
-    method @Deprecated public long getCompletionTime();
-    method @Deprecated public android.os.Bundle? getCustomCommandResult();
-    method @Deprecated public androidx.media2.common.MediaItem? getMediaItem();
-    method @Deprecated public int getResultCode();
-    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
-    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
-    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
-    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
-    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
-    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
-    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
-    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
-    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
-    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
-    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
-    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
-    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
-    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
-    field @Deprecated public static final int RESULT_SUCCESS = 0; // 0x0
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
-    ctor @Deprecated public SessionToken(android.content.Context, android.content.ComponentName);
-    method @Deprecated public android.os.Bundle getExtras();
-    method @Deprecated public String getPackageName();
-    method @Deprecated public String? getServiceName();
-    method @Deprecated public int getType();
-    method @Deprecated public int getUid();
-    field @Deprecated public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
-    field @Deprecated public static final int TYPE_SESSION = 0; // 0x0
-    field @Deprecated public static final int TYPE_SESSION_SERVICE = 1; // 0x1
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public StarRating(@IntRange(from=1) int);
-    ctor @Deprecated public StarRating(@IntRange(from=1) int, float);
-    method @Deprecated public int getMaxStars();
-    method @Deprecated public float getStarRating();
-    method @Deprecated public boolean isRated();
-  }
-
-  @Deprecated @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
-    ctor @Deprecated public ThumbRating();
-    ctor @Deprecated public ThumbRating(boolean);
-    method @Deprecated public boolean isRated();
-    method @Deprecated public boolean isThumbUp();
-  }
-
-}
-
diff --git a/media2/media2-session/build.gradle b/media2/media2-session/build.gradle
deleted file mode 100644
index c2a30ee..0000000
--- a/media2/media2-session/build.gradle
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2018 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.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("androidx.stableaidl")
-}
-
-apply(from: "../constants.gradle")
-
-dependencies {
-    api(project(":media2:media2-common"))
-    api(libs.guavaListenableFuture)
-    implementation("androidx.collection:collection:" + COLLECTION_VERSION)
-    implementation("androidx.concurrent:concurrent-futures:" + CONCURRENT_FUTURE_VERSION)
-    compileOnly(libs.checkerframework)
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(project(":internal-testutils-runtime"))
-    annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
-}
-
-android {
-    buildFeatures {
-        aidl = true
-    }
-    buildTypes.all {
-        stableAidl {
-            version 1
-        }
-    }
-    namespace "androidx.media2.session"
-}
-
-androidx {
-    name = "Media2 Session"
-    publish = Publish.SNAPSHOT_AND_RELEASE
-    inceptionYear = "2018"
-    description = "Media2 Session"
-    failOnDeprecationWarnings = false
-    metalavaK2UastEnabled = true
-}
diff --git a/media2/media2-session/lint-baseline.xml b/media2/media2-session/lint-baseline.xml
deleted file mode 100644
index c66e18b..0000000
--- a/media2/media2-session/lint-baseline.xml
+++ /dev/null
@@ -1,814 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: VolumeProviderCompat.VOLUME_CONTROL_FIXED, VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE"
-        errorLine1="        return new VolumeProviderCompat(player.getVolumeControlType(), player.getMaxVolume(),"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionLegacyStub.java"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: SessionResult.RESULT_SUCCESS, BaseResult.RESULT_ERROR_UNKNOWN, BaseResult.RESULT_ERROR_INVALID_STATE, BaseResult.RESULT_ERROR_BAD_VALUE, BaseResult.RESULT_ERROR_PERMISSION_DENIED, BaseResult.RESULT_ERROR_IO, BaseResult.RESULT_INFO_SKIPPED, RemoteResult.RESULT_ERROR_SESSION_DISCONNECTED, BaseResult.RESULT_ERROR_NOT_SUPPORTED, RemoteResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, RemoteResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, RemoteResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, RemoteResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, RemoteResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, RemoteResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, RemoteResult.RESULT_ERROR_SESSION_SETUP_REQUIRED, but could be BaseResult.RESULT_SUCCESS"
-        errorLine1="        return new SessionResult(result.getResultCode(), null, result.getMediaItem(),"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/SessionResult.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java"/>
-    </issue>
-
-    <issue
-        id="UnspecifiedRegisterReceiverFlag"
-        message="`mBroadcastReceiver` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for android.intent.action.MEDIA_BUTTON"
-        errorLine1="                context.registerReceiver(mBroadcastReceiver, filter);"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionImplBase.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onSessionResult(final int seq, final ParcelImpl sessionResult) {"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onLibraryResult(final int seq, final ParcelImpl libraryResult) {"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onCurrentMediaItemChanged(int seq, final ParcelImpl item, final int currentIdx,"
-        errorLine2="                                                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onBufferingStateChanged(int seq, final ParcelImpl item, @BuffState final int state,"
-        errorLine2="                                                       ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl metadata, final int currentIdx, final int previousIdx,"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onPlaylistMetadataChanged(int seq, final ParcelImpl metadata)"
-        errorLine2="                                                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onPlaybackInfoChanged(int seq, final ParcelImpl playbackInfo)"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onVideoSizeChanged(int seq, final ParcelImpl item, final ParcelImpl videoSize) {"
-        errorLine2="                                                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onVideoSizeChanged(int seq, final ParcelImpl item, final ParcelImpl videoSize) {"
-        errorLine2="                                                                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onSubtitleData(int seq, final ParcelImpl item, final ParcelImpl track,"
-        errorLine2="                                              ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onSubtitleData(int seq, final ParcelImpl item, final ParcelImpl track,"
-        errorLine2="                                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl data) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onConnected(int seq, ParcelImpl connectionResult) {"
-        errorLine2="                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onSetCustomLayout(final int seq, final List&lt;ParcelImpl> commandButtonList) {"
-        errorLine2="                                                            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onAllowedCommandsChanged(int seq, final ParcelImpl commands) {"
-        errorLine2="                                                        ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onCustomCommand(final int seq, final ParcelImpl commandParcel, final Bundle args) {"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onTrackInfoChanged(final int seq, final List&lt;ParcelImpl> trackInfoList,"
-        errorLine2="                                                             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl selectedVideoParcel, final ParcelImpl selectedAudioParcel,"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl selectedVideoParcel, final ParcelImpl selectedAudioParcel,"
-        errorLine2="                                                        ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl selectedSubtitleParcel, final ParcelImpl selectedMetadataParcel) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl selectedSubtitleParcel, final ParcelImpl selectedMetadataParcel) {"
-        errorLine2="                                                           ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onTrackSelected(final int seq, final ParcelImpl trackInfoParcel) {"
-        errorLine2="                                                     ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void onTrackDeselected(final int seq, final ParcelImpl trackInfoParcel) {"
-        errorLine2="                                                       ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl libraryParams) throws RuntimeException {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl libraryParams) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaControllerStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="MediaBrowserServiceCompat.onSubscribe can only be called from within the same library (androidx.media:media)"
-        errorLine1="    public void onSubscribe(final String id, final Bundle option) {"
-        errorLine2="                ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="MediaBrowserServiceCompat.onUnsubscribe can only be called from within the same library (androidx.media:media)"
-        errorLine1="    public void onUnsubscribe(final String id) {"
-        errorLine2="                ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        public void connect(final IMediaController caller, final ParcelImpl connectionRequest) {"
-        errorLine2="                                                                 ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionServiceImplBase.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void connect(final IMediaController caller, int seq, ParcelImpl connectionRequest)"
-        errorLine2="                                                                ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl sessionResult) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl command, final Bundle args) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl ratingParcelable) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl metadata) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl metadata) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void selectTrack(IMediaController caller, int seq, final ParcelImpl trackInfoParcel) {"
-        errorLine2="                                                                    ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public void deselectTrack(IMediaController caller, int seq, final ParcelImpl trackInfoParcel) {"
-        errorLine2="                                                                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl libraryParams) throws RuntimeException {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final int page, final int pageSize, final ParcelImpl libraryParams)"
-        errorLine2="                                                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl libraryParams) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final int page, final int pageSize, final ParcelImpl libraryParams) {"
-        errorLine2="                                                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl libraryParams) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            ParcelImpl videoSizeParcel = MediaParcelUtils.toParcelable(videoSize);"
-        errorLine2="            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            List&lt;ParcelImpl> trackInfoList = MediaParcelUtils.toParcelableList(tracks);"
-        errorLine2="                 ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            ParcelImpl itemParcel = MediaParcelUtils.toParcelable(item);"
-        errorLine2="            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            ParcelImpl trackParcel = MediaParcelUtils.toParcelable(track);"
-        errorLine2="            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            ParcelImpl dataParcel = MediaParcelUtils.toParcelable(data);"
-        errorLine2="            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaSessionStub.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        List&lt;ParcelImpl> parcelImplList = listSlice.getList();"
-        errorLine2="             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="            final ParcelImpl itemParcelImpl = parcelImplList.get(i);"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="    public static List&lt;ParcelImpl> convertCommandButtonListToParcelImplList("
-        errorLine2="                       ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        List&lt;ParcelImpl> parcelImplList = new ArrayList&lt;>();"
-        errorLine2="             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="        List&lt;ParcelImpl> itemParcelableList = new ArrayList&lt;>();"
-        errorLine2="             ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="ParcelImpl can only be accessed from within the same library (androidx.versionedparcelable:versionedparcelable)"
-        errorLine1="                final ParcelImpl itemParcelImpl = MediaParcelUtils.toParcelable(item);"
-        errorLine2="                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {"
-        errorLine2="                  ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaItem convertToMediaItem(QueueItem item) {"
-        errorLine2="                  ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaItem convertToMediaItem(QueueItem item) {"
-        errorLine2="                                               ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;MediaItem> convertMediaItemListToMediaItemList("
-        errorLine2="                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            List&lt;MediaBrowserCompat.MediaItem> items) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;MediaItem> convertQueueItemListToMediaItemList(List&lt;QueueItem> items) {"
-        errorLine2="                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;MediaItem> convertQueueItemListToMediaItemList(List&lt;QueueItem> items) {"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaDescriptionCompat createMediaDescriptionCompat(String mediaId) {"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaDescriptionCompat createMediaDescriptionCompat(String mediaId) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;QueueItem> convertToQueueItemList(List&lt;MediaItem> items) {"
-        errorLine2="                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;QueueItem> convertToQueueItemList(List&lt;MediaItem> items) {"
-        errorLine2="                                                         ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;MediaItem> convertParcelImplListSliceToMediaItemList("
-        errorLine2="                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            ParcelImplListSlice listSlice) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static &lt;T extends Parcelable> List&lt;T> truncateListBySize(final List&lt;T> list,"
-        errorLine2="                                         ~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static &lt;T extends Parcelable> List&lt;T> truncateListBySize(final List&lt;T> list,"
-        errorLine2="                                                                          ~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaMetadata convertToMediaMetadata(CharSequence queueTitle) {"
-        errorLine2="                  ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaMetadata convertToMediaMetadata(CharSequence queueTitle) {"
-        errorLine2="                                                       ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaMetadataCompat convertToMediaMetadataCompat(MediaMetadata metadata) {"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaMetadataCompat convertToMediaMetadataCompat(MediaMetadata metadata) {"
-        errorLine2="                                                                   ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Rating convertToRating(RatingCompat ratingCompat) {"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Rating convertToRating(RatingCompat ratingCompat) {"
-        errorLine2="                                         ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static RatingCompat convertToRatingCompat(Rating rating) {"
-        errorLine2="                  ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static RatingCompat convertToRatingCompat(Rating rating) {"
-        errorLine2="                                                     ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static List&lt;ParcelImpl> convertCommandButtonListToParcelImplList("
-        errorLine2="                  ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            List&lt;CommandButton> commandButtonList) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static ParcelImplListSlice convertMediaItemListToParcelImplListSlice("
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            List&lt;MediaItem> mediaItemList) {"
-        errorLine2="            ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static int convertToPlayerState(PlaybackStateCompat state) {"
-        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static MediaController.PlaybackInfo toPlaybackInfo2("
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            MediaControllerCompat.PlaybackInfo info) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean isUnparcelableBundle(Bundle bundle) {"
-        errorLine2="                                               ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void keepUnparcelableBundlesOnly(final List&lt;Bundle> bundles) {"
-        errorLine2="                                                         ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/session/MediaUtils.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-session/src/androidTest/AndroidManifest.xml b/media2/media2-session/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 0a74e49..0000000
--- a/media2/media2-session/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application android:usesCleartextTraffic="true">
-        <activity
-            android:name="androidx.media2.session.MockActivity"
-            android:exported="false" />
-        <activity
-            android:name="androidx.media2.session.SurfaceActivity"
-            android:exported="false" />
-
-        <!-- Keep the test services synced together with the MockMediaSessionService -->
-        <receiver
-            android:name="androidx.media.session.MediaButtonReceiver"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
-            </intent-filter>
-        </receiver>
-        <!-- Keep the test services synced together with the MockMediaLibraryService -->
-        <service
-            android:name="androidx.media2.session.MockMediaLibraryService"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaLibraryService" />
-                <action android:name="android.media.browse.MediaBrowserService" />
-            </intent-filter>
-        </service>
-        <!-- Keep the test services synced together with the MockMediaSessionService -->
-        <service
-            android:name="androidx.media2.session.MockMediaBrowserServiceCompat"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
-            </intent-filter>
-        </service>
-        <service
-            android:name="androidx.media2.session.MockMediaSessionService"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaSessionService" />
-                <action android:name="android.media.browse.MediaBrowserService" />
-            </intent-filter>
-        </service>
-    </application>
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-</manifest>
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java
deleted file mode 100644
index e021880..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java
+++ /dev/null
@@ -1,551 +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.media2.session;
-
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaDescriptionCompat;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media.MediaBrowserServiceCompat.Result;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MockMediaBrowserServiceCompat.Proxy;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaBrowser} with {@link MediaBrowserServiceCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaBrowserLegacyTest extends MediaSessionTestBase {
-
-    @Override
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints,
-            @Nullable final TestBrowserCallback callback) throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaBrowser.Builder builder = new MediaBrowser.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-
-    private MediaBrowser createBrowser(BrowserCallback callback) {
-        return createBrowser(true, null, callback);
-    }
-
-    private MediaBrowser createBrowser(boolean waitForConnect, Bundle connectionHints,
-            BrowserCallback callback) {
-        SessionToken token = new SessionToken(mContext,
-                new ComponentName(mContext, MockMediaBrowserServiceCompat.class));
-        try {
-            return (MediaBrowser) createController(token, waitForConnect, null, callback);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        fail("failed to create MediaBrowser for connecting MediaBrowserServiceCompat");
-        return null;
-    }
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void connect() throws InterruptedException {
-        MediaBrowser browser = createBrowser(new MediaBrowser.BrowserCallback() { });
-        // If connection failed, exception will be thrown inside of #createBrowser().
-    }
-
-    @Test
-    public void connect_rejected() throws InterruptedException {
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
-                    Bundle rootHints) {
-                return null;
-            }
-        });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaBrowser browser = createBrowser(false, null, new MediaBrowser.BrowserCallback() {
-            @Override
-            public void onConnected(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup allowedCommands) {
-                fail("shouldn't allow connection");
-                super.onConnected(controller, allowedCommands);
-            }
-
-            @Override
-            public void onDisconnected(@NonNull MediaController controller) {
-                super.onDisconnected(controller);
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getLibraryRoot() throws Exception {
-        final String testMediaId = "testGetLibraryRoot";
-        final Bundle testExtra = new Bundle();
-        testExtra.putString(testMediaId, testMediaId);
-        final LibraryParams testParams = new LibraryParams.Builder()
-                .setExtras(testExtra).setOffline(true).setRecent(true).setSuggested(true).build();
-
-        final BrowserRoot browserRootWithoutParam = new BrowserRoot(testMediaId, null);
-        final Bundle testReturnedExtra = new Bundle(testExtra);
-        testReturnedExtra.putBoolean(BrowserRoot.EXTRA_OFFLINE, true);
-        final BrowserRoot browserRootWithParam = new BrowserRoot(testMediaId, testReturnedExtra);
-
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
-                    Bundle rootHints) {
-                assertEquals(mContext.getPackageName(), clientPackageName);
-                if (rootHints != null && rootHints.keySet().contains(testMediaId)) {
-                    // This should happen because getLibraryRoot() is called with testExtras.
-                    return browserRootWithParam;
-                }
-                // This shouldn't be happen for getLibraryRoot(testParams)
-                return browserRootWithoutParam;
-            }
-        });
-
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getLibraryRoot(testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        TestUtils.assertMediaItemWithId(testMediaId, result.getMediaItem());
-
-        assertEquals(testReturnedExtra.getBoolean(BrowserRoot.EXTRA_RECENT),
-                result.getLibraryParams().isRecent());
-        assertEquals(testReturnedExtra.getBoolean(BrowserRoot.EXTRA_OFFLINE),
-                result.getLibraryParams().isOffline());
-        assertEquals(testReturnedExtra.getBoolean(BrowserRoot.EXTRA_SUGGESTED),
-                result.getLibraryParams().isSuggested());
-
-        // Note that TestUtils#equals() cannot be used for this because
-        // MediaBrowserServiceCompat adds extra_client_version to the rootHints.
-        assertTrue(TestUtils.contains(result.getLibraryParams().getExtras(), testExtra));
-    }
-
-    @Test
-    public void getItem() throws Exception {
-        final String testMediaId = "test_media_item";
-        final MediaBrowserCompat.MediaItem testItem = createMediaItem(testMediaId);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadItem(String itemId, Result<MediaBrowserCompat.MediaItem> result) {
-                assertEquals(testMediaId, itemId);
-                result.sendResult(testItem);
-            }
-        });
-
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getItem(testMediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertItemEquals(testItem, result.getMediaItem());
-    }
-
-    @Test
-    public void getItem_nullResult() throws Exception {
-        final String testMediaId = "test_media_item";
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadItem(String itemId, Result<MediaBrowserCompat.MediaItem> result) {
-                assertEquals(testMediaId, itemId);
-                result.sendResult(null);
-            }
-        });
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getItem(testMediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void getChildren_onLoadChildrenWithoutOptions() throws Exception {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaBrowserCompat.MediaItem> testFullMediaItemList = createMediaItems(
-                (testPage + 1) * testPageSize);
-        final List<MediaBrowserCompat.MediaItem> testPaginatedMediaItemList =
-                testFullMediaItemList.subList(testPage * testPageSize,
-                        Math.min((testPage + 1) * testPageSize, testFullMediaItemList.size()));
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result) {
-                result.sendResult(testFullMediaItemList);
-            }
-        });
-        MediaBrowser browser = createBrowser(null);
-        ListenableFuture<LibraryResult> future = browser
-                .getChildren(testParentId, testPage, testPageSize, null);
-        LibraryResult result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertItemsEquals(testPaginatedMediaItemList, result.getMediaItems());
-    }
-
-    @Test
-    public void getChildren_withoutOption() throws Exception {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaBrowserCompat.MediaItem> testMediaItemList = createMediaItems(testPageSize);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result) {
-                fail("This isn't expected to be called");
-            }
-
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertEquals(2, options.keySet().size());
-                result.sendResult(testMediaItemList);
-            }
-        });
-
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertItemsEquals(testMediaItemList, result.getMediaItems());
-        assertNull(result.getLibraryParams());
-    }
-
-    @Test
-    public void getChildren_withOption() throws Exception {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = TestUtils.createLibraryParams();
-        final List<MediaBrowserCompat.MediaItem> testMediaItemList =
-                createMediaItems(testPageSize / 2);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertTrue(TestUtils.contains(options, testParams.getExtras()));
-                result.sendResult(testMediaItemList);
-            }
-        });
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertItemsEquals(testMediaItemList, result.getMediaItems());
-        assertNull(result.getLibraryParams());
-    }
-
-    @Test
-    public void getChildren_nullResult() throws Exception {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                result.sendResult(null);
-            }
-        });
-
-        MediaBrowser browser = createBrowser(null);
-        LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getLibraryParams());
-    }
-
-    @Test
-    public void search() throws Exception {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = TestUtils.createLibraryParams();
-        final List<MediaBrowserCompat.MediaItem> testFullSearchResult = createMediaItems(
-                (testPage + 1) * testPageSize + 3);
-        final List<MediaBrowserCompat.MediaItem> testPaginatedSearchResult =
-                testFullSearchResult.subList(testPage * testPageSize,
-                        Math.min((testPage + 1) * testPageSize, testFullSearchResult.size()));
-
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras,
-                    Result<List<MediaBrowserCompat.MediaItem>> result) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.contains(extras, testParams.getExtras()));
-                if (extras != null && extras.getInt(MediaBrowserCompat.EXTRA_PAGE, -1) >= 0) {
-                    assertEquals(testPage, extras.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                    assertEquals(testPageSize, extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                    result.sendResult(testPaginatedSearchResult);
-                } else {
-                    result.sendResult(testFullSearchResult);
-                }
-            }
-        });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaBrowser browser = createBrowser(new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(MediaBrowser browser, String query, int itemCount,
-                    LibraryParams params) {
-                assertEquals(testQuery, query);
-                assertEquals(testFullSearchResult.size(), itemCount);
-                assertNull(params);
-                latch.countDown();
-            }
-        });
-
-        LibraryResult result = browser.search(testQuery, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        result = browser.getSearchResult(testQuery, testPage, testPageSize, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertItemsEquals(testPaginatedSearchResult, result.getMediaItems());
-    }
-
-    @Test
-    public void search_nullResult() throws Exception {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras,
-                    Result<List<MediaBrowserCompat.MediaItem>> result) {
-                result.sendResult(null);
-            }
-        });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaBrowser browser = createBrowser(new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(MediaBrowser browser, String query, int itemCount,
-                    LibraryParams params) {
-                assertEquals(testQuery, query);
-                assertEquals(0, itemCount);
-                latch.countDown();
-            }
-        });
-
-        LibraryResult result = browser.search(testQuery, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        result = browser.getSearchResult(testQuery, testPage, testPageSize, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    /**
-     * Tests following APIs
-     * <ul>
-     *    <li>{@link MediaBrowser#subscribe(String, LibraryParams)}</li>
-     *    <li>{@link MediaBrowser#unsubscribe(String)}</li>
-     *    <li>{@link MediaBrowser.BrowserCallback#onChildrenChanged}</li>
-     * </ul>
-     * @throws InterruptedException
-     */
-    @Test
-    public void subscribeAndUnsubscribe() throws Exception {
-        final String testParentId = "testSubscribe";
-        final LibraryParams testParams = TestUtils.createLibraryParams();
-        final List<MediaBrowserCompat.MediaItem> testFullMediaItemList = createMediaItems(4);
-        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-        final CountDownLatch latch = new CountDownLatch(2);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result, Bundle option) {
-                // Called by subscribe and notifyChildrenChanged()
-                assertEquals(testParentId, parentId);
-                assertTrue(TestUtils.equals(testParams.getExtras(), option));
-                result.sendResult(testFullMediaItemList);
-
-                // Shouldn't call notifyChildrenChanged() again here because it will call
-                // onLoadChildren() again for getting list of children.
-                if (subscribeLatch.getCount() > 0) {
-                    subscribeLatch.countDown();
-                }
-            }
-        });
-        MediaBrowser browser = createBrowser(new MediaBrowser.BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                // Triggered by both subscribe and notifyChildrenChanged().
-                // Shouldn't be called after the unsubscribe().
-                assertNotEquals(0, latch.getCount());
-                assertEquals(testParentId, parentId);
-                assertEquals(testFullMediaItemList.size(), itemCount);
-                assertNull(params);
-                latch.countDown();
-            }
-        });
-        LibraryResult result = browser.subscribe(testParentId, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        MockMediaBrowserServiceCompat.getInstance().notifyChildrenChanged(testParentId);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        result = browser.unsubscribe(testParentId).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // Unsubscribe takes some time. Wait for some time.
-        Thread.sleep(TIMEOUT_MS);
-        MockMediaBrowserServiceCompat.getInstance().notifyChildrenChanged(testParentId);
-        // This shouldn't trigger browser's onChildrenChanged().
-        // Wait for some time. Exception will be thrown in the callback if error happens.
-        Thread.sleep(TIMEOUT_MS);
-    }
-
-    @Test
-    public void subscribe_failed() throws Exception {
-        final String testParentId = "testSubscribe_failed";
-        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId,
-                    Result<List<MediaBrowserCompat.MediaItem>> result, Bundle option) {
-                // Called by subscribe and notifyChildrenChanged()
-                assertEquals(testParentId, parentId);
-
-                // Cannot use Result#sendError() for sending error here. The API is specific to
-                // custom action.
-                result.sendResult(null);
-
-                // Shouldn't call notifyChildrenChanged() again here because it will call
-                // onLoadChildren() again for getting list of children.
-                if (subscribeLatch.getCount() > 0) {
-                    subscribeLatch.countDown();
-                }
-            }
-        });
-        MediaBrowser browser = createBrowser(new MediaBrowser.BrowserCallback() {});
-        LibraryResult result = browser.subscribe(testParentId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private static MediaBrowserCompat.MediaItem createMediaItem(String mediaId) {
-        final MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId).setTitle("title: " + mediaId).build();
-        return new MediaBrowserCompat.MediaItem(desc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
-    }
-
-    private static List<MediaBrowserCompat.MediaItem> createMediaItems(int size) {
-        final List<MediaBrowserCompat.MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[2].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(createMediaItem(caller + "_child_" + i));
-        }
-        return list;
-    }
-
-    private static void assertItemEquals(MediaBrowserCompat.MediaItem item, MediaItem item2) {
-        assertEquals(item.getMediaId(), item2.getMediaId());
-        assertEquals(item.getMediaId(), item2.getMetadata().getString(
-                MediaMetadata.METADATA_KEY_MEDIA_ID));
-        assertEquals(item.getDescription().getTitle(),
-                item2.getMetadata().getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
-    }
-
-    private static void assertItemsEquals(List<MediaBrowserCompat.MediaItem> itemList,
-            List<MediaItem> item2List) {
-        if (itemList == null && item2List == null) {
-            return;
-        }
-        assertFalse(itemList == null || item2List == null);
-        assertEquals(itemList.size(), item2List.size());
-        for (int i = 0; i < itemList.size(); i++) {
-            assertItemEquals(itemList.get(i), item2List.get(i));
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
deleted file mode 100644
index 603f846..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ /dev/null
@@ -1,1701 +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.media2.session;
-
-import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_608;
-
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.TestServiceRegistry.SessionServiceCallback;
-import androidx.media2.session.TestUtils.SyncHandler;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.Suppress;
-import androidx.testutils.PollingCheck;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController}.
- */
-// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
-// TODO(jaewan): Fix flaky failure -- see MediaControllerImpl.getController()
-// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
-@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-@FlakyTest
-public class MediaControllerTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaControllerTest";
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    PendingIntent mIntent;
-    MediaSession mSession;
-    MediaController mController;
-    MockPlayer mPlayer;
-    AudioManager mAudioManager;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-
-        mPlayer = new MockPlayer(1);
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                        return TestUtils.createMediaItem(mediaId);
-                    }
-                })
-                .setSessionActivity(mIntent)
-                .setId(TAG).build();
-        mController = createController(mSession.getToken());
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        TestServiceRegistry.getInstance().setHandler(sHandler);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-
-        if (mController != null) {
-            mController.close();
-            mController = null;
-        }
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Test
-    public void play() throws Exception {
-        SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void play_autoPrepare() throws Exception {
-        final MockPlayer player = new MockPlayer(2);
-        player.mLastPlayerState = SessionPlayer.PLAYER_STATE_IDLE;
-        mSession.updatePlayer(player);
-        SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(player.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(player.mPlayCalled);
-        assertTrue(player.mPrepareCalled);
-    }
-
-    @Test
-    public void pause() throws Exception {
-        SessionResult result = mController.pause().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void prepare() throws Exception {
-        SessionResult result = mController.prepare().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() throws Exception {
-        final long seekPosition = 12125L;
-        SessionResult result = mController.seekTo(seekPosition)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void gettersAfterConnected() throws InterruptedException {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-        final float speed = 0.5f;
-        final long timeDiff = 102;
-        final MediaItem currentMediaItem = TestUtils.createMediaItemWithMetadata();
-        final int shuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        final int repeatMode = SessionPlayer.REPEAT_MODE_ONE;
-
-        mPlayer.mLastPlayerState = state;
-        mPlayer.mLastBufferingState = bufferingState;
-        mPlayer.mCurrentPosition = position;
-        mPlayer.mBufferedPosition = bufferedPosition;
-        mPlayer.mPlaybackSpeed = speed;
-        mPlayer.mCurrentMediaItem = currentMediaItem;
-        mPlayer.mShuffleMode = shuffleMode;
-        mPlayer.mRepeatMode = repeatMode;
-
-        MediaController controller = createController(mSession.getToken());
-        controller.setTimeDiff(timeDiff);
-        assertEquals(state, controller.getPlayerState());
-        assertEquals(bufferedPosition, controller.getBufferedPosition());
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-        assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
-        assertEquals(currentMediaItem, controller.getCurrentMediaItem());
-        assertEquals(shuffleMode, controller.getShuffleMode());
-        assertEquals(repeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void updatePlayer() throws InterruptedException {
-        final int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final List<MediaItem> testPlaylist = TestUtils.createMediaItems(3);
-        final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_RING).build();
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_ONE;
-        final CountDownLatch latch = new CountDownLatch(5);
-        mController = createController(mSession.getToken(), true, null, new ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                assertEquals(mController, controller);
-                assertEquals(testState, state);
-                latch.countDown();
-            }
-
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller, List<MediaItem> list,
-                    MediaMetadata metadata) {
-                assertEquals(mController, controller);
-                assertEquals(testPlaylist, list);
-                assertNull(metadata);
-                latch.countDown();
-            }
-
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                assertEquals(mController, controller);
-                assertEquals(testAudioAttributes, info.getAudioAttributes());
-                latch.countDown();
-            }
-
-            @Override
-            public void onShuffleModeChanged(
-                    @NonNull MediaController controller,
-                    int shuffleMode) {
-                assertEquals(mController, controller);
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-
-            @Override
-            public void onRepeatModeChanged(
-                    @NonNull MediaController controller,
-                    int repeatMode) {
-                assertEquals(mController, controller);
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        });
-
-        MockPlayer player = new MockPlayer(0);
-        player.mLastPlayerState = testState;
-        player.mAudioAttributes = testAudioAttributes;
-        player.mPlaylist = testPlaylist;
-        player.mShuffleMode = testShuffleMode;
-        player.mRepeatMode = testRepeatMode;
-
-        mSession.updatePlayer(player);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getSessionActivity() {
-        PendingIntent sessionActivity = mController.getSessionActivity();
-        assertNotNull(sessionActivity);
-        if (Build.VERSION.SDK_INT >= 17) {
-            // PendingIntent#getCreatorPackage() is added in API 17.
-            assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
-            assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
-        }
-    }
-
-    @Test
-    public void setPlaylist() throws Exception {
-        final List<String> list = TestUtils.createMediaIds(2);
-        SessionResult result = mController.setPlaylist(list, null /* Metadata */)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertNull(mPlayer.mMetadata);
-
-        assertNotNull(mPlayer.mPlaylist);
-        assertEquals(list.size(), mPlayer.mPlaylist.size());
-        for (int i = 0; i < list.size(); i++) {
-            // MediaController.setPlaylist does not ensure the equality of the items.
-            assertEquals(list.get(i), mPlayer.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void setMediaItem() throws Exception {
-        String mediaId = "testSetMediaItem";
-        SessionResult result = mController.setMediaItem(mediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertNull(mPlayer.mMetadata);
-        assertEquals(mediaId, mPlayer.mItem.getMediaId());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onPlaylistChanged(
-     * MediaController, List, MediaMetadata)}.
-     */
-    @Test
-    public void getPlaylist() throws InterruptedException {
-        final List<MediaItem> testList = TestUtils.createMediaItems(2);
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller,
-                    List<MediaItem> playlist, MediaMetadata metadata) {
-                assertNotNull(playlist);
-                TestUtils.assertMediaItemListEquals(testList, playlist);
-                TestUtils.assertMetadataEquals(testMetadata, metadata);
-                listFromCallback.set(playlist);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.mPlaylist = testList;
-        mPlayer.mMetadata = testMetadata;
-        mPlayer.notifyPlaylistChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        // Ensures object equality
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-        TestUtils.assertMetadataEquals(testMetadata, controller.getPlaylistMetadata());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onPlaylistChanged(
-     * MediaController, List, MediaMetadata)}.
-     */
-    @Test
-    @LargeTest
-    public void getPlaylist_withLongPlaylist() throws InterruptedException {
-        final List<MediaItem> testList = TestUtils.createMediaItems(5000);
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller,
-                    List<MediaItem> playlist, MediaMetadata metadata) {
-                assertNotNull(playlist);
-                TestUtils.assertMediaItemListEquals(testList, playlist);
-                TestUtils.assertMetadataEquals(testMetadata, metadata);
-                listFromCallback.set(playlist);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.mPlaylist = testList;
-        mPlayer.mMetadata = testMetadata;
-        mPlayer.notifyPlaylistChanged();
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-        // Ensures object equality
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-        TestUtils.assertMetadataEquals(testMetadata, controller.getPlaylistMetadata());
-    }
-
-    @Test
-    public void updatePlaylistMetadata() throws Exception {
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        SessionResult result = mController.updatePlaylistMetadata(testMetadata)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertNotNull(mPlayer.mMetadata);
-        assertEquals(testMetadata.getMediaId(), mPlayer.mMetadata.getMediaId());
-    }
-
-    @Test
-    public void getPlaylistMetadata() throws InterruptedException {
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        final AtomicReference<MediaMetadata> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                    MediaMetadata metadata) {
-                assertNotNull(testMetadata);
-                assertEquals(testMetadata.getMediaId(), metadata.getMediaId());
-                metadataFromCallback.set(metadata);
-                latch.countDown();
-            }
-        };
-        mPlayer.mMetadata = testMetadata;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testGetPlaylistMetadata")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .build()) {
-            MediaController controller = createController(session.getToken(), true, null, callback);
-            mPlayer.notifyPlaylistMetadataChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(metadataFromCallback.get().getMediaId(),
-                    controller.getPlaylistMetadata().getMediaId());
-        }
-    }
-
-    @Test
-    public void setPlaybackSpeed() throws Exception {
-        final float speed = 1.5f;
-        SessionResult result = mController.setPlaybackSpeed(speed)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    /**
-     * This also tests {@link MediaController.ControllerCallback#onPlaybackSpeedChanged(
-     * MediaController, float)}.
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void getPlaybackSpeed() throws InterruptedException {
-        final float speed = 1.5f;
-        mPlayer.mPlaybackSpeed = speed;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController controller =
-                createController(mSession.getToken(), true, null, new ControllerCallback() {
-                    @Override
-                    public void onPlaybackSpeedChanged(@NonNull MediaController controller,
-                            float speedOut) {
-                        assertEquals(speed, speedOut, 0.0f);
-                        latch.countDown();
-                    }
-                });
-
-        mPlayer.notifyPlaybackSpeedChanged(speed);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-    }
-
-    /**
-     * Test whether {@link SessionPlayer#setPlaylist(List, MediaMetadata)} is notified
-     * through the
-     * {@link ControllerCallback#onPlaylistMetadataChanged(MediaController, MediaMetadata)}
-     * if the controller doesn't have {@link SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST} but
-     * {@link SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA}.
-     */
-    @Test
-    public void controllerCallback_onPlaylistMetadataChanged() throws InterruptedException {
-        final MediaItem item = TestUtils.createMediaItemWithMetadata();
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                    MediaMetadata metadata) {
-                assertNotNull(metadata);
-                assertEquals(item.getMediaId(), metadata.getMediaId());
-                latch.countDown();
-            }
-        };
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (Process.myUid() == controller.getUid()) {
-                    SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                            .addCommand(new SessionCommand(
-                                    SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA))
-                            .build();
-                    return commands;
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        mPlayer.mMetadata = item.getMetadata();
-        mPlayer.mPlaylist = list;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testControllerCallback_onPlaylistMetadataChanged")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            MediaController controller = createController(session.getToken(), true, null, callback);
-            mPlayer.notifyPlaylistChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void controllerCallback_onSeekCompleted() throws InterruptedException {
-        final long testSeekPosition = 400;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onSeekCompleted(@NonNull MediaController controller, long position) {
-                controller.setTimeDiff(Long.valueOf(0));
-                assertEquals(testSeekPosition, position);
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-        final MediaController controller = createController(mSession.getToken(), true, null,
-                callback);
-        mPlayer.mCurrentPosition = testPosition;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        mPlayer.notifySeekCompleted(testSeekPosition);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * This also tests {@link MediaController#getBufferedPosition()}, and
-     * {@link MediaController#getBufferingState()}.
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void controllerCallback_onBufferingStateChanged() throws InterruptedException {
-        final List<MediaItem> testPlaylist = TestUtils.createMediaItems(3);
-        final MediaItem testItem = testPlaylist.get(0);
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        final long testBufferingPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                controller.setTimeDiff(Long.valueOf(0));
-                assertEquals(testItem, item);
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                latch.countDown();
-            }
-        };
-        final MediaController controller = createController(mSession.getToken(), true, null,
-                callback);
-        mPlayer.mPlaylist = testPlaylist;
-        mPlayer.mBufferedPosition = testBufferingPosition;
-        mPlayer.notifyBufferingStateChanged(testItem, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlayerState()}.
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void controllerCallback_onPlayerStateChanged() throws InterruptedException {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                controller.setTimeDiff(Long.valueOf(0));
-                assertEquals(testPlayerState, state);
-                assertEquals(testPlayerState, controller.getPlayerState());
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-        final MediaController controller = createController(mSession.getToken(), true, null,
-                callback);
-        mPlayer.mCurrentPosition = testPosition;
-        mPlayer.notifyPlayerStateChanged(testPlayerState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * This also tests {@link MediaController#getCurrentMediaItem()}.
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void controllerCallback_onCurrentMediaItemChanged() throws InterruptedException {
-        final int listSize = 5;
-        final List<MediaItem> list = TestUtils.createMediaItems(listSize);
-        mPlayer.mPlaylist = list;
-
-        final int index = 3;
-        final MediaItem currentItem = list.get(index);
-        final MediaItem unknownItem = TestUtils.createMediaItemWithMetadata();
-        final CountDownLatch latch = new CountDownLatch(3);
-        final MediaController controller =
-                createController(mSession.getToken(), true, null, new ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem item) {
-                        switch ((int) latch.getCount()) {
-                            case 3:
-                                assertEquals(-1, controller.getCurrentMediaItemIndex());
-                                assertEquals(unknownItem, item);
-                                break;
-                            case 2:
-                                assertEquals(index, controller.getCurrentMediaItemIndex());
-                                assertEquals(currentItem, item);
-                                break;
-                            case 1:
-                                assertEquals(-1, controller.getCurrentMediaItemIndex());
-                                assertNull(item);
-                        }
-                        latch.countDown();
-                    }
-                });
-
-        // Player notifies with the unknown item. It's still OK.
-        mPlayer.notifyCurrentMediaItemChanged(unknownItem);
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // Known DSD should be notified through the onCurrentMediaItemChanged.
-        mPlayer.mIndex = index;
-        mPlayer.mCurrentMediaItem = mPlayer.mItem = mPlayer.mPlaylist.get(index);
-        mPlayer.notifyCurrentMediaItemChanged(currentItem);
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // Null DSD becomes null MediaItem.
-        mPlayer.mCurrentMediaItem = mPlayer.mItem = null;
-        mPlayer.notifyCurrentMediaItemChanged(null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void addPlaylistItem() throws Exception {
-        final int testIndex = 12;
-        final String testId = "testAddPlaylistItem";
-        SessionResult result = mController.addPlaylistItem(testIndex, testId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertEquals(testId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void removePlaylistItem() throws Exception {
-        mPlayer.mPlaylist = TestUtils.createMediaItems(2);
-
-        // Recreate controller for sending removePlaylistItem.
-        // It's easier to ensure that MediaController.getPlaylist() returns the playlist from the
-        // player.
-        MediaController controller = createController(mSession.getToken());
-        int targetIndex = 0;
-        SessionResult result = controller.removePlaylistItem(targetIndex)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void replacePlaylistItem() throws Exception {
-        final int testIndex = 12;
-        final String testId = "testAddPlaylistItem";
-        SessionResult result = mController.replacePlaylistItem(testIndex, testId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        // MediaController.replacePlaylistItem does not ensure the equality of the items.
-        assertEquals(testId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void skipToPreviousItem() throws Exception {
-        SessionResult result = mController.skipToPreviousPlaylistItem()
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNextItem() throws Exception {
-        SessionResult result = mController.skipToNextPlaylistItem()
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItem() throws Exception {
-        List<MediaItem> playlist = TestUtils.createMediaItems(2);
-        int targetIndex = 1;
-        mPlayer.mPlaylist = playlist;
-        MediaController controller = createController(mSession.getToken());
-        SessionResult result = controller.skipToPlaylistItem(targetIndex)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getShuffleMode() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mPlayer.mShuffleMode = testShuffleMode;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-    }
-
-    @Test
-    public void setShuffleMode() throws Exception {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        SessionResult result = mController.setShuffleMode(testShuffleMode)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getRepeatMode() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mPlayer.mRepeatMode = testRepeatMode;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void setRepeatMode() throws Exception {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        SessionResult result = mController.setRepeatMode(testRepeatMode)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void updatedIndicesInRepeatMode() throws InterruptedException {
-        final int noneRepeatMode = SessionPlayer.REPEAT_MODE_NONE;
-        final int groupRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        final int currentIndex = -1;
-        final int targetIndex = 2;
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(noneRepeatMode, repeatMode);
-                        break;
-                    case 1:
-                        assertEquals(groupRepeatMode, repeatMode);
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-
-        mPlayer.mPrevMediaItemIndex = currentIndex;
-        mPlayer.mRepeatMode = noneRepeatMode;
-        // Need to call this in order to update previous media item index.
-        mPlayer.notifyRepeatModeChanged();
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(currentIndex, controller.getPreviousMediaItemIndex());
-
-        mPlayer.mPrevMediaItemIndex = targetIndex;
-        mPlayer.mRepeatMode = groupRepeatMode;
-        mPlayer.notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetIndex, controller.getPreviousMediaItemIndex());
-    }
-
-    @Test
-    public void updatedIndicesInShuffleMode() throws InterruptedException {
-        final int noneShuffleMode = SessionPlayer.SHUFFLE_MODE_NONE;
-        final int groupShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final int currentIndex = -1;
-        final int targetIndex = 2;
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(noneShuffleMode, shuffleMode);
-                        break;
-                    case 1:
-                        assertEquals(groupShuffleMode, shuffleMode);
-
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-
-        mPlayer.mPrevMediaItemIndex = currentIndex;
-        mPlayer.mShuffleMode = noneShuffleMode;
-        mPlayer.notifyShuffleModeChanged();
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(currentIndex, controller.getPreviousMediaItemIndex());
-
-        mPlayer.mPrevMediaItemIndex = targetIndex;
-        mPlayer.mShuffleMode = groupShuffleMode;
-        mPlayer.notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetIndex, controller.getPreviousMediaItemIndex());
-    }
-
-    @Test
-    public void setVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-        final MediaController controller = createController(mSession.getToken(), true, null, null);
-
-        final int targetVolume = 50;
-        SessionResult result = controller.setVolumeTo(targetVolume, 0 /* flags */)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mSetVolumeToCalled);
-        assertEquals(targetVolume, (int) remotePlayer.mCurrentVolume);
-    }
-
-    @Test
-    public void adjustVolume() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-        final MediaController controller = createController(mSession.getToken(), true, null, null);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        SessionResult result = controller.adjustVolume(direction, 0 /* flags */)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mAdjustVolumeCalled);
-        assertEquals(direction, remotePlayer.mDirection);
-    }
-
-    @Test
-    @Suppress // b/183700008
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        mPlayer.mAudioAttributes = attrs;
-        mSession.updatePlayer(mPlayer);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        SessionResult result = mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    @Suppress // b/183700008
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        mPlayer.mAudioAttributes = attrs;
-        mSession.updatePlayer(mPlayer);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        SessionResult result = mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void getPackageName() {
-        assertEquals(mContext.getPackageName(),
-                mController.getConnectedToken().getPackageName());
-    }
-
-    @Test
-    public void sendCustomCommand() throws Exception {
-        // TODO(jaewan): Need to revisit with the permission.
-        final String command = "test_custom_command";
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "test_args");
-        final SessionCommand testCommand = new SessionCommand(command, null);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup commands =
-                        new SessionCommandGroup.Builder(super.onConnect(session, controller))
-                        .addCommand(testCommand)
-                        .build();
-                return commands;
-            }
-
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller,
-                    @NonNull SessionCommand customCommand, Bundle args) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(command, customCommand.getCustomAction());
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        mSession.close();
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
-        final MediaController controller = createController(mSession.getToken());
-        SessionResult result = controller.sendCustomCommand(testCommand, testArgs)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onConnected() throws InterruptedException {
-        // createController() uses controller callback to wait until the controller becomes
-        // available.
-        MediaController controller = createController(mSession.getToken());
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void controllerCallback_sessionRejects() throws InterruptedException {
-        final MediaSession.SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                return null;
-            }
-        };
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-            }
-        });
-        MediaController controller =
-                createController(mSession.getToken(), false, null, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false);
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void controllerCallback_releaseSession() throws InterruptedException {
-        mSession.close();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void controllerCallback_close() throws InterruptedException {
-        mController.close();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void fastForward() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onFastForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testFastForward").build()) {
-            MediaController controller = createController(session.getToken());
-            SessionResult result = controller.fastForward().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertEquals(RESULT_SUCCESS, result.getResultCode());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void rewind() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testRewind").build()) {
-            MediaController controller = createController(session.getToken());
-            SessionResult result = controller.rewind().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertEquals(RESULT_SUCCESS, result.getResultCode());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void setMediaUri() throws Exception {
-        final Uri request = Uri.parse("foo://boo");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testSetMediaUri").build()) {
-            MediaController controller = createController(session.getToken());
-            SessionResult result = controller.setMediaUri(request, bundle)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertEquals(RESULT_SUCCESS, result.getResultCode());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void setRating() throws Exception {
-        final float ratingValue = 3.5f;
-        final Rating rating = new StarRating(5, ratingValue);
-        final String mediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetRating(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaIdOut,
-                    @NonNull Rating ratingOut) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(mediaId, mediaIdOut);
-                assertEquals(rating, ratingOut);
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testSetRating").build()) {
-            MediaController controller = createController(session.getToken());
-            SessionResult result = controller.setRating(mediaId, rating)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertEquals(RESULT_SUCCESS, result.getResultCode());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void isConnected() throws InterruptedException {
-        assertTrue(mController.isConnected());
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-            }
-        });
-        waitForDisconnect(mController, true);
-        assertFalse(mController.isConnected());
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    public void deadlock() throws InterruptedException {
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = null;
-            }
-        });
-
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    mSession = new MediaSession.Builder(mContext, mPlayer)
-                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                            .setId("testDeadlock").build();
-                }
-            });
-            final MediaController controller = createController(mSession.getToken());
-            testHandler.post(new Runnable() {
-                @SuppressWarnings("FutureReturnValueIgnored")
-                @Override
-                public void run() {
-                    final int state = SessionPlayer.PLAYER_STATE_ERROR;
-                    for (int i = 0; i < 100; i++) {
-                        // triggers call from session to controller.
-                        player.notifyPlayerStateChanged(state);
-                        // triggers call from controller to session.
-                        controller.play();
-
-                        // Repeat above
-                        player.notifyPlayerStateChanged(state);
-                        controller.pause();
-                        player.notifyPlayerStateChanged(state);
-                        controller.seekTo(0);
-                        player.notifyPlayerStateChanged(state);
-                        controller.skipToNextPlaylistItem();
-                        player.notifyPlayerStateChanged(state);
-                        controller.skipToPreviousPlaylistItem();
-                    }
-                    // This may hang if deadlock happens.
-                    latch.countDown();
-                }
-            });
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Clean up here because sessionHandler will be removed afterwards.
-                        mSession.close();
-                        mSession = null;
-                    }
-                });
-            }
-
-            if (Build.VERSION.SDK_INT >= 18) {
-                sessionThread.quitSafely();
-                testThread.quitSafely();
-            } else {
-                sessionThread.quit();
-                testThread.quit();
-            }
-        }
-    }
-
-    @Test
-    public void getServiceToken() {
-        SessionToken token = TestUtils.getServiceToken(mContext, MockMediaSessionService.ID);
-        assertNotNull(token);
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
-    }
-
-    @Test
-    public void connectToService_sessionService() throws Exception {
-        connectToService(MockMediaSessionService.ID);
-    }
-
-    @Test
-    public void connectToService_libraryService() throws Exception {
-        connectToService(MockMediaLibraryService.ID);
-    }
-
-    private void connectToService(String id) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (Process.myUid() == controller.getUid()) {
-                    if (mSession != null) {
-                        mSession.close();
-                    }
-                    mSession = session;
-                    mPlayer = (MockPlayer) session.getPlayer();
-                    assertEquals(mContext.getPackageName(), controller.getPackageName());
-                    assertFalse(controller.isTrusted());
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        final SessionCommand testCommand = new SessionCommand("testConnectToService", null);
-        final CountDownLatch controllerLatch = new CountDownLatch(1);
-        mController = createController(TestUtils.getServiceToken(mContext, id), true,
-                null, new ControllerCallback() {
-                    @Override
-                    @NonNull
-                    public SessionResult onCustomCommand(@NonNull MediaController controller,
-                            @NonNull SessionCommand command, Bundle args) {
-                        if (testCommand.equals(command)) {
-                            controllerLatch.countDown();
-                        }
-                        return new SessionResult(RESULT_SUCCESS);
-                    }
-                }
-        );
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // Test command from controller to session service.
-        SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-
-        // Test command from session service to controller.
-        mSession.broadcastCustomCommand(testCommand, null);
-        assertTrue(controllerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @LargeTest
-    @Test
-    public void controllerAfterSessionIsGone_session() throws InterruptedException {
-        testControllerAfterSessionIsClosed(TAG);
-    }
-
-    @Ignore("b/259936005")
-    @LargeTest
-    @Test
-    public void controllerAfterSessionIsClosed_sessionService() throws Exception {
-        connectToService(MockMediaSessionService.ID);
-        testControllerAfterSessionIsClosed(MockMediaSessionService.ID);
-    }
-
-    @Test
-    public void close_beforeConnected() throws InterruptedException {
-        MediaController controller =
-                createController(mSession.getToken(), false, null, null);
-        controller.close();
-    }
-
-    @Test
-    public void close_twice() {
-        mController.close();
-        mController.close();
-    }
-
-    @LargeTest
-    @Test
-    public void close_session() throws InterruptedException {
-        mController.close();
-        // close is done immediately for session.
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsClosed(TAG);
-    }
-
-    @LargeTest
-    @Test
-    public void close_sessionService() throws Exception {
-        testCloseFromService(MockMediaSessionService.ID);
-    }
-
-    @LargeTest
-    @Test
-    public void close_libraryService() throws Exception {
-        testCloseFromService(MockMediaLibraryService.ID);
-    }
-
-    @Test
-    public void getCurrentPosition() throws InterruptedException {
-        final int pausedState = SessionPlayer.PLAYER_STATE_PAUSED;
-        final int playingState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long timeDiff = 5000L;
-        final long position = 0L;
-        final CountDownLatch latch = new CountDownLatch(2);
-
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(state, pausedState);
-                        assertEquals(position, controller.getCurrentPosition());
-                        mPlayer.notifyPlayerStateChanged(playingState);
-                        break;
-                    case 1:
-                        assertEquals(state, playingState);
-                        assertEquals(position + timeDiff, controller.getCurrentPosition());
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        controller.setTimeDiff(timeDiff);
-        mPlayer.notifyPlayerStateChanged(pausedState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void setMetadataForCurrentMediaItem() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(2);
-        final long duration = 1000L;
-        final MediaItem item = TestUtils.createMediaItemWithMetadata();
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                    @Nullable MediaItem item) {
-                MediaMetadata metadata = item.getMetadata();
-                if (metadata != null) {
-                    switch ((int) latch.getCount()) {
-                        case 2:
-                            assertEquals(-1, controller.getCurrentMediaItemIndex());
-                            assertFalse(metadata.containsKey(
-                                    MediaMetadata.METADATA_KEY_DURATION));
-                            break;
-                        case 1:
-                            assertEquals(-1, controller.getCurrentMediaItemIndex());
-                            assertTrue(metadata.containsKey(
-                                    MediaMetadata.METADATA_KEY_DURATION));
-                            assertEquals(duration,
-                                    metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
-                    }
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.mCurrentMediaItem = mPlayer.mItem = item;
-        mPlayer.notifyCurrentMediaItemChanged(item);
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        item.setMetadata(TestUtils.createMetadata(item.getMediaId(), duration));
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void setMetadataForMediaItemInPlaylist() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(2);
-        final long duration = 1000L;
-        final int currentItemIdx = 0;
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        final MediaMetadata oldMetadata = list.get(1).getMetadata();
-        final MediaMetadata newMetadata = TestUtils.createMetadata(oldMetadata.getMediaId(),
-                duration);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller,
-                    @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(currentItemIdx, controller.getCurrentMediaItemIndex());
-                        assertFalse(oldMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION));
-                        break;
-                    case 1:
-                        assertEquals(currentItemIdx, controller.getCurrentMediaItemIndex());
-                        assertNotNull(list);
-                        assertTrue(list.get(1).getMetadata().containsKey(
-                                MediaMetadata.METADATA_KEY_DURATION));
-                        assertEquals(duration, list.get(1).getMetadata().getLong(
-                                MediaMetadata.METADATA_KEY_DURATION));
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.mPlaylist = list;
-        mPlayer.mIndex = currentItemIdx;
-        mPlayer.mCurrentMediaItem = mPlayer.mItem = mPlayer.mPlaylist.get(currentItemIdx);
-        mPlayer.notifyPlaylistChanged();
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        list.get(1).setMetadata(newMetadata);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getVideoSize() throws InterruptedException {
-        final MediaItem item = TestUtils.createMediaItemWithMetadata();
-        final VideoSize testVideoSize = new VideoSize(100, 42);
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onVideoSizeChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, @NonNull VideoSize videoSize) {
-                assertNotNull(item);
-                assertEquals(testVideoSize, videoSize);
-                latch.countDown();
-            }
-
-            @Override
-            public void onVideoSizeChanged(@NonNull MediaController controller,
-                    @NonNull VideoSize videoSize) {
-                assertEquals(testVideoSize, videoSize);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.notifyCurrentMediaItemChanged(item);
-        mPlayer.notifyVideoSizeChanged(testVideoSize);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testVideoSize, controller.getVideoSize());
-    }
-
-    @Test
-    public void getTracks() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final List<TrackInfo> testTracks = TestUtils.createTrackInfoList();
-
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onTracksChanged(@NonNull MediaController controller,
-                    @NonNull List<TrackInfo> tracks) {
-                assertEquals(testTracks.size(), tracks.size());
-                for (int i = 0; i < testTracks.size(); i++) {
-                    assertEquals(testTracks.get(i), tracks.get(i));
-                }
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.notifyTracksChanged(testTracks);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        List<TrackInfo> tracks = controller.getTracks();
-        assertEquals(testTracks.size(), tracks.size());
-        for (int i = 0; i < testTracks.size(); i++) {
-            assertEquals(testTracks.get(i), tracks.get(i));
-        }
-    }
-
-    @Test
-    public void selectDeselectTrackAndGetSelectedTrack() throws InterruptedException {
-        final CountDownLatch trackSelectedLatch = new CountDownLatch(1);
-        final CountDownLatch trackDeselectedLatch = new CountDownLatch(1);
-        final List<TrackInfo> testTracks = TestUtils.createTrackInfoList();
-        final TrackInfo testTrack = testTracks.get(2);
-        int testTrackType = testTrack.getTrackType();
-
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onTrackSelected(@NonNull MediaController controller,
-                    @NonNull TrackInfo trackInfo) {
-                assertEquals(testTrack, trackInfo);
-                trackSelectedLatch.countDown();
-            }
-
-            @Override
-            public void onTrackDeselected(@NonNull MediaController controller,
-                    @NonNull TrackInfo trackInfo) {
-                assertEquals(testTrack, trackInfo);
-                trackDeselectedLatch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-
-        assertNull(controller.getSelectedTrack(testTrackType));
-
-        mPlayer.notifyTrackSelected(testTrack);
-        assertTrue(trackSelectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, controller.getSelectedTrack(testTrackType));
-
-        mPlayer.notifyTrackDeselected(testTrack);
-        assertTrue(trackDeselectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getSelectedTrack(testTrackType));
-    }
-
-    @Test
-    public void onSubtitleData() throws InterruptedException {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_LANGUAGE, "und");
-        format.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
-        final MediaItem testItem = TestUtils.createMediaItem("onSubtitleData");
-        final TrackInfo testTrack = new TrackInfo(1, TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, format);
-        final SubtitleData testData = new SubtitleData(123, 456,
-                new byte[] { 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
-                    @NonNull TrackInfo track, @NonNull SubtitleData data) {
-                assertSame(testItem, item);
-                assertEquals(testTrack, track);
-                assertEquals(testData, data);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mSession.getToken(), true, null, callback);
-        mPlayer.notifySubtitleData(testItem, testTrack, testData);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void testCloseFromService(String id) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(new SessionServiceCallback() {
-            @Override
-            public void onCreated() {
-                // Do nothing.
-            }
-
-            @Override
-            public void onDestroyed() {
-                latch.countDown();
-            }
-        });
-        mController = createController(TestUtils.getServiceToken(mContext, id));
-        mController.close();
-        // Wait until close triggers onDestroy() of the session service.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(TestServiceRegistry.getInstance().getServiceInstanceBlocking());
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsClosed(id);
-    }
-
-    private void testControllerAfterSessionIsClosed(final String id) throws InterruptedException {
-        // This cause session service to be died.
-        mSession.close();
-        waitForDisconnect(mController, true);
-        testNoInteraction();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        // Recreated session has different session stub, so previously created controller
-        // shouldn't be available.
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .setId(id).build();
-        testNoInteraction();
-    }
-
-    // Test that mSession and mController doesn't interact.
-    // Note that this method can be called after the mSession is died, so mSession may not have
-    // valid player.
-    private void testNoInteraction() throws InterruptedException {
-        // TODO: check that calls from the controller to session shouldn't be delivered.
-
-        // Calls from the session to controller shouldn't be delivered.
-        final CountDownLatch latch = new CountDownLatch(1);
-        setRunnableForOnCustomCommand(mController, new Runnable() {
-            @Override
-            public void run() {
-                latch.countDown();
-            }
-        });
-        SessionCommand customCommand = new SessionCommand("testNoInteraction", null);
-        mSession.broadcastCustomCommand(customCommand, null);
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        setRunnableForOnCustomCommand(mController, null);
-    }
-
-    // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
-    //               active/inactive and connection accept/refuse
-
-    class TestSessionCallback extends SessionCallback {
-        CountDownLatch mLatch;
-
-        void resetLatchCount(int count) {
-            mLatch = new CountDownLatch(count);
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_FrameworkMediaSessionTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_FrameworkMediaSessionTest.java
deleted file mode 100644
index b4f2161..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_FrameworkMediaSessionTest.java
+++ /dev/null
@@ -1,118 +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.media2.session;
-
-import static org.junit.Assert.assertTrue;
-
-import android.media.session.PlaybackState;
-import android.os.Build;
-import android.os.Looper;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.NonNull;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaController} with framework MediaSession.
- */
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) // For framework MediaSession
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaController_FrameworkMediaSessionTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaController_FrameworkMediaSessionTest";
-
-    private android.media.session.MediaSession mFwkSession;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-        mFwkSession = new android.media.session.MediaSession(mContext, TAG);
-        mFwkSession.setActive(true);
-        mFwkSession.setFlags(android.media.session.MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                | android.media.session.MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
-        mFwkSession.setCallback(new android.media.session.MediaSession.Callback() {});
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        mFwkSession.release();
-    }
-
-    @Test
-    public void connect() throws Exception {
-        CountDownLatch connectedLatch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback = new MediaController.ControllerCallback() {
-            @Override
-            public void onConnected(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup allowedCommands) {
-                connectedLatch.countDown();
-            }
-        };
-        try (MediaController controller = new MediaController.Builder(mContext)
-                .setSessionCompatToken(
-                        MediaSessionCompat.Token.fromToken(mFwkSession.getSessionToken()))
-                .setControllerCallback(sHandlerExecutor, callback)
-                .build()) {
-            assertTrue(connectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void playerStateChanged() throws Exception {
-        CountDownLatch connectedLatch = new CountDownLatch(1);
-        CountDownLatch playerStateChangedLatch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback = new MediaController.ControllerCallback() {
-            @Override
-            public void onConnected(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup allowedCommands) {
-                connectedLatch.countDown();
-            }
-
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                playerStateChangedLatch.countDown();
-            }
-        };
-        try (MediaController controller = new MediaController.Builder(mContext)
-                .setSessionCompatToken(
-                        MediaSessionCompat.Token.fromToken(mFwkSession.getSessionToken()))
-                .setControllerCallback(sHandlerExecutor, callback)
-                .build()) {
-            assertTrue(connectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            mFwkSession.setPlaybackState(new PlaybackState.Builder()
-                    .setState(PlaybackState.STATE_PLAYING, 0, 1.0f)
-                    .build());
-            assertTrue(playerStateChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
deleted file mode 100644
index 7fa8fc8..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
+++ /dev/null
@@ -1,146 +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.media2.session;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.os.Build;
-import android.os.Process;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaController#setSurface(Surface)}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaController_SurfaceTest extends MediaSessionTestBase {
-    private static final String TAG = "MC_SurfaceTest";
-
-    private Instrumentation mInstrumentation;
-    private SurfaceActivity mActivity;
-    private MockPlayer mPlayer;
-    private MediaSession mSession;
-    private MediaController mController;
-
-    @Rule
-    public ActivityTestRule<SurfaceActivity> mActivityRule =
-            new ActivityTestRule<>(SurfaceActivity.class);
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.getActivity();
-
-        setKeepScreenOn();
-
-        mPlayer = new MockPlayer(0);
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(
-                            @NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                })
-                .setId(TAG)
-                .build();
-        mController = createController(mSession.getToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-
-        mSession.close();
-    }
-
-    @Test
-    public void setSurface() throws Exception {
-        // Set
-        final Surface testSurface = mActivity.getSurfaceHolder().getSurface();
-        SessionResult result = mController.setSurface(testSurface)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertSame(testSurface, mPlayer.mSurface);
-
-        // Reset
-        result = mController.setSurface(null).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertNull(mPlayer.mSurface);
-    }
-
-    private void setKeepScreenOn() throws Exception {
-        try {
-            setKeepScreenOnOrThrow();
-        } catch (Throwable tr) {
-            throw new Exception(tr);
-        }
-    }
-
-    private void setKeepScreenOnOrThrow() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (Build.VERSION.SDK_INT >= 27) {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                    mActivity.setTurnScreenOn(true);
-                    mActivity.setShowWhenLocked(true);
-                    KeyguardManager keyguardManager = (KeyguardManager)
-                            mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-                    keyguardManager.requestDismissKeyguard(mActivity, null);
-                } else {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-                }
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
deleted file mode 100644
index fff5512..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
+++ /dev/null
@@ -1,740 +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.media2.session;
-
-import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
-
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.MediaSessionManager;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaSessionTest";
-
-    private MediaSession mSession;
-    private MockPlayer mPlayer;
-    private ControllerInfo mTestControllerInfo;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-
-        if (mSession != null && !mSession.isClosed()) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setId(TAG)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TextUtils.equals(mContext.getPackageName(),
-                                controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                }).build();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-    }
-
-    @Test
-    public void builder() {
-        MediaSession.Builder builder;
-        try {
-            builder = new MediaSession.Builder(mContext, null);
-            fail("null player shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setId(null);
-            fail("null id shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-    }
-
-    @Test
-    public void builder_emptyStringAsId() throws Exception {
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("").build()) {
-            // Using empty string as Id shouldn't crash.
-        }
-    }
-
-    @Test
-    public void close_noException() {
-        MediaSession session = new MediaSession.Builder(mContext, mPlayer).build();
-        session.close();
-    }
-
-    @Test
-    public void updatePlayer() throws Exception {
-        MockPlayer anotherPlayer = new MockPlayer(0);
-
-        // Test if setPlayer doesn't crash with various situations.
-        mSession.updatePlayer(mPlayer);
-        assertEquals(mPlayer, mSession.getPlayer());
-
-        mSession.updatePlayer(anotherPlayer);
-        assertEquals(anotherPlayer, mSession.getPlayer());
-    }
-
-    @Test
-    public void updatePlayer_playbackInfo() throws Exception {
-        MockPlayer player = new MockPlayer(0);
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-        player.mAudioAttributes = attrs;
-
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VOLUME_CONTROL_ABSOLUTE;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                Assert.assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-                assertEquals(attrs, info.getAudioAttributes());
-                assertEquals(volumeControlType, info.getPlaybackType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                latch.countDown();
-            }
-        };
-
-        mSession.updatePlayer(player);
-
-        final MediaController controller = createController(mSession.getToken(), true, null,
-                callback);
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-
-        int localVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
-        if (Build.VERSION.SDK_INT >= 21 && manager.isVolumeFixed()) {
-            localVolumeControlType = VOLUME_CONTROL_FIXED;
-        }
-        assertEquals(localVolumeControlType, info.getControlType());
-        assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
-        assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
-
-        MockRemotePlayer remotePlayer = new MockRemotePlayer(
-                volumeControlType, maxVolume, currentVolume) {
-            @Override
-            public AudioAttributesCompat getAudioAttributes() {
-                return attrs;
-            }
-        };
-        mSession.updatePlayer(remotePlayer);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        assertEquals(volumeControlType, info.getControlType());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void play() {
-        assertNotNull(mSession.getPlayer().play());
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pause() {
-        assertNotNull(mSession.getPlayer().pause());
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void prepare() {
-        assertNotNull(mSession.getPlayer().prepare());
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() {
-        final long pos = 1004L;
-        assertNotNull(mSession.getPlayer().seekTo(pos));
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(pos, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void getDuration() {
-        final long testDuration = 9999;
-        mPlayer.mDuration = testDuration;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        assertEquals(testDuration, mSession.getPlayer().getDuration());
-    }
-
-    @Test
-    public void setPlaybackSpeed() {
-        final float speed = 1.5f;
-        assertNotNull(mSession.getPlayer().setPlaybackSpeed(speed));
-        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void getPlaybackSpeed() {
-        final float speed = 1.5f;
-        mPlayer.mPlaybackSpeed = speed;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        assertEquals(speed, mSession.getPlayer().getPlaybackSpeed(), 0.0f);
-    }
-
-    @Test
-    public void getCurrentMediaItem() {
-        MediaItem item = TestUtils.createMediaItemWithMetadata();
-        mPlayer.mCurrentMediaItem = item;
-        assertEquals(item, mSession.getPlayer().getCurrentMediaItem());
-    }
-
-    @Test
-    public void skipToPreviousItem() {
-        assertNotNull(mSession.getPlayer().skipToPreviousPlaylistItem());
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNextItem() {
-        assertNotNull(mSession.getPlayer().skipToNextPlaylistItem());
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItem() {
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        int targetIndex = 0;
-        assertNotNull(mSession.getPlayer().setPlaylist(list, null));
-        assertNotNull(mSession.getPlayer().skipToPlaylistItem(targetIndex));
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertSame(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void getPlayerState() {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        mPlayer.mLastPlayerState = state;
-        assertEquals(state, mSession.getPlayer().getPlayerState());
-    }
-
-    @Test
-    public void getBufferingState() {
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        mPlayer.mLastBufferingState = bufferingState;
-        assertEquals(bufferingState, mSession.getPlayer().getBufferingState());
-    }
-
-    @Test
-    public void getPosition() {
-        final long position = 150000;
-        mPlayer.mCurrentPosition = position;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        assertEquals(position, mSession.getPlayer().getCurrentPosition());
-    }
-
-    @Test
-    public void getBufferedPosition() {
-        final long bufferedPosition = 900000;
-        mPlayer.mBufferedPosition = bufferedPosition;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        assertEquals(bufferedPosition, mSession.getPlayer().getBufferedPosition());
-    }
-
-    @Test
-    public void setPlaylist() {
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        assertNotNull(mSession.getPlayer().setPlaylist(list, null));
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertSame(list, mPlayer.mPlaylist);
-        assertNull(mPlayer.mMetadata);
-    }
-
-    @Test
-    public void getPlaylist() {
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        mPlayer.mPlaylist = list;
-        assertEquals(list, mSession.getPlayer().getPlaylist());
-    }
-
-    @Test
-    public void updatePlaylistMetadata() {
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        assertNotNull(mSession.getPlayer().updatePlaylistMetadata(testMetadata));
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertSame(testMetadata, mPlayer.mMetadata);
-    }
-
-    @Test
-    public void getPlaylistMetadata() {
-        final MediaMetadata testMetadata = TestUtils.createMetadata();
-        mPlayer.mMetadata = testMetadata;
-        assertEquals(testMetadata, mSession.getPlayer().getPlaylistMetadata());
-    }
-
-    @Test
-    public void addPlaylistItem() {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = TestUtils.createMediaItemWithMetadata();
-        assertNotNull(mSession.getPlayer().addPlaylistItem(testIndex, testMediaItem));
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void removePlaylistItem() {
-        final List<MediaItem> list = TestUtils.createMediaItems(2);
-        int targetIndex = 0;
-        assertNotNull(mSession.getPlayer().setPlaylist(list, null));
-        assertNotNull(mSession.getPlayer().removePlaylistItem(targetIndex));
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertSame(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void replacePlaylistItem() {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = TestUtils.createMediaItemWithMetadata();
-        assertNotNull(mSession.getPlayer().replacePlaylistItem(testIndex, testMediaItem));
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void setShuffleMode() {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        assertNotNull(mSession.getPlayer().setShuffleMode(testShuffleMode));
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatMode() {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        assertNotNull(mSession.getPlayer().setRepeatMode(testRepeatMode));
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void onCommandCallback() throws Exception {
-        final MockOnCommandCallback callback = new MockOnCommandCallback();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mPlayer = new MockPlayer(1);
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setSessionCallback(sHandlerExecutor, callback).build();
-            }
-        });
-        MediaController controller = createController(mSession.getToken());
-        SessionResult pauseResult = controller.pause().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_ERROR_INVALID_STATE, pauseResult.getResultCode());
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(1, callback.commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                callback.commands.get(0).getCommandCode());
-
-        SessionResult playResult = controller.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, playResult.getResultCode());
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(2, callback.commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                (long) callback.commands.get(1).getCommandCode());
-    }
-
-    @Test
-    public void onConnectCallback() throws InterruptedException {
-        final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-            }
-        });
-        MediaController controller = createController(mSession.getToken(), false, null, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false);
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void onDisconnectCallback() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testOnDisconnectCallback")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        assertEquals(Process.myUid(), controller.getUid());
-                        latch.countDown();
-                    }
-                }).build()) {
-            MediaController controller = createController(session.getToken());
-            controller.close();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void setCustomLayout() throws Exception {
-        final List<CommandButton> customLayout = new ArrayList<>();
-        customLayout.add(new CommandButton.Builder()
-                .setCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY))
-                .setDisplayName("button")
-                .build());
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testSetCustomLayout")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (mContext.getPackageName().equals(controller.getPackageName())) {
-                            mTestControllerInfo = controller;
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                })
-                .build()) {
-
-            final ControllerCallback callback = new ControllerCallback() {
-                @Override
-                public int onSetCustomLayout(
-                        @NonNull MediaController controller, @NonNull List<CommandButton> layout) {
-                    assertEquals(customLayout.size(), layout.size());
-                    for (int i = 0; i < layout.size(); i++) {
-                        assertEquals(customLayout.get(i).getCommand(), layout.get(i).getCommand());
-                        assertEquals(customLayout.get(i).getDisplayName(),
-                                layout.get(i).getDisplayName());
-                    }
-                    latch.countDown();
-                    return RESULT_SUCCESS;
-                }
-            };
-            MediaController controller = createController(session.getToken(), true, null, callback);
-            SessionResult result = session.setCustomLayout(mTestControllerInfo, customLayout)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertEquals(RESULT_SUCCESS, result.getResultCode());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    /**
-     * This also tests {@link MediaController#getAllowedCommands()}.
-      */
-    @Test
-    public void setAllowedCommands() throws InterruptedException {
-        final SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                .addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY))
-                .addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PAUSE))
-                .build();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup commandsOut) {
-                assertEquals(commands, commandsOut);
-                latch.countDown();
-            }
-        };
-
-        final MediaController controller = createController(mSession.getToken(), true, null,
-                callback);
-        ControllerInfo controllerInfo = getTestControllerInfo();
-        assertNotNull(controllerInfo);
-
-        mSession.setAllowedCommands(controllerInfo, commands);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(commands, controller.getAllowedCommands());
-    }
-
-    @Test
-    public void sendCustomCommand() throws Exception {
-        final SessionCommand testCommand = new SessionCommand("test_command_code", null);
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "testSendCustomAction");
-
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(testCommand, command);
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS);
-            }
-        };
-        final MediaController controller =
-                createController(mSession.getToken(), true, null, callback);
-        // TODO(jaewan): Test with multiple controllers
-        mSession.broadcastCustomCommand(testCommand, testArgs);
-
-        ControllerInfo controllerInfo = getTestControllerInfo();
-        assertNotNull(controllerInfo);
-        // TODO(jaewan): Test receivers as well.
-        SessionResult result = mSession.sendCustomCommand(controllerInfo, testCommand, testArgs)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * Test expected failure of sendCustomCommand() when it's called in
-     * SessionCallback#onConnect().
-     */
-    @Test
-    public void sendCustomCommand_onConnect() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCommand testCommand = new SessionCommand("test", null);
-        final SessionCallback testSessionCallback = new SessionCallback() {
-            @Override
-            @Nullable
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertNotNull(session.sendCustomCommand(controller, testCommand, null));
-                return super.onConnect(session, controller);
-            }
-        };
-        final ControllerCallback testControllerCallback = new ControllerCallback() {
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, @Nullable Bundle args) {
-                if (TextUtils.equals(testCommand.getCustomAction(), command.getCustomAction())) {
-                    latch.countDown();
-                }
-                return super.onCustomCommand(controller, command, args);
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, testSessionCallback).build()) {
-            MediaController controller = createController(session.getToken(), true,
-                    null, testControllerCallback);
-            assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    /**
-     * Test expected success of sendCustomCommand() when it's called in
-     * SessionCallback#onPostConnect().
-     */
-    @Test
-    public void sendCustomCommand_onPostConnect() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCommand testCommand = new SessionCommand("test", null);
-        final SessionCallback testSessionCallback = new SessionCallback() {
-            @Override
-            @Nullable
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup gr = super.onConnect(session, controller);
-                return gr;
-            }
-
-            @Override
-            public void onPostConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertNotNull(session.sendCustomCommand(controller, testCommand, null));
-            }
-        };
-        final ControllerCallback testControllerCallback = new ControllerCallback() {
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, @Nullable Bundle args) {
-                if (TextUtils.equals(testCommand.getCustomAction(), command.getCustomAction())) {
-                    latch.countDown();
-                }
-                return super.onCustomCommand(controller, command, args);
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, testSessionCallback).build()) {
-            MediaController controller = createController(session.getToken(), true,
-                    null, testControllerCallback);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    /**
-     * Test {@link MediaSession#getSessionCompatToken()}.
-     */
-    @Test
-    public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat()
-            throws Exception {
-        String expectedControllerCompatPackageName =
-                (21 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 24)
-                        ? MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER
-                        : mContext.getPackageName();
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TextUtils.equals(expectedControllerCompatPackageName,
-                                controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                }).build()) {
-            MediaSessionCompat.Token token = session.getSessionCompatToken();
-            MediaControllerCompat compat = new MediaControllerCompat(mContext, token);
-            long testSeekPosition = 1234;
-            compat.getTransportControls().seekTo(testSeekPosition);
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mSeekToCalled);
-            assertEquals(testSeekPosition, mPlayer.mSeekPosition);
-        }
-    }
-
-    private ControllerInfo getTestControllerInfo() {
-        List<ControllerInfo> controllers = mSession.getConnectedControllers();
-        assertNotNull(controllers);
-        for (int i = 0; i < controllers.size(); i++) {
-            if (Process.myUid() == controllers.get(i).getUid()) {
-                return controllers.get(i);
-            }
-        }
-        fail("Failed to get test controller info");
-        return null;
-    }
-
-    public class MockOnConnectCallback extends SessionCallback {
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controllerInfo) {
-            if (Process.myUid() != controllerInfo.getUid()) {
-                return null;
-            }
-            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
-            assertEquals(Process.myUid(), controllerInfo.getUid());
-            assertFalse(controllerInfo.isTrusted());
-            // Reject all
-            return null;
-        }
-    }
-
-    public class MockOnCommandCallback extends SessionCallback {
-        public final ArrayList<SessionCommand> commands = new ArrayList<>();
-
-        @Override
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull SessionCommand command) {
-            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
-            assertEquals(Process.myUid(), controllerInfo.getUid());
-            assertFalse(controllerInfo.isTrusted());
-            commands.add(command);
-            if (command.getCommandCode() == SessionCommand.COMMAND_CODE_PLAYER_PAUSE) {
-                return RESULT_ERROR_INVALID_STATE;
-            }
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java
deleted file mode 100644
index 2b482e0..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java
+++ /dev/null
@@ -1,163 +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.media2.session;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.TestUtils.SyncHandler;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSessionTestBase extends MediaTestBase {
-    static final int TIMEOUT_MS = 1000;
-
-    static SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private Map<MediaController, TestBrowserCallback> mControllers = new HashMap<>();
-
-    interface TestControllerCallbackInterface {
-        void waitForConnect(boolean expect) throws InterruptedException;
-        void waitForDisconnect(boolean expect) throws InterruptedException;
-        void setRunnableForOnCustomCommand(Runnable runnable);
-    }
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    SyncHandler handler;
-                    synchronized (MediaSessionTestBase.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (MediaController controller : mControllers.keySet()) {
-            controller.close();
-        }
-        mControllers.clear();
-    }
-
-    final MediaController createController(SessionToken token) throws InterruptedException {
-        return createController(token, true, null, null);
-    }
-
-    final MediaController createController(@NonNull SessionToken token,
-            boolean waitForConnect, @Nullable Bundle connectionHints,
-            @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestBrowserCallback testCallback = new TestBrowserCallback(callback);
-        MediaController controller = onCreateController(token, connectionHints, testCallback);
-        mControllers.put(controller, testCallback);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    final TestControllerCallbackInterface getTestControllerCallbackInterface(
-            MediaController controller) {
-        return mControllers.get(controller);
-    }
-
-    final void waitForConnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForConnect(expected);
-    }
-
-    final void waitForDisconnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForDisconnect(expected);
-    }
-
-    final void setRunnableForOnCustomCommand(MediaController controller,
-            Runnable runnable) {
-        getTestControllerCallbackInterface(controller).setRunnableForOnCustomCommand(runnable);
-    }
-
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints, @Nullable final TestBrowserCallback callback)
-            throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaController.Builder builder = new MediaController.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaTestBase.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaTestBase.java
deleted file mode 100644
index 42f7f5f..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaTestBase.java
+++ /dev/null
@@ -1,55 +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.media2.session;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.BeforeClass;
-
-/**
- * Base class for all media tests.
- */
-abstract class MediaTestBase {
-    @BeforeClass
-    public static void setupMainLooper() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Prepare the main looper if it hasn't.
-                // Some framework APIs always run on the main looper.
-                if (Looper.getMainLooper() == null) {
-                    Looper.prepareMainLooper();
-                }
-
-                // Initialize AudioManager on the main thread to workaround b/78617702 that
-                // audio focus listener is called on the thread where the AudioManager was
-                // originally initialized.
-                // Without posting this, audio focus listeners wouldn't be called because the
-                // listeners would be posted to the test thread (here) where it waits until the
-                // tests are finished.
-                Context context = ApplicationProvider.getApplicationContext();
-                AudioManager manager =
-                        (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            }
-        });
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockActivity.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockActivity.java
deleted file mode 100644
index 33eaa35..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockActivity.java
+++ /dev/null
@@ -1,22 +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.media2.session;
-
-import android.app.Activity;
-
-public class MockActivity extends Activity {
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java
deleted file mode 100644
index a33ac3a..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,195 +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.media2.session;
-
-import android.os.Bundle;
-import android.os.Process;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.Callback;
-
-import androidx.annotation.GuardedBy;
-import androidx.media.MediaBrowserServiceCompat;
-
-import java.lang.reflect.Method;
-import java.util.List;
-
-/**
- * Mock implementation of the browser.
- */
-public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-    private static final String TAG = "MockMediaBrowserServiceCompat";
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static volatile MockMediaBrowserServiceCompat sInstance;
-    @GuardedBy("sLock")
-    private static volatile Proxy sServiceProxy;
-
-    private MediaSessionCompat mSessionCompat;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        synchronized (sLock) {
-            sInstance = this;
-        }
-        mSessionCompat = new MediaSessionCompat(this, TAG);
-        mSessionCompat.setCallback(new Callback() { });
-        mSessionCompat.setActive(true);
-        setSessionToken(mSessionCompat.getSessionToken());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mSessionCompat.release();
-        synchronized (sLock) {
-            sInstance = null;
-            // Note: Don't reset sServiceProxy.
-            //       When a test is finished and its next test is running, this service will be
-            //       destroyed and re-created for the next test. When it happens, onDestroy() may be
-            //       called after the next test's proxy has set because onDestroy() and tests run on
-            //       the different threads.
-            //       So keep sServiceProxy for the next test.
-        }
-    }
-
-    public static MockMediaBrowserServiceCompat getInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    public static void setMediaBrowserServiceProxy(Proxy proxy) {
-        synchronized (sLock) {
-            sServiceProxy = proxy;
-        }
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName) {
-        return isProxyOverridesMethod(methodName, -1);
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName, int paramCount) {
-        synchronized (sLock) {
-            if (sServiceProxy == null) {
-                return false;
-            }
-            Method[] methods = sServiceProxy.getClass().getMethods();
-            if (methods == null) {
-                return false;
-            }
-            for (int i = 0; i < methods.length; i++) {
-                if (methods[i].getName().equals(methodName)) {
-                    if (paramCount < 0
-                            || (methods[i].getParameterTypes() != null
-                            && methods[i].getParameterTypes().length == paramCount)) {
-                        // Found method. Check if it overrides
-                        return methods[i].getDeclaringClass() != Proxy.class;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        if (clientUid != Process.myUid()) {
-            // Test only -- reject any other request.
-            return null;
-        }
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onGetRoot")) {
-                return sServiceProxy.onGetRoot(clientPackageName, clientUid, rootHints);
-            }
-        }
-        return new BrowserRoot("stub", null);
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 2)) {
-                sServiceProxy.onLoadChildren(parentId, result);
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result, Bundle options) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 3)) {
-                sServiceProxy.onLoadChildren(parentId, result, options);
-                return;
-            }
-        }
-        super.onLoadChildren(parentId, result, options);
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadItem")) {
-                sServiceProxy.onLoadItem(itemId, result);
-                return;
-            }
-        }
-        super.onLoadItem(itemId, result);
-    }
-
-    @Override
-    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onSearch")) {
-                sServiceProxy.onSearch(query, extras, result);
-                return;
-            }
-        }
-        super.onSearch(query, extras, result);
-    }
-
-    @Override
-    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onCustomAction")) {
-                sServiceProxy.onCustomAction(action, extras, result);
-                return;
-            }
-        }
-        super.onCustomAction(action, extras, result);
-    }
-
-    public static class Proxy {
-        public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-            return new BrowserRoot("stub", null);
-        }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result) { }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                Bundle options) { }
-
-        public void onLoadItem(String itemId, Result<MediaItem> result) { }
-
-        public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) { }
-
-        public void onCustomAction(String action, Bundle extras, Result<Bundle> result) { }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java
deleted file mode 100644
index c52bd8c..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java
+++ /dev/null
@@ -1,296 +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.media2.session;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_MIXED;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.TestUtils.SyncHandler;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Mock implementation of {@link MediaLibraryService} for testing.
- */
-public class MockMediaLibraryService extends MediaLibraryService {
-    /**
-     * ID of the session that this service will create
-     */
-    public static final String ID = "TestLibrary";
-
-    public static final MediaItem ROOT_ITEM;
-    public static final Bundle ROOT_PARAMS_EXTRA;
-    public static final LibraryParams ROOT_PARAMS;
-
-    public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
-
-    public static final String PARENT_ID = "parent_id";
-    public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
-    public static final String PARENT_ID_ERROR = "parent_id_error";
-
-    public static final List<MediaItem> GET_CHILDREN_RESULT = new ArrayList<>();
-    public static final int CHILDREN_COUNT = 100;
-
-    public static final String SEARCH_QUERY = "search_query";
-    public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
-    public static final int SEARCH_TIME_IN_MS = 5000;
-    public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
-
-    public static final List<MediaItem> SEARCH_RESULT = new ArrayList<>();
-    public static final int SEARCH_RESULT_COUNT = 50;
-
-    static {
-        ROOT_ITEM = new MediaItem.Builder()
-                .setMetadata(new MediaMetadata.Builder()
-                        .putString(METADATA_KEY_MEDIA_ID, "rootId")
-                        .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                        .putLong(METADATA_KEY_PLAYABLE, 1).build()).build();
-        ROOT_PARAMS_EXTRA = new Bundle();
-        ROOT_PARAMS_EXTRA.putString(ID, ID);
-        ROOT_PARAMS = new LibraryParams.Builder().setExtras(ROOT_PARAMS_EXTRA).build();
-    }
-
-    private static final String TAG = "MockMediaLibrarySvc2";
-
-    @GuardedBy("MockMediaLibraryService.class")
-    private static SessionToken sToken;
-    @GuardedBy("MockMediaLibraryService.class")
-    private static LibraryParams sExpectedParams;
-
-    private MediaLibrarySession mSession;
-
-    public MockMediaLibraryService() {
-        super();
-        GET_CHILDREN_RESULT.clear();
-        String getChildrenMediaIdPrefix = "get_children_media_id_";
-        for (int i = 0; i < CHILDREN_COUNT; i++) {
-            GET_CHILDREN_RESULT.add(createMediaItem(getChildrenMediaIdPrefix + i));
-        }
-
-        SEARCH_RESULT.clear();
-        String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
-        for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
-            SEARCH_RESULT.add(createMediaItem(getSearchResultMediaIdPrefix + i));
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-    }
-
-    @Override
-    public MediaLibrarySession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return (MediaLibrarySession) onGetSessionHandler.onGetSession();
-        }
-        final MockPlayer player = new MockPlayer(1);
-        final SyncHandler handler = (SyncHandler) registry.getHandler();
-        final Executor executor = new Executor() {
-            @Override
-            public void execute(Runnable runnable) {
-                handler.post(runnable);
-            }
-        };
-        SessionCallback callback = registry.getSessionCallback();
-        MediaLibrarySessionCallback librarySessionCallback;
-        if (callback instanceof MediaLibrarySessionCallback) {
-            librarySessionCallback = (MediaLibrarySessionCallback) callback;
-        } else {
-            // Callback hasn't set. Use default callback
-            librarySessionCallback = new TestLibrarySessionCallback();
-        }
-        mSession = new MediaLibrarySession.Builder(MockMediaLibraryService.this, player, executor,
-                librarySessionCallback).setId(ID).build();
-        return mSession;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        setAssertLibraryParams(null);
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    public static SessionToken getToken(Context context) {
-        synchronized (MockMediaLibraryService.class) {
-            if (sToken == null) {
-                sToken = new SessionToken(context, new ComponentName(
-                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-                assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, sToken.getType());
-            }
-            return sToken;
-        }
-    }
-
-    public static void setAssertLibraryParams(LibraryParams params) {
-        synchronized (MockMediaLibraryService.class) {
-            sExpectedParams = params;
-        }
-    }
-
-    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
-        private String mLastQuery;
-
-        @NonNull
-        @Override
-        public LibraryResult onGetLibraryRoot(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, LibraryParams params) {
-            assertLibraryParams(params);
-            return new LibraryResult(RESULT_SUCCESS, ROOT_ITEM, ROOT_PARAMS);
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetItem(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String mediaId) {
-            if (MEDIA_ID_GET_ITEM.equals(mediaId)) {
-                return new LibraryResult(RESULT_SUCCESS, createMediaItem(mediaId), null);
-            } else {
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetChildren(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String parentId, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (PARENT_ID.equals(parentId)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), null);
-            } else if (PARENT_ID_ERROR.equals(parentId)) {
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-            // Includes the case of PARENT_ID_NO_CHILDREN.
-            return new LibraryResult(RESULT_SUCCESS, new ArrayList<MediaItem>(), null);
-        }
-
-        @Override
-        public int onSearch(@NonNull MediaLibrarySession session,
-                @NonNull final ControllerInfo controllerInfo, @NonNull final String query,
-                final LibraryParams params) {
-            assertLibraryParams(params);
-            mLastQuery = query;
-            if (SEARCH_QUERY.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
-                        params);
-            } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
-                // Searching takes some time. Notify after 5 seconds.
-                assertNotNull(Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSession.notifySearchResultChanged(
-                                controllerInfo, query, SEARCH_RESULT_COUNT, params);
-                    }
-                }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS));
-            } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, 0, params);
-            } else {
-                return RESULT_ERROR_BAD_VALUE;
-            }
-            return RESULT_SUCCESS;
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetSearchResult(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull String query, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (!TextUtils.equals(mLastQuery, query)) {
-                // Ensure whether onSearch() has called before
-                return new LibraryResult(RESULT_ERROR_INVALID_STATE);
-            }
-            if (SEARCH_QUERY.equals(query) || SEARCH_QUERY_TAKES_TIME.equals(query)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(SEARCH_RESULT, page, pageSize), null);
-            } else {
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-        }
-
-        private void assertLibraryParams(LibraryParams params) {
-            synchronized (MockMediaLibraryService.class) {
-                TestUtils.assertLibraryParamsEquals(sExpectedParams, params);
-            }
-        }
-    }
-
-    private List<MediaItem> getPaginatedResult(List<MediaItem> items, int page, int pageSize) {
-        if (items == null) {
-            return null;
-        } else if (items.size() == 0) {
-            return new ArrayList<>();
-        }
-
-        final int totalItemCount = items.size();
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, totalItemCount);
-
-        List<MediaItem> paginatedResult = new ArrayList<>();
-        try {
-            // The case of (fromIndex >= totalItemCount) will throw exception below.
-            paginatedResult = items.subList(fromIndex, toIndex);
-        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
-            Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
-                    + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
-        }
-        return paginatedResult;
-    }
-
-    private MediaItem createMediaItem(String mediaId) {
-        return new MediaItem.Builder()
-                .setMetadata(
-                        new MediaMetadata.Builder()
-                                .putString(METADATA_KEY_MEDIA_ID, mediaId)
-                                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                                .putLong(METADATA_KEY_PLAYABLE, 1)
-                                .build())
-                .build();
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaSessionService.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaSessionService.java
deleted file mode 100644
index f08c542..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockMediaSessionService.java
+++ /dev/null
@@ -1,79 +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.media2.session;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.TestUtils.SyncHandler;
-
-import java.util.concurrent.Executor;
-
-/**
- * Mock implementation of {@link MediaSessionService} for testing.
- */
-public class MockMediaSessionService extends MediaSessionService {
-    /**
-     * ID of the session that this service will create.
-     */
-    public static final String ID = "TestSession";
-
-    private MediaSession mSession;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-    }
-
-    @Override
-    public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return onGetSessionHandler.onGetSession();
-        }
-        if (getSessions().size() > 0) {
-            return getSessions().get(0);
-        }
-        final MockPlayer player = new MockPlayer(1);
-        final SyncHandler handler = (SyncHandler) registry.getHandler();
-        final Executor executor = new Executor() {
-            @Override
-            public void execute(Runnable runnable) {
-                handler.post(runnable);
-            }
-        };
-        SessionCallback sessionCallback = registry.getSessionCallback();
-        if (sessionCallback == null) {
-            // Ensures non-null
-            sessionCallback = new SessionCallback() {};
-        }
-        mSession = new MediaSession.Builder(this, player)
-                .setSessionCallback(executor, sessionCallback)
-                .setId(ID)
-                .build();
-        return mSession;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockPlayer.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockPlayer.java
deleted file mode 100644
index 34834d0..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockPlayer.java
+++ /dev/null
@@ -1,630 +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.media2.session;
-
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * A mock implementation of {@link SessionPlayer} for testing.
- */
-public class MockPlayer extends SessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mCountDownLatch;
-    public final boolean mChangePlayerStateWithTransportControl;
-
-    public boolean mPlayCalled;
-    public boolean mPauseCalled;
-    public boolean mPrepareCalled;
-    public boolean mSeekToCalled;
-    public boolean mSetPlaybackSpeedCalled;
-    public long mSeekPosition;
-    public long mCurrentPosition;
-    public long mBufferedPosition;
-    public float mPlaybackSpeed = 1.0f;
-    @PlayerState
-    public int mLastPlayerState;
-    @BuffState
-    public int mLastBufferingState;
-    public long mDuration;
-
-    public List<MediaItem> mPlaylist;
-    public MediaMetadata mMetadata;
-    public MediaItem mCurrentMediaItem;
-    public MediaItem mItem;
-    public int mIndex = -1;
-    public int mPrevMediaItemIndex;
-    public int mNextMediaItemIndex;
-    @RepeatMode
-    public int mRepeatMode = -1;
-    @ShuffleMode
-    public int mShuffleMode = -1;
-    public VideoSize mVideoSize = new VideoSize(0, 0);
-    public Surface mSurface;
-    public TrackInfo mSelectedVideoTrack;
-    public TrackInfo mSelectedAudioTrack;
-    public TrackInfo mSelectedSubtitleTrack;
-    public TrackInfo mSelectedMetadataTrack;
-
-    public boolean mSetPlaylistCalled;
-    public boolean mUpdatePlaylistMetadataCalled;
-    public boolean mAddPlaylistItemCalled;
-    public boolean mRemovePlaylistItemCalled;
-    public boolean mReplacePlaylistItemCalled;
-    public boolean mSkipToPlaylistItemCalled;
-    public boolean mSkipToPreviousItemCalled;
-    public boolean mSkipToNextItemCalled;
-    public boolean mSetRepeatModeCalled;
-    public boolean mSetShuffleModeCalled;
-
-    AudioAttributesCompat mAudioAttributes;
-
-    public MockPlayer(int count) {
-        this(count, false);
-    }
-
-    public MockPlayer(boolean changePlayerStateWithTransportControl) {
-        this(0, changePlayerStateWithTransportControl);
-    }
-
-    private MockPlayer(int count, boolean changePlayerStateWithTransportControl) {
-        mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
-        mChangePlayerStateWithTransportControl = changePlayerStateWithTransportControl;
-        // This prevents MS2#play() from triggering SessionPlayer#prepare().
-        mLastPlayerState = PLAYER_STATE_PAUSED;
-
-        // Sets default audio attributes to prevent setVolume() from being called with the play().
-        mAudioAttributes = new AudioAttributesCompat.Builder()
-                .setUsage(AudioAttributesCompat.USAGE_MEDIA).build();
-    }
-
-    @Override
-    public void close() {
-        // no-op
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        mPlayCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PLAYING);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        mPauseCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        mPrepareCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long pos) {
-        mSeekToCalled = true;
-        mSeekPosition = pos;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPlayerState() {
-        return mLastPlayerState;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return mCurrentPosition;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return mPlaybackSpeed;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return mLastBufferingState;
-    }
-
-    @Override
-    public long getDuration() {
-        return mDuration;
-    }
-
-    public void notifyPlayerStateChanged(final int state) {
-        mLastPlayerState = state;
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlayerStateChanged(MockPlayer.this, state);
-                }
-            });
-        }
-    }
-
-    public void notifyCurrentMediaItemChanged(final MediaItem item) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onCurrentMediaItemChanged(MockPlayer.this, item);
-                }
-            });
-        }
-    }
-
-    public void notifyBufferingStateChanged(final MediaItem item,
-            final @BuffState int buffState) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onBufferingStateChanged(MockPlayer.this, item, buffState);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaybackSpeedChanged(final float speed) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaybackSpeedChanged(MockPlayer.this, speed);
-                }
-            });
-        }
-    }
-
-    public void notifySeekCompleted(final long position) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSeekCompleted(MockPlayer.this, position);
-                }
-            });
-        }
-    }
-
-    public void notifyTracksChanged(final List<TrackInfo> tracks) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTracksChanged(MockPlayer.this, tracks);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackSelected(final TrackInfo trackInfo) {
-        switch (trackInfo.getTrackType()) {
-            case TrackInfo.MEDIA_TRACK_TYPE_VIDEO:
-                mSelectedVideoTrack = trackInfo;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_AUDIO:
-                mSelectedAudioTrack = trackInfo;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
-                mSelectedSubtitleTrack = trackInfo;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_METADATA:
-                mSelectedMetadataTrack = trackInfo;
-                break;
-        }
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackSelected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackDeselected(final TrackInfo trackInfo) {
-        switch (trackInfo.getTrackType()) {
-            case TrackInfo.MEDIA_TRACK_TYPE_VIDEO:
-                mSelectedVideoTrack = null;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_AUDIO:
-                mSelectedAudioTrack = null;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
-                mSelectedSubtitleTrack = null;
-                break;
-            case TrackInfo.MEDIA_TRACK_TYPE_METADATA:
-                mSelectedMetadataTrack = null;
-                break;
-        }
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackDeselected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        mAudioAttributes = attributes;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float speed) {
-        mSetPlaybackSpeedCalled = true;
-        mPlaybackSpeed = speed;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////
-    // Playlist APIs
-    /////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return mPlaylist;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        mItem = item;
-        mCurrentMediaItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(
-            @NonNull List<MediaItem> list, MediaMetadata metadata) {
-        mSetPlaylistCalled = true;
-        mPlaylist = list;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return mMetadata;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        mUpdatePlaylistMetadataCalled = true;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return mCurrentMediaItem;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        if (mPlaylist == null) {
-            return ITEM_NONE;
-        }
-        return mPlaylist.indexOf(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return mPrevMediaItemIndex;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return mNextMediaItemIndex;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mAddPlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        // TODO: check for invalid index
-        mRemovePlaylistItemCalled = true;
-        mIndex = index;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mReplacePlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        // TODO: check for invalid index
-        mSkipToPlaylistItemCalled = true;
-        mIndex = index;
-        if (mPlaylist != null && index >= 0 && index < mPlaylist.size()) {
-            mItem = mPlaylist.get(index);
-            mCurrentMediaItem = mItem;
-        }
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        mSkipToPreviousItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        mSkipToNextItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return mRepeatMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        mSetRepeatModeCalled = true;
-        mRepeatMode = repeatMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return mShuffleMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        mSetShuffleModeCalled = true;
-        mShuffleMode = shuffleMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    public void notifyShuffleModeChanged() {
-        final int shuffleMode = mShuffleMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onShuffleModeChanged(MockPlayer.this, shuffleMode);
-                }
-            });
-        }
-    }
-
-    public void notifyRepeatModeChanged() {
-        final int repeatMode = mRepeatMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onRepeatModeChanged(MockPlayer.this, repeatMode);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistChanged() {
-        final List<MediaItem> list = mPlaylist;
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistChanged(MockPlayer.this, list, metadata);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistMetadataChanged() {
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistMetadataChanged(MockPlayer.this, metadata);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        if (mVideoSize == null) {
-            mVideoSize = new VideoSize(0, 0);
-        }
-        return mVideoSize;
-    }
-
-    void notifyVideoSizeChanged(final VideoSize videoSize) {
-        mVideoSize = videoSize;
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onVideoSizeChanged(MockPlayer.this, videoSize);
-                }
-            });
-        }
-    }
-
-    @Override
-    @Nullable
-    public TrackInfo getSelectedTrack(int trackType) {
-        switch (trackType) {
-            case TrackInfo.MEDIA_TRACK_TYPE_VIDEO:
-                return mSelectedVideoTrack;
-            case TrackInfo.MEDIA_TRACK_TYPE_AUDIO:
-                return mSelectedAudioTrack;
-            case TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
-                return mSelectedSubtitleTrack;
-            case TrackInfo.MEDIA_TRACK_TYPE_METADATA:
-                return mSelectedMetadataTrack;
-        }
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setSurface(@Nullable Surface surface) {
-        mSurface = surface;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    void notifySubtitleData(@NonNull final MediaItem item, @NonNull final TrackInfo track,
-            @NonNull final SubtitleData data) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSubtitleData(MockPlayer.this, item, track, data);
-                }
-            });
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockRemotePlayer.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MockRemotePlayer.java
deleted file mode 100644
index 3780c82..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MockRemotePlayer.java
+++ /dev/null
@@ -1,261 +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.media2.session;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Mock implementation of {@link RemoteSessionPlayer}.
- */
-public class MockRemotePlayer extends RemoteSessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mLatch = new CountDownLatch(1);
-    public boolean mSetVolumeToCalled;
-    public boolean mAdjustVolumeCalled;
-    @VolumeControlType
-    public int mControlType;
-    public int mCurrentVolume;
-    public int mMaxVolume;
-    public int mDirection;
-
-    public MockRemotePlayer(int controlType, int maxVolume, int currentVolume) {
-        mControlType = controlType;
-        mMaxVolume = maxVolume;
-        mCurrentVolume = currentVolume;
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> setVolume(int volume) {
-        mSetVolumeToCalled = true;
-        mCurrentVolume = volume;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> adjustVolume(int direction) {
-        mAdjustVolumeCalled = true;
-        mDirection = direction;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @Override
-    public int getVolume() {
-        return mCurrentVolume;
-    }
-
-    @Override
-    public int getMaxVolume() {
-        return mMaxVolume;
-    }
-
-    @Override
-    public int getVolumeControlType() {
-        return mControlType;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long position) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        return null;
-    }
-
-    @Override
-    public int getPlayerState() {
-        return 0;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return 0;
-    }
-
-    @Override
-    public long getDuration() {
-        return 0;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return 0;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return 0;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return 0;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(@NonNull List<MediaItem> list,
-            MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        return null;
-    }
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return null;
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return null;
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return 0;
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return 0;
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return null;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return ITEM_NONE;
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/RatingTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/RatingTest.java
deleted file mode 100644
index 4b5e7cc..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/RatingTest.java
+++ /dev/null
@@ -1,120 +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.media2.session;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-
-import androidx.media2.common.Rating;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link Rating} and its subclasses.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RatingTest extends MediaTestBase {
-    @Test
-    public void unratedHeartRating() {
-        HeartRating rating = new HeartRating();
-        assertEquals(false, rating.isRated());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void ratedHeartRating() {
-        final boolean hasHeart = true;
-        HeartRating rating = new HeartRating(hasHeart);
-        assertEquals(true, rating.isRated());
-        assertEquals(hasHeart, rating.hasHeart());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void unratedPercentageRating() {
-        PercentageRating rating = new PercentageRating();
-        assertEquals(false, rating.isRated());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void ratedPercentageRating() {
-        double delta = 0.000001;
-        float percentage = 20.5f;
-        PercentageRating rating = new PercentageRating(percentage);
-        assertEquals(true, rating.isRated());
-        assertEquals(percentage, rating.getPercentRating(), delta);
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void unratedThumbRating() {
-        ThumbRating rating = new ThumbRating();
-        assertEquals(false, rating.isRated());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void ratedThumbRating() {
-        boolean isThumbUp = true;
-        ThumbRating rating = new ThumbRating(isThumbUp);
-        assertEquals(true, rating.isRated());
-        assertEquals(isThumbUp, rating.isThumbUp());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void unratedStarRating() {
-        int maxStars = 5;
-        StarRating rating = new StarRating(maxStars);
-        assertEquals(false, rating.isRated());
-        assertEquals(maxStars, rating.getMaxStars());
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    @Test
-    public void ratedStarRating() {
-        double delta = 0.000001;
-        int maxStars = 5;
-        float starRating = 3.1f;
-        StarRating rating = new StarRating(maxStars, starRating);
-        assertEquals(true, rating.isRated());
-        assertEquals(maxStars, rating.getMaxStars());
-        assertEquals(starRating, rating.getStarRating(), delta);
-        assertEquals(rating, writeToParcelAndCreateRating(rating));
-    }
-
-    private Rating writeToParcelAndCreateRating(Rating rating) {
-        ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(rating);
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcelImpl.writeToParcel(parcel, 0);
-            parcel.setDataPosition(0);
-            ParcelImpl newParcelImpl = ParcelImpl.CREATOR.createFromParcel(parcel);
-            return ParcelUtils.fromParcelable(newParcelImpl);
-        } finally {
-            parcel.recycle();
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java
deleted file mode 100644
index 480d92b..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java
+++ /dev/null
@@ -1,41 +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.media2.session;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.media2.session.test.R;
-
-public class SurfaceActivity extends Activity {
-    private SurfaceHolder mHolder;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_surface);
-
-        SurfaceView surfaceView = findViewById(R.id.surface_view);
-        mHolder = surfaceView.getHolder();
-    }
-
-    public SurfaceHolder getSurfaceHolder() {
-        return mHolder;
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/SyncListenableFuture.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/SyncListenableFuture.java
deleted file mode 100644
index 7491dde..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/SyncListenableFuture.java
+++ /dev/null
@@ -1,71 +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.media2.session;
-
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Implements {@link ListenableFuture} for synchrous calls.
- */
-public class SyncListenableFuture implements ListenableFuture<SessionPlayer.PlayerResult> {
-    private final SessionPlayer.PlayerResult mResult;
-
-    SyncListenableFuture(MediaItem item) {
-        mResult = new SessionPlayer.PlayerResult(RESULT_SUCCESS, item);
-    }
-
-    @Override
-    public void addListener(Runnable listener, Executor executor) {
-        executor.execute(listener);
-    }
-
-    @Override
-    public boolean cancel(boolean b) {
-        return false;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return false;
-    }
-
-    @Override
-    public boolean isDone() {
-        return true;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get() throws InterruptedException, ExecutionException {
-        return mResult;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get(long l, TimeUnit timeUnit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return mResult;
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java
deleted file mode 100644
index 8714dac..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java
+++ /dev/null
@@ -1,235 +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.media2.session;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.os.Bundle;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaSessionTestBase.TestControllerCallbackInterface;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A proxy class for {@link MediaBrowser.BrowserCallback} which implements
- * {@link TestControllerCallbackInterface}
- */
-public class TestBrowserCallback extends BrowserCallback
-        implements TestControllerCallbackInterface {
-
-    public final ControllerCallback mCallbackProxy;
-    public final CountDownLatch connectLatch = new CountDownLatch(1);
-    public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-    @GuardedBy("this")
-    private Runnable mOnCustomCommandRunnable;
-
-    TestBrowserCallback(@Nullable ControllerCallback callbackProxy) {
-        mCallbackProxy = callbackProxy == null ? new BrowserCallback() {} : callbackProxy;
-    }
-
-    @CallSuper
-    @Override
-    public void onConnected(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        connectLatch.countDown();
-        mCallbackProxy.onConnected(controller, commands);
-    }
-
-    @CallSuper
-    @Override
-    public void onDisconnected(@NonNull MediaController controller) {
-        disconnectLatch.countDown();
-        mCallbackProxy.onDisconnected(controller);
-    }
-
-    @Override
-    public void waitForConnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(connectLatch.await(
-                    MediaSessionTestBase.TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(connectLatch.await(
-                    MediaSessionTestBase.TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Override
-    public void waitForDisconnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(disconnectLatch.await(
-                    MediaSessionTestBase.TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(disconnectLatch.await(
-                    MediaSessionTestBase.TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Override
-    @NonNull
-    public SessionResult onCustomCommand(@NonNull MediaController controller,
-            @NonNull SessionCommand command, Bundle args) {
-        synchronized (this) {
-            if (mOnCustomCommandRunnable != null) {
-                mOnCustomCommandRunnable.run();
-            }
-        }
-        return mCallbackProxy.onCustomCommand(controller, command, args);
-    }
-
-    @Override
-    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-            @NonNull MediaController.PlaybackInfo info) {
-        mCallbackProxy.onPlaybackInfoChanged(controller, info);
-    }
-
-    @Override
-    public int onSetCustomLayout(@NonNull MediaController controller,
-            @NonNull List<CommandButton> layout) {
-        return mCallbackProxy.onSetCustomLayout(controller, layout);
-    }
-
-    @Override
-    public void onAllowedCommandsChanged(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        mCallbackProxy.onAllowedCommandsChanged(controller, commands);
-    }
-
-    @Override
-    public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-        mCallbackProxy.onPlayerStateChanged(controller, state);
-    }
-
-    @Override
-    public void onSeekCompleted(@NonNull MediaController controller, long position) {
-        mCallbackProxy.onSeekCompleted(controller, position);
-    }
-
-    @Override
-    public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
-        mCallbackProxy.onPlaybackSpeedChanged(controller, speed);
-    }
-
-    @Override
-    public void onBufferingStateChanged(@NonNull MediaController controller,
-            @NonNull MediaItem item, int state) {
-        mCallbackProxy.onBufferingStateChanged(controller, item, state);
-    }
-
-    @Override
-    public void onCurrentMediaItemChanged(@NonNull MediaController controller, MediaItem item) {
-        mCallbackProxy.onCurrentMediaItemChanged(controller, item);
-    }
-
-    @Override
-    public void onPlaylistChanged(@NonNull MediaController controller,
-            List<MediaItem> list, MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistChanged(controller, list, metadata);
-    }
-
-    @Override
-    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-            MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistMetadataChanged(controller, metadata);
-    }
-
-    @Override
-    public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-        mCallbackProxy.onShuffleModeChanged(controller, shuffleMode);
-    }
-
-    @Override
-    public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-        mCallbackProxy.onRepeatModeChanged(controller, repeatMode);
-    }
-
-    @Override
-    public void onPlaybackCompleted(@NonNull MediaController controller) {
-        mCallbackProxy.onPlaybackCompleted(controller);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, item, videoSize);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, videoSize);
-    }
-
-    @Override
-    public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
-        mCallbackProxy.onSubtitleData(controller, item, track, data);
-    }
-
-    @Override
-    public void onChildrenChanged(@NonNull MediaBrowser browser, @NonNull String parentId,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onChildrenChanged(
-                browser, parentId, itemCount, params);
-    }
-
-    @Override
-    public void onSearchResultChanged(@NonNull MediaBrowser browser, @NonNull String query,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onSearchResultChanged(
-                browser, query, itemCount, params);
-    }
-
-    @Override
-    public void onTracksChanged(@NonNull MediaController controller,
-            @NonNull List<SessionPlayer.TrackInfo> tracks) {
-        mCallbackProxy.onTracksChanged(controller, tracks);
-    }
-
-    @Override
-    public void onTrackSelected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackSelected(controller, trackInfo);
-    }
-
-    @Override
-    public void onTrackDeselected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackDeselected(controller, trackInfo);
-    }
-
-    @Override
-    public void setRunnableForOnCustomCommand(Runnable runnable) {
-        synchronized (this) {
-            mOnCustomCommandRunnable = runnable;
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestServiceRegistry.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/TestServiceRegistry.java
deleted file mode 100644
index ae5bbff..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestServiceRegistry.java
+++ /dev/null
@@ -1,173 +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.media2.session;
-
-import static org.junit.Assert.fail;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-
-import androidx.annotation.GuardedBy;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.TestUtils.SyncHandler;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Keeps the instance of currently running {@link MockMediaSessionService}. And also provides
- * a way to control them in one place.
- * <p>
- * It only support only one service at a time.
- */
-public class TestServiceRegistry {
-    @GuardedBy("TestServiceRegistry.class")
-    private static TestServiceRegistry sInstance;
-
-    private final ConditionVariable mServiceSet;
-
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaSessionService mService;
-    @GuardedBy("TestServiceRegistry.class")
-    private SyncHandler mHandler;
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaLibrarySessionCallback mSessionCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private SessionServiceCallback mSessionServiceCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private OnGetSessionHandler mOnGetSessionHandler;
-
-    private TestServiceRegistry() {
-        this.mServiceSet = new ConditionVariable();
-    }
-
-    /**
-     * Callback for session service's lifecyle (onCreate() / onDestroy())
-     */
-    public interface SessionServiceCallback {
-        void onCreated();
-        void onDestroyed();
-    }
-
-    public static TestServiceRegistry getInstance() {
-        synchronized (TestServiceRegistry.class) {
-            if (sInstance == null) {
-                sInstance = new TestServiceRegistry();
-            }
-            return sInstance;
-        }
-    }
-
-    public void setOnGetSessionHandler(OnGetSessionHandler onGetSessionHandler) {
-        synchronized (TestServiceRegistry.class) {
-            mOnGetSessionHandler = onGetSessionHandler;
-        }
-    }
-
-    public OnGetSessionHandler getOnGetSessionHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mOnGetSessionHandler;
-        }
-    }
-
-    public void setHandler(Handler handler) {
-        synchronized (TestServiceRegistry.class) {
-            mHandler = new SyncHandler(handler.getLooper());
-        }
-    }
-
-    public Handler getHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mHandler;
-        }
-    }
-
-    public void setSessionServiceCallback(SessionServiceCallback sessionServiceCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionServiceCallback = sessionServiceCallback;
-        }
-    }
-
-    public void setSessionCallback(MediaLibrarySessionCallback sessionCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionCallback = sessionCallback;
-        }
-    }
-
-    public MediaLibrarySessionCallback getSessionCallback() {
-        synchronized (TestServiceRegistry.class) {
-            return mSessionCallback;
-        }
-    }
-
-    public void setServiceInstance(MediaSessionService service) {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                fail("Previous service instance is still running. Clean up manually to ensure"
-                        + " previoulsy running service doesn't break current test");
-            }
-            mService = service;
-            if (service != null) {
-                mServiceSet.open();
-            }
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onCreated();
-            }
-        }
-    }
-
-    public MediaSessionService getServiceInstanceBlocking() throws TimeoutException {
-        if (!mServiceSet.block(5_000)) {
-            throw new TimeoutException(
-                    "Timed out waiting for TestServiceRegistry.setServiceInstance() to be called.");
-        }
-        synchronized (TestServiceRegistry.class) {
-            return mService;
-        }
-    }
-
-    public void cleanUp() {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                // TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
-                List<MediaSession> sessions = mService.getSessions();
-                for (int i = 0; i < sessions.size(); i++) {
-                    sessions.get(i).close();
-                }
-                // stopSelf() would not kill service while the binder connection established by
-                // bindService() exists, and close() above will do the job instead.
-                // So stopSelf() isn't really needed, but just for sure.
-                mService.stopSelf();
-                mServiceSet.close();
-                mService = null;
-            }
-            if (mHandler != null) {
-                mHandler.removeCallbacksAndMessages(null);
-            }
-            mSessionCallback = null;
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onDestroyed();
-                mSessionServiceCallback = null;
-            }
-            mOnGetSessionHandler = null;
-        }
-    }
-
-    public interface OnGetSessionHandler {
-        MediaSession onGetSession();
-    }
-}
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestUtils.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/TestUtils.java
deleted file mode 100644
index a51ccf1..0000000
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/TestUtils.java
+++ /dev/null
@@ -1,337 +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.media2.session;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utilities for tests.
- */
-public final class TestUtils {
-    private static final int TIMEOUT_MS = 1000;
-
-    /**
-     * Finds the session with id in this test package.
-     *
-     * @param context
-     * @param id
-     * @return
-     */
-    public static SessionToken getServiceToken(Context context, String id) {
-        switch (id) {
-            case MockMediaSessionService.ID:
-                return new SessionToken(context, new ComponentName(
-                        context.getPackageName(), MockMediaSessionService.class.getName()));
-            case MockMediaLibraryService.ID:
-                return new SessionToken(context, new ComponentName(
-                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-        }
-        fail("Unknown id=" + id);
-        return null;
-    }
-
-    /**
-     * Compares contents of two bundles.
-     *
-     * @param a a bundle
-     * @param b another bundle
-     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
-     *     incorrect if any bundle contains a bundle.
-     */
-    public static boolean equals(Bundle a, Bundle b) {
-        return contains(a, b) && contains(b, a);
-    }
-
-    /**
-     * Checks whether a Bundle contains another bundle.
-     *
-     * @param a a bundle
-     * @param b another bundle
-     * @return {@code true} if a contains b. {@code false} otherwise. This may be incorrect if any
-     *      bundle contains a bundle.
-     */
-    @SuppressWarnings("deprecation")
-    public static boolean contains(Bundle a, Bundle b) {
-        if (a == b) {
-            return true;
-        }
-        if (a == null || b == null) {
-            return b == null;
-        }
-        if (!a.keySet().containsAll(b.keySet())) {
-            return false;
-        }
-        for (String key : b.keySet()) {
-            if (!ObjectsCompat.equals(a.get(key), b.get(key))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Create a list of media items for testing purpose
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size list size
-     * @return the newly created media item list
-     */
-    public static List<MediaItem> createMediaItems(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[3].getMethodName();
-        for (int i = 0; i < size; i++) {
-            MediaItem item = new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                    .setMetadata(new MediaMetadata.Builder()
-                            .putString(METADATA_KEY_MEDIA_ID, caller + "_item_" + (i + 1))
-                            .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                            .putLong(METADATA_KEY_PLAYABLE, 1)
-                            .build())
-                    .build();
-            list.add(item);
-        }
-        return list;
-    }
-
-    /**
-     * Create a list of media ids for testing purpose
-     * <p>
-     * Caller's method name will be used for prefix of media id.
-     *
-     * @param size list size
-     * @return the newly created ids
-     */
-    public static List<String> createMediaIds(int size) {
-        final List<String> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[3].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(caller + "_item_" + (size + 1));
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem createMediaItemWithMetadata() {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(createMetadata()).build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[3].getMethodName();
-        return new MediaMetadata.Builder()
-                .putString(METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata(String mediaId, long duration) {
-        return new MediaMetadata.Builder()
-                .putString(METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(METADATA_KEY_DURATION, duration)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-    }
-
-    /**
-     * Create a {@link MediaItem} with the id.
-     *
-     * @return the newly created media item.
-     */
-    public static MediaItem createMediaItem(String mediaId) {
-        return new MediaItem.Builder().setMetadata(createMetadata(mediaId, 0)).build();
-    }
-
-    public static List<SessionPlayer.TrackInfo> createTrackInfoList() {
-        List<SessionPlayer.TrackInfo> list = new ArrayList<>();
-        list.add(createTrackInfo(0, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO));
-        list.add(createTrackInfo(1, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO));
-        list.add(createTrackInfo(2, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-        return list;
-    }
-
-    public static SessionPlayer.TrackInfo createTrackInfo(int trackId, int trackType) {
-        MediaFormat format = new MediaFormat();
-        if (trackType == SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-            format.setString(MediaFormat.KEY_LANGUAGE, "eng");
-            format.setString(MediaFormat.KEY_MIME, "text/cea-608");
-        }
-        return new SessionPlayer.TrackInfo(trackId, trackType, format);
-    }
-
-    public static LibraryParams createLibraryParams() {
-        String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
-
-        Bundle extras = new Bundle();
-        extras.putString(callingTestName, callingTestName);
-        return new LibraryParams.Builder().setExtras(extras).build();
-    }
-
-    /**
-     * Asserts if two lists equals
-     *
-     * @param a a list
-     * @param b another list
-     */
-    public static void assertMediaItemListEquals(List<MediaItem> a, List<MediaItem> b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-            return;
-        }
-        assertEquals(a.size(), b.size());
-
-        for (int i = 0; i < a.size(); i++) {
-            MediaItem aItem = a.get(i);
-            MediaItem bItem = b.get(i);
-
-            if (aItem == null || bItem == null) {
-                assertEquals(aItem, bItem);
-                continue;
-            }
-
-            assertEquals(aItem.getMediaId(), bItem.getMediaId());
-            TestUtils.assertMetadataEquals(aItem.getMetadata(), bItem.getMetadata());
-
-            // Note: Here it does not check whether MediaItem are equal,
-            // since there DataSourceDec is not comparable.
-        }
-    }
-
-    public static void assertPaginatedListEquals(List<MediaItem> fullList, int page, int pageSize,
-            List<MediaItem> paginatedList) {
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, fullList.size());
-        // Compare the given results with originals.
-        for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-            int relativeIndex = originalIndex - fromIndex;
-            assertMediaItemEquals(fullList.get(originalIndex), paginatedList.get(relativeIndex));
-        }
-    }
-
-    public static void assertMetadataEquals(MediaMetadata expected, MediaMetadata actual) {
-        if (expected == null || actual == null) {
-            assertEquals(expected, actual);
-        } else {
-            Set<String> expectedKeySet = expected.keySet();
-            Set<String> actualKeySet = actual.keySet();
-
-            assertEquals(expectedKeySet, actualKeySet);
-            for (String key : expectedKeySet) {
-                assertEquals(expected.getObject(key), actual.getObject(key));
-            }
-        }
-    }
-
-    public static void assertMediaItemWithId(String expectedId, MediaItem item) {
-        assertNotNull(item);
-        assertNotNull(item.getMetadata());
-        assertEquals(expectedId, item.getMetadata().getString(
-                METADATA_KEY_MEDIA_ID));
-    }
-
-    public static void assertMediaItemEquals(MediaItem a, MediaItem b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertMetadataEquals(a.getMetadata(), b.getMetadata());
-        }
-    }
-
-    public static void assertLibraryParamsEquals(LibraryParams a, LibraryParams b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertEquals(a.isSuggested(), b.isSuggested());
-            assertEquals(a.isOffline(), b.isOffline());
-            assertEquals(a.isRecent(), b.isRecent());
-            assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
-        }
-    }
-
-    /**
-     * Handler that always waits until the Runnable finishes.
-     */
-    public static class SyncHandler extends Handler {
-        public SyncHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void postAndSync(final Runnable runnable) throws InterruptedException {
-            if (getLooper() == Looper.myLooper()) {
-                runnable.run();
-            } else {
-                final CountDownLatch latch = new CountDownLatch(1);
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        runnable.run();
-                        latch.countDown();
-                    }
-                });
-                assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/src/androidTest/res/layout/activity_surface.xml b/media2/media2-session/src/androidTest/res/layout/activity_surface.xml
deleted file mode 100644
index 7945f97..0000000
--- a/media2/media2-session/src/androidTest/res/layout/activity_surface.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:keepScreenOn="true">
-    <SurfaceView
-        android:id="@+id/surface_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </SurfaceView>
-</LinearLayout>
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/ConnectedControllersManager.java b/media2/media2-session/src/main/java/androidx/media2/session/ConnectedControllersManager.java
deleted file mode 100644
index 7b541e6..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/ConnectedControllersManager.java
+++ /dev/null
@@ -1,211 +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.media2.session;
-
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Nullable;
-import androidx.collection.ArrayMap;
-import androidx.media2.session.MediaSession.ControllerInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages connected {@link ControllerInfo}. This is thread-safe.
- * The generic T denotes a key of connected MediaController, and it can be either IBinder or
- * RemoteUserInfo.
- */
-class ConnectedControllersManager<T> {
-    static final String TAG = "MS2ControllerMgr";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private final ArrayMap<T, ControllerInfo> mControllerInfoMap = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private final ArrayMap<ControllerInfo, ConnectedControllerRecord> mControllerRecords =
-            new ArrayMap<>();
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final MediaSession.MediaSessionImpl mSessionImpl;
-
-    ConnectedControllersManager(MediaSession.MediaSessionImpl session) {
-        mSessionImpl = session;
-    }
-
-    public void addController(T controllerKey, ControllerInfo controllerInfo,
-            SessionCommandGroup commands) {
-        if (controllerKey == null || controllerInfo == null) {
-            if (DEBUG) {
-                throw new IllegalArgumentException("controllerKey and controllerInfo shouldn't be"
-                        + " null");
-            }
-            return;
-        }
-        synchronized (mLock) {
-            ControllerInfo savedInfo = getController(controllerKey);
-            if (savedInfo == null) {
-                mControllerInfoMap.put(controllerKey, controllerInfo);
-                mControllerRecords.put(controllerInfo, new ConnectedControllerRecord(
-                        controllerKey, new SequencedFutureManager(), commands));
-            } else {
-                // already exist. Only update allowed commands.
-                ConnectedControllerRecord record = mControllerRecords.get(savedInfo);
-                record.allowedCommands = commands;
-            }
-        }
-        // TODO: Also notify controller connected.
-    }
-
-    public void updateAllowedCommands(ControllerInfo controllerInfo,
-            SessionCommandGroup commands) {
-        if (controllerInfo == null) {
-            return;
-        }
-        synchronized (mLock) {
-            ConnectedControllerRecord record = mControllerRecords.get(controllerInfo);
-            if (record != null) {
-                record.allowedCommands = commands;
-                return;
-            }
-        }
-        // TODO: Also notify allowed command changes here.
-    }
-
-    public void removeController(T controllerKey) {
-        if (controllerKey == null) {
-            return;
-        }
-        removeController(getController(controllerKey));
-    }
-
-    public void removeController(final ControllerInfo controllerInfo) {
-        if (controllerInfo == null) {
-            return;
-        }
-        ConnectedControllerRecord record;
-        synchronized (mLock) {
-            record = mControllerRecords.remove(controllerInfo);
-            if (record == null) {
-                return;
-            }
-            mControllerInfoMap.remove(record.controllerKey);
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Controller " + controllerInfo + " is disconnected");
-        }
-        record.sequencedFutureManager.close();
-        mSessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (mSessionImpl.isClosed()) {
-                    return;
-                }
-                mSessionImpl.getCallback().onDisconnected(mSessionImpl.getInstance(),
-                        controllerInfo);
-            }
-        });
-    }
-
-    public final List<ControllerInfo> getConnectedControllers() {
-        ArrayList<ControllerInfo> controllers = new ArrayList<>();
-        synchronized (mLock) {
-            controllers.addAll(mControllerInfoMap.values());
-        }
-        return controllers;
-    }
-
-    public final boolean isConnected(ControllerInfo controllerInfo) {
-        synchronized (mLock) {
-            return mControllerRecords.get(controllerInfo) != null;
-        }
-    }
-
-    /**
-     * Gets the sequenced future manager.
-     *
-     * @param controllerInfo controller info
-     * @return sequenced future manager. Can be {@code null} if the controller was null or
-     *         disconencted.
-     */
-    @Nullable
-    public final SequencedFutureManager getSequencedFutureManager(
-            @Nullable ControllerInfo controllerInfo) {
-        ConnectedControllerRecord info;
-        synchronized (mLock) {
-            info = mControllerRecords.get(controllerInfo);
-        }
-        return info != null ? info.sequencedFutureManager : null;
-    }
-
-    /**
-     * Gets the sequenced future manager.
-     *
-     * @param controllerKey key
-     * @return sequenced future manager. Can be {@code null} if the controller was null or
-     *         disconnected.
-     */
-    public SequencedFutureManager getSequencedFutureManager(@Nullable T controllerKey) {
-        ConnectedControllerRecord info;
-        synchronized (mLock) {
-            info = mControllerRecords.get(getController(controllerKey));
-        }
-        return info != null ? info.sequencedFutureManager : null;
-    }
-
-    public boolean isAllowedCommand(ControllerInfo controllerInfo, SessionCommand command) {
-        ConnectedControllerRecord info;
-        synchronized (mLock) {
-            info = mControllerRecords.get(controllerInfo);
-        }
-        return info != null && info.allowedCommands.hasCommand(command);
-    }
-
-    public boolean isAllowedCommand(
-            ControllerInfo controllerInfo, @SessionCommand.CommandCode int commandCode) {
-        ConnectedControllerRecord info;
-        synchronized (mLock) {
-            info = mControllerRecords.get(controllerInfo);
-        }
-        return info != null && info.allowedCommands.hasCommand(commandCode);
-    }
-
-    public ControllerInfo getController(T controllerKey) {
-        synchronized (mLock) {
-            return mControllerInfoMap.get(controllerKey);
-        }
-    }
-
-    private class ConnectedControllerRecord {
-        public final T controllerKey;
-        public final SequencedFutureManager sequencedFutureManager;
-        public SessionCommandGroup allowedCommands;
-
-        ConnectedControllerRecord(T controllerKey, SequencedFutureManager sequencedFutureManager,
-                SessionCommandGroup allowedCommands) {
-            this.controllerKey = controllerKey;
-            this.sequencedFutureManager = sequencedFutureManager;
-            this.allowedCommands = allowedCommands;
-            if (this.allowedCommands == null) {
-                this.allowedCommands = new SessionCommandGroup();
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/ConnectionRequest.java b/media2/media2-session/src/main/java/androidx/media2/session/ConnectionRequest.java
deleted file mode 100644
index 9531c2f..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/ConnectionRequest.java
+++ /dev/null
@@ -1,70 +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.media2.session;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * Created by {@link MediaController} to send its state to the {@link MediaSession} to request
- * to connect. It's intentionally {@link VersionedParcelable} for future extension.
- * <p>
- * All fields here are effectively final. Do not modify.
- */
-@VersionedParcelize
-class ConnectionRequest implements VersionedParcelable {
-    @ParcelField(0)
-    int mVersion;
-    @ParcelField(1)
-    String mPackageName;
-    @ParcelField(2)
-    int mPid;
-    @ParcelField(3)
-    Bundle mConnectionHints;
-
-    // For versioned parcelable.
-    ConnectionRequest() {
-        // no-op
-    }
-
-    ConnectionRequest(String packageName, int pid, @Nullable Bundle connectionHints) {
-        mVersion = MediaUtils.CURRENT_VERSION;
-        mPackageName = packageName;
-        mPid = pid;
-        mConnectionHints = connectionHints;
-    }
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    public int getVersion() {
-        return mVersion;
-    }
-
-    public int getPid() {
-        return mPid;
-    }
-
-    public Bundle getConnectionHints() {
-        return mConnectionHints;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/ConnectionResult.java b/media2/media2-session/src/main/java/androidx/media2/session/ConnectionResult.java
deleted file mode 100644
index f9b01c5..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/ConnectionResult.java
+++ /dev/null
@@ -1,281 +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.media2.session;
-
-import android.app.PendingIntent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.SystemClock;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Created by {@link MediaSession} to send its state to the {@link MediaController} when the
- * connection request is accepted. It's intentionally {@link VersionedParcelable} for future
- * extension.
- * <p>
- * All fields here are effectively final. Do not modify.
- */
-@VersionedParcelize(isCustom = true)
-class ConnectionResult extends CustomVersionedParcelable {
-    @ParcelField(0)
-    int mVersion;
-    // Parceled via mSessionBinder.
-    @NonParcelField
-    IMediaSession mSessionStub;
-    // For parceling mSessionStub. Should be only used by onPreParceling() and onPostParceling().
-    @ParcelField(1)
-    IBinder mSessionBinder;
-    @ParcelField(2)
-    PendingIntent mSessionActivity;
-    @ParcelField(3)
-    int mPlayerState;
-    // Parceled via mParcelableCurrentMediaItem.
-    @NonParcelField
-    MediaItem mCurrentMediaItem;
-    // For parceling mCurrentMediaItem. Should be only used by onPreParceling() and
-    // onPostParceling().
-    @ParcelField(4)
-    MediaItem mParcelableCurrentMediaItem;
-    @ParcelField(5)
-    long mPositionEventTimeMs;
-    @ParcelField(6)
-    long mPositionMs;
-    @ParcelField(7)
-    float mPlaybackSpeed;
-    @ParcelField(8)
-    long mBufferedPositionMs;
-    @ParcelField(9)
-    MediaController.PlaybackInfo mPlaybackInfo;
-    @ParcelField(10)
-    int mRepeatMode;
-    @ParcelField(11)
-    int mShuffleMode;
-    @ParcelField(12)
-    ParcelImplListSlice mPlaylistSlice;
-    @ParcelField(13)
-    SessionCommandGroup mAllowedCommands;
-    @ParcelField(14)
-    int mCurrentMediaItemIndex;
-    @ParcelField(15)
-    int mPreviousMediaItemIndex;
-    @ParcelField(16)
-    int mNextMediaItemIndex;
-    @ParcelField(17)
-    Bundle mTokenExtras;
-    @ParcelField(18)
-    VideoSize mVideoSize;
-    @ParcelField(19)
-    List<TrackInfo> mTracks;
-    @ParcelField(20)
-    TrackInfo mSelectedVideoTrack;
-    @ParcelField(21)
-    TrackInfo mSelectedAudioTrack;
-    @ParcelField(23)
-    TrackInfo mSelectedSubtitleTrack;
-    @ParcelField(24)
-    TrackInfo mSelectedMetadataTrack;
-    @ParcelField(25)
-    MediaMetadata mPlaylistMetadata;
-    @ParcelField(26)
-    int mBufferingState;
-
-    // For versioned parcelable
-    ConnectionResult() {
-        // no-op
-    }
-
-    ConnectionResult(@NonNull MediaSessionStub sessionStub,
-            @NonNull MediaSession.MediaSessionImpl sessionImpl,
-            @NonNull SessionCommandGroup allowedCommands) {
-        mSessionStub = sessionStub;
-        mPlayerState = sessionImpl.getPlayerState();
-        mCurrentMediaItem = sessionImpl.getCurrentMediaItem();
-        mPositionEventTimeMs = SystemClock.elapsedRealtime();
-        mPositionMs = sessionImpl.getCurrentPosition();
-        mPlaybackSpeed = sessionImpl.getPlaybackSpeed();
-        mBufferedPositionMs = sessionImpl.getBufferedPosition();
-        mPlaybackInfo = sessionImpl.getPlaybackInfo();
-        mRepeatMode = sessionImpl.getRepeatMode();
-        mShuffleMode = sessionImpl.getShuffleMode();
-        mSessionActivity = sessionImpl.getSessionActivity();
-        mCurrentMediaItemIndex = sessionImpl.getCurrentMediaItemIndex();
-        mPreviousMediaItemIndex = sessionImpl.getPreviousMediaItemIndex();
-        mNextMediaItemIndex = sessionImpl.getNextMediaItemIndex();
-        mTokenExtras = sessionImpl.getToken().getExtras();
-        mVideoSize = sessionImpl.getVideoSize();
-        mTracks = sessionImpl.getTracks();
-        mSelectedVideoTrack = sessionImpl.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_VIDEO);
-        mSelectedAudioTrack = sessionImpl.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_AUDIO);
-        mSelectedSubtitleTrack = sessionImpl.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        mSelectedMetadataTrack = sessionImpl.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_METADATA);
-        if (allowedCommands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST)) {
-            List<MediaItem> playlist = sessionImpl.getPlaylist();
-            mPlaylistSlice = MediaUtils.convertMediaItemListToParcelImplListSlice(playlist);
-        } else {
-            mPlaylistSlice = null;
-        }
-        if (allowedCommands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST)
-                || allowedCommands.hasCommand(
-                        SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)) {
-            mPlaylistMetadata = sessionImpl.getPlaylistMetadata();
-        } else {
-            mPlaylistMetadata = null;
-        }
-        mBufferingState = sessionImpl.getBufferingState();
-        mAllowedCommands = allowedCommands;
-        mVersion = MediaUtils.CURRENT_VERSION;
-    }
-
-    public IMediaSession getSessionStub() {
-        return mSessionStub;
-    }
-
-    public PendingIntent getSessionActivity() {
-        return mSessionActivity;
-    }
-
-    public int getPlayerState() {
-        return mPlayerState;
-    }
-
-    public MediaItem getCurrentMediaItem() {
-        return mCurrentMediaItem;
-    }
-
-    public long getPositionEventTimeMs() {
-        return mPositionEventTimeMs;
-    }
-
-    public long getPositionMs() {
-        return mPositionMs;
-    }
-
-    public float getPlaybackSpeed() {
-        return mPlaybackSpeed;
-    }
-
-    public long getBufferedPositionMs() {
-        return mBufferedPositionMs;
-    }
-
-    public MediaController.PlaybackInfo getPlaybackInfo() {
-        return mPlaybackInfo;
-    }
-
-    public int getRepeatMode() {
-        return mRepeatMode;
-    }
-
-    public int getShuffleMode() {
-        return mShuffleMode;
-    }
-
-    public ParcelImplListSlice getPlaylistSlice() {
-        return mPlaylistSlice;
-    }
-
-    public SessionCommandGroup getAllowedCommands() {
-        return mAllowedCommands;
-    }
-
-    public int getVersion() {
-        return mVersion;
-    }
-
-    public int getCurrentMediaItemIndex() {
-        return mCurrentMediaItemIndex;
-    }
-
-    public int getPreviousMediaItemIndex() {
-        return mPreviousMediaItemIndex;
-    }
-
-    public int getNextMediaItemIndex() {
-        return mNextMediaItemIndex;
-    }
-
-    public Bundle getTokenExtras() {
-        return mTokenExtras;
-    }
-
-    public VideoSize getVideoSize() {
-        return mVideoSize;
-    }
-
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        return (mTracks == null) ? Collections.emptyList() : mTracks;
-    }
-
-    public TrackInfo getSelectedVideoTrack() {
-        return mSelectedVideoTrack;
-    }
-
-    public TrackInfo getSelectedAudioTrack() {
-        return mSelectedAudioTrack;
-    }
-
-    public TrackInfo getSelectedSubtitleTrack() {
-        return mSelectedSubtitleTrack;
-    }
-
-    public TrackInfo getSelectedMetadataTrack() {
-        return mSelectedMetadataTrack;
-    }
-
-    @Nullable
-    public MediaMetadata getPlaylistMetadata() {
-        return mPlaylistMetadata;
-    }
-
-    public int getBufferingState() {
-        return mBufferingState;
-    }
-
-    @Override
-    @SuppressWarnings("SynchronizeOnNonFinalField") // mSessionStub is effectively final.
-    public void onPreParceling(boolean isStream) {
-        synchronized (mSessionStub) {
-            if (mSessionBinder == null) {
-                mSessionBinder = (IBinder) mSessionStub;
-                mParcelableCurrentMediaItem =
-                        MediaUtils.upcastForPreparceling(mCurrentMediaItem);
-            }
-        }
-    }
-
-    @Override
-    public void onPostParceling() {
-        mSessionStub = IMediaSession.Stub.asInterface(mSessionBinder);
-        mCurrentMediaItem = mParcelableCurrentMediaItem;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/HeartRating.java b/media2/media2-session/src/main/java/androidx/media2/session/HeartRating.java
deleted file mode 100644
index 5a785d6..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/HeartRating.java
+++ /dev/null
@@ -1,92 +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.media2.session;
-
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.Rating;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * A class for rating with a single degree of rating, "heart" vs "no heart". This can be used to
- * indicate the content referred to is a favorite (or not).
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class HeartRating implements Rating {
-    @ParcelField(1)
-    boolean mIsRated;
-
-    @ParcelField(2)
-    boolean mHasHeart;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Creates a unrated HeartRating instance.
-     */
-    public HeartRating() {
-        mIsRated = false;
-    }
-
-    /**
-     * Creates a HeartRating instance.
-     *
-     * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
-     */
-    public HeartRating(boolean hasHeart) {
-        mHasHeart = hasHeart;
-        mIsRated = true;
-    }
-
-    @Override
-    public boolean isRated() {
-        return mIsRated;
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mIsRated, mHasHeart);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof HeartRating)) {
-            return false;
-        }
-        HeartRating other = (HeartRating) obj;
-        return mHasHeart == other.mHasHeart && mIsRated == other.mIsRated;
-    }
-
-    @Override
-    public String toString() {
-        return "HeartRating: " + (mIsRated ? "hasHeart=" + mHasHeart : "unrated");
-    }
-
-    /**
-     * Returns whether the rating is "heart selected".
-     *
-     * @return true if the rating is "heart selected", false if the rating is "heart unselected",
-     *         or if it is unrated.
-     */
-    public boolean hasHeart() {
-        return mHasHeart;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/LibraryResult.java b/media2/media2-session/src/main/java/androidx/media2/session/LibraryResult.java
deleted file mode 100644
index 6fdd26a..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/LibraryResult.java
+++ /dev/null
@@ -1,258 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.os.SystemClock;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/**
- * Result class to be used with {@link ListenableFuture} for asynchronous calls between {@link
- * MediaLibraryService.MediaLibrarySession} and {@link MediaBrowser}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize(isCustom = true)
-public class LibraryResult extends CustomVersionedParcelable implements RemoteResult {
-    /**
-     */
-    @IntDef(flag = false, /*prefix = "RESULT_CODE",*/ value = {
-            RESULT_SUCCESS,
-            RESULT_ERROR_UNKNOWN,
-            RESULT_ERROR_INVALID_STATE,
-            RESULT_ERROR_BAD_VALUE,
-            RESULT_ERROR_PERMISSION_DENIED,
-            RESULT_ERROR_IO,
-            RESULT_INFO_SKIPPED,
-            RESULT_ERROR_SESSION_DISCONNECTED,
-            RESULT_ERROR_NOT_SUPPORTED,
-            RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
-            RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
-            RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
-            RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
-            RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
-            RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED,
-            RESULT_ERROR_SESSION_SETUP_REQUIRED})
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface ResultCode {}
-
-    @ParcelField(1)
-    int mResultCode;
-    @ParcelField(2)
-    long mCompletionTime;
-    // Parceled via mParcelableItem.
-    @NonParcelField
-    MediaItem mItem;
-    // For parceling mItem. Should be only used by onPreParceling() and onPostParceling().
-    @ParcelField(3)
-    MediaItem mParcelableItem;
-    @ParcelField(4)
-    MediaLibraryService.LibraryParams mParams;
-    // Parceled via mItemListSlice
-    @NonParcelField
-    List<MediaItem> mItemList;
-    // For parceling mItemList. Should be only used by onPreParceling() and onPostParceling().
-    @ParcelField(5)
-    ParcelImplListSlice mItemListSlice;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    // For versioned parcelable
-    LibraryResult() {
-        // no-op.
-    }
-
-    /**
-     * Constructor only with the result code.
-     * <p>
-     * For success, use other constructor that you can also return the result.
-     *
-     * @param resultCode result code
-     */
-    public LibraryResult(@ResultCode int resultCode) {
-        this(resultCode, null, null, null);
-    }
-
-    /**
-     * Constructor with the result code and a media item.
-     *
-     * @param resultCode result code
-     * @param item a media item. Can be {@code null} for error
-     * @param params optional library params to describe the returned media item
-     */
-    public LibraryResult(@ResultCode int resultCode, @Nullable MediaItem item,
-            @Nullable MediaLibraryService.LibraryParams params) {
-        this(resultCode, item, null, params);
-    }
-
-    /**
-     * Constructor with the result code and a list of media items.
-     *
-     * @param resultCode result code
-     * @param items list of media items. Can be {@code null} for error
-     * @param params optional library params to describe the returned list of media items.
-     */
-    public LibraryResult(@ResultCode int resultCode, @Nullable List<MediaItem> items,
-            @Nullable MediaLibraryService.LibraryParams params) {
-        this(resultCode, null, items, params);
-    }
-
-    private LibraryResult(@ResultCode int resultCode, @Nullable MediaItem item,
-            @Nullable List<MediaItem> items, @Nullable MediaLibraryService.LibraryParams params) {
-        mResultCode = resultCode;
-        mCompletionTime = SystemClock.elapsedRealtime();
-        mItem = item;
-        mItemList = items;
-        mParams = params;
-    }
-
-    static ListenableFuture<LibraryResult> createFutureWithResult(@ResultCode int resultCode) {
-        ResolvableFuture<LibraryResult> result = ResolvableFuture.create();
-        result.set(new LibraryResult(resultCode));
-        return result;
-    }
-
-    /**
-     * Gets the result code.
-     *
-     * @return result code
-     * @see #RESULT_SUCCESS
-     * @see #RESULT_ERROR_UNKNOWN
-     * @see #RESULT_ERROR_INVALID_STATE
-     * @see #RESULT_ERROR_BAD_VALUE
-     * @see #RESULT_ERROR_PERMISSION_DENIED
-     * @see #RESULT_ERROR_IO
-     * @see #RESULT_INFO_SKIPPED
-     * @see #RESULT_ERROR_SESSION_DISCONNECTED
-     * @see #RESULT_ERROR_NOT_SUPPORTED
-     * @see #RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
-     * @see #RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED
-     * @see #RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT
-     * @see #RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED
-     * @see #RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION
-     * @see #RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED
-     * @see #RESULT_ERROR_SESSION_SETUP_REQUIRED
-     */
-    @Override
-    @ResultCode
-    public int getResultCode() {
-        return mResultCode;
-    }
-
-    /**
-     * Gets the completion time of the command. Being more specific, it's the same as
-     * {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
-     *
-     * @return completion time of the command
-     */
-    @Override
-    public long getCompletionTime() {
-        return mCompletionTime;
-    }
-
-    /**
-     * Gets the media item.
-     * <p>
-     * Can be {@code null} if an error happened or the command doesn't return a media item.
-     *
-     * @return media item
-     * @see MediaBrowser#getLibraryRoot(MediaLibraryService.LibraryParams)
-     * @see MediaBrowser#getItem(String)
-     */
-    @Override
-    @Nullable
-    public MediaItem getMediaItem() {
-        return mItem;
-    }
-
-    /**
-     * Gets the list of media item.
-     * <p>
-     * Can be {@code null} if an error happened or the command doesn't return a list of media
-     * items.
-     *
-     * @return list of media item
-     * @see MediaBrowser#getSearchResult(String, int, int, MediaLibraryService.LibraryParams)
-     * @see MediaBrowser#getChildren(String, int, int, MediaLibraryService.LibraryParams)
-     */
-    @Nullable
-    public List<MediaItem> getMediaItems() {
-        return mItemList;
-    }
-
-    /**
-     * Gets the library params
-     *
-     * @return library params.
-     */
-    @Nullable
-    public MediaLibraryService.LibraryParams getLibraryParams() {
-        return mParams;
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    @SuppressWarnings("SynchronizeOnNonFinalField") // mItem and mItemList are effectively final.
-    public void onPreParceling(boolean isStream) {
-        if (mItem != null) {
-            synchronized (mItem) {
-                if (mParcelableItem == null) {
-                    mParcelableItem = MediaUtils.upcastForPreparceling(mItem);
-                }
-            }
-        }
-        if (mItemList != null) {
-            synchronized (mItemList) {
-                if (mItemListSlice == null) {
-                    mItemListSlice = MediaUtils.convertMediaItemListToParcelImplListSlice(
-                            mItemList);
-                }
-            }
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    public void onPostParceling() {
-        mItem = mParcelableItem;
-        mItemList = MediaUtils.convertParcelImplListSliceToMediaItemList(mItemListSlice);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowser.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowser.java
deleted file mode 100644
index 410b736..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowser.java
+++ /dev/null
@@ -1,391 +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.media2.session;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.Executor;
-
-/**
- * Browses media content offered by a {@link MediaLibraryService}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class MediaBrowser extends MediaController {
-    static final String TAG = "MediaBrowser";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    /**
-     * Callback to listen events from {@link MediaLibraryService}.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static class BrowserCallback extends MediaController.ControllerCallback {
-        /**
-         * Called when there's change in the parent's children after you've subscribed to the parent
-         * with {@link #subscribe}.
-         * <p>
-         * This API is called when the library service called
-         * {@link MediaLibraryService.MediaLibrarySession#notifyChildrenChanged} for the parent.
-         *
-         * @param browser the browser for this event
-         * @param parentId non-empty parent id that you've specified with
-         *                 {@link #subscribe(String, LibraryParams)}
-         * @param itemCount number of children
-         * @param params library params from the library service. Can be differ from params
-         *               that you've specified with {@link #subscribe(String, LibraryParams)}.
-         */
-        public void onChildrenChanged(@NonNull MediaBrowser browser, @NonNull String parentId,
-                @IntRange(from = 0) int itemCount, @Nullable LibraryParams params) { }
-
-        /**
-         * Called when there's change in the search result requested by the previous
-         * {@link MediaBrowser#search(String, LibraryParams)}.
-         *
-         * @param browser the browser for this event
-         * @param query non-empty search query that you've specified with
-         *              {@link #search(String, LibraryParams)}
-         * @param itemCount The item count for the search result
-         * @param params library params from the library service. Can be differ from params
-         *               that you've specified with {@link #search(String, LibraryParams)}.
-         */
-        public void onSearchResultChanged(@NonNull MediaBrowser browser, @NonNull String query,
-                @IntRange(from = 0) int itemCount, @Nullable LibraryParams params) { }
-    }
-
-    /**
-     * Creates a {@link MediaBrowser} from the {@link SessionToken}.
-     *
-     * @param context context
-     * @param token token to connect to
-     * @param executor executor to run callbacks on
-     * @param callback controller callback to receive changes in
-     */
-    MediaBrowser(@NonNull Context context, @NonNull SessionToken token,
-            @Nullable Bundle connectionHints, @Nullable Executor executor,
-            @Nullable BrowserCallback callback) {
-        super(context, token, connectionHints, executor, callback);
-    }
-
-    MediaBrowser(@NonNull Context context, @NonNull MediaSessionCompat.Token token,
-            @Nullable Bundle connectionHints, @Nullable Executor executor,
-            @Nullable BrowserCallback callback) {
-        super(context, token, connectionHints, executor, callback);
-    }
-
-    @Override
-    MediaBrowserImpl createImpl(@NonNull Context context, @NonNull SessionToken token,
-            @Nullable Bundle connectionHints) {
-        if (token.isLegacySession()) {
-            return new MediaBrowserImplLegacy(context, this, token);
-        } else {
-            return new MediaBrowserImplBase(context, this, token, connectionHints);
-        }
-    }
-
-    @Override
-    MediaBrowserImpl getImpl() {
-        return (MediaBrowserImpl) super.getImpl();
-    }
-
-    /**
-     * Gets the library root.
-     * <p>
-     * If it's successfully completed, {@link LibraryResult#getMediaItem()} will return the library
-     * root.
-     *
-     * @param params library params getting root
-     * @see LibraryResult#getMediaItem()
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> getLibraryRoot(@Nullable final LibraryParams params) {
-        if (isConnected()) {
-            return getImpl().getLibraryRoot(params);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Subscribes to a parent id for the change in its children. When there's a change,
-     * {@link BrowserCallback#onChildrenChanged(MediaBrowser, String, int, LibraryParams)} will be
-     * called with the library params. You should call
-     * {@link #getChildren(String, int, int, LibraryParams)}
-     * to get the items under the parent.
-     *
-     * @param parentId non-empty parent id
-     * @param params library params
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> subscribe(@NonNull String parentId,
-            @Nullable LibraryParams params) {
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().subscribe(parentId, params);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Unsubscribes for changes to the children of the parent, which was previously subscribed with
-     * {@link #subscribe(String, LibraryParams)}.
-     * <p>
-     * This unsubscribes all previous subscriptions with the parent id, regardless of the library
-     * param that was previously sent to the library service.
-     *
-     * @param parentId non-empty parent id
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> unsubscribe(@NonNull String parentId) {
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().unsubscribe(parentId);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the list of children under the parent.
-     * <p>
-     * If it's successfully completed, {@link LibraryResult#getMediaItems()} will return the list
-     * of children.
-     *
-     * @param parentId non-empty parent id for getting the children
-     * @param page page number to get the result. Starts from {@code 0}
-     * @param pageSize page size. Should be greater than or equal to {@code 1}
-     * @param params library params
-     * @see LibraryResult#getMediaItems()
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> getChildren(@NonNull String parentId,
-            @IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
-            @Nullable LibraryParams params) {
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId shouldn't be empty");
-        }
-        if (page < 0) {
-            throw new IllegalArgumentException("page shouldn't be negative");
-        }
-        if (pageSize < 1) {
-            throw new IllegalArgumentException("pageSize shouldn't be less than 1");
-        }
-        if (isConnected()) {
-            return getImpl().getChildren(parentId, page, pageSize, params);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the media item with the given media id.
-     * <p>
-     * If it's successfully completed, {@link LibraryResult#getMediaItem()} will return the media
-     * item.
-     *
-     * @param mediaId non-empty media id for specifying the item
-     * @see LibraryResult#getMediaItem()
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> getItem(@NonNull final String mediaId) {
-        if (TextUtils.isEmpty(mediaId)) {
-            throw new IllegalArgumentException("mediaId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().getItem(mediaId);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Sends a search request to the library service.
-     * <p>
-     * Returned {@link LibraryResult} will only tell whether the attempt to search was successful.
-     * For getting the search result, wait for
-     * {@link BrowserCallback#onSearchResultChanged(MediaBrowser, String, int, LibraryParams)}
-     * being called and call {@link #getSearchResult(String, int, int, LibraryParams)}}
-     * for getting the result.
-     *
-     * @param query non-empty search query
-     * @param params library params
-     * @see BrowserCallback#onSearchResultChanged(MediaBrowser, String, int, LibraryParams)
-     * @see #getSearchResult(String, int, int, LibraryParams)
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> search(@NonNull String query,
-            @Nullable LibraryParams params) {
-        if (TextUtils.isEmpty(query)) {
-            throw new IllegalArgumentException("query shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().search(query, params);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the search result from the library service.
-     * <p>
-     * If it's successfully completed, {@link LibraryResult#getMediaItems()} will return the search
-     * result.
-     *
-     * @param query non-empty search query that you've specified with
-     *              {@link #search(String, LibraryParams)}.
-     * @param page page number to get search result. Starts from {@code 0}
-     * @param pageSize page size. Should be greater or equal to {@code 1}
-     * @param params library params
-     * @see LibraryResult#getMediaItems()
-     */
-    @NonNull
-    public ListenableFuture<LibraryResult> getSearchResult(@NonNull final String query,
-            @IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
-            @Nullable final LibraryParams params) {
-        if (TextUtils.isEmpty(query)) {
-            throw new IllegalArgumentException("query shouldn't be empty");
-        }
-        if (page < 0) {
-            throw new IllegalArgumentException("page shouldn't be negative");
-        }
-        if (pageSize < 1) {
-            throw new IllegalArgumentException("pageSize shouldn't be less than 1");
-        }
-        if (isConnected()) {
-            return getImpl().getSearchResult(query, page, pageSize, params);
-        }
-        return createDisconnectedFuture();
-    }
-
-    void notifyBrowserCallback(final BrowserCallbackRunnable callbackRunnable) {
-        if (mPrimaryCallback != null && mPrimaryCallbackExecutor != null) {
-            mPrimaryCallbackExecutor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callbackRunnable.run((BrowserCallback) mPrimaryCallback);
-                }
-            });
-        }
-    }
-
-    interface BrowserCallbackRunnable {
-        void run(@NonNull BrowserCallback callback);
-    }
-
-    /**
-     * Builder for {@link MediaBrowser}.
-     *
-     * <p>To set the token of the session for the controller to connect to, one of the {@link
-     * #setSessionToken(SessionToken)} or {@link #setSessionCompatToken(MediaSessionCompat.Token)}
-     * should be called. Otherwise, the {@link #build()} will throw an {@link
-     * IllegalArgumentException}.
-     *
-     * <p>Any incoming event from the {@link MediaSession} will be handled on the callback executor.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder
-            extends BuilderBase<MediaBrowser, MediaBrowser.Builder, BrowserCallback> {
-        public Builder(@NonNull Context context) {
-            super(context);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionToken(@NonNull SessionToken token) {
-            return super.setSessionToken(token);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionCompatToken(@NonNull MediaSessionCompat.Token compatToken) {
-            return super.setSessionCompatToken(compatToken);
-        }
-
-        @Override
-        @NonNull
-        public Builder setControllerCallback(@NonNull Executor executor,
-                @NonNull BrowserCallback callback) {
-            return super.setControllerCallback(executor, callback);
-        }
-
-        @Override
-        @NonNull
-        public Builder setConnectionHints(@NonNull Bundle connectionHints) {
-            return super.setConnectionHints(connectionHints);
-        }
-
-        /**
-         * Builds a {@link MediaBrowser}.
-         *
-         * @throws IllegalArgumentException if both {@link SessionToken} and
-         * {@link MediaSessionCompat.Token} are not set.
-         *
-         * @return a new browser
-         */
-        @Override
-        @NonNull
-        public MediaBrowser build() {
-            if (mToken == null && mCompatToken == null) {
-                throw new IllegalArgumentException("token and compat token shouldn't be both null");
-            }
-            if (mToken != null) {
-                return new MediaBrowser(mContext, mToken, mConnectionHints,
-                        mCallbackExecutor, (BrowserCallback) mCallback);
-            } else {
-                return new MediaBrowser(mContext, mCompatToken, mConnectionHints,
-                        mCallbackExecutor, (BrowserCallback) mCallback);
-            }
-        }
-    }
-
-    private static ListenableFuture<LibraryResult> createDisconnectedFuture() {
-        return LibraryResult.createFutureWithResult(
-                LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED);
-    }
-
-    interface MediaBrowserImpl extends MediaControllerImpl {
-        ListenableFuture<LibraryResult> getLibraryRoot(
-                @Nullable LibraryParams rootHints);
-        ListenableFuture<LibraryResult> subscribe(@NonNull String parentId,
-                @Nullable LibraryParams params);
-        ListenableFuture<LibraryResult> unsubscribe(@NonNull String parentId);
-        ListenableFuture<LibraryResult> getChildren(@NonNull String parentId, int page,
-                int pageSize, @Nullable LibraryParams params);
-        ListenableFuture<LibraryResult> getItem(@NonNull String mediaId);
-        ListenableFuture<LibraryResult> search(@NonNull String query,
-                @Nullable LibraryParams params);
-        ListenableFuture<LibraryResult> getSearchResult(@NonNull String query, int page,
-                int pageSize, @Nullable LibraryParams params);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java
deleted file mode 100644
index 30affc9..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java
+++ /dev/null
@@ -1,194 +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.media2.session;
-
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED;
-import static androidx.media2.session.LibraryResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaBrowser.BrowserCallbackRunnable;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SequencedFutureManager.SequencedFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-/**
- * Base implementation of MediaBrowser.
- */
-class MediaBrowserImplBase extends MediaControllerImplBase implements
-        MediaBrowser.MediaBrowserImpl {
-    private static final LibraryResult RESULT_WHEN_CLOSED =
-            new LibraryResult(RESULT_INFO_SKIPPED);
-
-    MediaBrowserImplBase(Context context, MediaController instance, SessionToken token,
-            @Nullable Bundle connectionHints) {
-        super(context, instance, token, connectionHints);
-    }
-
-    @NonNull
-    MediaBrowser getMediaBrowser() {
-        return (MediaBrowser) mInstance;
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getLibraryRoot(final LibraryParams params) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.getLibraryRoot(mControllerStub, seq,
-                                MediaParcelUtils.toParcelable(params));
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> subscribe(final String parentId,
-            final LibraryParams params) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_SUBSCRIBE,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.subscribe(mControllerStub, seq, parentId,
-                                MediaParcelUtils.toParcelable(params));
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> unsubscribe(final String parentId) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_UNSUBSCRIBE,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.unsubscribe(mControllerStub, seq, parentId);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getChildren(final String parentId, final int page,
-            final int pageSize, final LibraryParams params) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_GET_CHILDREN,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.getChildren(mControllerStub, seq, parentId, page, pageSize,
-                                MediaParcelUtils.toParcelable(params));
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getItem(final String mediaId) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_GET_ITEM,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.getItem(mControllerStub, seq, mediaId);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> search(final String query, final LibraryParams params) {
-        return dispatchRemoteLibrarySessionTask(COMMAND_CODE_LIBRARY_SEARCH,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.search(mControllerStub, seq, query,
-                                MediaParcelUtils.toParcelable(params));
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getSearchResult(final String query, final int page,
-            final int pageSize, final LibraryParams params) {
-        return dispatchRemoteLibrarySessionTask(
-                COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT,
-                new RemoteLibrarySessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.getSearchResult(mControllerStub, seq, query, page, pageSize,
-                                MediaParcelUtils.toParcelable(params));
-                    }
-                });
-    }
-
-    void notifySearchResultChanged(final String query, final int itemCount,
-            final LibraryParams libraryParams) {
-        getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
-            @Override
-            public void run(@NonNull BrowserCallback callback) {
-                callback.onSearchResultChanged(getMediaBrowser(), query, itemCount, libraryParams);
-            }
-        });
-    }
-
-    void notifyChildrenChanged(final String parentId, final int itemCount,
-            final LibraryParams libraryParams) {
-        getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
-            @Override
-            public void run(@NonNull BrowserCallback callback) {
-                callback.onChildrenChanged(getMediaBrowser(), parentId, itemCount, libraryParams);
-            }
-        });
-    }
-
-    private ListenableFuture<LibraryResult> dispatchRemoteLibrarySessionTask(int commandCode,
-            RemoteLibrarySessionTask task) {
-        final IMediaSession iSession = getSessionInterfaceIfAble(commandCode);
-        if (iSession != null) {
-            final SequencedFuture<LibraryResult> result =
-                    mSequencedFutureManager.createSequencedFuture(RESULT_WHEN_CLOSED);
-            try {
-                task.run(iSession, result.getSequenceNumber());
-            } catch (RemoteException e) {
-                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
-                result.set(new LibraryResult(RESULT_ERROR_SESSION_DISCONNECTED));
-            }
-            return result;
-        } else {
-            // Don't create Future with SequencedFutureManager.
-            // Otherwise session would receive discontinued sequence number, and it would make
-            // future work item 'keeping call sequence when session execute commands' impossible.
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_PERMISSION_DENIED);
-        }
-    }
-
-    @FunctionalInterface
-    private interface RemoteLibrarySessionTask {
-        void run(IMediaSession iSession, int seq) throws RemoteException;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java
deleted file mode 100644
index 9eb3f8a..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java
+++ /dev/null
@@ -1,495 +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.media2.session;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_MIXED;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.ItemCallback;
-import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaBrowser.BrowserCallbackRunnable;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support.
- */
-class MediaBrowserImplLegacy extends MediaControllerImplLegacy implements
-        MediaBrowser.MediaBrowserImpl {
-    private static final String TAG = "MB2ImplLegacy";
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final HashMap<LibraryParams, MediaBrowserCompat> mBrowserCompats = new HashMap<>();
-    @GuardedBy("mLock")
-    private final HashMap<String, List<SubscribeCallback>> mSubscribeCallbacks = new HashMap<>();
-
-    MediaBrowserImplLegacy(@NonNull Context context, MediaBrowser instance,
-            @NonNull SessionToken token) {
-        super(context, instance, token);
-    }
-
-    @NonNull
-    MediaBrowser getMediaBrowser() {
-        return (MediaBrowser) mInstance;
-    }
-
-    @Override
-    public void close() {
-        synchronized (mLock) {
-            for (MediaBrowserCompat browserCompat : mBrowserCompats.values()) {
-                browserCompat.disconnect();
-            }
-            mBrowserCompats.clear();
-            // Ensure that ControllerCallback#onDisconnected() is called by super.close().
-            super.close();
-        }
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getLibraryRoot(@Nullable final LibraryParams params) {
-        final ResolvableFuture<LibraryResult> result = ResolvableFuture.create();
-        final MediaBrowserCompat browserCompat = getBrowserCompat(params);
-        if (browserCompat != null) {
-            // Already connected with the given extras.
-            result.set(new LibraryResult(RESULT_SUCCESS, createRootMediaItem(browserCompat),
-                    null));
-        } else {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Do this on the callback executor to set the looper of MediaBrowserCompat's
-                    // callback handler to this looper.
-                    Bundle rootHints = MediaUtils.convertToRootHints(params);
-                    MediaBrowserCompat newBrowser = new MediaBrowserCompat(getContext(),
-                            getConnectedToken().getComponentName(),
-                            new GetLibraryRootCallback(result, params), rootHints);
-                    synchronized (mLock) {
-                        mBrowserCompats.put(params, newBrowser);
-                    }
-                    newBrowser.connect();
-                }
-            });
-        }
-        return result;
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> subscribe(@NonNull String parentId,
-            @Nullable LibraryParams params) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-        ResolvableFuture<LibraryResult> future = ResolvableFuture.create();
-        SubscribeCallback callback = new SubscribeCallback(future);
-        synchronized (mLock) {
-            List<SubscribeCallback> list = mSubscribeCallbacks.get(parentId);
-            if (list == null) {
-                list = new ArrayList<>();
-                mSubscribeCallbacks.put(parentId, list);
-            }
-            list.add(callback);
-        }
-        browserCompat.subscribe(parentId, createOptions(params), callback);
-        return future;
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> unsubscribe(@NonNull String parentId) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-        // Note: don't use MediaBrowserCompat#unsubscribe(String) here, to keep the subscription
-        // callback for getChildren.
-        synchronized (mLock) {
-            List<SubscribeCallback> list = mSubscribeCallbacks.get(parentId);
-            if (list == null) {
-                return LibraryResult.createFutureWithResult(RESULT_ERROR_BAD_VALUE);
-            }
-            for (int i = 0; i < list.size(); i++) {
-                browserCompat.unsubscribe(parentId, list.get(i));
-            }
-        }
-
-        // No way to get result. Just return success.
-        return LibraryResult.createFutureWithResult(LibraryResult.RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getChildren(@NonNull String parentId, int page,
-            int pageSize, @Nullable LibraryParams params) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-
-        final ResolvableFuture<LibraryResult> future = ResolvableFuture.create();
-        Bundle options = createOptions(params, page, pageSize);
-        browserCompat.subscribe(parentId, options, new GetChildrenCallback(future, parentId));
-        return future;
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getItem(@NonNull final String mediaId) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-        final ResolvableFuture<LibraryResult> result = ResolvableFuture.create();
-        browserCompat.getItem(mediaId, new ItemCallback() {
-            @Override
-            public void onItemLoaded(final MediaBrowserCompat.MediaItem item) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (item != null) {
-                            result.set(new LibraryResult(RESULT_SUCCESS,
-                                    MediaUtils.convertToMediaItem(item), null));
-                        } else {
-                            result.set(new LibraryResult(RESULT_ERROR_BAD_VALUE));
-                        }
-                    }
-                });
-            }
-
-            @Override
-            public void onError(@NonNull String itemId) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        result.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-                    }
-                });
-            }
-        });
-        return result;
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> search(@NonNull String query,
-            @Nullable LibraryParams params) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-        browserCompat.search(query, getExtras(params), new MediaBrowserCompat.SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull final String query, final Bundle extras,
-                    @NonNull final List<MediaBrowserCompat.MediaItem> items) {
-                getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull BrowserCallback callback) {
-                        // Set extra null here, because 'extra' have different meanings between old
-                        // API and new API as follows.
-                        // - Old API: Extra/Option specified with search().
-                        // - New API: Extra from MediaLibraryService to MediaBrowser
-                        // TODO(Post-P): Cache search result for later getSearchResult() calls.
-                        callback.onSearchResultChanged(
-                                getMediaBrowser(), query, items.size(), null);
-                    }
-                });
-            }
-
-            @Override
-            public void onError(@NonNull final String query, final Bundle extras) {
-                getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull BrowserCallback callback) {
-                        // Set extra null here, because 'extra' have different meanings between old
-                        // API and new API as follows.
-                        // - Old API: Extra/Option specified with search().
-                        // - New API: Extra from MediaLibraryService to MediaBrowser
-                        callback.onSearchResultChanged(
-                                getMediaBrowser(), query, 0, null);
-                    }
-                });
-            }
-        });
-        // No way to get result. Just return success.
-        return LibraryResult.createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<LibraryResult> getSearchResult(@NonNull final String query,
-            final int page, final int pageSize, @Nullable final LibraryParams params) {
-        MediaBrowserCompat browserCompat = getBrowserCompat();
-        if (browserCompat == null) {
-            return LibraryResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        }
-
-        final ResolvableFuture<LibraryResult> future = ResolvableFuture.create();
-        Bundle options = createOptions(params, page, pageSize);
-        browserCompat.search(query, options, new MediaBrowserCompat.SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull final String query, final Bundle extrasSent,
-                    @NonNull final List<MediaBrowserCompat.MediaItem> items) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        List<MediaItem> item2List =
-                                MediaUtils.convertMediaItemListToMediaItemList(items);
-                        future.set(new LibraryResult(RESULT_SUCCESS, item2List, null));
-                    }
-                });
-            }
-
-            @Override
-            public void onError(@NonNull final String query, final Bundle extrasSent) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        future.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-                    }
-                });
-            }
-        });
-        return future;
-    }
-
-    private MediaBrowserCompat getBrowserCompat(LibraryParams extras) {
-        synchronized (mLock) {
-            return mBrowserCompats.get(extras);
-        }
-    }
-
-    private static Bundle createOptions(@Nullable LibraryParams params) {
-        return params == null || params.getExtras() == null
-                ? new Bundle() : new Bundle(params.getExtras());
-    }
-
-    private static Bundle createOptions(@Nullable LibraryParams params, int page, int pageSize) {
-        Bundle options = createOptions(params);
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-        return options;
-    }
-
-    private static Bundle getExtras(@Nullable LibraryParams params) {
-        return params != null ? params.getExtras() : null;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem createRootMediaItem(@NonNull MediaBrowserCompat browserCompat) {
-        // TODO: Query again with getMediaItem() to get real media item.
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putString(METADATA_KEY_MEDIA_ID, browserCompat.getRoot())
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                .putLong(METADATA_KEY_PLAYABLE, 0)
-                .setExtras(browserCompat.getExtras())
-                .build();
-        return new MediaItem.Builder().setMetadata(metadata).build();
-    }
-
-    private class GetLibraryRootCallback extends MediaBrowserCompat.ConnectionCallback {
-        final ResolvableFuture<LibraryResult> mResult;
-        final LibraryParams mParams;
-
-        GetLibraryRootCallback(ResolvableFuture<LibraryResult> result, LibraryParams params) {
-            super();
-            mResult = result;
-            mParams = params;
-        }
-
-        @Override
-        public void onConnected() {
-            MediaBrowserCompat browserCompat;
-            synchronized (mLock) {
-                browserCompat = mBrowserCompats.get(mParams);
-            }
-            if (browserCompat == null) {
-                // Shouldn't be happen. Internal error?
-                mResult.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-            } else {
-                mResult.set(new LibraryResult(RESULT_SUCCESS,
-                        createRootMediaItem(browserCompat),
-                        MediaUtils.convertToLibraryParams(mContext, browserCompat.getExtras())));
-            }
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            onConnectionFailed();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            // Unknown extra field.
-            mResult.set(new LibraryResult(RESULT_ERROR_BAD_VALUE));
-            close();
-        }
-    }
-
-    private class SubscribeCallback extends SubscriptionCallback {
-        final ResolvableFuture<LibraryResult> mFuture;
-
-        SubscribeCallback(ResolvableFuture<LibraryResult> future) {
-            mFuture = future;
-        }
-
-        @Override
-        public void onError(@NonNull String parentId) {
-            onErrorInternal();
-        }
-
-        @Override
-        public void onError(@NonNull String parentId, @NonNull Bundle options) {
-            onErrorInternal();
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull String parentId,
-                @NonNull List<MediaBrowserCompat.MediaItem> children) {
-            onChildrenLoadedInternal(parentId, children);
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull final String parentId,
-                @NonNull List<MediaBrowserCompat.MediaItem> children,
-                @NonNull final Bundle options) {
-            onChildrenLoadedInternal(parentId, children);
-        }
-
-        private void onErrorInternal() {
-            // Don't need to unsubscribe here, because MediaBrowserServiceCompat can notify children
-            // changed after the initial failure and MediaBrowserCompat could receive the changes.
-            mFuture.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-        }
-
-        private void onChildrenLoadedInternal(@NonNull final String parentId,
-                @Nullable List<MediaBrowserCompat.MediaItem> children) {
-            if (TextUtils.isEmpty(parentId)) {
-                Log.w(TAG, "SubscribeCallback.onChildrenLoaded(): Ignoring empty parentId");
-                return;
-            }
-            final MediaBrowserCompat browserCompat = getBrowserCompat();
-            if (browserCompat == null) {
-                // Browser is closed.
-                return;
-            }
-            final int itemCount;
-            if (children != null) {
-                itemCount = children.size();
-            } else {
-                // Currently no way to tell failures in MediaBrowser#subscribe().
-                return;
-            }
-
-            final LibraryParams params = MediaUtils.convertToLibraryParams(mContext,
-                    browserCompat.getNotifyChildrenChangedOptions());
-            getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
-                @Override
-                public void run(@NonNull BrowserCallback callback) {
-                    // TODO(Post-P): Cache children result for later getChildren() calls.
-                    callback.onChildrenChanged(getMediaBrowser(), parentId, itemCount, params);
-                }
-            });
-            mFuture.set(new LibraryResult(RESULT_SUCCESS));
-        }
-    }
-
-    private class GetChildrenCallback extends SubscriptionCallback {
-        final ResolvableFuture<LibraryResult> mFuture;
-        final String mParentId;
-
-        GetChildrenCallback(ResolvableFuture<LibraryResult> future, String parentId) {
-            super();
-            mFuture = future;
-            mParentId = parentId;
-        }
-
-        @Override
-        public void onError(@NonNull String parentId) {
-            onErrorInternal();
-        }
-
-        @Override
-        public void onError(@NonNull String parentId, @NonNull Bundle options) {
-            onErrorInternal();
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull String parentId,
-                @NonNull List<MediaBrowserCompat.MediaItem> children) {
-            onChildrenLoadedInternal(parentId, children);
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull final String parentId,
-                @NonNull List<MediaBrowserCompat.MediaItem> children, @NonNull Bundle options) {
-            onChildrenLoadedInternal(parentId, children);
-        }
-
-        private void onErrorInternal() {
-            mFuture.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-        }
-
-        private void onChildrenLoadedInternal(@NonNull final String parentId,
-                @NonNull List<MediaBrowserCompat.MediaItem> children) {
-            if (TextUtils.isEmpty(parentId)) {
-                Log.w(TAG, "GetChildrenCallback.onChildrenLoaded(): Ignoring empty parentId");
-                return;
-            }
-            MediaBrowserCompat browserCompat = getBrowserCompat();
-            if (browserCompat == null) {
-                mFuture.set(new LibraryResult(RESULT_ERROR_SESSION_DISCONNECTED));
-                return;
-            }
-            browserCompat.unsubscribe(mParentId, GetChildrenCallback.this);
-
-            final List<MediaItem> items = new ArrayList<>();
-            if (children == null) {
-                // list are non-Null, so it must be internal error.
-                mFuture.set(new LibraryResult(RESULT_ERROR_UNKNOWN));
-            } else {
-                for (int i = 0; i < children.size(); i++) {
-                    items.add(MediaUtils.convertToMediaItem(children.get(i)));
-                }
-                // Don't set extra here, because 'extra' have different meanings between old
-                // API and new API as follows.
-                // - Old API: Extra/Option specified with subscribe().
-                // - New API: Extra from MediaLibraryService to MediaBrowser
-                mFuture.set(new LibraryResult(RESULT_SUCCESS, items, null));
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaConstants.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaConstants.java
deleted file mode 100644
index 4e1be15..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaConstants.java
+++ /dev/null
@@ -1,123 +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.media2.session;
-
-import android.net.Uri;
-
-/**
- * Media constants for sharing constants between media provider and consumer apps
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class MediaConstants {
-    /**
-     * A {@link android.net.Uri} scheme used in a media Uri.
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_SCHEME = "androidx";
-
-    /**
-     * A {@link android.net.Uri} authority used in a media Uri.
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_AUTHORITY = "media2-session";
-
-    /**
-     * A {@link android.net.Uri} path used by {@link android.support.v4.media.session
-     * .MediaControllerCompat.TransportControls#playFromMediaId}
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
-
-    /**
-     * A {@link android.net.Uri} path used by {@link android.support.v4.media.session
-     * .MediaControllerCompat.TransportControls#playFromSearch}
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
-
-    /**
-     * A {@link android.net.Uri} path used by {@link android.support.v4.media.session
-     * .MediaControllerCompat.TransportControls#prepareFromMediaId}
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
-
-    /**
-     * A {@link android.net.Uri} path used by {@link android.support.v4.media.session
-     * .MediaControllerCompat.TransportControls#prepareFromSearch}
-     *
-     * See {@link MediaController#setMediaUri} and
-     * {@link MediaSession.SessionCallback#onSetMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
-
-    /**
-     * A {@link android.net.Uri} path for encoding how the Uri will be translated when connected
-     * to {@link android.support.v4.media.session.MediaSessionCompat}.
-     *
-     * See {@link MediaController#setMediaUri} for more details.
-     */
-    public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
-
-    // From scheme to path, plus path delimiter
-    static final String MEDIA_URI_SET_MEDIA_URI_PREFIX =
-            new Uri.Builder()
-                    .scheme(MEDIA_URI_SCHEME)
-                    .authority(MEDIA_URI_AUTHORITY)
-                    .path(MEDIA_URI_PATH_SET_MEDIA_URI).build().toString() + "?";
-
-    /**
-     * A {@link android.net.Uri} query for media ID.
-     *
-     * @see MediaSession.SessionCallback#onSetMediaUri
-     * @see MediaController#setMediaUri
-     */
-    public static final String MEDIA_URI_QUERY_ID = "id";
-
-    /**
-     * A {@link android.net.Uri} query for search query.
-     *
-     * @see MediaSession.SessionCallback#onSetMediaUri
-     * @see MediaController#setMediaUri
-     */
-    public static final String MEDIA_URI_QUERY_QUERY = "query";
-
-    /**
-     * A {@link android.net.Uri} query for media Uri.
-     *
-     * @see MediaController#setMediaUri
-     */
-    public static final String MEDIA_URI_QUERY_URI = "uri";
-
-    static final String ARGUMENT_CAPTIONING_ENABLED = "androidx.media2.argument.CAPTIONING_ENABLED";
-
-    private MediaConstants() {
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaController.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaController.java
deleted file mode 100644
index a7eddd0..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaController.java
+++ /dev/null
@@ -1,2286 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX;
-import static androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-import static androidx.media2.common.SessionPlayer.REPEAT_MODE_NONE;
-import static androidx.media2.common.SessionPlayer.SHUFFLE_MODE_NONE;
-import static androidx.media2.common.SessionPlayer.UNKNOWN_TIME;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntDef;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.util.ObjectsCompat;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.RepeatMode;
-import androidx.media2.common.SessionPlayer.ShuffleMode;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.Closeable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * Allows an app to interact with an active {@link MediaSession} or a {@link MediaSessionService}
- * which would provide {@link MediaSession}. Media buttons and other commands can be sent to the
- * session.
- *
- * <p>MediaController objects are thread-safe.
- *
- * <p>Topics covered here:
- *
- * <ol>
- *   <li><a href="#ControllerLifeCycle">Controller Lifecycle</a>
- *   <li><a href="#MediaSessionInTheSameProcess">Controlling the {@link MediaSession} in the same
- *       process</a>
- *   <li><a href="#PackageVisibilityFilter">Package Visibility Filter</a>
- * </ol>
- *
- * <h3 id="ControllerLifeCycle">Controller Lifecycle</h3>
- *
- * <p>When a controller is created with the {@link SessionToken} for a {@link MediaSession} (i.e.
- * session token type is {@link SessionToken#TYPE_SESSION}), the controller will connect to the
- * specific session.
- *
- * <p>When a controller is created with the {@link SessionToken} for a {@link MediaSessionService}
- * (i.e. session token type is {@link SessionToken#TYPE_SESSION_SERVICE} or {@link
- * SessionToken#TYPE_LIBRARY_SERVICE}), the controller binds to the service for connecting to a
- * {@link MediaSession} in it. {@link MediaSessionService} will provide a session to connect.
- *
- * <p>When a controller connects to a session, {@link
- * MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)} will be called
- * to either accept or reject the connection. Wait {@link
- * ControllerCallback#onConnected(MediaController, SessionCommandGroup)} or {@link
- * ControllerCallback#onDisconnected(MediaController)} for the result.
- *
- * <p>When the connected session is closed, the controller will receive {@link
- * ControllerCallback#onDisconnected(MediaController)}.
- *
- * <p>When you're done, use {@link #close()} to clean up resources. This also helps session service
- * to be destroyed when there's no controller associated with it.
- *
- * <p><a name="MediaSessionInTheSameProcess"></a>
- *
- * <h3>Controlling the MediaSession in the same process</h3>
- *
- * When you control the {@link MediaSession} and its {@link SessionPlayer}, it's recommended to use
- * them directly rather than creating {@link MediaController}. However, if you need to use {@link
- * MediaController} in the same process, be careful not to block session callback executor's thread.
- * Here's an example code that would never return due to the thread issue.
- *
- * <p>
- *
- * <pre>{@code
- * // Code runs on the main thread.
- * MediaSession session = new MediaSession.Builder(context, player)
- *    .setSessionCallback(sessionCallback, Context.getMainExecutor(context)).build();
- * MediaController controller = new MediaController.Builder(context)
- *    .setSessionToken(session.getToken())
- *    .setControllerCallback(Context.getMainExecutor(context), controllerCallback)
- *    .build();
- *
- * // This will hang and never return.
- * controller.play().get();
- * }</pre>
- *
- * When a session gets a command from a controller, the session's {@link
- * MediaSession.SessionCallback#onCommandRequest} would be executed on the session's callback
- * executor to decide whether to ignore or handle the incoming command. To do so, the session's
- * callback executor shouldn't be blocked to handle the incoming calls. However, if you call {@link
- * ListenableFuture#get} on the thread for the session callback executor, then your call wouldn't be
- * executed and never return.
- *
- * <p>To avoid such issue, don't block the session callback executor's thread. Creating a dedicated
- * thread for the session callback executor would be helpful. See {@link
- * Executors#newSingleThreadExecutor} for creating a new thread.
- *
- * <h3 id="PackageVisibilityFilter">Package Visibility Filter</h3>
- *
- * <p>The app targeting API level 30 or higher must include a {@code <queries>} element in their
- * manifest to connect to a service component of another app like {@link MediaSessionService},
- * {@link MediaLibraryService}, or {@link androidx.media.MediaBrowserServiceCompat}). See the
- * following example and <a href="{@docRoot}training/package-visibility">this guide</a> for more
- * information.
- *
- * <pre>{@code
- * <!-- As intent actions -->
- * <intent>
- *   <action android:name="androidx.media2.session.MediaSessionService" />
- * </intent>
- * <intent>
- *   <action android:name="androidx.media2.session.MediaLibraryService" />
- * </intent>
- * <intent>
- *   <action android:name="android.media.browse.MediaBrowserService" />
- * </intent>
- * <!-- Or, as a package name -->
- * <package android:name="package_name_of_the_other_app" />
- * }</pre>
- *
- * @see MediaSession
- * @see MediaSessionService
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class MediaController implements Closeable {
-    private static final String TAG = "MediaController";
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef({AudioManager.ADJUST_LOWER, AudioManager.ADJUST_RAISE, AudioManager.ADJUST_SAME,
-            AudioManager.ADJUST_MUTE, AudioManager.ADJUST_UNMUTE, AudioManager.ADJUST_TOGGLE_MUTE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface VolumeDirection {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef(value = {AudioManager.FLAG_SHOW_UI, AudioManager.FLAG_ALLOW_RINGER_MODES,
-            AudioManager.FLAG_PLAY_SOUND, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
-            AudioManager.FLAG_VIBRATE}, flag = true)
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface VolumeFlags {}
-
-    final Object mLock = new Object();
-    @GuardedBy("mLock")
-    MediaControllerImpl mImpl;
-    @GuardedBy("mLock")
-    boolean mClosed;
-
-    final ControllerCallback mPrimaryCallback;
-    final Executor mPrimaryCallbackExecutor;
-
-    @GuardedBy("mLock")
-    private final List<Pair<ControllerCallback, Executor>> mExtraControllerCallbacks =
-            new ArrayList<>();
-
-    // For testing.
-    Long mTimeDiff;
-
-    /**
-     * Creates a {@link MediaController} from the {@link SessionToken}.
-     *
-     * @param context context
-     * @param token token to connect to
-     * @param executor executor to run callbacks on
-     * @param callback controller callback to receive changes in
-     */
-    MediaController(@NonNull final Context context, @NonNull final SessionToken token,
-            @Nullable Bundle connectionHints, @Nullable Executor executor,
-            @Nullable ControllerCallback callback) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        if (token == null) {
-            throw new NullPointerException("token shouldn't be null");
-        }
-        mPrimaryCallback = callback;
-        mPrimaryCallbackExecutor = executor;
-        synchronized (mLock) {
-            mImpl = createImpl(context, token, connectionHints);
-        }
-    }
-
-    /**
-     * Creates a {@link MediaController} from the {@link MediaSessionCompat.Token}.
-     *
-     * @param context context
-     * @param token token to connect to
-     * @param executor executor to run callbacks on
-     * @param callback controller callback to receive changes in
-     */
-    MediaController(@NonNull final Context context, @NonNull final MediaSessionCompat.Token token,
-            @Nullable final Bundle connectionHints, @Nullable final Executor executor,
-            @Nullable final ControllerCallback callback) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        if (token == null) {
-            throw new NullPointerException("token shouldn't be null");
-        }
-        mPrimaryCallback = callback;
-        mPrimaryCallbackExecutor = executor;
-        SessionToken.createSessionToken(context, token, (compatToken, sessionToken) -> {
-            boolean closed;
-            synchronized (mLock) {
-                closed = mClosed;
-                if (!closed) {
-                    mImpl = createImpl(context, sessionToken, connectionHints);
-                }
-            }
-            if (closed) {
-                notifyAllControllerCallbacks(cb -> cb.onDisconnected(MediaController.this));
-            }
-        });
-    }
-
-    MediaControllerImpl createImpl(@NonNull Context context, @NonNull SessionToken token,
-            @Nullable Bundle connectionHints) {
-        if (token.isLegacySession()) {
-            return new MediaControllerImplLegacy(context, this, token);
-        } else {
-            return new MediaControllerImplBase(context, this, token, connectionHints);
-        }
-    }
-
-    MediaControllerImpl getImpl() {
-        synchronized (mLock) {
-            return mImpl;
-        }
-    }
-
-    /**
-     * Releases this object, and disconnects from the session. After this, callbacks wouldn't be
-     * received.
-     */
-    @Override
-    public void close() {
-        try {
-            MediaControllerImpl impl;
-            synchronized (mLock) {
-                if (mClosed) {
-                    return;
-                }
-                mClosed = true;
-                impl = mImpl;
-            }
-            if (impl != null) {
-                impl.close();
-            }
-        } catch (Exception e) {
-            // Should not be here.
-        }
-    }
-
-    /**
-     * Returns the {@link SessionToken} of the connected session.
-     * If it is not connected yet, it returns {@code null}.
-     * <p>
-     * This may differ from the {@link SessionToken} from the constructor. For example, if the
-     * controller is created with the token for {@link MediaSessionService}, this would return
-     * token for the {@link MediaSession} in the service.
-     *
-     * @return SessionToken of the connected session, or {@code null} if not connected
-     */
-    @Nullable
-    public SessionToken getConnectedToken() {
-        return isConnected() ? getImpl().getConnectedToken() : null;
-    }
-
-    /**
-     * Returns whether this class is connected to active {@link MediaSession} or not.
-     */
-    public boolean isConnected() {
-        MediaControllerImpl impl = getImpl();
-        return impl != null && impl.isConnected();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * starts or resumes playback.
-     * <p>
-     * On success, this transfers the player state to {@link SessionPlayer#PLAYER_STATE_PLAYING}
-     * and a {@link SessionResult} would be returned with the current media item when the command
-     * was completed.
-     * If the player state is {@link SessionPlayer#PLAYER_STATE_IDLE}, the session would also call
-     * {@link SessionPlayer#prepare} and then {@link SessionPlayer#play} to start playback. If you
-     * want to have finer grained control of the playback start, call {@link #prepare} manually
-     * before this. Calling {@link #prepare} in advance would help this method to start playback
-     * faster and also help to take audio focus at the last moment.
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat}, then this will be grouped
-     * together with previously called {@link #setMediaUri}. See {@link #setMediaUri} for
-     * details.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #prepare
-     * @see #setMediaUri
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> play() {
-        if (isConnected()) {
-            return getImpl().play();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * pauses playback.
-     * <p>
-     * On success, this transfers the player state to {@link SessionPlayer#PLAYER_STATE_PAUSED} and
-     * a {@link SessionResult} would be returned with the current media item when the command
-     * was completed. If it is called in {@link SessionPlayer#PLAYER_STATE_IDLE} or
-     * {@link SessionPlayer#PLAYER_STATE_ERROR}, it whould be ignored and a {@link SessionResult}
-     * would be returned with {@link SessionResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> pause() {
-        if (isConnected()) {
-            return getImpl().pause();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * prepares the media items for playback. During this time, the player may allocate resources
-     * required to play, such as audio and video decoders. Before calling this API, sets media
-     * item(s) through either {@link #setMediaItem} or {@link #setPlaylist}.
-     * <p>
-     * On success, this transfers the player state from {@link SessionPlayer#PLAYER_STATE_IDLE} to
-     * {@link SessionPlayer#PLAYER_STATE_PAUSED} and a {@link SessionResult} would be returned
-     * with the prepared media item when the command completed. If it's not called in
-     * {@link SessionPlayer#PLAYER_STATE_IDLE}, it would be ignored and {@link SessionResult}
-     * would be returned with {@link SessionResult#RESULT_ERROR_INVALID_STATE}.
-     * <p>
-     * Playback can be started without this. But this provides finer grained control of playback
-     * start. See {@link #play} for details.
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat}, then this call may be grouped
-     * together with previously called {@link #setMediaUri}. See {@link #setMediaUri} for
-     * details.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #play
-     * @see #setMediaUri
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> prepare() {
-        if (isConnected()) {
-            return getImpl().prepare();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * to fast forward playback.
-     * <p>
-     * The implementation may be different depending on the players. For example, it can be
-     * implemented by seeking forward once, series of seeking forward, or increasing playback speed.
-     * If you need full control, then use {@link #seekTo} or {@link #setPlaybackSpeed} directly.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see MediaSession.SessionCallback#onFastForward(MediaSession, MediaSession.ControllerInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> fastForward() {
-        if (isConnected()) {
-            return getImpl().fastForward();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * to rewind playback.
-     * <p>
-     * The implementation may be different depending on the players. For example, it can be
-     * implemented by seeking backward once, series of seeking backward, or decreasing playback
-     * speed. If you need full control, then use {@link #seekTo} or {@link #setPlaybackSpeed}
-     * directly.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see MediaSession.SessionCallback#onRewind(MediaSession, MediaSession.ControllerInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> rewind() {
-        if (isConnected()) {
-            return getImpl().rewind();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * skips backward within the current media item.
-     * <p>
-     * The implementation may be different depending on the players. For example, it can be
-     * implemented by seeking forward once with the fixed amount of seconds, or seeking forward to
-     * the nearest bookmark. If you need full control, then use {@link #seekTo} directly.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see MediaSession.SessionCallback#onSkipForward(MediaSession, MediaSession.ControllerInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> skipForward() {
-        // To match with KEYCODE_MEDIA_SKIP_FORWARD
-        if (isConnected()) {
-            return getImpl().skipForward();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * skips forward within the current media item.
-     * <p>
-     * The implementation may be different depending on the players. For example, it can be
-     * implemented by seeking backward once with the fixed amount of seconds, or seeking backward to
-     * the nearest bookmark. If you need full control, then use {@link #seekTo} directly.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see MediaSession.SessionCallback#onSkipBackward(MediaSession, MediaSession.ControllerInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> skipBackward() {
-        // To match with KEYCODE_MEDIA_SKIP_BACKWARD
-        if (isConnected()) {
-            return getImpl().skipBackward();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * seeks to the specified position.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
-     * calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed. If it's called in {@link SessionPlayer#PLAYER_STATE_IDLE}, it is ignored
-     * and a {@link SessionResult} would be returned with
-     * {@link SessionResult#RESULT_ERROR_INVALID_STATE}.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @param position the new playback position in ms. The value should be in the range of start
-     * and end positions defined in {@link MediaItem}.
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> seekTo(long position) {
-        if (isConnected()) {
-            return getImpl().seekTo(position);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the connected {@link MediaSession} sets the volume of the output that is
-     * playing on. The command will be ignored if it does not support
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}.
-     * <p>
-     * If the session is local playback, this changes the device's volume with the stream that
-     * session's player is using. Flags will be specified for the {@link AudioManager}.
-     * <p>
-     * If the session is remote player (i.e. session has set volume provider), its volume provider
-     * will receive this request instead.
-     *
-     * @param value the value to set it to, between 0 and the reported max
-     * @param flags flags from {@link AudioManager} to include with the volume request for local
-     *              playback
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #getPlaybackInfo()
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setVolumeTo(int value, @VolumeFlags int flags) {
-        if (isConnected()) {
-            return getImpl().setVolumeTo(value, flags);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the connected {@link MediaSession} adjusts the volume of the output that is
-     * playing on. The direction must be one of {@link AudioManager#ADJUST_LOWER},
-     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
-     * <p>
-     * The command will be ignored if the session does not support
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}.
-     * <p>
-     * If the session is local playback, this changes the device's volume with the stream that
-     * session's player is using. Flags will be specified for the {@link AudioManager}.
-     * <p>
-     * If the session is remote player (i.e. session has set volume provider), its volume provider
-     * will receive this request instead.
-     *
-     * @param direction the direction to adjust the volume in
-     * @param flags flags from {@link AudioManager} to include with the volume request for local
-     *              playback
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #getPlaybackInfo()
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> adjustVolume(@VolumeDirection int direction,
-            @VolumeFlags int flags) {
-        if (isConnected()) {
-            return getImpl().adjustVolume(direction, flags);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets an intent for launching UI associated with this session if one exists.
-     * If it is not connected yet, it returns {@code null}.
-     *
-     * @return a {@link PendingIntent} to launch UI or null
-     */
-    @Nullable
-    public PendingIntent getSessionActivity() {
-        return isConnected() ? getImpl().getSessionActivity() : null;
-    }
-
-    /**
-     * Gets the state of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. If it is not connected yet, it returns
-     * {@link SessionPlayer#PLAYER_STATE_IDLE}.
-     *
-     * @return the player state
-     * @see ControllerCallback#onPlayerStateChanged(MediaController, int)
-     * @see SessionPlayer#PLAYER_STATE_IDLE
-     * @see SessionPlayer#PLAYER_STATE_PAUSED
-     * @see SessionPlayer#PLAYER_STATE_PLAYING
-     * @see SessionPlayer#PLAYER_STATE_ERROR
-     */
-    public int getPlayerState() {
-        return isConnected() ? getImpl().getPlayerState() : PLAYER_STATE_IDLE;
-    }
-
-    /**
-     * Gets the duration of the current media item, or {@link SessionPlayer#UNKNOWN_TIME} if
-     * unknown or not connected. If the current {@link MediaItem} has either start or end position,
-     * then duration would be adjusted accordingly instead of returning the whole size of the
-     * {@link MediaItem}.
-     *
-     * @return the duration in ms, or {@link SessionPlayer#UNKNOWN_TIME} if unknonw or not
-     *         connected.
-     */
-    public long getDuration() {
-        return isConnected() ? getImpl().getDuration() : UNKNOWN_TIME;
-    }
-
-    /**
-     * Gets the playback position of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
-     * So the position {@code 0} means the start position of the {@link MediaItem}.
-     *
-     * @return the current playback position in ms, or {@link SessionPlayer#UNKNOWN_TIME}
-     *         if unknown or not connected
-     */
-    public long getCurrentPosition() {
-        return isConnected() ? getImpl().getCurrentPosition() : UNKNOWN_TIME;
-    }
-
-    /**
-     * Gets the playback speed to be used by the of the {@link SessionPlayer} associated with the
-     * connected {@link MediaSession} when playing. A value of {@code 1.0f}
-     * is the default playback value, and a negative value indicates reverse playback.
-     * <p>
-     * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
-     *
-     * @return speed the playback speed, or 0f if unknown or not connected
-     */
-    public float getPlaybackSpeed() {
-        return isConnected() ? getImpl().getPlaybackSpeed() : 0f;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets the playback speed. The default playback speed is {@code 1.0f} is the default, and
-     * negative values indicate reverse playback and {@code 0.0f} is not allowed.
-     * <p>
-     * The supported playback speed range depends on the player, so it is recommended to query the
-     * actual speed of the player via {@link #getPlaybackSpeed()} after the operation completes.
-     * In particular, please note that the player may not support reverse playback.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @param playbackSpeed the requested playback speed
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see #getPlaybackSpeed()
-     * @see SessionPlayer.PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
-     * @throws IllegalArgumentException if the {@code speed} is equal to zero.
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setPlaybackSpeed(float playbackSpeed) {
-        if (playbackSpeed == 0.0f) {
-            throw new IllegalArgumentException("speed must not be zero");
-        }
-        if (isConnected()) {
-            return getImpl().setPlaybackSpeed(playbackSpeed);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the current buffering state of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}.
-     * <p>
-     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
-     * So the position {@code 0} means the start position of the {@link MediaItem}.
-     *
-     * @return the buffering state, or {@link SessionPlayer#BUFFERING_STATE_UNKNOWN}
-     *         if unknown or not connected
-     */
-    @SessionPlayer.BuffState
-    public int getBufferingState() {
-        return isConnected() ? getImpl().getBufferingState() : BUFFERING_STATE_UNKNOWN;
-    }
-
-    /**
-     * Gets the position for how much has been buffered of the {@link SessionPlayer} associated
-     * with the connected {@link MediaSession}, or {@link SessionPlayer#UNKNOWN_TIME} if
-     * unknown or not connected.
-     *
-     * @return buffering position in ms, or {@link SessionPlayer#UNKNOWN_TIME} if
-     *         unknown or not connected
-     */
-    public long getBufferedPosition() {
-        return isConnected() ? getImpl().getBufferedPosition() : UNKNOWN_TIME;
-    }
-
-    /**
-     * Get the current playback info for this session.
-     * If it is not connected yet, it returns {@code null}.
-     *
-     * @return the current playback info or null
-     */
-    @Nullable
-    public PlaybackInfo getPlaybackInfo() {
-        return isConnected() ? getImpl().getPlaybackInfo() : null;
-    }
-
-    /**
-     * Requests that the connected {@link MediaSession} rates the media. This will cause the rating
-     * to be set for the current user. The rating style must follow the user rating style from the
-     * session.You can get the rating style from the session through the
-     * {@link MediaMetadata#getRating(String)} with the key
-     * {@link MediaMetadata#METADATA_KEY_USER_RATING}.
-     * <p>
-     * If the user rating was {@code null}, the media item does not accept setting user rating.
-     *
-     * @param mediaId the non-empty media id
-     * @param rating the rating to set
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setRating(@NonNull String mediaId,
-            @NonNull Rating rating) {
-        if (mediaId == null) {
-            throw new NullPointerException("mediaId shouldn't be null");
-        } else if (TextUtils.isEmpty(mediaId)) {
-            throw new IllegalArgumentException("mediaId shouldn't be empty");
-        }
-        if (rating == null) {
-            throw new NullPointerException("rating shouldn't be null");
-        }
-        if (isConnected()) {
-            return getImpl().setRating(mediaId, rating);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Sends a custom command to the session
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat},
-     * {@link SessionResult#getResultCode()} will return the custom result code from the
-     * {@link ResultReceiver#onReceiveResult(int, Bundle)} instead of the standard result codes
-     * defined in the {@link SessionResult}.
-     * <p>
-     * A command is not accepted if it is not a custom command.
-     *
-     * @param command custom command
-     * @param args optional argument
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> sendCustomCommand(@NonNull SessionCommand command,
-            @Nullable Bundle args) {
-        if (command == null) {
-            throw new NullPointerException("command shouldn't be null");
-        }
-        if (command.getCommandCode() != SessionCommand.COMMAND_CODE_CUSTOM) {
-            throw new IllegalArgumentException("command should be a custom command");
-        }
-        if (isConnected()) {
-            return getImpl().sendCustomCommand(command, args);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the playlist of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. It can be {@code null} if the playlist hasn't been set or it's reset
-     * by {@link #setMediaItem}.
-     * <p>
-     * This list may differ from the list that was specified with
-     * {@link #setPlaylist(List, MediaMetadata)} depending on the {@link SessionPlayer}
-     * implementation.
-     *
-     * @return playlist, or {@code null} if the playlist hasn't been set or the controller isn't
-     *         connected
-     * @see SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST
-     */
-    @Nullable
-    public List<MediaItem> getPlaylist() {
-        return isConnected() ? getImpl().getPlaylist() : null;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets the playlist with the list of media IDs. Use this, {@link #setMediaUri}, or
-     * {@link #setMediaItem} to specify which items to play.
-     * <p>
-     * All media IDs in the list shouldn't be an empty string.
-     * <p>
-     * This can be called multiple times in any states other than
-     * {@link SessionPlayer#PLAYER_STATE_ERROR}. This would override previous call of this,
-     * {@link #setMediaItem}, or {@link #setMediaUri}.
-     * <p>
-     * The {@link ControllerCallback#onPlaylistChanged} and/or
-     * {@link ControllerCallback#onCurrentMediaItemChanged} would be called when it's completed.
-     * The current item would be the first item in the playlist.
-     *
-     * @param list list of media id. Shouldn't contain an empty id
-     * @param metadata metadata of the playlist
-     * @see #setMediaItem
-     * @see #setMediaUri
-     * @see ControllerCallback#onCurrentMediaItemChanged
-     * @see ControllerCallback#onPlaylistChanged
-     * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-     * @throws IllegalArgumentException if the list is {@code null} or contains any empty string.
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setPlaylist(@NonNull List<String> list,
-            @Nullable MediaMetadata metadata) {
-        if (list == null) {
-            throw new NullPointerException("list shouldn't be null");
-        }
-        for (int i = 0; i < list.size(); i++) {
-            if (TextUtils.isEmpty(list.get(i))) {
-                throw new IllegalArgumentException("list shouldn't contain empty id, index=" + i);
-            }
-        }
-        if (isConnected()) {
-            return getImpl().setPlaylist(list, metadata);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets a {@link MediaItem} for playback. Use this, {@link #setMediaUri}, or
-     * {@link #setPlaylist} to specify which items to play.
-     * If you want to change current item in the playlist, use one of {@link #skipToPlaylistItem},
-     * {@link #skipToNextPlaylistItem}, or {@link #skipToPreviousPlaylistItem} instead of this
-     * method.
-     * <p>
-     * This can be called multiple times in any states other than
-     * {@link SessionPlayer#PLAYER_STATE_ERROR}. This would override previous call of this,
-     * {@link #setMediaUri}, or {@link #setPlaylist}.
-     * <p>
-     * The {@link ControllerCallback#onPlaylistChanged} and/or
-     * {@link ControllerCallback#onCurrentMediaItemChanged} would be called when it's completed.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} set.
-     *
-     * @param mediaId the non-empty media id of the item to play
-     * @see #setMediaUri
-     * @see #setPlaylist
-     * @see ControllerCallback#onCurrentMediaItemChanged
-     * @see ControllerCallback#onPlaylistChanged
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setMediaItem(@NonNull String mediaId) {
-        if (TextUtils.isEmpty(mediaId)) {
-            throw new IllegalArgumentException("mediaId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().setMediaItem(mediaId);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the connected {@link MediaSession} sets a specific {@link Uri} for playback.
-     * Use this, {@link #setMediaItem}, or {@link #setPlaylist} to specify which items to play.
-     * <p>
-     * This can be called multiple times in any states other than
-     * {@link SessionPlayer#PLAYER_STATE_ERROR}. This would override previous call of this,
-     * {@link #setMediaItem}, or {@link #setPlaylist}.
-     * <p>
-     * The {@link ControllerCallback#onPlaylistChanged} and/or
-     * {@link ControllerCallback#onCurrentMediaItemChanged} would be called when it's completed.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} set.
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat}, this call will be grouped
-     * together with later {@link #prepare} or {@link #play}, depending on the Uri pattern as
-     * follows:
-     * <table>
-     * <tr>
-     * <th align="left">Uri patterns</th><th>Following API calls</th><th>Method</th>
-     * </tr><tr>
-     * <td rowspan="2">{@code androidx://media2-session/setMediaUri?uri=[uri]}</td>
-     * <td>{@link #prepare}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
-     * </tr><tr>
-     * <td>{@link #play}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
-     * </tr><tr>
-     * <td rowspan="2">{@code androidx://media2-session/setMediaUri?id=[mediaId]}</td>
-     * <td>{@link #prepare}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#prepareFromMediaId prepareFromMediaId}
-     * </tr><tr>
-     * <td>{@link #play}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#playFromMediaId playFromMediaId}
-     * </tr><tr>
-     * <td rowspan="2">{@code androidx://media2-session/setMediaUri?query=[query]}</td>
-     * <td>{@link #prepare}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#prepareFromSearch prepareFromSearch}
-     * </tr><tr>
-     * <td>{@link #play}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#playFromSearch playFromSearch}
-     * </tr><tr>
-     * <td rowspan="2">Does not match with any pattern above</td>
-     * <td>{@link #prepare}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
-     * </tr><tr>
-     * <td>{@link #play}</td>
-     * <td>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
-     * </tr></table>
-     * <p>
-     * Returned {@link ListenableFuture} will return {@link SessionResult#RESULT_SUCCESS} when it's
-     * handled together with {@link #prepare} or {@link #play}. If this API is called multiple times
-     * without prepare or play, then {@link SessionResult#RESULT_INFO_SKIPPED} will be returned
-     * for previous calls.
-     *
-     * @param uri the Uri of the item to play
-     * @see #setMediaItem
-     * @see #setPlaylist
-     * @see ControllerCallback#onCurrentMediaItemChanged
-     * @see ControllerCallback#onPlaylistChanged
-     * @see MediaConstants#MEDIA_URI_AUTHORITY
-     * @see MediaConstants#MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID
-     * @see MediaConstants#MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID
-     * @see MediaConstants#MEDIA_URI_PATH_PREPARE_FROM_SEARCH
-     * @see MediaConstants#MEDIA_URI_PATH_PLAY_FROM_SEARCH
-     * @see MediaConstants#MEDIA_URI_PATH_SET_MEDIA_URI
-     * @see MediaConstants#MEDIA_URI_QUERY_ID
-     * @see MediaConstants#MEDIA_URI_QUERY_QUERY
-     * @see MediaConstants#MEDIA_URI_QUERY_URI
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setMediaUri(@NonNull Uri uri, @Nullable Bundle extras) {
-        if (uri == null) {
-            throw new NullPointerException("mediaUri shouldn't be null");
-        }
-        if (isConnected()) {
-            return getImpl().setMediaUri(uri, extras);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * updates the playlist metadata while keeping the playlist as-is.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @param metadata metadata of the playlist
-     * @see ControllerCallback#onPlaylistMetadataChanged(MediaController, MediaMetadata)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> updatePlaylistMetadata(
-            @Nullable MediaMetadata metadata) {
-        if (isConnected()) {
-            return getImpl().updatePlaylistMetadata(metadata);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the playlist metadata of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}.
-     *
-     * @return metadata of the playlist, or null if none is set or the controller is not
-     *         connected
-     * @see ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)
-     * @see ControllerCallback#onPlaylistMetadataChanged(MediaController, MediaMetadata)
-     */
-    @Nullable
-    public MediaMetadata getPlaylistMetadata() {
-        return isConnected() ? getImpl().getPlaylistMetadata() : null;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * adds the media item to the playlist at the index with the media
-     * ID. Index equals to or greater than the current playlist size
-     * (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of the playlist.
-     * <p>
-     * If index is less than or equal to the current index of the playlist,
-     * the current index of the playlist will be increased correspondingly.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} added.
-     *
-     * @param index the index you want to add
-     * @param mediaId the non-empty media id of the new item
-     * @see ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)
-     * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> addPlaylistItem(@IntRange(from = 0) int index,
-            @NonNull String mediaId) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (TextUtils.isEmpty(mediaId)) {
-            throw new IllegalArgumentException("mediaId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().addPlaylistItem(index, mediaId);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * removes the media item at index in the playlist.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} removed.
-     *
-     * @param index the media item you want to add
-     * @see ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> removePlaylistItem(@IntRange(from = 0) int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (isConnected()) {
-            return getImpl().removePlaylistItem(index);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * replaces the media item at index in the playlist with the media ID.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} set.
-     *
-     * @param index the index of the item to replace
-     * @param mediaId the non-empty media id of the new item
-     * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-     * @see ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> replacePlaylistItem(@IntRange(from = 0) int index,
-            @NonNull String mediaId) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (TextUtils.isEmpty(mediaId)) {
-            throw new IllegalArgumentException("mediaId shouldn't be empty");
-        }
-        if (isConnected()) {
-            return getImpl().replacePlaylistItem(index, mediaId);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * moves the media item at {@code fromIdx} to {@code toIdx} in the playlist.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with {@code item} set.
-     *
-     * @param fromIndex the media item's initial index in the playlist
-     * @param toIndex the media item's target index in the playlist
-     * @see ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> movePlaylistItem(@IntRange(from = 0) int fromIndex,
-            @IntRange(from = 0) int toIndex) {
-        if (fromIndex < 0 || toIndex < 0) {
-            throw new IllegalArgumentException("indexes shouldn't be negative");
-        }
-        if (isConnected()) {
-            return getImpl().movePlaylistItem(fromIndex, toIndex);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the current media item of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. This can be currently playing or would be played with later
-     * {@link #play}. This value may be updated when
-     * {@link ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)} or
-     * {@link ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)} is
-     * called.
-     *
-     * @return the current media item. Can be {@code null} only when media item or playlist hasn't
-     *         been set or the controller is not connected.
-     * @see #setMediaItem
-     * @see #setPlaylist
-     */
-    @Nullable
-    public MediaItem getCurrentMediaItem() {
-        return isConnected() ? getImpl().getCurrentMediaItem() : null;
-    }
-
-    /**
-     * Gets the current item index in the playlist of the {@link SessionPlayer} associated with
-     * the connected {@link MediaSession}. The value would be updated when
-     * {@link ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)} or
-     * {@link ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)} is called.
-     *
-     * @return the index of current item in playlist, or {@link SessionPlayer#INVALID_ITEM_INDEX}
-     *         if current media item does not exist or playlist hasn't been set
-     */
-    public int getCurrentMediaItemIndex() {
-        return isConnected() ? getImpl().getCurrentMediaItemIndex() : INVALID_ITEM_INDEX;
-    }
-
-    /**
-     * Gets the previous item index in the playlist of the {@link SessionPlayer} associated with
-     * the connected {@link MediaSession}. This value would be updated when
-     * {@link ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)} or
-     * {@link ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)} is called.
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat}, this will always return
-     * {@link SessionPlayer#INVALID_ITEM_INDEX}.
-     *
-     * @return the index of previous item in playlist, or {@link SessionPlayer#INVALID_ITEM_INDEX}
-     *         if previous media item does not exist or playlist hasn't been set
-     */
-    public int getPreviousMediaItemIndex() {
-        return isConnected() ? getImpl().getPreviousMediaItemIndex() : INVALID_ITEM_INDEX;
-    }
-
-    /**
-     * Gets the next item index in the playlist of the {@link SessionPlayer} associated with
-     * the connected {@link MediaSession}. This value would be updated when
-     * {@link ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)} or
-     * {@link ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)} is called.
-     * <p>
-     * Interoperability: When connected to
-     * {@link android.support.v4.media.session.MediaSessionCompat}, this will always return
-     * {@link SessionPlayer#INVALID_ITEM_INDEX}..
-     *
-     * @return the index of next item in playlist, or {@link SessionPlayer#INVALID_ITEM_INDEX}
-     *         if next media item does not exist or playlist hasn't been set
-     */
-    public int getNextMediaItemIndex() {
-        return isConnected() ? getImpl().getNextMediaItemIndex() : INVALID_ITEM_INDEX;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * skips to the previous item in the playlist.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> skipToPreviousPlaylistItem() {
-        if (isConnected()) {
-            return getImpl().skipToPreviousItem();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * skips to the next item in the playlist.
-     * <p>
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> skipToNextPlaylistItem() {
-        if (isConnected()) {
-            return getImpl().skipToNextItem();
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * skips to the item in the playlist at the index.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @param index The index of the item you want to play in the playlist
-     * @return a {@link ListenableFuture} representing the pending completion of the command
-     * @see ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> skipToPlaylistItem(@IntRange(from = 0) int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (isConnected()) {
-            return getImpl().skipToPlaylistItem(index);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the repeat mode of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. If it is not connected yet, it returns
-     * {@link SessionPlayer#REPEAT_MODE_NONE}.
-     *
-     * @return repeat mode
-     * @see SessionPlayer#REPEAT_MODE_NONE
-     * @see SessionPlayer#REPEAT_MODE_ONE
-     * @see SessionPlayer#REPEAT_MODE_ALL
-     * @see SessionPlayer#REPEAT_MODE_GROUP
-     */
-    @RepeatMode
-    public int getRepeatMode() {
-        return isConnected() ? getImpl().getRepeatMode() : REPEAT_MODE_NONE;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets the repeat mode.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @param repeatMode repeat mode
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see SessionPlayer#REPEAT_MODE_NONE
-     * @see SessionPlayer#REPEAT_MODE_ONE
-     * @see SessionPlayer#REPEAT_MODE_ALL
-     * @see SessionPlayer#REPEAT_MODE_GROUP
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setRepeatMode(@RepeatMode int repeatMode) {
-        if (isConnected()) {
-            return getImpl().setRepeatMode(repeatMode);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the shuffle mode of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. If it is not connected yet, it returns
-     * {@link SessionPlayer#SHUFFLE_MODE_NONE}.
-     *
-     * @return the shuffle mode
-     * @see SessionPlayer#SHUFFLE_MODE_NONE
-     * @see SessionPlayer#SHUFFLE_MODE_ALL
-     * @see SessionPlayer#SHUFFLE_MODE_GROUP
-     */
-    @ShuffleMode
-    public int getShuffleMode() {
-        return isConnected() ? getImpl().getShuffleMode() : SHUFFLE_MODE_NONE;
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets the shuffle mode.
-     * <p>
-     * On success, a {@link SessionResult} would be returned with the current media item when the
-     * command completed.
-     *
-     * @param shuffleMode the shuffle mode
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see SessionPlayer#SHUFFLE_MODE_NONE
-     * @see SessionPlayer#SHUFFLE_MODE_ALL
-     * @see SessionPlayer#SHUFFLE_MODE_GROUP
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setShuffleMode(@ShuffleMode int shuffleMode) {
-        if (isConnected()) {
-            return getImpl().setShuffleMode(shuffleMode);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the video size of the {@link SessionPlayer} associated with the connected
-     * {@link MediaSession}. If it is not connected yet, it returns {@code new VideoSize(0, 0)}.
-     *
-     * @return the size of the video. The width and height of size could be 0 if there is no video
-     *         or the size has not been determined yet.
-     * @see ControllerCallback#onVideoSizeChanged(MediaController, VideoSize)
-     */
-    @NonNull
-    public VideoSize getVideoSize() {
-        return isConnected() ? getImpl().getVideoSize() : new VideoSize(0, 0);
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * sets the {@link Surface} to be used as the sink for the video portion of the media.
-     * <p>
-     * A null surface will reset any Surface and result in only the audio track being played.
-     * <p>
-     * On success, a {@link SessionResult} is returned with the current media item when the command
-     * completed.
-     *
-     * @param surface the {@link Surface} to be used for the video portion of the media
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setSurface(@Nullable Surface surface) {
-        if (isConnected()) {
-            return getImpl().setSurface(surface);
-        }
-        return createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the full list of selected and unselected tracks that the media contains of the
-     * {@link SessionPlayer} associated with the connected {@link MediaSession}. The order of
-     * the list is irrelevant as different players expose tracks in different ways, but the tracks
-     * will generally be ordered based on track type.
-     * <p>
-     * The types of tracks supported may vary based on player implementation.
-     *
-     * @return list of tracks. The total number of tracks is the size of the list. If empty,
-     *         an empty list would be returned.
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     */
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        return isConnected() ? getImpl().getTracks() : Collections.emptyList();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * selects the {@link TrackInfo} for the current media item.
-     * <p>
-     * Generally one track will be selected for each track type.
-     * <p>
-     * The types of tracks supported may vary based on players.
-     * <p>
-     * Note: {@link #getTracks()} returns the list of tracks that can be selected, but the
-     * list may be invalidated when
-     * {@link ControllerCallback#onTracksChanged(MediaController, List)} is called.
-     *
-     * @param trackInfo track to be selected
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     * @see ControllerCallback#onTrackSelected(MediaController, TrackInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> selectTrack(@NonNull TrackInfo trackInfo) {
-        if (trackInfo == null) {
-            throw new NullPointerException("TrackInfo shouldn't be null");
-        }
-        return isConnected() ? getImpl().selectTrack(trackInfo) : createDisconnectedFuture();
-    }
-
-    /**
-     * Requests that the {@link SessionPlayer} associated with the connected {@link MediaSession}
-     * deselects the {@link TrackInfo} for the current media item.
-     * <p>
-     * Generally, a track should already be selected in order to be deselected and audio and video
-     * tracks should not be deselected.
-     * <p>
-     * The types of tracks supported may vary based on players.
-     * <p>
-     * Note: {@link #getSelectedTrack(int)} returns the currently selected track per track type that
-     * can be deselected, but the list may be invalidated when
-     * {@link ControllerCallback#onTracksChanged(MediaController, List)} is called.
-     *
-     * @param trackInfo track to be deselected
-     * @return a {@link ListenableFuture} which represents the pending completion of the command
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     * @see ControllerCallback#onTrackDeselected(MediaController, TrackInfo)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> deselectTrack(@NonNull TrackInfo trackInfo) {
-        if (trackInfo == null) {
-            throw new NullPointerException("TrackInfo shouldn't be null");
-        }
-        return isConnected() ? getImpl().deselectTrack(trackInfo) : createDisconnectedFuture();
-    }
-
-    /**
-     * Gets the currently selected track for the given track type of the {@link SessionPlayer}
-     * associated with the connected {@link MediaSession}. If it is not connected yet, it returns
-     * {@code null}.
-     * <p>
-     * The returned value can be outdated after
-     * {@link ControllerCallback#onTracksChanged(MediaController, List)},
-     * {@link ControllerCallback#onTrackSelected(MediaController, TrackInfo)},
-     * or {@link ControllerCallback#onTrackDeselected(MediaController, TrackInfo)} is called.
-     *
-     * @param trackType type of selected track
-     * @return selected track info
-     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-     */
-    @Nullable
-    public TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
-        return isConnected() ? getImpl().getSelectedTrack(trackType) : null;
-    }
-
-    /**
-     * Sets the time diff forcefully when calculating current position.
-     * @param timeDiff {@code null} for reset
-     *
-     */
-    @RestrictTo(LIBRARY)
-    public void setTimeDiff(Long timeDiff) {
-        mTimeDiff = timeDiff;
-    }
-
-    /**
-     * Registers an extra {@link ControllerCallback}.
-     * @param executor a callback executor
-     * @param callback a ControllerCallback
-     * @see #unregisterExtraCallback(ControllerCallback)
-     *
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void registerExtraCallback(@NonNull /*@CallbackExecutor*/ Executor executor,
-            @NonNull ControllerCallback callback) {
-        if (executor == null) {
-            throw new NullPointerException("executor shouldn't be null");
-        }
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
-        }
-        boolean found = false;
-        synchronized (mLock) {
-            for (Pair<ControllerCallback, Executor> pair : mExtraControllerCallbacks) {
-                if (pair.first == callback) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                mExtraControllerCallbacks.add(new Pair<>(callback, executor));
-            }
-        }
-        if (found) {
-            Log.w(TAG, "registerExtraCallback: the callback already exists");
-        }
-    }
-
-    /**
-     * Unregisters an {@link ControllerCallback} that has been registered by
-     * {@link #registerExtraCallback(Executor, ControllerCallback)}.
-     * The callback passed to {@link Builder#setControllerCallback(Executor, ControllerCallback)}
-     * can not be unregistered by this method.
-     * @param callback a ControllerCallback
-     * @see #registerExtraCallback(Executor, ControllerCallback)
-     *
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void unregisterExtraCallback(@NonNull ControllerCallback callback) {
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
-        }
-        boolean found = false;
-        synchronized (mLock) {
-            for (int i = mExtraControllerCallbacks.size() - 1; i >= 0; i--) {
-                if (mExtraControllerCallbacks.get(i).first == callback) {
-                    found = true;
-                    mExtraControllerCallbacks.remove(i);
-                    break;
-                }
-            }
-        }
-        if (!found) {
-            Log.w(TAG, "unregisterExtraCallback: no such callback found");
-        }
-    }
-
-    @RestrictTo(LIBRARY)
-    @NonNull
-    public List<Pair<ControllerCallback, Executor>> getExtraControllerCallbacks() {
-        List<Pair<ControllerCallback, Executor>> extraCallbacks;
-        synchronized (mLock) {
-            extraCallbacks = new ArrayList<>(mExtraControllerCallbacks);
-        }
-        return extraCallbacks;
-    }
-
-    /**
-     * Gets the cached allowed commands from {@link ControllerCallback#onAllowedCommandsChanged}.
-     * If it is not connected yet, it returns {@code null}.
-     *
-     * @return the allowed commands
-     */
-    @Nullable
-    public SessionCommandGroup getAllowedCommands() {
-        if (!isConnected()) {
-            return null;
-        }
-        return getImpl().getAllowedCommands();
-    }
-
-    private static ListenableFuture<SessionResult> createDisconnectedFuture() {
-        return SessionResult.createFutureWithResult(
-                SessionResult.RESULT_ERROR_SESSION_DISCONNECTED);
-    }
-
-    void notifyPrimaryControllerCallback(
-            @NonNull final ControllerCallbackRunnable callbackRunnable) {
-        if (mPrimaryCallback != null && mPrimaryCallbackExecutor != null) {
-            mPrimaryCallbackExecutor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callbackRunnable.run(mPrimaryCallback);
-                }
-            });
-        }
-    }
-
-    @RestrictTo(LIBRARY)
-    public void notifyAllControllerCallbacks(
-            @NonNull final ControllerCallbackRunnable callbackRunnable) {
-        notifyPrimaryControllerCallback(callbackRunnable);
-
-        for (Pair<ControllerCallback, Executor> pair : getExtraControllerCallbacks()) {
-            final ControllerCallback callback = pair.first;
-            final Executor executor = pair.second;
-            if (callback == null) {
-                Log.e(TAG, "notifyAllControllerCallbacks: mExtraControllerCallbacks contains a "
-                        + "null ControllerCallback! Ignoring.");
-                continue;
-            }
-            if (executor == null) {
-                Log.e(TAG, "notifyAllControllerCallbacks: mExtraControllerCallbacks contains a "
-                        + "null Executor! Ignoring.");
-                continue;
-            }
-            executor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callbackRunnable.run(callback);
-                }
-            });
-        }
-    }
-
-    @RestrictTo(LIBRARY)
-    public interface ControllerCallbackRunnable {
-        /**
-         * Runs the {@link ControllerCallback}.
-         *
-         * @param callback the callback
-         */
-        void run(@NonNull ControllerCallback callback);
-    }
-
-    interface MediaControllerImpl extends Closeable {
-        @Nullable SessionToken getConnectedToken();
-        boolean isConnected();
-        ListenableFuture<SessionResult> play();
-        ListenableFuture<SessionResult> pause();
-        ListenableFuture<SessionResult> prepare();
-        ListenableFuture<SessionResult> fastForward();
-        ListenableFuture<SessionResult> rewind();
-        ListenableFuture<SessionResult> seekTo(long pos);
-        ListenableFuture<SessionResult> skipForward();
-        ListenableFuture<SessionResult> skipBackward();
-        ListenableFuture<SessionResult> setVolumeTo(int value, @VolumeFlags int flags);
-        ListenableFuture<SessionResult> adjustVolume(@VolumeDirection int direction,
-                @VolumeFlags int flags);
-        @Nullable
-        PendingIntent getSessionActivity();
-        int getPlayerState();
-        long getDuration();
-        long getCurrentPosition();
-        float getPlaybackSpeed();
-        ListenableFuture<SessionResult> setPlaybackSpeed(float speed);
-        @SessionPlayer.BuffState
-        int getBufferingState();
-        long getBufferedPosition();
-        @Nullable
-        PlaybackInfo getPlaybackInfo();
-        ListenableFuture<SessionResult> setRating(@NonNull String mediaId,
-                @NonNull Rating rating);
-        ListenableFuture<SessionResult> sendCustomCommand(@NonNull SessionCommand command,
-                @Nullable Bundle args);
-        @Nullable
-        List<MediaItem> getPlaylist();
-        ListenableFuture<SessionResult> setPlaylist(@NonNull List<String> list,
-                @Nullable MediaMetadata metadata);
-        ListenableFuture<SessionResult> setMediaItem(@NonNull String mediaId);
-        ListenableFuture<SessionResult> setMediaUri(@NonNull Uri uri, @Nullable Bundle extras);
-        ListenableFuture<SessionResult> updatePlaylistMetadata(
-                @Nullable MediaMetadata metadata);
-        @Nullable MediaMetadata getPlaylistMetadata();
-        ListenableFuture<SessionResult> addPlaylistItem(int index, @NonNull String mediaId);
-        ListenableFuture<SessionResult> removePlaylistItem(int index);
-        ListenableFuture<SessionResult> replacePlaylistItem(int index,
-                @NonNull String mediaId);
-        ListenableFuture<SessionResult> movePlaylistItem(int fromIndex, int toIndex);
-        MediaItem getCurrentMediaItem();
-        int getCurrentMediaItemIndex();
-        int getPreviousMediaItemIndex();
-        int getNextMediaItemIndex();
-        ListenableFuture<SessionResult> skipToPreviousItem();
-        ListenableFuture<SessionResult> skipToNextItem();
-        ListenableFuture<SessionResult> skipToPlaylistItem(int index);
-        @RepeatMode
-        int getRepeatMode();
-        ListenableFuture<SessionResult> setRepeatMode(@RepeatMode int repeatMode);
-        @ShuffleMode
-        int getShuffleMode();
-        ListenableFuture<SessionResult> setShuffleMode(@ShuffleMode int shuffleMode);
-        @NonNull
-        VideoSize getVideoSize();
-        ListenableFuture<SessionResult> setSurface(@Nullable Surface surface);
-        @NonNull
-        List<TrackInfo> getTracks();
-        ListenableFuture<SessionResult> selectTrack(TrackInfo trackInfo);
-        ListenableFuture<SessionResult> deselectTrack(TrackInfo trackInfo);
-        @Nullable
-        TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType);
-        @Nullable
-        SessionCommandGroup getAllowedCommands();
-
-        // Internally used methods
-        @NonNull
-        Context getContext();
-        @Nullable
-        MediaBrowserCompat getBrowserCompat();
-    }
-
-    /**
-     * Builder for {@link MediaController}.
-     *
-     * <p>To set the token of the session for the controller to connect to, one of the {@link
-     * #setSessionToken(SessionToken)} or {@link #setSessionCompatToken(MediaSessionCompat.Token)}
-     * should be called. Otherwise, the {@link #build()} will throw an {@link
-     * IllegalArgumentException}.
-     *
-     * <p>Any incoming event from the {@link MediaSession} will be handled on the callback executor.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder
-            extends BuilderBase<MediaController, Builder, ControllerCallback> {
-        public Builder(@NonNull Context context) {
-            super(context);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionToken(@NonNull SessionToken token) {
-            return super.setSessionToken(token);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionCompatToken(@NonNull MediaSessionCompat.Token compatToken) {
-            return super.setSessionCompatToken(compatToken);
-        }
-
-        @Override
-        @NonNull
-        public Builder setConnectionHints(@NonNull Bundle connectionHints) {
-            return super.setConnectionHints(connectionHints);
-        }
-
-        @Override
-        @NonNull
-        public Builder setControllerCallback(@NonNull Executor executor,
-                @NonNull ControllerCallback callback) {
-            return super.setControllerCallback(executor, callback);
-        }
-
-        /**
-         * Builds a {@link MediaController}.
-         *
-         * @throws IllegalArgumentException if both {@link SessionToken} and
-         * {@link MediaSessionCompat.Token} are not set.
-         * @return a new controller
-         */
-        @Override
-        @NonNull
-        public MediaController build() {
-            if (mToken == null && mCompatToken == null) {
-                throw new IllegalArgumentException("token and compat token shouldn't be both null");
-            }
-            if (mToken != null) {
-                return new MediaController(mContext, mToken, mConnectionHints,
-                        mCallbackExecutor, mCallback);
-            } else {
-                return new MediaController(mContext, mCompatToken, mConnectionHints,
-                        mCallbackExecutor, mCallback);
-            }
-        }
-    }
-
-    /**
-     * Base builder class for MediaController and its subclass. Any change in this class should be
-     * also applied to the subclasses {@link MediaController.Builder} and
-     * {@link MediaBrowser.Builder}.
-     * <p>
-     * APIs here should be package private, but should have documentations for developers.
-     * Otherwise, javadoc will generate documentation with the generic types such as follows.
-     * <pre>U extends BuilderBase<T, U, C> setControllerCallback(Executor executor,
-     * C callback)</pre>
-     * <p>
-     * This class is hidden to prevent from generating test stub, which fails with
-     * 'unexpected bound' because it tries to auto generate stub class as follows.
-     * <pre>abstract static class BuilderBase<
-     *      T extends androidx.media2.MediaController,
-     *      U extends androidx.media2.MediaController.BuilderBase<
-     *              T, U, C extends androidx.media2.MediaController.ControllerCallback>, C></pre>
-     */
-    @RestrictTo(LIBRARY)
-    abstract static class BuilderBase<T extends MediaController, U extends BuilderBase<T, U, C>,
-            C extends ControllerCallback> {
-        final Context mContext;
-        SessionToken mToken;
-        MediaSessionCompat.Token mCompatToken;
-        Bundle mConnectionHints;
-        Executor mCallbackExecutor;
-        ControllerCallback mCallback;
-
-        /**
-         * Creates a builder for {@link MediaController}.
-         *
-         * @param context context
-         */
-        BuilderBase(@NonNull Context context) {
-            if (context == null) {
-                throw new NullPointerException("context shouldn't be null");
-            }
-            mContext = context;
-        }
-
-        /**
-         * Sets the {@link SessionToken} for the controller to connect to.
-         * <p>
-         * When this method is called, the {@link MediaSessionCompat.Token} which was set by calling
-         * {@link #setSessionCompatToken} is removed.
-         * <p>
-         * Detailed behavior of the {@link MediaController} differs according to the type of the
-         * token as follows.
-         * <p>
-         * <ol>
-         * <li>Connected to a {@link SessionToken#TYPE_SESSION} token
-         * <p>
-         * The controller connects to the specified session directly. It's recommended when you're
-         * sure which session to control, or a you've got token directly from the session app.
-         * <p>
-         * This can be used only when the session for the token is running. Once the session is
-         * closed, the token becomes unusable.
-         * </li>
-         * <li>Connected to a {@link SessionToken#TYPE_SESSION_SERVICE} or
-         * {@link SessionToken#TYPE_LIBRARY_SERVICE}
-         * <p>
-         * The controller connects to the session provided by the
-         * {@link MediaSessionService#onGetSession(ControllerInfo)}.
-         * It's up to the service's decision which session would be returned for the connection.
-         * Use the {@link #getConnectedSessionToken()} to know the connected session.
-         * <p>
-         * This can be used regardless of the session app is running or not. The controller would
-         * bind to the service while connected to wake up and keep the service process running.
-         * </li>
-         * </ol>
-         *
-         * @param token token to connect to
-         * @return the Builder to allow chaining
-         * @see MediaSessionService#onGetSession(ControllerInfo)
-         * @see #getConnectedSessionToken()
-         * @see #setConnectionHints(Bundle)
-         */
-        @NonNull
-        @SuppressWarnings("unchecked")
-        U setSessionToken(@NonNull SessionToken token) {
-            if (token == null) {
-                throw new NullPointerException("token shouldn't be null");
-            }
-            mToken = token;
-            mCompatToken = null;
-            return (U) this;
-        }
-
-        /**
-         * Sets the {@link MediaSessionCompat.Token} for the controller to connect to.
-         * <p>
-         * When this method is called, the {@link SessionToken} which was set by calling
-         * {@link #setSessionToken(SessionToken)} is removed.
-         *
-         * @param compatToken token to connect to
-         * @return the Builder to allow chaining
-         */
-        @NonNull
-        @SuppressWarnings("unchecked")
-        U setSessionCompatToken(@NonNull MediaSessionCompat.Token compatToken) {
-            if (compatToken == null) {
-                throw new NullPointerException("compatToken shouldn't be null");
-            }
-            mCompatToken = compatToken;
-            mToken = null;
-            return (U) this;
-        }
-
-        /**
-         * Sets the connection hints for the controller.
-         * <p>
-         * {@code connectionHints} is a session-specific argument to send to the session when
-         * connecting. The contents of this bundle may affect the connection result.
-         * <p>
-         * The hints specified here are only used when when connecting to the {@link MediaSession}.
-         * They will be ignored when connecting to {@link MediaSessionCompat}.
-         *
-         * @param connectionHints a bundle which contains the connection hints
-         * @return the Builder to allow chaining
-         * @throws IllegalArgumentException if the bundle contains any non-framework Parcelable
-         * objects.
-         */
-        @NonNull
-        @SuppressWarnings("unchecked")
-        public U setConnectionHints(@NonNull Bundle connectionHints) {
-            if (connectionHints == null) {
-                throw new NullPointerException("connectionHints shouldn't be null");
-            }
-            if (MediaUtils.doesBundleHaveCustomParcelable(connectionHints)) {
-                throw new IllegalArgumentException(
-                        "connectionHints shouldn't contain any custom parcelables");
-            }
-            mConnectionHints = new Bundle(connectionHints);
-            return (U) this;
-        }
-
-        /**
-         * Sets the callback for the controller and its executor.
-         *
-         * @param executor callback executor
-         * @param callback controller callback.
-         * @return the Builder to allow chaining
-         */
-        @NonNull
-        @SuppressWarnings("unchecked")
-        U setControllerCallback(@NonNull Executor executor, @NonNull C callback) {
-            if (executor == null) {
-                throw new NullPointerException("executor shouldn't be null");
-            }
-            if (callback == null) {
-                throw new NullPointerException("callback shouldn't be null");
-            }
-            mCallbackExecutor = executor;
-            mCallback = callback;
-            return (U) this;
-        }
-
-        @NonNull
-        abstract T build();
-    }
-
-    /**
-     * Interface for listening to change in activeness of the {@link MediaSession}. It's active if
-     * and only if it has set a player.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public abstract static class ControllerCallback {
-        /**
-         * Called when the controller is successfully connected to the session. The controller
-         * becomes available afterwards.
-         *
-         * @param controller the controller for this event
-         * @param allowedCommands commands that's allowed by the session
-         */
-        public void onConnected(@NonNull MediaController controller,
-                @NonNull SessionCommandGroup allowedCommands) {}
-
-        /**
-         * Called when the session refuses the controller or the controller is disconnected from
-         * the session. The controller becomes unavailable afterwards and the callback wouldn't
-         * be called.
-         * <p>
-         * It will be also called after the {@link #close()}, so you can put clean up code here.
-         * You don't need to call {@link #close()} after this.
-         *
-         * @param controller the controller for this event
-         */
-        public void onDisconnected(@NonNull MediaController controller) {}
-
-        /**
-         * Called when the session set the custom layout through the
-         * {@link MediaSession#setCustomLayout(MediaSession.ControllerInfo, List)}.
-         * <p>
-         * Can be called before {@link #onConnected(MediaController, SessionCommandGroup)}
-         * is called.
-         * <p>
-         * Default implementation returns {@link SessionResult#RESULT_ERROR_NOT_SUPPORTED}.
-         *
-         * @param controller the controller for this event
-         * @param layout
-         */
-        @SessionResult.ResultCode
-        public int onSetCustomLayout(
-                @NonNull MediaController controller, @NonNull List<CommandButton> layout) {
-            return SessionResult.RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when the session has changed anything related with the {@link PlaybackInfo}.
-         * <p>
-         * Interoperability: When connected to
-         * {@link android.support.v4.media.session.MediaSessionCompat}, this may be called when the
-         * session changes playback info by calling
-         * {@link android.support.v4.media.session.MediaSessionCompat#setPlaybackToLocal(int)} or
-         * {@link android.support.v4.media.session.MediaSessionCompat#setPlaybackToRemote(
-         * VolumeProviderCompat)}}. Specifically:
-         * <ul>
-         * <li> Prior to API 21, this will always be called whenever any of those two methods is
-         *      called.
-         * <li> From API 21 to 22, this is called only when the playback type is changed from local
-         *      to remote (i.e. not from remote to local).
-         * <li> From API 23, this is called only when the playback type is changed.
-         * </ul>
-         *
-         * @param controller the controller for this event
-         * @param info new playback info
-         */
-        public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                @NonNull PlaybackInfo info) {}
-
-        /**
-         * Called when the allowed commands are changed by session.
-         *
-         * @param controller the controller for this event
-         * @param commands newly allowed commands
-         */
-        public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                @NonNull SessionCommandGroup commands) {}
-
-        /**
-         * Called when the session sent a custom command. Returns a {@link SessionResult} for
-         * session to get notification back. If the {@code null} is returned,
-         * {@link SessionResult#RESULT_ERROR_UNKNOWN} will be returned.
-         * <p>
-         * Default implementation returns {@link SessionResult#RESULT_ERROR_NOT_SUPPORTED}.
-         *
-         * @param controller the controller for this event
-         * @param command
-         * @param args
-         * @return result of handling custom command
-         */
-        @NonNull
-        public SessionResult onCustomCommand(@NonNull MediaController controller,
-                @NonNull SessionCommand command, @Nullable Bundle args) {
-            return new SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED);
-        }
-
-        /**
-         * Called when the player state is changed.
-         *
-         * @param controller the controller for this event
-         * @param state the new player state
-         */
-        public void onPlayerStateChanged(@NonNull MediaController controller,
-                @SessionPlayer.PlayerState int state) {}
-
-        /**
-         * Called when playback speed is changed.
-         *
-         * @param controller the controller for this event
-         * @param speed speed
-         */
-        public void onPlaybackSpeedChanged(@NonNull MediaController controller,
-                float speed) {}
-
-        /**
-         * Called to report buffering events for a media item.
-         * <p>
-         * Use {@link #getBufferedPosition()} for current buffering position.
-         *
-         * @param controller the controller for this event
-         * @param item the media item for which buffering is happening
-         * @param state the new buffering state
-         */
-        public void onBufferingStateChanged(@NonNull MediaController controller,
-                @NonNull MediaItem item, @SessionPlayer.BuffState int state) {}
-
-        /**
-         * Called to indicate that seeking is completed.
-         *
-         * @param controller the controller for this event
-         * @param position the previous seeking request
-         */
-        public void onSeekCompleted(@NonNull MediaController controller, long position) {}
-
-        /**
-         * Called when the current item is changed. It's also called after
-         * {@link #setPlaylist} or {@link #setMediaItem}.
-         * Also called when {@link MediaItem#setMetadata(MediaMetadata)} is called on the current
-         * media item.
-         * <p>
-         * When it's called, you should invalidate previous playback information and wait for later
-         * callbacks. Also, current, previous, and next media item indices may need to be updated.
-         *
-         * @param controller the controller for this event
-         * @param item new current media item
-         * @see #getPlaylist()
-         * @see #getPlaylistMetadata()
-         */
-        public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                @Nullable MediaItem item) {}
-
-        /**
-         * Called when a playlist is changed. It's also called after {@link #setPlaylist} or
-         * {@link #setMediaItem}.
-         * Also called when {@link MediaItem#setMetadata(MediaMetadata)} is called on a media item
-         * that is contained in the current playlist.
-         * <p>
-         * When it's called, current, previous, and next media item indices may need to be updated.
-         *
-         * @param controller the controller for this event
-         * @param list new playlist
-         * @param metadata new metadata
-         * @see #getPlaylist()
-         * @see #getPlaylistMetadata()
-         */
-        public void onPlaylistChanged(@NonNull MediaController controller,
-                @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {}
-
-        /**
-         * Called when a playlist metadata is changed.
-         *
-         * @param controller the controller for this event
-         * @param metadata new metadata
-         */
-        public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                @Nullable MediaMetadata metadata) {}
-
-        /**
-         * Called when the shuffle mode is changed.
-         *
-         * @param controller the controller for this event
-         * @param shuffleMode repeat mode
-         * @see SessionPlayer#SHUFFLE_MODE_NONE
-         * @see SessionPlayer#SHUFFLE_MODE_ALL
-         * @see SessionPlayer#SHUFFLE_MODE_GROUP
-         */
-        public void onShuffleModeChanged(@NonNull MediaController controller,
-                @SessionPlayer.ShuffleMode int shuffleMode) {}
-
-        /**
-         * Called when the repeat mode is changed.
-         *
-         * @param controller the controller for this event
-         * @param repeatMode repeat mode
-         * @see SessionPlayer#REPEAT_MODE_NONE
-         * @see SessionPlayer#REPEAT_MODE_ONE
-         * @see SessionPlayer#REPEAT_MODE_ALL
-         * @see SessionPlayer#REPEAT_MODE_GROUP
-         */
-        public void onRepeatModeChanged(@NonNull MediaController controller,
-                @SessionPlayer.RepeatMode int repeatMode) {}
-
-        /**
-         * Called when the playback is completed.
-         *
-         * @param controller the controller for this event
-         */
-        public void onPlaybackCompleted(@NonNull MediaController controller) {}
-
-        /**
-         * @deprecated Use {@link #onVideoSizeChanged(MediaController, VideoSize)} instead.
-         */
-        @RestrictTo(LIBRARY)
-        @Deprecated
-        public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
-                @NonNull VideoSize videoSize) {}
-
-        /**
-         * Called when video size is changed.
-         *
-         * @param controller the controller for this event
-         * @param videoSize the size of video
-         */
-        public void onVideoSizeChanged(@NonNull MediaController controller,
-                @NonNull VideoSize videoSize) {}
-
-        /**
-         * Called when the tracks of the current media item is changed such as
-         * 1) when tracks of a media item become available,
-         * 2) when new tracks are found during playback, or
-         * 3) when the current media item is changed.
-         * <p>
-         * When it's called, you should invalidate previous track information and use the new
-         * tracks to call {@link #selectTrack(TrackInfo)} or
-         * {@link #deselectTrack(TrackInfo)}.
-         * <p>
-         * The types of tracks supported may vary based on player implementation.
-         *
-         * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-         * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-         *
-         * @param controller the controller for this event
-         * @param tracks the list of tracks. It can be empty.
-         */
-        public void onTracksChanged(@NonNull MediaController controller,
-                @NonNull List<TrackInfo> tracks) {}
-
-        /**
-         * Called when a track is selected.
-         * <p>
-         * The types of tracks supported may vary based on player implementation, but generally
-         * one track will be selected for each track type.
-         *
-         * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-         * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-         *
-         * @param controller the controller for this event
-         * @param trackInfo the selected track
-         */
-        public void onTrackSelected(@NonNull MediaController controller,
-                @NonNull TrackInfo trackInfo) {}
-
-        /**
-         * Called when a track is deselected.
-         * <p>
-         * The types of tracks supported may vary based on player implementation, but generally
-         * a track should already be selected in order to be deselected and audio and video tracks
-         * should not be deselected.
-         *
-         * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
-         * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
-         * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
-         *
-         * @param controller the controller for this event
-         * @param trackInfo the deselected track
-         */
-        public void onTrackDeselected(@NonNull MediaController controller,
-                @NonNull TrackInfo trackInfo) {}
-
-        /**
-         * Called when the subtitle track has new subtitle data available.
-         * @param controller the controller for this event
-         * @param item the MediaItem of this media item
-         * @param track the track that has the subtitle data
-         * @param data the subtitle data
-         */
-        public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) {}
-    }
-
-    /**
-     * Holds information about the way volume is handled for this session.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    // The same as MediaController.PlaybackInfo
-    @Deprecated
-    @VersionedParcelize
-    public static final class PlaybackInfo implements VersionedParcelable {
-        @ParcelField(1)
-        int mPlaybackType;
-        @ParcelField(2)
-        int mControlType;
-        @ParcelField(3)
-        int mMaxVolume;
-        @ParcelField(4)
-        int mCurrentVolume;
-        @ParcelField(5)
-        AudioAttributesCompat mAudioAttrsCompat;
-
-        // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-        /**
-         * The session uses local playback.
-         */
-        public static final int PLAYBACK_TYPE_LOCAL = 1;
-        /**
-         * The session uses remote playback.
-         */
-        public static final int PLAYBACK_TYPE_REMOTE = 2;
-
-        /**
-         * Used for VersionedParcelable
-         */
-        PlaybackInfo() {
-        }
-
-        PlaybackInfo(int playbackType, AudioAttributesCompat attrs, int controlType, int max,
-                int current) {
-            mPlaybackType = playbackType;
-            mAudioAttrsCompat = attrs;
-            mControlType = controlType;
-            mMaxVolume = max;
-            mCurrentVolume = current;
-        }
-
-        /**
-         * Gets the type of playback which affects volume handling. One of:
-         * <ul>
-         * <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
-         * <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
-         * </ul>
-         *
-         * @return the type of playback this session is using
-         */
-        public int getPlaybackType() {
-            return mPlaybackType;
-        }
-
-        /**
-         * Gets the audio attributes for this session. The attributes will affect
-         * volume handling for the session. When the volume type is
-         * {@link #PLAYBACK_TYPE_REMOTE} these may be ignored by the
-         * remote volume handler.
-         *
-         * @return the attributes for this session
-         */
-        @Nullable
-        public AudioAttributesCompat getAudioAttributes() {
-            return mAudioAttrsCompat;
-        }
-
-        /**
-         * Gets the type of volume control that can be used. One of:
-         * <ul>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
-         * </ul>
-         *
-         * @return the type of volume control that may be used with this session
-         */
-        public int getControlType() {
-            return mControlType;
-        }
-
-        /**
-         * Gets the maximum volume that may be set for this session.
-         * <p>
-         * This is only meaningful when the playback type is {@link #PLAYBACK_TYPE_REMOTE}.
-         *
-         * @return the maximum allowed volume where this session is playing
-         */
-        public int getMaxVolume() {
-            return mMaxVolume;
-        }
-
-        /**
-         * Gets the current volume for this session.
-         * <p>
-         * This is only meaningful when the playback type is {@link #PLAYBACK_TYPE_REMOTE}.
-         *
-         * @return the current volume where this session is playing
-         */
-        public int getCurrentVolume() {
-            return mCurrentVolume;
-        }
-
-        @Override
-        public int hashCode() {
-            return ObjectsCompat.hash(
-                    mPlaybackType, mControlType, mMaxVolume, mCurrentVolume, mAudioAttrsCompat);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (!(obj instanceof PlaybackInfo)) {
-                return false;
-            }
-            PlaybackInfo other = (PlaybackInfo) obj;
-            return mPlaybackType == other.mPlaybackType
-                    && mControlType == other.mControlType
-                    && mMaxVolume == other.mMaxVolume
-                    && mCurrentVolume == other.mCurrentVolume
-                    && ObjectsCompat.equals(mAudioAttrsCompat, other.mAudioAttrsCompat);
-        }
-
-        static PlaybackInfo createPlaybackInfo(int playbackType, AudioAttributesCompat attrs,
-                int controlType, int max, int current) {
-            return new PlaybackInfo(playbackType, attrs, controlType, max, current);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
deleted file mode 100644
index c1e8344..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
+++ /dev/null
@@ -1,1406 +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.media2.session;
-
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION;
-import static androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.UNKNOWN_TIME;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PREPARE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SURFACE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.session.SessionResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.session.SessionToken.TYPE_SESSION;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.v4.media.MediaBrowserCompat;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.RepeatMode;
-import androidx.media2.common.SessionPlayer.ShuffleMode;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.ControllerCallbackRunnable;
-import androidx.media2.session.MediaController.MediaControllerImpl;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaController.VolumeDirection;
-import androidx.media2.session.MediaController.VolumeFlags;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-class MediaControllerImplBase implements MediaControllerImpl {
-    private static final boolean THROW_EXCEPTION_FOR_NULL_RESULT = true;
-    private static final SessionResult RESULT_WHEN_CLOSED =
-            new SessionResult(RESULT_INFO_SKIPPED);
-
-    static final String TAG = "MC2ImplBase";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final MediaController mInstance;
-    private final Context mContext;
-    private final Object mLock = new Object();
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final SessionToken mToken;
-    private final IBinder.DeathRecipient mDeathRecipient;
-    final SequencedFutureManager mSequencedFutureManager;
-    final MediaControllerStub mControllerStub;
-
-    @GuardedBy("mLock")
-    private SessionToken mConnectedToken;
-    @GuardedBy("mLock")
-    private SessionServiceConnection mServiceConnection;
-    @GuardedBy("mLock")
-    private boolean mIsReleased;
-    @GuardedBy("mLock")
-    private List<MediaItem> mPlaylist;
-    @GuardedBy("mLock")
-    private MediaMetadata mPlaylistMetadata;
-    @GuardedBy("mLock")
-    private @RepeatMode int mRepeatMode;
-    @GuardedBy("mLock")
-    private @ShuffleMode int mShuffleMode;
-    @GuardedBy("mLock")
-    private int mPlayerState;
-    @GuardedBy("mLock")
-    private long mPositionEventTimeMs;
-    @GuardedBy("mLock")
-    private long mPositionMs;
-    @GuardedBy("mLock")
-    private float mPlaybackSpeed;
-    @GuardedBy("mLock")
-    private MediaItem mCurrentMediaItem;
-    @GuardedBy("mLock")
-    private int mCurrentMediaItemIndex = -1;
-    @GuardedBy("mLock")
-    private int mPreviousMediaItemIndex = -1;
-    @GuardedBy("mLock")
-    private int mNextMediaItemIndex = -1;
-    @GuardedBy("mLock")
-    private int mBufferingState;
-    @GuardedBy("mLock")
-    private long mBufferedPositionMs;
-    @GuardedBy("mLock")
-    private PlaybackInfo mPlaybackInfo;
-    @GuardedBy("mLock")
-    private PendingIntent mSessionActivity;
-    @GuardedBy("mLock")
-    private SessionCommandGroup mAllowedCommands;
-    @GuardedBy("mLock")
-    private VideoSize mVideoSize = new VideoSize(0, 0);
-    @GuardedBy("mLock")
-    private List<TrackInfo> mTracks = Collections.emptyList();
-    @GuardedBy("mLock")
-    private SparseArray<TrackInfo> mSelectedTracks = new SparseArray<>();
-
-    // Assignment should be used with the lock hold, but should be used without a lock to prevent
-    // potential deadlock.
-    @GuardedBy("mLock")
-    private volatile IMediaSession mISession;
-
-    MediaControllerImplBase(Context context, MediaController instance, SessionToken token,
-            @Nullable Bundle connectionHints) {
-        mInstance = instance;
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        if (token == null) {
-            throw new NullPointerException("token shouldn't be null");
-        }
-        mContext = context;
-        mSequencedFutureManager = new SequencedFutureManager();
-        mControllerStub = new MediaControllerStub(this);
-        mToken = token;
-        mDeathRecipient = new IBinder.DeathRecipient() {
-            @Override
-            public void binderDied() {
-                mInstance.close();
-            }
-        };
-
-        boolean connectionRequested;
-        if (mToken.getType() == TYPE_SESSION) {
-            // Session
-            mServiceConnection = null;
-            connectionRequested = requestConnectToSession(connectionHints);
-        } else {
-            mServiceConnection = new SessionServiceConnection(connectionHints);
-            connectionRequested = requestConnectToService();
-        }
-        if (!connectionRequested) {
-            mInstance.close();
-        }
-    }
-
-    @Override
-    public void close() {
-        if (DEBUG) {
-            Log.d(TAG, "release from " + mToken);
-        }
-        final IMediaSession iSession;
-        synchronized (mLock) {
-            iSession = mISession;
-            if (mIsReleased) {
-                // Prevent re-enterance from the ControllerCallback.onDisconnected()
-                return;
-            }
-            mIsReleased = true;
-            if (mServiceConnection != null) {
-                mContext.unbindService(mServiceConnection);
-                mServiceConnection = null;
-            }
-            mISession = null;
-            mControllerStub.destroy();
-        }
-        if (iSession != null) {
-            int seq = mSequencedFutureManager.obtainNextSequenceNumber();
-            try {
-                iSession.asBinder().unlinkToDeath(mDeathRecipient, 0);
-                iSession.release(mControllerStub, seq);
-            } catch (RemoteException e) {
-                // No-op.
-            }
-        }
-        mSequencedFutureManager.close();
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onDisconnected(mInstance);
-            }
-        });
-    }
-
-    @Override
-    public SessionToken getConnectedToken() {
-        synchronized (mLock) {
-            return isConnected() ? mConnectedToken : null;
-        }
-    }
-
-    @Override
-    public boolean isConnected() {
-        synchronized (mLock) {
-            return mISession != null;
-        }
-    }
-
-    @FunctionalInterface
-    private interface RemoteSessionTask {
-        void run(IMediaSession iSession, int seq) throws RemoteException;
-    }
-
-    private ListenableFuture<SessionResult> dispatchRemoteSessionTask(int commandCode,
-            RemoteSessionTask task) {
-        return dispatchRemoteSessionTaskInternal(commandCode, null, task);
-    }
-
-    private ListenableFuture<SessionResult> dispatchRemoteSessionTask(
-            SessionCommand sessionCommand, RemoteSessionTask task) {
-        return dispatchRemoteSessionTaskInternal(COMMAND_CODE_CUSTOM, sessionCommand, task);
-    }
-
-    private ListenableFuture<SessionResult> dispatchRemoteSessionTaskInternal(int commandCode,
-            SessionCommand sessionCommand, RemoteSessionTask task) {
-        final IMediaSession iSession = sessionCommand != null
-                ? getSessionInterfaceIfAble(sessionCommand)
-                : getSessionInterfaceIfAble(commandCode);
-        if (iSession != null) {
-            final SequencedFutureManager.SequencedFuture<SessionResult> result =
-                    mSequencedFutureManager.createSequencedFuture(RESULT_WHEN_CLOSED);
-            try {
-                task.run(iSession, result.getSequenceNumber());
-            } catch (RemoteException e) {
-                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
-                result.set(new SessionResult(RESULT_ERROR_SESSION_DISCONNECTED));
-            }
-            return result;
-        } else {
-            // Don't create Future with SequencedFutureManager.
-            // Otherwise session would receive discontinued sequence number, and it would make
-            // future work item 'keeping call sequence when session execute commands' impossible.
-            return SessionResult.createFutureWithResult(RESULT_ERROR_PERMISSION_DENIED);
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> play() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_PLAY, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.play(mControllerStub, seq);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> pause() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_PAUSE, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.pause(mControllerStub, seq);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> prepare() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_PREPARE, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.prepare(mControllerStub, seq);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> fastForward() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_FAST_FORWARD,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.fastForward(mControllerStub, seq);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> rewind() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_REWIND, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.rewind(mControllerStub, seq);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipForward() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SKIP_FORWARD,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.skipForward(mControllerStub, seq);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipBackward() {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SKIP_BACKWARD,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.skipBackward(mControllerStub, seq);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> seekTo(final long pos) {
-        if (pos < 0) {
-            throw new IllegalArgumentException("position shouldn't be negative");
-        }
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SEEK_TO, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.seekTo(mControllerStub, seq, pos);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setVolumeTo(final int value,
-            final @VolumeFlags int flags) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_VOLUME_SET_VOLUME, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.setVolumeTo(mControllerStub, seq, value, flags);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> adjustVolume(final @VolumeDirection int direction,
-            final @VolumeFlags int flags) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_VOLUME_ADJUST_VOLUME,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.adjustVolume(mControllerStub, seq, direction, flags);
-                    }
-                });
-    }
-
-    @Override
-    public PendingIntent getSessionActivity() {
-        synchronized (mLock) {
-            return mSessionActivity;
-        }
-    }
-
-    @Override
-    public int getPlayerState() {
-        synchronized (mLock) {
-            return mPlayerState;
-        }
-    }
-
-    @Override
-    public long getDuration() {
-        synchronized (mLock) {
-            MediaMetadata metadata = mCurrentMediaItem == null ? null
-                    : mCurrentMediaItem.getMetadata();
-            if (metadata != null && metadata.containsKey(METADATA_KEY_DURATION)) {
-                return metadata.getLong(METADATA_KEY_DURATION);
-            }
-        }
-        return SessionPlayer.UNKNOWN_TIME;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        synchronized (mLock) {
-            if (mISession == null) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return UNKNOWN_TIME;
-            }
-            if (mPlayerState == SessionPlayer.PLAYER_STATE_PLAYING
-                    && mBufferingState != SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED) {
-                long timeDiff = (mInstance.mTimeDiff != null) ? mInstance.mTimeDiff
-                        : SystemClock.elapsedRealtime() - mPositionEventTimeMs;
-                long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
-                return Math.max(0, expectedPosition);
-            }
-            return mPositionMs;
-        }
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        synchronized (mLock) {
-            if (mISession == null) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return 0f;
-            }
-            return mPlaybackSpeed;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setPlaybackSpeed(final float speed) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_SPEED, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.setPlaybackSpeed(mControllerStub, seq, speed);
-            }
-        });
-    }
-
-    @Override
-    @SessionPlayer.BuffState
-    public int getBufferingState() {
-        synchronized (mLock) {
-            if (mISession == null) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return BUFFERING_STATE_UNKNOWN;
-            }
-            return mBufferingState;
-        }
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        synchronized (mLock) {
-            if (mISession == null) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return UNKNOWN_TIME;
-            }
-            return mBufferedPositionMs;
-        }
-    }
-
-    @Override
-    public PlaybackInfo getPlaybackInfo() {
-        synchronized (mLock) {
-            return mPlaybackInfo;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setRating(@NonNull final String mediaId,
-            @NonNull final Rating rating) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SET_RATING, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.setRating(mControllerStub, seq, mediaId,
-                        MediaParcelUtils.toParcelable(rating));
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> sendCustomCommand(
-            @NonNull final SessionCommand command, @Nullable final Bundle args) {
-        return dispatchRemoteSessionTask(command, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.onCustomCommand(mControllerStub, seq,
-                        MediaParcelUtils.toParcelable(command), args);
-            }
-        });
-    }
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        synchronized (mLock) {
-            return mPlaylist == null ? null : new ArrayList<>(mPlaylist);
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setPlaylist(@NonNull final List<String> list,
-            @Nullable final MediaMetadata metadata) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_PLAYLIST, new RemoteSessionTask() {
-            @Override
-            public void run(IMediaSession iSession, int seq) throws RemoteException {
-                iSession.setPlaylist(mControllerStub, seq, list,
-                        MediaParcelUtils.toParcelable(metadata));
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setMediaItem(@NonNull final String mediaId) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.setMediaItem(mControllerStub, seq, mediaId);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setMediaUri(@NonNull final Uri uri,
-            @Nullable final Bundle extras) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SET_MEDIA_URI,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.setMediaUri(mControllerStub, seq, uri, extras);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> updatePlaylistMetadata(
-            @Nullable final MediaMetadata metadata) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.updatePlaylistMetadata(mControllerStub, seq,
-                                MediaParcelUtils.toParcelable(metadata));
-                    }
-                });
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        synchronized (mLock) {
-            return mPlaylistMetadata;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> addPlaylistItem(final int index,
-            @NonNull final String mediaId) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.addPlaylistItem(mControllerStub, seq, index, mediaId);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> removePlaylistItem(final int index) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.removePlaylistItem(mControllerStub, seq, index);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> replacePlaylistItem(final int index,
-            @NonNull final String mediaId) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.replacePlaylistItem(mControllerStub, seq, index, mediaId);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> movePlaylistItem(
-            final int fromIndex, final int toIndex) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.movePlaylistItem(mControllerStub, seq, fromIndex, toIndex);
-                    }
-                });
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        synchronized (mLock) {
-            return mCurrentMediaItem;
-        }
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        synchronized (mLock) {
-            return mCurrentMediaItemIndex;
-        }
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        synchronized (mLock) {
-            return mPreviousMediaItemIndex;
-        }
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        synchronized (mLock) {
-            return mNextMediaItemIndex;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToPreviousItem() {
-        return dispatchRemoteSessionTask(
-                COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.skipToPreviousItem(mControllerStub, seq);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToNextItem() {
-        return dispatchRemoteSessionTask(
-                COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.skipToNextItem(mControllerStub, seq);
-                    }
-                });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToPlaylistItem(final int index) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.skipToPlaylistItem(mControllerStub, seq, index);
-                    }
-                });
-    }
-
-    @Override
-    public int getRepeatMode() {
-        synchronized (mLock) {
-            return mRepeatMode;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setRepeatMode(final int repeatMode) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.setRepeatMode(mControllerStub, seq, repeatMode);
-                    }
-                });
-    }
-
-    @Override
-    public int getShuffleMode() {
-        synchronized (mLock) {
-            return mShuffleMode;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setShuffleMode(final int shuffleMode) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.setShuffleMode(mControllerStub, seq, shuffleMode);
-                    }
-                });
-    }
-
-    @Override
-    @NonNull
-    public List<SessionPlayer.TrackInfo> getTracks() {
-        synchronized (mLock) {
-            return mTracks;
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<SessionResult> selectTrack(
-            @NonNull final SessionPlayer.TrackInfo trackInfo) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SELECT_TRACK,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.selectTrack(mControllerStub, seq,
-                                MediaParcelUtils.toParcelable(trackInfo));
-                    }
-                });
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<SessionResult> deselectTrack(
-            @NonNull final SessionPlayer.TrackInfo trackInfo) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_DESELECT_TRACK,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.deselectTrack(mControllerStub, seq,
-                                MediaParcelUtils.toParcelable(trackInfo));
-                    }
-                });
-    }
-
-    @Override
-    @Nullable
-    public TrackInfo getSelectedTrack(int trackType) {
-        synchronized (mLock) {
-            return mSelectedTracks.get(trackType);
-        }
-    }
-
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        synchronized (mLock) {
-            return mVideoSize;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setSurface(@Nullable final Surface surface) {
-        return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_SURFACE,
-                new RemoteSessionTask() {
-                    @Override
-                    public void run(IMediaSession iSession, int seq) throws RemoteException {
-                        iSession.setSurface(mControllerStub, seq, surface);
-                    }
-                });
-    }
-
-    @Override
-    public SessionCommandGroup getAllowedCommands() {
-        synchronized (mLock) {
-            if (mISession == null) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mAllowedCommands;
-        }
-    }
-
-    @Override
-    @NonNull
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    @Nullable
-    public MediaBrowserCompat getBrowserCompat() {
-        return null;
-    }
-
-    private boolean requestConnectToService() {
-        // Service. Needs to get fresh binder whenever connection is needed.
-        final Intent intent = new Intent(MediaSessionService.SERVICE_INTERFACE);
-        intent.setClassName(mToken.getPackageName(), mToken.getServiceName());
-
-        // Use bindService() instead of startForegroundService() to start session service for three
-        // reasons.
-        // 1. Prevent session service owner's stopSelf() from destroying service.
-        //    With the startForegroundService(), service's call of stopSelf() will trigger immediate
-        //    onDestroy() calls on the main thread even when onConnect() is running in another
-        //    thread.
-        // 2. Minimize APIs for developers to take care about.
-        //    With bindService(), developers only need to take care about Service.onBind()
-        //    but Service.onStartCommand() should be also taken care about with the
-        //    startForegroundService().
-        // 3. Future support for UI-less playback
-        //    If a service wants to keep running, it should be either foreground service or
-        //    bound service. But there had been request for the feature for system apps
-        //    and using bindService() will be better fit with it.
-        synchronized (mLock) {
-            boolean result = mContext.bindService(intent, mServiceConnection,
-                    Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES);
-            if (!result) {
-                Log.w(TAG, "bind to " + mToken + " failed");
-                return false;
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "bind to " + mToken + " succeeded");
-        }
-        return true;
-    }
-
-    private boolean requestConnectToSession(@Nullable Bundle connectionHints) {
-        IMediaSession iSession = IMediaSession.Stub.asInterface((IBinder) mToken.getBinder());
-        int seq = mSequencedFutureManager.obtainNextSequenceNumber();
-        ConnectionRequest request =
-                new ConnectionRequest(mContext.getPackageName(), Process.myPid(), connectionHints);
-        try {
-            iSession.connect(mControllerStub, seq, MediaParcelUtils.toParcelable(request));
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to call connection request.", e);
-            return false;
-        }
-        return true;
-    }
-
-    // Returns session interface if the controller can send the command.
-    IMediaSession getSessionInterfaceIfAble(@SessionCommand.CommandCode int commandCode) {
-        synchronized (mLock) {
-            if (!mAllowedCommands.hasCommand(commandCode)) {
-                // Cannot send because isn't allowed to.
-                Log.w(TAG, "Controller isn't allowed to call command, commandCode="
-                        + commandCode);
-                return null;
-            }
-            return mISession;
-        }
-    }
-
-    // Returns session binder if the controller can send the command.
-    @SuppressWarnings("ObjectToString")
-    IMediaSession getSessionInterfaceIfAble(SessionCommand command) {
-        synchronized (mLock) {
-            if (!mAllowedCommands.hasCommand(command)) {
-                Log.w(TAG, "Controller isn't allowed to call command, command=" + command);
-                return null;
-            }
-            return mISession;
-        }
-    }
-
-    void notifyCurrentMediaItemChanged(final MediaItem item, int currentMediaItemIndex,
-            int previousMediaItemIndex, int nextMediaItemIndex) {
-        synchronized (mLock) {
-            mCurrentMediaItem = item;
-            mCurrentMediaItemIndex = currentMediaItemIndex;
-            mPreviousMediaItemIndex = previousMediaItemIndex;
-            mNextMediaItemIndex = nextMediaItemIndex;
-            if (mPlaylist != null && currentMediaItemIndex >= 0
-                    && currentMediaItemIndex < mPlaylist.size()) {
-                mPlaylist.set(currentMediaItemIndex, item);
-            }
-            // Reset position to zero as a stopgap. media2-session 1.0.x didn't notify new position
-            // when current item changes.
-            mPositionEventTimeMs = SystemClock.elapsedRealtime();
-            mPositionMs = 0;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onCurrentMediaItemChanged(mInstance, item);
-            }
-        });
-    }
-
-    void notifyPlayerStateChanges(long eventTimeMs, long positionMs, final int state) {
-        synchronized (mLock) {
-            mPositionEventTimeMs = eventTimeMs;
-            mPositionMs = positionMs;
-            mPlayerState = state;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlayerStateChanged(mInstance, state);
-            }
-        });
-    }
-
-    void notifyPlaybackSpeedChanges(long eventTimeMs, long positionMs, final float speed) {
-        synchronized (mLock) {
-            mPositionEventTimeMs = eventTimeMs;
-            mPositionMs = positionMs;
-            mPlaybackSpeed = speed;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlaybackSpeedChanged(mInstance, speed);
-            }
-        });
-    }
-
-    void notifyBufferingStateChanged(final MediaItem item, final int state,
-            long bufferedPositionMs, long eventTimeMs, long positionMs) {
-        synchronized (mLock) {
-            mBufferingState = state;
-            mBufferedPositionMs = bufferedPositionMs;
-            mPositionEventTimeMs = eventTimeMs;
-            mPositionMs = positionMs;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onBufferingStateChanged(mInstance, item, state);
-            }
-        });
-    }
-
-    void notifyPlaylistChanges(final List<MediaItem> playlist, final MediaMetadata metadata,
-            int currentMediaItemIndex, int previousMediaItemIndex, int nextMediaItemIndex) {
-        synchronized (mLock) {
-            mPlaylist = playlist;
-            mPlaylistMetadata = metadata;
-            mCurrentMediaItemIndex = currentMediaItemIndex;
-            mPreviousMediaItemIndex = previousMediaItemIndex;
-            mNextMediaItemIndex = nextMediaItemIndex;
-            if (currentMediaItemIndex >= 0 && playlist != null
-                    && currentMediaItemIndex < playlist.size()) {
-                mCurrentMediaItem = playlist.get(currentMediaItemIndex);
-            }
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlaylistChanged(mInstance, playlist, metadata);
-            }
-        });
-    }
-
-    void notifyPlaylistMetadataChanges(final MediaMetadata metadata) {
-        synchronized (mLock) {
-            mPlaylistMetadata = metadata;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlaylistMetadataChanged(mInstance, metadata);
-            }
-        });
-    }
-
-    void notifyPlaybackInfoChanges(final PlaybackInfo info) {
-        synchronized (mLock) {
-            mPlaybackInfo = info;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlaybackInfoChanged(mInstance, info);
-            }
-        });
-    }
-
-    void notifyRepeatModeChanges(final int repeatMode, int currentMediaItemIndex,
-            int previousMediaItemIndex, int nextMediaItemIndex) {
-        synchronized (mLock) {
-            mRepeatMode = repeatMode;
-            mCurrentMediaItemIndex = currentMediaItemIndex;
-            mPreviousMediaItemIndex = previousMediaItemIndex;
-            mNextMediaItemIndex = nextMediaItemIndex;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onRepeatModeChanged(mInstance, repeatMode);
-            }
-        });
-    }
-
-    void notifyShuffleModeChanges(final int shuffleMode, int currentMediaItemIndex,
-            int previousMediaItemIndex, int nextMediaItemIndex) {
-        synchronized (mLock) {
-            mShuffleMode = shuffleMode;
-            mCurrentMediaItemIndex = currentMediaItemIndex;
-            mPreviousMediaItemIndex = previousMediaItemIndex;
-            mNextMediaItemIndex = nextMediaItemIndex;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onShuffleModeChanged(mInstance, shuffleMode);
-            }
-        });
-    }
-
-    void notifyPlaybackCompleted() {
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onPlaybackCompleted(mInstance);
-            }
-        });
-    }
-
-    void notifySeekCompleted(long eventTimeMs, long positionMs, final long seekPositionMs) {
-        synchronized (mLock) {
-            mPositionEventTimeMs = eventTimeMs;
-            mPositionMs = positionMs;
-        }
-
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onSeekCompleted(mInstance, seekPositionMs);
-            }
-        });
-    }
-
-    void notifyVideoSizeChanged(final VideoSize videoSize) {
-        final MediaItem currentItem;
-        synchronized (mLock) {
-            mVideoSize = videoSize;
-            currentItem = mCurrentMediaItem;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-
-                if (currentItem != null) {
-                    callback.onVideoSizeChanged(mInstance, currentItem, videoSize);
-                }
-                callback.onVideoSizeChanged(mInstance, videoSize);
-            }
-        });
-    }
-
-    void notifyTracksChanged(final int seq, final List<TrackInfo> tracks,
-            TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-            TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack) {
-        synchronized (mLock) {
-            mTracks = tracks;
-            // Update selected tracks
-            mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_VIDEO, selectedVideoTrack);
-            mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_AUDIO, selectedAudioTrack);
-            mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, selectedSubtitleTrack);
-            mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_METADATA, selectedMetadataTrack);
-        }
-
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onTracksChanged(mInstance, tracks);
-            }
-        });
-    }
-
-    void notifyTrackSelected(final int seq, final TrackInfo trackInfo) {
-        synchronized (mLock) {
-            mSelectedTracks.put(trackInfo.getTrackType(), trackInfo);
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onTrackSelected(mInstance, trackInfo);
-            }
-        });
-    }
-
-    void notifyTrackDeselected(final int seq, final TrackInfo trackInfo) {
-        synchronized (mLock) {
-            mSelectedTracks.remove(trackInfo.getTrackType());
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onTrackDeselected(mInstance, trackInfo);
-            }
-        });
-    }
-
-    void notifySubtitleData(final MediaItem item, final TrackInfo track, final SubtitleData data) {
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                callback.onSubtitleData(mInstance, item, track, data);
-            }
-        });
-    }
-
-    <T> void setFutureResult(int seq, T futureResult) {
-        if (futureResult == null) {
-            return;
-        }
-        mSequencedFutureManager.setFutureResult(seq, futureResult);
-    }
-
-    // Should be used without a lock to prevent potential deadlock.
-    @SuppressWarnings("ObjectToString")
-    void onConnectedNotLocked(final int sessionVersion,
-            IMediaSession sessionBinder,
-            final SessionCommandGroup allowedCommands,
-            final int playerState,
-            final MediaItem currentMediaItem,
-            final long positionEventTimeMs,
-            final long positionMs,
-            final float playbackSpeed,
-            final long bufferedPositionMs,
-            final PlaybackInfo info,
-            final int repeatMode,
-            final int shuffleMode,
-            final List<MediaItem> playlist,
-            final PendingIntent sessionActivity,
-            final int currentMediaItemIndex,
-            final int previousMediaItemIndex,
-            final int nextMediaItemIndex,
-            final Bundle tokenExtras,
-            final VideoSize videoSize,
-            final List<TrackInfo> trackInfos,
-            final TrackInfo selectedVideoTrack,
-            final TrackInfo selectedAudioTrack,
-            final TrackInfo selectedSubtitleTrack,
-            final TrackInfo selectedMetadataTrack,
-            final MediaMetadata playlistMetadata,
-            final int bufferingState) {
-        if (DEBUG) {
-            Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
-                    + ", allowedCommands=" + allowedCommands);
-        }
-        // 'close' is used in try-finally
-        boolean close = false;
-        try {
-            if (sessionBinder == null || allowedCommands == null) {
-                // Connection rejected.
-                close = true;
-                return;
-            }
-            synchronized (mLock) {
-                if (mIsReleased) {
-                    return;
-                }
-                if (mISession != null) {
-                    Log.e(TAG, "Cannot be notified about the connection result many times."
-                            + " Probably a bug or malicious app.");
-                    close = true;
-                    return;
-                }
-                mAllowedCommands = allowedCommands;
-                mPlayerState = playerState;
-                mCurrentMediaItem = currentMediaItem;
-                mPositionEventTimeMs = positionEventTimeMs;
-                mPositionMs = positionMs;
-                mPlaybackSpeed = playbackSpeed;
-                mBufferedPositionMs = bufferedPositionMs;
-                mPlaybackInfo = info;
-                mRepeatMode = repeatMode;
-                mShuffleMode = shuffleMode;
-                mPlaylist = playlist;
-                mSessionActivity = sessionActivity;
-                mISession = sessionBinder;
-                mCurrentMediaItemIndex = currentMediaItemIndex;
-                mPreviousMediaItemIndex = previousMediaItemIndex;
-                mNextMediaItemIndex = nextMediaItemIndex;
-                mVideoSize = videoSize;
-                mTracks = trackInfos;
-                mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_VIDEO, selectedVideoTrack);
-                mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_AUDIO, selectedAudioTrack);
-                mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, selectedSubtitleTrack);
-                mSelectedTracks.put(TrackInfo.MEDIA_TRACK_TYPE_METADATA, selectedMetadataTrack);
-                mPlaylistMetadata = playlistMetadata;
-                mBufferingState = bufferingState;
-                try {
-                    // Implementation for the local binder is no-op,
-                    // so can be used without worrying about deadlock.
-                    mISession.asBinder().linkToDeath(mDeathRecipient, 0);
-                } catch (RemoteException e) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Session died too early.", e);
-                    }
-                    close = true;
-                    return;
-                }
-                mConnectedToken = new SessionToken(new SessionTokenImplBase(
-                        mToken.getUid(), TYPE_SESSION, mToken.getPackageName(), sessionBinder,
-                        tokenExtras));
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onConnected(mInstance, allowedCommands);
-                }
-            });
-        } finally {
-            if (close) {
-                // Trick to call release() without holding the lock, to prevent potential deadlock
-                // with the developer's custom lock within the ControllerCallback.onDisconnected().
-                mInstance.close();
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void sendControllerResult(int seq, @NonNull SessionResult result) {
-        final IMediaSession iSession;
-        synchronized (mLock) {
-            iSession = mISession;
-        }
-        if (iSession == null) {
-            return;
-        }
-        try {
-            iSession.onControllerResult(mControllerStub, seq,
-                    MediaParcelUtils.toParcelable(result));
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error in sending");
-        }
-    }
-
-    void onCustomCommand(final int seq, final SessionCommand command, final Bundle args) {
-        if (DEBUG) {
-            Log.d(TAG, "onCustomCommand cmd=" + command.getCustomAction());
-        }
-        mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                SessionResult result = callback.onCustomCommand(mInstance, command, args);
-                if (result == null) {
-                    if (THROW_EXCEPTION_FOR_NULL_RESULT) {
-                        throw new RuntimeException("ControllerCallback#onCustomCommand() has"
-                                + " returned null, command=" + command.getCustomAction());
-                    } else {
-                        result = new SessionResult(RESULT_ERROR_UNKNOWN);
-                    }
-                }
-                sendControllerResult(seq, result);
-            }
-        });
-    }
-
-    void onAllowedCommandsChanged(final SessionCommandGroup commands) {
-        synchronized (mLock) {
-            mAllowedCommands = commands;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onAllowedCommandsChanged(mInstance, commands);
-            }
-        });
-    }
-
-    void onSetCustomLayout(final int seq, final List<MediaSession.CommandButton> layout) {
-        mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                int resultCode = callback.onSetCustomLayout(mInstance, layout);
-                SessionResult result = new SessionResult(resultCode);
-                sendControllerResult(seq, result);
-            }
-        });
-    }
-
-    // This will be called on the main thread.
-    private class SessionServiceConnection implements ServiceConnection {
-        private final Bundle mConnectionHints;
-
-        SessionServiceConnection(@Nullable Bundle connectionHints) {
-            mConnectionHints = connectionHints;
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            boolean connectionRequested = false;
-            try {
-                // Note that it's always main-thread.
-                if (DEBUG) {
-                    Log.d(TAG, "onServiceConnected " + name + " " + this);
-                }
-                if (!mToken.getPackageName().equals(name.getPackageName())) {
-                    Log.wtf(TAG, "Expected connection to " + mToken.getPackageName() + " but is"
-                            + " connected to " + name);
-                    return;
-                }
-                IMediaSessionService iService = IMediaSessionService.Stub.asInterface(service);
-                if (iService == null) {
-                    Log.wtf(TAG, "Service interface is missing.");
-                    return;
-                }
-                ConnectionRequest request = new ConnectionRequest(getContext().getPackageName(),
-                        Process.myPid(), mConnectionHints);
-                iService.connect(mControllerStub, MediaParcelUtils.toParcelable(request));
-                connectionRequested = true;
-            } catch (RemoteException e) {
-                Log.w(TAG, "Service " + name + " has died prematurely");
-            } finally {
-                if (!connectionRequested) {
-                    mInstance.close();
-                }
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            // Temporal lose of the binding because of the service crash. System will automatically
-            // rebind, but we'd better to close() here. Otherwise ControllerCallback#onConnected()
-            // would be called multiple times, and the controller would be connected to the
-            // different session everytime.
-            if (DEBUG) {
-                Log.w(TAG, "Session service " + name + " is disconnected.");
-            }
-            mInstance.close();
-        }
-
-        @Override
-        public void onBindingDied(ComponentName name) {
-            // Permanent lose of the binding because of the service package update or removed.
-            // This SessionServiceRecord will be removed accordingly, but forget session binder here
-            // for sure.
-            mInstance.close();
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
deleted file mode 100644
index 9a4987c..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
+++ /dev/null
@@ -1,1432 +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.media2.session;
-
-import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
-import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID;
-
-import static androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.common.BaseResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-import static androidx.media2.common.SessionPlayer.UNKNOWN_TIME;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_ID;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_URI;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_SET_MEDIA_URI_PREFIX;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.BuffState;
-import androidx.media2.common.SessionPlayer.RepeatMode;
-import androidx.media2.common.SessionPlayer.ShuffleMode;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.ControllerCallbackRunnable;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaController.VolumeDirection;
-import androidx.media2.session.MediaController.VolumeFlags;
-import androidx.media2.session.MediaSession.CommandButton;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-// TODO: Find better way to return listenable future.
-class MediaControllerImplLegacy implements MediaController.MediaControllerImpl {
-    private static final String TAG = "MC2ImplLegacy";
-    private static final int ITEM_NONE = -1;
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final long POSITION_DIFF_TOLERANCE = 100;
-    static final String SESSION_COMMAND_ON_EXTRAS_CHANGED =
-            "android.media.session.command.ON_EXTRAS_CHANGED";
-    static final String SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED =
-            "android.media.session.command.ON_CAPTIONING_ENALBED_CHANGED";
-
-    final Context mContext;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final SessionToken mToken;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final HandlerThread mHandlerThread;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Handler mHandler;
-
-    final Object mLock = new Object();
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaController mInstance;
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaBrowserCompat mBrowserCompat;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean mClosed;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<MediaItem> mPlaylist;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<QueueItem> mQueue;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaMetadata mPlaylistMetadata;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @RepeatMode int mRepeatMode;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @ShuffleMode int mShuffleMode;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mPlayerState;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaItem mCurrentMediaItem;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mBufferingState;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mCurrentMediaItemIndex;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mSkipToPlaylistIndex = -1;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    long mBufferedPosition;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    PlaybackInfo mPlaybackInfo;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    SessionCommandGroup mAllowedCommands;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    List<CommandButton> mCustomLayout;
-
-    // Media 1.0 variables
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaControllerCompat mControllerCompat;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    ControllerCompatCallback mControllerCompatCallback;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    PlaybackStateCompat mPlaybackStateCompat;
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaMetadataCompat mMediaMetadataCompat;
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean mConnected;
-
-    @GuardedBy("mLock")
-    private SetMediaUriRequest mPendingSetMediaUriRequest;
-
-    MediaControllerImplLegacy(@NonNull Context context, @NonNull MediaController instance,
-            @NonNull SessionToken token) {
-        mContext = context;
-        mInstance = instance;
-        mHandlerThread = new HandlerThread("MediaController_Thread");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        mToken = token;
-
-        if (mToken.getType() == SessionToken.TYPE_SESSION) {
-            synchronized (mLock) {
-                mBrowserCompat = null;
-            }
-            connectToSession((MediaSessionCompat.Token) mToken.getBinder());
-        } else {
-            connectToService();
-        }
-    }
-
-    @Override
-    public void close() {
-        if (DEBUG) {
-            Log.d(TAG, "close from " + mToken);
-        }
-        synchronized (mLock) {
-            if (mClosed) {
-                // Prevent re-enterance from the ControllerCallback.onDisconnected()
-                return;
-            }
-            mHandler.removeCallbacksAndMessages(null);
-
-            mHandlerThread.quitSafely();
-
-            mClosed = true;
-
-            if (mBrowserCompat != null) {
-                mBrowserCompat.disconnect();
-                mBrowserCompat = null;
-            }
-            if (mControllerCompat != null) {
-                mControllerCompat.unregisterCallback(mControllerCompatCallback);
-                mControllerCompat = null;
-            }
-            mConnected = false;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onDisconnected(mInstance);
-            }
-        });
-    }
-
-    @Override
-    @Nullable
-    public SessionToken getConnectedToken() {
-        synchronized (mLock) {
-            return mConnected ? mToken : null;
-        }
-    }
-
-    private ListenableFuture<SessionResult> createFutureWithResult(int resultCode) {
-        ResolvableFuture<SessionResult> result = ResolvableFuture.create();
-        setFutureResult(result, resultCode);
-        return result;
-    }
-
-    private void setFutureResult(ResolvableFuture<SessionResult> result, int resultCode) {
-        final MediaItem item;
-        synchronized (mLock) {
-            item = mCurrentMediaItem;
-        }
-        result.set(new SessionResult(resultCode, null, item));
-    }
-
-    @Override
-    public boolean isConnected() {
-        synchronized (mLock) {
-            return mConnected;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> play() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mPendingSetMediaUriRequest == null) {
-                mControllerCompat.getTransportControls().play();
-            } else {
-                switch (mPendingSetMediaUriRequest.type) {
-                    case MEDIA_URI_QUERY_ID:
-                        mControllerCompat.getTransportControls()
-                                .playFromMediaId(
-                                        mPendingSetMediaUriRequest.value,
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    case MEDIA_URI_QUERY_QUERY:
-                        mControllerCompat.getTransportControls()
-                                .playFromSearch(
-                                        mPendingSetMediaUriRequest.value,
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    case MEDIA_URI_QUERY_URI:
-                        mControllerCompat.getTransportControls()
-                                .playFromUri(
-                                        Uri.parse(mPendingSetMediaUriRequest.value),
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    default:
-                        // Impossible.
-                        mPendingSetMediaUriRequest = null;
-                        return createFutureWithResult(RESULT_ERROR_INVALID_STATE);
-                }
-                setFutureResult(mPendingSetMediaUriRequest.result, RESULT_SUCCESS);
-                mPendingSetMediaUriRequest = null;
-            }
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> pause() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().pause();
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> prepare() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mPendingSetMediaUriRequest == null) {
-                mControllerCompat.getTransportControls().prepare();
-            } else {
-                switch (mPendingSetMediaUriRequest.type) {
-                    case MEDIA_URI_QUERY_ID:
-                        mControllerCompat.getTransportControls()
-                                .prepareFromMediaId(
-                                        mPendingSetMediaUriRequest.value,
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    case MEDIA_URI_QUERY_QUERY:
-                        mControllerCompat.getTransportControls()
-                                .prepareFromSearch(
-                                        mPendingSetMediaUriRequest.value,
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    case MEDIA_URI_QUERY_URI:
-                        mControllerCompat.getTransportControls()
-                                .prepareFromUri(
-                                        Uri.parse(mPendingSetMediaUriRequest.value),
-                                        mPendingSetMediaUriRequest.extras);
-                        break;
-                    default:
-                        // Impossible.
-                        mPendingSetMediaUriRequest = null;
-                        return createFutureWithResult(RESULT_ERROR_INVALID_STATE);
-                }
-                setFutureResult(mPendingSetMediaUriRequest.result, RESULT_SUCCESS);
-                mPendingSetMediaUriRequest = null;
-            }
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> fastForward() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().fastForward();
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> rewind() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().rewind();
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipForward() {
-        // Unsupported action
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipBackward() {
-        // Unsupported action
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> seekTo(long pos) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().seekTo(pos);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setVolumeTo(int value, @VolumeFlags int flags) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.setVolumeTo(value, flags);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> adjustVolume(@VolumeDirection int direction,
-            @VolumeFlags int flags) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.adjustVolume(direction, flags);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    @Nullable
-    public PendingIntent getSessionActivity() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mControllerCompat.getSessionActivity();
-        }
-    }
-
-    @Override
-    public int getPlayerState() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return SessionPlayer.PLAYER_STATE_ERROR;
-            }
-            return mPlayerState;
-        }
-    }
-
-    @Override
-    public long getDuration() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return UNKNOWN_TIME;
-            }
-            if (mMediaMetadataCompat != null
-                    && mMediaMetadataCompat.containsKey(METADATA_KEY_DURATION)) {
-                return mMediaMetadataCompat.getLong(METADATA_KEY_DURATION);
-            }
-        }
-        return UNKNOWN_TIME;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return UNKNOWN_TIME;
-            }
-            if (mPlaybackStateCompat != null) {
-                return mPlaybackStateCompat.getCurrentPosition(mInstance.mTimeDiff);
-            }
-            return UNKNOWN_TIME;
-        }
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return 0f;
-            }
-            return (mPlaybackStateCompat == null) ? 0f : mPlaybackStateCompat.getPlaybackSpeed();
-        }
-    }
-
-    @Override
-    @BuffState
-    public int getBufferingState() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return BUFFERING_STATE_UNKNOWN;
-            }
-            return mPlaybackStateCompat == null ? SessionPlayer.BUFFERING_STATE_UNKNOWN
-                    : MediaUtils.toBufferingState(mPlaybackStateCompat.getState());
-        }
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return UNKNOWN_TIME;
-            }
-            return (mPlaybackStateCompat == null) ? UNKNOWN_TIME
-                    : mPlaybackStateCompat.getBufferedPosition();
-        }
-    }
-
-    @Override
-    @Nullable
-    public PlaybackInfo getPlaybackInfo() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mPlaybackInfo;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setRating(@NonNull String mediaId,
-            @NonNull Rating rating) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mCurrentMediaItem != null && mediaId.equals(mCurrentMediaItem.getMediaId())) {
-                mControllerCompat.getTransportControls().setRating(
-                        MediaUtils.convertToRatingCompat(rating));
-            }
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setPlaybackSpeed(float speed) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().setPlaybackSpeed(speed);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> sendCustomCommand(@NonNull SessionCommand command,
-            @Nullable Bundle args) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mAllowedCommands.hasCommand(command)) {
-                mControllerCompat.getTransportControls().sendCustomAction(
-                        command.getCustomAction(), args);
-                return createFutureWithResult(RESULT_SUCCESS);
-            }
-            final ResolvableFuture<SessionResult> result = ResolvableFuture.create();
-            ResultReceiver cb = new ResultReceiver(mHandler) {
-                @Override
-                protected void onReceiveResult(int resultCode, Bundle resultData) {
-                    result.set(new SessionResult(resultCode, resultData));
-                }
-            };
-            mControllerCompat.sendCommand(command.getCustomAction(), args, cb);
-            return result;
-        }
-    }
-
-    @Override
-    @Nullable
-    public List<MediaItem> getPlaylist() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return (mPlaylist == null || mPlaylist.size() == 0) ? null : new ArrayList<>(mPlaylist);
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setPlaylist(@NonNull List<String> list,
-            @Nullable MediaMetadata metadata) {
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setMediaItem(@NonNull String mediaId) {
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setMediaUri(@NonNull Uri uri,
-            @Nullable Bundle extras) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-
-            if (mPendingSetMediaUriRequest != null) {
-                Log.w(TAG, "SetMediaUri() is called multiple times without prepare() nor play()."
-                        + " Previous call will be skipped.");
-                setFutureResult(mPendingSetMediaUriRequest.result, RESULT_INFO_SKIPPED);
-                mPendingSetMediaUriRequest = null;
-            }
-            ResolvableFuture<SessionResult> result = ResolvableFuture.create();
-            if (uri.toString().startsWith(MEDIA_URI_SET_MEDIA_URI_PREFIX)
-                    && uri.getQueryParameterNames().size() == 1) {
-                String queryParameterName = uri.getQueryParameterNames().iterator().next();
-                if (TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_ID)
-                        || TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_QUERY)
-                        || TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_URI)) {
-                    mPendingSetMediaUriRequest =
-                            new SetMediaUriRequest(
-                                    queryParameterName,
-                                    uri.getQueryParameter(queryParameterName),
-                                    extras,
-                                    result);
-                }
-            }
-            if (mPendingSetMediaUriRequest == null) {
-                mPendingSetMediaUriRequest =
-                        new SetMediaUriRequest(MEDIA_URI_QUERY_URI, uri.toString(), extras, result);
-            }
-            return result;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> updatePlaylistMetadata(
-            @Nullable MediaMetadata metadata) {
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    @Nullable
-    public MediaMetadata getPlaylistMetadata() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mPlaylistMetadata;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> addPlaylistItem(int index, @NonNull String mediaId) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.addQueueItem(
-                    MediaUtils.createMediaDescriptionCompat(mediaId), index);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> removePlaylistItem(int index) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mQueue == null || index < 0 || index >= mQueue.size()) {
-                return createFutureWithResult(RESULT_ERROR_BAD_VALUE);
-            }
-            mControllerCompat.removeQueueItem(mQueue.get(index).getDescription());
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> replacePlaylistItem(int index, @NonNull String mediaId) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            if (mQueue == null || index < 0 || index >= mQueue.size()) {
-                return createFutureWithResult(RESULT_ERROR_BAD_VALUE);
-            }
-            mControllerCompat.removeQueueItem(mQueue.get(index).getDescription());
-            mControllerCompat.addQueueItem(MediaUtils.createMediaDescriptionCompat(mediaId), index);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> movePlaylistItem(int fromIndex, int toIndex) {
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mCurrentMediaItem;
-        }
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        return mCurrentMediaItemIndex;
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToPreviousItem() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().skipToPrevious();
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToNextItem() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mControllerCompat.getTransportControls().skipToNext();
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> skipToPlaylistItem(int index) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            mSkipToPlaylistIndex = index;
-            mControllerCompat.getTransportControls().skipToQueueItem(
-                    mQueue.get(index).getQueueId());
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    @RepeatMode
-    public int getRepeatMode() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return SessionPlayer.REPEAT_MODE_NONE;
-            }
-            return mRepeatMode;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setRepeatMode(@RepeatMode int repeatMode) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            // SessionPlayer.RepeatMode has the same values with
-            // PlaybackStateCompat.RepeatMode.
-            mControllerCompat.getTransportControls().setRepeatMode(repeatMode);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    @ShuffleMode
-    public int getShuffleMode() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return SessionPlayer.SHUFFLE_MODE_NONE;
-            }
-            return mShuffleMode;
-        }
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setShuffleMode(@ShuffleMode int shuffleMode) {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-            }
-            // SessionPlayer.ShuffleMode has the same values with
-            // PlaybackStateCompat.ShuffleMode.
-            mControllerCompat.getTransportControls().setShuffleMode(shuffleMode);
-        }
-        return createFutureWithResult(RESULT_SUCCESS);
-    }
-
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        Log.w(TAG, "Session doesn't support getting VideoSize");
-        return new VideoSize(0, 0);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setSurface(@Nullable Surface surface) {
-        Log.w(TAG, "Session doesn't support setting Surface");
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        Log.w(TAG, "Session doesn't support getting TrackInfo");
-        return Collections.emptyList();
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<SessionResult> selectTrack(@NonNull TrackInfo trackInfo) {
-        Log.w(TAG, "Session doesn't support selecting track");
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<SessionResult> deselectTrack(
-            @NonNull TrackInfo trackInfo) {
-        Log.w(TAG, "Session doesn't support deselecting track");
-        return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
-    }
-
-    @Override
-    @Nullable
-    public TrackInfo getSelectedTrack(int trackType) {
-        Log.w(TAG, "Session doesn't support getting selected track");
-        return null;
-    }
-
-    @Override
-    public SessionCommandGroup getAllowedCommands() {
-        synchronized (mLock) {
-            if (!mConnected) {
-                Log.w(TAG, "Session isn't active", new IllegalStateException());
-                return null;
-            }
-            return mAllowedCommands;
-        }
-    }
-
-    @Override
-    @NonNull
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    @Nullable
-    public MediaBrowserCompat getBrowserCompat() {
-        synchronized (mLock) {
-            return mBrowserCompat;
-        }
-    }
-
-    // Should be used without a lock to prevent potential deadlock.
-    void onConnectedNotLocked() {
-        if (DEBUG) {
-            Log.d(TAG, "onConnectedNotLocked token=" + mToken);
-        }
-        final SessionCommandGroup allowedCommands;
-        final List<CommandButton> customLayout;
-
-        synchronized (mLock) {
-            if (mClosed || mConnected) {
-                return;
-            }
-            mPlaybackStateCompat = mControllerCompat.getPlaybackState();
-            mAllowedCommands = MediaUtils.convertToSessionCommandGroup(
-                    mControllerCompat.getFlags(), mPlaybackStateCompat);
-            mPlayerState = MediaUtils.convertToPlayerState(mPlaybackStateCompat);
-            mBufferedPosition = mPlaybackStateCompat == null
-                    ? UNKNOWN_TIME : mPlaybackStateCompat.getBufferedPosition();
-            mCustomLayout = MediaUtils.convertToCustomLayout(mPlaybackStateCompat);
-
-            allowedCommands = mAllowedCommands;
-            customLayout = mCustomLayout;
-
-            mPlaybackInfo = MediaUtils.toPlaybackInfo2(mControllerCompat.getPlaybackInfo());
-
-            mRepeatMode = mControllerCompat.getRepeatMode();
-            mShuffleMode = mControllerCompat.getShuffleMode();
-
-            mQueue = MediaUtils.removeNullElements(mControllerCompat.getQueue());
-            if (mQueue == null || mQueue.size() == 0) {
-                // MediaSessionCompat can set queue as null or empty. However, SessionPlayer should
-                // not set playlist as null or empty. Therefore, we treat them as the same.
-                mQueue = null;
-                mPlaylist = null;
-            } else {
-                mPlaylist = MediaUtils.convertQueueItemListToMediaItemList(mQueue);
-            }
-            mPlaylistMetadata = MediaUtils.convertToMediaMetadata(
-                    mControllerCompat.getQueueTitle());
-
-            // Call this after set playlist.
-            setCurrentMediaItemLocked(mControllerCompat.getMetadata());
-            mConnected = true;
-        }
-        mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onConnected(mInstance, allowedCommands);
-            }
-        });
-        if (!customLayout.isEmpty()) {
-            mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onSetCustomLayout(mInstance, customLayout);
-                }
-            });
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void connectToSession(MediaSessionCompat.Token sessionCompatToken) {
-        boolean isSessionReady;
-        MediaControllerCompat controllerCompat = new MediaControllerCompat(mContext,
-                sessionCompatToken);
-        synchronized (mLock) {
-            mControllerCompat = controllerCompat;
-            mControllerCompatCallback = new ControllerCompatCallback();
-            isSessionReady = mControllerCompat.isSessionReady();
-            mControllerCompat.registerCallback(mControllerCompatCallback, mHandler);
-        }
-        if (!isSessionReady) {
-            // If the session not ready here, then call onConnectedNotLocked() immediately. The
-            // session would be higly likely framework MediaSession, so wouldn't be ready forever.
-            // Just FYI, previous attempt to make session ready (i.e. request extra binder) had been
-            // failed already in MediaController's constructor via SessionToken#createSessionToken.
-            onConnectedNotLocked();
-        }
-    }
-
-    private void connectToService() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mLock) {
-                    mBrowserCompat = new MediaBrowserCompat(mContext, mToken.getComponentName(),
-                            new ConnectionCallback(), null);
-                    mBrowserCompat.connect();
-                }
-            }
-        });
-    }
-
-    @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* WeakerAccess for synthetic access */
-    void setCurrentMediaItemLocked(MediaMetadataCompat metadata) {
-        mMediaMetadataCompat = metadata;
-        int ratingType = mControllerCompat.getRatingType();
-        if (metadata == null) {
-            mCurrentMediaItemIndex = -1;
-            mCurrentMediaItem = null;
-            return;
-        }
-
-        if (mQueue == null) {
-            mCurrentMediaItemIndex = -1;
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
-            return;
-        }
-
-        if (mPlaybackStateCompat != null) {
-            // If playback state is updated before, compare use queue id and media id.
-            long queueId = mPlaybackStateCompat.getActiveQueueItemId();
-            for (int i = 0; i < mQueue.size(); ++i) {
-                QueueItem item = mQueue.get(i);
-                if (item.getQueueId() == queueId) {
-                    mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
-                    mCurrentMediaItemIndex = i;
-                    return;
-                }
-            }
-        }
-
-        String mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
-        if (mediaId == null) {
-            mCurrentMediaItemIndex = -1;
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
-            return;
-        }
-
-        // Need to find the media item in the playlist using mediaId.
-        // Note that there can be multiple media items with the same media id.
-        if (mSkipToPlaylistIndex >= 0 && mSkipToPlaylistIndex < mQueue.size()
-                && TextUtils.equals(mediaId,
-                        mQueue.get(mSkipToPlaylistIndex).getDescription().getMediaId())) {
-            // metadata changed after skipToPlaylistIItem() was called.
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
-            mCurrentMediaItemIndex = mSkipToPlaylistIndex;
-            mSkipToPlaylistIndex = -1;
-            return;
-        }
-
-        // Find mediaId from the playlist.
-        for (int i = 0; i < mQueue.size(); ++i) {
-            QueueItem item = mQueue.get(i);
-            if (TextUtils.equals(mediaId, item.getDescription().getMediaId())) {
-                mCurrentMediaItemIndex = i;
-                mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
-                return;
-            }
-        }
-
-        // Failed to find media item from the playlist.
-        mCurrentMediaItemIndex = -1;
-        mCurrentMediaItem = MediaUtils.convertToMediaItem(mMediaMetadataCompat, ratingType);
-    }
-
-    private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        ConnectionCallback() {
-        }
-
-        @Override
-        public void onConnected() {
-            MediaBrowserCompat browser = getBrowserCompat();
-            if (browser != null) {
-                connectToSession(browser.getSessionToken());
-            } else if (DEBUG) {
-                Log.d(TAG, "Controller is closed prematually", new IllegalStateException());
-            }
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            close();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            close();
-        }
-    }
-
-    private final class ControllerCompatCallback extends MediaControllerCompat.Callback {
-        ControllerCompatCallback() {
-        }
-
-        @Override
-        public void onSessionReady() {
-            boolean connected;
-            synchronized (mLock) {
-                connected = mConnected;
-            }
-            if (!connected) {
-                onConnectedNotLocked();
-            } else {
-                // Handle rare occasion that extra binder becomes available lately.
-                // See connectToSession() for detail.
-                PlaybackStateCompat state;
-                int shuffleMode;
-                int repeatMode;
-                boolean isCaptioningEnabled;
-                synchronized (mLock) {
-                    state = mControllerCompat.getPlaybackState();
-                    shuffleMode = mControllerCompat.getShuffleMode();
-                    repeatMode = mControllerCompat.getRepeatMode();
-                    isCaptioningEnabled = mControllerCompat.isCaptioningEnabled();
-                }
-                onPlaybackStateChanged(state);
-                onShuffleModeChanged(shuffleMode);
-                onRepeatModeChanged(repeatMode);
-                onCaptioningEnabledChanged(isCaptioningEnabled);
-            }
-        }
-
-        @Override
-        public void onSessionDestroyed() {
-            close();
-        }
-
-        @Override
-        public void onSessionEvent(final String event, final Bundle extras) {
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-            }
-            mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    // Ignore return because legacy session cannot get result back.
-                    callback.onCustomCommand(mInstance, new SessionCommand(event, null), extras);
-                }
-            });
-        }
-
-        @Override
-        public void onPlaybackStateChanged(final PlaybackStateCompat state) {
-            final PlaybackStateCompat prevState;
-            final MediaItem prevItem;
-            final MediaItem currentItem;
-            final List<CommandButton> prevLayout;
-            final List<CommandButton> currentLayout;
-            final SessionCommandGroup prevAllowedCommands;
-            final SessionCommandGroup currentAllowedCommands;
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                prevItem = mCurrentMediaItem;
-                prevState = mPlaybackStateCompat;
-                mPlaybackStateCompat = state;
-                mPlayerState = MediaUtils.convertToPlayerState(state);
-                mBufferedPosition = state == null ? UNKNOWN_TIME
-                        : state.getBufferedPosition();
-
-                if (mQueue != null && state != null) {
-                    for (int i = 0; i < mQueue.size(); ++i) {
-                        if (mQueue.get(i).getQueueId() == state.getActiveQueueItemId()) {
-                            mCurrentMediaItemIndex = i;
-                            mCurrentMediaItem = mPlaylist.get(i);
-                        }
-                    }
-                }
-                currentItem = mCurrentMediaItem;
-
-                prevLayout = mCustomLayout;
-                mCustomLayout = MediaUtils.convertToCustomLayout(state);
-                currentLayout = mCustomLayout;
-
-                prevAllowedCommands = mAllowedCommands;
-                mAllowedCommands = MediaUtils.convertToSessionCommandGroup(
-                        mControllerCompat.getFlags(), mPlaybackStateCompat);
-                currentAllowedCommands = mAllowedCommands;
-            }
-
-            if (prevItem != currentItem) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onCurrentMediaItemChanged(mInstance, currentItem);
-                    }
-                });
-            }
-
-            if (state == null) {
-                if (prevState != null) {
-                    mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                        @Override
-                        public void run(@NonNull ControllerCallback callback) {
-                            callback.onPlayerStateChanged(mInstance, PLAYER_STATE_IDLE);
-                        }
-                    });
-                }
-                return;
-            }
-            if (prevState == null || prevState.getState() != state.getState()) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onPlayerStateChanged(
-                                mInstance, MediaUtils.convertToPlayerState(state));
-                    }
-                });
-            }
-            if (prevState == null || prevState.getPlaybackSpeed() != state.getPlaybackSpeed()) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onPlaybackSpeedChanged(mInstance, state.getPlaybackSpeed());
-                    }
-                });
-            }
-
-            if (prevState != null) {
-                final long currentPosition = state.getCurrentPosition(mInstance.mTimeDiff);
-                long positionDiff = Math.abs(currentPosition
-                        - prevState.getCurrentPosition(mInstance.mTimeDiff));
-                if (positionDiff > POSITION_DIFF_TOLERANCE) {
-                    mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                        @Override
-                        public void run(@NonNull ControllerCallback callback) {
-                            callback.onSeekCompleted(mInstance, currentPosition);
-                        }
-                    });
-                }
-            }
-
-            if (!prevAllowedCommands.equals(currentAllowedCommands)) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onAllowedCommandsChanged(mInstance, currentAllowedCommands);
-                    }
-                });
-            }
-            boolean layoutChanged;
-            if (prevLayout.size() == currentLayout.size()) {
-                layoutChanged = false;
-                for (int i = 0; i < currentLayout.size(); i++) {
-                    if (!ObjectsCompat.equals(prevLayout.get(i).getCommand(),
-                            currentLayout.get(i).getCommand())) {
-                        layoutChanged = true;
-                        break;
-                    }
-                }
-            } else {
-                layoutChanged = true;
-            }
-            if (layoutChanged) {
-                mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onSetCustomLayout(mInstance, currentLayout);
-                    }
-                });
-            }
-
-            if (currentItem == null) {
-                return;
-            }
-            // Update buffering state if needed
-            final int bufferingState = MediaUtils.toBufferingState(state.getState());
-            final int prevBufferingState = prevState == null
-                    ? SessionPlayer.BUFFERING_STATE_UNKNOWN
-                    : MediaUtils.toBufferingState(prevState.getState());
-            if (bufferingState != prevBufferingState) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onBufferingStateChanged(mInstance, currentItem, bufferingState);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadataCompat metadata) {
-            final MediaItem prevItem;
-            final MediaItem currentItem;
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                prevItem = mCurrentMediaItem;
-                setCurrentMediaItemLocked(metadata);
-                currentItem = mCurrentMediaItem;
-            }
-            if (prevItem != currentItem) {
-                mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                    @Override
-                    public void run(@NonNull ControllerCallback callback) {
-                        callback.onCurrentMediaItemChanged(mInstance, currentItem);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onQueueChanged(List<QueueItem> queue) {
-            final List<MediaItem> playlist;
-            final MediaMetadata playlistMetadata;
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                mQueue = MediaUtils.removeNullElements(queue);
-                if (mQueue == null || mQueue.size() == 0) {
-                    // MediaSessionCompat can set queue as null or empty. However, SessionPlayer
-                    // should not set playlist as null or empty. Therefore, we treat them as the
-                    // same.
-                    mQueue = null;
-                    mPlaylist = null;
-                } else {
-                    mPlaylist = MediaUtils.convertQueueItemListToMediaItemList(mQueue);
-                }
-                playlist = mPlaylist;
-                playlistMetadata = mPlaylistMetadata;
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onPlaylistChanged(mInstance, playlist, playlistMetadata);
-                }
-            });
-        }
-
-        @Override
-        public void onQueueTitleChanged(CharSequence title) {
-            final MediaMetadata playlistMetadata;
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                mPlaylistMetadata = MediaUtils.convertToMediaMetadata(title);
-                playlistMetadata = mPlaylistMetadata;
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onPlaylistMetadataChanged(mInstance, playlistMetadata);
-                }
-            });
-        }
-
-        @Override
-        public void onExtrasChanged(final Bundle extras) {
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-            }
-            mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onCustomCommand(mInstance, new SessionCommand(
-                            SESSION_COMMAND_ON_EXTRAS_CHANGED, null), extras);
-                }
-            });
-        }
-
-        @Override
-        public void onAudioInfoChanged(final MediaControllerCompat.PlaybackInfo infoCompat) {
-            final PlaybackInfo info = MediaUtils.toPlaybackInfo2(infoCompat);
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                mPlaybackInfo = info;
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onPlaybackInfoChanged(mInstance, info);
-                }
-            });
-        }
-
-        @Override
-        public void onCaptioningEnabledChanged(final boolean enabled) {
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-            }
-            mInstance.notifyPrimaryControllerCallback(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    Bundle args = new Bundle();
-                    args.putBoolean(MediaConstants.ARGUMENT_CAPTIONING_ENABLED, enabled);
-                    callback.onCustomCommand(mInstance, new SessionCommand(
-                            SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED, null), args);
-                }
-            });
-        }
-
-        @Override
-        public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode final int repeatMode) {
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                mRepeatMode = repeatMode;
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onRepeatModeChanged(mInstance, repeatMode);
-                }
-            });
-        }
-
-        @Override
-        public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode final int shuffleMode) {
-            synchronized (mLock) {
-                if (mClosed || !mConnected) {
-                    return;
-                }
-                mShuffleMode = shuffleMode;
-            }
-            mInstance.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-                @Override
-                public void run(@NonNull ControllerCallback callback) {
-                    callback.onShuffleModeChanged(mInstance, shuffleMode);
-                }
-            });
-        }
-    }
-
-    private static final class SetMediaUriRequest {
-        public final String type;
-        public final String value;
-        public final Bundle extras;
-        public final ResolvableFuture<SessionResult> result;
-
-        SetMediaUriRequest(
-                @NonNull String type,
-                @NonNull String value,
-                @Nullable Bundle extras,
-                @NonNull ResolvableFuture<SessionResult> result) {
-            this.type = type;
-            this.value = value;
-            this.extras = extras;
-            this.result = result;
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerStub.java
deleted file mode 100644
index 10bb019..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerStub.java
+++ /dev/null
@@ -1,528 +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.media2.session;
-
-import android.os.Binder;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer.BuffState;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-class MediaControllerStub extends IMediaController.Stub {
-    private static final String TAG = "MediaControllerStub";
-    private static final boolean DEBUG = true; // TODO(jaewan): Change
-
-    private final WeakReference<MediaControllerImplBase> mController;
-
-    MediaControllerStub(MediaControllerImplBase controller) {
-        mController = new WeakReference<>(controller);
-    }
-
-    @Override
-    public void onSessionResult(final int seq, final ParcelImpl sessionResult) {
-        if (sessionResult == null) {
-            return;
-        }
-        dispatchControllerTask(controller ->
-                controller.setFutureResult(seq, MediaParcelUtils.fromParcelable(sessionResult)));
-    }
-
-    @Override
-    public void onLibraryResult(final int seq, final ParcelImpl libraryResult) {
-        if (libraryResult == null) {
-            return;
-        }
-        dispatchBrowserTask(browser ->
-                browser.setFutureResult(seq, MediaParcelUtils.fromParcelable(libraryResult)));
-    }
-
-    @Override
-    public void onCurrentMediaItemChanged(int seq, final ParcelImpl item, final int currentIdx,
-            final int previousIdx, final int nextIdx) {
-        if (item == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyCurrentMediaItemChanged(
-                        (MediaItem) MediaParcelUtils.fromParcelable(item), currentIdx, previousIdx,
-                        nextIdx);
-            }
-        });
-    }
-
-    @Override
-    public void onPlayerStateChanged(int seq, final long eventTimeMs, final long positionMs,
-            final int state) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyPlayerStateChanges(eventTimeMs, positionMs, state);
-            }
-        });
-    }
-
-    @Override
-    public void onPlaybackSpeedChanged(int seq, final long eventTimeMs, final long positionMs,
-            final float speed) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyPlaybackSpeedChanges(eventTimeMs, positionMs, speed);
-            }
-        });
-    }
-
-    @Override
-    public void onBufferingStateChanged(int seq, final ParcelImpl item, @BuffState final int state,
-            final long bufferedPositionMs, final long eventTimeMs, final long positionMs) {
-        if (item == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                MediaItem itemObj = MediaParcelUtils.fromParcelable(item);
-                if (itemObj == null) {
-                    Log.w(TAG, "onBufferingStateChanged(): Ignoring null item");
-                    return;
-                }
-                controller.notifyBufferingStateChanged(itemObj, state, bufferedPositionMs,
-                        eventTimeMs, positionMs);
-            }
-        });
-    }
-
-    @Override
-    public void onPlaylistChanged(int seq, final ParcelImplListSlice listSlice,
-            final ParcelImpl metadata, final int currentIdx, final int previousIdx,
-            final int nextIdx) {
-        if (metadata == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                List<MediaItem> playlist =
-                        MediaUtils.convertParcelImplListSliceToMediaItemList(listSlice);
-                controller.notifyPlaylistChanges(playlist,
-                        (MediaMetadata) MediaParcelUtils.fromParcelable(metadata), currentIdx,
-                        previousIdx, nextIdx);
-            }
-        });
-    }
-
-    @Override
-    public void onPlaylistMetadataChanged(int seq, final ParcelImpl metadata)
-            throws RuntimeException {
-        if (metadata == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyPlaylistMetadataChanges(
-                        (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-            }
-        });
-    }
-
-    @Override
-    public void onRepeatModeChanged(int seq, final int repeatMode, final int currentIdx,
-            final int previousIdx, final int nextIdx) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyRepeatModeChanges(repeatMode, currentIdx, previousIdx, nextIdx);
-            }
-        });
-    }
-
-    @Override
-    public void onShuffleModeChanged(int seq, final int shuffleMode, final int currentIdx,
-            final int previousIdx, final int nextIdx) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyShuffleModeChanges(shuffleMode, currentIdx, previousIdx, nextIdx);
-            }
-        });
-    }
-
-    @Override
-    public void onPlaybackCompleted(int seq) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifyPlaybackCompleted();
-            }
-        });
-    }
-
-    @Override
-    public void onPlaybackInfoChanged(int seq, final ParcelImpl playbackInfo)
-            throws RuntimeException {
-        if (playbackInfo == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "onPlaybackInfoChanged");
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                MediaController.PlaybackInfo info = MediaParcelUtils.fromParcelable(playbackInfo);
-                if (info == null) {
-                    Log.w(TAG, "onPlaybackInfoChanged(): Ignoring null playbackInfo");
-                    return;
-                }
-                controller.notifyPlaybackInfoChanges(info);
-            }
-        });
-    }
-
-    @Override
-    public void onSeekCompleted(int seq, final long eventTimeMs, final long positionMs,
-            final long seekPositionMs) {
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                controller.notifySeekCompleted(eventTimeMs, positionMs, seekPositionMs);
-            }
-        });
-    }
-
-    @Override
-    public void onVideoSizeChanged(int seq, final ParcelImpl item, final ParcelImpl videoSize) {
-        if (videoSize == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                VideoSize size = MediaParcelUtils.fromParcelable(videoSize);
-                if (size == null) {
-                    Log.w(TAG, "onVideoSizeChanged(): Ignoring null VideoSize");
-                    return;
-                }
-                controller.notifyVideoSizeChanged(size);
-            }
-        });
-    }
-
-    @Override
-    public void onSubtitleData(int seq, final ParcelImpl item, final ParcelImpl track,
-            final ParcelImpl data) {
-        if (item == null || track == null || data == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                MediaItem itemObj = MediaParcelUtils.fromParcelable(item);
-                if (itemObj == null) {
-                    Log.w(TAG, "onSubtitleData(): Ignoring null MediaItem");
-                    return;
-                }
-                TrackInfo trackObj = MediaParcelUtils.fromParcelable(track);
-                if (trackObj == null) {
-                    Log.w(TAG, "onSubtitleData(): Ignoring null TrackInfo");
-                    return;
-                }
-                SubtitleData dataObj = MediaParcelUtils.fromParcelable(data);
-                if (dataObj == null) {
-                    Log.w(TAG, "onSubtitleData(): Ignoring null SubtitleData");
-                    return;
-                }
-                controller.notifySubtitleData(itemObj, trackObj, dataObj);
-            }
-        });
-    }
-    @Override
-    public void onConnected(int seq, ParcelImpl connectionResult) {
-        if (connectionResult == null) {
-            // disconnected
-            onDisconnected(seq);
-            return;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final MediaControllerImplBase controller = mController.get();
-            if (controller == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "onConnected after MediaController.close()");
-                }
-                return;
-            }
-            ConnectionResult result = MediaParcelUtils.fromParcelable(connectionResult);
-            List<MediaItem> itemList =
-                    MediaUtils.convertParcelImplListSliceToMediaItemList(result.getPlaylistSlice());
-            controller.onConnectedNotLocked(result.getVersion(), result.getSessionStub(),
-                    result.getAllowedCommands(), result.getPlayerState(),
-                    result.getCurrentMediaItem(), result.getPositionEventTimeMs(),
-                    result.getPositionMs(), result.getPlaybackSpeed(),
-                    result.getBufferedPositionMs(), result.getPlaybackInfo(),
-                    result.getRepeatMode(), result.getShuffleMode(), itemList,
-                    result.getSessionActivity(), result.getCurrentMediaItemIndex(),
-                    result.getPreviousMediaItemIndex(), result.getNextMediaItemIndex(),
-                    result.getTokenExtras(), result.getVideoSize(), result.getTracks(),
-                    result.getSelectedVideoTrack(), result.getSelectedAudioTrack(),
-                    result.getSelectedSubtitleTrack(), result.getSelectedMetadataTrack(),
-                    result.getPlaylistMetadata(), result.getBufferingState());
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public void onDisconnected(int seq) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final MediaControllerImplBase controller = mController.get();
-            if (controller == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "onDisconnected after MediaController.close()");
-                }
-                return;
-            }
-            controller.mInstance.close();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public void onSetCustomLayout(final int seq, final List<ParcelImpl> commandButtonList) {
-        if (commandButtonList == null) {
-            Log.w(TAG, "setCustomLayout(): Ignoring null commandButtonList");
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                List<MediaSession.CommandButton> layout = new ArrayList<>();
-                for (int i = 0; i < commandButtonList.size(); i++) {
-                    MediaSession.CommandButton button =
-                            MediaParcelUtils.fromParcelable(commandButtonList.get(i));
-                    if (button != null) {
-                        layout.add(button);
-                    }
-                }
-                controller.onSetCustomLayout(seq, layout);
-            }
-        });
-    }
-
-    @Override
-    public void onAllowedCommandsChanged(int seq, final ParcelImpl commands) {
-        if (commands == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                SessionCommandGroup commandGroup = MediaParcelUtils.fromParcelable(commands);
-                if (commandGroup == null) {
-                    Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
-                    return;
-                }
-                controller.onAllowedCommandsChanged(commandGroup);
-            }
-        });
-    }
-
-    @Override
-    public void onCustomCommand(final int seq, final ParcelImpl commandParcel, final Bundle args) {
-        if (commandParcel == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                SessionCommand command = MediaParcelUtils.fromParcelable(commandParcel);
-                if (command == null) {
-                    Log.w(TAG, "sendCustomCommand(): Ignoring null command");
-                    return;
-                }
-                controller.onCustomCommand(seq, command, args);
-            }
-        });
-    }
-
-    @Override
-    public void onTrackInfoChanged(final int seq, final List<ParcelImpl> trackInfoList,
-            final ParcelImpl selectedVideoParcel, final ParcelImpl selectedAudioParcel,
-            final ParcelImpl selectedSubtitleParcel, final ParcelImpl selectedMetadataParcel) {
-        if (trackInfoList == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                List<TrackInfo> trackInfos = MediaParcelUtils.fromParcelableList(trackInfoList);
-                TrackInfo selectedVideoTrack = MediaParcelUtils.fromParcelable(selectedVideoParcel);
-                TrackInfo selectedAudioTrack = MediaParcelUtils.fromParcelable(selectedAudioParcel);
-                TrackInfo selectedSubtitleTrack =
-                        MediaParcelUtils.fromParcelable(selectedSubtitleParcel);
-                TrackInfo selectedMetadataTrack =
-                        MediaParcelUtils.fromParcelable(selectedMetadataParcel);
-                controller.notifyTracksChanged(seq, trackInfos, selectedVideoTrack,
-                        selectedAudioTrack, selectedSubtitleTrack, selectedMetadataTrack);
-            }
-        });
-    }
-
-    @Override
-    public void onTrackSelected(final int seq, final ParcelImpl trackInfoParcel) {
-        if (trackInfoParcel == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                TrackInfo trackInfo = MediaParcelUtils.fromParcelable(trackInfoParcel);
-                if (trackInfo == null) {
-                    Log.w(TAG, "onTrackSelected(): Ignoring null track info");
-                    return;
-                }
-                controller.notifyTrackSelected(seq, trackInfo);
-            }
-        });
-    }
-
-    @Override
-    public void onTrackDeselected(final int seq, final ParcelImpl trackInfoParcel) {
-        if (trackInfoParcel == null) {
-            return;
-        }
-        dispatchControllerTask(new ControllerTask() {
-            @Override
-            public void run(MediaControllerImplBase controller) {
-                TrackInfo trackInfo = MediaParcelUtils.fromParcelable(trackInfoParcel);
-                if (trackInfo == null) {
-                    Log.w(TAG, "onTrackSelected(): Ignoring null track info");
-                    return;
-                }
-                controller.notifyTrackDeselected(seq, trackInfo);
-            }
-        });
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////
-    // MediaBrowser specific
-    ////////////////////////////////////////////////////////////////////////////////////////////
-    @Override
-    public void onSearchResultChanged(int seq, final String query, final int itemCount,
-            final ParcelImpl libraryParams) throws RuntimeException {
-        if (libraryParams == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "onSearchResultChanged(): Ignoring empty query");
-            return;
-        }
-        if (itemCount < 0) {
-            Log.w(TAG, "onSearchResultChanged(): Ignoring negative itemCount: " + itemCount);
-            return;
-        }
-        dispatchBrowserTask(new BrowserTask() {
-            @Override
-            public void run(MediaBrowserImplBase browser) {
-                browser.notifySearchResultChanged(query, itemCount,
-                        (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-            }
-        });
-    }
-
-    @Override
-    public void onChildrenChanged(int seq, final String parentId, final int itemCount,
-            final ParcelImpl libraryParams) {
-        if (libraryParams == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(parentId)) {
-            Log.w(TAG, "onChildrenChanged(): Ignoring empty parentId");
-            return;
-        }
-        if (itemCount < 0) {
-            Log.w(TAG, "onChildrenChanged(): Ignoring negative itemCount: " + itemCount);
-            return;
-        }
-        dispatchBrowserTask(new BrowserTask() {
-            @Override
-            public void run(MediaBrowserImplBase browser) {
-                browser.notifyChildrenChanged(parentId, itemCount,
-                        (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-            }
-        });
-    }
-
-    public void destroy() {
-        mController.clear();
-    }
-
-    private void dispatchControllerTask(ControllerTask task) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final MediaControllerImplBase controller = mController.get();
-            if (controller == null || !controller.isConnected()) {
-                return;
-            }
-            task.run(controller);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void dispatchBrowserTask(BrowserTask task) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final MediaControllerImplBase browser = mController.get();
-            if (!(browser instanceof MediaBrowserImplBase) || !browser.isConnected()) {
-                return;
-            }
-            task.run((MediaBrowserImplBase) browser);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @FunctionalInterface
-    private interface ControllerTask {
-        void run(MediaControllerImplBase controller);
-    }
-
-    @FunctionalInterface
-    private interface BrowserTask {
-        void run(MediaBrowserImplBase browser);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaInterface.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaInterface.java
deleted file mode 100644
index 04fa0ec..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaInterface.java
+++ /dev/null
@@ -1,89 +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.media2.session;
-
-import android.view.Surface;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-class MediaInterface {
-    private MediaInterface() {
-    }
-
-    // TODO: relocate methods among different interfaces and classes.
-    interface SessionPlaybackControl {
-        ListenableFuture<PlayerResult> prepare();
-        ListenableFuture<PlayerResult> play();
-        ListenableFuture<PlayerResult> pause();
-
-        ListenableFuture<PlayerResult> seekTo(long pos);
-
-        int getPlayerState();
-        long getCurrentPosition();
-        long getDuration();
-
-        long getBufferedPosition();
-        int getBufferingState();
-
-        float getPlaybackSpeed();
-        ListenableFuture<PlayerResult> setPlaybackSpeed(float speed);
-    }
-
-    interface SessionPlaylistControl {
-        List<MediaItem> getPlaylist();
-        MediaMetadata getPlaylistMetadata();
-        ListenableFuture<PlayerResult> setPlaylist(List<MediaItem> list, MediaMetadata metadata);
-        ListenableFuture<PlayerResult> setMediaItem(MediaItem item);
-        ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata);
-
-        MediaItem getCurrentMediaItem();
-        int getCurrentMediaItemIndex();
-        int getPreviousMediaItemIndex();
-        int getNextMediaItemIndex();
-        ListenableFuture<PlayerResult> skipToPlaylistItem(int index);
-        ListenableFuture<PlayerResult> skipToPreviousItem();
-        ListenableFuture<PlayerResult> skipToNextItem();
-
-        ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem item);
-        ListenableFuture<PlayerResult> removePlaylistItem(int index);
-        ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem item);
-        ListenableFuture<PlayerResult> movePlaylistItem(int fromIndex, int toIndex);
-
-        int getRepeatMode();
-        ListenableFuture<PlayerResult> setRepeatMode(int repeatMode);
-        int getShuffleMode();
-        ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode);
-    }
-
-    // Common interface for session and controller
-    interface SessionPlayer extends SessionPlaybackControl, SessionPlaylistControl {
-        VideoSize getVideoSize();
-        ListenableFuture<PlayerResult> setSurface(Surface surface);
-        List<TrackInfo> getTracks();
-        ListenableFuture<PlayerResult> selectTrack(TrackInfo trackInfo);
-        ListenableFuture<PlayerResult> deselectTrack(TrackInfo trackInfo);
-        TrackInfo getSelectedTrack(int trackType);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryService.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryService.java
deleted file mode 100644
index b063bcd..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryService.java
+++ /dev/null
@@ -1,730 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.TextUtils;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.content.ContextCompat;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.LibraryResult.ResultCode;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.util.concurrent.Executor;
-
-/**
- * Base class for media library services, which is the service containing {@link
- * MediaLibrarySession}.
- *
- * <p>Media library services enable applications to browse media content provided by an application
- * and ask the application to start playing it. They may also be used to control content that is
- * already playing by way of a {@link MediaSession}.
- *
- * <p>When extending this class, also add the following to your {@code AndroidManifest.xml}.
- *
- * <pre>
- * &lt;service android:name="component_name_of_your_implementation" &gt;
- *   &lt;intent-filter&gt;
- *     &lt;action android:name="androidx.media2.session.MediaLibraryService" /&gt;
- *   &lt;/intent-filter&gt;
- * &lt;/service&gt;</pre>
- *
- * <p>You may also declare
- *
- * <pre>android.media.browse.MediaBrowserService</pre>
- *
- * for compatibility with {@link android.support.v4.media.MediaBrowserCompat}. This service can
- * handle it automatically.
- *
- * @see MediaSessionService
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public abstract class MediaLibraryService extends MediaSessionService {
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
-
-    /**
-     * Session for the {@link MediaLibraryService}. Build this object with {@link Builder} and
-     * return in {@link MediaSessionService#onGetSession(ControllerInfo)}.
-     *
-     * <h3 id="BackwardCompatibility">Backward compatibility with legacy media browser APIs</h3>
-     *
-     * Media library session supports connection from both {@link MediaBrowser} and {@link
-     * android.support.v4.media.MediaBrowserCompat}, but {@link ControllerInfo} may not be precise.
-     * Here are current limitations with details.
-     *
-     * <table>
-     * <tr><th>SDK version</th>
-     *     <th>{@link ControllerInfo#getPackageName()}<br>for legacy browser</th>
-     *     <th>{@link ControllerInfo#getUid()}<br>for legacy browser</th></tr>
-     * <tr><td>{@code SDK_VERSION} &lt; 21</td>
-     *     <td>Actual package name via {@link Context#getPackageName()}</td>
-     *     <td>Actual UID</td></tr>
-     * <tr><td>21 &ge; {@code SDK_VERSION} &lt; 28,<br>
-     *         {@code MediaLibrarySessionCallback#onConnect} and<br>
-     *         {@code MediaLibrarySessionCallback#onGetLibraryRoot}</td>
-     *     <td>Actual package name via {@link Context#getPackageName()}</td>
-     *     <td>Actual UID</td></tr>
-     * <tr><td>21 &ge; {@code SDK_VERSION} &lt; 28, for other callbacks</td>
-     *     <td>{@link RemoteUserInfo#LEGACY_CONTROLLER}</td>
-     *     <td>Negative value</td></tr>
-     * <tr><td>28 &ge; {@code SDK_VERSION}</td>
-     *     <td>Actual package name via {@link Context#getPackageName()}</td>
-     *     <td>Actual UID</td></tr>
-     * </table>
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class MediaLibrarySession extends MediaSession {
-        private final boolean mThrowsWhenInvalidReturn;
-
-        /**
-         * Callback for the {@link MediaLibrarySession}.
-         *
-         * <p>When you return {@link LibraryResult} with media items, items must have valid {@link
-         * MediaMetadata#METADATA_KEY_MEDIA_ID} and specify {@link
-         * MediaMetadata#METADATA_KEY_BROWSABLE} and {@link MediaMetadata#METADATA_KEY_PLAYABLE}.
-         *
-         * @deprecated androidx.media2 is deprecated. Please migrate to <a
-         *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a> .
-         */
-        @Deprecated
-        public static class MediaLibrarySessionCallback extends MediaSession.SessionCallback {
-            /**
-             * Called to get the root information for browsing by a {@link MediaBrowser}.
-             * <p>
-             * To allow browsing media information, return the {@link LibraryResult} with the
-             * {@link LibraryResult#RESULT_SUCCESS} and the root media item with the valid
-             * {@link MediaMetadata#METADATA_KEY_MEDIA_ID media id}. The media id must be included
-             * for the browser to get the children under it.
-             * <p>
-             * Interoperability: this callback may be called on the main thread, regardless of the
-             * callback executor.
-             *
-             * @param session the session for this event
-             * @param controller information of the controller requesting access to browse media.
-             * @param params An optional library params of service-specific arguments to send
-             *               to the media library service when connecting and retrieving the
-             *               root id for browsing, or {@code null} if none.
-             * @return a library result with the root media item with the id. A runtime exception
-             *         will be thrown if an invalid result is returned.
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT
-             * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-             * @see LibraryParams
-             */
-            @NonNull
-            public LibraryResult onGetLibraryRoot(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @Nullable LibraryParams params) {
-                return new LibraryResult(RESULT_ERROR_NOT_SUPPORTED);
-            }
-
-            /**
-             * Called to get an item.
-             * <p>
-             * To allow getting the item, return the {@link LibraryResult} with the
-             * {@link LibraryResult#RESULT_SUCCESS} and the media item.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param mediaId non-empty media id of the requested item
-             * @return a library result with a media item with the id. A runtime exception
-             *         will be thrown if an invalid result is returned.
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_GET_ITEM
-             */
-            @NonNull
-            public LibraryResult onGetItem(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                return new LibraryResult(RESULT_ERROR_NOT_SUPPORTED);
-            }
-
-            /**
-             * Called to get children of given parent id. Return the children here for the browser.
-             * <p>
-             * To allow getting the children, return the {@link LibraryResult} with the
-             * {@link LibraryResult#RESULT_SUCCESS} and the list of media item. Return an empty
-             * list for no children rather than using result code for error.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param parentId non-empty parent id to get children
-             * @param page page number. Starts from {@code 0}.
-             * @param pageSize page size. Should be greater or equal to {@code 1}.
-             * @param params library params
-             * @return a library result with a list of media item with the id. A runtime exception
-             *         will be thrown if an invalid result is returned.
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_GET_CHILDREN
-             * @see LibraryParams
-             */
-            @NonNull
-            public LibraryResult onGetChildren(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @NonNull String parentId,
-                    @IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
-                    @Nullable LibraryParams params) {
-                return new LibraryResult(RESULT_ERROR_NOT_SUPPORTED);
-            }
-
-            /**
-             * Called when a controller subscribes to the parent.
-             * <p>
-             * It's your responsibility to keep subscriptions by your own and call
-             * {@link MediaLibrarySession#notifyChildrenChanged(
-             * ControllerInfo, String, int, LibraryParams)} when the parent is changed until it's
-             * unsubscribed.
-             * <p>
-             * Interoperability: This will be called when
-             * {@link android.support.v4.media.MediaBrowserCompat#subscribe} is called.
-             * However, this won't be called when {@link MediaBrowser#subscribe} is called.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param parentId non-empty parent id
-             * @param params library params
-             * @return result code
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_SUBSCRIBE
-             * @see LibraryParams
-             */
-            @ResultCode
-            public int onSubscribe(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @NonNull String parentId,
-                    @Nullable LibraryParams params) {
-                return RESULT_ERROR_NOT_SUPPORTED;
-            }
-
-            /**
-             * Called when a controller unsubscribes to the parent.
-             * <p>
-             * Interoperability: This wouldn't be called if {@link MediaBrowser#unsubscribe} is
-             * called while works well with
-             * {@link android.support.v4.media.MediaBrowserCompat#unsubscribe}.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param parentId non-empty parent id
-             * @return result code
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_UNSUBSCRIBE
-             */
-            @ResultCode
-            public int onUnsubscribe(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @NonNull String parentId) {
-                return RESULT_ERROR_NOT_SUPPORTED;
-            }
-
-            /**
-             * Called when a controller requests search.
-             * <p>
-             * Return immediately with the result of the attempt to search with the query. Notify
-             * the number of search result through
-             * {@link #notifySearchResultChanged(ControllerInfo, String, int, LibraryParams)}.
-             * {@link MediaBrowser} will ask the search result with the pagination later.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param query The non-empty search query sent from the media browser.
-             *              It contains keywords separated by space.
-             * @param params library params
-             * @return result code
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_SEARCH
-             * @see #notifySearchResultChanged(ControllerInfo, String, int, LibraryParams)
-             * @see LibraryParams
-             */
-            @ResultCode
-            public int onSearch(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo controller, @NonNull String query,
-                    @Nullable LibraryParams params) {
-                return RESULT_ERROR_NOT_SUPPORTED;
-            }
-
-            /**
-             * Called to get the search result.
-             * <p>
-             * To allow getting the search result, return the {@link LibraryResult} with the
-             * {@link LibraryResult#RESULT_SUCCESS} and the list of media item. Return an empty
-             * list for no search result rather than using result code for error.
-             * <p>
-             * This may be called with a query that hasn't called with {@link #onSearch}, especially
-             * when {@link android.support.v4.media.MediaBrowserCompat#search} is used.
-             *
-             * @param session the session for this event
-             * @param controller controller
-             * @param query The non-empty search query which was previously sent through
-             *              {@link #onSearch}.
-             * @param page page number. Starts from {@code 0}.
-             * @param pageSize page size. Should be greater or equal to {@code 1}.
-             * @param params library params
-             * @return a library result with a list of media item with the id. A runtime exception
-             *         will be thrown if an invalid result is returned.
-             * @see SessionCommand#COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT
-             * @see LibraryParams
-             */
-            @NonNull
-            public LibraryResult onGetSearchResult(
-                    @NonNull MediaLibrarySession session, @NonNull ControllerInfo controller,
-                    @NonNull String query, @IntRange(from = 0) int page,
-                    @IntRange(from = 1) int pageSize, @Nullable LibraryParams params) {
-                return new LibraryResult(RESULT_ERROR_NOT_SUPPORTED);
-            }
-        }
-
-        /**
-         * Builder for {@link MediaLibrarySession}.
-         *
-         * <p>Any incoming event from the {@link MediaController} will be handled on the callback
-         * executor. If it's not set, {@link ContextCompat#getMainExecutor(Context)} will be used by
-         * default.
-         *
-         * @deprecated androidx.media2 is deprecated. Please migrate to <a
-         *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a> .
-         */
-        // Override all methods just to show them with the type instead of generics in Javadoc.
-        // This workarounds javadoc issue described in the MediaSession.BuilderBase.
-        // Note: Don't override #setSessionCallback() because the callback can be set by the
-        // constructor.
-        @Deprecated
-        public static final class Builder
-                extends MediaSession.BuilderBase<
-                        MediaLibrarySession, Builder, MediaLibrarySessionCallback> {
-            private boolean mThrowsWhenInvalidReturn = true;
-
-            // Builder requires MediaLibraryService instead of Context just to ensure that the
-            // builder can be only instantiated within the MediaLibraryService.
-            // Ideally it's better to make it inner class of service to enforce, but it violates API
-            // guideline that Builders should be the inner class of the building target.
-            public Builder(@NonNull MediaLibraryService service,
-                    @NonNull SessionPlayer player,
-                    @NonNull Executor callbackExecutor,
-                    @NonNull MediaLibrarySessionCallback callback) {
-                super(service, player);
-                setSessionCallback(callbackExecutor, callback);
-            }
-
-            @Override
-            @NonNull
-            public Builder setSessionActivity(@Nullable PendingIntent pi) {
-                return super.setSessionActivity(pi);
-            }
-
-            @Override
-            @NonNull
-            public Builder setId(@NonNull String id) {
-                return super.setId(id);
-            }
-
-            @Override
-            @NonNull
-            public Builder setExtras(@NonNull Bundle extras) {
-                return super.setExtras(extras);
-            }
-
-            /**
-             * Prevents session to be crashed when it returns any invalid return.
-             *
-             */
-            @RestrictTo(LIBRARY)
-            @NonNull
-            @VisibleForTesting
-            public Builder setThrowsWhenInvalidReturn(boolean throwsWhenInvalidReturn) {
-                mThrowsWhenInvalidReturn = throwsWhenInvalidReturn;
-                return this;
-            }
-
-            @Override
-            @NonNull
-            public MediaLibrarySession build() {
-                if (mCallbackExecutor == null) {
-                    mCallbackExecutor = ContextCompat.getMainExecutor(mContext);
-                }
-                if (mCallback == null) {
-                    mCallback = new MediaLibrarySession.MediaLibrarySessionCallback() {};
-                }
-                return new MediaLibrarySession(mContext, mId, mPlayer, mSessionActivity,
-                        mCallbackExecutor, mCallback, mExtras, mThrowsWhenInvalidReturn);
-            }
-        }
-
-        MediaLibrarySession(Context context, String id, SessionPlayer player,
-                PendingIntent sessionActivity, Executor callbackExecutor,
-                MediaSession.SessionCallback callback, Bundle tokenExtras,
-                boolean throwsWhenInvalidReturn) {
-            super(context, id, player, sessionActivity, callbackExecutor, callback, tokenExtras);
-            mThrowsWhenInvalidReturn = throwsWhenInvalidReturn;
-        }
-
-        @Override
-        MediaLibrarySessionImpl createImpl(Context context, String id, SessionPlayer player,
-                PendingIntent sessionActivity, Executor callbackExecutor,
-                MediaSession.SessionCallback callback, Bundle tokenExtras) {
-            return new MediaLibrarySessionImplBase(this, context, id, player, sessionActivity,
-                    callbackExecutor, callback, tokenExtras, mThrowsWhenInvalidReturn);
-        }
-
-        @Override
-        MediaLibrarySessionImpl getImpl() {
-            return (MediaLibrarySessionImpl) super.getImpl();
-        }
-
-        /**
-         * Notifies the controller of the change in a parent's children.
-         * <p>
-         * If the controller hasn't subscribed to the parent, the API will do nothing.
-         * <p>
-         * Controllers will use {@link MediaBrowser#getChildren(String, int, int, LibraryParams)}
-         * to get the list of children.
-         *
-         * @param controller controller to notify
-         * @param parentId non-empty parent id with changes in its children
-         * @param itemCount number of children.
-         * @param params library params
-         */
-        public void notifyChildrenChanged(@NonNull ControllerInfo controller,
-                @NonNull String parentId, @IntRange(from = 0) int itemCount,
-                @Nullable LibraryParams params) {
-            if (controller == null) {
-                throw new NullPointerException("controller shouldn't be null");
-            }
-            if (parentId == null) {
-                throw new NullPointerException("parentId shouldn't be null");
-            } else if (TextUtils.isEmpty(parentId)) {
-                throw new IllegalArgumentException("parentId shouldn't be empty");
-            }
-            if (itemCount < 0) {
-                throw new IllegalArgumentException("itemCount shouldn't be negative");
-            }
-            getImpl().notifyChildrenChanged(controller, parentId, itemCount, params);
-        }
-
-        /**
-         * Notifies all controllers that subscribed to the parent about change in the parent's
-         * children, regardless of the library params supplied by
-         * {@link MediaBrowser#subscribe(String, LibraryParams)}.
-         *  @param parentId non-empty parent id
-         * @param itemCount number of children
-         * @param params library params
-         */
-        // This is for the backward compatibility.
-        public void notifyChildrenChanged(@NonNull String parentId, int itemCount,
-                @Nullable LibraryParams params) {
-            if (TextUtils.isEmpty(parentId)) {
-                throw new IllegalArgumentException("parentId shouldn't be empty");
-            }
-            if (itemCount < 0) {
-                throw new IllegalArgumentException("itemCount shouldn't be negative");
-            }
-            getImpl().notifyChildrenChanged(parentId, itemCount, params);
-        }
-
-        /**
-         * Notifies controller about change in the search result.
-         *
-         * @param controller controller to notify
-         * @param query previously sent non-empty search query from the controller.
-         * @param itemCount the number of items that have been found in the search.
-         * @param params library params
-         */
-        public void notifySearchResultChanged(@NonNull ControllerInfo controller,
-                @NonNull String query, @IntRange(from = 0) int itemCount,
-                @Nullable LibraryParams params) {
-            if (controller == null) {
-                throw new NullPointerException("controller shouldn't be null");
-            }
-            if (query == null) {
-                throw new NullPointerException("query shouldn't be null");
-            } else if (TextUtils.isEmpty(query)) {
-                throw new IllegalArgumentException("query shouldn't be empty");
-            }
-            if (itemCount < 0) {
-                throw new IllegalArgumentException("itemCount shouldn't be negative");
-            }
-            getImpl().notifySearchResultChanged(controller, query, itemCount, params);
-        }
-
-        @Override
-        @NonNull
-        MediaLibrarySessionCallback getCallback() {
-            return (MediaLibrarySessionCallback) super.getCallback();
-        }
-
-        interface MediaLibrarySessionImpl extends MediaSessionImpl {
-            // LibrarySession methods
-            void notifyChildrenChanged(
-                    @NonNull String parentId, int itemCount, @Nullable LibraryParams params);
-            void notifyChildrenChanged(@NonNull ControllerInfo controller,
-                    @NonNull String parentId, int itemCount, @Nullable LibraryParams params);
-            void notifySearchResultChanged(@NonNull ControllerInfo controller,
-                    @NonNull String query, int itemCount, @Nullable LibraryParams params);
-
-            // LibrarySession callback implementations called on the executors
-            LibraryResult onGetLibraryRootOnExecutor(@NonNull ControllerInfo controller,
-                    @Nullable LibraryParams params);
-            LibraryResult onGetItemOnExecutor(@NonNull ControllerInfo controller,
-                    @NonNull String mediaId);
-            LibraryResult onGetChildrenOnExecutor(@NonNull ControllerInfo controller,
-                    @NonNull String parentId, int page, int pageSize,
-                    @Nullable LibraryParams params);
-            int onSubscribeOnExecutor(@NonNull ControllerInfo controller,
-                    @NonNull String parentId, @Nullable LibraryParams params);
-            int onUnsubscribeOnExecutor(@NonNull ControllerInfo controller,
-                    @NonNull String parentId);
-            int onSearchOnExecutor(@NonNull ControllerInfo controller, @NonNull String query,
-                    @Nullable LibraryParams params);
-            LibraryResult onGetSearchResultOnExecutor(@NonNull ControllerInfo controller,
-                    @NonNull String query, int page, int pageSize, @Nullable LibraryParams params);
-
-            // Internally used methods - only changing return type
-            @Override
-            MediaLibrarySession getInstance();
-
-            @Override
-            MediaLibrarySessionCallback getCallback();
-        }
-    }
-
-    @Override
-    MediaSessionServiceImpl createImpl() {
-        return new MediaLibraryServiceImplBase();
-    }
-
-    @Override
-    public IBinder onBind(@NonNull Intent intent) {
-        return super.onBind(intent);
-    }
-
-    @Override
-    @Nullable
-    public abstract MediaLibrarySession onGetSession(@NonNull ControllerInfo controllerInfo);
-
-    /**
-     * Contains information that the library service needs to send to the client.
-     *
-     * <p>When the browser supplies {@link LibraryParams}, it's optional field when getting the
-     * media item(s). The library session is recommended to do the best effort to provide such
-     * result. It's not an error even when the library session didn't return such items.
-     *
-     * <p>The library params returned in the library session callback must include the information
-     * about the returned media item(s).
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    @VersionedParcelize
-    public static final class LibraryParams implements VersionedParcelable {
-        @ParcelField(1)
-        Bundle mBundle;
-
-        // Types are intentionally Integer for future extension of the value with less effort.
-        @ParcelField(2)
-        int mRecent;
-        @ParcelField(3)
-        int mOffline;
-        @ParcelField(4)
-        int mSuggested;
-
-        // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-        // For versioned parcelable.
-        LibraryParams() {
-            // no-op
-        }
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        LibraryParams(Bundle bundle, boolean recent, boolean offline, boolean suggested) {
-            // Keeps the booleans in Integer type.
-            // Types are intentionally Integer for future extension of the value with less effort.
-            this(bundle,
-                    convertToInteger(recent),
-                    convertToInteger(offline),
-                    convertToInteger(suggested));
-        }
-
-        private LibraryParams(Bundle bundle, int recent, int offline, int suggested) {
-            mBundle = bundle;
-            mRecent = recent;
-            mOffline = offline;
-            mSuggested = suggested;
-        }
-
-        private static int convertToInteger(boolean a) {
-            return a ? 1 : 0;
-        }
-
-        private static boolean convertToBoolean(int a) {
-            return a == 0 ? false : true;
-        }
-
-        /**
-         * Returns {@code true} for recent media items.
-         * <p>
-         * When the browser supplies {@link LibraryParams} with the {@code true}, library
-         * session is recommended to provide such media items. If so, the library session
-         * implementation must return the params with the {@code true} as well. The list of
-         * media items is considered ordered by relevance, first being the top suggestion.
-         *
-         * @return {@code true} for recent items. {@code false} otherwise.
-         */
-        public boolean isRecent() {
-            return convertToBoolean(mRecent);
-        }
-
-        /**
-         * Returns {@code true} for offline media items, which can be played without an internet
-         * connection.
-         * <p>
-         * When the browser supplies {@link LibraryParams} with the {@code true}, library
-         * session is recommended to provide such media items. If so, the library session
-         * implementation must return the params with the {@code true} as well.
-         *
-         * @return {@code true} for offline items. {@code false} otherwise.
-         */
-        public boolean isOffline() {
-            return convertToBoolean(mOffline);
-        }
-
-        /**
-         * Returns {@code true} for suggested media items.
-         * <p>
-         * When the browser supplies {@link LibraryParams} with the {@code true}, library
-         * session is recommended to provide such media items. If so, the library session
-         * implementation must return the params with the {@code true} as well. The list of
-         * media items is considered ordered by relevance, first being the top suggestion.
-         *
-         * @return {@code true} for suggested items. {@code false} otherwise
-         */
-        public boolean isSuggested() {
-            return convertToBoolean(mSuggested);
-        }
-
-        /**
-         * Gets the extras.
-         * <p>
-         * Extras are the private contract between browser and library session.
-         */
-        @Nullable
-        public Bundle getExtras() {
-            return mBundle;
-        }
-
-        /**
-         * Builds a {@link LibraryParams}.
-         *
-         * @deprecated androidx.media2 is deprecated. Please migrate to <a
-         *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-         */
-        @Deprecated
-        public static final class Builder {
-            private boolean mRecent;
-            private boolean mOffline;
-            private boolean mSuggested;
-
-            private Bundle mBundle;
-
-            /**
-             * Sets whether recently played media item.
-             * <p>
-             * When the browser supplies the {@link LibraryParams} with the {@code true}, library
-             * session is recommended to provide such media items. If so, the library session
-             * implementation must return the params with the {@code true} as well.
-             *
-             * @param recent {@code true} for recent items. {@code false} otherwise.
-             * @return this builder
-             */
-            @NonNull
-            public Builder setRecent(boolean recent) {
-                mRecent = recent;
-                return this;
-            }
-
-            /**
-             * Sets whether offline media items, which can be played without an internet connection.
-             * <p>
-             * When the browser supplies {@link LibraryParams} with the {@code true}, library
-             * session is recommended to provide such media items. If so, the library session
-             * implementation must return the params with the {@code true} as well.
-             *
-             * @param offline {@code true} for offline items. {@code false} otherwise.
-             * @return this builder
-             */
-            @NonNull
-            public Builder setOffline(boolean offline) {
-                mOffline = offline;
-                return this;
-            }
-
-            /**
-             * Sets whether suggested media items.
-             * <p>
-             * When the browser supplies {@link LibraryParams} with the {@code true}, library
-             * session is recommended to provide such media items. If so, the library session
-             * implementation must return the params with the {@code true} as well. The list of
-             * media items is considered ordered by relevance, first being the top suggestion.
-             *
-             * @param suggested {@code true} for suggested items. {@code false} otherwise
-             * @return this builder
-             */
-            @NonNull
-            public Builder setSuggested(boolean suggested) {
-                mSuggested = suggested;
-                return this;
-            }
-
-            /**
-             * Set a bundle of extras, that browser and library session can understand each other.
-             *
-             * @param extras The extras or null.
-             * @return this builder
-             */
-            @NonNull
-            public Builder setExtras(@Nullable Bundle extras) {
-                mBundle = extras;
-                return this;
-            }
-
-            /**
-             * Builds a {@link LibraryParams}.
-             *
-             * @return new LibraryParams
-             */
-            @NonNull
-            public LibraryParams build() {
-                return new LibraryParams(mBundle, mRecent, mOffline, mSuggested);
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceImplBase.java
deleted file mode 100644
index 3d4bce3..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceImplBase.java
+++ /dev/null
@@ -1,33 +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.media2.session;
-
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Implementation of {@link MediaLibraryService}.
- */
-class MediaLibraryServiceImplBase extends MediaSessionServiceImplBase {
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (MediaLibraryService.SERVICE_INTERFACE.equals(intent.getAction())) {
-            return getServiceBinder();
-        }
-        return super.onBind(intent);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
deleted file mode 100644
index 14b0d3c..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
+++ /dev/null
@@ -1,682 +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.media2.session;
-
-import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE;
-import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE_SIZE;
-
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-import static androidx.media2.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
-
-import android.content.Context;
-import android.os.BadParcelableException;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionImpl;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaSession.ControllerCb;
-import androidx.media2.session.MediaSession.ControllerInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of {@link MediaBrowserServiceCompat} for interoperability between
- * {@link MediaLibraryService} and {@link MediaBrowserCompat}.
- */
-class MediaLibraryServiceLegacyStub extends MediaSessionServiceLegacyStub {
-    private static final String TAG = "MLS2LegacyStub";
-    private static final boolean DEBUG = false;
-
-    private final ControllerCb mBrowserLegacyCbForBroadcast;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final MediaLibrarySessionImpl mLibrarySessionImpl;
-
-    // Note: We'd better not obtain token from the session because it's called inside of the
-    // session's constructor and session's token may not be initialized here.
-    MediaLibraryServiceLegacyStub(Context context, MediaLibrarySessionImpl session,
-            MediaSessionCompat.Token token) {
-        super(context, session, token);
-        mLibrarySessionImpl = session;
-        mBrowserLegacyCbForBroadcast = new BrowserLegacyCbForBroadcast(this);
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, final Bundle rootHints) {
-        BrowserRoot browserRoot = super.onGetRoot(clientPackageName, clientUid, rootHints);
-        if (browserRoot == null) {
-            return null;
-        }
-        final ControllerInfo controller = getCurrentController();
-        if (controller == null) {
-            return null;
-        }
-        if (getConnectedControllersManager().isAllowedCommand(controller,
-                SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) {
-            // Call callbacks directly instead of execute on the executor. Here's the reason.
-            // We need to return browser root here. So if we run the callback on the executor, we
-            // should wait for the completion.
-            // However, we cannot wait if the callback executor is the main executor, which posts
-            // the runnable to the main thread's. In that case, since this onGetRoot() always runs
-            // on the main thread, the posted runnable for calling onGetLibraryRoot() wouldn't run
-            // in here. Even worse, we cannot know whether it would be run on the main thread or
-            // not.
-            // Because of the reason, just call onGetLibraryRoot() directly here. onGetLibraryRoot()
-            // has documentation that it may be called on the main thread.
-            LibraryParams params = MediaUtils.convertToLibraryParams(
-                    mLibrarySessionImpl.getContext(), rootHints);
-            LibraryResult result = mLibrarySessionImpl.getCallback().onGetLibraryRoot(
-                    mLibrarySessionImpl.getInstance(), controller, params);
-            if (result != null && result.getResultCode() == RESULT_SUCCESS
-                    && result.getMediaItem() != null) {
-                MediaMetadata metadata = result.getMediaItem().getMetadata();
-                String id = metadata != null
-                        ? metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) : "";
-                return new BrowserRoot(id,
-                        MediaUtils.convertToRootHints(result.getLibraryParams()));
-            } else if (DEBUG) {
-                Log.d(TAG, "Unexpected LibraryResult for getting the root from the legacy browser."
-                        + " Will return stub root to allow getting session.");
-            }
-        } else if (DEBUG) {
-            Log.d(TAG, "Command MBC.connect from " + controller + " was rejected by "
-                    + mLibrarySessionImpl);
-        }
-        // No library root, but keep browser compat connected to allow getting session.
-        return MediaUtils.sDefaultBrowserRoot;
-    }
-
-    @Override
-    public void onSubscribe(final String id, final Bundle option) {
-        final ControllerInfo controller = getCurrentController();
-        if (TextUtils.isEmpty(id)) {
-            Log.w(TAG, "onSubscribe(): Ignoring empty id from " + controller);
-            return;
-        }
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                // Note: If a developer calls notifyChildrenChanged inside, onLoadChildren will be
-                // called twice for a single subscription event.
-                // TODO(post 1.0): Fix the issue above.
-                if (!getConnectedControllersManager().isAllowedCommand(controller,
-                        SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.subscribe() from " + controller + " was rejected"
-                                + " by " + mLibrarySessionImpl);
-                    }
-                    return;
-                }
-                LibraryParams params = MediaUtils.convertToLibraryParams(
-                        mLibrarySessionImpl.getContext(), option);
-                mLibrarySessionImpl.onSubscribeOnExecutor(controller, id, params);
-            }
-        });
-    }
-
-    @Override
-    public void onUnsubscribe(final String id) {
-        final ControllerInfo controller = getCurrentController();
-        if (TextUtils.isEmpty(id)) {
-            Log.w(TAG, "onUnsubscribe(): Ignoring empty id from " + controller);
-            return;
-        }
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (!getConnectedControllersManager().isAllowedCommand(controller,
-                        SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.unsubscribe() from " + controller + " was rejected"
-                                + " by " + mLibrarySessionImpl);
-                    }
-                    return;
-                }
-                mLibrarySessionImpl.onUnsubscribeOnExecutor(controller, id);
-            }
-        });
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaBrowserCompat.MediaItem>> result) {
-        onLoadChildren(parentId, result, null);
-    }
-
-    @Override
-    public void onLoadChildren(final String parentId,
-            final Result<List<MediaBrowserCompat.MediaItem>> result, final Bundle options) {
-        final ControllerInfo controller = getCurrentController();
-        if (TextUtils.isEmpty(parentId)) {
-            Log.w(TAG, "onLoadChildren(): Ignoring empty parentId from " + controller);
-            result.sendError(null);
-            return;
-        }
-        result.detach();
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (!getConnectedControllersManager().isAllowedCommand(controller,
-                        SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.subscribe() from " + controller + " was rejected"
-                                + " by " + mLibrarySessionImpl);
-                    }
-                    result.sendError(null);
-                    return;
-                }
-                if (options != null) {
-                    options.setClassLoader(mLibrarySessionImpl.getContext().getClassLoader());
-                    try {
-                        int page = options.getInt(EXTRA_PAGE);
-                        int pageSize = options.getInt(EXTRA_PAGE_SIZE);
-                        if (page > 0 && pageSize > 0) {
-                            // Requesting the list of children through pagination.
-                            LibraryParams params = MediaUtils.convertToLibraryParams(
-                                    mLibrarySessionImpl.getContext(), options);
-                            LibraryResult libraryResult = mLibrarySessionImpl.getCallback()
-                                    .onGetChildren(mLibrarySessionImpl.getInstance(), controller,
-                                            parentId, page, pageSize, params);
-                            if (libraryResult == null
-                                    || libraryResult.getResultCode() != RESULT_SUCCESS) {
-                                result.sendResult(null);
-                            } else {
-                                result.sendResult(MediaUtils.truncateListBySize(
-                                        MediaUtils.convertToMediaItemList(
-                                                libraryResult.getMediaItems()),
-                                        TRANSACTION_SIZE_LIMIT_IN_BYTES));
-                            }
-                            return;
-                        }
-                        // Cannot distinguish onLoadChildren() why it's called either by
-                        // {@link MediaBrowserCompat#subscribe()} or
-                        // {@link MediaBrowserServiceCompat#notifyChildrenChanged}.
-                    } catch (BadParcelableException e) {
-                        // pass-through.
-                    }
-                }
-                // A MediaBrowserCompat called loadChildren with no pagination option.
-                LibraryResult libraryResult = mLibrarySessionImpl.getCallback()
-                        .onGetChildren(mLibrarySessionImpl.getInstance(), controller, parentId,
-                                0 /* page */, Integer.MAX_VALUE /* pageSize*/,
-                                null /* extras */);
-                if (libraryResult == null
-                        || libraryResult.getResultCode() != RESULT_SUCCESS) {
-                    result.sendResult(null);
-                } else {
-                    result.sendResult(MediaUtils.truncateListBySize(
-                            MediaUtils.convertToMediaItemList(libraryResult.getMediaItems()),
-                            TRANSACTION_SIZE_LIMIT_IN_BYTES));
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onLoadItem(final String itemId, final Result<MediaBrowserCompat.MediaItem> result) {
-        final ControllerInfo controller = getCurrentController();
-        if (TextUtils.isEmpty(itemId)) {
-            Log.w(TAG, "Ignoring empty itemId from " + controller);
-            result.sendError(null);
-            return;
-        }
-        result.detach();
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (!getConnectedControllersManager().isAllowedCommand(controller,
-                        SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.getItem() from " + controller + " was rejected by "
-                                + mLibrarySessionImpl);
-                    }
-                    result.sendError(null);
-                    return;
-                }
-                LibraryResult libraryResult = mLibrarySessionImpl.getCallback().onGetItem(
-                        mLibrarySessionImpl.getInstance(), controller, itemId);
-                if (libraryResult == null || libraryResult.getResultCode() != RESULT_SUCCESS) {
-                    result.sendResult(null);
-                } else {
-                    result.sendResult(MediaUtils.convertToMediaItem(libraryResult.getMediaItem()));
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onSearch(final String query, final Bundle extras,
-            final Result<List<MediaBrowserCompat.MediaItem>> result) {
-        final ControllerInfo controller = getCurrentController();
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "Ignoring empty query from " + controller);
-            result.sendError(null);
-            return;
-        }
-        if (!(controller.getControllerCb() instanceof BrowserLegacyCb)) {
-            if (DEBUG) {
-                throw new IllegalStateException("Callback hasn't registered. Must be a bug");
-            }
-            return;
-        }
-        result.detach();
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (!getConnectedControllersManager().isAllowedCommand(controller,
-                        SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.search() from " + controller + " was rejected by "
-                                + mLibrarySessionImpl);
-                    }
-                    result.sendError(null);
-                    return;
-                }
-                BrowserLegacyCb cb = (BrowserLegacyCb) controller.getControllerCb();
-                cb.registerSearchRequest(controller, query, extras, result);
-                LibraryParams params = MediaUtils.convertToLibraryParams(
-                        mLibrarySessionImpl.getContext(), extras);
-                mLibrarySessionImpl.getCallback().onSearch(mLibrarySessionImpl.getInstance(),
-                        controller, query, params);
-                // Actual search result will be sent by notifySearchResultChanged().
-            }
-        });
-    }
-
-    @Override
-    public void onCustomAction(final String action, final Bundle extras,
-            final Result<Bundle> result) {
-        if (result != null) {
-            result.detach();
-        }
-        final ControllerInfo controller = getCurrentController();
-        mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            @SuppressWarnings("ObjectToString")
-            public void run() {
-                SessionCommand command = new SessionCommand(action, null);
-                if (!getConnectedControllersManager().isAllowedCommand(controller, command)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Command MBC.sendCustomAction(" + command + ") from "
-                                + controller + " was rejected by " + mLibrarySessionImpl);
-                    }
-                    if (result != null) {
-                        result.sendError(null);
-                    }
-                    return;
-                }
-                SessionResult sessionResult = mLibrarySessionImpl.getCallback().onCustomCommand(
-                        mLibrarySessionImpl.getInstance(), controller, command, extras);
-                if (sessionResult != null) {
-                    result.sendResult(sessionResult.getCustomCommandResult());
-                }
-            }
-        });
-    }
-
-    @Override
-    ControllerInfo createControllerInfo(RemoteUserInfo remoteUserInfo) {
-        return new ControllerInfo(remoteUserInfo, MediaUtils.VERSION_UNKNOWN,
-                mManager.isTrustedForMediaControl(remoteUserInfo),
-                new BrowserLegacyCb(remoteUserInfo), null /* connectionHints */);
-    }
-
-    ControllerCb getBrowserLegacyCbForBroadcast() {
-        return mBrowserLegacyCbForBroadcast;
-    }
-
-    private ControllerInfo getCurrentController() {
-        return getConnectedControllersManager().getController(getCurrentBrowserInfo());
-    }
-
-    private static class SearchRequest {
-        public final ControllerInfo mController;
-        public final RemoteUserInfo mRemoteUserInfo;
-        public final String mQuery;
-        public final Bundle mExtras;
-        public final Result<List<MediaBrowserCompat.MediaItem>> mResult;
-
-        SearchRequest(ControllerInfo controller, RemoteUserInfo remoteUserInfo, String query,
-                Bundle extras, Result<List<MediaBrowserCompat.MediaItem>> result) {
-            mController = controller;
-            mRemoteUserInfo = remoteUserInfo;
-            mQuery = query;
-            mExtras = extras;
-            mResult = result;
-        }
-    }
-
-    // Base class for MediaBrowserCompat's ControllerCb.
-    // This documents
-    //   1) Why some APIs does nothing
-    //   2) Why some APIs should throw exception when DEBUG is {@code true}.
-    private abstract static class BaseBrowserLegacyCb extends MediaSession.ControllerCb {
-        @Override
-        void onPlayerResult(int seq, PlayerResult result) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Session features.
-        }
-
-        @Override
-        void onSessionResult(int seq, SessionResult result) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Session features.
-        }
-
-        @Override
-        void onLibraryResult(int seq, LibraryResult result) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Browser features.
-        }
-
-        @Override
-        void onPlayerChanged(int seq,
-                @Nullable SessionPlayer oldPlayer,
-                @Nullable MediaController.PlaybackInfo oldPlaybackInfo,
-                @NonNull SessionPlayer player,
-                @NonNull MediaController.PlaybackInfo playbackInfo)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void setCustomLayout(int seq, @NonNull List<CommandButton> layout)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlaybackInfoChanged(int seq, @NonNull PlaybackInfo info)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onAllowedCommandsChanged(int seq, @NonNull SessionCommandGroup commands)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void sendCustomCommand(int seq, @NonNull SessionCommand command, Bundle args)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int playerState)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onBufferingStateChanged(int seq, @NonNull MediaItem item, int bufferingState,
-                long bufferedPositionMs, long eventTimeMs, long positionMs) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long position)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onCurrentMediaItemChanged(int seq, MediaItem item, int currentIdx,
-                int previousIdx, int nextIdx) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlaylistChanged(int seq, @NonNull List<MediaItem> playlist,
-                MediaMetadata metadata, int currentIdx, int previousIdx, int nextIdx)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlaylistMetadataChanged(int seq, MediaMetadata metadata)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onPlaybackCompleted(int seq) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onDisconnected(int seq) throws RemoteException {
-            // No-op. BrowserCompat doesn't have concept of receiving release of a session.
-        }
-
-        @Override
-        void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        void onTracksChanged(int seq, List<TrackInfo> tracks,
-                TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-                TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onTrackSelected(int seq, TrackInfo trackInfo)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onTrackDeselected(int seq, TrackInfo trackInfo)
-                throws RemoteException {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-
-        @Override
-        final void onSubtitleData(int seq, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) {
-            // No-op. BrowserCompat doesn't understand Controller features.
-        }
-    }
-
-    private final class BrowserLegacyCb extends BaseBrowserLegacyCb {
-        private final Object mLock = new Object();
-        private final RemoteUserInfo mRemoteUserInfo;
-
-        @GuardedBy("mLock")
-        private final List<SearchRequest> mSearchRequests = new ArrayList<>();
-
-        BrowserLegacyCb(RemoteUserInfo remoteUserInfo) {
-            mRemoteUserInfo = remoteUserInfo;
-        }
-
-        @Override
-        void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                LibraryParams params) throws RemoteException {
-            Bundle extras = params != null ? params.getExtras() : null;
-            notifyChildrenChanged(mRemoteUserInfo, parentId, extras);
-        }
-
-        @Override
-        @SuppressWarnings("ObjectToString")
-        void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // In MediaLibrarySession/MediaBrowser, we have two different APIs for getting size of
-            // search result (and also starting search) and getting result.
-            // However, MediaBrowserService/MediaBrowserCompat only have one search API for getting
-            // search result.
-            final List<SearchRequest> searchRequests = new ArrayList<>();
-            synchronized (mLock) {
-                for (int i = mSearchRequests.size() - 1; i >= 0; i--) {
-                    SearchRequest iter = mSearchRequests.get(i);
-                    if (ObjectsCompat.equals(mRemoteUserInfo, iter.mRemoteUserInfo)
-                            && iter.mQuery.equals(query)) {
-                        searchRequests.add(iter);
-                        mSearchRequests.remove(i);
-                    }
-                }
-                if (searchRequests.size() == 0) {
-                    if (DEBUG) {
-                        Log.d(TAG, "search() hasn't called by " + mRemoteUserInfo
-                                + " with query=" + query);
-                    }
-                    return;
-                }
-            }
-
-            mLibrarySessionImpl.getCallbackExecutor().execute(new Runnable() {
-                @Override
-                public void run() {
-                    for (int i = 0; i < searchRequests.size(); i++) {
-                        SearchRequest request = searchRequests.get(i);
-                        int page = 0;
-                        int pageSize = Integer.MAX_VALUE;
-                        if (request.mExtras != null) {
-                            try {
-                                request.mExtras.setClassLoader(
-                                        mLibrarySessionImpl.getContext().getClassLoader());
-                                page = request.mExtras.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
-                                pageSize = request.mExtras
-                                        .getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
-                            } catch (BadParcelableException e) {
-                                request.mResult.sendResult(null);
-                                return;
-                            }
-                        }
-                        if (page < 0 || pageSize < 1) {
-                            page = 0;
-                            pageSize = Integer.MAX_VALUE;
-                        }
-                        LibraryParams params = MediaUtils.convertToLibraryParams(
-                                mLibrarySessionImpl.getContext(), request.mExtras);
-                        LibraryResult libraryResult  = mLibrarySessionImpl.getCallback()
-                                .onGetSearchResult(mLibrarySessionImpl.getInstance(),
-                                        request.mController, request.mQuery, page, pageSize,
-                                        params);
-                        if (libraryResult == null
-                                || libraryResult.getResultCode() != RESULT_SUCCESS) {
-                            request.mResult.sendResult(null);
-                        } else {
-                            request.mResult.sendResult(
-                                    MediaUtils.truncateListBySize(
-                                            MediaUtils.convertToMediaItemList(
-                                                    libraryResult.getMediaItems()),
-                                    TRANSACTION_SIZE_LIMIT_IN_BYTES));
-                        }
-                    }
-                }
-            });
-        }
-
-        void registerSearchRequest(ControllerInfo controller, String query, Bundle extras,
-                Result<List<MediaBrowserCompat.MediaItem>> result) {
-            synchronized (mLock) {
-                mSearchRequests.add(new SearchRequest(controller, controller.getRemoteUserInfo(),
-                        query, extras, result));
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return ObjectsCompat.hash(mRemoteUserInfo);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (!(obj instanceof BrowserLegacyCb)) {
-                return false;
-            }
-            BrowserLegacyCb other = (BrowserLegacyCb) obj;
-            return ObjectsCompat.equals(mRemoteUserInfo, other.mRemoteUserInfo);
-        }
-    }
-
-    // Intentionally static class to prevent lint warning 'SyntheticAccessor' in constructor.
-    private static class BrowserLegacyCbForBroadcast extends BaseBrowserLegacyCb {
-        private final MediaBrowserServiceCompat mService;
-
-        BrowserLegacyCbForBroadcast(MediaBrowserServiceCompat service) {
-            mService = service;
-        }
-
-        @Override
-        void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                LibraryParams libraryParams) throws RemoteException {
-            // This will trigger {@link MediaLibraryServiceLegacyStub#onLoadChildren}.
-            if (libraryParams == null || libraryParams.getExtras() == null) {
-                mService.notifyChildrenChanged(parentId);
-            } else {
-                mService.notifyChildrenChanged(parentId, libraryParams.getExtras());
-            }
-        }
-
-        @Override
-        void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // Shouldn't be called. If it's called, it's bug.
-            // This method in the base class is introduced to internally send return of
-            // {@link MediaLibrarySessionCallback#onSearchResultChanged}. However, for
-            // BrowserCompat, it should be done by {@link Result#sendResult} from
-            // {@link MediaLibraryServiceLegacyStub#onSearch} instead.
-            if (DEBUG) {
-                throw new RuntimeException("Unexpected API call. Use result.sendResult() for"
-                        + " sending onSearchResultChanged() result instead of this");
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibrarySessionImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibrarySessionImplBase.java
deleted file mode 100644
index 56b0e3e..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibrarySessionImplBase.java
+++ /dev/null
@@ -1,354 +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.media2.session;
-
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.support.v4.media.session.MediaSessionCompat.Token;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.collection.ArrayMap;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession;
-import androidx.media2.session.MediaSession.ControllerCb;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-class MediaLibrarySessionImplBase extends MediaSessionImplBase implements
-        MediaLibrarySession.MediaLibrarySessionImpl {
-    private final boolean mThrowsWhenInvalidReturn;
-    @GuardedBy("mLock")
-    private final ArrayMap<ControllerCb, Set<String>> mSubscriptions = new ArrayMap<>();
-
-    MediaLibrarySessionImplBase(MediaSession instance, Context context, String id,
-            SessionPlayer player, PendingIntent sessionActivity, Executor callbackExecutor,
-            MediaSession.SessionCallback callback, Bundle tokenExtras,
-            boolean throwsWhenInvalidReturn) {
-        super(instance, context, id, player, sessionActivity, callbackExecutor, callback,
-                tokenExtras);
-        mThrowsWhenInvalidReturn = throwsWhenInvalidReturn;
-    }
-
-    @Override
-    MediaBrowserServiceCompat createLegacyBrowserServiceLocked(Context context, SessionToken token,
-            Token sessionToken) {
-        return new MediaLibraryServiceLegacyStub(context, this, sessionToken);
-    }
-
-    @Override
-    @NonNull
-    public MediaLibrarySession getInstance() {
-        return (MediaLibrarySession) super.getInstance();
-    }
-
-    @Override
-    public MediaLibrarySession.MediaLibrarySessionCallback getCallback() {
-        return (MediaLibrarySession.MediaLibrarySessionCallback) super.getCallback();
-    }
-
-    @Override
-    MediaLibraryServiceLegacyStub getLegacyBrowserService() {
-        return (MediaLibraryServiceLegacyStub) super.getLegacyBrowserService();
-    }
-
-    @Override
-    @NonNull
-    public List<ControllerInfo> getConnectedControllers() {
-        List<ControllerInfo> list = super.getConnectedControllers();
-        MediaLibraryServiceLegacyStub legacyStub = getLegacyBrowserService();
-        if (legacyStub != null) {
-            list.addAll(legacyStub.getConnectedControllersManager()
-                    .getConnectedControllers());
-        }
-        return list;
-    }
-
-    @Override
-    public boolean isConnected(@NonNull ControllerInfo controller) {
-        if (super.isConnected(controller)) {
-            return true;
-        }
-        MediaLibraryServiceLegacyStub legacyStub = getLegacyBrowserService();
-        return legacyStub != null
-                ? legacyStub.getConnectedControllersManager().isConnected(controller) : false;
-    }
-
-    @Override
-    public void notifyChildrenChanged(@NonNull final String parentId, final int itemCount,
-            final LibraryParams params) {
-        dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                if (isSubscribed(callback, parentId)) {
-                    callback.onChildrenChanged(seq, parentId, itemCount, params);
-                }
-            }
-        });
-    }
-
-    @Override
-    public void notifyChildrenChanged(@NonNull final ControllerInfo controller,
-            @NonNull final String parentId, final int itemCount, final LibraryParams params) {
-        dispatchRemoteControllerTaskWithoutReturn(controller, new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                if (!isSubscribed(callback, parentId)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Skipping notifyChildrenChanged() to " + controller
-                                + " because it hasn't subscribed");
-                        dumpSubscription();
-                    }
-                    return;
-                }
-                callback.onChildrenChanged(seq, parentId, itemCount, params);
-            }
-        });
-    }
-
-    @Override
-    public void notifySearchResultChanged(@NonNull ControllerInfo controller,
-            @NonNull final String query, final int itemCount, final LibraryParams params) {
-        dispatchRemoteControllerTaskWithoutReturn(controller, new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                callback.onSearchResultChanged(seq, query, itemCount, params);
-            }
-        });
-    }
-
-    private LibraryResult ensureNonNullResult(LibraryResult returnedResult) {
-        if (returnedResult == null) {
-            handleError("LibraryResult shouldn't be null");
-            returnedResult = new LibraryResult(RESULT_ERROR_UNKNOWN);
-        }
-        return returnedResult;
-    }
-
-    private LibraryResult ensureNonNullResultWithValidList(LibraryResult returnedResult,
-            int pageSize) {
-        returnedResult = ensureNonNullResult(returnedResult);
-        if (returnedResult.getResultCode() == RESULT_SUCCESS) {
-            List<MediaItem> items = returnedResult.getMediaItems();
-
-            if (items == null) {
-                handleError("List shouldn't be null for the success");
-                return new LibraryResult(RESULT_ERROR_UNKNOWN);
-            }
-            if (items.size() > pageSize) {
-                handleError("List shouldn't contain items more than pageSize"
-                        + ", size=" + returnedResult.getMediaItems().size()
-                        + ", pageSize" + pageSize);
-                return new LibraryResult(RESULT_ERROR_UNKNOWN);
-            }
-            for (MediaItem item : items) {
-                if (!isValidItem(item)) {
-                    return new LibraryResult(RESULT_ERROR_UNKNOWN);
-                }
-            }
-        }
-        return returnedResult;
-    }
-
-    private LibraryResult ensureNonNullResultWithValidItem(LibraryResult returnedResult) {
-        returnedResult = ensureNonNullResult(returnedResult);
-        if (returnedResult.getResultCode() == RESULT_SUCCESS) {
-            if (!isValidItem(returnedResult.getMediaItem())) {
-                return new LibraryResult(RESULT_ERROR_UNKNOWN);
-            }
-        }
-        return returnedResult;
-    }
-
-    private boolean isValidItem(MediaItem item) {
-        if (item == null) {
-            handleError("Item shouldn't be null for the success");
-            return false;
-        }
-        if (TextUtils.isEmpty(item.getMediaId())) {
-            handleError(
-                    "Media ID of an item shouldn't be empty for the success");
-            return false;
-        }
-        MediaMetadata metadata = item.getMetadata();
-        if (metadata == null) {
-            handleError(
-                    "Metadata of an item shouldn't be null for the success");
-            return false;
-        }
-        if (!metadata.containsKey(MediaMetadata.METADATA_KEY_BROWSABLE)) {
-            handleError(
-                    "METADATA_KEY_BROWSABLE should be specified in metadata of an item");
-            return false;
-        }
-        if (!metadata.containsKey(MediaMetadata.METADATA_KEY_PLAYABLE)) {
-            handleError(
-                    "METADATA_KEY_PLAYABLE should be specified in metadata of an item");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Called by {@link MediaSessionStub#getLibraryRoot(IMediaController, int, ParcelImpl)}.
-     *
-     * @param controller
-     * @param params
-     */
-    @Override
-    public LibraryResult onGetLibraryRootOnExecutor(@NonNull ControllerInfo controller,
-            final LibraryParams params) {
-        LibraryResult result = getCallback().onGetLibraryRoot(getInstance(), controller, params);
-        return ensureNonNullResultWithValidItem(result);
-    }
-
-    /**
-     * Called by {@link MediaSessionStub#getItem(IMediaController, int, String)}.
-     *
-     * @param controller
-     * @param mediaId
-     */
-    @Override
-    public LibraryResult onGetItemOnExecutor(@NonNull ControllerInfo controller,
-            @NonNull final String mediaId) {
-        LibraryResult result = getCallback().onGetItem(getInstance(), controller, mediaId);
-        return ensureNonNullResultWithValidItem(result);
-    }
-
-    @Override
-    public LibraryResult onGetChildrenOnExecutor(@NonNull ControllerInfo controller,
-            @NonNull final String parentId, final int page, final int pageSize,
-            final LibraryParams params) {
-        LibraryResult result = getCallback().onGetChildren(getInstance(),
-                controller, parentId, page, pageSize, params);
-        return ensureNonNullResultWithValidList(result, pageSize);
-    }
-
-    @Override
-    public int onSubscribeOnExecutor(@NonNull ControllerInfo controller, @NonNull String parentId,
-            LibraryParams params) {
-        synchronized (mLock) {
-            Set<String> subscription = mSubscriptions.get(controller.getControllerCb());
-            if (subscription == null) {
-                subscription = new HashSet<>();
-                mSubscriptions.put(controller.getControllerCb(), subscription);
-            }
-            subscription.add(parentId);
-        }
-        // Call callbacks after adding it to the subscription list because library session may want
-        // to call notifyChildrenChanged() in the callback.
-        int resultCode = getCallback().onSubscribe(getInstance(), controller, parentId, params);
-
-        // When error happens, remove from the subscription list.
-        if (resultCode != RESULT_SUCCESS) {
-            synchronized (mLock) {
-                mSubscriptions.remove(controller.getControllerCb());
-            }
-        }
-        return resultCode;
-    }
-
-    @Override
-    public int onUnsubscribeOnExecutor(@NonNull ControllerInfo controller,
-            @NonNull String parentId) {
-        int resultCode = getCallback().onUnsubscribe(getInstance(), controller, parentId);
-        synchronized (mLock) {
-            mSubscriptions.remove(controller.getControllerCb());
-        }
-        return resultCode;
-    }
-
-    @Override
-    public int onSearchOnExecutor(@NonNull ControllerInfo controller, @NonNull String query,
-            LibraryParams params) {
-        return getCallback().onSearch(getInstance(), controller, query, params);
-    }
-
-    @Override
-    public LibraryResult onGetSearchResultOnExecutor(@NonNull ControllerInfo controller,
-            @NonNull final String query, final int page, final int pageSize,
-            final LibraryParams params) {
-        LibraryResult result = getCallback().onGetSearchResult(getInstance(),
-                controller, query, page, pageSize, params);
-        return ensureNonNullResultWithValidList(result, pageSize);
-    }
-
-    @Override
-    void dispatchRemoteControllerTaskWithoutReturn(@NonNull RemoteControllerTask task) {
-        super.dispatchRemoteControllerTaskWithoutReturn(task);
-        MediaLibraryServiceLegacyStub legacyStub = getLegacyBrowserService();
-        if (legacyStub != null) {
-            try {
-                task.run(legacyStub.getBrowserLegacyCbForBroadcast(), /* seq= */ 0);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Exception in using media1 API", e);
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean isSubscribed(ControllerCb callback, String parentId) {
-        synchronized (mLock) {
-            Set<String> subscriptions = mSubscriptions.get(callback);
-            if (subscriptions == null || !subscriptions.contains(parentId)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    // Debug only
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void dumpSubscription() {
-        if (!DEBUG) {
-            return;
-        }
-        synchronized (mLock) {
-            Log.d(TAG, "Dumping subscription, controller sz=" + mSubscriptions.size());
-            for (int i = 0; i < mSubscriptions.size(); i++) {
-                Log.d(TAG, "  controller " + mSubscriptions.valueAt(i));
-                for (String parentId : mSubscriptions.valueAt(i)) {
-                    Log.d(TAG, "  - " + parentId);
-                }
-            }
-        }
-    }
-
-    private void handleError(@NonNull String message) {
-        if (mThrowsWhenInvalidReturn) {
-            throw new RuntimeException(message);
-        } else {
-            Log.e(TAG, message);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaNotificationHandler.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaNotificationHandler.java
deleted file mode 100644
index 4246a84..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaNotificationHandler.java
+++ /dev/null
@@ -1,276 +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.media2.session;
-
-import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
-import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
-import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
-import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
-import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Parcelable;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.view.KeyEvent;
-
-import androidx.core.app.NotificationChannelCompat;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
-import androidx.core.content.ContextCompat;
-import androidx.media.app.NotificationCompat.MediaStyle;
-import androidx.media2.common.ClassVerificationHelper;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-
-import java.util.List;
-
-/**
- * Provides default media notification for {@link MediaSessionService}, and set the service as
- * foreground/background according to the player state.
- */
-/* package */ class MediaNotificationHandler extends
-        MediaSession.SessionCallback.ForegroundServiceEventCallback {
-    private static final int NOTIFICATION_ID = 1001;
-    private static final String NOTIFICATION_CHANNEL_ID = "default_channel_id";
-
-    private final MediaSessionService mServiceInstance;
-    private final NotificationManagerCompat mNotificationManagerCompat;
-    private final String mNotificationChannelName;
-
-    private final Intent mStartSelfIntent;
-    private final NotificationCompat.Action mPlayAction;
-    private final NotificationCompat.Action mPauseAction;
-    private final NotificationCompat.Action mSkipToPrevAction;
-    private final NotificationCompat.Action mSkipToNextAction;
-
-    MediaNotificationHandler(MediaSessionService service) {
-        mServiceInstance = service;
-        mStartSelfIntent = new Intent(mServiceInstance, mServiceInstance.getClass());
-
-        mNotificationManagerCompat = NotificationManagerCompat.from(mServiceInstance);
-        mNotificationChannelName = mServiceInstance.getResources().getString(
-                R.string.default_notification_channel_name);
-
-        mPlayAction = createNotificationAction(
-                R.drawable.media_session_service_notification_ic_play,
-                R.string.play_button_content_description, ACTION_PLAY);
-        mPauseAction = createNotificationAction(
-                R.drawable.media_session_service_notification_ic_pause,
-                R.string.pause_button_content_description, ACTION_PAUSE);
-        mSkipToPrevAction = createNotificationAction(
-                R.drawable.media_session_service_notification_ic_skip_to_previous,
-                R.string.skip_to_previous_item_button_content_description, ACTION_SKIP_TO_PREVIOUS);
-        mSkipToNextAction = createNotificationAction(
-                R.drawable.media_session_service_notification_ic_skip_to_next,
-                R.string.skip_to_next_item_button_content_description, ACTION_SKIP_TO_NEXT);
-    }
-
-    /**
-     * Sets the service as foreground/background according to the player state.
-     * This will be called when the player state is changed.
-     *
-     * @param state player state
-     */
-    @Override
-    public void onPlayerStateChanged(MediaSession session,
-            @SessionPlayer.PlayerState int state) {
-        MediaSessionService.MediaNotification mediaNotification =
-                mServiceInstance.onUpdateNotification(session);
-        if (mediaNotification == null) {
-            // The service implementation doesn't want to use the automatic start/stopForeground
-            // feature.
-            return;
-        }
-
-        int id = mediaNotification.getNotificationId();
-        Notification notification = mediaNotification.getNotification();
-
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Call Notification.MediaStyle#setMediaSession() indirectly.
-            Parcelable fwkToken = (Parcelable)
-                            session.getSessionCompat().getSessionToken().getToken();
-            notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
-        }
-
-        if (isPlaybackStopped(state)) {
-            stopForegroundServiceIfNeeded();
-            mNotificationManagerCompat.notify(id, notification);
-            return;
-        }
-
-        // state == SessionPlayer.PLAYER_STATE_PLAYING
-        ContextCompat.startForegroundService(mServiceInstance, mStartSelfIntent);
-        mServiceInstance.startForeground(id, notification);
-    }
-
-    /**
-     * Updates the notification when needed.
-     * This will be called when the current media item is changed.
-     */
-    @Override
-    public void onNotificationUpdateNeeded(MediaSession session) {
-        MediaSessionService.MediaNotification mediaNotification =
-                mServiceInstance.onUpdateNotification(session);
-        if (mediaNotification == null) {
-            // The service implementation doesn't want to use the automatic start/stopForeground
-            // feature.
-            return;
-        }
-
-        int id = mediaNotification.getNotificationId();
-        Notification notification = mediaNotification.getNotification();
-
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Call Notification.MediaStyle#setMediaSession() indirectly.
-            Parcelable fwkToken =
-                    (Parcelable) session.getSessionCompat().getSessionToken().getToken();
-            notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
-        }
-
-        mNotificationManagerCompat.notify(id, notification);
-    }
-
-    @Override
-    public void onSessionClosed(MediaSession session) {
-        mServiceInstance.removeSession(session);
-        stopForegroundServiceIfNeeded();
-    }
-
-    @SuppressWarnings("deprecation")
-    private void stopForegroundServiceIfNeeded() {
-        List<MediaSession> sessions = mServiceInstance.getSessions();
-        for (int i = 0; i < sessions.size(); i++) {
-            if (!isPlaybackStopped(sessions.get(i).getPlayer().getPlayerState())) {
-                return;
-            }
-        }
-        // Calling stopForeground(true) is a workaround for pre-L devices which prevents
-        // the media notification from being undismissable.
-        boolean shouldRemoveNotification = Build.VERSION.SDK_INT < 21;
-        mServiceInstance.stopForeground(shouldRemoveNotification);
-    }
-
-    /**
-     * Creates a default media style notification for {@link MediaSessionService}.
-     */
-    public MediaSessionService.MediaNotification onUpdateNotification(MediaSession session) {
-        ensureNotificationChannel();
-
-        NotificationCompat.Builder builder = new NotificationCompat.Builder(
-                mServiceInstance, NOTIFICATION_CHANNEL_ID);
-
-        // TODO: Filter actions when SessionPlayer#getSupportedActions() is introduced.
-        builder.addAction(mSkipToPrevAction);
-        if (session.getPlayer().getPlayerState() == SessionPlayer.PLAYER_STATE_PLAYING) {
-            builder.addAction(mPauseAction);
-        } else {
-            builder.addAction(mPlayAction);
-        }
-        builder.addAction(mSkipToNextAction);
-
-        // Set metadata info in the notification.
-        if (session.getPlayer().getCurrentMediaItem() != null) {
-            MediaMetadata metadata = session.getPlayer().getCurrentMediaItem().getMetadata();
-            if (metadata != null) {
-                CharSequence title = metadata.getText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
-                if (title == null) {
-                    title = metadata.getText(MediaMetadata.METADATA_KEY_TITLE);
-                }
-                builder.setContentTitle(title)
-                        .setContentText(metadata.getText(MediaMetadata.METADATA_KEY_ARTIST))
-                        .setLargeIcon(metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART));
-            }
-        }
-
-        MediaStyle mediaStyle = new MediaStyle()
-                .setCancelButtonIntent(createPendingIntent(ACTION_STOP))
-                .setMediaSession(session.getSessionCompat().getSessionToken())
-                .setShowActionsInCompactView(1 /* Show play/pause button only in compact view */);
-
-        Notification notification = builder
-                .setContentIntent(session.getImpl().getSessionActivity())
-                .setDeleteIntent(createPendingIntent(ACTION_STOP))
-                .setOnlyAlertOnce(true)
-                .setSmallIcon(getSmallIconResId())
-                .setStyle(mediaStyle)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setOngoing(false)
-                .build();
-
-        return new MediaSessionService.MediaNotification(NOTIFICATION_ID, notification);
-    }
-
-    private NotificationCompat.Action createNotificationAction(int iconResId, int titleResId,
-            @PlaybackStateCompat.Actions long action) {
-        CharSequence title = mServiceInstance.getResources().getText(titleResId);
-        return new NotificationCompat.Action(iconResId, title, createPendingIntent(action));
-    }
-
-    private PendingIntent createPendingIntent(@PlaybackStateCompat.Actions long action) {
-        int keyCode = PlaybackStateCompat.toKeyCode(action);
-        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-        intent.setComponent(new ComponentName(mServiceInstance, mServiceInstance.getClass()));
-        intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-
-        if (Build.VERSION.SDK_INT >= 26 && action != ACTION_PAUSE && action != ACTION_STOP) {
-            return ClassVerificationHelper.PendingIntent.Api26.getForegroundService(
-                    mServiceInstance, keyCode /* requestCode */, intent,
-                    PendingIntent.FLAG_IMMUTABLE);
-        } else {
-            return PendingIntent.getService(
-                    mServiceInstance, keyCode /* requestCode */, intent,
-                    Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-        }
-    }
-
-    private void ensureNotificationChannel() {
-        if (Build.VERSION.SDK_INT < 26) {
-            return;
-        }
-        if (mNotificationManagerCompat.getNotificationChannel(NOTIFICATION_CHANNEL_ID) != null) {
-            return;
-        }
-        // Need to create a notification channel.
-        NotificationChannelCompat channelCompat =
-                new NotificationChannelCompat.Builder(
-                        NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
-                        .setName(mNotificationChannelName)
-                        .build();
-        mNotificationManagerCompat.createNotificationChannel(channelCompat);
-    }
-
-    private int getSmallIconResId() {
-        int appIcon = mServiceInstance.getApplicationInfo().icon;
-        if (appIcon != 0) {
-            return appIcon;
-        } else {
-            // App icon is not set.
-            return R.drawable.media_session_service_notification_ic_music_note;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static boolean isPlaybackStopped(int state) {
-        return state == SessionPlayer.PLAYER_STATE_PAUSED
-                || state == SessionPlayer.PLAYER_STATE_IDLE
-                || state == SessionPlayer.PLAYER_STATE_ERROR;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
deleted file mode 100644
index 14dbbc0..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
+++ /dev/null
@@ -1,1435 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.view.KeyEvent;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.content.ContextCompat;
-import androidx.core.util.ObjectsCompat;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.BuffState;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.PlayerState;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionResult.ResultCode;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.Closeable;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Allows a media app to expose its transport controls and playback information in a process to
- * other processes including the Android framework and other apps. Common use cases are as follows.
- *
- * <ul>
- *   <li>Bluetooth/wired headset key events support
- *   <li>Android Auto/Wearable support
- *   <li>Separating UI process and playback process
- * </ul>
- *
- * <p>A MediaSession should be created when an app wants to publish media playback information or
- * handle media keys. In general an app only needs one session for all playback, though multiple
- * sessions can be created to provide finer grain controls of media. See <a
- * href="#MultipleSessions">Supporting Multiple Sessions</a> for detail.
- *
- * <p>If you want to support background playback, {@link MediaSessionService} is preferred instead.
- * With it, your playback can be revived even after playback is finished. See {@link
- * MediaSessionService} for details.
- *
- * <p>Topics covered here:
- *
- * <ol>
- *   <li><a href="#SessionLifecycle">Session Lifecycle</a>
- *   <li><a href="#Thread">Thread</a>
- *   <li><a href="#KeyEvents">Media key events mapping</a>
- *   <li><a href="#MultipleSessions">Supporting Multiple Sessions</a>
- *   <li><a href="#CompatibilitySession">Backward compatibility with legacy session APIs</a>
- *   <li><a href="#CompatibilityController">Backward compatibility with legacy controller APIs</a>
- * </ol>
- *
- * <h3 id="SessionLifecycle">Session Lifecycle</h3>
- *
- * <p>A session can be obtained by {@link Builder}. The owner of the session may pass its session
- * token to other processes to allow them to create a {@link MediaController} to interact with the
- * session.
- *
- * <p>When a session receive transport control commands, the session sends the commands directly to
- * the underlying media player set by {@link Builder} or {@link #updatePlayer}.
- *
- * <p>When an app is finished performing playback it must call {@link #close()} to clean up the
- * session and notify any controllers. The app is responsible for closing the underlying player
- * after closing the session. is closed.
- *
- * <h3 id="Thread">Thread</h3>
- *
- * <p>{@link MediaSession} objects are thread safe, but should be used on the thread on the looper.
- *
- * <h3 id="KeyEvents">Media key events mapping</h3>
- *
- * <p>Here's the table of per key event.
- *
- * <table>
- * <tr><th>Key code</th><th>{@link MediaSession} API</th></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_PLAY}</td>
- *     <td>{@link SessionPlayer#play()}</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_PAUSE}</td>
- *     <td>{@link SessionPlayer#pause()}</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_NEXT}</td>
- *     <td>{@link SessionPlayer#skipToNextPlaylistItem()}</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}</td>
- *     <td>{@link SessionPlayer#skipToPreviousPlaylistItem()}</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_STOP}</td>
- *     <td>{@link SessionPlayer#pause()} and then
- *         {@link SessionPlayer#seekTo(long)} with 0</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}</td>
- *     <td>{@link SessionCallback#onFastForward}</td></tr>
- * <tr><td>{@link KeyEvent#KEYCODE_MEDIA_REWIND}</td>
- *     <td>{@link SessionCallback#onRewind}</td></tr>
- * <tr><td><ul><li>{@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}</li>
- *             <li>{@link KeyEvent#KEYCODE_HEADSETHOOK}</li></ul></td>
- *     <td><ul><li>For a single tap
- *             <ul><li>{@link SessionPlayer#pause()} if
- *             {@link SessionPlayer#PLAYER_STATE_PLAYING}</li>
- *             <li>{@link SessionPlayer#play()} otherwise</li></ul>
- *             <li>For a double tap, {@link SessionPlayer#skipToNextPlaylistItem()}</li></ul></td>
- *     </tr>
- * </table>
- *
- * <h3 id="MultipleSessions">Supporting Multiple Sessions</h3>
- *
- * Generally speaking, multiple sessions aren't necessary for most media apps. One exception is if
- * your app can play multiple media content at the same time, but only for the playback of
- * video-only media or remote playback, since <a
- * href="{@docRoot}guide/topics/media-apps/audio-focus.html">audio focus policy</a> recommends not
- * playing multiple audio content at the same time. Also keep in mind that multiple media sessions
- * would make Android Auto and Bluetooth device with display to show your apps multiple times,
- * because they list up media sessions, not media apps.
- *
- * <h3 id="CompatibilitySession">Backward compatibility with legacy session APIs</h3>
- *
- * An active {@link MediaSessionCompat} is internally created with the MediaSession for the backward
- * compatibility. It's used to handle incoming connection and command from {@link
- * MediaControllerCompat}. And helps to utilize existing APIs that are built with legacy media
- * session APIs. Use {@link #getSessionCompatToken} for getting the token for the underlying
- * MediaSessionCompat.
- *
- * <h3 id="CompatibilityController">Backward compatibility with legacy controller APIs</h3>
- *
- * In addition to the {@link MediaController media2 controller} API, session also supports
- * connection from the legacy controller API - {@link android.media.session.MediaController
- * framework controller} and {@link MediaControllerCompat AndroidX controller compat}. However,
- * {@link ControllerInfo} may not be precise for legacy controller. See {@link ControllerInfo} for
- * the details.
- *
- * <p>Unknown package name nor UID doesn't mean that you should disallow connection nor commands.
- * For SDK levels where such issue happen, session tokens could only be obtained by trusted apps
- * (e.g. Bluetooth, Auto, ...), so it may be better for you to allow them as you did with legacy
- * session.
- *
- * @see MediaSessionService
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class MediaSession implements Closeable {
-
-    // It's better to have private static lock instead of using MediaSession.class because the
-    // private lock object isn't exposed.
-    private static final Object STATIC_LOCK = new Object();
-    // Note: This checks the uniqueness of a session ID only in single process.
-    // When the framework becomes able to check the uniqueness, this logic should be removed.
-    @GuardedBy("STATIC_LOCK")
-    private static final HashMap<String, MediaSession> SESSION_ID_TO_SESSION_MAP = new HashMap<>();
-
-    private final MediaSessionImpl mImpl;
-
-    MediaSession(Context context, String id, SessionPlayer player,
-            PendingIntent sessionActivity, Executor callbackExecutor, SessionCallback callback,
-            Bundle tokenExtras) {
-        synchronized (STATIC_LOCK) {
-            if (SESSION_ID_TO_SESSION_MAP.containsKey(id)) {
-                throw new IllegalStateException("Session ID must be unique. ID=" + id);
-            }
-            SESSION_ID_TO_SESSION_MAP.put(id, this);
-        }
-        mImpl = createImpl(context, id, player, sessionActivity, callbackExecutor, callback,
-                tokenExtras);
-    }
-
-    MediaSessionImpl createImpl(Context context, String id, SessionPlayer player,
-            PendingIntent sessionActivity, Executor callbackExecutor, SessionCallback callback,
-            Bundle tokenExtras) {
-        return new MediaSessionImplBase(this, context, id, player, sessionActivity,
-                callbackExecutor, callback, tokenExtras);
-    }
-
-    /**
-     * Should be only used by subclass.
-     */
-    MediaSessionImpl getImpl() {
-        return mImpl;
-    }
-
-    static MediaSession getSession(Uri sessionUri) {
-        synchronized (STATIC_LOCK) {
-            for (MediaSession session : SESSION_ID_TO_SESSION_MAP.values()) {
-                if (ObjectsCompat.equals(session.getUri(), sessionUri)) {
-                    return session;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Updates the underlying {@link SessionPlayer} for this session to dispatch incoming event to.
-     *
-     * @param player a player that handles actual media playback in your app
-     */
-    public void updatePlayer(@NonNull SessionPlayer player) {
-        if (player == null) {
-            throw new NullPointerException("player shouldn't be null");
-        }
-        mImpl.updatePlayer(player);
-    }
-
-    @Override
-    public void close() {
-        try {
-            synchronized (STATIC_LOCK) {
-                SESSION_ID_TO_SESSION_MAP.remove(mImpl.getId());
-            }
-            mImpl.close();
-        } catch (Exception e) {
-            // Should not be here.
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public boolean isClosed() {
-        return mImpl.isClosed();
-    }
-
-    /**
-     * Gets the underlying {@link SessionPlayer}.
-     * <p>
-     * When the session is closed, it returns the lastly set player.
-     *
-     * @return player.
-     */
-    @NonNull
-    public SessionPlayer getPlayer() {
-        return mImpl.getPlayer();
-    }
-
-    /**
-     * Gets the session ID
-     *
-     * @return
-     */
-    @NonNull
-    public String getId() {
-        return mImpl.getId();
-    }
-
-    /**
-     * Returns the {@link SessionToken} for creating {@link MediaController}.
-     */
-    @NonNull
-    public SessionToken getToken() {
-        return mImpl.getToken();
-    }
-
-    @NonNull
-    Context getContext() {
-        return mImpl.getContext();
-    }
-
-    @NonNull
-    Executor getCallbackExecutor() {
-        return mImpl.getCallbackExecutor();
-    }
-
-    @NonNull
-    SessionCallback getCallback() {
-        return mImpl.getCallback();
-    }
-
-    /**
-     * Returns the list of connected controller.
-     *
-     * @return list of {@link ControllerInfo}
-     */
-    @NonNull
-    public List<ControllerInfo> getConnectedControllers() {
-        return mImpl.getConnectedControllers();
-    }
-
-    /**
-     * Sets ordered list of {@link CommandButton} for controllers to build UI with it.
-     * <p>
-     * It's up to controller's decision how to represent the layout in its own UI.
-     * Here are some examples.
-     * <p>
-     * Note: <code>layout[i]</code> means a CommandButton at index i in the given list
-     * <table>
-     * <tr><th>Controller UX layout</th><th>Layout example</th></tr>
-     * <tr><td>Row with 3 icons</td>
-     *     <td><code>layout[1]</code> <code>layout[0]</code> <code>layout[2]</code></td></tr>
-     * <tr><td>Row with 5 icons</td>
-     *     <td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
-     *         <code>layout[2]</code> <code>layout[4]</code></td></tr>
-     * <tr><td rowspan=2>Row with 5 icons and an overflow icon, and another expandable row with 5
-     *         extra icons</td>
-     *     <td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
-     *         <code>layout[2]</code> <code>layout[4]</code></td></tr>
-     * <tr><td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
-     *         <code>layout[2]</code> <code>layout[4]</code></td></tr>
-     * </table>
-     * <p>
-     * This API can be called in the
-     * {@link SessionCallback#onConnect(MediaSession, ControllerInfo)}.
-     *
-     * @param controller controller to specify layout.
-     * @param layout ordered list of layout.
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> setCustomLayout(
-            @NonNull ControllerInfo controller, @NonNull List<CommandButton> layout) {
-        if (controller == null) {
-            throw new NullPointerException("controller shouldn't be null");
-        }
-        if (layout == null) {
-            throw new NullPointerException("layout shouldn't be null");
-        }
-        return mImpl.setCustomLayout(controller, layout);
-    }
-
-    /**
-     * Sets the new allowed command group for the controller.
-     * <p>
-     * This is synchronous call. Changes in the allowed commands take effect immediately regardless
-     * of the controller notified about the change through
-     * {@link MediaController.ControllerCallback
-     * #onAllowedCommandsChanged(MediaController, SessionCommandGroup)}
-     *
-     * @param controller controller to change allowed commands
-     * @param commands new allowed commands
-     */
-    public void setAllowedCommands(@NonNull ControllerInfo controller,
-            @NonNull SessionCommandGroup commands) {
-        if (controller == null) {
-            throw new NullPointerException("controller shouldn't be null");
-        }
-        if (commands == null) {
-            throw new NullPointerException("commands shouldn't be null");
-        }
-        mImpl.setAllowedCommands(controller, commands);
-    }
-
-    /**
-     * Broadcasts a custom command to all connected controllers.
-     * <p>
-     * This is synchronous call and doesn't wait for result from the controller. Use
-     * {@link #sendCustomCommand(ControllerInfo, SessionCommand, Bundle)} for getting the result.
-     * <p>
-     * A command is not accepted if it is not a custom command.
-     *
-     * @param command a command
-     * @param args optional argument
-     * @see #sendCustomCommand(ControllerInfo, SessionCommand, Bundle)
-     */
-    public void broadcastCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        if (command == null) {
-            throw new NullPointerException("command shouldn't be null");
-        }
-        if (command.getCommandCode() != SessionCommand.COMMAND_CODE_CUSTOM) {
-            throw new IllegalArgumentException("command should be a custom command");
-        }
-        mImpl.broadcastCustomCommand(command, args);
-    }
-
-    /**
-     * Sends a custom command to a specific controller.
-     * <p>
-     * A command is not accepted if it is not a custom command.
-     *
-     * @param command a command
-     * @param args optional argument
-     * @see #broadcastCustomCommand(SessionCommand, Bundle)
-     */
-    @NonNull
-    public ListenableFuture<SessionResult> sendCustomCommand(
-            @NonNull ControllerInfo controller, @NonNull SessionCommand command,
-            @Nullable Bundle args) {
-        if (controller == null) {
-            throw new NullPointerException("controller shouldn't be null");
-        }
-        if (command == null) {
-            throw new NullPointerException("command shouldn't be null");
-        }
-        if (command.getCommandCode() != SessionCommand.COMMAND_CODE_CUSTOM) {
-            throw new IllegalArgumentException("command should be a custom command");
-        }
-        return mImpl.sendCustomCommand(controller, command, args);
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public MediaSessionCompat getSessionCompat() {
-        return mImpl.getSessionCompat();
-    }
-
-    /**
-     * Gets the {@link MediaSessionCompat.Token} for the MediaSessionCompat created internally
-     * by this session.
-     *
-     * @return {@link MediaSessionCompat.Token}
-     */
-    @NonNull
-    public MediaSessionCompat.Token getSessionCompatToken() {
-        return mImpl.getSessionCompat().getSessionToken();
-    }
-
-    /**
-     * Sets the timeout for disconnecting legacy controller.
-     * @param timeoutMs timeout in millis
-     *
-     */
-    @RestrictTo(LIBRARY)
-    public void setLegacyControllerConnectionTimeoutMs(long timeoutMs) {
-        mImpl.setLegacyControllerConnectionTimeoutMs(timeoutMs);
-    }
-
-    /**
-     * Handles the controller's connection request from {@link MediaSessionService}.
-     *
-     * @param controller controller aidl
-     * @param packageName controller package name
-     * @param pid controller pid
-     * @param uid controller uid
-     * @param connectionHints controller connection hints
-     */
-    void handleControllerConnectionFromService(IMediaController controller,
-            int controllerVersion, String packageName, int pid, int uid,
-            @Nullable Bundle connectionHints) {
-        mImpl.connectFromService(controller, controllerVersion, packageName, pid, uid,
-                connectionHints);
-    }
-
-    IBinder getLegacyBrowerServiceBinder() {
-        return mImpl.getLegacyBrowserServiceBinder();
-    }
-
-    @NonNull
-    private Uri getUri() {
-        return mImpl.getUri();
-    }
-
-    /**
-     * Callback to be called for all incoming commands from {@link MediaController}s.
-     *
-     * <p>If it's not set, the session will accept all controllers and all incoming commands by
-     * default.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public abstract static class SessionCallback {
-        ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
-        /**
-         * Called when a controller is created for this session. Return allowed commands for
-         * controller. By default it allows all connection requests and commands.
-         * <p>
-         * You can reject the connection by return {@code null}. In that case, the controller
-         * receives {@link MediaController.ControllerCallback#onDisconnected(MediaController)} and
-         * cannot be used.
-         * <p>
-         * The controller hasn't connected yet in this method, so calls to the controller
-         * (e.g. {@link #sendCustomCommand}, {@link #setCustomLayout}) would be ignored. Override
-         * {@link #onPostConnect} for the custom initialization for the controller instead.
-         *
-         * @param session the session for this event
-         * @param controller controller information.
-         * @return allowed commands. Can be {@code null} to reject connection.
-         * @see #onPostConnect(MediaSession, ControllerInfo)
-         */
-        @Nullable
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                    .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_CURRENT)
-                    .build();
-            return commands;
-        }
-
-        /**
-         * Called immediately after a controller is connected. This is a convenient method to add
-         * custom initialization between the session and a controller.
-         * <p>
-         * Note that calls to the controller (e.g. {@link #sendCustomCommand},
-         * {@link #setCustomLayout}) work here but don't work in {@link #onConnect} because the
-         * controller hasn't connected yet in {@link #onConnect}.
-         *
-         * @param session the session for this event
-         * @param controller controller information.
-         */
-        public void onPostConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-        }
-
-        /**
-         * Called when a controller is disconnected.
-         * <p>
-         * Interoperability: For legacy controller, this is called when the controller doesn't send
-         * any command for a while. It's because there were no explicit disconnect API in legacy
-         * controller API.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         */
-        public void onDisconnected(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {}
-
-        /**
-         * Called when a controller sent a command which will be sent directly to one of the
-         * following:
-         * <ul>
-         *  <li>{@link SessionPlayer}</li>
-         *  <li>{@link android.media.AudioManager}</li>
-         * </ul>
-         * <p>
-         * Return {@link SessionResult#RESULT_SUCCESS} to proceed the command. If something
-         * else is returned, command wouldn't be sent and the controller would receive the code with
-         * it.
-         *
-         * @param session the session for this event
-         * @param controller controller information.
-         * @param command a command. This method will be called for every single command.
-         * @return {@code RESULT_SUCCESS} if you want to proceed with incoming command.
-         *         Another code for ignore.
-         * @see SessionCommand#COMMAND_CODE_PLAYER_PLAY
-         * @see SessionCommand#COMMAND_CODE_PLAYER_PAUSE
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_PREPARE
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SEEK_TO
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SET_REPEAT_MODE
-         * @see SessionCommand#COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM
-         * @see SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST
-         * @see SessionCommand#COMMAND_CODE_PLAYER_SET_PLAYLIST
-         * @see SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA
-         * @see SessionCommand#COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA
-         * @see SessionCommand#COMMAND_CODE_VOLUME_SET_VOLUME
-         * @see SessionCommand#COMMAND_CODE_VOLUME_ADJUST_VOLUME
-         */
-        @ResultCode
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand command) {
-            return RESULT_SUCCESS;
-        }
-
-        /**
-         * Called when a controller has sent a command with a {@link MediaItem} to add a new media
-         * item to this session. Being specific, this will be called for following APIs.
-         * <ol>
-         * <li>{@link MediaController#addPlaylistItem(int, String)}
-         * <li>{@link MediaController#replacePlaylistItem(int, String)}
-         * <li>{@link MediaController#setPlaylist(List, MediaMetadata)}
-         * <li>{@link MediaController#setMediaItem(String)}
-         * </ol>
-         * Override this to translate incoming {@code mediaId} to a {@link MediaItem} to be
-         * understood by your player. For example, a player may only understand
-         * {@link androidx.media2.common.FileMediaItem}, {@link UriMediaItem},
-         * and {@link CallbackMediaItem}. Check the documentation of the player that you're using.
-         * <p>
-         * If the given media ID is valid, you should return the media item with the given media ID.
-         * If the ID doesn't match, an {@link RuntimeException} will be thrown.
-         * You may return {@code null} if the given item is invalid. Here's the behavior when it
-         * happens.
-         * <table border="0" cellspacing="0" cellpadding="0">
-         * <tr><th>Controller command</th> <th>Behavior when {@code null} is returned</th></tr>
-         * <tr><td>addPlaylistItem</td> <td>Ignore</td></tr>
-         * <tr><td>replacePlaylistItem</td> <td>Ignore</td></tr>
-         * <tr><td>setPlaylist</td>
-         *     <td>Ignore {@code null} items, and build a list with non-{@code null} items. Call
-         *         {@link SessionPlayer#setPlaylist(List, MediaMetadata)} with the list</td></tr>
-         * <tr><td>setMediaItem</td> <td>Ignore</td></tr>
-         * </table>
-         * <p>
-         * This will be called on the same thread where {@link #onCommandRequest} and commands with
-         * the media controller will be executed.
-         * <p>
-         * Default implementation returns the {@code null}.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @param mediaId non-empty media id for creating item with
-         * @return translated media item for player with the mediaId. Can be {@code null} to ignore.
-         * @see MediaMetadata#METADATA_KEY_MEDIA_ID
-         */
-        @Nullable
-        public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull String mediaId) {
-            return null;
-        }
-
-        /**
-         * Called when a controller set rating of a media item through
-         * {@link MediaController#setRating(String, Rating)}.
-         * <p>
-         * To allow setting user rating for a {@link MediaItem}, the media item's metadata
-         * should have {@link Rating} with the key {@link MediaMetadata#METADATA_KEY_USER_RATING},
-         * in order to provide possible rating style for controller. Controller will follow the
-         * rating style.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @param mediaId non-empty media id
-         * @param rating new rating from the controller
-         * @see SessionCommand#COMMAND_CODE_SESSION_SET_RATING
-         */
-        @ResultCode
-        public int onSetRating(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull String mediaId,
-                @NonNull Rating rating) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when a controller requested to set the specific media item(s) represented by a URI
-         * through {@link MediaController#setMediaUri(Uri, Bundle)}.
-         * <p>
-         * The implementation should create proper {@link MediaItem media item(s)} for the given
-         * {@code uri} and call {@link SessionPlayer#setMediaItem} or
-         * {@link SessionPlayer#setPlaylist}.
-         * <p>
-         * When {@link MediaControllerCompat} is connected and sends commands with following
-         * methods, the {@code uri} would have the following patterns:
-         * <table>
-         * <tr>
-         * <th>Method</th><th align="left">Uri pattern</th>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
-         * </td><td>The {@code uri} passed as argument</td>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#prepareFromMediaId prepareFromMediaId}
-         * </td><td>{@code androidx://media2-session/prepareFromMediaId?id=[mediaId]}</td>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#prepareFromSearch prepareFromSearch}
-         * </td><td>{@code androidx://media2-session/prepareFromSearch?query=[query]}</td>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
-         * </td><td>The {@code uri} passed as argument</td>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#playFromMediaId playFromMediaId}
-         * </td><td>{@code androidx://media2-session/playFromMediaId?id=[mediaId]}</td>
-         * </tr><tr>
-         * <td>{@link MediaControllerCompat.TransportControls#playFromSearch playFromSearch}
-         * </td><td>{@code androidx://media2-session/playFromSearch?query=[query]}</td>
-         * </tr></table>
-         * <p>
-         * {@link SessionPlayer#prepare()} or {@link SessionPlayer#play()} would be followed if
-         * this is called by above methods.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @param uri uri
-         * @param extras optional extra bundle
-         */
-        @ResultCode
-        public int onSetMediaUri(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when a controller sent a custom command through
-         * {@link MediaController#sendCustomCommand(SessionCommand, Bundle)}.
-         * <p>
-         * Interoperability: This would be also called by {@link
-         * android.support.v4.media.MediaBrowserCompat
-         * #sendCustomAction(String, Bundle, CustomActionCallback)}. If so, extra from
-         * sendCustomAction will be considered as args and customCommand would have null extra.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @param customCommand custom command.
-         * @param args optional arguments
-         * @return result of handling custom command. A runtime exception will be thrown if
-         *         {@code null} is returned.
-         * @see SessionCommand#COMMAND_CODE_CUSTOM
-         */
-        @NonNull
-        public SessionResult onCustomCommand(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand customCommand,
-                @Nullable Bundle args) {
-            return new SessionResult(RESULT_ERROR_NOT_SUPPORTED, null);
-        }
-
-        /**
-         * Called when a controller called {@link MediaController#fastForward()}.
-         * <p>
-         * It can be implemented in many ways. For example, it can be implemented by seeking forward
-         * once, series of seeking forward, or increasing playback speed.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @see SessionCommand#COMMAND_CODE_SESSION_FAST_FORWARD
-         */
-        @ResultCode
-        public int onFastForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when a controller called {@link MediaController#rewind()}.
-         * <p>
-         * It can be implemented in many ways. For example, it can be implemented by seeking
-         * backward once, series of seeking backward, or decreasing playback speed.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @see SessionCommand#COMMAND_CODE_SESSION_REWIND
-         */
-        @ResultCode
-        public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when a controller called {@link MediaController#skipForward()}.
-         * <p>
-         * It's recommended to seek forward within the current media item, but its detail may vary.
-         * For example, it can be implemented by seeking forward for the fixed amount of seconds, or
-         * seeking forward to the nearest bookmark.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @see SessionCommand#COMMAND_CODE_SESSION_SKIP_FORWARD
-         */
-        @ResultCode
-        public int onSkipForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when a controller called {@link MediaController#skipBackward()}.
-         * <p>
-         * It's recommended to seek backward within the current media item, but its detail may vary.
-         * For example, it can be implemented by seeking backward for the fixed amount of seconds,
-         * or seeking backward to the nearest bookmark.
-         *
-         * @param session the session for this event
-         * @param controller controller information
-         * @see SessionCommand#COMMAND_CODE_SESSION_SKIP_BACKWARD
-         */
-        @ResultCode
-        public int onSkipBackward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            return RESULT_ERROR_NOT_SUPPORTED;
-        }
-
-        /**
-         * Called when the player state is changed. Used internally for setting the
-         * {@link MediaSessionService} as foreground/background.
-         */
-        final void onPlayerStateChanged(MediaSession session, @PlayerState int state) {
-            if (mForegroundServiceEventCallback != null) {
-                mForegroundServiceEventCallback.onPlayerStateChanged(session, state);
-            }
-        }
-
-        final void onCurrentMediaItemChanged(MediaSession session) {
-            if (mForegroundServiceEventCallback != null) {
-                mForegroundServiceEventCallback.onNotificationUpdateNeeded(session);
-            }
-        }
-
-        final void onSessionClosed(MediaSession session) {
-            if (mForegroundServiceEventCallback != null) {
-                mForegroundServiceEventCallback.onSessionClosed(session);
-            }
-        }
-
-        void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
-            mForegroundServiceEventCallback = callback;
-        }
-
-        abstract static class ForegroundServiceEventCallback {
-            public void onPlayerStateChanged(MediaSession session, @PlayerState int state) {}
-            public void onNotificationUpdateNeeded(MediaSession session) {}
-            public void onSessionClosed(MediaSession session) {}
-        }
-    }
-
-    /**
-     * Builder for {@link MediaSession}.
-     *
-     * <p>Any incoming event from the {@link MediaController} will be handled on the callback
-     * executor. If it's not set, {@link ContextCompat#getMainExecutor(Context)} will be used by
-     * default.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder extends BuilderBase<MediaSession, Builder, SessionCallback> {
-        public Builder(@NonNull Context context, @NonNull SessionPlayer player) {
-            super(context, player);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionActivity(@Nullable PendingIntent pi) {
-            return super.setSessionActivity(pi);
-        }
-
-        @Override
-        @NonNull
-        public Builder setId(@NonNull String id) {
-            return super.setId(id);
-        }
-
-        @Override
-        @NonNull
-        public Builder setSessionCallback(@NonNull Executor executor,
-                @NonNull SessionCallback callback) {
-            return super.setSessionCallback(executor, callback);
-        }
-
-        @Override
-        @NonNull
-        public Builder setExtras(@NonNull Bundle extras) {
-            return super.setExtras(extras);
-        }
-
-        @Override
-        @NonNull
-        public MediaSession build() {
-            if (mCallbackExecutor == null) {
-                mCallbackExecutor = ContextCompat.getMainExecutor(mContext);
-            }
-            if (mCallback == null) {
-                mCallback = new SessionCallback() {};
-            }
-            return new MediaSession(mContext, mId, mPlayer, mSessionActivity,
-                    mCallbackExecutor, mCallback, mExtras);
-        }
-    }
-
-    /**
-     * Information of a controller.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class ControllerInfo {
-        @SuppressWarnings("UnusedVariable")
-        private final int mControllerVersion;
-        private final RemoteUserInfo mRemoteUserInfo;
-        private final boolean mIsTrusted;
-        private final ControllerCb mControllerCb;
-        private final Bundle mConnectionHints;
-
-        /**
-         * @param remoteUserInfo remote user info
-         * @param version connected controller version
-         * @param trusted {@code true} if trusted, {@code false} otherwise
-         * @param cb ControllerCb. Can be {@code null} only when a MediaBrowserCompat connects to
-         *           MediaSessionService and ControllerInfo is needed for
-         *           SessionCallback#onConnected().
-         * @param connectionHints a session-specific argument sent from the controller for the
-         *                        connection. The contents of this bundle may affect the
-         *                        connection result.
-         */
-        ControllerInfo(@NonNull RemoteUserInfo remoteUserInfo, int version, boolean trusted,
-                @Nullable ControllerCb cb, @Nullable Bundle connectionHints) {
-            mRemoteUserInfo = remoteUserInfo;
-            mControllerVersion = version;
-            mIsTrusted = trusted;
-            mControllerCb = cb;
-            if (connectionHints == null
-                    || MediaUtils.doesBundleHaveCustomParcelable(connectionHints)) {
-                mConnectionHints = null;
-            } else {
-                mConnectionHints = connectionHints;
-            }
-        }
-
-        RemoteUserInfo getRemoteUserInfo() {
-            return mRemoteUserInfo;
-        }
-
-        /**
-         * Gets the package name. Can be
-         * {@link androidx.media.MediaSessionManager.RemoteUserInfo#LEGACY_CONTROLLER} for
-         * interoperability.
-         * <p>
-         * Interoperability: Package name may not be precisely obtained for legacy controller API on
-         * older device. Here are details.
-         * <table>
-         * <tr><th>SDK version when package name isn't precise</th>
-         *     <th>{@code ControllerInfo#getPackageName()} for legacy controller</th>
-         * <tr><td>{@code SDK_VERSION} &lt; {@code 21}</td>
-         *     <td>Actual package name via {@link PackageManager#getNameForUid} with UID.<br>
-         *         It's sufficient for most cases, but doesn't precisely distinguish caller if it
-         *         uses shared user ID.</td>
-         * <tr><td>{@code 21} &le; {@code SDK_VERSION} &lt; {@code 24}</td>
-         *     <td>{@link RemoteUserInfo#LEGACY_CONTROLLER LEGACY_CONTROLLER}</td>
-         * </table>
-         *
-         * @return package name of the controller. Can be
-         *         {@link RemoteUserInfo#LEGACY_CONTROLLER LEGACY_CONTROLLER} if the package name
-         *         cannot be obtained.
-         */
-        @NonNull
-        public String getPackageName() {
-            return mRemoteUserInfo.getPackageName();
-        }
-
-        /**
-         * Gets the UID of the controller. Can be a negative value for interoperability.
-         * <p>
-         * Interoperability: If {@code 21} &le; {@code SDK_VERSION} &lt; {@code 28}, then UID would
-         * be a negative value because it cannot be obtained.
-         *
-         * @return uid of the controller. Can be a negative value if the uid cannot be obtained.
-         */
-        public int getUid() {
-            return mRemoteUserInfo.getUid();
-        }
-
-        /**
-         * Gets the connection hints sent from controller, or {@link Bundle#EMPTY} if none.
-         */
-        @NonNull
-        public Bundle getConnectionHints() {
-            return mConnectionHints == null ? Bundle.EMPTY : new Bundle(mConnectionHints);
-        }
-
-        /**
-         * Returns if the controller has been granted
-         * {@code android.permission.MEDIA_CONTENT_CONTROL} or has a enabled notification listener
-         * so can be trusted to accept connection and incoming command request.
-         *
-         * @return {@code true} if the controller is trusted.
-         */
-        @RestrictTo(LIBRARY)
-        public boolean isTrusted() {
-            return mIsTrusted;
-        }
-
-        @Override
-        public int hashCode() {
-            return ObjectsCompat.hash(mControllerCb, mRemoteUserInfo);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof ControllerInfo)) {
-                return false;
-            }
-            if (this == obj) {
-                return true;
-            }
-            ControllerInfo other = (ControllerInfo) obj;
-            if (mControllerCb != null || other.mControllerCb != null) {
-                return ObjectsCompat.equals(mControllerCb, other.mControllerCb);
-            }
-            return mRemoteUserInfo.equals(other.mRemoteUserInfo);
-        }
-
-        @Override
-        public String toString() {
-            return "ControllerInfo {pkg=" + mRemoteUserInfo.getPackageName() + ", uid="
-                    + mRemoteUserInfo.getUid() + "})";
-        }
-
-        @Nullable ControllerCb getControllerCb() {
-            return mControllerCb;
-        }
-
-        @NonNull
-        static ControllerInfo createLegacyControllerInfo() {
-            RemoteUserInfo legacyRemoteUserInfo =
-                    new RemoteUserInfo(
-                            RemoteUserInfo.LEGACY_CONTROLLER,
-                            /* pid= */ RemoteUserInfo.UNKNOWN_PID,
-                            /* uid= */ RemoteUserInfo.UNKNOWN_UID);
-            return new ControllerInfo(
-                    legacyRemoteUserInfo,
-                    MediaUtils.VERSION_UNKNOWN,
-                    /* trusted= */ false,
-                    /* cb= */ null,
-                    /* connectionHints= */ null);
-        }
-    }
-
-    /**
-     * Button for a {@link SessionCommand} that will be shown by the controller.
-     *
-     * <p>It's up to the controller's decision to respect or ignore this customization request.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    @VersionedParcelize
-    public static final class CommandButton implements VersionedParcelable {
-        @ParcelField(1)
-        SessionCommand mCommand;
-        @ParcelField(2)
-        int mIconResId;
-        @ParcelField(3)
-        CharSequence mDisplayName;
-        @ParcelField(4)
-        Bundle mExtras;
-        @ParcelField(5)
-        boolean mEnabled;
-
-        // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-        /**
-         * Used for VersionedParcelable
-         */
-        CommandButton() {
-        }
-
-        CommandButton(@Nullable SessionCommand command, int iconResId,
-                @Nullable CharSequence displayName, Bundle extras, boolean enabled) {
-            mCommand = command;
-            mIconResId = iconResId;
-            mDisplayName = displayName;
-            mExtras = extras;
-            mEnabled = enabled;
-        }
-
-        /**
-         * Gets the command associated with this button. Can be {@code null} if the button isn't
-         * enabled and only providing placeholder.
-         *
-         * @return command or {@code null}
-         */
-        @Nullable
-        public SessionCommand getCommand() {
-            return mCommand;
-        }
-
-        /**
-         * Gets the resource id of the button in this package. Can be {@code 0} if the command is
-         * predefined and custom icon isn't needed.
-         *
-         * @return resource id of the icon. Can be {@code 0}.
-         */
-        public int getIconResId() {
-            return mIconResId;
-        }
-
-        /**
-         * Gets the display name of the button. Can be {@code null} or empty if the command is
-         * predefined and custom name isn't needed.
-         *
-         * @return custom display name. Can be {@code null} or empty.
-         */
-        @Nullable
-        public CharSequence getDisplayName() {
-            return mDisplayName;
-        }
-
-        /**
-         * Gets extra information of the button. It's private information between session and
-         * controller.
-         *
-         * @return
-         */
-        @Nullable
-        public Bundle getExtras() {
-            return mExtras;
-        }
-
-        /**
-         * Returns whether it's enabled.
-         *
-         * @return {@code true} if enabled. {@code false} otherwise.
-         */
-        public boolean isEnabled() {
-            return mEnabled;
-        }
-
-        /**
-         * Builder for {@link CommandButton}.
-         *
-         * @deprecated androidx.media2 is deprecated. Please migrate to <a
-         *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a> .
-         */
-        @Deprecated
-        public static final class Builder {
-            private SessionCommand mCommand;
-            private int mIconResId;
-            private CharSequence mDisplayName;
-            private Bundle mExtras;
-            private boolean mEnabled;
-
-            /**
-             * Sets the {@link SessionCommand} that would be sent to the session when the button
-             * is clicked.
-             *
-             * @param command session command
-             */
-            @NonNull
-            public Builder setCommand(@Nullable SessionCommand command) {
-                mCommand = command;
-                return this;
-            }
-
-            /**
-             * Sets the bitmap-type (e.g. PNG) icon resource id of the button.
-             * <p>
-             * None bitmap type (e.g. VectorDrawabale) may cause unexpected behavior when it's sent
-             * to {@link MediaController} app, so please avoid using it especially for the older
-             * platform (API < 21).
-             *
-             * @param resId resource id of the button
-             */
-            @NonNull
-            public Builder setIconResId(int resId) {
-                mIconResId = resId;
-                return this;
-            }
-
-            /**
-             * Sets the display name of the button.
-             *
-             * @param displayName display name of the button
-             */
-            @NonNull
-            public Builder setDisplayName(@Nullable CharSequence displayName) {
-                mDisplayName = displayName;
-                return this;
-            }
-
-            /**
-             * Sets whether the button is enabled. Can be {@code false} to indicate that the button
-             * should be shown but isn't clickable.
-             *
-             * @param enabled {@code true} if the button is enabled and ready.
-             *          {@code false} otherwise.
-             */
-            @NonNull
-            public Builder setEnabled(boolean enabled) {
-                mEnabled = enabled;
-                return this;
-            }
-
-            /**
-             * Sets the extras of the button.
-             *
-             * @param extras extras information of the button
-             */
-            @NonNull
-            public Builder setExtras(@Nullable Bundle extras) {
-                mExtras = extras;
-                return this;
-            }
-
-            /**
-             * Builds the {@link CommandButton}.
-             *
-             * @return a new {@link CommandButton}
-             */
-            @NonNull
-            public CommandButton build() {
-                return new CommandButton(mCommand, mIconResId, mDisplayName, mExtras, mEnabled);
-            }
-        }
-    }
-
-    // TODO: Drop 'Cb' from the name.
-    abstract static class ControllerCb {
-        abstract void onPlayerResult(int seq, PlayerResult result) throws RemoteException;
-        abstract void onSessionResult(int seq, SessionResult result) throws RemoteException;
-        abstract void onLibraryResult(int seq, LibraryResult result) throws RemoteException;
-        abstract void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
-                @Nullable PlaybackInfo oldPlaybackInfo, @NonNull SessionPlayer player,
-                @NonNull PlaybackInfo playbackInfo) throws RemoteException;
-
-        // Mostly matched with the methods in MediaController.ControllerCallback
-        abstract void setCustomLayout(int seq, @NonNull List<CommandButton> layout)
-                throws RemoteException;
-        abstract void sendCustomCommand(int seq, @NonNull SessionCommand command,
-                @Nullable Bundle args) throws RemoteException;
-        abstract void onPlaybackInfoChanged(int seq, @NonNull PlaybackInfo info)
-                throws RemoteException;
-        abstract void onAllowedCommandsChanged(int seq, @NonNull SessionCommandGroup commands)
-                throws RemoteException;
-        abstract void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs,
-                int playerState) throws RemoteException;
-        abstract void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs,
-                float speed) throws RemoteException;
-        abstract void onBufferingStateChanged(int seq, @NonNull MediaItem item,
-                @BuffState int bufferingState, long bufferedPositionMs, long eventTimeMs,
-                long positionMs) throws RemoteException;
-        abstract void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long position)
-                throws RemoteException;
-        abstract void onCurrentMediaItemChanged(int seq, @Nullable MediaItem item, int currentIdx,
-                int previousIdx, int nextIdx) throws RemoteException;
-        abstract void onPlaylistChanged(int seq, @NonNull List<MediaItem> playlist,
-                @Nullable MediaMetadata metadata, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException;
-        abstract void onPlaylistMetadataChanged(int seq, @Nullable MediaMetadata metadata)
-                throws RemoteException;
-        abstract void onShuffleModeChanged(int seq, @SessionPlayer.ShuffleMode int shuffleMode,
-                int currentIdx, int previousIdx, int nextIdx) throws RemoteException;
-        abstract void onRepeatModeChanged(int seq, @SessionPlayer.RepeatMode int repeatMode,
-                int currentIdx, int previousIdx, int nextIdx) throws RemoteException;
-        abstract void onPlaybackCompleted(int seq) throws RemoteException;
-        abstract void onDisconnected(int seq) throws RemoteException;
-        abstract void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize)
-                throws RemoteException;
-        abstract void onTracksChanged(int seq, List<TrackInfo> tracks,
-                TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-                TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
-                throws RemoteException;
-        abstract void onTrackSelected(int seq, TrackInfo trackInfo) throws RemoteException;
-        abstract void onTrackDeselected(int seq, TrackInfo trackInfo) throws RemoteException;
-        abstract void onSubtitleData(int seq, @NonNull MediaItem item, @NonNull TrackInfo track,
-                @NonNull SubtitleData data) throws RemoteException;
-
-        // Mostly matched with the methods in MediaBrowser.BrowserCallback.
-        abstract void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                @Nullable LibraryParams params) throws RemoteException;
-        abstract void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                @Nullable LibraryParams params) throws RemoteException;
-    }
-
-    interface MediaSessionImpl extends MediaInterface.SessionPlayer, Closeable {
-        void updatePlayer(@NonNull SessionPlayer player);
-        @NonNull
-        SessionPlayer getPlayer();
-        @NonNull
-        String getId();
-        @NonNull
-        Uri getUri();
-        @NonNull
-        SessionToken getToken();
-        @NonNull
-        List<ControllerInfo> getConnectedControllers();
-        boolean isConnected(@NonNull ControllerInfo controller);
-
-        ListenableFuture<SessionResult> setCustomLayout(@NonNull ControllerInfo controller,
-                @NonNull List<CommandButton> layout);
-        void setAllowedCommands(@NonNull ControllerInfo controller,
-                @NonNull SessionCommandGroup commands);
-        void broadcastCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args);
-        ListenableFuture<SessionResult> sendCustomCommand(@NonNull ControllerInfo controller,
-                @NonNull SessionCommand command, @Nullable Bundle args);
-
-        // Internally used methods
-        MediaSession getInstance();
-        @NonNull MediaSessionCompat getSessionCompat();
-        void setLegacyControllerConnectionTimeoutMs(long timeoutMs);
-        Context getContext();
-        Executor getCallbackExecutor();
-        SessionCallback getCallback();
-        boolean isClosed();
-        PlaybackStateCompat createPlaybackStateCompat();
-        PlaybackInfo getPlaybackInfo();
-        PendingIntent getSessionActivity();
-        IBinder getLegacyBrowserServiceBinder();
-        void connectFromService(IMediaController caller, int controllerVersion, String packageName,
-                int pid, int uid, @Nullable Bundle connectionHints);
-    }
-
-    /**
-     * Base builder class for MediaSession and its subclass. Any change in this class should be
-     * also applied to the subclasses {@link MediaSession.Builder} and
-     * {@link MediaLibraryService.MediaLibrarySession.Builder}.
-     * <p>
-     * APIs here should be package private, but should have documentations for developers.
-     * Otherwise, javadoc will generate documentation with the generic types such as follows.
-     * <pre>U extends BuilderBase<T, U, C> setSessionCallback(Executor executor, C callback)</pre>
-     * <p>
-     * This class is hidden to prevent from generating test stub, which fails with
-     * 'unexpected bound' because it tries to auto generate stub class as follows.
-     * <pre>abstract static class BuilderBase<
-     *      T extends MediaSession,
-     *      U extends MediaSession.BuilderBase<
-     *              T, U, C extends MediaSession.SessionCallback>, C></pre>
-     */
-    @RestrictTo(LIBRARY)
-    abstract static class BuilderBase
-            <T extends MediaSession, U extends BuilderBase<T, U, C>, C extends SessionCallback> {
-        final Context mContext;
-        SessionPlayer mPlayer;
-        String mId;
-        Executor mCallbackExecutor;
-        C mCallback;
-        PendingIntent mSessionActivity;
-        Bundle mExtras;
-
-        BuilderBase(@NonNull Context context, @NonNull SessionPlayer player) {
-            if (context == null) {
-                throw new NullPointerException("context shouldn't be null");
-            }
-            if (player == null) {
-                throw new NullPointerException("player shouldn't be null");
-            }
-            mContext = context;
-            mPlayer = player;
-            // Ensure non-null id.
-            mId = "";
-        }
-
-        /**
-         * Sets an intent for launching UI for this Session. This can be used as a
-         * quick link to an ongoing media screen. The intent should be for an
-         * activity that may be started using {@link Context#startActivity(Intent)}.
-         *
-         * @param pi The intent to launch to show UI for this session.
-         */
-        @SuppressWarnings("unchecked")
-        @NonNull
-        U setSessionActivity(@Nullable PendingIntent pi) {
-            mSessionActivity = pi;
-            return (U) this;
-        }
-
-        /**
-         * Sets the ID of the session. If it's not set, an empty string will be used to create a
-         * session.
-         * <p>
-         * Use this if and only if your app supports multiple playback at the same time and also
-         * wants to provide external apps to have finer controls of them.
-         *
-         * @param id id of the session. Must be unique per package.
-         * @return
-         */
-        // Note: This ID is not visible to the controllers. ID is introduced in order to prevent
-        // apps from creating multiple sessions without any clear reasons. If they create two
-        // sessions with the same ID in a process, then an IllegalStateException will be thrown.
-        @SuppressWarnings("unchecked")
-        @NonNull
-        U setId(@NonNull String id) {
-            if (id == null) {
-                throw new NullPointerException("id shouldn't be null");
-            }
-            mId = id;
-            return (U) this;
-        }
-
-        /**
-         * Sets callback for the session.
-         *
-         * @param executor callback executor
-         * @param callback session callback
-         * @return
-         */
-        @SuppressWarnings("unchecked")
-        @NonNull
-        U setSessionCallback(@NonNull Executor executor, @NonNull C callback) {
-            if (executor == null) {
-                throw new NullPointerException("executor shouldn't be null");
-            }
-            if (callback == null) {
-                throw new NullPointerException("callback shouldn't be null");
-            }
-            mCallbackExecutor = executor;
-            mCallback = callback;
-            return (U) this;
-        }
-
-        /**
-         * Sets extras for the session token.  If not set, {@link SessionToken#getExtras()}
-         * will return an empty {@link Bundle}.
-         *
-         * @return the Builder to allow chaining
-         * @throws IllegalArgumentException if the bundle contains any non-framework Parcelable
-         * objects.
-         * @see SessionToken#getExtras()
-         */
-        @NonNull
-        @SuppressWarnings("unchecked")
-        U setExtras(@NonNull Bundle extras) {
-            if (extras == null) {
-                throw new NullPointerException("extras shouldn't be null");
-            }
-            if (MediaUtils.doesBundleHaveCustomParcelable(extras)) {
-                throw new IllegalArgumentException(
-                        "extras shouldn't contain any custom parcelables");
-            }
-            mExtras = new Bundle(extras);
-            return (U) this;
-        }
-
-        /**
-         * Builds a {@link MediaSession}.
-         *
-         * @return a new session
-         * @throws IllegalStateException if the session with the same id already exists for the
-         *      package.
-         */
-        @NonNull abstract T build();
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
deleted file mode 100644
index e4ea230..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ /dev/null
@@ -1,1704 +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.media2.session;
-
-import static androidx.media2.common.BaseResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DURATION;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX;
-import static androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-import static androidx.media2.common.SessionPlayer.UNKNOWN_TIME;
-import static androidx.media2.session.MediaUtils.DIRECT_EXECUTOR;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.session.SessionResult.RESULT_INFO_SKIPPED;
-
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.Token;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Surface;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.concurrent.futures.AbstractResolvableFuture;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.ObjectsCompat;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.BaseResult;
-import androidx.media2.common.ClassVerificationHelper;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession.ControllerCb;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.SequencedFutureManager.SequencedFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-
-class MediaSessionImplBase implements MediaSession.MediaSessionImpl {
-    // Create a static lock for synchronize methods below.
-    // We'd better not use MediaSessionImplBase.class for synchronized(), which indirectly exposes
-    // lock object to the outside of the class.
-    private static final Object STATIC_LOCK = new Object();
-    @GuardedBy("STATIC_LOCK")
-    private static boolean sComponentNamesInitialized = false;
-    @GuardedBy("STATIC_LOCK")
-    private static ComponentName sServiceComponentName;
-
-    static final String TAG = "MSImplBase";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final SessionResult RESULT_WHEN_CLOSED = new SessionResult(RESULT_INFO_SKIPPED);
-
-    final Object mLock = new Object();
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Uri mSessionUri;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final Executor mCallbackExecutor;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final SessionCallback mCallback;
-
-    private final Context mContext;
-    private final HandlerThread mHandlerThread;
-    private final Handler mHandler;
-    private final MediaSessionStub mSessionStub;
-    private final MediaSessionLegacyStub mSessionLegacyStub;
-    private final String mSessionId;
-    private final SessionToken mSessionToken;
-    private final AudioManager mAudioManager;
-    private final SessionPlayerCallback mPlayerCallback;
-    private final MediaSession mInstance;
-    private final PendingIntent mSessionActivity;
-    private final PendingIntent mMediaButtonIntent;
-    private final BroadcastReceiver mBroadcastReceiver;
-
-    @GuardedBy("mLock")
-    private boolean mClosed;
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaController.PlaybackInfo mPlaybackInfo;
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    SessionPlayer mPlayer;
-
-    @GuardedBy("mLock")
-    private MediaBrowserServiceCompat mBrowserServiceLegacyStub;
-
-    MediaSessionImplBase(MediaSession instance, Context context, String id, SessionPlayer player,
-            PendingIntent sessionActivity, Executor callbackExecutor, SessionCallback callback,
-            Bundle tokenExtras) {
-        mContext = context;
-        mInstance = instance;
-        mHandlerThread = new HandlerThread("MediaSession_Thread");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-
-        mSessionStub = new MediaSessionStub(this);
-        mSessionActivity = sessionActivity;
-
-        mCallback = callback;
-        mCallbackExecutor = callbackExecutor;
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
-        mPlayerCallback = new SessionPlayerCallback(this);
-
-        mSessionId = id;
-        // Build Uri that differentiate sessions across the creation/destruction in PendingIntent.
-        // Here's the reason why Session ID / SessionToken aren't suitable here.
-        //   - Session ID
-        //     PendingIntent from the previously closed session with the same ID can be sent to the
-        //     newly created session.
-        //   - SessionToken
-        //     SessionToken is a Parcelable so we can only put it into the intent extra.
-        //     However, creating two different PendingIntent that only differs extras isn't allowed.
-        //     See {@link PendingIntent} and {@link Intent#filterEquals} for details.
-        mSessionUri = new Uri.Builder().scheme(MediaSessionImplBase.class.getName()).appendPath(id)
-                .appendPath(String.valueOf(SystemClock.elapsedRealtime())).build();
-        mSessionToken = new SessionToken(new SessionTokenImplBase(Process.myUid(),
-                SessionToken.TYPE_SESSION, context.getPackageName(), mSessionStub, tokenExtras));
-
-        ComponentName mbrComponent = null;
-        synchronized (STATIC_LOCK) {
-            if (!sComponentNamesInitialized) {
-                sServiceComponentName = getServiceComponentByAction(
-                        MediaLibraryService.SERVICE_INTERFACE);
-                if (sServiceComponentName == null) {
-                    sServiceComponentName = getServiceComponentByAction(
-                            MediaSessionService.SERVICE_INTERFACE);
-                }
-                sComponentNamesInitialized = true;
-            }
-            mbrComponent = sServiceComponentName;
-        }
-        int pendingIntentFlagMutable = Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0;
-        if (mbrComponent == null) {
-            // No service to revive playback after it's dead.
-            // Create a PendingIntent that points to the runtime broadcast receiver.
-            Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, mSessionUri);
-            intent.setPackage(context.getPackageName());
-            mMediaButtonIntent = PendingIntent.getBroadcast(
-                    context, 0 /* requestCode */, intent, pendingIntentFlagMutable);
-
-            // Creates a fake ComponentName for MediaSessionCompat in pre-L.
-            // TODO: Replace this with the MediaButtonReceiver class.
-            mbrComponent = new ComponentName(context, context.getClass());
-
-            // Create and register a BroadcastReceiver for receiving PendingIntent.
-            // TODO: Introduce MediaButtonReceiver in AndroidManifest instead of this,
-            //       or register only one receiver for all sessions.
-            mBroadcastReceiver = new MediaButtonReceiver();
-            IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
-            filter.addDataScheme(mSessionUri.getScheme());
-            if (Build.VERSION.SDK_INT < 33) {
-                context.registerReceiver(mBroadcastReceiver, filter);
-            } else {
-                Api33.registerReceiver(context, mBroadcastReceiver, filter,
-                        Context.RECEIVER_NOT_EXPORTED);
-            }
-        } else {
-            // Has MediaSessionService to revive playback after it's dead.
-            Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, mSessionUri);
-            intent.setComponent(mbrComponent);
-            if (Build.VERSION.SDK_INT >= 26) {
-                mMediaButtonIntent =
-                        ClassVerificationHelper.PendingIntent.Api26.getForegroundService(
-                                mContext, 0, intent, pendingIntentFlagMutable);
-            } else {
-                mMediaButtonIntent = PendingIntent.getService(
-                        mContext, 0, intent, pendingIntentFlagMutable);
-            }
-            mBroadcastReceiver = null;
-        }
-
-        mSessionLegacyStub = new MediaSessionLegacyStub(this, mbrComponent,
-                mMediaButtonIntent, mHandler);
-
-        updatePlayer(player);
-
-        // Do followings at the last moment. Otherwise commands through framework would be sent to
-        // this session while initializing, and end up with unexpected situation.
-        mSessionLegacyStub.start();
-    }
-
-    // TODO(jaewan): Remove SuppressLint when removing duplication session callback.
-    @Override
-    @SuppressLint("WrongConstant")
-    public void updatePlayer(@NonNull SessionPlayer player) {
-        final SessionPlayer oldPlayer;
-        final MediaController.PlaybackInfo oldPlaybackInfo;
-        final MediaController.PlaybackInfo playbackInfo = createPlaybackInfo(player, null);
-
-        synchronized (mLock) {
-            if (mPlayer == player) {
-                return;
-            }
-            oldPlayer = mPlayer;
-            mPlayer = player;
-            oldPlaybackInfo = mPlaybackInfo;
-            mPlaybackInfo = playbackInfo;
-        }
-
-        if (oldPlayer != null) {
-            oldPlayer.unregisterPlayerCallback(mPlayerCallback);
-        }
-        player.registerPlayerCallback(mCallbackExecutor, mPlayerCallback);
-
-        notifyPlayerUpdatedNotLocked(oldPlayer, oldPlaybackInfo, player, playbackInfo);
-    }
-
-    @NonNull
-    MediaController.PlaybackInfo createPlaybackInfo(@NonNull SessionPlayer player,
-            AudioAttributesCompat audioAttributes) {
-        final AudioAttributesCompat attrs = audioAttributes != null ? audioAttributes :
-                player.getAudioAttributes();
-
-        if (!(player instanceof RemoteSessionPlayer)) {
-            int stream = MediaUtils.getLegacyStreamType(attrs);
-            int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-            if (Build.VERSION.SDK_INT >= 21
-                    && ClassVerificationHelper.AudioManager.Api21.isVolumeFixed(mAudioManager)) {
-                controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
-            }
-            return MediaController.PlaybackInfo.createPlaybackInfo(
-                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    attrs,
-                    controlType,
-                    mAudioManager.getStreamMaxVolume(stream),
-                    mAudioManager.getStreamVolume(stream));
-        } else {
-            RemoteSessionPlayer remotePlayer = (RemoteSessionPlayer) player;
-            return MediaController.PlaybackInfo.createPlaybackInfo(
-                    MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                    attrs,
-                    remotePlayer.getVolumeControlType(),
-                    remotePlayer.getMaxVolume(),
-                    remotePlayer.getVolume());
-        }
-    }
-
-    @Override
-    public void close() {
-        SessionPlayer player;
-        synchronized (mLock) {
-            if (mClosed) {
-                return;
-            }
-            mClosed = true;
-            if (DEBUG) {
-                Log.d(TAG, "Closing session, id=" + getId() + ", token="
-                        + getToken());
-            }
-
-            player = mPlayer;
-        }
-        player.unregisterPlayerCallback(mPlayerCallback);
-        mMediaButtonIntent.cancel();
-        mSessionLegacyStub.close();
-        if (mBroadcastReceiver != null) {
-            mContext.unregisterReceiver(mBroadcastReceiver);
-        }
-        mCallback.onSessionClosed(mInstance);
-        dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                callback.onDisconnected(seq);
-            }
-        });
-        mHandler.removeCallbacksAndMessages(null);
-        if (mHandlerThread.isAlive()) {
-            mHandlerThread.quitSafely();
-        }
-    }
-
-    @Override
-    @NonNull
-    public SessionPlayer getPlayer() {
-        synchronized (mLock) {
-            return mPlayer;
-        }
-    }
-
-    @Override
-    @NonNull
-    public String getId() {
-        return mSessionId;
-    }
-
-    @Override
-    @NonNull
-    public Uri getUri() {
-        return mSessionUri;
-    }
-
-    @Override
-    @NonNull
-    public SessionToken getToken() {
-        return mSessionToken;
-    }
-
-    @Override
-    @NonNull
-    public List<ControllerInfo> getConnectedControllers() {
-        List<ControllerInfo> controllers = new ArrayList<>();
-        controllers.addAll(mSessionStub.getConnectedControllersManager()
-                .getConnectedControllers());
-        controllers.addAll(mSessionLegacyStub.getConnectedControllersManager()
-                .getConnectedControllers());
-        return controllers;
-    }
-
-    @Override
-    public boolean isConnected(@NonNull ControllerInfo controller) {
-        if (controller == null) {
-            return false;
-        }
-        return mSessionStub.getConnectedControllersManager().isConnected(controller)
-                || mSessionLegacyStub.getConnectedControllersManager().isConnected(controller);
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> setCustomLayout(@NonNull ControllerInfo controller,
-            @NonNull final List<MediaSession.CommandButton> layout) {
-        return dispatchRemoteControllerTask(controller, new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb controller, int seq) throws RemoteException {
-                controller.setCustomLayout(seq, layout);
-            }
-        });
-    }
-
-    @Override
-    public void setAllowedCommands(@NonNull ControllerInfo controller,
-            @NonNull final SessionCommandGroup commands) {
-        if (mSessionStub.getConnectedControllersManager().isConnected(controller)) {
-            mSessionStub.getConnectedControllersManager()
-                    .updateAllowedCommands(controller, commands);
-            dispatchRemoteControllerTaskWithoutReturn(controller, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onAllowedCommandsChanged(seq, commands);
-                }
-            });
-        } else {
-            mSessionLegacyStub.getConnectedControllersManager()
-                    .updateAllowedCommands(controller, commands);
-        }
-    }
-
-    @Override
-    public void broadcastCustomCommand(@NonNull final SessionCommand command,
-            @Nullable final Bundle args) {
-        dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb controller, int seq) throws RemoteException {
-                controller.sendCustomCommand(seq, command, args);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<SessionResult> sendCustomCommand(
-            @NonNull ControllerInfo controller, @NonNull final SessionCommand command,
-            @Nullable final Bundle args) {
-        return dispatchRemoteControllerTask(controller, new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb controller, int seq) throws RemoteException {
-                controller.sendCustomCommand(seq, command, args);
-            }
-        });
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public ListenableFuture<PlayerResult> play() {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                if (player.getPlayerState() != PLAYER_STATE_IDLE) {
-                    return player.play();
-                }
-                final ListenableFuture<PlayerResult> prepareFuture = player.prepare();
-                final ListenableFuture<PlayerResult> playFuture = player.play();
-                if (prepareFuture == null || playFuture == null) {
-                    // Let dispatchPlayerTask() handle such cases.
-                    return null;
-                }
-                return CombinedCommandResultFuture.create(
-                        DIRECT_EXECUTOR, prepareFuture, playFuture);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> pause() {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.pause();
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> prepare() {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.prepare();
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> seekTo(final long pos) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.seekTo(pos);
-            }
-        });
-    }
-
-    @Override
-    @SessionPlayer.PlayerState
-    public int getPlayerState() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getPlayerState();
-            }
-        }, SessionPlayer.PLAYER_STATE_ERROR);
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return dispatchPlayerTask(new PlayerTask<Long>() {
-            @Override
-            public Long run(@NonNull SessionPlayer player) throws Exception {
-                if (isInPlaybackState(player)) {
-                    return player.getCurrentPosition();
-                }
-                return null;
-            }
-        }, SessionPlayer.UNKNOWN_TIME);
-    }
-
-    @Override
-    public long getDuration() {
-        return dispatchPlayerTask(new PlayerTask<Long>() {
-            @Override
-            public Long run(@NonNull SessionPlayer player) throws Exception {
-                if (isInPlaybackState(player)) {
-                    return player.getDuration();
-                }
-                return null;
-            }
-        }, SessionPlayer.UNKNOWN_TIME);
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return dispatchPlayerTask(new PlayerTask<Long>() {
-            @Override
-            public Long run(@NonNull SessionPlayer player) throws Exception {
-                if (isInPlaybackState(player)) {
-                    return player.getBufferedPosition();
-                }
-                return null;
-            }
-        }, SessionPlayer.UNKNOWN_TIME);
-    }
-
-    @Override
-    @SessionPlayer.BuffState
-    public int getBufferingState() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getBufferingState();
-            }
-        }, SessionPlayer.BUFFERING_STATE_UNKNOWN);
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return dispatchPlayerTask(new PlayerTask<Float>() {
-            @Override
-            public Float run(@NonNull SessionPlayer player) throws Exception {
-                if (isInPlaybackState(player)) {
-                    return player.getPlaybackSpeed();
-                }
-                return null;
-            }
-        }, 1.0f);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(final float speed) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.setPlaybackSpeed(speed);
-            }
-        });
-    }
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return dispatchPlayerTask(new PlayerTask<List<MediaItem>>() {
-            @Override
-            public List<MediaItem> run(@NonNull SessionPlayer player) throws Exception {
-                return player.getPlaylist();
-            }
-        }, null);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setPlaylist(@NonNull final List<MediaItem> list,
-            @Nullable final MediaMetadata metadata) {
-        if (list == null) {
-            throw new NullPointerException("list shouldn't be null");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.setPlaylist(list, metadata);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull final MediaItem item) {
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.setMediaItem(item);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(final int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                final List<MediaItem> list = player.getPlaylist();
-                if (index >= list.size()) {
-                    return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                }
-                return player.skipToPlaylistItem(index);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> skipToPreviousItem() {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.skipToPreviousPlaylistItem();
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> skipToNextItem() {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.skipToNextPlaylistItem();
-            }
-        });
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return dispatchPlayerTask(new PlayerTask<MediaMetadata>() {
-            @Override
-            public MediaMetadata run(@NonNull SessionPlayer player) throws Exception {
-                return player.getPlaylistMetadata();
-            }
-        }, null);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> addPlaylistItem(final int index,
-            @NonNull final MediaItem item) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.addPlaylistItem(index, item);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> removePlaylistItem(final int index) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                final List<MediaItem> list = player.getPlaylist();
-                if (index >= list.size()) {
-                    return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                }
-                return player.removePlaylistItem(index);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> replacePlaylistItem(final int index,
-            @NonNull final MediaItem item) {
-        if (index < 0) {
-            throw new IllegalArgumentException("index shouldn't be negative");
-        }
-        if (item == null) {
-            throw new NullPointerException("item shouldn't be null");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.replacePlaylistItem(index, item);
-            }
-        });
-
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> movePlaylistItem(final int fromIndex, final int toIndex) {
-        if (fromIndex < 0 || toIndex < 0) {
-            throw new IllegalArgumentException("indices shouldn't be negative");
-        }
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.movePlaylistItem(fromIndex, toIndex);
-            }
-        });
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return dispatchPlayerTask(new PlayerTask<MediaItem>() {
-            @Override
-            public MediaItem run(@NonNull SessionPlayer player) throws Exception {
-                return player.getCurrentMediaItem();
-            }
-        }, null);
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getCurrentMediaItemIndex();
-            }
-        }, INVALID_ITEM_INDEX);
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getPreviousMediaItemIndex();
-            }
-        }, INVALID_ITEM_INDEX);
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getNextMediaItemIndex();
-            }
-        }, INVALID_ITEM_INDEX);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(
-            @Nullable final MediaMetadata metadata) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.updatePlaylistMetadata(metadata);
-            }
-        });
-    }
-
-    @Override
-    @SessionPlayer.RepeatMode
-    public int getRepeatMode() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getRepeatMode();
-            }
-        }, SessionPlayer.REPEAT_MODE_NONE);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setRepeatMode(
-            final @SessionPlayer.RepeatMode int repeatMode) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.setRepeatMode(repeatMode);
-            }
-        });
-    }
-
-    @Override
-    @SessionPlayer.ShuffleMode
-    public int getShuffleMode() {
-        return dispatchPlayerTask(new PlayerTask<Integer>() {
-            @Override
-            public Integer run(@NonNull SessionPlayer player) throws Exception {
-                return player.getShuffleMode();
-            }
-        }, SessionPlayer.SHUFFLE_MODE_NONE);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setShuffleMode(
-            final @SessionPlayer.ShuffleMode int shuffleMode) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.setShuffleMode(shuffleMode);
-            }
-        });
-    }
-
-    @Override
-    public VideoSize getVideoSize() {
-        return dispatchPlayerTask(new PlayerTask<VideoSize>() {
-            @Override
-            public VideoSize run(@NonNull SessionPlayer player) {
-                return MediaUtils.upcastForPreparceling(player.getVideoSize());
-            }
-        }, new VideoSize(0, 0));
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> setSurface(final Surface surface) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player) {
-                return player.setSurface(surface);
-            }
-        });
-    }
-
-    @Override
-    public List<TrackInfo> getTracks() {
-        return dispatchPlayerTask(new PlayerTask<List<TrackInfo>>() {
-            @Override
-            public List<TrackInfo> run(@NonNull SessionPlayer player) throws Exception {
-                return MediaUtils.upcastForPreparceling(player.getTracks());
-            }
-        }, null);
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> selectTrack(final TrackInfo trackInfo) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.selectTrack(trackInfo);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<PlayerResult> deselectTrack(final TrackInfo trackInfo) {
-        return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
-            @Override
-            public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player)
-                    throws Exception {
-                return player.deselectTrack(trackInfo);
-            }
-        });
-    }
-
-    @Override
-    public TrackInfo getSelectedTrack(final int trackType) {
-        return dispatchPlayerTask(new PlayerTask<TrackInfo>() {
-            @Override
-            public TrackInfo run(@NonNull SessionPlayer player) throws Exception {
-                return MediaUtils.upcastForPreparceling(player.getSelectedTrack(trackType));
-            }
-        }, null);
-    }
-
-    ///////////////////////////////////////////////////
-    // package private and private methods
-    ///////////////////////////////////////////////////
-    @Override
-    @NonNull
-    public MediaSession getInstance() {
-        return mInstance;
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public Executor getCallbackExecutor() {
-        return mCallbackExecutor;
-    }
-
-    @Override
-    public SessionCallback getCallback() {
-        return mCallback;
-    }
-
-    @Override
-    @NonNull
-    public MediaSessionCompat getSessionCompat() {
-        return mSessionLegacyStub.getSessionCompat();
-    }
-
-    @Override
-    public void setLegacyControllerConnectionTimeoutMs(long timeoutMs) {
-        mSessionLegacyStub.setLegacyControllerDisconnectTimeoutMs(timeoutMs);
-    }
-
-    @Override
-    public boolean isClosed() {
-        synchronized (mLock) {
-            return mClosed;
-        }
-    }
-
-    @Override
-    public PlaybackStateCompat createPlaybackStateCompat() {
-        int state = MediaUtils.convertToPlaybackStateCompatState(getPlayerState(),
-                getBufferingState());
-        long allActions = PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_REWIND
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_FAST_FORWARD
-                | PlaybackStateCompat.ACTION_SET_RATING
-                | PlaybackStateCompat.ACTION_SEEK_TO | PlaybackStateCompat.ACTION_PLAY_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
-                | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
-                | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
-                | PlaybackStateCompat.ACTION_PLAY_FROM_URI | PlaybackStateCompat.ACTION_PREPARE
-                | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID
-                | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH
-                | PlaybackStateCompat.ACTION_PREPARE_FROM_URI
-                | PlaybackStateCompat.ACTION_SET_REPEAT_MODE
-                | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
-                | PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED;
-        long queueItemId = MediaUtils.convertToQueueItemId(getCurrentMediaItemIndex());
-        return new PlaybackStateCompat.Builder()
-                .setState(state, getCurrentPosition(), getPlaybackSpeed(),
-                        SystemClock.elapsedRealtime())
-                .setActions(allActions)
-                .setActiveQueueItemId(queueItemId)
-                .setBufferedPosition(getBufferedPosition())
-                .build();
-    }
-
-    @Override
-    public MediaController.PlaybackInfo getPlaybackInfo() {
-        synchronized (mLock) {
-            return mPlaybackInfo;
-        }
-    }
-
-    @Override
-    public PendingIntent getSessionActivity() {
-        return mSessionActivity;
-    }
-
-    MediaBrowserServiceCompat createLegacyBrowserServiceLocked(Context context, SessionToken token,
-            Token sessionToken) {
-        return new MediaSessionServiceLegacyStub(context, this, sessionToken);
-    }
-
-    @Override
-    public void connectFromService(IMediaController caller, int controllerVersion,
-            String packageName, int pid, int uid, @Nullable Bundle connectionHints) {
-        mSessionStub.connect(caller, controllerVersion, packageName, pid, uid, connectionHints);
-    }
-
-    /**
-     * Gets the service binder from the MediaBrowserServiceCompat. Should be only called by the
-     * thread with a Looper.
-     *
-     * @return
-     */
-    @Override
-    public IBinder getLegacyBrowserServiceBinder() {
-        MediaBrowserServiceCompat legacyStub;
-        synchronized (mLock) {
-            if (mBrowserServiceLegacyStub == null) {
-                mBrowserServiceLegacyStub = createLegacyBrowserServiceLocked(mContext,
-                        mSessionToken, mSessionLegacyStub.getSessionCompat().getSessionToken());
-            }
-            legacyStub = mBrowserServiceLegacyStub;
-        }
-        Intent intent = new Intent(MediaBrowserServiceCompat.SERVICE_INTERFACE);
-        return legacyStub.onBind(intent);
-    }
-
-    MediaBrowserServiceCompat getLegacyBrowserService() {
-        synchronized (mLock) {
-            return mBrowserServiceLegacyStub;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    boolean isInPlaybackState(@NonNull SessionPlayer player) {
-        return !isClosed()
-                && player.getPlayerState() != SessionPlayer.PLAYER_STATE_IDLE
-                && player.getPlayerState() != SessionPlayer.PLAYER_STATE_ERROR;
-    }
-
-    private ListenableFuture<PlayerResult> dispatchPlayerTask(
-            @NonNull PlayerTask<ListenableFuture<PlayerResult>> command) {
-        ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
-        result.set(new PlayerResult(RESULT_ERROR_INVALID_STATE, null));
-        return dispatchPlayerTask(command, result);
-    }
-
-    private <T> T dispatchPlayerTask(@NonNull PlayerTask<T> command, T defaultResult) {
-        final SessionPlayer player;
-        synchronized (mLock) {
-            player = mPlayer;
-        }
-        try {
-            if (!isClosed()) {
-                T result = command.run(player);
-                if (result != null) {
-                    return result;
-                }
-            } else if (DEBUG) {
-                Log.d(TAG, "API calls after the close()", new IllegalStateException());
-            }
-        } catch (Exception e) {
-        }
-        return defaultResult;
-    }
-
-    // TODO(jaewan): Remove SuppressLint when removing duplication session callback.
-    @SuppressLint("WrongConstant")
-    private void notifyPlayerUpdatedNotLocked(@Nullable SessionPlayer oldPlayer,
-            @Nullable MediaController.PlaybackInfo oldPlaybackInfo,
-            @NonNull SessionPlayer player, @NonNull MediaController.PlaybackInfo playbackInfo) {
-        dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                callback.onPlayerChanged(seq, oldPlayer, oldPlaybackInfo, player, playbackInfo);
-            }
-        });
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void notifyPlaybackInfoChangedNotLocked(final MediaController.PlaybackInfo info) {
-        dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-            @Override
-            public void run(ControllerCb callback, int seq) throws RemoteException {
-                callback.onPlaybackInfoChanged(seq, info);
-            }
-        });
-    }
-
-    void dispatchRemoteControllerTaskWithoutReturn(@NonNull RemoteControllerTask task) {
-        List<ControllerInfo> controllers =
-                mSessionStub.getConnectedControllersManager().getConnectedControllers();
-        for (int i = 0; i < controllers.size(); i++) {
-            ControllerInfo controller = controllers.get(i);
-            dispatchRemoteControllerTaskWithoutReturn(controller, task);
-        }
-        try {
-            task.run(mSessionLegacyStub.getControllerLegacyCbForBroadcast(), /* seq= */ 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception in using media1 API", e);
-        }
-    }
-
-    void dispatchRemoteControllerTaskWithoutReturn(@NonNull ControllerInfo controller,
-            @NonNull RemoteControllerTask task) {
-        try {
-            final int seq;
-            final SequencedFutureManager manager =
-                    mSessionStub.getConnectedControllersManager()
-                            .getSequencedFutureManager(controller);
-            if (manager != null) {
-                seq = manager.obtainNextSequenceNumber();
-            } else {
-                if (!isConnected(controller)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Skipping dispatching task to disconnected controller"
-                                + ", controller=" + controller);
-                    }
-                    return;
-                }
-                // 0 is OK for legacy controllers, because sequence number is media2 specific.
-                seq = 0;
-            }
-            task.run(controller.getControllerCb(), seq);
-        } catch (DeadObjectException e) {
-            onDeadObjectException(controller, e);
-        } catch (RemoteException e) {
-            // Currently it's TransactionTooLargeException or DeadSystemException.
-            // We'd better to leave log for those cases because
-            //   - TransactionTooLargeException means that we may need to fix our code.
-            //     (e.g. add pagination or special way to deliver Bitmap)
-            //   - DeadSystemException means that errors around it can be ignored.
-            Log.w(TAG, "Exception in " + controller.toString(), e);
-        }
-    }
-
-    private ListenableFuture<SessionResult> dispatchRemoteControllerTask(
-            @NonNull ControllerInfo controller, @NonNull RemoteControllerTask task) {
-        try {
-            final ListenableFuture<SessionResult> future;
-            final int seq;
-            final SequencedFutureManager manager =
-                    mSessionStub.getConnectedControllersManager()
-                            .getSequencedFutureManager(controller);
-            if (manager != null) {
-                future = manager.createSequencedFuture(RESULT_WHEN_CLOSED);
-                seq = ((SequencedFuture<SessionResult>) future).getSequenceNumber();
-            } else {
-                if (!isConnected(controller)) {
-                    return SessionResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-                }
-                // 0 is OK for legacy controllers, because sequence number is media2 specific.
-                seq = 0;
-                // Tell that operation is successful, although we don't know the actual result.
-                future = SessionResult.createFutureWithResult(SessionResult.RESULT_SUCCESS);
-            }
-            task.run(controller.getControllerCb(), seq);
-            return future;
-        } catch (DeadObjectException e) {
-            onDeadObjectException(controller, e);
-            return SessionResult.createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
-        } catch (RemoteException e) {
-            // Currently it's TransactionTooLargeException or DeadSystemException.
-            // We'd better to leave log for those cases because
-            //   - TransactionTooLargeException means that we may need to fix our code.
-            //     (e.g. add pagination or special way to deliver Bitmap)
-            //   - DeadSystemException means that errors around it can be ignored.
-            Log.w(TAG, "Exception in " + controller.toString(), e);
-        }
-        return SessionResult.createFutureWithResult(RESULT_ERROR_UNKNOWN);
-    }
-
-    /**
-     * Removes controller. Call this when DeadObjectException is happened with binder call.
-     */
-    private void onDeadObjectException(ControllerInfo controller, DeadObjectException e) {
-        if (DEBUG) {
-            Log.d(TAG, controller.toString() + " is gone", e);
-        }
-        // Note: Only removing from MediaSessionStub and ignoring (legacy) stubs would be fine for
-        //       now. Because calls to the legacy stubs doesn't throw DeadObjectException.
-        mSessionStub.getConnectedControllersManager().removeController(controller);
-    }
-
-    @Nullable
-    @SuppressWarnings("deprecation")
-    private ComponentName getServiceComponentByAction(@NonNull String action) {
-        PackageManager pm = mContext.getPackageManager();
-        Intent queryIntent = new Intent(action);
-        queryIntent.setPackage(mContext.getPackageName());
-        List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0 /* flags */);
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            return null;
-        }
-        ResolveInfo resolveInfo = resolveInfos.get(0);
-        return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
-    }
-
-    ///////////////////////////////////////////////////
-    // Inner classes
-    ///////////////////////////////////////////////////
-    @FunctionalInterface
-    interface PlayerTask<T> {
-        T run(@NonNull SessionPlayer player) throws Exception;
-    }
-
-    @FunctionalInterface
-    interface RemoteControllerTask {
-        void run(ControllerCb controller, int seq) throws RemoteException;
-    }
-
-    private static class SessionPlayerCallback extends RemoteSessionPlayer.Callback implements
-            MediaItem.OnMetadataChangedListener {
-        private final WeakReference<MediaSessionImplBase> mSession;
-        private MediaItem mMediaItem;
-        private List<MediaItem> mPlaylist;
-        private final PlaylistItemListener mPlaylistItemChangedListener;
-
-        SessionPlayerCallback(MediaSessionImplBase session) {
-            mSession = new WeakReference<>(session);
-            mPlaylistItemChangedListener = new PlaylistItemListener(session);
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(@NonNull final SessionPlayer player,
-                @NonNull final MediaItem item) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null || player == null || session.getPlayer() != player) {
-                return;
-            }
-            if (mMediaItem != null) {
-                mMediaItem.removeOnMetadataChangedListener(this);
-            }
-            if (item != null) {
-                item.addOnMetadataChangedListener(session.mCallbackExecutor, this);
-            }
-            mMediaItem = item;
-            session.getCallback().onCurrentMediaItemChanged(session.getInstance());
-
-            boolean notifyingPended = false;
-            if (item != null) {
-                notifyingPended = updateCurrentMediaItemMetadataWithDuration(
-                        player, item, item.getMetadata());
-            }
-            if (!notifyingPended) {
-                // Forcefully notify, if updateCurrentMediaItemMetadataWithDuration wouldn't.
-                notifyCurrentMediaItemChanged(item);
-            }
-        }
-
-        @Override
-        public void onPlayerStateChanged(@NonNull final SessionPlayer player, final int state) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null || player == null || session.getPlayer() != player) {
-                return;
-            }
-            session.getCallback().onPlayerStateChanged(session.getInstance(), state);
-            updateCurrentMediaItemMetadataWithDuration(player);
-            session.dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlayerStateChanged(seq, SystemClock.elapsedRealtime(),
-                            player.getCurrentPosition(), state);
-                }
-            });
-        }
-
-        @Override
-        public void onBufferingStateChanged(@NonNull final SessionPlayer player,
-                final MediaItem item, final int state) {
-            updateCurrentMediaItemMetadataWithDuration(player);
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onBufferingStateChanged(seq, item, state, player.getBufferedPosition(),
-                            SystemClock.elapsedRealtime(), player.getCurrentPosition());
-                }
-            });
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(@NonNull final SessionPlayer player, final float speed) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaybackSpeedChanged(seq, SystemClock.elapsedRealtime(),
-                            player.getCurrentPosition(), speed);
-                }
-            });
-        }
-
-        @Override
-        public void onSeekCompleted(@NonNull final SessionPlayer player, final long position) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onSeekCompleted(seq, SystemClock.elapsedRealtime(),
-                            player.getCurrentPosition(), position);
-                }
-            });
-        }
-
-        @Override
-        public void onPlaylistChanged(@NonNull final SessionPlayer player,
-                final List<MediaItem> list, final MediaMetadata metadata) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null || player == null || session.getPlayer() != player) {
-                return;
-            }
-            if (mPlaylist != null) {
-                for (int i = 0; i < mPlaylist.size(); i++) {
-                    mPlaylist.get(i).removeOnMetadataChangedListener(mPlaylistItemChangedListener);
-                }
-            }
-            if (list != null) {
-                for (int i = 0; i < list.size(); i++) {
-                    list.get(i).addOnMetadataChangedListener(session.mCallbackExecutor,
-                            mPlaylistItemChangedListener);
-                }
-            }
-            mPlaylist = list;
-
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaylistChanged(seq, list, metadata,
-                            session.getCurrentMediaItemIndex(), session.getPreviousMediaItemIndex(),
-                            session.getNextMediaItemIndex());
-                }
-            });
-        }
-
-        @Override
-        public void onPlaylistMetadataChanged(@NonNull final SessionPlayer player,
-                final MediaMetadata metadata) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaylistMetadataChanged(seq, metadata);
-                }
-            });
-        }
-
-        @Override
-        public void onRepeatModeChanged(@NonNull final SessionPlayer player, final int repeatMode) {
-            final MediaSessionImplBase session = getSession();
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onRepeatModeChanged(seq, repeatMode,
-                            session.getCurrentMediaItemIndex(),
-                            session.getPreviousMediaItemIndex(),
-                            session.getNextMediaItemIndex());
-                }
-            });
-        }
-
-        @Override
-        public void onShuffleModeChanged(@NonNull final SessionPlayer player,
-                final int shuffleMode) {
-            final MediaSessionImplBase session = getSession();
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onShuffleModeChanged(seq, shuffleMode,
-                            session.getCurrentMediaItemIndex(),
-                            session.getPreviousMediaItemIndex(),
-                            session.getNextMediaItemIndex());
-                }
-            });
-        }
-
-        @Override
-        public void onPlaybackCompleted(@NonNull SessionPlayer player) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaybackCompleted(seq);
-                }
-            });
-        }
-
-        @Override
-        public void onAudioAttributesChanged(@NonNull final SessionPlayer player,
-                final AudioAttributesCompat attributes) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null || player == null || session.getPlayer() != player) {
-                return;
-            }
-            MediaController.PlaybackInfo newInfo = session.createPlaybackInfo(player, attributes);
-            MediaController.PlaybackInfo oldInfo;
-            synchronized (session.mLock) {
-                oldInfo = session.mPlaybackInfo;
-                session.mPlaybackInfo = newInfo;
-            }
-            if (!ObjectsCompat.equals(newInfo, oldInfo)) {
-                session.notifyPlaybackInfoChangedNotLocked(newInfo);
-            }
-        }
-
-        @Override
-        public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onVideoSizeChanged(seq, MediaUtils.upcastForPreparceling(size));
-                }
-            });
-        }
-
-        @Override
-        public void onTracksChanged(@NonNull SessionPlayer player,
-                @NonNull List<TrackInfo> tracks) {
-            final MediaSessionImplBase session = getSession();
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onTracksChanged(seq, MediaUtils.upcastForPreparceling(tracks),
-                            MediaUtils.upcastForPreparceling(
-                                    session.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_VIDEO)),
-                            MediaUtils.upcastForPreparceling(
-                                    session.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_AUDIO)),
-                            MediaUtils.upcastForPreparceling(
-                                    session.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE)),
-                            MediaUtils.upcastForPreparceling(
-                                    session.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_METADATA)));
-                }
-            });
-        }
-
-        @Override
-        public void onTrackSelected(@NonNull SessionPlayer player,
-                @NonNull final TrackInfo trackInfo) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onTrackSelected(seq, MediaUtils.upcastForPreparceling(trackInfo));
-                }
-            });
-        }
-
-        @Override
-        public void onTrackDeselected(@NonNull SessionPlayer player,
-                @NonNull final TrackInfo trackInfo) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onTrackDeselected(seq, MediaUtils.upcastForPreparceling(trackInfo));
-                }
-            });
-        }
-
-        @Override
-        public void onSubtitleData(@NonNull final SessionPlayer player,
-                @NonNull final MediaItem item, @NonNull final TrackInfo track,
-                @NonNull final SubtitleData data) {
-            dispatchRemoteControllerTask(player, new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onSubtitleData(seq, item, track, data);
-                }
-            });
-        }
-
-        // Called only when current media item's metadata is changed.
-        @Override
-        public void onMetadataChanged(@NonNull MediaItem currentMediaItem,
-                @Nullable MediaMetadata currentMediaItemMetadata) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null) {
-                return;
-            }
-            SessionPlayer player = session.getPlayer();
-            boolean notifyingPended = updateCurrentMediaItemMetadataWithDuration(
-                    player, currentMediaItem, currentMediaItemMetadata);
-            if (!notifyingPended) {
-                // Forcefully notify, if updateCurrentMediaItemMetadataWithDuration wouldn't.
-                notifyCurrentMediaItemChanged(currentMediaItem);
-            }
-        }
-
-        @Override
-        public void onVolumeChanged(@NonNull RemoteSessionPlayer player, int volume) {
-            MediaSessionImplBase session = getSession();
-            if (session == null) {
-                return;
-            }
-            MediaController.PlaybackInfo newInfo =
-                    session.createPlaybackInfo(player, /* audioAttributes= */ null);
-            MediaController.PlaybackInfo oldInfo;
-            synchronized (session.mLock) {
-                if (session.mPlayer != player) {
-                    return;
-                }
-                oldInfo = session.mPlaybackInfo;
-                session.mPlaybackInfo = newInfo;
-            }
-            if (!ObjectsCompat.equals(newInfo, oldInfo)) {
-                session.notifyPlaybackInfoChangedNotLocked(newInfo);
-            }
-        }
-
-        private MediaSessionImplBase getSession() {
-            final MediaSessionImplBase session = mSession.get();
-            if (session == null && DEBUG) {
-                Log.d(TAG, "Session is closed", new IllegalStateException());
-            }
-            return session;
-        }
-
-        private void dispatchRemoteControllerTask(@NonNull SessionPlayer player,
-                @NonNull RemoteControllerTask task) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null || player == null || session.getPlayer() != player) {
-                return;
-            }
-            session.dispatchRemoteControllerTaskWithoutReturn(task);
-        }
-
-        /**
-         * Update metadata of the player's current media item with duration. Update would be
-         * indirectly notified via {@link #onMetadataChanged}.
-         *
-         * @param player player to get duration
-         * @return {@code true} if updated. {@code false} otherwise.
-         */
-        private boolean updateCurrentMediaItemMetadataWithDuration(@NonNull SessionPlayer player) {
-            MediaItem currentMediaItem = player.getCurrentMediaItem();
-            if (currentMediaItem == null) {
-                return false;
-            }
-            return updateCurrentMediaItemMetadataWithDuration(player, currentMediaItem,
-                    currentMediaItem.getMetadata());
-        }
-
-        /**
-         * Update metadata of the player's current media item with duration. Update would be
-         * indirectly notified via {@link #onMetadataChanged}.
-         *
-         * @param player player to get duration
-         * @param currentMediaItem currentMediaItem. May differ with player.getCurrentMediaItem().
-         * @param currentMediaItemMetadata currentMediaItem's metadata. May differ with
-         *                                 currentMediaItem.getMetadata() due to the timing issue.
-         * @return {@code true} if updated. {@code false} otherwise.
-         */
-        private boolean updateCurrentMediaItemMetadataWithDuration(@NonNull SessionPlayer player,
-                @NonNull MediaItem currentMediaItem,
-                @Nullable MediaMetadata currentMediaItemMetadata) {
-            final long duration = player.getDuration();
-            // Check if the duration from the player can be the currentMediaItem's duration.
-            if (currentMediaItem == player.getCurrentMediaItem()
-                    && player.getPlayerState() != PLAYER_STATE_IDLE && duration > 0
-                    && duration != UNKNOWN_TIME) {
-                MediaMetadata metadataWithDurationUpdate = null;
-                if (currentMediaItemMetadata != null) {
-                    if (!currentMediaItemMetadata.containsKey(METADATA_KEY_DURATION)) {
-                        metadataWithDurationUpdate =
-                                new MediaMetadata.Builder(currentMediaItemMetadata)
-                                        .putLong(METADATA_KEY_DURATION, duration)
-                                        .putLong(METADATA_KEY_PLAYABLE, 1)
-                                        .build();
-                    } else {
-                        long durationFromMetadata =
-                                currentMediaItemMetadata.getLong(METADATA_KEY_DURATION);
-                        if (duration != durationFromMetadata) {
-                            // Warns developers about the mismatch. Don't log media item here to
-                            // keep metadata secure.
-                            Log.w(TAG, "duration mismatch for an item."
-                                    + " duration from player=" + duration
-                                    + " duration from metadata=" + durationFromMetadata
-                                    + ". May be a timing issue?");
-                            // Trust duration in the metadata set by developer.
-                            // In theory, duration may differ if the current item has been
-                            // changed before the getDuration(). So it's better not touch
-                            // duration set by developer.
-                        }
-                    }
-                } else {
-                    metadataWithDurationUpdate = new MediaMetadata.Builder()
-                            .putLong(METADATA_KEY_DURATION, duration)
-                            .putString(METADATA_KEY_MEDIA_ID, currentMediaItem.getMediaId())
-                            .putLong(METADATA_KEY_PLAYABLE, 1)
-                            .build();
-                }
-                if (metadataWithDurationUpdate != null) {
-                    // Note: Don't check whether the currentMediaItemMetadata is still the
-                    // currentMediaItem's metadata. Do best effort for not missing any notification
-                    // changes.
-                    // Note that updated metadata will be notified anyway via later
-                    // SessionPlayerCallback#onMetadataChanged().
-                    currentMediaItem.setMetadata(metadataWithDurationUpdate);
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private void notifyCurrentMediaItemChanged(@Nullable MediaItem currentMediaItem) {
-            final MediaSessionImplBase session = getSession();
-            if (session == null) {
-                return;
-            }
-            dispatchRemoteControllerTask(session.getPlayer(), new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onCurrentMediaItemChanged(seq, currentMediaItem,
-                            session.getCurrentMediaItemIndex(),
-                            session.getPreviousMediaItemIndex(),
-                            session.getNextMediaItemIndex());
-                }
-            });
-        }
-    }
-
-    static class PlaylistItemListener implements MediaItem.OnMetadataChangedListener {
-        private final WeakReference<MediaSessionImplBase> mSession;
-
-        PlaylistItemListener(MediaSessionImplBase session) {
-            mSession = new WeakReference<>(session);
-        }
-
-        @Override
-        public void onMetadataChanged(@NonNull final MediaItem item,
-                MediaMetadata metadata) {
-            final MediaSessionImplBase session = mSession.get();
-            if (session == null || item == null) {
-                return;
-            }
-            final List<MediaItem> list = session.getPlaylist();
-            if (list == null) {
-                return;
-            }
-            for (int i = 0; i < list.size(); i++) {
-                if (item.equals(list.get(i))) {
-                    session.dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                        @Override
-                        public void run(ControllerCb callback, int seq) throws RemoteException {
-                            callback.onPlaylistChanged(seq, list,
-                                    session.getPlaylistMetadata(),
-                                    session.getCurrentMediaItemIndex(),
-                                    session.getPreviousMediaItemIndex(),
-                                    session.getNextMediaItemIndex());
-                        }
-                    });
-                    return;
-                }
-            }
-        }
-    }
-
-    static final class CombinedCommandResultFuture<T extends BaseResult>
-            extends AbstractResolvableFuture<T> {
-        final ListenableFuture<T>[] mFutures;
-        AtomicInteger mSuccessCount = new AtomicInteger(0);
-
-        @SafeVarargs
-        public static <U extends BaseResult> CombinedCommandResultFuture<U> create(
-                Executor executor, ListenableFuture<U>... futures) {
-            return new CombinedCommandResultFuture<>(executor, futures);
-        }
-
-        private CombinedCommandResultFuture(Executor executor,
-                ListenableFuture<T>[] futures) {
-            mFutures = futures;
-            for (int i = 0; i < mFutures.length; ++i) {
-                final int cur = i;
-                mFutures[i].addListener(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            T result = mFutures[cur].get();
-                            int resultCode = result.getResultCode();
-                            if (resultCode != SessionResult.RESULT_SUCCESS
-                                    && resultCode != RESULT_INFO_SKIPPED) {
-                                for (int j = 0; j < mFutures.length; ++j) {
-                                    if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
-                                            && cur != j) {
-                                        mFutures[j].cancel(true);
-                                    }
-                                }
-                                set(result);
-                            } else {
-                                int cnt = mSuccessCount.incrementAndGet();
-                                if (cnt == mFutures.length) {
-                                    set(result);
-                                }
-                            }
-                        } catch (Exception e) {
-                            for (int j = 0; j < mFutures.length; ++j) {
-                                if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
-                                        && cur != j) {
-                                    mFutures[j].cancel(true);
-                                }
-                            }
-                            setException(e);
-                        }
-                    }
-                }, executor);
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final class MediaButtonReceiver extends BroadcastReceiver {
-        @SuppressWarnings("deprecation")
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
-                return;
-            }
-            Uri sessionUri = intent.getData();
-            if (!ObjectsCompat.equals(sessionUri, mSessionUri)) {
-                return;
-            }
-            KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-            if (keyEvent == null) {
-                return;
-            }
-            getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
-        }
-    };
-
-    @RequiresApi(33)
-    private static class Api33 {
-        @DoNotInline
-        static void registerReceiver(@NonNull Context context, @NonNull BroadcastReceiver receiver,
-                @NonNull IntentFilter filter, int flags) {
-            context.registerReceiver(receiver, filter, flags);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
deleted file mode 100644
index 175835b..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
+++ /dev/null
@@ -1,1135 +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.media2.session;
-
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
-import static androidx.media2.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
-import static androidx.media2.session.SessionCommand.COMMAND_VERSION_CURRENT;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.media.MediaSessionManager;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaSession.ControllerCb;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand.CommandCode;
-
-import java.io.Closeable;
-import java.util.List;
-import java.util.Set;
-
-// Getting the commands from MediaControllerCompat'
-class MediaSessionLegacyStub extends MediaSessionCompat.Callback implements Closeable {
-
-    private static final String TAG = "MediaSessionLegacyStub";
-    private static final String DEFAULT_MEDIA_SESSION_TAG_PREFIX = "androidx.media2.session.id";
-    private static final String DEFAULT_MEDIA_SESSION_TAG_DELIM = ".";
-
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // Used to call onDisconnected() after the timeout.
-    private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 300_000; // 5 min.
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static final SparseArray<SessionCommand> sCommandsForOnCommandRequest =
-            new SparseArray<>();
-
-    static {
-        SessionCommandGroup group = new SessionCommandGroup.Builder()
-                .addAllPlayerCommands(COMMAND_VERSION_CURRENT)
-                .addAllVolumeCommands(COMMAND_VERSION_CURRENT)
-                .build();
-        Set<SessionCommand> commands = group.getCommands();
-        for (SessionCommand command : commands) {
-            sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
-        }
-    }
-
-    final ConnectedControllersManager<RemoteUserInfo> mConnectedControllersManager;
-
-    final MediaSession.MediaSessionImpl mSessionImpl;
-    final MediaSessionManager mSessionManager;
-    final Context mContext;
-    final ControllerCb mControllerLegacyCbForBroadcast;
-    final ConnectionTimeoutHandler mConnectionTimeoutHandler;
-    final MediaSessionCompat mSessionCompat;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    volatile long mConnectionTimeoutMs;
-
-    private final Handler mHandler;
-
-    MediaSessionLegacyStub(MediaSession.MediaSessionImpl session,
-            ComponentName mbrComponent, PendingIntent mediaButtonIntent, Handler handler) {
-        mSessionImpl = session;
-        mContext = mSessionImpl.getContext();
-        mSessionManager = MediaSessionManager.getSessionManager(mContext);
-        mControllerLegacyCbForBroadcast = new ControllerLegacyCbForBroadcast();
-        mConnectionTimeoutHandler = new ConnectionTimeoutHandler(handler.getLooper());
-        mConnectedControllersManager = new ConnectedControllersManager<>(session);
-        mConnectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS;
-        mHandler = handler;
-
-        String sessionCompatId = TextUtils.join(DEFAULT_MEDIA_SESSION_TAG_DELIM,
-                new String[] {DEFAULT_MEDIA_SESSION_TAG_PREFIX, session.getId()});
-        mSessionCompat = new MediaSessionCompat(mContext,
-                sessionCompatId,
-                mbrComponent,
-                mediaButtonIntent, session.getToken().getExtras(),
-                session.getToken());
-        mSessionCompat.setSessionActivity(session.getSessionActivity());
-        mSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
-        // Note: Rest of mSessionCompat initialization will be done via
-        // {@link ControllerLegacyCbForBroadcast#onPlayerChanged} and {@link #start}, called
-        // indirectly by {@link MediaSessionImplBase#MediaSessionImplBase}.
-    }
-
-    /**
-     * Starts to receive and send commands.
-     */
-    public void start() {
-        mSessionCompat.setCallback(this, mHandler);
-        mSessionCompat.setActive(true);
-    }
-
-    @Override
-    public void close() {
-        mSessionCompat.release();
-    }
-
-    public MediaSessionCompat getSessionCompat() {
-        return mSessionCompat;
-    }
-
-    @Override
-    public void onCommand(final String commandName, final Bundle args, final ResultReceiver cb) {
-        if (commandName == null) {
-            return;
-        }
-        final SessionCommand command = new SessionCommand(commandName, null);
-        dispatchSessionTask(command, new SessionTask() {
-            @Override
-            public void run(final ControllerInfo controller) throws RemoteException {
-                SessionResult result = mSessionImpl.getCallback().onCustomCommand(
-                        mSessionImpl.getInstance(), controller, command, args);
-                if (cb != null) {
-                    cb.send(result.getResultCode(), result.getCustomCommandResult());
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onPrepare() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PREPARE, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                mSessionImpl.prepare();
-            }
-        });
-    }
-
-    @Override
-    public void onPrepareFromMediaId(final String mediaId, final Bundle extras) {
-        Uri mediaUri = new Uri.Builder()
-                .scheme(MediaConstants.MEDIA_URI_SCHEME)
-                .authority(MediaConstants.MEDIA_URI_AUTHORITY)
-                .path(MediaConstants.MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID)
-                .appendQueryParameter(MediaConstants.MEDIA_URI_QUERY_ID, mediaId)
-                .build();
-        onPrepareFromUri(mediaUri, extras);
-    }
-
-    @Override
-    public void onPrepareFromSearch(final String query, final Bundle extras) {
-        Uri mediaUri = new Uri.Builder()
-                .scheme(MediaConstants.MEDIA_URI_SCHEME)
-                .authority(MediaConstants.MEDIA_URI_AUTHORITY)
-                .path(MediaConstants.MEDIA_URI_PATH_PREPARE_FROM_SEARCH)
-                .appendQueryParameter(MediaConstants.MEDIA_URI_QUERY_QUERY, query)
-                .build();
-        onPrepareFromUri(mediaUri, extras);
-    }
-
-    @Override
-    public void onPrepareFromUri(final Uri mediaUri, final Bundle extras) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                if (mSessionImpl.getCallback().onSetMediaUri(mSessionImpl.getInstance(),
-                        controller, mediaUri, extras) == RESULT_SUCCESS) {
-                    mSessionImpl.prepare();
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onPlay() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PLAY, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                mSessionImpl.play();
-            }
-        });
-    }
-
-    @Override
-    public void onPlayFromMediaId(final String mediaId, final Bundle extras) {
-        Uri mediaUri = new Uri.Builder()
-                .scheme(MediaConstants.MEDIA_URI_SCHEME)
-                .authority(MediaConstants.MEDIA_URI_AUTHORITY)
-                .path(MediaConstants.MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID)
-                .appendQueryParameter(MediaConstants.MEDIA_URI_QUERY_ID, mediaId)
-                .build();
-        onPlayFromUri(mediaUri, extras);
-    }
-
-    @Override
-    public void onPlayFromSearch(final String query, final Bundle extras) {
-        Uri mediaUri = new Uri.Builder()
-                .scheme(MediaConstants.MEDIA_URI_SCHEME)
-                .authority(MediaConstants.MEDIA_URI_AUTHORITY)
-                .path(MediaConstants.MEDIA_URI_PATH_PLAY_FROM_SEARCH)
-                .appendQueryParameter(MediaConstants.MEDIA_URI_QUERY_QUERY, query)
-                .build();
-        onPlayFromUri(mediaUri, extras);
-    }
-
-    @Override
-    public void onPlayFromUri(final Uri mediaUri, final Bundle extras) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                if (mSessionImpl.getCallback().onSetMediaUri(mSessionImpl.getInstance(),
-                        controller, mediaUri, extras) == RESULT_SUCCESS) {
-                    mSessionImpl.play();
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onPause() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PAUSE, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                mSessionImpl.pause();
-            }
-        });
-    }
-
-    @Override
-    public void onStop() {
-        // Here, we don't call SessionPlayer#reset() since it may result removing
-        // all callbacks from the player. Instead, we pause and seek to zero.
-        // Here, we check both permissions: Pause / SeekTo.
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PAUSE, new SessionTask() {
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                handleTaskOnExecutor(controller, null,
-                        SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, new SessionTask() {
-                            @SuppressWarnings("FutureReturnValueIgnored")
-                            @Override
-                            public void run(ControllerInfo controller) throws RemoteException {
-                                mSessionImpl.pause();
-                                mSessionImpl.seekTo(0);
-                            }
-                        });
-            }
-        });
-    }
-
-    @Override
-    public void onSeekTo(final long pos) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                mSessionImpl.seekTo(pos);
-            }
-        });
-    }
-
-    @Override
-    public void onSkipToNext() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.skipToNextItem();
-                    }
-                });
-    }
-
-    @Override
-    public void onSkipToPrevious() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.skipToPreviousItem();
-                    }
-                });
-    }
-
-    @Override
-    public void onSetPlaybackSpeed(final float speed) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, new SessionTask() {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void run(ControllerInfo controller) throws RemoteException {
-                mSessionImpl.setPlaybackSpeed(speed);
-            }
-        });
-    }
-
-    @Override
-    public void onSkipToQueueItem(final long queueId) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        List<MediaItem> playlist = mSessionImpl.getPlayer().getPlaylist();
-                        if (playlist == null) {
-                            return;
-                        }
-                        // Use queueId as an index as we've published {@link QueueItem} as so.
-                        // see: {@link MediaUtils#convertToQueueItemList}.
-                        mSessionImpl.skipToPlaylistItem((int) queueId);
-                    }
-                });
-    }
-
-    @Override
-    public void onFastForward() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD,
-                new SessionTask() {
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.getCallback().onFastForward(
-                                mSessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void onRewind() {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_SESSION_REWIND,
-                new SessionTask() {
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.getCallback().onRewind(mSessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void onSetRating(final RatingCompat rating) {
-        onSetRating(rating, null);
-    }
-
-    @Override
-    public void onSetRating(final RatingCompat rating, Bundle extras) {
-        if (rating == null) {
-            return;
-        }
-        // extras is ignored.
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_SESSION_SET_RATING,
-                new SessionTask() {
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        MediaItem currentItem = mSessionImpl.getCurrentMediaItem();
-                        if (currentItem == null) {
-                            return;
-                        }
-                        mSessionImpl.getCallback().onSetRating(mSessionImpl.getInstance(),
-                                controller, currentItem.getMediaId(),
-                                MediaUtils.convertToRating(rating));
-                    }
-                });
-    }
-
-    @Override
-    public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
-        // no-op
-    }
-
-    @Override
-    public void onSetCaptioningEnabled(boolean enabled) {
-        // no-op
-    }
-
-    @Override
-    public void onSetRepeatMode(final int repeatMode) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.setRepeatMode(repeatMode);
-                    }
-                });
-    }
-
-    @Override
-    public void onSetShuffleMode(final int shuffleMode) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        mSessionImpl.setShuffleMode(shuffleMode);
-                    }
-                });
-    }
-
-    @Override
-    public void onAddQueueItem(final MediaDescriptionCompat description) {
-        onAddQueueItem(description, Integer.MAX_VALUE);
-    }
-
-    @Override
-    public void onAddQueueItem(final MediaDescriptionCompat description, final int index) {
-        if (description == null) {
-            return;
-        }
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        String mediaId = description.getMediaId();
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "onAddQueueItem(): Media ID shouldn't be empty");
-                            return;
-                        }
-                        MediaItem newItem = mSessionImpl.getCallback().onCreateMediaItem(
-                                mSessionImpl.getInstance(), controller, mediaId);
-                        mSessionImpl.addPlaylistItem(index, newItem);
-                    }
-                });
-    }
-
-    @Override
-    public void onRemoveQueueItem(final MediaDescriptionCompat description) {
-        if (description == null) {
-            return;
-        }
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        String mediaId = description.getMediaId();
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "onRemoveQueueItem(): Media ID shouldn't be null");
-                            return;
-                        }
-                        List<MediaItem> playlist = mSessionImpl.getPlaylist();
-                        for (int i = 0; i < playlist.size(); i++) {
-                            MediaItem item = playlist.get(i);
-                            if (TextUtils.equals(item.getMediaId(), mediaId)) {
-                                mSessionImpl.removePlaylistItem(i);
-                                return;
-                            }
-                        }
-                    }
-                });
-    }
-
-    @Override
-    public void onRemoveQueueItemAt(final int index) {
-        dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new SessionTask() {
-                    @SuppressWarnings("FutureReturnValueIgnored")
-                    @Override
-                    public void run(ControllerInfo controller) throws RemoteException {
-                        if (index < 0) {
-                            Log.w(TAG, "onRemoveQueueItem(): index shouldn't be negative");
-                            return;
-                        }
-                        mSessionImpl.removePlaylistItem(index);
-                    }
-                });
-    }
-
-    ControllerCb getControllerLegacyCbForBroadcast() {
-        return mControllerLegacyCbForBroadcast;
-    }
-
-    ConnectedControllersManager<RemoteUserInfo> getConnectedControllersManager() {
-        return mConnectedControllersManager;
-    }
-
-    private void dispatchSessionTask(@CommandCode final int commandCode,
-            @NonNull final SessionTask task) {
-        dispatchSessionTaskInternal(null, commandCode, task);
-    }
-
-    private void dispatchSessionTask(@NonNull final SessionCommand sessionCommand,
-            @NonNull final SessionTask task) {
-        dispatchSessionTaskInternal(sessionCommand, SessionCommand.COMMAND_CODE_CUSTOM, task);
-    }
-
-    @SuppressWarnings("ObjectToString")
-    private void dispatchSessionTaskInternal(@Nullable final SessionCommand sessionCommand,
-            @CommandCode final int commandCode, @NonNull final SessionTask task) {
-        if (mSessionImpl.isClosed()) {
-            return;
-        }
-        final RemoteUserInfo remoteUserInfo =
-                mSessionCompat.getCurrentControllerInfo();
-        if (remoteUserInfo == null) {
-            Log.d(TAG, "RemoteUserInfo is null, ignoring command=" + sessionCommand
-                    + ", commandCode=" + commandCode);
-            return;
-        }
-        mSessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                if (mSessionImpl.isClosed()) {
-                    return;
-                }
-                ControllerInfo controller =
-                        mConnectedControllersManager.getController(remoteUserInfo);
-                if (controller == null) {
-                    // Try connect.
-                    controller = new ControllerInfo(
-                            remoteUserInfo, MediaUtils.VERSION_UNKNOWN,
-                            mSessionManager.isTrustedForMediaControl(remoteUserInfo),
-                            new ControllerLegacyCb(remoteUserInfo), /* connectionHints= */ null);
-
-                    SessionCommandGroup allowedCommands = mSessionImpl.getCallback().onConnect(
-                            mSessionImpl.getInstance(), controller);
-                    if (allowedCommands == null) {
-                        try {
-                            controller.getControllerCb().onDisconnected(/* seq= */ 0);
-                        } catch (RemoteException ex) {
-                            // Controller may have died prematurely.
-                        }
-                        return;
-                    }
-                    mConnectedControllersManager.addController(
-                            controller.getRemoteUserInfo(), controller, allowedCommands);
-                }
-
-                // Reset disconnect timeout.
-                mConnectionTimeoutHandler.disconnectControllerAfterTimeout(
-                        controller, mConnectionTimeoutMs);
-                handleTaskOnExecutor(controller, sessionCommand, commandCode, task);
-            }
-        });
-    }
-
-    /* synthetic access */
-    @SuppressWarnings({"WeakerAccess", "ObjectToString"})
-    void handleTaskOnExecutor(@NonNull final ControllerInfo controller,
-            @Nullable final SessionCommand sessionCommand, @CommandCode final int commandCode,
-            @NonNull final SessionTask task) {
-        SessionCommand command;
-        if (sessionCommand != null) {
-            if (!mConnectedControllersManager.isAllowedCommand(controller, sessionCommand)) {
-                return;
-            }
-            command = sCommandsForOnCommandRequest.get(sessionCommand.getCommandCode());
-        } else {
-            if (!mConnectedControllersManager.isAllowedCommand(controller, commandCode)) {
-                return;
-            }
-            command = sCommandsForOnCommandRequest.get(commandCode);
-        }
-        if (command != null) {
-            int resultCode = mSessionImpl.getCallback().onCommandRequest(
-                    mSessionImpl.getInstance(), controller, command);
-            if (resultCode != RESULT_SUCCESS) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command (" + command + ") from "
-                            + controller + " was rejected by " + mSessionImpl);
-                }
-                return;
-            }
-        }
-        try {
-            task.run(controller);
-        } catch (RemoteException e) {
-            // Currently it's TransactionTooLargeException or DeadSystemException.
-            // We'd better to leave log for those cases because
-            //   - TransactionTooLargeException means that we may need to fix our code.
-            //     (e.g. add pagination or special way to deliver Bitmap)
-            //   - DeadSystemException means that errors around it can be ignored.
-            Log.w(TAG, "Exception in " + controller, e);
-        }
-    }
-
-    public void setLegacyControllerDisconnectTimeoutMs(long timeoutMs) {
-        mConnectionTimeoutMs = timeoutMs;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static VolumeProviderCompat createVolumeProviderCompat(
-            @NonNull RemoteSessionPlayer player) {
-        return new VolumeProviderCompat(player.getVolumeControlType(), player.getMaxVolume(),
-                player.getVolume()) {
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onSetVolumeTo(int volume) {
-                player.setVolume(volume);
-            }
-
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onAdjustVolume(int direction) {
-                player.adjustVolume(direction);
-            }
-        };
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static int getRatingType(@Nullable Rating rating) {
-        if (rating instanceof HeartRating) {
-            return RatingCompat.RATING_HEART;
-        } else if (rating instanceof ThumbRating) {
-            return RatingCompat.RATING_THUMB_UP_DOWN;
-        } else if (rating instanceof StarRating) {
-            switch (((StarRating) rating).getMaxStars()) {
-                case 3:
-                    return RatingCompat.RATING_3_STARS;
-                case 4:
-                    return RatingCompat.RATING_4_STARS;
-                case 5:
-                    return RatingCompat.RATING_5_STARS;
-                default:
-                    return RatingCompat.RATING_NONE;
-            }
-        } else if (rating instanceof PercentageRating) {
-            return RatingCompat.RATING_PERCENTAGE;
-        }
-        return RatingCompat.RATING_NONE;
-    }
-
-    @FunctionalInterface
-    private interface SessionTask {
-        void run(ControllerInfo controller) throws RemoteException;
-    }
-
-    @SuppressWarnings("ClassCanBeStatic")
-    final class ControllerLegacyCb extends MediaSession.ControllerCb {
-        private final RemoteUserInfo mRemoteUserInfo;
-
-        ControllerLegacyCb(RemoteUserInfo remoteUserInfo) {
-            mRemoteUserInfo = remoteUserInfo;
-        }
-
-        @Override
-        void onPlayerResult(int seq, PlayerResult result) throws RemoteException {
-            // no-op.
-        }
-
-        @Override
-        void onSessionResult(int seq, SessionResult result) throws RemoteException {
-            // no-op.
-        }
-
-        @Override
-        void onLibraryResult(int seq, LibraryResult result) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
-                @Nullable PlaybackInfo oldPlaybackInfo, @NonNull SessionPlayer player,
-                @NonNull PlaybackInfo playbackInfo) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
-            // no-op.
-        }
-
-        @Override
-        void onPlaybackInfoChanged(int seq, @NonNull PlaybackInfo info) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onAllowedCommandsChanged(int seq, @NonNull SessionCommandGroup commands)
-                throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void sendCustomCommand(int seq, @NonNull SessionCommand command, Bundle args)
-                throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int playerState)
-                throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed)
-                throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onBufferingStateChanged(int seq, @NonNull MediaItem item, int bufferingState,
-                long bufferedPositionMs, long eventTimeMs, long positionMs) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long position)
-                throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onCurrentMediaItemChanged(int seq, MediaItem item, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onPlaylistChanged(int seq, @NonNull List<MediaItem> playlist, MediaMetadata metadata,
-                int currentIdx, int previousIdx, int nextIdx) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onPlaylistMetadataChanged(int seq, MediaMetadata metadata) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onPlaybackCompleted(int seq) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // no-op
-        }
-        @Override
-        void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onDisconnected(int seq) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTracksChanged(int seq, List<TrackInfo> tracks,
-                TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-                TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
-                throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTrackSelected(int seq, TrackInfo trackInfo) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTrackDeselected(int seq, TrackInfo trackInfo) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onSubtitleData(int seq, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) {
-            // no-op
-        }
-
-        @Override
-        public int hashCode() {
-            return ObjectsCompat.hash(mRemoteUserInfo);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null || obj.getClass() != ControllerLegacyCb.class) {
-                return false;
-            }
-            ControllerLegacyCb other = (ControllerLegacyCb) obj;
-            return ObjectsCompat.equals(mRemoteUserInfo, other.mRemoteUserInfo);
-        }
-    }
-
-    // TODO: Find a way to notify error through PlaybackStateCompat
-    final class ControllerLegacyCbForBroadcast extends MediaSession.ControllerCb {
-        ControllerLegacyCbForBroadcast() {
-        }
-
-        @Override
-        void onPlayerResult(int seq, PlayerResult result) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onSessionResult(int seq, SessionResult result) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onLibraryResult(int seq, LibraryResult result) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
-                @Nullable PlaybackInfo oldPlaybackInfo,
-                @NonNull SessionPlayer player, @NonNull PlaybackInfo playbackInfo)
-                throws RemoteException {
-            // Tells the playlist change first, so current media item index change notification
-            // can point to the valid current media item in the playlist.
-            if (oldPlayer == null
-                    || !ObjectsCompat.equals(oldPlayer.getPlaylist(), player.getPlaylist())) {
-                onPlaylistChanged(seq,
-                        player.getPlaylist(), player.getPlaylistMetadata(),
-                        player.getCurrentMediaItemIndex(),
-                        player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            } else if (oldPlayer == null
-                    || !ObjectsCompat.equals(oldPlayer.getPlaylistMetadata(),
-                            player.getPlaylistMetadata())) {
-                onPlaylistMetadataChanged(seq, player.getPlaylistMetadata());
-            }
-            if (oldPlayer == null || oldPlayer.getShuffleMode() != player.getShuffleMode()) {
-                onShuffleModeChanged(seq, player.getShuffleMode(),
-                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            }
-            if (oldPlayer == null || oldPlayer.getRepeatMode() != player.getRepeatMode()) {
-                onRepeatModeChanged(seq, player.getRepeatMode(),
-                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            }
-            if (oldPlayer == null
-                    || !ObjectsCompat.equals(oldPlayer.getCurrentMediaItem(),
-                            player.getCurrentMediaItem())) {
-                // Note: This will update PlaybackStateCompat.
-                onCurrentMediaItemChanged(seq, player.getCurrentMediaItem(),
-                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            } else {
-                // If PlaybackStateCompat isn't updated by above if-statement, forcefully update
-                // PlaybackStateCompat to tell the latest position and its event
-                // time. This would also update playback speed and buffering/player state.
-                mSessionCompat.setPlaybackState(mSessionImpl.createPlaybackStateCompat());
-            }
-
-            // Forcefully update playback info to update VolumeProviderCompat attached to the
-            // old player.
-            onPlaybackInfoChanged(seq, playbackInfo);
-        }
-
-        @Override
-        void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void onPlaybackInfoChanged(int seq,
-                @NonNull PlaybackInfo playbackInfo) throws RemoteException {
-            if (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
-                VolumeProviderCompat volumeProviderCompat =
-                        createVolumeProviderCompat(
-                                (RemoteSessionPlayer) mSessionImpl.getPlayer());
-                mSessionCompat.setPlaybackToRemote(volumeProviderCompat);
-            } else {
-                int stream = MediaUtils.getLegacyStreamType(playbackInfo.getAudioAttributes());
-                mSessionCompat.setPlaybackToLocal(stream);
-            }
-        }
-
-        @Override
-        void onAllowedCommandsChanged(int seq, @NonNull SessionCommandGroup commands)
-                throws RemoteException {
-            throw new AssertionError("This shouldn't be called");
-        }
-
-        @Override
-        void sendCustomCommand(int seq, @NonNull SessionCommand command, Bundle args)
-                throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int playerState)
-                throws RemoteException {
-            // Note: This method does not use any of the given arguments.
-            mSessionCompat.setPlaybackState(
-                    mSessionImpl.createPlaybackStateCompat());
-        }
-
-        @Override
-        void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed)
-                throws RemoteException {
-            // Note: This method does not use any of the given arguments.
-            mSessionCompat.setPlaybackState(
-                    mSessionImpl.createPlaybackStateCompat());
-        }
-
-        @Override
-        void onBufferingStateChanged(int seq, @NonNull MediaItem item, int bufferingState,
-                long bufferedPositionMs, long eventTimeMs, long positionMs) throws RemoteException {
-            // Note: This method does not use any of the given arguments.
-            mSessionCompat.setPlaybackState(
-                    mSessionImpl.createPlaybackStateCompat());
-        }
-
-        @Override
-        void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long position)
-                throws RemoteException {
-            // Note: This method does not use any of the given arguments.
-            mSessionCompat.setPlaybackState(
-                    mSessionImpl.createPlaybackStateCompat());
-        }
-
-        @Override
-        void onCurrentMediaItemChanged(int seq, MediaItem item, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            MediaMetadata metadata = (item == null) ? null : item.getMetadata();
-            mSessionCompat.setMetadata(MediaUtils.convertToMediaMetadataCompat(metadata));
-            int ratingType = getRatingType(
-                    (metadata == null)
-                            ? null
-                            : metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
-            mSessionCompat.setRatingType(ratingType);
-            mSessionCompat.setPlaybackState(
-                    mSessionImpl.createPlaybackStateCompat());
-        }
-
-        @Override
-        void onPlaylistChanged(int seq, @NonNull List<MediaItem> playlist, MediaMetadata metadata,
-                int currentIdx, int previousIdx, int nextIdx) throws RemoteException {
-            if (Build.VERSION.SDK_INT < 21) {
-                if (playlist == null) {
-                    mSessionCompat.setQueue(null);
-                } else {
-                    // In order to avoid TransactionTooLargeException for below API 21, we need to
-                    // cut the list so that it doesn't exceed the binder transaction limit.
-                    List<QueueItem> queueItemList = MediaUtils.convertToQueueItemList(playlist);
-                    List<QueueItem> truncatedList = MediaUtils.truncateListBySize(
-                            queueItemList, TRANSACTION_SIZE_LIMIT_IN_BYTES);
-                    if (truncatedList.size() != playlist.size()) {
-                        Log.i(TAG, "Sending " + truncatedList.size() + " items out of "
-                                + playlist.size());
-                    }
-                    mSessionCompat.setQueue(truncatedList);
-                }
-
-            } else {
-                // Framework MediaSession#setQueue() uses ParceledListSlice,
-                // which means we can safely send long lists.
-                mSessionCompat.setQueue(
-                        MediaUtils.convertToQueueItemList(playlist));
-            }
-            onPlaylistMetadataChanged(seq, metadata);
-        }
-
-        @Override
-        void onPlaylistMetadataChanged(int seq, MediaMetadata metadata) throws RemoteException {
-            // Since there is no 'queue metadata', only set title of the queue.
-            CharSequence oldTitle = mSessionCompat.getController().getQueueTitle();
-            CharSequence newTitle = null;
-
-            if (metadata != null) {
-                newTitle = metadata.getText(METADATA_KEY_DISPLAY_TITLE);
-                if (newTitle == null) {
-                    newTitle = metadata.getText(METADATA_KEY_TITLE);
-                }
-            }
-
-            if (!TextUtils.equals(oldTitle, newTitle)) {
-                mSessionCompat.setQueueTitle(newTitle);
-            }
-        }
-
-        @Override
-        void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            mSessionCompat.setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            mSessionCompat.setRepeatMode(repeatMode);
-        }
-
-        @Override
-        void onPlaybackCompleted(int seq) throws RemoteException {
-            PlaybackStateCompat state = mSessionImpl.createPlaybackStateCompat();
-            if (state.getState() != PlaybackStateCompat.STATE_PAUSED) {
-                state = new PlaybackStateCompat.Builder(state)
-                        .setState(PlaybackStateCompat.STATE_PAUSED, state.getPosition(),
-                                state.getPlaybackSpeed())
-                        .build();
-            }
-            mSessionCompat.setPlaybackState(state);
-        }
-
-        @Override
-        void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // no-op
-        }
-        @Override
-        void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                LibraryParams params) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onDisconnected(int seq) throws RemoteException {
-            // no-op. Calling MediaSessionCompat#release() is already done in close().
-        }
-
-        @Override
-        void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTracksChanged(int seq, List<TrackInfo> tracks,
-                TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-                TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
-                throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTrackSelected(int seq, TrackInfo trackInfo) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onTrackDeselected(int seq, TrackInfo trackInfo) throws RemoteException {
-            // no-op
-        }
-
-        @Override
-        void onSubtitleData(int seq, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data) {
-            // no-op
-        }
-    }
-
-    private class ConnectionTimeoutHandler extends Handler {
-        private static final int MSG_CONNECTION_TIMED_OUT = 1001;
-
-        ConnectionTimeoutHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            ControllerInfo controller = (ControllerInfo) msg.obj;
-            if (mConnectedControllersManager.isConnected(controller)) {
-                try {
-                    controller.getControllerCb().onDisconnected(/* seq= */ 0);
-                } catch (RemoteException ex) {
-                    // Controller may have died prematurely.
-                }
-                mConnectedControllersManager.removeController(controller);
-            }
-        }
-
-        public void disconnectControllerAfterTimeout(@NonNull ControllerInfo controller,
-                long disconnectTimeoutMs) {
-            removeMessages(MSG_CONNECTION_TIMED_OUT, controller);
-            Message msg = obtainMessage(MSG_CONNECTION_TIMED_OUT, controller);
-            sendMessageDelayed(msg, disconnectTimeoutMs);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionManager.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionManager.java
deleted file mode 100644
index 0310d67..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionManager.java
+++ /dev/null
@@ -1,148 +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.media2.session;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.collection.ArraySet;
-import androidx.media.MediaBrowserServiceCompat;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Provides support for interacting with media sessions that applications have published in order to
- * express their ongoing media playback state.
- *
- * @see MediaSessionCompat
- * @see MediaSession
- * @see MediaSessionService
- * @see MediaLibraryService
- * @see MediaControllerCompat
- * @see MediaController
- * @see MediaBrowser
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public final class MediaSessionManager {
-    static final String TAG = "MediaSessionManager";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final Object sLock = new Object();
-    @GuardedBy("sLock")
-    private static MediaSessionManager sInstance;
-
-    private final Context mContext;
-
-    /**
-     * Gets an instance of MediaSessionManager associated with the context.
-     *
-     * @return the MediaSessionManager instance for this context
-     */
-    @NonNull
-    public static MediaSessionManager getInstance(@NonNull Context context) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        synchronized (sLock) {
-            if (sInstance == null) {
-                sInstance = new MediaSessionManager(context.getApplicationContext());
-            }
-            return sInstance;
-        }
-    }
-
-    private MediaSessionManager(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Gets {@link Set} of {@link SessionToken} for {@link MediaSessionService} regardless of
-     * their activeness. This list represents media apps that support background playback.
-     *
-     * <p>
-     * The app targeting API level 30 or higher must include a {@code <queries>} element in their
-     * manifest to get service tokens of other apps. See the following example and
-     * <a href="{@docRoot}training/package-visibility">this guide</a> for more information.
-     * <pre>{@code
-     * <intent>
-     *   <action android:name="androidx.media2.session.MediaSessionService" />
-     * </intent>
-     * <intent>
-     *   <action android:name="androidx.media2.session.MediaLibraryService" />
-     * </intent>
-     * <intent>
-     *   <action android:name="android.media.browse.MediaBrowserService" />
-     * </intent>
-     * }</pre>
-     *
-     * @return set of tokens
-     */
-    @NonNull
-    @SuppressWarnings("deprecation")
-    public Set<SessionToken> getSessionServiceTokens() {
-        ArraySet<SessionToken> sessionServiceTokens = new ArraySet<>();
-        PackageManager pm = mContext.getPackageManager();
-        List<ResolveInfo> services = new ArrayList<>();
-        // If multiple actions are declared for a service, browser gets higher priority.
-        List<ResolveInfo> libraryServices = pm.queryIntentServices(
-                new Intent(MediaLibraryService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
-        if (libraryServices != null) {
-            services.addAll(libraryServices);
-        }
-        List<ResolveInfo> sessionServices = pm.queryIntentServices(
-                new Intent(MediaSessionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
-        if (sessionServices != null) {
-            services.addAll(sessionServices);
-        }
-        List<ResolveInfo> browserServices = pm.queryIntentServices(
-                new Intent(MediaBrowserServiceCompat.SERVICE_INTERFACE),
-                PackageManager.GET_META_DATA);
-        if (browserServices != null) {
-            services.addAll(browserServices);
-        }
-
-        for (ResolveInfo service : services) {
-            if (service == null || service.serviceInfo == null) {
-                continue;
-            }
-            ServiceInfo serviceInfo = service.serviceInfo;
-            SessionToken token = new SessionToken(mContext,
-                    new ComponentName(serviceInfo.packageName, serviceInfo.name));
-            sessionServiceTokens.add(token);
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Found " + sessionServiceTokens.size() + " session services");
-            for (SessionToken token : sessionServiceTokens) {
-                Log.d(TAG, "   " + token);
-            }
-        }
-        return sessionServiceTokens;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionService.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionService.java
deleted file mode 100644
index 1984eae..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionService.java
+++ /dev/null
@@ -1,366 +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.media2.session;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media2.session.MediaSession.ControllerInfo;
-
-import java.util.List;
-
-/**
- * Base class for media session services, which is the service containing {@link MediaSession}.
- *
- * <p>It's highly recommended for an app to use this if it wants to keep media playback in the
- * background.
- *
- * <p>Here are the benefits of using {@link MediaSessionService}.
- *
- * <ul>
- *   <li>Another app can know that your app supports {@link MediaSession} even when your app isn't
- *       running.
- *   <li>Another app can start playback of your app even when your app isn't running.
- * </ul>
- *
- * For example, user's voice command can start playback of your app even when it's not running.
- *
- * <p>To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
- *
- * <pre>
- * &lt;service android:name="component_name_of_your_implementation" &gt;
- *   &lt;intent-filter&gt;
- *     &lt;action android:name="androidx.media2.session.MediaSessionService" /&gt;
- *   &lt;/intent-filter&gt;
- * &lt;/service&gt;</pre>
- *
- * <p>You may also declare
- *
- * <pre>android.media.browse.MediaBrowserService</pre>
- *
- * for compatibility with {@link android.support.v4.media.MediaBrowserCompat}. This service can
- * handle it automatically.
- *
- * <p>It's recommended for an app to have a single {@link MediaSessionService} declared in the
- * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another
- * app fails to pick the right session service when it wants to start the playback of this app. If
- * you want to provide multiple sessions here, take a look at <a href="#MultipleSessions">Supporting
- * Multiple Sessions</a>.
- *
- * <p>Topics covered here:
- *
- * <ol>
- *   <li><a href="#ServiceLifecycle">Service Lifecycle</a>
- *   <li><a href="#Permissions">Permissions</a>
- *   <li><a href="#MultipleSessions">Supporting Multiple Sessions</a>
- * </ol>
- *
- * <div>
- *
- * <h3 id="ServiceLifecycle">Service Lifecycle</h3>
- *
- * <p>Session service is a bound service. When a {@link MediaController} is created for the session
- * service, the controller binds to the session service. {@link #onGetSession(ControllerInfo)} would
- * be called inside of the {@link #onBind(Intent)}.
- *
- * <p>After the binding, session's {@link MediaSession.SessionCallback#onConnect(MediaSession,
- * MediaSession.ControllerInfo)} will be called to accept or reject connection request from a
- * controller. If the connection is rejected, the controller will unbind. If it's accepted, the
- * controller will be available to use and keep binding.
- *
- * <p>When playback is started for this session service, {@link #onUpdateNotification(MediaSession)}
- * is called for the playback's session and service would become a foreground service. It's needed
- * to keep playback after the controller is destroyed. The session service becomes background
- * service when all playbacks are stopped. Apps targeting API {@link
- * android.os.Build.VERSION_CODES#P} or later must request the permission {@link
- * android.Manifest.permission#FOREGROUND_SERVICE} in order to make the service foreground.
- *
- * <p>The service is destroyed when the all sessions are closed, or no media controller is binding
- * to the session while the service is not running as a foreground service.
- *
- * <h3 id="Permissions">Permissions</h3>
- *
- * <p>Any app can bind to the session service with controller, but the controller can be used only
- * if the session service accepted the connection request through {@link
- * MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)}.
- *
- * <h3 id="MultipleSessions">Supporting Multiple Sessions</h3>
- *
- * Generally speaking, multiple sessions aren't necessary for most media apps. One exception is if
- * your app can play multiple media content at the same time, but only for the playback of
- * video-only media or remote playback, since <a
- * href="{@docRoot}guide/topics/media-apps/audio-focus.html">audio focus policy</a> recommends not
- * playing multiple audio content at the same time. Also keep in mind that multiple media sessions
- * would make Android Auto and Bluetooth device with display to show your apps multiple times,
- * because they list up media sessions, not media apps.
- *
- * <p>However, if you're capable of handling multiple playback and want to keep their sessions while
- * the app is in the background, create multiple sessions and add to this service with {@link
- * #addSession(MediaSession)}.
- *
- * <p>Note that {@link MediaController} can be created with {@link SessionToken} for connecting any
- * session in this service. In that case, {@link #onGetSession(ControllerInfo)} will be called to
- * know which session to handle incoming connection request. Pick the best session among added
- * sessions, or create new one and return from the {@link #onGetSession(ControllerInfo)}. </div>
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public abstract class MediaSessionService extends Service {
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
-
-    private final MediaSessionServiceImpl mImpl;
-
-    public MediaSessionService() {
-        super();
-        // Note: This service doesn't have valid context at this moment.
-        mImpl = createImpl();
-    }
-
-    MediaSessionServiceImpl createImpl() {
-        return new MediaSessionServiceImplBase();
-    }
-
-    /**
-     * Called by the system when the service is first created. Do not call this method directly.
-     * <p>
-     * Override this method if you need your own initialization. Derived classes MUST call through
-     * to the super class's implementation of this method.
-     */
-    @CallSuper
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mImpl.onCreate(this);
-    }
-
-    /**
-     * Called when a {@link MediaController} is created with the this service's
-     * {@link SessionToken}. Return the session for telling the controller which session to
-     * connect. Return {@code null} to reject the connection from this controller.
-     * <p>
-     * Session service automatically maintains the returned session. In other words, session
-     * returned here will be added here and removed when the session is closed.  You don't need to
-     * manually call {@link #addSession(MediaSession)} nor {@link #removeSession(MediaSession)}.
-     * <p>
-     * There are two special cases where the {@link ControllerInfo#getPackageName()} returns
-     * non-existent package name:
-     * <ul>
-     *     <li>
-     *         When the service is being started through the media button intent, the method will
-     *         return {@link Intent#ACTION_MEDIA_BUTTON}. If you want to allow the service being
-     *         started by the media button events, do not return {@code null}.
-     *     </li>
-     *     <li>
-     *         When the legacy {@link android.media.browse.MediaBrowser} or
-     *         {@link android.support.v4.media.MediaBrowserCompat} tries to connect, the method will
-     *         return {@link MediaBrowserServiceCompat#SERVICE_INTERFACE}. If you want to allow the
-     *         service being bound by the legacy media browsers, do not return {@code null}.
-     *     </li>
-     * </ul>
-     * For those special cases, the values returned by {@link ControllerInfo#getUid()} and
-     * {@link ControllerInfo#getConnectionHints()} have no meaning.
-     * <p>
-     * This method is always called on the main thread.
-     *
-     * @param controllerInfo information of the controller which is trying to connect
-     * @return a {@link MediaSession} instance for the controller to connect to, or {@code null}
-     *         to reject connection
-     * @see MediaSession.Builder
-     * @see #getSessions()
-     */
-    @Nullable
-    public abstract MediaSession onGetSession(@NonNull ControllerInfo controllerInfo);
-
-    /**
-     * Adds a session to this service. This is not necessary for most media apps. See
-     * <a href="#MultipleSessions">Supporting Multiple Sessions</a> for detail.
-     * <p>
-     * Added session will be removed automatically when it's closed, or removed when
-     * {@link #removeSession} is called.
-     *
-     * @param session a session to be added.
-     * @see #removeSession(MediaSession)
-     */
-    public final void addSession(@NonNull MediaSession session) {
-        if (session == null) {
-            throw new NullPointerException("session shouldn't be null");
-        }
-        if (session.isClosed()) {
-            throw new IllegalArgumentException("session is already closed");
-        }
-        mImpl.addSession(session);
-    }
-
-    /**
-     * Removes a session from this service. This is not necessary for most media apps. See
-     * <a href="#MultipleSessions">Supporting Multiple Sessions</a> for detail.
-     *
-     * @param session a session to be removed.
-     * @see #addSession(MediaSession)
-     */
-    public final void removeSession(@NonNull MediaSession session) {
-        if (session == null) {
-            throw new NullPointerException("session shouldn't be null");
-        }
-        mImpl.removeSession(session);
-    }
-
-    /**
-     * Called when notification UI needs update. Override this method to show or cancel your own
-     * notification UI.
-     * <p>
-     * This would be called on {@link MediaSession}'s callback executor when player state is
-     * changed, or when the current media item of the session is changed.
-     * <p>
-     * With the notification returned here, the service becomes foreground service when the playback
-     * is started. Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
-     * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
-     * this API. It becomes background service after the playback is stopped.
-     *
-     * @param session a session that needs notification update
-     * @return a {@link MediaNotification}. Can be {@code null}
-     */
-    @Nullable
-    public MediaNotification onUpdateNotification(@NonNull MediaSession session) {
-        if (session == null) {
-            throw new NullPointerException("session shouldn't be null");
-        }
-        return mImpl.onUpdateNotification(session);
-    }
-
-    /**
-     * Gets the list of {@link MediaSession}s that you've added to this service via
-     * {@link #addSession} or {@link #onGetSession(ControllerInfo)}.
-     *
-     * @return sessions
-     */
-    @NonNull
-    public final List<MediaSession> getSessions() {
-        return mImpl.getSessions();
-    }
-
-    /**
-     * Default implementation for {@link MediaSessionService} to handle incoming binding
-     * request. If the request is for getting the session, the intent will have action
-     * {@link #SERVICE_INTERFACE}.
-     * <p>
-     * Override this method if this service also needs to handle binder requests other than
-     * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's
-     * implementation of this method.
-     *
-     * @param intent
-     * @return Binder
-     */
-    @CallSuper
-    @Override
-    @Nullable
-    public IBinder onBind(@NonNull Intent intent) {
-        return mImpl.onBind(intent);
-    }
-
-    @CallSuper
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        return mImpl.onStartCommand(intent, flags, startId);
-    }
-
-    /**
-     * Called by the system to notify that it is no longer used and is being removed. Do not call
-     * this method directly.
-     * <p>
-     * Override this method if you need your own clean up. Derived classes MUST call through
-     * to the super class's implementation of this method.
-     */
-    @CallSuper
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mImpl.onDestroy();
-    }
-
-    /**
-     * Returned by {@link #onUpdateNotification(MediaSession)} for making session service foreground
-     * service to keep playback running in the background. It's highly recommended to show media
-     * style notification here.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static class MediaNotification {
-        private final int mNotificationId;
-        private final Notification mNotification;
-
-        /**
-         * Default constructor
-         *
-         * @param notificationId notification id to be used for
-         *      {@link NotificationManager#notify(int, Notification)}.
-         * @param notification a notification to make session service foreground service. Media
-         *      style notification is recommended here.
-         */
-        public MediaNotification(int notificationId, @NonNull Notification notification) {
-            if (notification == null) {
-                throw new NullPointerException("notification shouldn't be null");
-            }
-            mNotificationId = notificationId;
-            mNotification = notification;
-        }
-
-        /**
-         * Gets the id of the id.
-         *
-         * @return the notification id
-         */
-        public int getNotificationId() {
-            return mNotificationId;
-        }
-
-        /**
-         * Gets the notification.
-         *
-         * @return the notification
-         */
-        @NonNull
-        public Notification getNotification() {
-            return mNotification;
-        }
-    }
-
-    interface MediaSessionServiceImpl {
-        void onCreate(MediaSessionService service);
-        int onStartCommand(Intent intent, int flags, int startId);
-        IBinder onBind(Intent intent);
-        void onDestroy();
-        void addSession(MediaSession session);
-        void removeSession(MediaSession session);
-        MediaNotification onUpdateNotification(MediaSession session);
-        List<MediaSession> getSessions();
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceImplBase.java
deleted file mode 100644
index 9515408..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceImplBase.java
+++ /dev/null
@@ -1,328 +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.media2.session;
-
-import static android.app.Service.START_STICKY;
-
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import androidx.annotation.GuardedBy;
-import androidx.collection.ArrayMap;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaSessionManager;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService.MediaNotification;
-import androidx.media2.session.MediaSessionService.MediaSessionServiceImpl;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.io.Closeable;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of {@link MediaSessionService}.
- */
-class MediaSessionServiceImplBase implements MediaSessionServiceImpl {
-    private static final String TAG = "MSS2ImplBase";
-    private static final boolean DEBUG = true;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    MediaSessionServiceStub mStub;
-    @GuardedBy("mLock")
-    MediaSessionService mInstance;
-    @GuardedBy("mLock")
-    private Map<String, MediaSession> mSessions = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private MediaNotificationHandler mNotificationHandler;
-
-    MediaSessionServiceImplBase() {
-    }
-
-    @Override
-    public void onCreate(MediaSessionService service) {
-        synchronized (mLock) {
-            mInstance = service;
-            mStub = new MediaSessionServiceStub(this);
-            mNotificationHandler = new MediaNotificationHandler(service);
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        final MediaSessionService service = getInstance();
-        if (service == null) {
-            Log.w(TAG, "Service hasn't created before onBind()");
-            return null;
-        }
-        switch (intent.getAction()) {
-            case MediaSessionService.SERVICE_INTERFACE: {
-                return getServiceBinder();
-            }
-            case MediaBrowserServiceCompat.SERVICE_INTERFACE: {
-                ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
-                final MediaSession session = service.onGetSession(controllerInfo);
-                if (session == null) {
-                    // Legacy MediaBrowser(Compat) cannot connect to this service.
-                    if (DEBUG) {
-                        Log.d(TAG, "Rejecting incoming connection request from"
-                                + " legacy media browsers.");
-                    }
-                    return null;
-                }
-                addSession(session);
-                // Return a specific session's legacy binder although the Android framework caches
-                // the returned binder here and next binding request may reuse cached binder even
-                // after the session is closed.
-                // Disclaimer: Although MediaBrowserCompat can only get the session that initially
-                // set, it doesn't make things bad. Such limitation had been there between
-                // MediaBrowserCompat and MediaBrowserServiceCompat.
-                return session.getLegacyBrowerServiceBinder();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        synchronized (mLock) {
-            mInstance = null;
-            if (mStub != null) {
-                mStub.close();
-                mStub = null;
-            }
-        }
-    }
-
-    @Override
-    public void addSession(final MediaSession session) {
-        final MediaSession old;
-        synchronized (mLock) {
-            old = mSessions.get(session.getId());
-            if (old != null && old != session) {
-                // TODO(b/112114183): Also check the uniqueness before sessions're returned by
-                //                    onGetSession.
-                throw new IllegalArgumentException("Session ID should be unique");
-            }
-            mSessions.put(session.getId(), session);
-        }
-        if (old == null) {
-            // Session has returned for the first time. Register callbacks.
-            // TODO: Check whether the session is registered in multiple sessions.
-            final MediaNotificationHandler handler;
-            synchronized (mLock) {
-                handler = mNotificationHandler;
-            }
-            handler.onPlayerStateChanged(session, session.getPlayer().getPlayerState());
-            session.getCallback().setForegroundServiceEventCallback(handler);
-        }
-    }
-
-    @Override
-    public void removeSession(MediaSession session) {
-        synchronized (mLock) {
-            mSessions.remove(session.getId());
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent == null || intent.getAction() == null) {
-            return START_STICKY;
-        }
-        switch (intent.getAction()) {
-            case Intent.ACTION_MEDIA_BUTTON: {
-                final MediaSessionService instance = getInstance();
-                if (instance == null) {
-                    Log.wtf(TAG, "Service hasn't created");
-                }
-                MediaSession session = MediaSession.getSession(intent.getData());
-                if (session == null) {
-                    ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
-                    session = instance.onGetSession(controllerInfo);
-                }
-                if (session == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Rejecting wake-up of the service from media key events.");
-                    }
-                    break;
-                }
-                KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                if (keyEvent != null) {
-                    session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
-                }
-                break;
-            }
-        }
-        return START_STICKY;
-    }
-
-    @Override
-    public MediaNotification onUpdateNotification(MediaSession session) {
-        final MediaNotificationHandler handler;
-        synchronized (mLock) {
-            handler = mNotificationHandler;
-        }
-        if (handler == null) {
-            throw new IllegalStateException("Service hasn't created");
-        }
-        return handler.onUpdateNotification(session);
-    }
-
-    @Override
-    public List<MediaSession> getSessions() {
-        List<MediaSession> list = new ArrayList<>();
-        synchronized (mLock) {
-            list.addAll(mSessions.values());
-        }
-        return list;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    MediaSessionService getInstance() {
-        synchronized (mLock) {
-            return mInstance;
-        }
-    }
-
-    IBinder getServiceBinder() {
-        synchronized (mLock) {
-            return (mStub != null) ? mStub.asBinder() : null;
-        }
-    }
-
-    private static final class MediaSessionServiceStub extends IMediaSessionService.Stub
-            implements Closeable {
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        final WeakReference<MediaSessionServiceImplBase> mServiceImpl;
-        private final Handler mHandler;
-        private final MediaSessionManager mMediaSessionManager;
-
-        MediaSessionServiceStub(final MediaSessionServiceImplBase serviceImpl) {
-            mServiceImpl = new WeakReference<>(serviceImpl);
-            mHandler = new Handler(serviceImpl.getInstance().getMainLooper());
-            mMediaSessionManager = MediaSessionManager.getSessionManager(serviceImpl.getInstance());
-        }
-
-        @Override
-        public void connect(final IMediaController caller, final ParcelImpl connectionRequest) {
-            final MediaSessionServiceImplBase serviceImpl = mServiceImpl.get();
-            if (serviceImpl == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "ServiceImpl isn't available");
-                }
-                return;
-            }
-            final int callingPid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            final ConnectionRequest request = MediaParcelUtils.fromParcelable(connectionRequest);
-            final int pid = (callingPid != 0) ? callingPid : request.getPid();
-            final String packageName = connectionRequest == null ? null : request.getPackageName();
-            final Bundle connectionHints = connectionRequest == null ? null :
-                    request.getConnectionHints();
-            final RemoteUserInfo remoteUserInfo = new RemoteUserInfo(packageName, pid, uid);
-            final boolean isTrusted = mMediaSessionManager.isTrustedForMediaControl(remoteUserInfo);
-            try {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        boolean shouldNotifyDisconnected = true;
-                        try {
-                            final MediaSessionServiceImplBase serviceImpl = mServiceImpl.get();
-                            if (serviceImpl == null) {
-                                if (DEBUG) {
-                                    Log.d(TAG, "ServiceImpl isn't available");
-                                }
-                                return;
-                            }
-                            final MediaSessionService service = serviceImpl.getInstance();
-                            if (service == null) {
-                                if (DEBUG) {
-                                    Log.d(TAG, "Service isn't available");
-                                }
-                                return;
-                            }
-
-                            ControllerInfo controllerInfo = new ControllerInfo(remoteUserInfo,
-                                    request.getVersion(), isTrusted, null /* controllerCb */,
-                                    connectionHints);
-                            if (DEBUG) {
-                                Log.d(TAG, "Handling incoming connection request from the"
-                                        + " controller=" + controllerInfo);
-                            }
-                            final MediaSession session;
-                            try {
-                                session = service.onGetSession(controllerInfo);
-                                if (session == null) {
-                                    if (DEBUG) {
-                                        Log.w(TAG, "Rejecting incoming connection request from the"
-                                                + " controller=" + controllerInfo);
-                                    }
-                                    return;
-                                }
-
-                                service.addSession(session);
-                                shouldNotifyDisconnected = false;
-
-                                session.handleControllerConnectionFromService(caller,
-                                        request.getVersion(), packageName,
-                                        pid, uid, connectionHints);
-                            } catch (Exception e) {
-                                // Don't propagate exception in service to the controller.
-                                Log.w(TAG, "Failed to add a session to session service", e);
-                            }
-                        } finally {
-                            // Trick to call onDisconnected() in one place.
-                            if (shouldNotifyDisconnected) {
-                                if (DEBUG) {
-                                    Log.d(TAG, "Notifying the controller of its disconnection");
-                                }
-                                try {
-                                    caller.onDisconnected(0);
-                                } catch (RemoteException e) {
-                                    // Controller may be died prematurely.
-                                    // Not an issue because we'll ignore it anyway.
-                                }
-                            }
-                        }
-                    }
-                });
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void close() {
-            mServiceImpl.clear();
-            mHandler.removeCallbacksAndMessages(null);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceLegacyStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceLegacyStub.java
deleted file mode 100644
index 63f7235..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionServiceLegacyStub.java
+++ /dev/null
@@ -1,89 +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.media2.session;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaSessionManager;
-import androidx.media.MediaSessionManager.RemoteUserInfo;
-import androidx.media2.session.MediaSession.ControllerInfo;
-
-import java.util.List;
-
-/**
- * Implementation of {@link MediaBrowserServiceCompat} for interoperability between
- * {@link MediaLibraryService} and {@link android.support.v4.media.MediaBrowserCompat}.
- */
-class MediaSessionServiceLegacyStub extends MediaBrowserServiceCompat {
-    private final MediaSession.MediaSessionImpl mSessionImpl;
-    private final ConnectedControllersManager<RemoteUserInfo> mConnectedControllersManager;
-
-    final MediaSessionManager mManager;
-
-    MediaSessionServiceLegacyStub(Context context, MediaSession.MediaSessionImpl sessionImpl,
-            MediaSessionCompat.Token token) {
-        super();
-        attachToBaseContext(context);
-        onCreate();
-        setSessionToken(token);
-        mManager = MediaSessionManager.getSessionManager(context);
-        mSessionImpl = sessionImpl;
-        mConnectedControllersManager = new ConnectedControllersManager<>(sessionImpl);
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        RemoteUserInfo info = getCurrentBrowserInfo();
-        final MediaSession.ControllerInfo controller = createControllerInfo(info);
-        // Call callbacks directly instead of execute on the executor. Here's the reason.
-        // We need to return browser root here. So if we run the callback on the executor, we
-        // should wait for the completion.
-        // However, we cannot wait if the callback executor is the main executor, which posts
-        // the runnable to the main thread's. In that case, since this onGetRoot() always runs
-        // on the main thread, the posted runnable for calling onConnect() wouldn't run
-        // in here. Even worse, we cannot know whether it would be run on the main thread or
-        // not.
-        // Because of the reason, just call onConnect() directly here. onConnect() has documentation
-        // that it may be called on the main thread.
-        SessionCommandGroup connectResult = mSessionImpl.getCallback().onConnect(
-                mSessionImpl.getInstance(), controller);
-        if (connectResult == null) {
-            return null;
-        }
-        mConnectedControllersManager.addController(info, controller, connectResult);
-        // No library root, but keep browser compat connected to allow getting session.
-        return MediaUtils.sDefaultBrowserRoot;
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-        result.sendResult(null);
-    }
-
-    ControllerInfo createControllerInfo(RemoteUserInfo info) {
-        return new ControllerInfo(info, MediaUtils.VERSION_UNKNOWN,
-                mManager.isTrustedForMediaControl(info), null, null /* connectionHints */);
-    }
-
-    ConnectedControllersManager<RemoteUserInfo> getConnectedControllersManager() {
-        return mConnectedControllersManager;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java
deleted file mode 100644
index 719af29..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java
+++ /dev/null
@@ -1,1491 +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.media2.session;
-
-import static androidx.media2.common.BaseResult.RESULT_ERROR_PERMISSION_DENIED;
-import static androidx.media2.common.BaseResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.session.MediaUtils.DIRECT_EXECUTOR;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM;
-import static androidx.media2.session.SessionCommand.COMMAND_VERSION_CURRENT;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.media.MediaSessionManager;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.PlayerResult;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionImpl;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaSession.ControllerCb;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.MediaSessionImpl;
-import androidx.media2.session.SessionCommand.CommandCode;
-import androidx.versionedparcelable.ParcelImpl;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Handles incoming commands from {@link MediaController} and {@link MediaBrowser}
- * to both {@link MediaSession} and {@link MediaLibrarySession}.
- * <p>
- * We cannot create a subclass for library service specific function because AIDL doesn't support
- * subclassing and it's generated stub class is an abstract class.
- */
-class MediaSessionStub extends IMediaSession.Stub {
-    private static final String TAG = "MediaSessionStub";
-    private static final boolean RETHROW_EXCEPTION = true;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    static final SparseArray<SessionCommand> sCommandsForOnCommandRequest =
-            new SparseArray<>();
-
-    static {
-        SessionCommandGroup group = new SessionCommandGroup.Builder()
-                .addAllPlayerCommands(COMMAND_VERSION_CURRENT)
-                .addAllVolumeCommands(COMMAND_VERSION_CURRENT)
-                .build();
-        Set<SessionCommand> commands = group.getCommands();
-        for (SessionCommand command : commands) {
-            sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final ConnectedControllersManager<IBinder> mConnectedControllersManager;
-
-    final Object mLock = new Object();
-
-    final WeakReference<MediaSessionImpl> mSessionImpl;
-    private final MediaSessionManager mSessionManager;
-
-    MediaSessionStub(MediaSession.MediaSessionImpl sessionImpl) {
-        mSessionImpl = new WeakReference<>(sessionImpl);
-        mSessionManager = MediaSessionManager.getSessionManager(sessionImpl.getContext());
-        mConnectedControllersManager = new ConnectedControllersManager<>(sessionImpl);
-    }
-
-    ConnectedControllersManager<IBinder> getConnectedControllersManager() {
-        return mConnectedControllersManager;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void sendSessionResult(@NonNull ControllerInfo controller, int seq,
-            int resultCode) {
-        sendSessionResult(controller, seq, new SessionResult(resultCode));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void sendSessionResult(@NonNull ControllerInfo controller, int seq,
-            @NonNull SessionResult result) {
-        try {
-            controller.getControllerCb().onSessionResult(seq, result);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Exception in " + controller.toString(), e);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void sendPlayerResult(@NonNull ControllerInfo controller, int seq,
-            @NonNull PlayerResult result) {
-        try {
-            controller.getControllerCb().onPlayerResult(seq, result);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Exception in " + controller.toString(), e);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void sendLibraryResult(@NonNull ControllerInfo controller, int seq,
-            int resultCode) {
-        sendLibraryResult(controller, seq, new LibraryResult(resultCode));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void sendLibraryResult(@NonNull ControllerInfo controller, int seq,
-            @NonNull LibraryResult result) {
-        try {
-            controller.getControllerCb().onLibraryResult(seq, result);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Exception in " + controller.toString(), e);
-        }
-    }
-
-    private void dispatchSessionTask(@NonNull IMediaController caller, int seq,
-            @CommandCode final int commandCode,
-            @NonNull final SessionTask task) {
-        dispatchSessionTaskInternal(caller, seq, null, commandCode, task);
-    }
-
-    private void dispatchSessionTask(@NonNull IMediaController caller, int seq,
-            @NonNull final SessionCommand sessionCommand,
-            @NonNull final SessionTask task) {
-        dispatchSessionTaskInternal(caller, seq, sessionCommand, COMMAND_CODE_CUSTOM, task);
-    }
-
-    private void dispatchSessionTaskInternal(@NonNull IMediaController caller, final int seq,
-            @Nullable final SessionCommand sessionCommand,
-            @CommandCode final int commandCode,
-            @NonNull final SessionTask task) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            MediaSessionImpl sessionImpl = mSessionImpl.get();
-            if (sessionImpl == null || sessionImpl.isClosed()) {
-                return;
-            }
-            final ControllerInfo controller = mConnectedControllersManager.getController(
-                    caller.asBinder());
-            if (controller == null) {
-                return;
-            }
-            sessionImpl.getCallbackExecutor().execute(new Runnable() {
-                @Override
-                @SuppressWarnings("ObjectToString")
-                public void run() {
-                    if (!mConnectedControllersManager.isConnected(controller)) {
-                        return;
-                    }
-                    SessionCommand commandForOnCommandRequest;
-                    if (sessionCommand != null) {
-                        if (!mConnectedControllersManager.isAllowedCommand(
-                                controller, sessionCommand)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Command (" + sessionCommand + ") from "
-                                        + controller + " isn't allowed.");
-                            }
-                            sendSessionResult(controller, seq, RESULT_ERROR_PERMISSION_DENIED);
-                            return;
-                        }
-                        commandForOnCommandRequest = sCommandsForOnCommandRequest.get(
-                                sessionCommand.getCommandCode());
-                    } else {
-                        if (!mConnectedControllersManager.isAllowedCommand(controller,
-                                commandCode)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Command (" + commandCode + ") from "
-                                        + controller + " isn't allowed.");
-                            }
-                            sendSessionResult(controller, seq, RESULT_ERROR_PERMISSION_DENIED);
-                            return;
-                        }
-                        commandForOnCommandRequest = sCommandsForOnCommandRequest.get(
-                                commandCode);
-                    }
-                    try {
-                        if (commandForOnCommandRequest != null) {
-                            int resultCode = sessionImpl.getCallback().onCommandRequest(
-                                    sessionImpl.getInstance(), controller,
-                                    commandForOnCommandRequest);
-                            if (resultCode != SessionResult.RESULT_SUCCESS) {
-                                // Don't run rejected command.
-                                if (DEBUG) {
-                                    Log.d(TAG, "Command (" + commandForOnCommandRequest
-                                            + ") from " + controller + " was rejected by "
-                                            + mSessionImpl + ", code=" + resultCode);
-                                }
-                                sendSessionResult(controller, seq, resultCode);
-                                return;
-                            }
-                        }
-                        if (task instanceof SessionPlayerTask) {
-                            final ListenableFuture<PlayerResult> future =
-                                    ((SessionPlayerTask) task).run(sessionImpl, controller);
-                            if (future == null) {
-                                throw new RuntimeException("SessionPlayer has returned null,"
-                                        + " commandCode=" + commandCode);
-                            } else {
-                                future.addListener(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        try {
-                                            sendPlayerResult(controller, seq,
-                                                    future.get(0, TimeUnit.MILLISECONDS));
-                                        } catch (Exception e) {
-                                            Log.w(TAG, "Cannot obtain PlayerResult after the"
-                                                    + " command is finished", e);
-                                            sendSessionResult(controller, seq,
-                                                    RESULT_ERROR_INVALID_STATE);
-                                        }
-                                    }
-                                }, DIRECT_EXECUTOR);
-                            }
-                        } else if (task instanceof SessionCallbackTask) {
-                            final Object result = ((SessionCallbackTask<?>) task).run(
-                                    sessionImpl, controller);
-                            if (result == null) {
-                                throw new RuntimeException("SessionCallback has returned null,"
-                                        + " commandCode=" + commandCode);
-                            } else if (result instanceof Integer) {
-                                sendSessionResult(controller, seq, (Integer) result);
-                            } else if (result instanceof SessionResult) {
-                                sendSessionResult(controller, seq, (SessionResult) result);
-                            } else if (DEBUG) {
-                                throw new RuntimeException("Unexpected return type " + result
-                                        + ". Fix bug");
-                            }
-                        } else if (task instanceof LibrarySessionCallbackTask) {
-                            final Object result = ((LibrarySessionCallbackTask<?>) task).run(
-                                    (MediaLibrarySessionImpl) sessionImpl, controller);
-                            if (result == null) {
-                                throw new RuntimeException("LibrarySessionCallback has returned"
-                                        + " null, commandCode=" + commandCode);
-                            } else if (result instanceof Integer) {
-                                sendLibraryResult(controller, seq, (Integer) result);
-                            } else if (result instanceof LibraryResult) {
-                                sendLibraryResult(controller, seq, (LibraryResult) result);
-                            } else if (DEBUG) {
-                                throw new RuntimeException("Unexpected return type " + result
-                                        + ". Fix bug");
-                            }
-                        } else if (DEBUG) {
-                            throw new RuntimeException("Unknown task " + task + ". Fix bug");
-                        }
-                    } catch (RemoteException e) {
-                        // Currently it's TransactionTooLargeException or DeadSystemException.
-                        // We'd better to leave log for those cases because
-                        //   - TransactionTooLargeException means that we may need to fix our code.
-                        //     (e.g. add pagination or special way to deliver Bitmap)
-                        //   - DeadSystemException means that errors around it can be ignored.
-                        Log.w(TAG, "Exception in " + controller.toString(), e);
-                    } catch (Exception e) {
-                        // Any random exception may be happen inside
-                        // of the session player / callback.
-
-                        if (RETHROW_EXCEPTION) {
-                            throw e;
-                        }
-                        if (task instanceof MediaSessionImplBase.PlayerTask) {
-                            sendPlayerResult(controller, seq,
-                                    new PlayerResult(
-                                            PlayerResult.RESULT_ERROR_UNKNOWN, null));
-                        } else if (task instanceof SessionCallbackTask) {
-                            sendSessionResult(controller, seq,
-                                    SessionResult.RESULT_ERROR_UNKNOWN);
-                        } else if (task instanceof LibrarySessionCallbackTask) {
-                            sendLibraryResult(controller, seq,
-                                    LibraryResult.RESULT_ERROR_UNKNOWN);
-                        }
-                    }
-                }
-            });
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    void connect(final IMediaController caller, final int controllerVersion,
-            final String callingPackage, final int pid, final int uid,
-            @Nullable Bundle connectionHints) {
-        MediaSessionManager.RemoteUserInfo remoteUserInfo =
-                new MediaSessionManager.RemoteUserInfo(callingPackage, pid, uid);
-        final ControllerInfo controllerInfo = new ControllerInfo(remoteUserInfo, controllerVersion,
-                mSessionManager.isTrustedForMediaControl(remoteUserInfo),
-                new Controller2Cb(caller), connectionHints);
-        MediaSessionImpl sessionImpl = mSessionImpl.get();
-        if (sessionImpl == null || sessionImpl.isClosed()) {
-            return;
-        }
-        sessionImpl.getCallbackExecutor().execute(new Runnable() {
-            @Override
-            @SuppressWarnings("ObjectToString")
-            public void run() {
-                if (sessionImpl.isClosed()) {
-                    return;
-                }
-                final IBinder callbackBinder = ((Controller2Cb) controllerInfo.getControllerCb())
-                        .getCallbackBinder();
-                SessionCommandGroup allowedCommands = sessionImpl.getCallback().onConnect(
-                        sessionImpl.getInstance(), controllerInfo);
-                // Don't reject connection for the request from trusted app.
-                // Otherwise server will fail to retrieve session's information to dispatch
-                // media keys to.
-                boolean accept = allowedCommands != null || controllerInfo.isTrusted();
-                if (accept) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Accepting connection, controllerInfo=" + controllerInfo
-                                + " allowedCommands=" + allowedCommands);
-                    }
-                    if (allowedCommands == null) {
-                        // For trusted apps, send non-null allowed commands to keep
-                        // connection.
-                        allowedCommands = new SessionCommandGroup();
-                    }
-                    SequencedFutureManager sequencedFutureManager;
-                    synchronized (mLock) {
-                        if (mConnectedControllersManager.isConnected(controllerInfo)) {
-                            Log.w(TAG, "Controller " + controllerInfo + " has sent connection"
-                                    + " request multiple times");
-                        }
-                        mConnectedControllersManager.addController(
-                                callbackBinder, controllerInfo, allowedCommands);
-                        sequencedFutureManager = mConnectedControllersManager
-                                .getSequencedFutureManager(controllerInfo);
-                    }
-                    // If connection is accepted, notify the current state to the controller.
-                    // It's needed because we cannot call synchronous calls between
-                    // session/controller.
-                    // Note: We're doing this after the onConnectionChanged(), but there's no
-                    //       guarantee that events here are notified after the onConnected()
-                    //       because IMediaController is oneway (i.e. async call) and Stub will
-                    //       use thread poll for incoming calls.
-                    ConnectionResult state = new ConnectionResult(
-                            MediaSessionStub.this, sessionImpl, allowedCommands);
-
-                    // Double check if session is still there, because close() can be called in
-                    // another thread.
-                    if (sessionImpl.isClosed()) {
-                        return;
-                    }
-                    try {
-                        caller.onConnected(sequencedFutureManager.obtainNextSequenceNumber(),
-                                MediaParcelUtils.toParcelable(state));
-                    } catch (RemoteException e) {
-                        // Controller may be died prematurely.
-                    }
-
-                    sessionImpl.getCallback().onPostConnect(
-                            sessionImpl.getInstance(), controllerInfo);
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Rejecting connection, controllerInfo=" + controllerInfo);
-                    }
-                    try {
-                        caller.onDisconnected(0);
-                    } catch (RemoteException e) {
-                        // Controller may be died prematurely.
-                        // Not an issue because we'll ignore it anyway.
-                    }
-                }
-            }
-        });
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @Nullable
-    MediaItem convertMediaItemOnExecutor(MediaSessionImpl sessionImpl, ControllerInfo controller,
-            String mediaId) {
-        if (TextUtils.isEmpty(mediaId)) {
-            return null;
-        }
-        MediaItem newItem = sessionImpl.getCallback().onCreateMediaItem(
-                sessionImpl.getInstance(), controller, mediaId);
-        if (newItem == null) {
-            Log.w(TAG, "onCreateMediaItem(mediaId=" + mediaId + ") returned null. Ignoring");
-        } else if (newItem.getMetadata() == null
-                || !TextUtils.equals(mediaId,
-                        newItem.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID))) {
-            throw new RuntimeException("onCreateMediaItem(mediaId=" + mediaId + "): media ID in the"
-                    + " returned media item should match");
-        }
-        return newItem;
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // AIDL methods for session overrides
-    //////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void connect(final IMediaController caller, int seq, ParcelImpl connectionRequest)
-            throws RuntimeException {
-        if (caller == null || connectionRequest == null) {
-            return;
-        }
-        final int uid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        final long token = Binder.clearCallingIdentity();
-        final ConnectionRequest request = MediaParcelUtils.fromParcelable(connectionRequest);
-        // Binder.getCallingPid() can be 0 for an oneway call from the remote process.
-        // If it's the case, use PID from the ConnectionRequest.
-        final int pid = (callingPid != 0) ? callingPid : request.getPid();
-        try {
-            connect(caller, request.getVersion(), request.getPackageName(), pid, uid,
-                    request.getConnectionHints());
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public void release(final IMediaController caller, int seq) throws RemoteException {
-        if (caller == null) {
-            return;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mConnectedControllersManager.removeController(caller.asBinder());
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public void onControllerResult(final IMediaController caller, int seq,
-            final ParcelImpl sessionResult) {
-        if (caller == null || sessionResult == null) {
-            return;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            SequencedFutureManager manager = mConnectedControllersManager.getSequencedFutureManager(
-                    caller.asBinder());
-            if (manager == null) {
-                return;
-            }
-            SessionResult result = MediaParcelUtils.fromParcelable(sessionResult);
-            manager.setFutureResult(seq, result);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public void setVolumeTo(final IMediaController caller, int seq, final int value,
-            final int flags) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        sessionImpl.getSessionCompat()
-                                .getController().setVolumeTo(value, flags);
-                        return SessionResult.RESULT_SUCCESS;
-                    }
-                });
-    }
-
-    @Override
-    public void adjustVolume(IMediaController caller, int seq, final int direction,
-            final int flags) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        sessionImpl.getSessionCompat()
-                                .getController().adjustVolume(direction, flags);
-                        return SessionResult.RESULT_SUCCESS;
-                    }
-                });
-    }
-
-    @Override
-    public void play(IMediaController caller, int seq) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.play();
-                    }
-                });
-    }
-
-    @Override
-    public void pause(IMediaController caller, int seq) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.pause();
-                    }
-                });
-    }
-
-    @Override
-    public void prepare(IMediaController caller, int seq) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_PREPARE,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.prepare();
-                    }
-                });
-    }
-
-    @Override
-    public void fastForward(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        return sessionImpl.getCallback().onFastForward(
-                                sessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void rewind(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_REWIND,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        return sessionImpl.getCallback().onRewind(
-                                sessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void skipForward(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        return sessionImpl.getCallback().onSkipForward(
-                                sessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void skipBackward(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        return sessionImpl.getCallback().onSkipBackward(
-                                sessionImpl.getInstance(), controller);
-                    }
-                });
-    }
-
-    @Override
-    public void seekTo(IMediaController caller, int seq, final long pos) throws RuntimeException {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.seekTo(pos);
-                    }
-                });
-    }
-
-    @Override
-    public void onCustomCommand(final IMediaController caller, final int seq,
-            final ParcelImpl command, final Bundle args) {
-        if (caller == null || command == null) {
-            return;
-        }
-        final SessionCommand sessionCommand = MediaParcelUtils.fromParcelable(command);
-        dispatchSessionTask(caller, seq, sessionCommand, new SessionCallbackTask<SessionResult>() {
-            @Override
-            @SuppressWarnings("ObjectToString")
-            public SessionResult run(MediaSessionImpl sessionImpl,
-                    final ControllerInfo controller) {
-                SessionResult result = sessionImpl.getCallback().onCustomCommand(
-                        sessionImpl.getInstance(), controller, sessionCommand, args);
-                if (result == null) {
-                    if (RETHROW_EXCEPTION) {
-                        throw new RuntimeException("SessionCallback#onCustomCommand has returned"
-                                + " null, command=" + sessionCommand);
-                    } else {
-                        result = new SessionResult(RESULT_ERROR_UNKNOWN);
-                    }
-                }
-                return result;
-            }
-        });
-    }
-
-    @Override
-    public void setRating(final IMediaController caller, int seq, final String mediaId,
-            final ParcelImpl ratingParcelable) {
-        if (caller == null || ratingParcelable == null) {
-            return;
-        }
-        final Rating rating = MediaParcelUtils.fromParcelable(ratingParcelable);
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_SET_RATING,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "setRating(): Ignoring empty mediaId from " + controller);
-                            return RESULT_ERROR_BAD_VALUE;
-                        }
-                        if (rating == null) {
-                            Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
-                            return RESULT_ERROR_BAD_VALUE;
-                        }
-                        return sessionImpl.getCallback().onSetRating(
-                                sessionImpl.getInstance(), controller, mediaId, rating);
-                    }
-                });
-    }
-
-    @Override
-    public void setPlaybackSpeed(final IMediaController caller, int seq, final float speed) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.setPlaybackSpeed(speed);
-                    }
-                });
-    }
-
-    @Override
-    public void setPlaylist(final IMediaController caller, int seq, final List<String> playlist,
-            final ParcelImpl metadata) {
-        if (caller == null || metadata == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        if (playlist == null) {
-                            Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        List<MediaItem> list = new ArrayList<>();
-                        for (int i = 0; i < playlist.size(); i++) {
-                            MediaItem item = convertMediaItemOnExecutor(sessionImpl, controller,
-                                    playlist.get(i));
-                            if (item != null) {
-                                list.add(item);
-                            }
-                        }
-                        return sessionImpl.setPlaylist(list,
-                                (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-                    }
-                });
-    }
-
-    @Override
-    public void setMediaItem(final IMediaController caller, int seq, final String mediaId) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "setMediaItem(): Ignoring empty mediaId from " + controller);
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        MediaItem item = convertMediaItemOnExecutor(
-                                sessionImpl, controller, mediaId);
-                        if (item == null) {
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.setMediaItem(item);
-                    }
-                });
-    }
-
-    @Override
-    public void setMediaUri(final IMediaController caller, int seq, final Uri uri,
-            final Bundle extras) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI,
-                new SessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        if (uri == null) {
-                            Log.w(TAG, "setMediaUri(): Ignoring null uri from " + controller);
-                            return RESULT_ERROR_BAD_VALUE;
-                        }
-                        return sessionImpl.getCallback().onSetMediaUri(
-                                sessionImpl.getInstance(), controller, uri, extras);
-                    }
-                });
-    }
-
-    @Override
-    public void updatePlaylistMetadata(final IMediaController caller, int seq,
-            final ParcelImpl metadata) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.updatePlaylistMetadata(
-                                (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-                    }
-                });
-    }
-
-    @Override
-    public void addPlaylistItem(IMediaController caller, int seq, final int index,
-            final String mediaId) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "addPlaylistItem(): Ignoring empty mediaId from "
-                                    + controller);
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        MediaItem item = convertMediaItemOnExecutor(
-                                sessionImpl, controller, mediaId);
-                        if (item == null) {
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.addPlaylistItem(index, item);
-                    }
-                });
-    }
-
-    @Override
-    public void removePlaylistItem(IMediaController caller, int seq, final int index) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.removePlaylistItem(index);
-                    }
-                });
-    }
-
-    @Override
-    public void replacePlaylistItem(IMediaController caller, int seq, final int index,
-            final String mediaId) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "replacePlaylistItem(): Ignoring empty mediaId from "
-                                    + controller);
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        MediaItem item = convertMediaItemOnExecutor(
-                                sessionImpl, controller, mediaId);
-                        if (item == null) {
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.replacePlaylistItem(index, item);
-                    }
-                });
-    }
-
-    @Override
-    public void movePlaylistItem(IMediaController caller, int seq, final int fromIndex,
-            final int toIndex) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.movePlaylistItem(fromIndex, toIndex);
-                    }
-                });
-    }
-
-    @Override
-    public void skipToPlaylistItem(IMediaController caller, int seq, final int index) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        if (index < 0) {
-                            Log.w(TAG, "skipToPlaylistItem(): Ignoring negative index from "
-                                    + controller);
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.skipToPlaylistItem(index);
-                    }
-                });
-    }
-
-    @Override
-    public void skipToPreviousItem(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq,
-                SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.skipToPreviousItem();
-                    }
-                });
-    }
-
-    @Override
-    public void skipToNextItem(IMediaController caller, int seq) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq,
-                SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.skipToNextItem();
-                    }
-                });
-    }
-
-    @Override
-    public void setRepeatMode(IMediaController caller, int seq, final int repeatMode) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.setRepeatMode(repeatMode);
-                    }
-                });
-    }
-
-    @Override
-    public void setShuffleMode(IMediaController caller, int seq, final int shuffleMode) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.setShuffleMode(shuffleMode);
-                    }
-                });
-    }
-
-    @Override
-    public void setSurface(IMediaController caller, int seq, final Surface surface) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_SURFACE,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        return sessionImpl.setSurface(surface);
-                    }
-                });
-    }
-
-    @Override
-    public void selectTrack(IMediaController caller, int seq, final ParcelImpl trackInfoParcel) {
-        if (caller == null || trackInfoParcel == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        TrackInfo trackInfo = MediaParcelUtils.fromParcelable(trackInfoParcel);
-                        if (trackInfo == null) {
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.selectTrack(trackInfo);
-                    }
-                });
-    }
-
-    @Override
-    public void deselectTrack(IMediaController caller, int seq, final ParcelImpl trackInfoParcel) {
-        if (caller == null || trackInfoParcel == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK,
-                new SessionPlayerTask() {
-                    @Override
-                    public ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                            ControllerInfo controller) {
-                        TrackInfo trackInfo = MediaParcelUtils.fromParcelable(trackInfoParcel);
-                        if (trackInfo == null) {
-                            return PlayerResult.createFuture(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return sessionImpl.deselectTrack(trackInfo);
-                    }
-                });
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // AIDL methods for LibrarySession overrides
-    //////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void getLibraryRoot(final IMediaController caller, int seq,
-            final ParcelImpl libraryParams) throws RuntimeException {
-        if (caller == null || libraryParams == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq,
-                SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT,
-                new LibrarySessionCallbackTask<LibraryResult>() {
-                    @Override
-                    public LibraryResult run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        return librarySessionImpl.onGetLibraryRootOnExecutor(controller,
-                                (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-                    }
-                });
-    }
-
-    @Override
-    public void getItem(final IMediaController caller, int seq, final String mediaId)
-            throws RuntimeException {
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM,
-                new LibrarySessionCallbackTask<LibraryResult>() {
-                    @Override
-                    public LibraryResult run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(mediaId)) {
-                            Log.w(TAG, "getItem(): Ignoring empty mediaId from " + controller);
-                            return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-                        }
-                        return librarySessionImpl.onGetItemOnExecutor(controller, mediaId);
-                    }
-                });
-    }
-
-    @Override
-    public void getChildren(final IMediaController caller, int seq, final String parentId,
-            final int page, final int pageSize, final ParcelImpl libraryParams)
-            throws RuntimeException {
-        if (caller == null || libraryParams == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN,
-                new LibrarySessionCallbackTask<LibraryResult>() {
-                    @Override
-                    public LibraryResult run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(parentId)) {
-                            Log.w(TAG, "getChildren(): Ignoring empty parentId from " + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        if (page < 0) {
-                            Log.w(TAG, "getChildren(): Ignoring negative page from " + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        if (pageSize < 1) {
-                            Log.w(TAG, "getChildren(): Ignoring pageSize less than 1 from "
-                                    + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        return librarySessionImpl.onGetChildrenOnExecutor(controller, parentId,
-                                page, pageSize,
-                                (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-                    }
-                });
-    }
-
-    @Override
-    public void search(IMediaController caller, int seq, final String query,
-            final ParcelImpl libraryParams) {
-        if (caller == null || libraryParams == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_LIBRARY_SEARCH,
-                new LibrarySessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(query)) {
-                            Log.w(TAG, "search(): Ignoring empty query from " + controller);
-                            return LibraryResult.RESULT_ERROR_BAD_VALUE;
-                        }
-                        return librarySessionImpl.onSearchOnExecutor(controller, query,
-                                (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-                    }
-                });
-    }
-
-    @Override
-    public void getSearchResult(final IMediaController caller, int seq, final String query,
-            final int page, final int pageSize, final ParcelImpl libraryParams) {
-        if (caller == null || libraryParams == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq,
-                SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT,
-                new LibrarySessionCallbackTask<LibraryResult>() {
-                    @Override
-                    public LibraryResult run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(query)) {
-                            Log.w(TAG, "getSearchResult(): Ignoring empty query from "
-                                    + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        if (page < 0) {
-                            Log.w(TAG, "getSearchResult(): Ignoring negative page from "
-                                    + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        if (pageSize < 1) {
-                            Log.w(TAG, "getSearchResult(): Ignoring pageSize less than 1 from "
-                                    + controller);
-                            return new LibraryResult(LibraryResult.RESULT_ERROR_BAD_VALUE);
-                        }
-                        return librarySessionImpl.onGetSearchResultOnExecutor(controller,
-                                query, page, pageSize,
-                                (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-                    }
-                });
-    }
-
-    @Override
-    public void subscribe(final IMediaController caller, int seq, final String parentId,
-            final ParcelImpl libraryParams) {
-        if (caller == null || libraryParams == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE,
-                new LibrarySessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(parentId)) {
-                            Log.w(TAG, "subscribe(): Ignoring empty parentId from " + controller);
-                            return LibraryResult.RESULT_ERROR_BAD_VALUE;
-                        }
-                        return librarySessionImpl.onSubscribeOnExecutor(
-                                controller, parentId,
-                                (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-                    }
-                });
-    }
-
-    @Override
-    public void unsubscribe(final IMediaController caller, int seq, final String parentId) {
-        if (caller == null) {
-            return;
-        }
-        dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE,
-                new LibrarySessionCallbackTask<Integer>() {
-                    @Override
-                    public Integer run(MediaLibrarySessionImpl librarySessionImpl,
-                            ControllerInfo controller) {
-                        if (TextUtils.isEmpty(parentId)) {
-                            Log.w(TAG, "unsubscribe(): Ignoring empty parentId from " + controller);
-                            return LibraryResult.RESULT_ERROR_BAD_VALUE;
-                        }
-                        return librarySessionImpl.onUnsubscribeOnExecutor(controller, parentId);
-                    }
-                });
-    }
-
-    /**
-     * Common interface for code snippets to handle all incoming commands from the controller.
-     *
-     * @see #dispatchSessionTask
-     */
-    private interface SessionTask {
-        // empty interface
-    }
-
-    private interface SessionPlayerTask extends SessionTask {
-        ListenableFuture<PlayerResult> run(MediaSessionImpl sessionImpl,
-                ControllerInfo controller) throws RemoteException;
-    }
-
-    private interface SessionCallbackTask<T> extends SessionTask {
-        T run(MediaSessionImpl sessionImpl, ControllerInfo controller) throws RemoteException;
-    }
-
-    private interface LibrarySessionCallbackTask<T> extends SessionTask {
-        T run(MediaLibrarySessionImpl librarySessionImpl,
-                ControllerInfo controller) throws RemoteException;
-    }
-
-    final class Controller2Cb extends ControllerCb {
-        // TODO: Drop 'Callback' from the name.
-        private final IMediaController mIControllerCallback;
-
-        Controller2Cb(@NonNull IMediaController callback) {
-            mIControllerCallback = callback;
-        }
-
-        @NonNull
-        IBinder getCallbackBinder() {
-            return mIControllerCallback.asBinder();
-        }
-
-        @Override
-        void onPlayerResult(int seq, @Nullable PlayerResult result) throws RemoteException {
-            onSessionResult(seq, SessionResult.from(result));
-        }
-
-        @Override
-        void onSessionResult(int seq, @Nullable SessionResult result) throws RemoteException {
-            if (result == null) {
-                result = new SessionResult(RESULT_ERROR_UNKNOWN, null);
-            }
-            mIControllerCallback.onSessionResult(seq, MediaParcelUtils.toParcelable(result));
-        }
-
-        @Override
-        void onLibraryResult(int seq, LibraryResult result) throws RemoteException {
-            if (result == null) {
-                result = new LibraryResult(LibraryResult.RESULT_ERROR_UNKNOWN);
-            }
-            mIControllerCallback.onLibraryResult(seq, MediaParcelUtils.toParcelable(result));
-        }
-
-        @Override
-        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
-                @Nullable PlaybackInfo oldPlaybackInfo,
-                @NonNull SessionPlayer player, @NonNull PlaybackInfo playbackInfo)
-                throws RemoteException {
-            if (oldPlayer == null) {
-                // Ignore player changed callback called in initialization.
-                return;
-            }
-            MediaSessionImpl sessionImpl = mSessionImpl.get();
-            if (sessionImpl == null) {
-                return;
-            }
-            // Tells the playlist change first, so current media item index change notification
-            // can point to the valid current media item in the playlist.
-            List<MediaItem> oldPlaylist = oldPlayer.getPlaylist();
-            final List<MediaItem> newPlaylist = player.getPlaylist();
-            if (!ObjectsCompat.equals(oldPlaylist, newPlaylist)) {
-                onPlaylistChanged(seq,
-                        newPlaylist, player.getPlaylistMetadata(),
-                        player.getCurrentMediaItemIndex(),
-                        player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            } else {
-                MediaMetadata oldMetadata = oldPlayer.getPlaylistMetadata();
-                final MediaMetadata newMetadata = player.getPlaylistMetadata();
-                if (!ObjectsCompat.equals(oldMetadata, newMetadata)) {
-                    onPlaylistMetadataChanged(seq, newMetadata);
-                }
-            }
-            MediaItem oldCurrentItem = oldPlayer.getCurrentMediaItem();
-            final MediaItem newCurrentItem = player.getCurrentMediaItem();
-            if (!ObjectsCompat.equals(oldCurrentItem, newCurrentItem)) {
-                onCurrentMediaItemChanged(seq, newCurrentItem,
-                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
-                        player.getNextMediaItemIndex());
-            }
-            final @SessionPlayer.RepeatMode int repeatMode = player.getRepeatMode();
-            if (oldPlayer.getRepeatMode() != repeatMode) {
-                onRepeatModeChanged(seq, repeatMode, player.getCurrentMediaItemIndex(),
-                        player.getPreviousMediaItemIndex(), player.getNextMediaItemIndex());
-            }
-            final @SessionPlayer.ShuffleMode int shuffleMode = player.getShuffleMode();
-            if (oldPlayer.getShuffleMode() != shuffleMode) {
-                onShuffleModeChanged(seq, shuffleMode, player.getCurrentMediaItemIndex(),
-                        player.getPreviousMediaItemIndex(), player.getNextMediaItemIndex());
-            }
-
-            // Always forcefully send the player state and buffered state to send the current
-            // position and buffered position.
-            final long currentTimeMs = SystemClock.elapsedRealtime();
-            final long positionMs = player.getCurrentPosition();
-            final int playerState = player.getPlayerState();
-            onPlayerStateChanged(seq, currentTimeMs, positionMs, playerState);
-
-            final MediaItem item = player.getCurrentMediaItem();
-            if (item != null) {
-                final int bufferingState = player.getBufferingState();
-                final long bufferedPositionMs = player.getBufferedPosition();
-                onBufferingStateChanged(seq, item, bufferingState, bufferedPositionMs,
-                        SystemClock.elapsedRealtime(), player.getCurrentPosition());
-            }
-            final float speed = player.getPlaybackSpeed();
-            if (speed != oldPlayer.getPlaybackSpeed()) {
-                onPlaybackSpeedChanged(seq, currentTimeMs, positionMs, speed);
-            }
-
-            if (!ObjectsCompat.equals(oldPlaybackInfo, playbackInfo)) {
-                onPlaybackInfoChanged(seq, playbackInfo);
-            }
-        }
-
-        @Override
-        void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
-            mIControllerCallback.onSetCustomLayout(seq,
-                    MediaUtils.convertCommandButtonListToParcelImplList(layout));
-        }
-
-        @Override
-        void onPlaybackInfoChanged(int seq, @NonNull PlaybackInfo info) throws RemoteException {
-            mIControllerCallback.onPlaybackInfoChanged(seq, MediaParcelUtils.toParcelable(info));
-        }
-
-        @Override
-        void onAllowedCommandsChanged(int seq, @NonNull SessionCommandGroup commands)
-                throws RemoteException {
-            mIControllerCallback.onAllowedCommandsChanged(
-                    seq, MediaParcelUtils.toParcelable(commands));
-        }
-
-        @Override
-        void sendCustomCommand(int seq, @NonNull SessionCommand command, Bundle args)
-                throws RemoteException {
-            mIControllerCallback.onCustomCommand(seq, MediaParcelUtils.toParcelable(command), args);
-        }
-
-        @Override
-        void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int playerState)
-                throws RemoteException {
-            mIControllerCallback.onPlayerStateChanged(seq, eventTimeMs, positionMs, playerState);
-        }
-
-        @Override
-        void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed)
-                throws RemoteException {
-            mIControllerCallback.onPlaybackSpeedChanged(seq, eventTimeMs, positionMs, speed);
-        }
-
-        @Override
-        void onBufferingStateChanged(int seq, @NonNull MediaItem item, int bufferingState,
-                long bufferedPositionMs, long eventTimeMs, long positionMs) throws RemoteException {
-            mIControllerCallback.onBufferingStateChanged(seq, MediaParcelUtils.toParcelable(item),
-                    bufferingState, bufferedPositionMs, eventTimeMs, positionMs);
-        }
-
-        @Override
-        void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long seekPositionMs)
-                throws RemoteException {
-            mIControllerCallback.onSeekCompleted(seq, eventTimeMs, positionMs, seekPositionMs);
-        }
-
-        @Override
-        void onCurrentMediaItemChanged(int seq, MediaItem item, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            mIControllerCallback.onCurrentMediaItemChanged(seq, MediaParcelUtils.toParcelable(item),
-                    currentIdx, previousIdx, nextIdx);
-        }
-
-        @Override
-        void onPlaylistChanged(int seq, @NonNull List<MediaItem> playlist, MediaMetadata metadata,
-                int currentIdx, int previousIdx, int nextIdx) throws RemoteException {
-            ControllerInfo controller = mConnectedControllersManager.getController(
-                    getCallbackBinder());
-            if (mConnectedControllersManager.isAllowedCommand(controller,
-                    SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST)) {
-                mIControllerCallback.onPlaylistChanged(seq,
-                        MediaUtils.convertMediaItemListToParcelImplListSlice(playlist),
-                        MediaParcelUtils.toParcelable(metadata), currentIdx, previousIdx, nextIdx);
-            } else if (mConnectedControllersManager.isAllowedCommand(controller,
-                    SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)) {
-                mIControllerCallback.onPlaylistMetadataChanged(seq,
-                        MediaParcelUtils.toParcelable(metadata));
-            }
-        }
-
-        @Override
-        void onPlaylistMetadataChanged(int seq, MediaMetadata metadata) throws RemoteException {
-            ControllerInfo controller = mConnectedControllersManager.getController(
-                    getCallbackBinder());
-            if (mConnectedControllersManager.isAllowedCommand(controller,
-                    SessionCommand.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)) {
-                mIControllerCallback.onPlaylistMetadataChanged(seq,
-                        MediaParcelUtils.toParcelable(metadata));
-            }
-        }
-
-        @Override
-        void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            mIControllerCallback.onShuffleModeChanged(seq, shuffleMode, currentIdx, previousIdx,
-                    nextIdx);
-        }
-
-        @Override
-        void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
-                int nextIdx) throws RemoteException {
-            mIControllerCallback.onRepeatModeChanged(seq, repeatMode, currentIdx, previousIdx,
-                    nextIdx);
-        }
-
-        @Override
-        void onPlaybackCompleted(int seq) throws RemoteException {
-            mIControllerCallback.onPlaybackCompleted(seq);
-        }
-
-        @Override
-        void onChildrenChanged(int seq, @NonNull String parentId, int itemCount,
-                LibraryParams params) throws RemoteException {
-            mIControllerCallback.onChildrenChanged(seq, parentId, itemCount,
-                    MediaParcelUtils.toParcelable(params));
-        }
-
-        @Override
-        void onSearchResultChanged(int seq, @NonNull String query, int itemCount,
-                LibraryParams params) throws RemoteException {
-            mIControllerCallback.onSearchResultChanged(seq, query, itemCount,
-                    MediaParcelUtils.toParcelable(params));
-        }
-
-        @Override
-        void onDisconnected(int seq) throws RemoteException {
-            mIControllerCallback.onDisconnected(seq);
-        }
-
-        @Override
-        void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
-            ParcelImpl videoSizeParcel = MediaParcelUtils.toParcelable(videoSize);
-            // A fake item is used instead of 'null' to keep backward compatibility with 1.0.0.
-            // In 1.0.0, MediaControllerStub filters out the corresponding call if the item is null.
-            MediaItem fakeItem = new MediaItem.Builder().build();
-            mIControllerCallback.onVideoSizeChanged(seq, MediaParcelUtils.toParcelable(fakeItem),
-                    videoSizeParcel);
-        }
-
-        @Override
-        void onTracksChanged(int seq, List<TrackInfo> tracks,
-                TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
-                TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
-                throws RemoteException {
-            List<ParcelImpl> trackInfoList = MediaParcelUtils.toParcelableList(tracks);
-            mIControllerCallback.onTrackInfoChanged(seq, trackInfoList,
-                    MediaParcelUtils.toParcelable(selectedVideoTrack),
-                    MediaParcelUtils.toParcelable(selectedAudioTrack),
-                    MediaParcelUtils.toParcelable(selectedSubtitleTrack),
-                    MediaParcelUtils.toParcelable(selectedMetadataTrack));
-        }
-
-        @Override
-        void onTrackSelected(int seq, TrackInfo trackInfo) throws RemoteException {
-            mIControllerCallback.onTrackSelected(seq, MediaParcelUtils.toParcelable(trackInfo));
-        }
-
-        @Override
-        void onTrackDeselected(int seq, TrackInfo trackInfo) throws RemoteException {
-            mIControllerCallback.onTrackDeselected(seq, MediaParcelUtils.toParcelable(trackInfo));
-        }
-
-        @Override
-        void onSubtitleData(int seq, @NonNull MediaItem item,
-                @NonNull TrackInfo track, @NonNull SubtitleData data)
-                throws RemoteException {
-            ParcelImpl itemParcel = MediaParcelUtils.toParcelable(item);
-            ParcelImpl trackParcel = MediaParcelUtils.toParcelable(track);
-            ParcelImpl dataParcel = MediaParcelUtils.toParcelable(data);
-            mIControllerCallback.onSubtitleData(seq, itemParcel, trackParcel, dataParcel);
-        }
-
-        @Override
-        public int hashCode() {
-            return ObjectsCompat.hash(getCallbackBinder());
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null || obj.getClass() != Controller2Cb.class) {
-                return false;
-            }
-            Controller2Cb other = (Controller2Cb) obj;
-            return ObjectsCompat.equals(getCallbackBinder(), other.getCallbackBinder());
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java
deleted file mode 100644
index 0aaec2b..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java
+++ /dev/null
@@ -1,1008 +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.media2.session;
-
-import static android.support.v4.media.MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE;
-import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_MIXED;
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_ADVERTISEMENT;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_EXTRAS;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SURFACE;
-import static androidx.media2.session.SessionCommand.COMMAND_VERSION_1;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.BadParcelableException;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- */
-@RestrictTo(LIBRARY)
-public class MediaUtils {
-    public static final String TAG = "MediaUtils";
-    public static final int TRANSACTION_SIZE_LIMIT_IN_BYTES = 256 * 1024; // 256KB
-
-    // Stub BrowserRoot for accepting any connection here.
-    public static final BrowserRoot sDefaultBrowserRoot =
-            new BrowserRoot(MediaLibraryService.SERVICE_INTERFACE, null);
-
-    public static final Executor DIRECT_EXECUTOR = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            command.run();
-        }
-    };
-
-    // UNKNOWN version for legacy support
-    public static final int VERSION_UNKNOWN = -1;
-
-    // Initial version for all Media2 APIs.
-    public static final int VERSION_0 = 0;
-
-    // Current version for all Media2 APIs.
-    public static final int CURRENT_VERSION = VERSION_0;
-
-    private static final Map<String, String> METADATA_COMPAT_KEY_TO_METADATA_KEY = new HashMap<>();
-    private static final Map<String, String> METADATA_KEY_TO_METADATA_COMPAT_KEY = new HashMap<>();
-    static {
-        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(
-                MediaMetadataCompat.METADATA_KEY_ADVERTISEMENT, METADATA_KEY_ADVERTISEMENT);
-        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE,
-                METADATA_KEY_BROWSABLE);
-        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS,
-                METADATA_KEY_DOWNLOAD_STATUS);
-
-        // Invert METADATA_COMPAT_KEY_TO_METADATA_KEY to create METADATA_KEY_TO_METADATA_COMPAT_KEY.
-        for (Map.Entry<String, String> entry : METADATA_COMPAT_KEY_TO_METADATA_KEY.entrySet()) {
-            if (METADATA_KEY_TO_METADATA_COMPAT_KEY.containsKey(entry.getValue())) {
-                throw new RuntimeException("Shouldn't map to the same value");
-            }
-            METADATA_KEY_TO_METADATA_COMPAT_KEY.put(entry.getValue(), entry.getKey());
-        }
-    }
-
-    private MediaUtils() {
-    }
-
-    /**
-     * Upcasts a {@link MediaItem} to the {@link MediaItem} type for pre-parceling. Note that
-     * {@link MediaItem}'s subclass object cannot be parceled due to the security issue.
-     *
-     * @param item an item
-     * @return upcasted item
-     */
-    @Nullable
-    public static MediaItem upcastForPreparceling(@Nullable MediaItem item) {
-        if (item == null || item.getClass() == MediaItem.class) {
-            return item;
-        }
-        return new MediaItem.Builder()
-                .setStartPosition(item.getStartPosition())
-                .setEndPosition(item.getEndPosition())
-                .setMetadata(item.getMetadata()).build();
-    }
-
-    /**
-     * Upcasts a {@link VideoSize} subclass to the {@link MediaItem} type for pre-parceling.
-     * Note that {@link VideoSize}'s subclass object cannot be parceled due the issue that remote
-     * apps may not have the subclass.
-     *
-     * @param size a size
-     * @return upcasted size
-     */
-    @Nullable
-    public static VideoSize upcastForPreparceling(@Nullable VideoSize size) {
-        if (size == null || size.getClass() == VideoSize.class) {
-            return size;
-        }
-        return new VideoSize(size.getWidth(), size.getHeight());
-    }
-
-    /**
-     * Upcasts a {@link TrackInfo} subclass to the {@link TrackInfo} type for pre-parceling.
-     * Note that {@link TrackInfo}'s subclass object cannot be parceled due to the issue that remote
-     * apps may not have the subclass.
-     *
-     * @param track a track
-     * @return upcasted track
-     */
-    @Nullable
-    public static TrackInfo upcastForPreparceling(@Nullable TrackInfo track) {
-        if (track == null || track.getClass() == TrackInfo.class) {
-            return track;
-        }
-        return new TrackInfo(track.getId(), track.getTrackType(), track.getFormat(),
-                track.isSelectable());
-    }
-
-    /**
-     * Upcasts a list of {@link TrackInfo} subclass objects to a List of {@link TrackInfo} type
-     * for pre-parceling. Note that {@link TrackInfo}'s subclass object cannot be parceled due
-     * to the issue that remote apps may not have the subclass.
-     *
-     * @param tracks a list of tracks
-     * @return list of upcasted tracks
-     */
-    @Nullable
-    public static List<TrackInfo> upcastForPreparceling(@Nullable List<TrackInfo> tracks) {
-        if (tracks == null) {
-            return tracks;
-        }
-        List<TrackInfo> upcastTracks = new ArrayList<>();
-        for (int i = 0; i < tracks.size(); i++) {
-            upcastTracks.add(upcastForPreparceling(tracks.get(i)));
-        }
-        return upcastTracks;
-    }
-
-    /**
-     * Creates a {@link MediaBrowserCompat.MediaItem} from the {@link MediaItem}.
-     *
-     * @param item2 an item.
-     * @return The newly created media item.
-     */
-    @Nullable
-    public static MediaBrowserCompat.MediaItem convertToMediaItem(@Nullable MediaItem item2) {
-        if (item2 == null) {
-            return null;
-        }
-        int flags = 0;
-        MediaDescriptionCompat descCompat;
-        MediaMetadata metadata = item2.getMetadata();
-        if (metadata == null) {
-            descCompat = new MediaDescriptionCompat.Builder()
-                    .setMediaId(item2.getMediaId())
-                    .build();
-        } else {
-            MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder()
-                    .setMediaId(item2.getMediaId())
-                    .setSubtitle(metadata.getText(METADATA_KEY_DISPLAY_SUBTITLE))
-                    .setDescription(metadata.getText(METADATA_KEY_DISPLAY_DESCRIPTION))
-                    .setIconBitmap(metadata.getBitmap(METADATA_KEY_DISPLAY_ICON))
-                    .setExtras(metadata.getExtras());
-
-            String title = metadata.getString(METADATA_KEY_TITLE);
-            if (title != null) {
-                builder.setTitle(title);
-            } else {
-                builder.setTitle(metadata.getString(METADATA_KEY_DISPLAY_TITLE));
-            }
-
-            String displayIconUri = metadata.getString(METADATA_KEY_DISPLAY_ICON_URI);
-            if (displayIconUri != null) {
-                builder.setIconUri(Uri.parse(displayIconUri));
-            }
-
-            String mediaUri = metadata.getString(METADATA_KEY_MEDIA_URI);
-            if (mediaUri != null) {
-                builder.setMediaUri(Uri.parse(mediaUri));
-            }
-
-            descCompat = builder.build();
-
-            boolean browsable = metadata.containsKey(METADATA_KEY_BROWSABLE)
-                    && metadata.getLong(METADATA_KEY_BROWSABLE) != BROWSABLE_TYPE_NONE;
-            boolean playable = metadata.getLong(METADATA_KEY_PLAYABLE) != 0;
-            flags = (browsable ? MediaBrowserCompat.MediaItem.FLAG_BROWSABLE : 0)
-                    | (playable ? MediaBrowserCompat.MediaItem.FLAG_PLAYABLE : 0);
-        }
-        return new MediaBrowserCompat.MediaItem(descCompat, flags);
-    }
-
-    /**
-     * Convert a list of {@link MediaItem} to a list of {@link MediaBrowserCompat.MediaItem}.
-     */
-    @Nullable
-    public static List<MediaBrowserCompat.MediaItem> convertToMediaItemList(
-            @Nullable List<MediaItem> items) {
-        if (items == null) {
-            return null;
-        }
-        List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
-        for (int i = 0; i < items.size(); i++) {
-            result.add(convertToMediaItem(items.get(i)));
-        }
-        return result;
-    }
-
-    /**
-     * Creates a {@link MediaItem} from the {@link MediaBrowserCompat.MediaItem}.
-     *
-     * @param item an item.
-     * @return The newly created media item.
-     */
-    public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {
-        if (item == null) {
-            return null;
-        }
-        MediaMetadata metadata = convertToMediaMetadata(item.getDescription(),
-                item.isBrowsable(), item.isPlayable());
-        return new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-    }
-
-    /**
-     * Convert a {@link QueueItem} to a {@link MediaItem}.
-     */
-    public static MediaItem convertToMediaItem(QueueItem item) {
-        if (item == null) {
-            return null;
-        }
-        // descriptionCompat cannot be null
-        MediaDescriptionCompat descriptionCompat = item.getDescription();
-        MediaMetadata metadata = convertToMediaMetadata(descriptionCompat, false, true);
-        return new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-    }
-
-    /**
-     * Convert a {@link MediaMetadataCompat} from the {@link MediaControllerCompat#getMetadata()}
-     * and rating type to a {@link MediaItem}.
-     */
-    @Nullable
-    @SuppressWarnings("deprecation")
-    public static MediaItem convertToMediaItem(@Nullable MediaMetadataCompat metadataCompat,
-            int ratingType) {
-        if (metadataCompat == null) {
-            return null;
-        }
-        // Item is from the MediaControllerCompat, so forcefully set the playable.
-        MediaMetadata.Builder builder = new MediaMetadata.Builder()
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .putRating(METADATA_KEY_USER_RATING,
-                        MediaUtils.convertToRating(RatingCompat.newUnratedRating(ratingType)));
-        for (String key : metadataCompat.keySet()) {
-            Object value = metadataCompat.getBundle().get(key);
-            String metadataKey = METADATA_COMPAT_KEY_TO_METADATA_KEY.containsKey(key)
-                    ? METADATA_COMPAT_KEY_TO_METADATA_KEY.get(key) : key;
-            if (value instanceof CharSequence) {
-                builder.putText(metadataKey, (CharSequence) value);
-            } else if (value instanceof Bitmap) {
-                builder.putBitmap(metadataKey, (Bitmap) value);
-            } else if (value instanceof Long) {
-                builder.putLong(metadataKey, (Long) value);
-            } else if (value instanceof RatingCompat || value instanceof android.media.Rating) {
-                // Must be fwk Rating or RatingCompat according to SDK versions.
-                // Use MediaMetadataCompat#getRating(key) to get a RatingCompat object.
-                try {
-                    RatingCompat rating = metadataCompat.getRating(key);
-                    builder.putRating(metadataKey, MediaUtils.convertToRating(rating));
-                } catch (Exception e) {
-                    // Prevent from CastException in the getRating() due to the future changes.
-                }
-            }
-        }
-        return new MediaItem.Builder().setMetadata(builder.build()).build();
-    }
-
-    /**
-     * Convert a {@link MediaDescriptionCompat} to a {@link MediaItem}.
-     */
-    @Nullable
-    public static MediaItem convertToMediaItem(@Nullable MediaDescriptionCompat descriptionCompat) {
-        MediaMetadata metadata = convertToMediaMetadata(descriptionCompat, false, true);
-        if (metadata == null) {
-            return null;
-        }
-        return new MediaItem.Builder().setMetadata(metadata).build();
-    }
-
-    /**
-     * Convert a list of {@link MediaBrowserCompat.MediaItem} to a list of {@link MediaItem}.
-     */
-    public static List<MediaItem> convertMediaItemListToMediaItemList(
-            List<MediaBrowserCompat.MediaItem> items) {
-        if (items == null) {
-            return null;
-        }
-        List<MediaItem> result = new ArrayList<>();
-        for (int i = 0; i < items.size(); i++) {
-            result.add(convertToMediaItem(items.get(i)));
-        }
-        return result;
-    }
-
-    /**
-     * Convert a list of {@link QueueItem} to a list of {@link MediaItem}.
-     */
-    public static List<MediaItem> convertQueueItemListToMediaItemList(List<QueueItem> items) {
-        if (items == null) {
-            return null;
-        }
-        List<MediaItem> result = new ArrayList<>();
-        for (int i = 0; i < items.size(); i++) {
-            MediaItem item = convertToMediaItem(items.get(i));
-            if (item != null) {
-                result.add(item);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Creates {@link MediaDescriptionCompat} with the id
-     */
-    public static MediaDescriptionCompat createMediaDescriptionCompat(String mediaId) {
-        if (TextUtils.isEmpty(mediaId)) {
-            return null;
-        }
-        return new MediaDescriptionCompat.Builder().setMediaId(mediaId).build();
-    }
-
-    /**
-     * Convert a list of {@link MediaItem} to a list of {@link QueueItem}. The index of the item
-     * would be used as the queue ID to match the behavior of {@link MediaController}.
-     */
-    public static List<QueueItem> convertToQueueItemList(List<MediaItem> items) {
-        if (items == null) {
-            return null;
-        }
-        List<QueueItem> result = new ArrayList<>();
-        for (int i = 0; i < items.size(); i++) {
-            MediaItem item = items.get(i);
-            MediaDescriptionCompat description = (item.getMetadata() == null)
-                    ? new MediaDescriptionCompat.Builder().setMediaId(item.getMediaId()).build()
-                    : convertToMediaMetadataCompat(item.getMetadata()).getDescription();
-            long id = convertToQueueItemId(i);
-            result.add(new QueueItem(description, id));
-        }
-        return result;
-    }
-
-    /**
-     * Convert the index of a {@link MediaItem} in a playlist into id of {@link QueueItem}.
-     *
-     * @param mediaItemIndex index of a {@link MediaItem} in a playlist. It can be
-     *        {@link SessionPlayer#INVALID_ITEM_INDEX}.
-     * @return id of {@link QueueItem} or {@link QueueItem#UNKNOWN_ID} if the index is
-     *         {@link SessionPlayer#INVALID_ITEM_INDEX}.
-     */
-    public static long convertToQueueItemId(int mediaItemIndex) {
-        if (mediaItemIndex == SessionPlayer.INVALID_ITEM_INDEX) {
-            return QueueItem.UNKNOWN_ID;
-        }
-        return mediaItemIndex;
-    }
-
-    /**
-     * Convert a {@link ParcelImplListSlice} to a list of {@link MediaItem}.
-     */
-    public static List<MediaItem> convertParcelImplListSliceToMediaItemList(
-            ParcelImplListSlice listSlice) {
-        if (listSlice == null) {
-            return null;
-        }
-        List<ParcelImpl> parcelImplList = listSlice.getList();
-        List<MediaItem> mediaItemList = new ArrayList<>();
-        for (int i = 0; i < parcelImplList.size(); i++) {
-            final ParcelImpl itemParcelImpl = parcelImplList.get(i);
-            if (itemParcelImpl != null) {
-                mediaItemList.add((MediaItem) MediaParcelUtils.fromParcelable(itemParcelImpl));
-            }
-        }
-        return mediaItemList;
-    }
-
-    /**
-     * Return a list which consists of first {@code N} items of the given list with the same order.
-     * {@code N} is determined as the maximum number of items whose total parcelled size is less
-     * than {@param sizeLimitInBytes}.
-     */
-    public static <T extends Parcelable> List<T> truncateListBySize(final List<T> list,
-            final int sizeLimitInBytes) {
-        if (list == null) {
-            return null;
-        }
-        List<T> result = new ArrayList<>();
-        Parcel parcel = Parcel.obtain();
-        try {
-            for (int i = 0; i < list.size(); i++) {
-                // Calculate the size.
-                T item = list.get(i);
-                parcel.writeParcelable(item, 0);
-                if (parcel.dataSize() < sizeLimitInBytes) {
-                    result.add(item);
-                } else {
-                    break;
-                }
-            }
-            return result;
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    /**
-     * Creates a {@link MediaMetadata} from the {@link MediaDescriptionCompat}.
-     *
-     * @param descCompat A {@link MediaDescriptionCompat} object.
-     * @param browsable {@code true} if it's from {@link MediaBrowserCompat.MediaItem} with
-     *                  browsable flag.
-     * @param playable {@code true} if it's from {@link MediaBrowserCompat.MediaItem} with
-     *                  playable flag, or from {@link QueueItem}.
-     * @return
-     */
-    private static MediaMetadata convertToMediaMetadata(MediaDescriptionCompat descCompat,
-            boolean browsable, boolean playable) {
-        if (descCompat == null) {
-            return null;
-        }
-
-        MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
-        metadataBuilder.putString(METADATA_KEY_MEDIA_ID, descCompat.getMediaId());
-
-        CharSequence title = descCompat.getTitle();
-        if (title != null) {
-            metadataBuilder.putText(METADATA_KEY_DISPLAY_TITLE, title);
-        }
-
-        CharSequence description = descCompat.getDescription();
-        if (description != null) {
-            metadataBuilder.putText(METADATA_KEY_DISPLAY_DESCRIPTION, descCompat.getDescription());
-        }
-
-        CharSequence subtitle = descCompat.getSubtitle();
-        if (subtitle != null) {
-            metadataBuilder.putText(METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
-        }
-
-        Bitmap icon = descCompat.getIconBitmap();
-        if (icon != null) {
-            metadataBuilder.putBitmap(METADATA_KEY_DISPLAY_ICON, icon);
-        }
-
-        Uri iconUri = descCompat.getIconUri();
-        if (iconUri != null) {
-            metadataBuilder.putText(METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString());
-        }
-
-        Bundle bundle = descCompat.getExtras();
-        if (bundle != null) {
-            metadataBuilder.setExtras(bundle);
-        }
-
-        Uri mediaUri = descCompat.getMediaUri();
-        if (mediaUri != null) {
-            metadataBuilder.putText(METADATA_KEY_MEDIA_URI, mediaUri.toString());
-        }
-
-        if (bundle != null && bundle.containsKey(EXTRA_BT_FOLDER_TYPE)) {
-            metadataBuilder.putLong(METADATA_KEY_BROWSABLE,
-                    bundle.getLong(EXTRA_BT_FOLDER_TYPE));
-        } else if (browsable) {
-            metadataBuilder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED);
-        } else {
-            metadataBuilder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE);
-        }
-
-        metadataBuilder.putLong(METADATA_KEY_PLAYABLE, playable ? 1 : 0);
-
-        return metadataBuilder.build();
-    }
-
-    /**
-     * Creates a {@link MediaMetadata} from the {@link CharSequence}.
-     */
-    public static MediaMetadata convertToMediaMetadata(CharSequence queueTitle) {
-        if (queueTitle == null) {
-            return null;
-        }
-        return new MediaMetadata.Builder()
-                .putString(METADATA_KEY_TITLE, queueTitle.toString())
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-    }
-
-    /**
-     * Creates a {@link MediaMetadataCompat} from the {@link MediaMetadata}.
-     *
-     * @param metadata A {@link MediaMetadata} object.
-     * @return The newly created {@link MediaMetadataCompat} object.
-     */
-    public static MediaMetadataCompat convertToMediaMetadataCompat(MediaMetadata metadata) {
-        if (metadata == null) {
-            return null;
-        }
-        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
-        for (String key : metadata.keySet()) {
-            String compatKey = METADATA_KEY_TO_METADATA_COMPAT_KEY.containsKey(key)
-                    ? METADATA_KEY_TO_METADATA_COMPAT_KEY.get(key) : key;
-            Object value = metadata.getObject(key);
-            if (value instanceof CharSequence) {
-                builder.putText(compatKey, (CharSequence) value);
-            } else if (value instanceof Bitmap) {
-                builder.putBitmap(compatKey, (Bitmap) value);
-            } else if (value instanceof Long) {
-                builder.putLong(compatKey, (Long) value);
-            } else if (value instanceof Bundle && !TextUtils.equals(key, METADATA_KEY_EXTRAS)) {
-                // Must be Bundle which contains a Rating.
-                // Use MediaMetadata#getRating(key) to get a Rating object.
-                try {
-                    Rating rating = metadata.getRating(key);
-                    builder.putRating(compatKey, MediaUtils.convertToRatingCompat(rating));
-                } catch (Exception e) {
-                    // Prevent from CastException in the getRating() due to the future changes.
-                }
-            }
-        }
-        return builder.build();
-    }
-
-    /**
-     * Creates a {@link Rating} from the {@link RatingCompat}.
-     *
-     * @param ratingCompat A {@link RatingCompat} object.
-     * @return The newly created {@link Rating} object.
-     */
-    public static Rating convertToRating(RatingCompat ratingCompat) {
-        if (ratingCompat == null) {
-            return null;
-        }
-        switch (ratingCompat.getRatingStyle()) {
-            case RatingCompat.RATING_3_STARS:
-                return ratingCompat.isRated()
-                        ? new StarRating(3, ratingCompat.getStarRating()) : new StarRating(3);
-            case RatingCompat.RATING_4_STARS:
-                return ratingCompat.isRated()
-                        ? new StarRating(4, ratingCompat.getStarRating()) : new StarRating(4);
-            case RatingCompat.RATING_5_STARS:
-                return ratingCompat.isRated()
-                        ? new StarRating(5, ratingCompat.getStarRating()) : new StarRating(5);
-            case RatingCompat.RATING_HEART:
-                return ratingCompat.isRated()
-                        ? new HeartRating(ratingCompat.hasHeart()) : new HeartRating();
-            case RatingCompat.RATING_THUMB_UP_DOWN:
-                return ratingCompat.isRated()
-                        ? new ThumbRating(ratingCompat.isThumbUp()) : new ThumbRating();
-            case RatingCompat.RATING_PERCENTAGE:
-                return ratingCompat.isRated()
-                        ? new PercentageRating(ratingCompat.getPercentRating())
-                        : new PercentageRating();
-            default:
-                return null;
-        }
-    }
-
-    /**
-     * Creates a {@link RatingCompat} from the {@link Rating}.
-     *
-     * @param rating A {@link Rating} object.
-     * @return The newly created {@link RatingCompat} object.
-     */
-    @SuppressLint("WrongConstant") // for @StarStyle
-    public static RatingCompat convertToRatingCompat(Rating rating) {
-        if (rating == null) {
-            return null;
-        }
-        int ratingCompatStyle = getRatingCompatStyle(rating);
-        if (!rating.isRated()) {
-            return RatingCompat.newUnratedRating(ratingCompatStyle);
-        }
-
-        switch (ratingCompatStyle) {
-            case RatingCompat.RATING_3_STARS:
-            case RatingCompat.RATING_4_STARS:
-            case RatingCompat.RATING_5_STARS:
-                return RatingCompat.newStarRating(
-                        ratingCompatStyle, ((StarRating) rating).getStarRating());
-            case RatingCompat.RATING_HEART:
-                return RatingCompat.newHeartRating(((HeartRating) rating).hasHeart());
-            case RatingCompat.RATING_THUMB_UP_DOWN:
-                return RatingCompat.newThumbRating(((ThumbRating) rating).isThumbUp());
-            case RatingCompat.RATING_PERCENTAGE:
-                return RatingCompat.newPercentageRating(
-                        ((PercentageRating) rating).getPercentRating());
-            default:
-                return null;
-        }
-    }
-
-    /**
-     * Convert a list of {@link CommandButton} to a list of {@link ParcelImpl}.
-     */
-    public static List<ParcelImpl> convertCommandButtonListToParcelImplList(
-            List<CommandButton> commandButtonList) {
-        if (commandButtonList == null) {
-            return null;
-        }
-        List<ParcelImpl> parcelImplList = new ArrayList<>();
-        for (int i = 0; i < commandButtonList.size(); i++) {
-            final CommandButton commandButton = commandButtonList.get(i);
-            parcelImplList.add(MediaParcelUtils.toParcelable(commandButton));
-        }
-        return parcelImplList;
-    }
-
-    /**
-     * Convert a list of {@link MediaItem} to a list of {@link ParcelImplListSlice}.
-     */
-    public static ParcelImplListSlice convertMediaItemListToParcelImplListSlice(
-            List<MediaItem> mediaItemList) {
-        if (mediaItemList == null) {
-            return null;
-        }
-        List<ParcelImpl> itemParcelableList = new ArrayList<>();
-        for (int i = 0; i < mediaItemList.size(); i++) {
-            final MediaItem item = mediaItemList.get(i);
-            if (item != null) {
-                final ParcelImpl itemParcelImpl = MediaParcelUtils.toParcelable(item);
-                itemParcelableList.add(itemParcelImpl);
-            }
-        }
-        return new ParcelImplListSlice(itemParcelableList);
-    }
-
-    /**
-     * Convert a {@link SessionPlayer.PlayerState} and
-     * {@link SessionPlayer.BuffState} into {@link PlaybackStateCompat.State}.
-     */
-    public static int convertToPlaybackStateCompatState(int playerState, int bufferingState) {
-        switch (playerState) {
-            case SessionPlayer.PLAYER_STATE_PLAYING:
-                switch (bufferingState) {
-                    case SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED:
-                        return PlaybackStateCompat.STATE_BUFFERING;
-                }
-                return PlaybackStateCompat.STATE_PLAYING;
-            case SessionPlayer.PLAYER_STATE_PAUSED:
-                return PlaybackStateCompat.STATE_PAUSED;
-            case SessionPlayer.PLAYER_STATE_IDLE:
-                return PlaybackStateCompat.STATE_NONE;
-            case SessionPlayer.PLAYER_STATE_ERROR:
-                return PlaybackStateCompat.STATE_ERROR;
-        }
-        // For unknown value
-        return PlaybackStateCompat.STATE_ERROR;
-    }
-
-    /**
-     * Convert a {@link PlaybackStateCompat} into {@link SessionPlayer.PlayerState}.
-     */
-    public static int convertToPlayerState(PlaybackStateCompat state) {
-        if (state == null) {
-            return SessionPlayer.PLAYER_STATE_IDLE;
-        }
-        switch (state.getState()) {
-            case PlaybackStateCompat.STATE_ERROR:
-                return SessionPlayer.PLAYER_STATE_ERROR;
-            case PlaybackStateCompat.STATE_NONE:
-                return SessionPlayer.PLAYER_STATE_IDLE;
-            case PlaybackStateCompat.STATE_PAUSED:
-            case PlaybackStateCompat.STATE_STOPPED:
-            case PlaybackStateCompat.STATE_BUFFERING: // means paused for buffering.
-                return SessionPlayer.PLAYER_STATE_PAUSED;
-            case PlaybackStateCompat.STATE_FAST_FORWARDING:
-            case PlaybackStateCompat.STATE_PLAYING:
-            case PlaybackStateCompat.STATE_REWINDING:
-            case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
-            case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
-            case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
-            case PlaybackStateCompat.STATE_CONNECTING: // Note: there's no perfect match for this.
-                return SessionPlayer.PLAYER_STATE_PLAYING;
-        }
-        return SessionPlayer.PLAYER_STATE_ERROR;
-    }
-
-    /**
-     * Convert a {@link PlaybackStateCompat.State} into {@link SessionPlayer.BuffState}.
-     */
-    // Note: there's no perfect match for this.
-    public static int toBufferingState(int playbackStateCompatState) {
-        switch (playbackStateCompatState) {
-            case PlaybackStateCompat.STATE_BUFFERING:
-                return SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
-            case PlaybackStateCompat.STATE_PLAYING:
-                return SessionPlayer.BUFFERING_STATE_COMPLETE;
-            default:
-                return SessionPlayer.BUFFERING_STATE_UNKNOWN;
-        }
-    }
-
-    /**
-     * Convert a {@link MediaControllerCompat.PlaybackInfo} into
-     * {@link MediaController.PlaybackInfo}.
-     */
-    public static MediaController.PlaybackInfo toPlaybackInfo2(
-            MediaControllerCompat.PlaybackInfo info) {
-        return MediaController.PlaybackInfo.createPlaybackInfo(info.getPlaybackType(),
-                new AudioAttributesCompat.Builder().setLegacyStreamType(
-                        info.getAudioAttributes().getLegacyStreamType()).build(),
-                info.getVolumeControl(), info.getMaxVolume(), info.getCurrentVolume());
-    }
-
-    /**
-     * Returns whether the bundle is not parcelable.
-     */
-    public static boolean isUnparcelableBundle(Bundle bundle) {
-        if (bundle == null) {
-            return false;
-        }
-        bundle.setClassLoader(MediaUtils.class.getClassLoader());
-        try {
-            bundle.size();
-        } catch (Exception e) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Removes unparcelable bundles in the given list.
-     */
-    public static void keepUnparcelableBundlesOnly(final List<Bundle> bundles) {
-        if (bundles == null) {
-            return;
-        }
-        for (int i = bundles.size() - 1; i >= 0; --i) {
-            Bundle bundle = bundles.get(i);
-            if (isUnparcelableBundle(bundle)) {
-                bundles.remove(i);
-            }
-        }
-    }
-
-    private static @RatingCompat.Style int getRatingCompatStyle(Rating rating) {
-        if (rating instanceof HeartRating) {
-            return RatingCompat.RATING_HEART;
-        } else if (rating instanceof ThumbRating) {
-            return RatingCompat.RATING_THUMB_UP_DOWN;
-        } else if (rating instanceof StarRating) {
-            switch (((StarRating) rating).getMaxStars()) {
-                case 3:
-                    return RatingCompat.RATING_3_STARS;
-                case 4:
-                    return RatingCompat.RATING_4_STARS;
-                case 5:
-                    return RatingCompat.RATING_5_STARS;
-            }
-        } else if (rating instanceof PercentageRating) {
-            return RatingCompat.RATING_PERCENTAGE;
-        }
-        return RatingCompat.RATING_NONE;
-    }
-
-    /**
-     * Converts the rootHints, option, and extra to the {@link LibraryParams}.
-     *
-     * @param legacyBundle
-     * @return new LibraryParams
-     */
-    @Nullable
-    public static LibraryParams convertToLibraryParams(@NonNull Context context,
-            @Nullable Bundle legacyBundle) {
-        if (legacyBundle == null) {
-            return null;
-        }
-        try {
-            legacyBundle.setClassLoader(context.getClassLoader());
-            return new LibraryParams.Builder().setExtras(legacyBundle)
-                    .setRecent(legacyBundle.getBoolean(BrowserRoot.EXTRA_RECENT))
-                    .setOffline(legacyBundle.getBoolean(BrowserRoot.EXTRA_OFFLINE))
-                    .setSuggested(legacyBundle.getBoolean(BrowserRoot.EXTRA_SUGGESTED))
-                    .build();
-        } catch (Exception e) {
-            // Failure when unpacking the legacy bundle.
-            return new LibraryParams.Builder().setExtras(legacyBundle).build();
-        }
-    }
-
-    /**
-     * Converts {@link LibraryParams} to the root hints.
-     *
-     * @param params
-     * @return new root hints
-     */
-    @Nullable
-    public static Bundle convertToRootHints(@Nullable LibraryParams params) {
-        if (params == null) {
-            return null;
-        }
-        Bundle rootHints = (params.getExtras() == null)
-                ? new Bundle() : new Bundle(params.getExtras());
-        rootHints.putBoolean(BrowserRoot.EXTRA_RECENT, params.isRecent());
-        rootHints.putBoolean(BrowserRoot.EXTRA_OFFLINE, params.isOffline());
-        rootHints.putBoolean(BrowserRoot.EXTRA_SUGGESTED, params.isSuggested());
-        return rootHints;
-    }
-
-    /**
-     * Removes all null elements from the list and returns it.
-     *
-     * @param list
-     * @return
-     */
-    @Nullable
-    public static <T> List<T> removeNullElements(@Nullable List<T> list) {
-        if (list == null) {
-            return null;
-        }
-        List<T> newList = new ArrayList<>();
-        for (T item : list) {
-            if (item != null) {
-                newList.add(item);
-            }
-        }
-        return newList;
-    }
-
-    /**
-     * Converts {@link MediaControllerCompat#getFlags() session flags} and
-     * {@link PlaybackStateCompat} to the {@link SessionCommandGroup}.
-     * <p>
-     * This ignores {@link PlaybackStateCompat#getActions() actions} in the
-     * {@link PlaybackStateCompat} to workaround media apps' issues that they don't set playback
-     * state correctly.
-     *
-     * @param sessionFlags session flag
-     * @param state playback state
-     * @return the converted session command group
-     */
-    @NonNull
-    public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags,
-            @Nullable PlaybackStateCompat state) {
-        SessionCommandGroup.Builder commandsBuilder = new SessionCommandGroup.Builder();
-
-        // MediaSessionCompat only support COMMAND_VERSION_1.
-        commandsBuilder.addAllPlayerBasicCommands(COMMAND_VERSION_1);
-        boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
-        if (includePlaylistCommands) {
-            commandsBuilder.addAllPlayerPlaylistCommands(COMMAND_VERSION_1);
-        }
-        commandsBuilder.addAllVolumeCommands(COMMAND_VERSION_1);
-        commandsBuilder.addAllSessionCommands(COMMAND_VERSION_1);
-
-        commandsBuilder.removeCommand(new SessionCommand(COMMAND_CODE_PLAYER_SET_SPEED));
-        commandsBuilder.removeCommand(new SessionCommand(COMMAND_CODE_PLAYER_SET_SURFACE));
-        commandsBuilder.removeCommand(new SessionCommand(COMMAND_CODE_PLAYER_SELECT_TRACK));
-        commandsBuilder.removeCommand(new SessionCommand(COMMAND_CODE_PLAYER_DESELECT_TRACK));
-
-        if (state != null && state.getCustomActions() != null) {
-            for (CustomAction customAction : state.getCustomActions()) {
-                commandsBuilder.addCommand(
-                        new SessionCommand(customAction.getAction(), customAction.getExtras()));
-            }
-        }
-        return commandsBuilder.build();
-    }
-
-    /**
-     * Converts {@link CustomAction} in the {@link PlaybackStateCompat} to the custom layout which
-     * is the list of the {@link CommandButton}.
-     *
-     * @param state playback state
-     * @return custom layout. Always non-null.
-     */
-    @NonNull
-    public static List<CommandButton> convertToCustomLayout(@Nullable PlaybackStateCompat state) {
-        List<CommandButton> layout = new ArrayList<>();
-        if (state == null) {
-            return layout;
-        }
-        for (CustomAction action : state.getCustomActions()) {
-            CommandButton button = new CommandButton.Builder()
-                    .setCommand(new SessionCommand(action.getAction(), action.getExtras()))
-                    .setDisplayName(action.getName())
-                    .setEnabled(true)
-                    .setIconResId(action.getIcon()).build();
-            layout.add(button);
-        }
-        return layout;
-    }
-
-    /**
-     * Gets the legacy stream type from {@link androidx.media.AudioAttributesCompat}.
-     *
-     * @param attrs audio attributes
-     * @return int legacy stream type from {@link AudioManager}
-     */
-    public static int getLegacyStreamType(@Nullable AudioAttributesCompat attrs) {
-        int stream;
-        if (attrs == null) {
-            stream = AudioManager.STREAM_MUSIC;
-        } else {
-            stream = attrs.getLegacyStreamType();
-            if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                // Usually, AudioAttributesCompat#getLegacyStreamType() does not return
-                // USE_DEFAULT_STREAM_TYPE unless the developer sets it with
-                // AudioAttributesCompat.Builder#setLegacyStreamType().
-                // But for safety, let's convert USE_DEFAULT_STREAM_TYPE to STREAM_MUSIC here.
-                stream = AudioManager.STREAM_MUSIC;
-            }
-        }
-        return stream;
-    }
-
-    @SuppressWarnings({"ParcelClassLoader", "deprecation"})
-    static boolean doesBundleHaveCustomParcelable(@NonNull Bundle bundle) {
-        // Try writing the bundle to parcel, and read it with framework classloader.
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeBundle(bundle);
-            parcel.setDataPosition(0);
-            Bundle out = parcel.readBundle(null);
-            for (String key : out.keySet()) {
-                // Attempt to retrieve all Bundle values with the framework class loader.
-                out.get(key);
-            }
-            return false;
-        } catch (BadParcelableException e) {
-            Log.d(TAG, "Custom parcelables are not allowed", e);
-            return true;
-        } finally {
-            parcel.recycle();
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/PercentageRating.java b/media2/media2-session/src/main/java/androidx/media2/session/PercentageRating.java
deleted file mode 100644
index 6a3fb90..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/PercentageRating.java
+++ /dev/null
@@ -1,92 +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.media2.session;
-
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.Rating;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * A class for rating expressed as a percentage.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class PercentageRating implements Rating {
-    private static final float RATING_NOT_RATED = -1.0f;
-
-    @ParcelField(1)
-    float mPercent;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Creates a unrated PercentageRating instance.
-     */
-    public PercentageRating() {
-        mPercent = RATING_NOT_RATED;
-    }
-
-    /**
-     * Creates a PercentageRating instance with the given percentage.
-     * If {@code percent} is less than 0f or greater than 100f, it will throw
-     * IllegalArgumentException.
-     *
-     * @param percent the value of the rating
-     */
-    public PercentageRating(float percent) {
-        if (percent < 0.0f || percent > 100.0f) {
-            throw new IllegalArgumentException("percent should be in the rage of [0, 100]");
-        }
-        mPercent = percent;
-    }
-
-    @Override
-    public boolean isRated() {
-        return mPercent != RATING_NOT_RATED;
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mPercent);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof PercentageRating)) {
-            return false;
-        }
-        return mPercent == ((PercentageRating) obj).mPercent;
-    }
-
-    @Override
-    public String toString() {
-        return "PercentageRating: " + (isRated() ? "percentage=" + mPercent : "unrated");
-    }
-
-    /**
-     * Returns the percentage-based rating value.
-     *
-     * @return a rating value greater or equal to 0.0f, or a negative value if it is unrated.
-     */
-    public float getPercentRating() {
-        return mPercent;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/RemoteResult.java b/media2/media2-session/src/main/java/androidx/media2/session/RemoteResult.java
deleted file mode 100644
index 9d8f72c..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/RemoteResult.java
+++ /dev/null
@@ -1,70 +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.media2.session;
-
-import androidx.media2.common.BaseResult;
-
-/**
- * Base interface for result classes in {@link MediaSession} and {@link MediaController} that may be
- * sent across the processes.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-interface RemoteResult extends BaseResult {
-    /**
-     * Result code representing that the session and controller were disconnected.
-     */
-    int RESULT_ERROR_SESSION_DISCONNECTED = -100;
-
-    /**
-     * Result code representing that the authentication has expired.
-     */
-    int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102;
-
-    /**
-     * Result code representing that a premium account is required.
-     */
-    int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103;
-
-    /**
-     * Result code representing that too many concurrent streams are detected.
-     */
-    int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104;
-
-    /**
-     * Result code representing that the content is blocked due to parental controls.
-     */
-    int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105;
-
-    /**
-     * Result code representing that the content is blocked due to being regionally unavailable.
-     */
-    int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106;
-
-    /**
-     * Result code representing that the application cannot skip any more because the skip limit is
-     * reached.
-     */
-    int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107;
-
-    /**
-     * Result code representing that the session needs user's manual intervention.
-     */
-    int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108;
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/RemoteSessionPlayer.java b/media2/media2-session/src/main/java/androidx/media2/session/RemoteSessionPlayer.java
deleted file mode 100644
index 9542545..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/RemoteSessionPlayer.java
+++ /dev/null
@@ -1,151 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.media2.common.SessionPlayer;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-
-/**
- * Base interface for all remote media players that want media session and playback happens on the
- * remote device through MediaRouter.
- *
- * <p>If you use this to the {@link MediaSession}, session would dispatch incoming volume change
- * event to the player instead of changing device stream volume.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public abstract class RemoteSessionPlayer extends SessionPlayer {
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface VolumeControlType {}
-
-    /**
-     * The volume is fixed and can not be modified. Requests to change volume
-     * should be ignored.
-     */
-    public static final int VOLUME_CONTROL_FIXED = 0;
-
-    /**
-     * The volume control uses relative adjustment via
-     * {@link #adjustVolume(int)}. Attempts to set the volume to a specific
-     * value should be ignored.
-     */
-    public static final int VOLUME_CONTROL_RELATIVE = 1;
-
-    /**
-     * The volume control uses an absolute value. It may be adjusted using
-     * {@link #adjustVolume(int)} or set directly using
-     * {@link #setVolume(int)}.
-     */
-    public static final int VOLUME_CONTROL_ABSOLUTE = 2;
-
-    /**
-     * Adjusts player volume with the direction. Override this API to customize volume change in
-     * remote device.
-     * <p>
-     * This would be ignored when volume control type is {@link #VOLUME_CONTROL_FIXED}.
-     *
-     * @param direction direction of the volume changes. Positive value for volume up, negative for
-     *                  volume down.
-     * @return result of adjusting the volume. Shouldn't be {@code null}.
-     */
-    @NonNull
-    public abstract Future<PlayerResult> adjustVolume(int direction);
-
-    /**
-     * Sets the volume of the audio of the media to play, expressed as a linear multiplier
-     * on the audio samples.
-     * <p>
-     * Note that this volume is specific to the player, and is separate from stream volume
-     * used across the platform.
-     * <p>
-     * A value of {@code 0} indicates muting. See {@link #getMaxVolume()} for the volume range
-     * supported by this player.
-     *
-     * @param volume a value between {@code 0} and {@link #getMaxVolume()}.
-     * @return result of setting the volume. Shouldn't be {@code null}.
-     */
-    @NonNull
-    public abstract Future<PlayerResult> setVolume(int volume);
-
-    /**
-     * Gets the current volume of this player to this player.
-     * <p>
-     * Note that it does not take into account the associated stream volume because the playback is
-     * happening outside of the phone device.
-     *
-     * @return the player volume.
-     */
-    public abstract int getVolume();
-
-    /**
-     * Gets the maximum volume that can be used in {@link #setVolume(int)}.
-     *
-     * @return the maximum volume. Shouldn't be negative.
-     */
-    public abstract int getMaxVolume();
-
-    /**
-     * Gets the volume type.
-     * <p>
-     * This shouldn't be changed after instantiation.
-     *
-     * @return one of the volume type
-     * @see #VOLUME_CONTROL_FIXED
-     * @see #VOLUME_CONTROL_RELATIVE
-     * @see #VOLUME_CONTROL_ABSOLUTE
-     */
-    @VolumeControlType
-    public abstract int getVolumeControlType();
-
-    /**
-     * A callback class to receive notifications for events on the remote session player. See {@link
-     * #registerPlayerCallback(Executor, PlayerCallback)} to register this callback.
-     *
-     * <p>This is registered by {@link MediaSession} to notify volume changes to the {@link
-     * MediaController}.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static class Callback extends SessionPlayer.PlayerCallback {
-        /**
-         * Called to indicate that the volume has changed.
-         *
-         * @param player the player that has changed volume.
-         * @param volume the new volume
-         * @see #setVolume(int)
-         */
-        public void onVolumeChanged(@NonNull RemoteSessionPlayer player, int volume) {
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SequencedFutureManager.java b/media2/media2-session/src/main/java/androidx/media2/session/SequencedFutureManager.java
deleted file mode 100644
index 3ab6fee..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SequencedFutureManager.java
+++ /dev/null
@@ -1,152 +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.media2.session;
-
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.collection.ArrayMap;
-import androidx.concurrent.futures.AbstractResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.Closeable;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages {@link SequencedFuture} that contains sequence number to be shared across the process.
- */
-class SequencedFutureManager implements Closeable {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "SequencedFutureManager";
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private int mNextSequenceNumber;
-    @GuardedBy("mLock")
-    private ArrayMap<Integer, SequencedFuture<?>> mSeqToFutureMap = new ArrayMap<>();
-
-    /**
-     * Obtains next sequence number without creating future. Used for methods with no return
-     * (e.g. close())
-     *
-     * @return sequence number
-     */
-    public int obtainNextSequenceNumber() {
-        synchronized (mLock) {
-            return mNextSequenceNumber++;
-        }
-    }
-
-    /**
-     * Creates {@link SequencedFuture} with sequence number. Used to return
-     * {@link ListenableFuture} for remote process call.
-     *
-     * @return AbstractFuture with sequence number
-     */
-    // TODO: Find better way to get closed result -- result has completion time, and it should be
-    //       set when the manager is closed, not now.
-    public <T> SequencedFuture<T> createSequencedFuture(T resultWhenClosed) {
-        synchronized (mLock) {
-            int seq = obtainNextSequenceNumber();
-            SequencedFuture<T> result = SequencedFuture.create(seq, resultWhenClosed);
-            mSeqToFutureMap.put(seq, result);
-            return result;
-        }
-    }
-
-    /**
-     * Sets result of the {@link SequencedFuture} with the sequence id. Specified future will be
-     * removed from the manager.
-     *
-     * @param seq sequence number to find future
-     * @param result result to set
-     */
-    @SuppressWarnings("unchecked")
-    public <T> void setFutureResult(int seq, T result) {
-        synchronized (mLock) {
-            SequencedFuture<?> future = mSeqToFutureMap.remove(seq);
-            if (future != null) {
-                if (result == null
-                        || future.getResultWhenClosed().getClass() == result.getClass()) {
-                    ((SequencedFuture<T>) future).set(result);
-                } else {
-                    Log.w(TAG, "Type mismatch, expected "
-                            + future.getResultWhenClosed().getClass()
-                            + ", but was " + result.getClass());
-                }
-            } else {
-                if (DEBUG) {
-                    // Note: May not be an error if the caller doesn't return ListenableFuture
-                    //       e.g. MediaSession#broadcastCustomCommand
-                    Log.d(TAG, "Unexpected sequence number, seq=" + seq,
-                            new IllegalArgumentException());
-                }
-            }
-        }
-    }
-
-    @Override
-    public void close() {
-        List<SequencedFuture<?>> pendingResults;
-        synchronized (mLock) {
-            pendingResults = new ArrayList<>(mSeqToFutureMap.values());
-            mSeqToFutureMap.clear();
-        }
-        for (SequencedFuture<?> result: pendingResults) {
-            result.setWithTheValueOfResultWhenClosed();
-        }
-    }
-
-    static final class SequencedFuture<T> extends AbstractResolvableFuture<T> {
-        private final int mSequenceNumber;
-        private final T mResultWhenClosed;
-
-        /**
-         * Creates a new {@code ResolvableFuture} that can be completed or cancelled by a later
-         * method call.
-         */
-        static <T> SequencedFuture<T> create(int seq, @NonNull T resultWhenClosed) {
-            return new SequencedFuture<>(seq, resultWhenClosed);
-        }
-
-        @Override
-        public boolean set(@Nullable T value) {
-            return super.set(value);
-        }
-
-        void setWithTheValueOfResultWhenClosed() {
-            set(mResultWhenClosed);
-        }
-
-        public int getSequenceNumber() {
-            return mSequenceNumber;
-        }
-
-        public @NonNull T getResultWhenClosed() {
-            return mResultWhenClosed;
-        }
-
-        private SequencedFuture(int seq, @NonNull T resultWhenClosed) {
-            mSequenceNumber = seq;
-            mResultWhenClosed = resultWhenClosed;
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionCommand.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionCommand.java
deleted file mode 100644
index b3e85f8..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionCommand.java
+++ /dev/null
@@ -1,677 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Defines a command that a {@link MediaController} can send to a {@link MediaSession}.
- *
- * <p>If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command. If
- * {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and {@link
- * #getCustomAction()} shouldn't be {@code null}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class SessionCommand implements VersionedParcelable {
-    /**
-     * The first version of session commands. This version is for commands introduced in
-     * AndroidX media2-session 1.0.0.
-     * <p>
-     * This would be used to specify which commands should be added by
-     * {@link SessionCommandGroup.Builder#addAllPredefinedCommands(int)}
-     *
-     * @see SessionCommandGroup.Builder#addAllPredefinedCommands(int)
-     */
-    public static final int COMMAND_VERSION_1 = 1;
-
-    /**
-     * The 2nd version of session commands. This version is for commands introduced in
-     * AndroidX media2-session 1.1.0.
-     * <p>
-     * This would be used to specify which commands should be added by
-     * {@link SessionCommandGroup.Builder#addAllPredefinedCommands(int)}
-     *
-     * @see SessionCommandGroup.Builder#addAllPredefinedCommands(int)
-     */
-    public static final int COMMAND_VERSION_2 = 2;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public static final int COMMAND_VERSION_CURRENT = COMMAND_VERSION_2;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @SuppressLint("UniqueConstants")
-    @IntDef({COMMAND_VERSION_1, COMMAND_VERSION_2, COMMAND_VERSION_CURRENT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CommandVersion {}
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @IntDef({COMMAND_CODE_CUSTOM,
-            COMMAND_CODE_PLAYER_PLAY,
-            COMMAND_CODE_PLAYER_PAUSE,
-            COMMAND_CODE_PLAYER_PREPARE,
-            COMMAND_CODE_PLAYER_SEEK_TO,
-            COMMAND_CODE_PLAYER_SET_SPEED,
-            COMMAND_CODE_PLAYER_GET_PLAYLIST,
-            COMMAND_CODE_PLAYER_SET_PLAYLIST,
-            COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
-            COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
-            COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA,
-            COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM,
-            COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM,
-            COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-            COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
-            COMMAND_CODE_PLAYER_SET_SURFACE,
-            COMMAND_CODE_PLAYER_SELECT_TRACK,
-            COMMAND_CODE_PLAYER_DESELECT_TRACK,
-            COMMAND_CODE_VOLUME_SET_VOLUME,
-            COMMAND_CODE_VOLUME_ADJUST_VOLUME,
-            COMMAND_CODE_SESSION_FAST_FORWARD,
-            COMMAND_CODE_SESSION_REWIND,
-            COMMAND_CODE_SESSION_SKIP_FORWARD,
-            COMMAND_CODE_SESSION_SKIP_BACKWARD,
-            COMMAND_CODE_SESSION_SET_MEDIA_URI,
-            COMMAND_CODE_SESSION_SET_RATING,
-            COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT,
-            COMMAND_CODE_LIBRARY_SUBSCRIBE,
-            COMMAND_CODE_LIBRARY_UNSUBSCRIBE,
-            COMMAND_CODE_LIBRARY_GET_CHILDREN,
-            COMMAND_CODE_LIBRARY_GET_ITEM,
-            COMMAND_CODE_LIBRARY_SEARCH,
-            COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CommandCode {}
-
-    /**
-     * Command code for the custom command which can be defined by string action in the
-     * {@link SessionCommand}.
-     */
-    public static final int COMMAND_CODE_CUSTOM = 0;
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Player commands (i.e. commands to {@link SessionPlayer})
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    static final SparseArray<List<Integer>> VERSION_PLAYER_BASIC_COMMANDS_MAP = new SparseArray<>();
-    static final SparseArray<List<Integer>> VERSION_PLAYER_PLAYLIST_COMMANDS_MAP =
-            new SparseArray<>();
-
-    /**
-     * Command code for {@link MediaController#play()}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo,
-     * SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_PLAY = 10000;
-
-    /**
-     * Command code for {@link MediaController#pause()}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo,
-     * SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_PAUSE = 10001;
-
-    /**
-     * Command code for {@link MediaController#prepare()}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo,
-     * SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_PREPARE = 10002;
-
-    /**
-     * Command code for {@link MediaController#seekTo(long)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo,
-     * SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003;
-
-    /**
-     * Command code for {@link MediaController#setPlaybackSpeed(float)}}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo,
-     * SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004;
-
-    /**
-     * Command code for {@link MediaController#getPlaylist()}. This will expose metadata
-     * information to the controller.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005;
-
-    /**
-     * Command code for {@link MediaController#setPlaylist(List, MediaMetadata)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006;
-
-    /**
-     * Command code for {@link MediaController#skipToPlaylistItem(int)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007;
-
-    /**
-     * Command code for {@link MediaController#skipToPreviousPlaylistItem()}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(
-     * MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008;
-
-    /**
-     * Command code for {@link MediaController#skipToNextPlaylistItem()}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the {@link SessionCallback#onCommandRequest(
-     * MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-
-    public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009;
-
-    /**
-     * Command code for {@link MediaController#setShuffleMode(int)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010;
-
-    /**
-     * Command code for {@link MediaController#setRepeatMode(int)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011;
-
-    /**
-     * Command code for {@link MediaController#getPlaylistMetadata()}. This will expose metadata
-     * information to the controller.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012;
-
-    /**
-     * Command code for {@link MediaController#addPlaylistItem(int, String)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013;
-
-    /**
-     * Command code for {@link MediaController#removePlaylistItem(int)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014;
-
-    /**
-     * Command code for {@link MediaController#replacePlaylistItem(int, String)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015;
-
-    /**
-     * Command code for {@link MediaController#getCurrentMediaItem()}. This will expose metadata
-     * information to the controller.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016;
-
-    /**
-     * Command code for {@link MediaController#updatePlaylistMetadata(MediaMetadata)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017;
-
-    /**
-     * Command code for {@link MediaController#setMediaItem(String)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018;
-
-    /**
-     * Command code for {@link MediaController#replacePlaylistItem(int, String)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_2}.
-     */
-    public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019;
-
-    /**
-     * Command code for {@link MediaController#setSurface(Surface)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000;
-
-    /**
-     * Command code for {@link MediaController#selectTrack(SessionPlayer.TrackInfo)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001;
-
-    /**
-     * Command code for {@link MediaController#deselectTrack(SessionPlayer.TrackInfo)}.
-     * <p>
-     * Command would be sent directly to the player if the session doesn't reject the request
-     * through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002;
-
-    static {
-        VERSION_PLAYER_BASIC_COMMANDS_MAP.put(COMMAND_VERSION_1,
-                Arrays.asList(COMMAND_CODE_PLAYER_PLAY,
-                        COMMAND_CODE_PLAYER_PAUSE,
-                        COMMAND_CODE_PLAYER_PREPARE,
-                        COMMAND_CODE_PLAYER_SEEK_TO,
-                        COMMAND_CODE_PLAYER_SET_SPEED,
-                        COMMAND_CODE_PLAYER_SET_SURFACE,
-                        COMMAND_CODE_PLAYER_SELECT_TRACK,
-                        COMMAND_CODE_PLAYER_DESELECT_TRACK));
-    }
-
-    static {
-        VERSION_PLAYER_PLAYLIST_COMMANDS_MAP.put(COMMAND_VERSION_1,
-                Arrays.asList(COMMAND_CODE_PLAYER_GET_PLAYLIST,
-                        COMMAND_CODE_PLAYER_SET_PLAYLIST,
-                        COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
-                        COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
-                        COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA,
-                        COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-                        COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM,
-                        COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-                        COMMAND_CODE_PLAYER_SET_MEDIA_ITEM));
-        VERSION_PLAYER_PLAYLIST_COMMANDS_MAP.put(COMMAND_VERSION_2,
-                Collections.singletonList(COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM));
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Volume commands (i.e. commands to {@link AudioManager} or {@link RemoteSessionPlayer})
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    static final SparseArray<List<Integer>> VERSION_VOLUME_COMMANDS_MAP = new SparseArray<>();
-
-    /**
-     * Command code for {@link MediaController#setVolumeTo(int, int)}.
-     * <p>
-     * If the session doesn't reject the request through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)},
-     * command would adjust the device volume. It would send to the player directly only if it's
-     * a {@link RemoteSessionPlayer}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000;
-
-    /**
-     * Command code for {@link MediaController#adjustVolume(int, int)}.
-     * <p>
-     * If the session doesn't reject the request through the
-     * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)},
-     * command would adjust the device volume. It would send to the player directly only if it's
-     * a {@link RemoteSessionPlayer}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001;
-
-    static {
-        VERSION_VOLUME_COMMANDS_MAP.put(COMMAND_VERSION_1,
-                Arrays.asList(COMMAND_CODE_VOLUME_SET_VOLUME,
-                        COMMAND_CODE_VOLUME_ADJUST_VOLUME));
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Session commands (i.e. commands to {@link MediaSession#SessionCallback})
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    static final SparseArray<List<Integer>> VERSION_SESSION_COMMANDS_MAP = new SparseArray<>();
-
-    /**
-     * Command code for {@link MediaController#fastForward()}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000;
-
-    /**
-     * Command code for {@link MediaController#rewind()}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_SESSION_REWIND = 40001;
-
-    /**
-     * Command code for {@link MediaController#skipForward()}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002;
-
-    /**
-     * Command code for {@link MediaController#skipBackward()}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003;
-
-    // 40004~40009: removed
-
-    /**
-     * Command code for {@link MediaController#setRating(String, Rating)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_SESSION_SET_RATING = 40010;
-
-    /**
-     * Command code for {@link MediaController#setMediaUri}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_2}.
-     */
-    public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011;
-
-    static {
-        VERSION_SESSION_COMMANDS_MAP.put(COMMAND_VERSION_1,
-                Arrays.asList(COMMAND_CODE_SESSION_FAST_FORWARD,
-                        COMMAND_CODE_SESSION_REWIND,
-                        COMMAND_CODE_SESSION_SKIP_FORWARD,
-                        COMMAND_CODE_SESSION_SKIP_BACKWARD,
-                        COMMAND_CODE_SESSION_SET_RATING));
-        VERSION_SESSION_COMMANDS_MAP.put(COMMAND_VERSION_2,
-                Collections.singletonList(COMMAND_CODE_SESSION_SET_MEDIA_URI));
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Session commands (i.e. commands to {@link MediaLibrarySession#MediaLibrarySessionCallback})
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    static final SparseArray<List<Integer>> VERSION_LIBRARY_COMMANDS_MAP = new SparseArray<>();
-
-    /**
-     * Command code for {@link MediaBrowser#getLibraryRoot(LibraryParams)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000;
-
-    /**
-     * Command code for {@link MediaBrowser#subscribe(String, LibraryParams)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001;
-
-    /**
-     * Command code for {@link MediaBrowser#unsubscribe(String)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002;
-
-    /**
-     * Command code for {@link MediaBrowser#getChildren(String, int, int, LibraryParams)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003;
-
-    /**
-     * Command code for {@link MediaBrowser#getItem(String)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004;
-
-    /**
-     * Command code for {@link MediaBrowser#search(String, LibraryParams)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005;
-
-    /**
-     * Command code for {@link MediaBrowser#getSearchResult(String, int, int, LibraryParams)}.
-     * <p>
-     * Code version is {@link #COMMAND_VERSION_1}.
-     */
-    public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006;
-
-    static {
-        VERSION_LIBRARY_COMMANDS_MAP.put(COMMAND_VERSION_1,
-                Arrays.asList(COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT,
-                        COMMAND_CODE_LIBRARY_SUBSCRIBE,
-                        COMMAND_CODE_LIBRARY_UNSUBSCRIBE,
-                        COMMAND_CODE_LIBRARY_GET_CHILDREN,
-                        COMMAND_CODE_LIBRARY_GET_ITEM,
-                        COMMAND_CODE_LIBRARY_SEARCH,
-                        COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT));
-    }
-
-    @ParcelField(1)
-    @CommandCode int mCommandCode;
-    // Nonnull if it's custom command
-    @ParcelField(2)
-    String mCustomAction;
-    @ParcelField(3)
-    Bundle mCustomExtras;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Used for VersionedParcelable.
-     */
-    SessionCommand() {
-    }
-
-    /**
-     * Constructor for creating a predefined command.
-     *
-     * @param commandCode A command code for predefined command.
-     */
-    public SessionCommand(@CommandCode int commandCode) {
-        if (commandCode == COMMAND_CODE_CUSTOM) {
-            throw new IllegalArgumentException("commandCode shouldn't be COMMAND_CODE_CUSTOM");
-        }
-        mCommandCode = commandCode;
-        mCustomAction = null;
-        mCustomExtras = null;
-    }
-
-    /**
-     * Constructor for creating a custom command.
-     *
-     * @param action The action of this custom command.
-     * @param extras An extra bundle for this custom command.
-     */
-    public SessionCommand(@NonNull String action, @Nullable Bundle extras) {
-        if (action == null) {
-            throw new NullPointerException("action shouldn't be null");
-        }
-        mCommandCode = COMMAND_CODE_CUSTOM;
-        mCustomAction = action;
-        mCustomExtras = extras;
-    }
-
-    /**
-     * Gets the command code of a predefined command.
-     * This will return {@link #COMMAND_CODE_CUSTOM} for a custom command.
-     */
-    @CommandCode
-    public int getCommandCode() {
-        return mCommandCode;
-    }
-
-    /**
-     * Gets the action of a custom command.
-     * This will return {@code null} for a predefined command.
-     */
-    @Nullable
-    public String getCustomAction() {
-        return mCustomAction;
-    }
-
-    /**
-     * Gets the extra bundle of a custom command.
-     * This will return {@code null} for a predefined command.
-     */
-    @Nullable
-    public Bundle getCustomExtras() {
-        return mCustomExtras;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SessionCommand)) {
-            return false;
-        }
-        SessionCommand other = (SessionCommand) obj;
-        return mCommandCode == other.mCommandCode
-                && TextUtils.equals(mCustomAction, other.mCustomAction);
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mCustomAction, mCommandCode);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionCommandGroup.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionCommandGroup.java
deleted file mode 100644
index 5a124c2..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionCommandGroup.java
+++ /dev/null
@@ -1,264 +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.media2.session;
-
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_CUSTOM;
-import static androidx.media2.session.SessionCommand.COMMAND_VERSION_1;
-import static androidx.media2.session.SessionCommand.COMMAND_VERSION_CURRENT;
-
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.session.SessionCommand.CommandCode;
-import androidx.media2.session.SessionCommand.CommandVersion;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A set of {@link SessionCommand} which represents a command group.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class SessionCommandGroup implements VersionedParcelable {
-    private static final String TAG = "SessionCommandGroup";
-
-    @ParcelField(1)
-    Set<SessionCommand> mCommands = new HashSet<>();
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Default Constructor.
-     */
-    public SessionCommandGroup() { }
-
-    /**
-     * Creates a new SessionCommandGroup with commands copied from another object.
-     *
-     * @param commands The collection of commands to copy.
-     */
-    public SessionCommandGroup(@Nullable Collection<SessionCommand> commands) {
-        if (commands != null) {
-            mCommands.addAll(commands);
-        }
-    }
-
-    /**
-     * Checks whether this command group has a command that matches given {@code command}.
-     *
-     * @param command A command to find. Shouldn't be {@code null}.
-     */
-    public boolean hasCommand(@NonNull SessionCommand command) {
-        if (command == null) {
-            throw new NullPointerException("command shouldn't be null");
-        }
-        return mCommands.contains(command);
-    }
-
-    /**
-     * Checks whether this command group has a command that matches given {@code commandCode}.
-     *
-     * @param commandCode A command code to find.
-     *                    Shouldn't be {@link SessionCommand#COMMAND_CODE_CUSTOM}.
-     */
-    public boolean hasCommand(@CommandCode int commandCode) {
-        if (commandCode == COMMAND_CODE_CUSTOM) {
-            throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
-        }
-        for (SessionCommand command : mCommands) {
-            if (command.getCommandCode() == commandCode) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Gets all commands of this command group.
-     */
-    @NonNull
-    public Set<SessionCommand> getCommands() {
-        return new HashSet<>(mCommands);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-        if (!(obj instanceof SessionCommandGroup)) return false;
-
-        SessionCommandGroup that = (SessionCommandGroup) obj;
-        if (mCommands == null) {
-            return that.mCommands == null;
-        }
-        return mCommands.equals(that.mCommands);
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hashCode(mCommands);
-    }
-
-    /**
-     * Builds a {@link SessionCommandGroup} object.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public static final class Builder {
-        private Set<SessionCommand> mCommands;
-
-        public Builder() {
-            mCommands = new HashSet<>();
-        }
-
-        /**
-         * Creates a new builder for {@link SessionCommandGroup} with commands copied from another
-         * {@link SessionCommandGroup} object.
-         * @param commandGroup
-         */
-        public Builder(@NonNull SessionCommandGroup commandGroup) {
-            if (commandGroup == null) {
-                throw new NullPointerException("commandGroup shouldn't be null");
-            }
-            mCommands = commandGroup.getCommands();
-        }
-
-        /**
-         * Adds a command to this command group.
-         *
-         * @param command A command to add. Shouldn't be {@code null}.
-         */
-        @NonNull
-        public Builder addCommand(@NonNull SessionCommand command) {
-            if (command == null) {
-                throw new NullPointerException("command shouldn't be null");
-            }
-            mCommands.add(command);
-            return this;
-        }
-
-        /**
-         * Adds all predefined session commands except for the commands added after the specified
-         * version without default implementation. This provides convenient way to add commands
-         * with implementation.
-         * <p>
-         * When you update support library version, it's recommended to take a look
-         * {@link SessionCommand} to double check whether this only adds commands that you want.
-         * You may increase the version here.
-         *
-         * @param version command version
-         * @see SessionCommand#COMMAND_VERSION_1
-         * @see SessionCommand#COMMAND_VERSION_2
-         * @see MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)
-         */
-        @NonNull
-        public Builder addAllPredefinedCommands(@CommandVersion int version) {
-            if (version < COMMAND_VERSION_1 || version > COMMAND_VERSION_CURRENT) {
-                throw new IllegalArgumentException("Unknown command version " + version);
-            }
-            addAllPlayerCommands(version);
-            addAllVolumeCommands(version);
-            addAllSessionCommands(version);
-            addAllLibraryCommands(version);
-            return this;
-        }
-
-        /**
-         * Removes a command from this group which matches given {@code command}.
-         *
-         * @param command A command to find. Shouldn't be {@code null}.
-         */
-        @NonNull
-        public Builder removeCommand(@NonNull SessionCommand command) {
-            if (command == null) {
-                throw new NullPointerException("command shouldn't be null");
-            }
-            mCommands.remove(command);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllPlayerCommands(@CommandVersion int version) {
-            addAllPlayerBasicCommands(version);
-            addAllPlayerPlaylistCommands(version);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllPlayerBasicCommands(@CommandVersion int version) {
-            addCommands(version, SessionCommand.VERSION_PLAYER_BASIC_COMMANDS_MAP);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllPlayerPlaylistCommands(@CommandVersion int version) {
-            addCommands(version, SessionCommand.VERSION_PLAYER_PLAYLIST_COMMANDS_MAP);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllVolumeCommands(@CommandVersion int version) {
-            addCommands(version, SessionCommand.VERSION_VOLUME_COMMANDS_MAP);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllSessionCommands(@CommandVersion int version) {
-            addCommands(version, SessionCommand.VERSION_SESSION_COMMANDS_MAP);
-            return this;
-        }
-
-        @NonNull
-        Builder addAllLibraryCommands(@CommandVersion int version) {
-            addCommands(version, SessionCommand.VERSION_LIBRARY_COMMANDS_MAP);
-            return this;
-        }
-
-        private void addCommands(@CommandVersion int version, SparseArray<List<Integer>> map) {
-            for (int i = 0; i < map.size(); i++) {
-                if (map.keyAt(i) > version) {
-                    break;
-                }
-                for (int commandCode : map.valueAt(i)) {
-                    addCommand(new SessionCommand(commandCode));
-                }
-            }
-        }
-
-        /**
-         * Builds a {@link SessionCommandGroup}.
-         *
-         * @return a new {@link SessionCommandGroup}
-         */
-        @NonNull
-        public SessionCommandGroup build() {
-            return new SessionCommandGroup(mCommands);
-        }
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionResult.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionResult.java
deleted file mode 100644
index 151b3219..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionResult.java
+++ /dev/null
@@ -1,244 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result class to be used with {@link ListenableFuture} for asynchronous calls between {@link
- * MediaSession} and {@link MediaController}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize(isCustom = true)
-public class SessionResult extends CustomVersionedParcelable implements RemoteResult {
-    /**
-     * Result code representing that the command is successfully completed.
-     * <p>
-     * Interoperability: This code is also used to tell that the command was successfully sent, but
-     * the result is unknown when connected with {@link MediaSessionCompat} or
-     * {@link MediaControllerCompat}.
-     */
-    // Redefined to override the Javadoc
-    public static final int RESULT_SUCCESS = 0;
-
-    /**
-     */
-    @IntDef(flag = false, /*prefix = "RESULT_CODE",*/ value = {
-            RESULT_SUCCESS,
-            RESULT_ERROR_UNKNOWN,
-            RESULT_ERROR_INVALID_STATE,
-            RESULT_ERROR_BAD_VALUE,
-            RESULT_ERROR_PERMISSION_DENIED,
-            RESULT_ERROR_IO,
-            RESULT_INFO_SKIPPED,
-            RESULT_ERROR_SESSION_DISCONNECTED,
-            RESULT_ERROR_NOT_SUPPORTED,
-            RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
-            RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
-            RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
-            RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
-            RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
-            RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED,
-            RESULT_ERROR_SESSION_SETUP_REQUIRED})
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface ResultCode {}
-
-    @ParcelField(1)
-    int mResultCode;
-    @ParcelField(2)
-    long mCompletionTime;
-    @ParcelField(3)
-    Bundle mCustomCommandResult;
-    // Parceled via mParcelableItem.
-    @NonParcelField
-    MediaItem mItem;
-    // For parceling mItem. Should be only used by onPreParceling() and onPostParceling().
-    @ParcelField(4)
-    MediaItem mParcelableItem;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Constructor to be used by {@link MediaSession.SessionCallback#onCustomCommand(
-     * MediaSession, MediaSession.ControllerInfo, SessionCommand, Bundle)}.
-     *
-     * @param resultCode result code
-     * @param customCommandResult custom command result.
-     */
-    public SessionResult(@ResultCode int resultCode, @Nullable Bundle customCommandResult) {
-        this(resultCode, customCommandResult, null, SystemClock.elapsedRealtime());
-    }
-
-    // For versioned-parcelable
-    SessionResult() {
-        // no-op
-    }
-
-    SessionResult(@ResultCode int resultCode) {
-        this(resultCode, null);
-    }
-
-    SessionResult(@ResultCode int resultCode, Bundle customCommandResult, MediaItem item) {
-        this(resultCode, customCommandResult, item, SystemClock.elapsedRealtime());
-    }
-
-    SessionResult(@ResultCode int resultCode, @Nullable Bundle customCommandResult,
-            @Nullable MediaItem item, long completionTime) {
-        mResultCode = resultCode;
-        mCustomCommandResult = customCommandResult;
-        mItem = item;
-        mCompletionTime = completionTime;
-    }
-
-    @Nullable
-    static SessionResult from(@Nullable SessionPlayer.PlayerResult result) {
-        if (result == null) {
-            return null;
-        }
-        return new SessionResult(result.getResultCode(), null, result.getMediaItem(),
-                result.getCompletionTime());
-    }
-
-    static ListenableFuture<SessionResult> createFutureWithResult(@ResultCode int resultCode) {
-        ResolvableFuture<SessionResult> result = ResolvableFuture.create();
-        result.set(new SessionResult(resultCode));
-        return result;
-    }
-
-    /**
-     * Gets the result code.
-     *
-     * @return result code
-     * @see #RESULT_SUCCESS
-     * @see #RESULT_ERROR_UNKNOWN
-     * @see #RESULT_ERROR_INVALID_STATE
-     * @see #RESULT_ERROR_BAD_VALUE
-     * @see #RESULT_ERROR_PERMISSION_DENIED
-     * @see #RESULT_ERROR_IO
-     * @see #RESULT_INFO_SKIPPED
-     * @see #RESULT_ERROR_SESSION_DISCONNECTED
-     * @see #RESULT_ERROR_NOT_SUPPORTED
-     * @see #RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
-     * @see #RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED
-     * @see #RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT
-     * @see #RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED
-     * @see #RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION
-     * @see #RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED
-     * @see #RESULT_ERROR_SESSION_SETUP_REQUIRED
-     */
-    @Override
-    @ResultCode
-    public int getResultCode() {
-        return mResultCode;
-    }
-
-    /**
-     * Gets the result of
-     * {@link MediaSession#sendCustomCommand(MediaSession.ControllerInfo, SessionCommand, Bundle)}
-     * and {@link MediaController#sendCustomCommand(SessionCommand, Bundle)} only when this object
-     * is returned by one of them.
-     * <p>
-     * If this object is returned by other methods, this method will be {@code null}.
-     *
-     * @see MediaSession#sendCustomCommand(MediaSession.ControllerInfo, SessionCommand, Bundle)
-     * @see MediaController#sendCustomCommand(SessionCommand, Bundle)
-     * @return result of sending custom command
-     */
-    @Nullable
-    public Bundle getCustomCommandResult() {
-        return mCustomCommandResult;
-    }
-
-    /**
-     * Gets the completion time of the command. Being more specific, it's the same as
-     * {@link SystemClock#elapsedRealtime()} when the command completed.
-     *
-     * @return completion time of the command
-     */
-    @Override
-    public long getCompletionTime() {
-        return mCompletionTime;
-    }
-
-    /**
-     * Gets the {@link MediaItem} for which the command was executed. In other words, this is
-     * the current media item when the command completed.
-     * <p>
-     * Can be {@code null} for many reasons. For examples,
-     * <ul>
-     * <li>Error happened.
-     * <li>Current media item was {@code null} at that time.
-     * <li>Command is irrelevant with the media item (e.g. custom command).
-     * </ul>
-     *
-     * @return media item when the command completed. Can be {@code null} for an error, the
-     *         current media item was {@code null}, or any other reason.
-     */
-    @Override
-    @Nullable
-    public MediaItem getMediaItem() {
-        return mItem;
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    @SuppressWarnings("SynchronizeOnNonFinalField") // mItem is effectively final.
-    public void onPreParceling(boolean isStream) {
-        if (mItem != null) {
-            synchronized (mItem) {
-                if (mParcelableItem == null) {
-                    mParcelableItem = MediaUtils.upcastForPreparceling(mItem);
-                }
-            }
-        }
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    public void onPostParceling() {
-        mItem = mParcelableItem;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java
deleted file mode 100644
index a0997be..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java
+++ /dev/null
@@ -1,416 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaSessionManager;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/**
- * Represents an ongoing {@link MediaSession} or a {@link MediaSessionService}. If it's representing
- * a session service, it may not be ongoing.
- *
- * <p>This may be passed to apps by the session owner to allow them to create a {@link
- * MediaController} to communicate with the session.
- *
- * <p>It can be also obtained by {@link MediaSessionManager}.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-// New version of MediaSession.Token for following reasons
-//   - Stop implementing Parcelable for updatable support
-//   - Represent session and library service (formerly browser service) in one class.
-//     Previously MediaSession.Token was for session and ComponentName was for service.
-//     This helps controller apps to keep target of dispatching media key events in uniform way.
-//     For details about the reason, see following. (Android O+)
-//         android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
-@Deprecated
-@VersionedParcelize
-public final class SessionToken implements VersionedParcelable {
-    private static final String TAG = "SessionToken";
-
-    private static final long WAIT_TIME_MS_FOR_SESSION_READY = 300;
-    private static final int MSG_SEND_TOKEN2_FOR_LEGACY_SESSION = 1000;
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
-    public @interface TokenType {
-    }
-
-    /**
-     * Type for {@link MediaSession}.
-     */
-    public static final int TYPE_SESSION = 0;
-
-    /**
-     * Type for {@link MediaSessionService}.
-     */
-    public static final int TYPE_SESSION_SERVICE = 1;
-
-    /**
-     * Type for {@link MediaLibraryService}.
-     */
-    public static final int TYPE_LIBRARY_SERVICE = 2;
-
-    /**
-     * Type for {@link MediaSessionCompat}.
-     */
-    static final int TYPE_SESSION_LEGACY = 100;
-
-    /**
-     * Type for {@link MediaBrowserServiceCompat}.
-     */
-    static final int TYPE_BROWSER_SERVICE_LEGACY = 101;
-
-    @ParcelField(1)
-    SessionTokenImpl mImpl;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Constructor for the token. You can create token of {@link MediaSessionService},
-     * {@link MediaLibraryService} or {@link MediaBrowserServiceCompat} for
-     * {@link MediaController} or {@link MediaBrowser}.
-     *
-     * @param context The context.
-     * @param serviceComponent The component name of the service.
-     */
-    public SessionToken(@NonNull Context context, @NonNull ComponentName serviceComponent) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        if (serviceComponent == null) {
-            throw new NullPointerException("serviceComponent shouldn't be null");
-        }
-        final PackageManager manager = context.getPackageManager();
-        final int uid = getUid(manager, serviceComponent.getPackageName());
-
-        final int type;
-        if (isInterfaceDeclared(manager, MediaLibraryService.SERVICE_INTERFACE,
-                serviceComponent)) {
-            type = TYPE_LIBRARY_SERVICE;
-        } else if (isInterfaceDeclared(manager, MediaSessionService.SERVICE_INTERFACE,
-                    serviceComponent)) {
-            type = TYPE_SESSION_SERVICE;
-        } else if (isInterfaceDeclared(manager,
-                        MediaBrowserServiceCompat.SERVICE_INTERFACE, serviceComponent)) {
-            type = TYPE_BROWSER_SERVICE_LEGACY;
-        } else {
-            throw new IllegalArgumentException(serviceComponent + " doesn't implement none of"
-                    + " MediaSessionService, MediaLibraryService, MediaBrowserService nor"
-                    + " MediaBrowserServiceCompat. Use service's full name");
-        }
-        if (type != TYPE_BROWSER_SERVICE_LEGACY) {
-            mImpl = new SessionTokenImplBase(serviceComponent, uid, type);
-        } else {
-            mImpl = new SessionTokenImplLegacy(serviceComponent, uid);
-        }
-    }
-
-    SessionToken(SessionTokenImpl impl) {
-        mImpl = impl;
-    }
-
-    /**
-     * Used for {@link VersionedParcelable}
-     */
-    SessionToken() {
-        // do nothing
-    }
-
-    @Override
-    public int hashCode() {
-        return mImpl.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SessionToken)) {
-            return false;
-        }
-        SessionToken other = (SessionToken) obj;
-        return mImpl.equals(other.mImpl);
-    }
-
-    @Override
-    public String toString() {
-        return mImpl.toString();
-    }
-
-    /**
-     * @return uid of the session
-     */
-    public int getUid() {
-        return mImpl.getUid();
-    }
-
-    /**
-     * @return package name of the session
-     */
-    @NonNull
-    public String getPackageName() {
-        return mImpl.getPackageName();
-    }
-
-    /**
-     * @return service name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
-     */
-    @Nullable
-    public String getServiceName() {
-        return mImpl.getServiceName();
-    }
-
-    /**
-     * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
-     */
-    @RestrictTo(LIBRARY)
-    public ComponentName getComponentName() {
-        return mImpl.getComponentName();
-    }
-
-    /**
-     * @return type of the token
-     * @see #TYPE_SESSION
-     * @see #TYPE_SESSION_SERVICE
-     * @see #TYPE_LIBRARY_SERVICE
-     */
-    @TokenType
-    public int getType() {
-        return mImpl.getType();
-    }
-
-    /**
-     * @return extras of the token
-     * @see MediaSession.Builder#setExtras(Bundle)
-     */
-    @NonNull
-    public Bundle getExtras() {
-        Bundle extras = mImpl.getExtras();
-        if (extras == null || MediaUtils.doesBundleHaveCustomParcelable(extras)) {
-            return Bundle.EMPTY;
-        }
-        return new Bundle(extras);
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public boolean isLegacySession() {
-        return mImpl.isLegacySession();
-    }
-
-    /**
-     */
-    @RestrictTo(LIBRARY)
-    public Object getBinder() {
-        return mImpl.getBinder();
-    }
-
-    /**
-     * Creates SessionToken object from MediaSessionCompat.Token.
-     * When the SessionToken is ready, OnSessionTokenCreateListener will be called.
-     */
-    @RestrictTo(LIBRARY)
-    public static void createSessionToken(@NonNull final Context context,
-            @NonNull final MediaSessionCompat.Token compatToken,
-            @NonNull final OnSessionTokenCreatedListener listener) {
-        if (context == null) {
-            throw new NullPointerException("context shouldn't be null");
-        }
-        if (compatToken == null) {
-            throw new NullPointerException("compatToken shouldn't be null");
-        }
-        if (listener == null) {
-            throw new NullPointerException("listener shouldn't be null");
-        }
-
-        // If the compat token already has the SessionToken in itself, just notify with that token.
-        VersionedParcelable token2 = compatToken.getSession2Token();
-        if (token2 instanceof SessionToken) {
-            listener.onSessionTokenCreated(compatToken, (SessionToken) token2);
-            return;
-        }
-
-        // Try retrieving media2 token by connecting to the session.
-        final MediaControllerCompat controller = createMediaControllerCompat(context, compatToken);
-
-        final String packageName = controller.getPackageName();
-        final int uid = getUid(context.getPackageManager(), packageName);
-        final HandlerThread thread = new HandlerThread(TAG);
-        thread.start();
-        final Handler handler = new Handler(thread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                synchronized (listener) {
-                    if (msg.what != MSG_SEND_TOKEN2_FOR_LEGACY_SESSION) {
-                        return;
-                    }
-                    controller.unregisterCallback((MediaControllerCompat.Callback) msg.obj);
-
-                    // MediaControllerCompat.Callback#onSessionReady() is not called, which means
-                    // that the connected session is a framework MediaSession instance.
-                    SessionToken resultToken = new SessionToken(new SessionTokenImplLegacy(
-                            compatToken, packageName, uid, controller.getSessionInfo()));
-
-                    // To prevent repeating this process with the same compat token, put the result
-                    // media2 token inside of the compat token.
-                    compatToken.setSession2Token(resultToken);
-
-                    listener.onSessionTokenCreated(compatToken, resultToken);
-                    quitHandlerThread(thread);
-                }
-            }
-        };
-
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onSessionReady() {
-                // The connected session is a MediaSessionCompat instance.
-                synchronized (listener) {
-                    handler.removeMessages(MSG_SEND_TOKEN2_FOR_LEGACY_SESSION);
-                    controller.unregisterCallback(this);
-
-                    // TODO: Add logic for getting media2 token in API 21- by using binder.
-
-                    SessionToken resultToken;
-                    if (compatToken.getSession2Token() instanceof SessionToken) {
-                        // TODO(b/132928776): Add tests for this code path.
-                        // The connected MediaSessionCompat is created by media2.MediaSession
-                        resultToken = (SessionToken) compatToken.getSession2Token();
-                    } else {
-                        // The connected MediaSessionCompat is standalone.
-                        resultToken = new SessionToken(new SessionTokenImplLegacy(
-                                compatToken, packageName, uid, controller.getSessionInfo()));
-                        // To prevent repeating this process with the same compat token,
-                        // put the result media2 token inside of the compat token.
-                        compatToken.setSession2Token(resultToken);
-                    }
-
-                    listener.onSessionTokenCreated(compatToken, resultToken);
-                    quitHandlerThread(thread);
-                }
-            }
-        };
-        synchronized (listener) {
-            controller.registerCallback(callback, handler);
-            Message msg = handler.obtainMessage(MSG_SEND_TOKEN2_FOR_LEGACY_SESSION, callback);
-            handler.sendMessageDelayed(msg, WAIT_TIME_MS_FOR_SESSION_READY);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void quitHandlerThread(HandlerThread thread) {
-        thread.quitSafely();
-    }
-
-    @SuppressWarnings("deprecation")
-    private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
-            ComponentName serviceComponent) {
-        Intent serviceIntent = new Intent(serviceInterface);
-        // Use queryIntentServices to find services with MediaLibraryService.SERVICE_INTERFACE.
-        // We cannot use resolveService with intent specified class name, because resolveService
-        // ignores actions if Intent.setClassName() is specified.
-        serviceIntent.setPackage(serviceComponent.getPackageName());
-
-        List<ResolveInfo> list = manager.queryIntentServices(
-                serviceIntent, PackageManager.GET_META_DATA);
-        if (list != null) {
-            for (int i = 0; i < list.size(); i++) {
-                ResolveInfo resolveInfo = list.get(i);
-                if (resolveInfo == null || resolveInfo.serviceInfo == null) {
-                    continue;
-                }
-                if (TextUtils.equals(
-                        resolveInfo.serviceInfo.name, serviceComponent.getClassName())) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    @SuppressWarnings("deprecation")
-    private static int getUid(PackageManager manager, String packageName) {
-        try {
-            return manager.getApplicationInfo(packageName, 0).uid;
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new IllegalArgumentException("Cannot find package " + packageName);
-        }
-    }
-
-    private static MediaControllerCompat createMediaControllerCompat(Context context,
-            MediaSessionCompat.Token sessionToken) {
-        return new MediaControllerCompat(context, sessionToken);
-    }
-
-    /**
-     * Interface definition of a listener to be invoked when a {@link SessionToken token2} object
-     * is created from a {@link MediaSessionCompat.Token compat token}.
-     *
-     * @see #createSessionToken
-     */
-    @RestrictTo(LIBRARY)
-    public interface OnSessionTokenCreatedListener {
-        /**
-         * Called when SessionToken object is created.
-         *
-         * @param compatToken the compat token used for creating {@code token2}
-         * @param sessionToken the created SessionToken object
-         */
-        void onSessionTokenCreated(MediaSessionCompat.Token compatToken, SessionToken sessionToken);
-    }
-
-    interface SessionTokenImpl extends VersionedParcelable {
-        boolean isLegacySession();
-        int getUid();
-        @NonNull String getPackageName();
-        @Nullable String getServiceName();
-        @Nullable ComponentName getComponentName();
-        @TokenType int getType();
-        @Nullable Bundle getExtras();
-        Object getBinder();
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
deleted file mode 100644
index a848071..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
+++ /dev/null
@@ -1,160 +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.media2.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.core.util.ObjectsCompat;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-@VersionedParcelize
-final class SessionTokenImplBase implements SessionToken.SessionTokenImpl {
-    @ParcelField(1)
-    int mUid;
-    @ParcelField(2)
-    @SessionToken.TokenType
-    int mType;
-    @ParcelField(3)
-    String mPackageName;
-    @ParcelField(4)
-    String mServiceName;
-    @ParcelField(5)
-    IBinder mISession;
-    @ParcelField(6)
-    ComponentName mComponentName;
-    @ParcelField(7)
-    Bundle mExtras;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Constructor for the token. You can only create token for session service or library service
-     * to use by {@link MediaController} or {@link MediaBrowser}.
-     */
-    SessionTokenImplBase(@NonNull ComponentName serviceComponent, int uid, int type) {
-        if (serviceComponent == null) {
-            throw new NullPointerException("serviceComponent shouldn't be null");
-        }
-        mComponentName = serviceComponent;
-        mPackageName = serviceComponent.getPackageName();
-        mServiceName = serviceComponent.getClassName();
-        mUid = uid;
-        mType = type;
-        mISession = null;
-        mExtras = null;
-    }
-
-    SessionTokenImplBase(int uid, int type, String packageName, IMediaSession iSession,
-            Bundle tokenExtras) {
-        mUid = uid;
-        mType = type;
-        mPackageName = packageName;
-        mServiceName = null;
-        mComponentName = null;
-        mISession = iSession.asBinder();
-        mExtras = tokenExtras;
-    }
-
-    /**
-     * Used for {@link VersionedParcelize}.
-     */
-    SessionTokenImplBase() {
-        // Do nothing.
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mType, mUid, mPackageName, mServiceName);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SessionTokenImplBase)) {
-            return false;
-        }
-        SessionTokenImplBase other = (SessionTokenImplBase) obj;
-        return mUid == other.mUid
-                && TextUtils.equals(mPackageName, other.mPackageName)
-                && TextUtils.equals(mServiceName, other.mServiceName)
-                && mType == other.mType
-                && ObjectsCompat.equals(mISession, other.mISession);
-    }
-
-    @Override
-    public String toString() {
-        return "SessionToken {pkg=" + mPackageName + " type=" + mType
-                + " service=" + mServiceName + " IMediaSession=" + mISession
-                + " extras=" + mExtras + "}";
-    }
-
-    @Override
-    public boolean isLegacySession() {
-        return false;
-    }
-
-    @Override
-    public int getUid() {
-        return mUid;
-    }
-
-    @Override
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    @Override
-    @Nullable
-    public String getServiceName() {
-        return mServiceName;
-    }
-
-    /**
-     * @return component name of this session token. Can be null for TYPE_SESSION.
-     */
-    @RestrictTo(LIBRARY)
-    @Override
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    @Override
-    @SessionToken.TokenType
-    public int getType() {
-        return mType;
-    }
-
-    @Override
-    @Nullable
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    @Override
-    public Object getBinder() {
-        return mISession;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java
deleted file mode 100644
index c43a969..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java
+++ /dev/null
@@ -1,214 +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.media2.session;
-
-import static androidx.media2.session.SessionToken.TYPE_BROWSER_SERVICE_LEGACY;
-import static androidx.media2.session.SessionToken.TYPE_LIBRARY_SERVICE;
-import static androidx.media2.session.SessionToken.TYPE_SESSION;
-import static androidx.media2.session.SessionToken.TYPE_SESSION_LEGACY;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.session.SessionToken.SessionTokenImpl;
-import androidx.versionedparcelable.CustomVersionedParcelable;
-import androidx.versionedparcelable.NonParcelField;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-@VersionedParcelize(isCustom = true)
-final class SessionTokenImplLegacy extends CustomVersionedParcelable implements SessionTokenImpl {
-    // Don't mark mLegacyToken @ParcelField, because we need to use toBundle()/fromBundle() instead
-    // of the writeToParcel()/Parcelable.Creator for sending extra binder.
-    @NonParcelField
-    private MediaSessionCompat.Token mLegacyToken;
-    // For parceling mLegacyToken. Should be only used by onPreParceling() and onPostParceling().
-    @ParcelField(1)
-    Bundle mLegacyTokenBundle;
-    @ParcelField(2)
-    int mUid;
-    @ParcelField(3)
-    int mType;
-    @ParcelField(4)
-    ComponentName mComponentName;
-    @ParcelField(5)
-    String mPackageName;
-    @ParcelField(6)
-    Bundle mExtras;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    SessionTokenImplLegacy(MediaSessionCompat.Token token, String packageName, int uid,
-            Bundle sessionInfo) {
-        if (token == null) {
-            throw new NullPointerException("token shouldn't be null");
-        }
-        if (packageName == null) {
-            throw new NullPointerException("packageName shouldn't be null");
-        } else if (TextUtils.isEmpty(packageName)) {
-            throw new IllegalArgumentException("packageName shouldn't be empty");
-        }
-
-        mLegacyToken = token;
-        mUid = uid;
-        mPackageName = packageName;
-        mComponentName = null;
-        mType = TYPE_SESSION_LEGACY;
-        mExtras = sessionInfo;
-    }
-
-    SessionTokenImplLegacy(ComponentName serviceComponent, int uid) {
-        if (serviceComponent == null) {
-            throw new NullPointerException("serviceComponent shouldn't be null");
-        }
-
-        mLegacyToken = null;
-        mUid = uid;
-        mType = TYPE_BROWSER_SERVICE_LEGACY;
-        mPackageName = serviceComponent.getPackageName();
-        mComponentName = serviceComponent;
-        mExtras = null;
-    }
-
-    /**
-     * Used for {@link VersionedParcelable}
-     */
-    SessionTokenImplLegacy() {
-        // Do nothing.
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mType, mComponentName, mLegacyToken);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SessionTokenImplLegacy)) {
-            return false;
-        }
-        SessionTokenImplLegacy other = (SessionTokenImplLegacy) obj;
-        if (mType != other.mType) {
-            return false;
-        }
-        switch (mType) {
-            case TYPE_SESSION_LEGACY:
-                return ObjectsCompat.equals(mLegacyToken, other.mLegacyToken);
-            case TYPE_BROWSER_SERVICE_LEGACY:
-                return ObjectsCompat.equals(mComponentName, other.mComponentName);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isLegacySession() {
-        return true;
-    }
-
-    @Override
-    @SuppressWarnings("ObjectToString")
-    public String toString() {
-        return "SessionToken {legacyToken=" + mLegacyToken + "}";
-    }
-
-    @Override
-    public int getUid() {
-        return mUid;
-    }
-
-    @Override
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    @Override
-    @Nullable
-    public String getServiceName() {
-        return mComponentName == null ? null : mComponentName.getClassName();
-    }
-
-    @Override
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    @Override
-    @SessionToken.TokenType
-    public int getType() {
-        switch (mType) {
-            case TYPE_SESSION_LEGACY:
-                return TYPE_SESSION;
-            case TYPE_BROWSER_SERVICE_LEGACY:
-                return TYPE_LIBRARY_SERVICE;
-        }
-        return TYPE_SESSION;
-    }
-
-    @Override
-    @Nullable
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    @Override
-    public Object getBinder() {
-        return mLegacyToken;
-    }
-
-    @Override
-    @SuppressWarnings("SynchronizeOnNonFinalField") // mLegacyToken is effectively final.
-    public void onPreParceling(boolean isStream) {
-        if (mLegacyToken != null) {
-            synchronized (mLegacyToken) {
-                // Note: mLegacyTokenBundle should always be recreated, because mLegacyToken is
-                // mutable.
-
-                // Note: token should be null or SessionToken whose impl equals to this object.
-                VersionedParcelable token = mLegacyToken.getSession2Token();
-
-                // Temporarily sets the SessionToken to null to prevent infinite loop when
-                // parceling. Otherwise, this will be called again when mLegacyToken parcelize
-                // SessionToken in it and it never ends.
-                mLegacyToken.setSession2Token(null);
-
-                // Although mLegacyToken is Parcelable, we should use toBundle() instead here
-                // because extra binder inside of the mLegacyToken are shared only through the
-                // toBundle().
-                mLegacyTokenBundle = mLegacyToken.toBundle();
-
-                // Resets the SessionToken.
-                mLegacyToken.setSession2Token(token);
-            }
-        } else {
-            mLegacyTokenBundle = null;
-        }
-    }
-
-    @Override
-    public void onPostParceling() {
-        // Although mLegacyToken is Parcelable, we should use fromBundle() instead here because
-        // extra binder inside of the mLegacyToken are shared only through the fromBundle().
-        mLegacyToken = MediaSessionCompat.Token.fromBundle(mLegacyTokenBundle);
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/StarRating.java b/media2/media2-session/src/main/java/androidx/media2/session/StarRating.java
deleted file mode 100644
index f2dcef8..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/StarRating.java
+++ /dev/null
@@ -1,126 +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.media2.session;
-
-import androidx.annotation.IntRange;
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.Rating;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * A class for rating expressed as the number of stars.
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class StarRating implements Rating {
-    private static final float RATING_NOT_RATED = -1.0f;
-
-    @ParcelField(1)
-    int mMaxStars;
-
-    @ParcelField(2)
-    float mStarRating;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /*
-     * Used for VersionedParcelable
-     */
-    StarRating() {
-    }
-
-    /**
-     * Creates a unrated StarRating instance with {@code maxStars}.
-     * If {@code maxStars} is not a positive integer, it will throw IllegalArgumentException.
-     *
-     * @param maxStars a range of this star rating from 0.0f to {@code maxStars}
-     */
-    public StarRating(@IntRange(from = 1) int maxStars) {
-        if (maxStars <= 0) {
-            throw new IllegalArgumentException("maxStars should be a positive integer");
-        }
-        mMaxStars = maxStars;
-        mStarRating = RATING_NOT_RATED;
-    }
-
-    /**
-     * Creates a StarRating instance with {@code maxStars} and the given integer or fractional
-     * number of stars. Non integer values can for instance be used to represent an average rating
-     * value, which might not be an integer number of stars.
-     * If {@code maxStars} is not a positive integer or {@code starRating} has invalid value,
-     * it will throw IllegalArgumentException.
-     *
-     * @param maxStars the maximum number of stars which this rating can have.
-     * @param starRating a number ranging from 0.0f to {@code maxStars}
-     */
-    public StarRating(@IntRange(from = 1) int maxStars, float starRating) {
-        if (maxStars <= 0) {
-            throw new IllegalArgumentException("maxStars should be a positive integer");
-        } else if (starRating < 0.0f || starRating > maxStars) {
-            throw new IllegalArgumentException("starRating is out of range [0, maxStars]");
-        }
-        mMaxStars = maxStars;
-        mStarRating = starRating;
-    }
-
-    @Override
-    public boolean isRated() {
-        return mStarRating >= 0.0f;
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mMaxStars, mStarRating);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof StarRating)) {
-            return false;
-        }
-        StarRating other = (StarRating) obj;
-        return mMaxStars == other.mMaxStars && mStarRating == other.mStarRating;
-    }
-
-    @Override
-    public String toString() {
-        return "StarRating: maxStars=" + mMaxStars
-                + (isRated() ? ", starRating=" + mStarRating : ", unrated");
-    }
-
-    /**
-     * Returns the max stars.
-     *
-     * @return a max number of stars for this star rating.
-     */
-    public int getMaxStars() {
-        return mMaxStars;
-    }
-
-    /**
-     * Returns the star-based rating value.
-     *
-     * @return a rating value greater or equal to 0.0f, or a negative value if it is unrated.
-     */
-    public float getStarRating() {
-        return mStarRating;
-    }
-}
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/ThumbRating.java b/media2/media2-session/src/main/java/androidx/media2/session/ThumbRating.java
deleted file mode 100644
index 2d5989d..0000000
--- a/media2/media2-session/src/main/java/androidx/media2/session/ThumbRating.java
+++ /dev/null
@@ -1,91 +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.media2.session;
-
-import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.Rating;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * A class for rating with a single degree of rating, "thumb up" vs "thumb down".
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-@VersionedParcelize
-public final class ThumbRating implements Rating {
-    @ParcelField(1)
-    boolean mIsRated;
-
-    @ParcelField(2)
-    boolean mThumbUp;
-
-    // WARNING: Adding a new ParcelField may break old library users (b/152830728)
-
-    /**
-     * Creates a unrated ThumbRating instance.
-     */
-    public ThumbRating() {
-        mIsRated = false;
-    }
-
-    /**
-     * Creates a ThumbRating instance.
-     *
-     * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
-     */
-    public ThumbRating(boolean thumbIsUp) {
-        mThumbUp = thumbIsUp;
-        mIsRated = true;
-    }
-
-    @Override
-    public boolean isRated() {
-        return mIsRated;
-    }
-
-    @Override
-    public int hashCode() {
-        return ObjectsCompat.hash(mIsRated, mThumbUp);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof ThumbRating)) {
-            return false;
-        }
-        ThumbRating other = (ThumbRating) obj;
-        return mThumbUp == other.mThumbUp && mIsRated == other.mIsRated;
-    }
-
-    @Override
-    public String toString() {
-        return "ThumbRating: " + (mIsRated ? "isThumbUp=" + mThumbUp : "unrated");
-    }
-
-    /**
-     * Returns whether the rating is "thumb up".
-     *
-     * @return true if the rating is "thumb up", false if the rating is "thumb down",
-     *         or if it is unrated.
-     */
-    public boolean isThumbUp() {
-        return mThumbUp;
-    }
-}
diff --git a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_music_note.xml b/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_music_note.xml
deleted file mode 100644
index f7744af..0000000
--- a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_music_note.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
-</vector>
diff --git a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_pause.xml b/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_pause.xml
deleted file mode 100644
index f6aa6b9..0000000
--- a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_pause.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="36dp"
-        android:height="36dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
-</vector>
diff --git a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_play.xml b/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_play.xml
deleted file mode 100644
index 2f3e025..0000000
--- a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_play.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="36dp"
-        android:height="36dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M8,5v14l11,-7z"/>
-</vector>
diff --git a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_next.xml b/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_next.xml
deleted file mode 100644
index 272d4ba..0000000
--- a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_next.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="36dp"
-        android:height="36dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
-</vector>
diff --git a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_previous.xml b/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_previous.xml
deleted file mode 100644
index dec2fdc..0000000
--- a/media2/media2-session/src/main/res/drawable/media_session_service_notification_ic_skip_to_previous.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="36dp"
-        android:height="36dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
-</vector>
diff --git a/media2/media2-session/src/main/res/values-af/strings.xml b/media2/media2-session/src/main/res/values-af/strings.xml
deleted file mode 100644
index 2b218f5..0000000
--- a/media2/media2-session/src/main/res/values-af/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Speel tans"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Speel"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Onderbreek"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Slaan oor na vorige item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Slaan oor na volgende item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-am/strings.xml b/media2/media2-session/src/main/res/values-am/strings.xml
deleted file mode 100644
index 2334a9be..0000000
--- a/media2/media2-session/src/main/res/values-am/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"አሁን እየተጫወተ ያለ"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"አጫውት"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ላፍታ አቁም"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ወደ ቀዳሚው ንጥል ዝለል"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ወደ ቀጣዩ ንጥል ዝለል"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ar/strings.xml b/media2/media2-session/src/main/res/values-ar/strings.xml
deleted file mode 100644
index 87acf94..0000000
--- a/media2/media2-session/src/main/res/values-ar/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"التعرف التلقائي على الوسائط"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"تشغيل"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"إيقاف مؤقت"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"التخطي إلى العنصر السابق"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"التخطي إلى العنصر التالي"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-as/strings.xml b/media2/media2-session/src/main/res/values-as/strings.xml
deleted file mode 100644
index 0f1be72..0000000
--- a/media2/media2-session/src/main/res/values-as/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"এতিয়া প্লে’ হৈ আছে"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"প্লে’ কৰক"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"পজ কৰক"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"এটা এৰি তাৰ পূৰ্বৱৰ্তী বস্তুটোলৈ যাওক"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"এটা এৰি তাৰ পৰৱৰ্তী বস্তুটোলৈ যাওক"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-az/strings.xml b/media2/media2-session/src/main/res/values-az/strings.xml
deleted file mode 100644
index fd8b4f2..0000000
--- a/media2/media2-session/src/main/res/values-az/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"İndi oxudulur"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Oxudun"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Durdurun"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Əvvəlki elementə keçin"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Növbəti elementə keçin"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml b/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index 642e80f..0000000
--- a/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Trenutno svira"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Pusti"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauziraj"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pređi na prethodnu stavku"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pređi na sledeću stavku"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-be/strings.xml b/media2/media2-session/src/main/res/values-be/strings.xml
deleted file mode 100644
index 0f1a9ca..0000000
--- a/media2/media2-session/src/main/res/values-be/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Зараз іграе"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Прайграць"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Прыпыніць"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перайсці да папярэдняга элемента"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перайсці да наступнага элемента"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-bg/strings.xml b/media2/media2-session/src/main/res/values-bg/strings.xml
deleted file mode 100644
index 8727a09..0000000
--- a/media2/media2-session/src/main/res/values-bg/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Възпроизвежда се в момента"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Пускане"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Поставяне на пауза"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Преминаване към предишния елемент"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Преминаване към следващия елемент"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-bn/strings.xml b/media2/media2-session/src/main/res/values-bn/strings.xml
deleted file mode 100644
index 4a816d0..0000000
--- a/media2/media2-session/src/main/res/values-bn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"এখন চলছে"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"চালান"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"পজ করুন"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"পূর্ববর্তী আইটেমে যান"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"পরবর্তী আইটেমে যান"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-bs/strings.xml b/media2/media2-session/src/main/res/values-bs/strings.xml
deleted file mode 100644
index 2354f80..0000000
--- a/media2/media2-session/src/main/res/values-bs/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Trenutno se reproducira"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproduciranje"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauza"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskok na prethodnu stavku"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskok na sljedeću stavku"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ca/strings.xml b/media2/media2-session/src/main/res/values-ca/strings.xml
deleted file mode 100644
index a216a5a..0000000
--- a/media2/media2-session/src/main/res/values-ca/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Està sonant"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reprodueix"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Posa en pausa"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ves a l\'element anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ves a l\'element següent"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-cs/strings.xml b/media2/media2-session/src/main/res/values-cs/strings.xml
deleted file mode 100644
index c95de94..0000000
--- a/media2/media2-session/src/main/res/values-cs/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Přehrává se"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Přehrát"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pozastavit"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Přejít na předchozí položku"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Přejít na další položku"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-da/strings.xml b/media2/media2-session/src/main/res/values-da/strings.xml
deleted file mode 100644
index b7583eb24..0000000
--- a/media2/media2-session/src/main/res/values-da/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Afspilles nu"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Afspil"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Sæt på pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Gå til forrige element"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Gå til næste element"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-de/strings.xml b/media2/media2-session/src/main/res/values-de/strings.xml
deleted file mode 100644
index 3e9b97d..0000000
--- a/media2/media2-session/src/main/res/values-de/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Wiedergeben"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausieren"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Zum vorherigen Titel springen"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Zum nächsten Titel springen"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-el/strings.xml b/media2/media2-session/src/main/res/values-el/strings.xml
deleted file mode 100644
index a7a786f..0000000
--- a/media2/media2-session/src/main/res/values-el/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ακούγεται τώρα"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Αναπαραγωγή"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Παύση"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Μετάβαση στο προηγούμενο στοιχείο"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Μετάβαση στο επόμενο στοιχείο"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-en-rAU/strings.xml b/media2/media2-session/src/main/res/values-en-rAU/strings.xml
deleted file mode 100644
index 7b0df40..0000000
--- a/media2/media2-session/src/main/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Play"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Skip to previous item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Skip to next item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-en-rCA/strings.xml b/media2/media2-session/src/main/res/values-en-rCA/strings.xml
deleted file mode 100644
index 7b0df40..0000000
--- a/media2/media2-session/src/main/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Play"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Skip to previous item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Skip to next item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-en-rGB/strings.xml b/media2/media2-session/src/main/res/values-en-rGB/strings.xml
deleted file mode 100644
index 7b0df40..0000000
--- a/media2/media2-session/src/main/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Play"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Skip to previous item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Skip to next item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-en-rIN/strings.xml b/media2/media2-session/src/main/res/values-en-rIN/strings.xml
deleted file mode 100644
index 7b0df40..0000000
--- a/media2/media2-session/src/main/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Play"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Skip to previous item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Skip to next item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-en-rXC/strings.xml b/media2/media2-session/src/main/res/values-en-rXC/strings.xml
deleted file mode 100644
index 1313932..0000000
--- a/media2/media2-session/src/main/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎Now playing‎‏‎‎‏‎"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎Play‎‏‎‎‏‎"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎Pause‎‏‎‎‏‎"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎Skip to previous item‎‏‎‎‏‎"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎Skip to next item‎‏‎‎‏‎"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-es-rUS/strings.xml b/media2/media2-session/src/main/res/values-es-rUS/strings.xml
deleted file mode 100644
index 581d257..0000000
--- a/media2/media2-session/src/main/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"En reproducción"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ir al elemento anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ir al siguiente elemento"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-es/strings.xml b/media2/media2-session/src/main/res/values-es/strings.xml
deleted file mode 100644
index f679d62b..0000000
--- a/media2/media2-session/src/main/res/values-es/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Está sonando"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Saltar al elemento anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Saltar al siguiente elemento"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-et/strings.xml b/media2/media2-session/src/main/res/values-et/strings.xml
deleted file mode 100644
index 8933aec..0000000
--- a/media2/media2-session/src/main/res/values-et/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Hetkel mängimas"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Esitamine"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Peatamine"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Eelmise üksuse juurde liikumine"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Järgmise üksuse juurde liikumine"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-eu/strings.xml b/media2/media2-session/src/main/res/values-eu/strings.xml
deleted file mode 100644
index 0d53ac5..0000000
--- a/media2/media2-session/src/main/res/values-eu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Orain erreproduzitzen"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Erreproduzitu"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausatu"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Joan aurreko elementura"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Joan hurrengo elementura"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-fa/strings.xml b/media2/media2-session/src/main/res/values-fa/strings.xml
deleted file mode 100644
index a1ef671..0000000
--- a/media2/media2-session/src/main/res/values-fa/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"درحال پخش"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"پخش"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"مکث"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"پرش به مورد قبلی"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"پرش به مورد بعدی"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-fi/strings.xml b/media2/media2-session/src/main/res/values-fi/strings.xml
deleted file mode 100644
index bd76c9b..0000000
--- a/media2/media2-session/src/main/res/values-fi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Nyt toistetaan"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Toista"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Keskeytä"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Siirry edelliseen"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Siirry seuraavaan"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-fr-rCA/strings.xml b/media2/media2-session/src/main/res/values-fr-rCA/strings.xml
deleted file mode 100644
index b039cf3..0000000
--- a/media2/media2-session/src/main/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"En cours de lecture"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Lire"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Passer à l\'élément précédent"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Passer à l\'élément suivant"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-fr/strings.xml b/media2/media2-session/src/main/res/values-fr/strings.xml
deleted file mode 100644
index 219bd45..0000000
--- a/media2/media2-session/src/main/res/values-fr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"En écoute"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Lire"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Passer à l\'élément précédent"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Passer à l\'élément suivant"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-gl/strings.xml b/media2/media2-session/src/main/res/values-gl/strings.xml
deleted file mode 100644
index bb39015..0000000
--- a/media2/media2-session/src/main/res/values-gl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Reproducindo"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pór en pausa"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Omitir o elemento e ir ao anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Omitir o elemento e ir ao seguinte"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-gu/strings.xml b/media2/media2-session/src/main/res/values-gu/strings.xml
deleted file mode 100644
index 1e64b22..0000000
--- a/media2/media2-session/src/main/res/values-gu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"હાલમાં ચાલી રહ્યું છે"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ચલાવો"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"થોભાવો"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"પહેલાંની આઇટમ પર જાઓ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"આગલી આઇટમ પર જાઓ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-hi/strings.xml b/media2/media2-session/src/main/res/values-hi/strings.xml
deleted file mode 100644
index 774d02c..0000000
--- a/media2/media2-session/src/main/res/values-hi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"अभी चल रहा है"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"चलाएं"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"रोकें"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"पिछले आइटम पर जाएं"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"अगले आइटम पर जाएं"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-hr/strings.xml b/media2/media2-session/src/main/res/values-hr/strings.xml
deleted file mode 100644
index 284694f..0000000
--- a/media2/media2-session/src/main/res/values-hr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sada se reproducira"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproduciraj"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauziraj"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskoči na prethodnu stavku"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskoči na sljedeću stavku"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-hu/strings.xml b/media2/media2-session/src/main/res/values-hu/strings.xml
deleted file mode 100644
index bf54bd9..0000000
--- a/media2/media2-session/src/main/res/values-hu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Most játszott tartalom"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Lejátszás"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Szünet"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ugrás az előző elemre"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ugrás a következő elemre"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-hy/strings.xml b/media2/media2-session/src/main/res/values-hy/strings.xml
deleted file mode 100644
index 7b9443e..0000000
--- a/media2/media2-session/src/main/res/values-hy/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Այժմ հնչում է"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Նվագարկել"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Դադարեցնել"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Անցնել նախորդ տարրին"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Անցնել հաջորդ տարրին"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-in/strings.xml b/media2/media2-session/src/main/res/values-in/strings.xml
deleted file mode 100644
index 4afd084..0000000
--- a/media2/media2-session/src/main/res/values-in/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sedang diputar"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Putar"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Jeda"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Lewati ke item sebelumnya"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Lewati ke item berikutnya"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-is/strings.xml b/media2/media2-session/src/main/res/values-is/strings.xml
deleted file mode 100644
index b734aff..0000000
--- a/media2/media2-session/src/main/res/values-is/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Í spilun"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Spila"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Gera hlé"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Fara í fyrra atriði"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Fara í næsta atriði"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-it/strings.xml b/media2/media2-session/src/main/res/values-it/strings.xml
deleted file mode 100644
index 7f52bd2..0000000
--- a/media2/media2-session/src/main/res/values-it/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ora in riproduzione"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Riproduci"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Metti in pausa"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Vai all\'elemento precedente"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Vai all\'elemento successivo"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-iw/strings.xml b/media2/media2-session/src/main/res/values-iw/strings.xml
deleted file mode 100644
index e10750a..0000000
--- a/media2/media2-session/src/main/res/values-iw/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"פועל עכשיו"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"הפעלה"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"השהיה"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"דילוג לפריט הקודם"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"דילוג לפריט הבא"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ja/strings.xml b/media2/media2-session/src/main/res/values-ja/strings.xml
deleted file mode 100644
index 4714e31..0000000
--- a/media2/media2-session/src/main/res/values-ja/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"再生中"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"再生"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"一時停止"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"前の項目にスキップ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"次の項目にスキップ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ka/strings.xml b/media2/media2-session/src/main/res/values-ka/strings.xml
deleted file mode 100644
index 6a03150..0000000
--- a/media2/media2-session/src/main/res/values-ka/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ამჟამად უკრავს"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"დაკვრა"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"პაუზა"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"წინა ერთეულზე გადასვლა"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"შემდეგ ერთეულზე გადასვლა"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-kk/strings.xml b/media2/media2-session/src/main/res/values-kk/strings.xml
deleted file mode 100644
index 44c6089..0000000
--- a/media2/media2-session/src/main/res/values-kk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ойнатылуда"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Ойнату"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Кідірту"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Алдыңғы элементке өткізіп жіберу"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Келесі элементке өткізіп жіберу"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-km/strings.xml b/media2/media2-session/src/main/res/values-km/strings.xml
deleted file mode 100644
index ee9817f..0000000
--- a/media2/media2-session/src/main/res/values-km/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"កំពុងចាក់"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ចាក់"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ផ្អាក"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"រំលងទៅមេឌៀមុន"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"រំលងទៅមេឌៀបន្ទាប់"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-kn/strings.xml b/media2/media2-session/src/main/res/values-kn/strings.xml
deleted file mode 100644
index d8206dc..0000000
--- a/media2/media2-session/src/main/res/values-kn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ಇದೀಗ ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ಪ್ಲೇ ಮಾಡಿ"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ವಿರಾಮಗೊಳಿಸಿ"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ಹಿಂದಿನ ಐಟಂಗೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ಮುಂದಿನ ಐಟಂಗೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ko/strings.xml b/media2/media2-session/src/main/res/values-ko/strings.xml
deleted file mode 100644
index 1b0e13a..0000000
--- a/media2/media2-session/src/main/res/values-ko/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"지금 재생 중인 미디어 이름"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"재생"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"일시중지"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"이전 항목으로 건너뛰기"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"다음 항목으로 건너뛰기"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ky/strings.xml b/media2/media2-session/src/main/res/values-ky/strings.xml
deleted file mode 100644
index 6d6af6d..0000000
--- a/media2/media2-session/src/main/res/values-ky/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Эмне ойноп жатат?"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Ойнотуу"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Тындыруу"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Мурунку нерсеге өтүү"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Кийинки нерсеге өтүү"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-lo/strings.xml b/media2/media2-session/src/main/res/values-lo/strings.xml
deleted file mode 100644
index cfb99a2..0000000
--- a/media2/media2-session/src/main/res/values-lo/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ກຳລັງຫຼິ້ນ"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ຫຼິ້ນ"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ຢຸດຊົ່ວຄາວ"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ຂ້າມໄປລາຍການກ່ອນໜ້າ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ຂ້າມໄປລາຍການຕໍ່ໄປ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-lt/strings.xml b/media2/media2-session/src/main/res/values-lt/strings.xml
deleted file mode 100644
index c15654c..0000000
--- a/media2/media2-session/src/main/res/values-lt/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Dabar leidžiama"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Leisti"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pristabdyti"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pereiti prie ankstesnio elemento"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pereiti prie kito elemento"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-lv/strings.xml b/media2/media2-session/src/main/res/values-lv/strings.xml
deleted file mode 100644
index 9d7edff..0000000
--- a/media2/media2-session/src/main/res/values-lv/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tagad atskaņo"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Atskaņot"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Apturēt"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pāriet uz iepriekšējo vienumu"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pāriet uz nākamo vienumu"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-mk/strings.xml b/media2/media2-session/src/main/res/values-mk/strings.xml
deleted file mode 100644
index 1f3ee24..0000000
--- a/media2/media2-session/src/main/res/values-mk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Пушти"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Паузирај"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Скокни на претходната ставка"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Скокни на следната ставка"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ml/strings.xml b/media2/media2-session/src/main/res/values-ml/strings.xml
deleted file mode 100644
index bab4b55..0000000
--- a/media2/media2-session/src/main/res/values-ml/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ഇപ്പോൾ പ്ലേ ചെയ്യുന്നത്"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"പ്ലേ ചെയ്യുക"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"താൽക്കാലികമായി നിർത്തുക"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"മുമ്പത്തെ ഇനത്തിലേക്ക് പോകുക"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"അടുത്ത ഇനത്തിലേക്ക് പോകുക"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-mn/strings.xml b/media2/media2-session/src/main/res/values-mn/strings.xml
deleted file mode 100644
index c963b5e..0000000
--- a/media2/media2-session/src/main/res/values-mn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Одоо тоглуулж байна"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Тоглуулах"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Түр зогсоох"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Өмнөх зүйл рүү алгасах"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Дараагийн зүйл рүү алгасах"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-mr/strings.xml b/media2/media2-session/src/main/res/values-mr/strings.xml
deleted file mode 100644
index cc9584a..0000000
--- a/media2/media2-session/src/main/res/values-mr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"आता प्ले करत आहे"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"प्ले करा"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"थांबवा"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"मागील आयटमवर जा"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"पुढील आयटमवर जा"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ms/strings.xml b/media2/media2-session/src/main/res/values-ms/strings.xml
deleted file mode 100644
index 9e2daa2..0000000
--- a/media2/media2-session/src/main/res/values-ms/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sedang dimainkan"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Main"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Jeda"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Langkau ke item sebelumnya"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Langkau ke item seterusnya"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-my/strings.xml b/media2/media2-session/src/main/res/values-my/strings.xml
deleted file mode 100644
index 13710d2..0000000
--- a/media2/media2-session/src/main/res/values-my/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ဖွင့်ရန်"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ခဏရပ်ရန်"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ယခင်တစ်ခုသို့ ပြန်သွားရန်"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"နောက်တစ်ခုသို့ ကျော်ရန်"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-nb/strings.xml b/media2/media2-session/src/main/res/values-nb/strings.xml
deleted file mode 100644
index 32bf8aa..0000000
--- a/media2/media2-session/src/main/res/values-nb/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Spilles nå"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Spill av"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Sett på pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Gå tilbake til det forrige elementet"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Hopp til det neste elementet"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ne/strings.xml b/media2/media2-session/src/main/res/values-ne/strings.xml
deleted file mode 100644
index 111ef1d..0000000
--- a/media2/media2-session/src/main/res/values-ne/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"प्ले गर्नुहोस्"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"पज गर्नुहोस्"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"अघिल्लो वस्तुमा जानुहोस्"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"अर्को वस्तुमा जानुहोस्"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-nl/strings.xml b/media2/media2-session/src/main/res/values-nl/strings.xml
deleted file mode 100644
index 606ce7a..0000000
--- a/media2/media2-session/src/main/res/values-nl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Wordt nu afgespeeld"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Afspelen"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauzeren"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Overslaan en naar vorig item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Overslaan en naar volgend item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-or/strings.xml b/media2/media2-session/src/main/res/values-or/strings.xml
deleted file mode 100644
index 93ed416..0000000
--- a/media2/media2-session/src/main/res/values-or/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ଏବେ ଚାଲୁଛି"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ଚଲାନ୍ତୁ"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ବିରତ କରନ୍ତୁ"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ପୂର୍ବବର୍ତ୍ତୀ ଆଇଟମକୁ ଯାଆନ୍ତୁ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ପରବର୍ତ୍ତୀ ଆଇଟମକୁ ଯାଆନ୍ତୁ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-pa/strings.xml b/media2/media2-session/src/main/res/values-pa/strings.xml
deleted file mode 100644
index f504f50..0000000
--- a/media2/media2-session/src/main/res/values-pa/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ਹੁਣੇ ਚੱਲ ਰਿਹਾ ਹੈ"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ਚਲਾਓ"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"ਰੋਕੋ"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ਪਿਛਲੀ ਆਈਟਮ \'ਤੇ ਜਾਓ"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ਅਗਲੀ ਆਈਟਮ \'ਤੇ ਜਾਓ"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-pl/strings.xml b/media2/media2-session/src/main/res/values-pl/strings.xml
deleted file mode 100644
index c497bdd..0000000
--- a/media2/media2-session/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Co jest grane"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Odtwórz"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Wstrzymaj"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Przeskocz do poprzedniego elementu"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Przeskocz do następnego elementu"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-pt-rBR/strings.xml b/media2/media2-session/src/main/res/values-pt-rBR/strings.xml
deleted file mode 100644
index 80311b2..0000000
--- a/media2/media2-session/src/main/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tocando agora"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Iniciar"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Voltar para o item anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pular para o próximo item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-pt-rPT/strings.xml b/media2/media2-session/src/main/res/values-pt-rPT/strings.xml
deleted file mode 100644
index faf4318..0000000
--- a/media2/media2-session/src/main/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"A reproduzir"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Reproduzir"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ir para o item anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ir para o item seguinte"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-pt/strings.xml b/media2/media2-session/src/main/res/values-pt/strings.xml
deleted file mode 100644
index 80311b2..0000000
--- a/media2/media2-session/src/main/res/values-pt/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tocando agora"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Iniciar"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Voltar para o item anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pular para o próximo item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ro/strings.xml b/media2/media2-session/src/main/res/values-ro/strings.xml
deleted file mode 100644
index c20b632..0000000
--- a/media2/media2-session/src/main/res/values-ro/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Redă"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Întrerupe"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Treci la elementul anterior"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Treci la elementul următor"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ru/strings.xml b/media2/media2-session/src/main/res/values-ru/strings.xml
deleted file mode 100644
index ff54cac..0000000
--- a/media2/media2-session/src/main/res/values-ru/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Текущий медиафайл"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Воспроизвести"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Приостановить"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перейти к предыдущему файлу"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перейти к следующему файлу"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-si/strings.xml b/media2/media2-session/src/main/res/values-si/strings.xml
deleted file mode 100644
index bd244dc..0000000
--- a/media2/media2-session/src/main/res/values-si/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"දැන් වාදනය වේ"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"වාදනය කරන්න"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"විරාම ගන්වන්න"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"පෙර අයිතමය වෙත යන්න"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ඊළඟ අයිතමය වෙත යන්න"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sk/strings.xml b/media2/media2-session/src/main/res/values-sk/strings.xml
deleted file mode 100644
index 0aab75e..0000000
--- a/media2/media2-session/src/main/res/values-sk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Prehráva sa"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Prehrať"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pozastaviť"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskočiť na predchádzajúcu položku"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskočiť na ďalšiu položku"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sl/strings.xml b/media2/media2-session/src/main/res/values-sl/strings.xml
deleted file mode 100644
index cfa794b..0000000
--- a/media2/media2-session/src/main/res/values-sl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Zdaj se predvaja"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Predvajanje"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Začasna zaustavitev"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskok na prejšnji element"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskok na naslednji element"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sq/strings.xml b/media2/media2-session/src/main/res/values-sq/strings.xml
deleted file mode 100644
index 24e8fa7..0000000
--- a/media2/media2-session/src/main/res/values-sq/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Po luhet tani"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Luaj"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Vendos në pauzë"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Kapërce tek artikulli i mëparshëm"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Kapërce tek artikulli tjetër"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sr/strings.xml b/media2/media2-session/src/main/res/values-sr/strings.xml
deleted file mode 100644
index 8bf6a2c..0000000
--- a/media2/media2-session/src/main/res/values-sr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Тренутно свира"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Пусти"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Паузирај"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Пређи на претходну ставку"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Пређи на следећу ставку"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sv/strings.xml b/media2/media2-session/src/main/res/values-sv/strings.xml
deleted file mode 100644
index f52d452..0000000
--- a/media2/media2-session/src/main/res/values-sv/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Spelas just nu"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Spela upp"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausa"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Hoppa till föregående objekt"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Hoppa till nästa objekt"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-sw/strings.xml b/media2/media2-session/src/main/res/values-sw/strings.xml
deleted file mode 100644
index 6571355..0000000
--- a/media2/media2-session/src/main/res/values-sw/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Inayocheza sasa"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Cheza"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Simamisha"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Rudi kwenye kipengee kilichotangulia"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Nenda kwenye kipengee kinachofuata"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ta/strings.xml b/media2/media2-session/src/main/res/values-ta/strings.xml
deleted file mode 100644
index 29fe641..0000000
--- a/media2/media2-session/src/main/res/values-ta/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"இப்போது பிளே செய்வது"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"பிளே செய்யும்"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"இடைநிறுத்தும்"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"முந்தையதற்குச் செல்லும்"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"அடுத்ததற்குச் செல்லும்"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-te/strings.xml b/media2/media2-session/src/main/res/values-te/strings.xml
deleted file mode 100644
index acfa1d9..0000000
--- a/media2/media2-session/src/main/res/values-te/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ప్రస్తుతం ప్లే అవుతున్నది"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"ప్లే చేయి"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"పాజ్ చేయి"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"మునుపటి ఐటెమ్‌కు స్కిప్ చేయి"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"తర్వాత ఐటెమ్‌కు స్కిప్ చేయి"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-th/strings.xml b/media2/media2-session/src/main/res/values-th/strings.xml
deleted file mode 100644
index 17136a7..0000000
--- a/media2/media2-session/src/main/res/values-th/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"กำลังเล่น"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"เล่น"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"หยุดชั่วคราว"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ข้ามไปยังรายการก่อนหน้า"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ข้ามไปยังรายการถัดไป"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-tl/strings.xml b/media2/media2-session/src/main/res/values-tl/strings.xml
deleted file mode 100644
index d56624b..0000000
--- a/media2/media2-session/src/main/res/values-tl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Nagpi-play ngayon"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"I-play"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"I-pause"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Lumaktaw sa nakaraang item"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Lumaktaw sa susunod na item"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-tr/strings.xml b/media2/media2-session/src/main/res/values-tr/strings.xml
deleted file mode 100644
index 8eff645..0000000
--- a/media2/media2-session/src/main/res/values-tr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Şimdi oynatılıyor"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Oynat"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Duraklat"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Önceki öğeye atla"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Sonraki öğeye atla"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-uk/strings.xml b/media2/media2-session/src/main/res/values-uk/strings.xml
deleted file mode 100644
index f805eab..0000000
--- a/media2/media2-session/src/main/res/values-uk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Зараз грає"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Відтворити"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Призупинити"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перейти до попереднього медіафайлу"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перейти до наступного медіафайлу"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-ur/strings.xml b/media2/media2-session/src/main/res/values-ur/strings.xml
deleted file mode 100644
index 5e36503..0000000
--- a/media2/media2-session/src/main/res/values-ur/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"ابھی چل رہا ہے"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"چلائیں"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"موقوف کریں"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"پچھلے آئٹم پر جائیں"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"اگلے آئٹم پر جائیں"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-uz/strings.xml b/media2/media2-session/src/main/res/values-uz/strings.xml
deleted file mode 100644
index d6aac7d..0000000
--- a/media2/media2-session/src/main/res/values-uz/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Bu qaysi musiqa"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Ijro"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauza"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Avvalgi obyektga oʻtish"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Keyingi faylga oʻtish"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-vi/strings.xml b/media2/media2-session/src/main/res/values-vi/strings.xml
deleted file mode 100644
index 12e0e99..0000000
--- a/media2/media2-session/src/main/res/values-vi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Đang phát"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Phát"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Tạm dừng"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Chuyển về mục trước đó"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Chuyển đến mục tiếp theo"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rCN/strings.xml b/media2/media2-session/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 19276e6..0000000
--- a/media2/media2-session/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"正在播放"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"暂停"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳至上一项"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳至下一项"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rHK/strings.xml b/media2/media2-session/src/main/res/values-zh-rHK/strings.xml
deleted file mode 100644
index 1f8a61c..0000000
--- a/media2/media2-session/src/main/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"現正播放"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"暫停"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳去上一個項目"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳去下一個項目"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rTW/strings.xml b/media2/media2-session/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100644
index b056fa7..0000000
--- a/media2/media2-session/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"現正播放"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"暫停"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳到上一個項目"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳到下一個項目"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values-zu/strings.xml b/media2/media2-session/src/main/res/values-zu/strings.xml
deleted file mode 100644
index 4e42ec2..0000000
--- a/media2/media2-session/src/main/res/values-zu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="default_notification_channel_name" msgid="7213672915724563695">"Okudlala manje"</string>
-    <string name="play_button_content_description" msgid="963503759453979404">"Dlala"</string>
-    <string name="pause_button_content_description" msgid="3510124037191104584">"Phumula"</string>
-    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Yeqela entweni yangaphambilini"</string>
-    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Yeqela entweni elandelayo"</string>
-</resources>
diff --git a/media2/media2-session/src/main/res/values/strings.xml b/media2/media2-session/src/main/res/values/strings.xml
deleted file mode 100644
index b8f1fe8..0000000
--- a/media2/media2-session/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<resources>
-    <!-- Notification channel name for default media notification. [CHAR LIMIT=40] -->
-    <string name="default_notification_channel_name">Now playing</string>
-
-    <!-- Content description of 'play' button on media notification for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="play_button_content_description">Play</string>
-    <!-- Content description of 'pause' button on media notification for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="pause_button_content_description">Pause</string>
-    <!-- Content description of 'skip to previous item' button on media notification for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="skip_to_previous_item_button_content_description">Skip to previous item</string>
-    <!-- Content description of 'skip to next item' button on media notification for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="skip_to_next_item_button_content_description">Skip to next item</string>
-</resources>
\ No newline at end of file
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl
deleted file mode 100644
index dc78b89..0000000
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2018 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.media2.session;
-
-import android.os.Bundle;
-
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.session.IMediaSession;
-import androidx.versionedparcelable.ParcelImpl;
-
-/**
- * Interface from MediaSession to MediaController.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- */
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-oneway interface IMediaController {
-    void onCurrentMediaItemChanged(int seq, in ParcelImpl item, int currentIdx, int previousIdx,
-            int nextIdx) = 0;
-    void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int state) = 1;
-    void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed) = 2;
-    void onBufferingStateChanged(int seq, in ParcelImpl item, int state,
-            long bufferedPositionMs, long eventTimeMs, long positionMs) = 3;
-    void onPlaylistChanged(int seq, in ParcelImplListSlice listSlice, in ParcelImpl metadata,
-            int currentIdx, int previousIdx, int nextIdx) = 4;
-    void onPlaylistMetadataChanged(int seq, in ParcelImpl metadata) = 5;
-    void onPlaybackInfoChanged(int seq, in ParcelImpl playbackInfo) = 6;
-    void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
-            int nextIdx) = 7;
-    void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
-            int nextIdx) = 8;
-    void onPlaybackCompleted(int seq) = 9;
-    void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long seekPositionMs) = 10;
-    void onVideoSizeChanged(int seq, in ParcelImpl item, in ParcelImpl videoSize) = 20;
-    void onSubtitleData(int seq, in ParcelImpl item, in ParcelImpl track, in ParcelImpl data) = 24;
-
-    void onConnected(int seq, in ParcelImpl connectionResult) = 11;
-    void onDisconnected(int seq) = 12;
-
-    void onSetCustomLayout(int seq, in List<ParcelImpl> commandButtonlist) = 13;
-    void onAllowedCommandsChanged(int seq, in ParcelImpl commandGroup) = 14;
-    void onCustomCommand(int seq, in ParcelImpl command, in Bundle args) = 15;
-
-    void onSessionResult(int seq, in ParcelImpl sessionResult) = 16;
-    void onLibraryResult(int seq, in ParcelImpl libraryResult) = 17;
-
-    void onTrackInfoChanged(int seq, in List<ParcelImpl> trackInfos,
-            in ParcelImpl selectedVideoTrack, in ParcelImpl selectedAudioTrack,
-            in ParcelImpl selectedSubtitleTrack, in ParcelImpl selectedMetadataTrack) = 21;
-    void onTrackSelected(int seq, in ParcelImpl trackInfo) = 22;
-    void onTrackDeselected(int seq, in ParcelImpl trackInfo) = 23;
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Browser sepcific
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    void onChildrenChanged(int seq, String parentId, int itemCount,
-            in ParcelImpl libraryParams) = 18;
-    void onSearchResultChanged(int seq, String query, int itemCount,
-            in ParcelImpl libraryParams) = 19;
-    // Next Id : 25
-}
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl
deleted file mode 100644
index 5f54a89..0000000
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2018 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.media2.session;
-
-import android.os.Bundle;
-import android.net.Uri;
-import android.view.Surface;
-
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.session.IMediaController;
-import androidx.versionedparcelable.ParcelImpl;
-
-/**
- * Interface from MediaController to MediaSession.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- */
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-oneway interface IMediaSession {
-    void connect(IMediaController caller, int seq, in ParcelImpl connectionRequest) = 0;
-    void release(IMediaController caller, int seq) = 1;
-
-    void setVolumeTo(IMediaController caller, int seq, int value, int flags) = 2;
-    void adjustVolume(IMediaController caller, int seq, int direction, int flags) = 3;
-
-    void play(IMediaController caller, int seq) = 4;
-    void pause(IMediaController caller, int seq) = 5;
-    void prepare(IMediaController caller, int seq) = 6;
-    void fastForward(IMediaController caller, int seq) = 7;
-    void rewind(IMediaController caller, int seq) = 8;
-    void skipForward(IMediaController caller, int seq) = 9;
-    void skipBackward(IMediaController caller, int seq) = 10;
-    void seekTo(IMediaController caller, int seq, long pos) = 11;
-    void onCustomCommand(IMediaController caller, int seq, in ParcelImpl sessionCommand,
-            in Bundle args) = 12;
-    // 13~18: removed
-    void setRating(IMediaController caller, int seq, String mediaId, in ParcelImpl rating) = 19;
-    void setPlaybackSpeed(IMediaController caller, int seq, float speed) = 20;
-
-    void setPlaylist(IMediaController caller, int seq, in List<String> list,
-            in ParcelImpl metadata) = 21;
-    void setMediaItem(IMediaController caller, int seq, String mediaId) = 22;
-    void setMediaUri(IMediaController caller, int seq, in Uri uri, in Bundle extras) = 44;
-    void updatePlaylistMetadata(IMediaController caller, int seq, in ParcelImpl metadata) = 23;
-    void addPlaylistItem(IMediaController caller, int seq, int index, String mediaId) = 24;
-    void removePlaylistItem(IMediaController caller, int seq, int index) = 25;
-    void replacePlaylistItem(IMediaController caller, int seq, int index, String mediaId) = 26;
-    void movePlaylistItem(IMediaController caller, int seq, int fromIndex, int toIndex) = 43;
-    void skipToPlaylistItem(IMediaController caller, int seq, int index) = 27;
-    void skipToPreviousItem(IMediaController caller, int seq) = 28;
-    void skipToNextItem(IMediaController caller, int seq) = 29;
-    void setRepeatMode(IMediaController caller, int seq, int repeatMode) = 30;
-    void setShuffleMode(IMediaController caller, int seq, int shuffleMode) = 31;
-    void setSurface(IMediaController caller, int seq, in Surface surface) = 40;
-    void selectTrack(IMediaController caller, int seq, in ParcelImpl trackInfo) = 41;
-    void deselectTrack(IMediaController caller, int seq, in ParcelImpl trackInfo) = 42;
-
-    void onControllerResult(IMediaController caller, int seq,
-            in ParcelImpl controllerResult) = 32;
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // library service specific
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    void getLibraryRoot(IMediaController caller, int seq, in ParcelImpl libraryParams) = 33;
-    void getItem(IMediaController caller, int seq, String mediaId) = 34;
-    void getChildren(IMediaController caller, int seq, String parentId, int page, int pageSize,
-            in ParcelImpl libraryParams) = 35;
-    void search(IMediaController caller, int seq, String query, in ParcelImpl libraryParams) = 36;
-    void getSearchResult(IMediaController caller, int seq, String query, int page, int pageSize,
-            in ParcelImpl libraryParams) = 37;
-    void subscribe(IMediaController caller, int seq, String parentId,
-            in ParcelImpl libraryParams) = 38;
-    void unsubscribe(IMediaController caller, int seq, String parentId) = 39;
-    // Next Id : 45
-}
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl
deleted file mode 100644
index 190723e..0000000
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2018 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.media2.session;
-
-import androidx.media2.session.IMediaController;
-import androidx.versionedparcelable.ParcelImpl;
-
-/**
- * Interface from MediaController to MediaSessionService.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- */
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-oneway interface IMediaSessionService {
-    void connect(IMediaController caller, in ParcelImpl connectionRequest) = 0;
-    // Next Id : 1
-}
diff --git a/media2/media2-session/version-compat-tests/build.gradle b/media2/media2-session/version-compat-tests/build.gradle
deleted file mode 100644
index 92fc391..0000000
--- a/media2/media2-session/version-compat-tests/build.gradle
+++ /dev/null
@@ -1,5 +0,0 @@
-// This project creates some version compat test artifacts
-// See TestSuiteConfiguration.kt
-plugins {
-    id("AndroidXPlugin")
-}
diff --git a/media2/media2-session/version-compat-tests/common/build.gradle b/media2/media2-session/version-compat-tests/common/build.gradle
deleted file mode 100644
index 2c374e3..0000000
--- a/media2/media2-session/version-compat-tests/common/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2018 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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-android {
-    buildFeatures {
-        aidl = true
-    }
-    namespace "androidx.media2.test.common"
-}
-
-dependencies {
-    api(project(":core:core"))
-    api("androidx.annotation:annotation:1.1.0")
-    api("androidx.versionedparcelable:versionedparcelable:1.1.1")
-    implementation(libs.junit)
-}
diff --git a/media2/media2-session/version-compat-tests/common/lint-baseline.xml b/media2/media2-session/version-compat-tests/common/lint-baseline.xml
deleted file mode 100644
index 4a84ecd..0000000
--- a/media2/media2-session/version-compat-tests/common/lint-baseline.xml
+++ /dev/null
@@ -1,121 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(TIME_SLICE);"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/PollingCheck.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void waitFor(final PollingCheckCondition condition) {"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/PollingCheck.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void waitFor(long timeout, final PollingCheckCondition condition) {"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/PollingCheck.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean equals(Bundle a, Bundle b) {"
-        errorLine2="                                 ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean equals(Bundle a, Bundle b) {"
-        errorLine2="                                           ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean contains(Bundle a, Bundle b) {"
-        errorLine2="                                   ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean contains(Bundle a, Bundle b) {"
-        errorLine2="                                             ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean equals(Object a, Object b) {"
-        errorLine2="                                 ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static boolean equals(Object a, Object b) {"
-        errorLine2="                                           ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Bundle createTestBundle() {"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static String getMediaIdInFakeList(int index) {"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SyncHandler(Looper looper) {"
-        errorLine2="                           ~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void postAndSync(final Runnable runnable) throws InterruptedException {"
-        errorLine2="                                      ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/test/common/TestUtils.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaBrowserCompat.aidl b/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaBrowserCompat.aidl
deleted file mode 100644
index 985fa2b..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaBrowserCompat.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (String controllerId, 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.media2.test.common;
-
-import android.content.ComponentName;
-
-interface IRemoteMediaBrowserCompat {
-
-    void create(String browserId, in ComponentName componentName);
-
-    // MediaBrowserCompat Methods
-    void connect(String browserId, boolean waitForConnection);
-    void disconnect(String browserId);
-    boolean isConnected(String browserId);
-    ComponentName getServiceComponent(String browserId);
-    String getRoot(String browserId);
-    Bundle getExtras(String browserId);
-    Bundle getConnectedSessionToken(String browserId);
-    void subscribe(String browserId, String parentId, in Bundle options);
-    void unsubscribe(String browserId, String parentId);
-    void getItem(String browserId, String mediaId);
-    void search(String browserId, String query, in Bundle extras);
-    void sendCustomAction(String browserId, String action, in Bundle extras);
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaController.aidl b/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaController.aidl
deleted file mode 100644
index b23d4ae..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaController.aidl
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (String controllerId, 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.media2.test.common;
-
-import android.net.Uri;
-import android.os.ResultReceiver;
-
-import androidx.versionedparcelable.ParcelImpl;
-
-interface IRemoteMediaController {
-
-    void create(boolean isBrowser, String controllerId, in ParcelImpl token,
-            in Bundle connectionHints, boolean waitForConnection);
-
-    // MediaController Methods
-    ParcelImpl getConnectedSessionToken(String controllerId);
-    void play(String controllerId);
-    void pause(String controllerId);
-    void prepare(String controllerId);
-    void seekTo(String controllerId, long pos);
-    void setPlaybackSpeed(String controllerId, float speed);
-    void setPlaylist(String controllerId, in List<String> list, in ParcelImpl metadata);
-    void createAndSetFakePlaylist(String controllerId, int size, in ParcelImpl metadata);
-    void setMediaItem(String controllerId, in String mediaId);
-    void setMediaUri(String controllerId,  in Uri uri, in Bundle extras);
-    void updatePlaylistMetadata(String controllerId, in ParcelImpl metadata);
-    void addPlaylistItem(String controllerId, int index, String mediaId);
-    void removePlaylistItem(String controllerId, int index);
-    void replacePlaylistItem(String controllerId, int index, String mediaId);
-    void movePlaylistItem(String controllerId, int fromIndex, int toIndex);
-    void skipToPreviousItem(String controllerId);
-    void skipToNextItem(String controllerId);
-    void skipToPlaylistItem(String controllerId, int index);
-    void setShuffleMode(String controllerId, int shuffleMode);
-    void setRepeatMode(String controllerId, int repeatMode);
-    void setVolumeTo(String controllerId, int value, int flags);
-    void adjustVolume(String controllerId, int direction, int flags);
-    void sendCustomCommand(String controllerId, in ParcelImpl command, in Bundle args);
-    void fastForward(String controllerId);
-    void rewind(String controllerId);
-    void skipForward(String controllerId);
-    void skipBackward(String controllerId);
-    void setRating(String controllerId, String mediaId, in ParcelImpl rating);
-    void close(String controllerId);
-
-    // MediaBrowser methods
-    void getLibraryRoot(String controllerId, in ParcelImpl libraryParams);
-    void subscribe(String controllerId, String parentId, in ParcelImpl libraryParams);
-    void unsubscribe(String controllerId, String parentId);
-    void getChildren(String controllerId, String parentId, int page, int pageSize,
-            in ParcelImpl libraryParams);
-    void getItem(String controllerId, String mediaId);
-    void search(String controllerId, String query, in ParcelImpl libraryParams);
-    void getSearchResult(String controllerId, String query, int page, int pageSize,
-            in ParcelImpl libraryParams);
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl b/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl
deleted file mode 100644
index 335fc00..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (String controllerId, 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.media2.test.common;
-
-import android.net.Uri;
-import android.os.ResultReceiver;
-
-interface IRemoteMediaControllerCompat {
-
-    void create(String controllerId, in Bundle token, boolean waitForConnection);
-
-    // MediaControllerCompat Methods
-    void addQueueItem(String controllerId, in Bundle description);
-    void addQueueItemWithIndex(String controllerId, in Bundle description, int index);
-    void removeQueueItem(String controllerId, in Bundle description);
-    void setVolumeTo(String controllerId, int value, int flags);
-    void adjustVolume(String controllerId, int direction, int flags);
-    void sendCommand(String controllerId, String command, in Bundle params, in ResultReceiver cb);
-
-    // TransportControl methods
-    void prepare(String controllerId);
-    void prepareFromMediaId(String controllerId, String mediaId, in Bundle extras);
-    void prepareFromSearch(String controllerId, String query, in Bundle extras);
-    void prepareFromUri(String controllerId, in Uri uri, in Bundle extras);
-    void play(String controllerId);
-    void playFromMediaId(String controllerId, String mediaId, in Bundle extras);
-    void playFromSearch(String controllerId, String query, in Bundle extras);
-    void playFromUri(String controllerId, in Uri uri, in Bundle extras);
-    void skipToQueueItem(String controllerId, long id);
-    void pause(String controllerId);
-    void stop(String controllerId);
-    void seekTo(String controllerId, long pos);
-    void setPlaybackSpeed(String controllerId, float speed);
-    void fastForward(String controllerId);
-    void skipToNext(String controllerId);
-    void rewind(String controllerId);
-    void skipToPrevious(String controllerId);
-    void setRating(String controllerId, in Bundle rating);
-    void setRatingWithExtras(String controllerId, in Bundle rating, in Bundle extras);
-    void setCaptioningEnabled(String controllerId, boolean enabled);
-    void setRepeatMode(String controllerId, int repeatMode);
-    void setShuffleMode(String controllerId, int shuffleMode);
-    void sendCustomAction(String controllerId, in Bundle customAction, in Bundle args);
-    void sendCustomActionWithName(String controllerId, String action, in Bundle args);
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl b/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl
deleted file mode 100644
index 725b169..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-import androidx.versionedparcelable.ParcelImpl;
-
-interface IRemoteMediaSession {
-
-    void create(String sessionId, in Bundle tokenExtras);
-
-    // MediaSession Methods
-    ParcelImpl getToken(String sessionId);
-    Bundle getCompatToken(String sessionId);
-    void updatePlayer(String sessionId, in Bundle playerBundle);
-    void broadcastCustomCommand(String sessionId, in ParcelImpl command, in Bundle args);
-    void sendCustomCommand(String sessionId, in Bundle controller, in ParcelImpl command,
-            in Bundle args);
-    void close(String sessionId);
-    void setAllowedCommands(String sessionId, in Bundle controller, in ParcelImpl commands);
-    void setCustomLayout(String sessionId, in Bundle controller, in List<ParcelImpl> layout);
-
-    // SessionPlayer Methods
-    void setPlayerState(String sessionId, int state);
-    void setCurrentPosition(String sessionId, long pos);
-    void setBufferedPosition(String sessionId, long pos);
-    void setDuration(String sessionId, long duration);
-    void setPlaybackSpeed(String sessionId, float speed);
-    void notifySeekCompleted(String sessionId, long pos);
-    void notifyBufferingStateChanged(String sessionId, int itemIndex, int buffState);
-    void notifyPlayerStateChanged(String sessionId, int state);
-    void notifyPlaybackSpeedChanged(String sessionId, float speed);
-    void notifyCurrentMediaItemChanged(String sessionId, int index);
-    void notifyAudioAttributesChanged(String sessionId, in ParcelImpl attrs);
-    void notifyVideoSizeChanged(String sessionId, in ParcelImpl videoSize);
-    boolean surfaceExists(String sessionId);
-    void notifySubtitleData(String sessionId, in ParcelImpl item, in ParcelImpl track,
-            in ParcelImpl data);
-
-    void setPlaylist(String sessionId, in List<ParcelImpl> playlist);
-    void setCurrentMediaItemMetadata(String sessionId, in ParcelImpl metadata);
-
-    void createAndSetFakePlaylist(String sessionId, int size);
-    void setPlaylistWithFakeItem(String sessionId, in List<ParcelImpl> playlist);
-    void setPlaylistMetadata(String sessionId, in ParcelImpl metadata);
-    void setPlaylistMetadataWithLargeBitmaps(String sessionId, int count, int width, int height);
-    void setShuffleMode(String sessionId, int shuffleMode);
-    void setRepeatMode(String sessionId, int repeatMode);
-    void setCurrentMediaItem(String sessionId, int index);
-    void notifyPlaylistChanged(String sessionId);
-    void notifyPlaylistMetadataChanged(String sessionId);
-    void notifyShuffleModeChanged(String sessionId);
-    void notifyRepeatModeChanged(String sessionId);
-    void notifyPlaybackCompleted(String sessionId);
-    void notifyTrackInfoChanged(String sessionId, in List<ParcelImpl> trackInfos);
-    void notifyTrackSelected(String sessionId, in ParcelImpl trackInfo);
-    void notifyTrackDeselected(String sessionId, in ParcelImpl trackInfo);
-    void notifyVolumeChanged(String sessionId, int volume);
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSessionCompat.aidl b/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSessionCompat.aidl
deleted file mode 100644
index 8528a78..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSessionCompat.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import android.app.PendingIntent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-// Here, we use Bundle instead of the *Compat class (which implement parcelable).
-// This is to avoid making dependency of testlib module on media library.
-interface IRemoteMediaSessionCompat {
-
-    void create(String sessionTag);
-
-    // MediaSessionCompat Methods
-    Bundle getSessionToken(String sessionTag);
-    void release(String sessionTag);
-    void setPlaybackToLocal(String sessionTag, int stream);
-    void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume,
-            int currentVolume);
-    void setPlaybackState(String sessionTag, in Bundle stateBundle);
-    void setMetadata(String sessionTag, in Bundle metadataBundle);
-    void setQueue(String sessionTag, in Bundle queueBundle);
-    void setQueueTitle(String sessionTag, in CharSequence title);
-    void setRepeatMode(String sessionTag, int repeatMode);
-    void setShuffleMode(String sessionTag, int shuffleMode);
-    void setSessionActivity(String sessionTag, in PendingIntent pi);
-    void setFlags(String sessionTag, int flags);
-    void setRatingType(String sessionTag, int type);
-    void sendSessionEvent(String sessionTag, String event, in Bundle extras);
-    void setCaptioningEnabled(String sessionTag, boolean enabled);
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java
deleted file mode 100644
index cf8d619..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import android.content.ComponentName;
-
-public class CommonConstants {
-
-    public static final String SERVICE_PACKAGE_NAME = "androidx.media2.test.service.test";
-    public static final String CLIENT_PACKAGE_NAME = "androidx.media2.test.client.test";
-
-    public static final ComponentName MEDIA2_SESSION_PROVIDER_SERVICE = new ComponentName(
-            SERVICE_PACKAGE_NAME, "androidx.media2.test.service.MediaSessionProviderService");
-    public static final ComponentName MEDIA2_CONTROLLER_PROVIDER_SERVICE = new ComponentName(
-            CLIENT_PACKAGE_NAME, "androidx.media2.test.client.MediaControllerProviderService");
-
-    public static final ComponentName MEDIA_SESSION_COMPAT_PROVIDER_SERVICE = new ComponentName(
-            SERVICE_PACKAGE_NAME, "androidx.media2.test.service.MediaSessionCompatProviderService");
-    public static final ComponentName MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE = new ComponentName(
-            CLIENT_PACKAGE_NAME,
-            "androidx.media2.test.client.MediaControllerCompatProviderService");
-    public static final ComponentName MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE = new ComponentName(
-            CLIENT_PACKAGE_NAME, "androidx.media2.test.client.MediaBrowserCompatProviderService");
-
-
-    public static final ComponentName MOCK_MEDIA2_SESSION_SERVICE = new ComponentName(
-            SERVICE_PACKAGE_NAME, "androidx.media2.test.service.MockMediaSessionService");
-    public static final ComponentName MOCK_MEDIA2_LIBRARY_SERVICE = new ComponentName(
-            SERVICE_PACKAGE_NAME, "androidx.media2.test.service.MockMediaLibraryService");
-
-    public static final String ACTION_MEDIA2_SESSION = "androidx.media2.test.action.MEDIA2_SESSION";
-    public static final String ACTION_MEDIA2_CONTROLLER =
-            "androidx.media2.test.action.MEDIA2_CONTROLLER";
-    public static final String ACTION_MEDIA_SESSION_COMPAT =
-            "androidx.media2.test.action.MEDIA_SESSION_COMPAT";
-    public static final String ACTION_MEDIA_CONTROLLER_COMPAT =
-            "androidx.media2.test.action.MEDIA_CONTROLLER_COMPAT";
-    public static final String ACTION_MEDIA_BROWSER_COMPAT =
-            "androidx.media2.test.action.MEDIA_BROWSER_COMPAT";
-
-    public static final String VERSION_TOT = "tot";
-    public static final String VERSION_PREVIOUS = "previous";
-
-    // Keys for arguments.
-    public static final String KEY_CLIENT_VERSION = "client_version";
-    public static final String KEY_SERVICE_VERSION = "service_version";
-    public static final String KEY_AUDIO_ATTRIBUTES = "audioAttributes";
-    public static final String KEY_PLAYER_STATE = "playerState";
-    public static final String KEY_PLAYLIST = "playlist";
-    public static final String KEY_CURRENT_POSITION = "currentPosition";
-    public static final String KEY_BUFFERED_POSITION = "bufferedPosition";
-    public static final String KEY_BUFFERING_STATE = "bufferingState";
-    public static final String KEY_PLAYBACK_SPEED = "playbackSpeed";
-    public static final String KEY_MEDIA_ITEM = "mediaItem";
-    public static final String KEY_PLAYLIST_METADATA = "playlistMetadata";
-    public static final String KEY_ARGUMENTS = "arguments";
-    public static final String KEY_RESULT_RECEIVER = "resultReceiver";
-    public static final String KEY_MAX_VOLUME = "maxVolume";
-    public static final String KEY_CURRENT_VOLUME = "currentVolume";
-    public static final String KEY_VOLUME_CONTROL_TYPE = "volumeControlType";
-    public static final String KEY_VIDEO_SIZE = "videoSize";
-    public static final String KEY_TRACK_INFO = "trackInfo";
-    public static final String KEY_SHUFFLE_MODE = "shuffleMode";
-    public static final String KEY_REPEAT_MODE = "repeatMode";
-
-    // SessionCompat arguments
-    public static final String KEY_SESSION_COMPAT_TOKEN = "sessionCompatToken";
-    public static final String KEY_PLAYBACK_STATE_COMPAT = "playbackStateCompat";
-    public static final String KEY_METADATA_COMPAT = "metadataCompat";
-    public static final String KEY_QUEUE = "queue";
-
-    public static final int INDEX_FOR_UNKONWN_ITEM = -1;
-    public static final int INDEX_FOR_NULL_ITEM = -2;
-
-    // Default test name
-    public static final String DEFAULT_TEST_NAME = "defaultTestName";
-
-    private CommonConstants() {
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java
deleted file mode 100644
index 0090976..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java
+++ /dev/null
@@ -1,59 +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.media2.test.common;
-
-import android.annotation.SuppressLint;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Custom Parcelable class to test sending/receiving user parcelables between processes.
- */
-@SuppressLint("BanParcelableUsage")
-public class CustomParcelable implements Parcelable {
-
-    private int mValue;
-
-    public CustomParcelable(int value) {
-        mValue = value;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @SuppressLint("UnknownNullness") // Parcel dest
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mValue);
-    }
-
-    public static final Parcelable.Creator<CustomParcelable> CREATOR =
-            new Parcelable.Creator<CustomParcelable>() {
-                @Override
-                public CustomParcelable createFromParcel(Parcel in) {
-                    int value = in.readInt();
-                    return new CustomParcelable(value);
-                }
-
-                @Override
-                public CustomParcelable[] newArray(int size) {
-                    return new CustomParcelable[size];
-                }
-            };
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaBrowserConstants.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaBrowserConstants.java
deleted file mode 100644
index 688a382..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaBrowserConstants.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Constants for calling MediaBrowser methods.
- */
-public class MediaBrowserConstants {
-
-    public static final String ROOT_ID = "rootId";
-    public static final Bundle ROOT_EXTRAS = new Bundle();
-
-    public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
-    public static final String MEDIA_ID_GET_NULL_ITEM = "media_id_get_null_item";
-    public static final String MEDIA_ID_GET_INVALID_ITEM = "media_id_get_invalid_item";
-
-    public static final String PARENT_ID = "parent_id";
-    public static final String PARENT_ID_LONG_LIST = "parent_id_long_list";
-    public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
-    public static final String PARENT_ID_ERROR = "parent_id_error";
-
-    public static final List<String> GET_CHILDREN_RESULT = new ArrayList<>();
-    public static final int CHILDREN_COUNT = 100;
-
-    public static final int LONG_LIST_COUNT = 5000;
-
-    public static final String SEARCH_QUERY = "search_query";
-    public static final String SEARCH_QUERY_LONG_LIST = "search_query_long_list";
-    public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
-    public static final int SEARCH_TIME_IN_MS = 5000;
-    public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
-    public static final String SEARCH_QUERY_ERROR = "search_query_error";
-
-    public static final List<String> SEARCH_RESULT = new ArrayList<>();
-    public static final int SEARCH_RESULT_COUNT = 50;
-
-    public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL =
-            "subscribe_id_notify_children_changed_to_all";
-    public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE =
-            "subscribe_id_notify_children_changed_to_one";
-    public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID =
-            "subscribe_id_notify_children_changed_to_all_with_non_subscribed_id";
-    public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID =
-            "subscribe_id_notify_children_changed_to_one_with_non_subscribed_id";
-    public static final int NOTIFY_CHILDREN_CHANGED_ITEM_COUNT = 101;
-    public static final Bundle NOTIFY_CHILDREN_CHANGED_EXTRAS = TestUtils.createTestBundle();
-
-    public static final String CUSTOM_ACTION = "customAction";
-    public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
-
-    public static final String CUSTOM_ACTION_ASSERT_PARAMS = "assertParams";
-
-    static {
-        ROOT_EXTRAS.putString(ROOT_ID, ROOT_ID);
-
-        CUSTOM_ACTION_EXTRAS.putString(CUSTOM_ACTION, CUSTOM_ACTION);
-
-        GET_CHILDREN_RESULT.clear();
-        String getChildrenMediaIdPrefix = "get_children_media_id_";
-        for (int i = 0; i < CHILDREN_COUNT; i++) {
-            GET_CHILDREN_RESULT.add(getChildrenMediaIdPrefix + i);
-        }
-
-        SEARCH_RESULT.clear();
-        String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
-        for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
-            SEARCH_RESULT.add(getSearchResultMediaIdPrefix + i);
-        }
-    }
-
-    private MediaBrowserConstants() {
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaSessionConstants.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaSessionConstants.java
deleted file mode 100644
index 39af6e8..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MediaSessionConstants.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-/**
- * Constants for calling MediaSession methods.
- */
-public class MediaSessionConstants {
-
-    // Test method names
-    public static final String TEST_GET_SESSION_ACTIVITY = "testGetSessionActivity";
-    public static final String TEST_CONTROLLER_CALLBACK_SESSION_REJECTS =
-            "testControllerCallback_sessionRejects";
-    public static final String TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST =
-            "testOnPlaylistMetadataChanged_sessionSetPlaylist";
-
-    private MediaSessionConstants() {
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MockActivity.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MockActivity.java
deleted file mode 100644
index 3420d71..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/MockActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import android.app.Activity;
-
-public class MockActivity extends Activity {
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/PollingCheck.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/PollingCheck.java
deleted file mode 100644
index b8b598b..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/PollingCheck.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import static org.junit.Assert.fail;
-
-/**
- * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
- * (Copied from testutils/src/main/java/androidx/testutils/PollingCheck.java.)
- */
-public abstract class PollingCheck {
-    private static final long DEFAULT_TIMEOUT = 3000;
-    private static final long TIME_SLICE = 50;
-    private final long mTimeout;
-
-    /**
-     * The condition that the PollingCheck should use to proceed successfully.
-     */
-    public interface PollingCheckCondition {
-        /**
-         * @return Whether the polling condition has been met.
-         */
-        boolean canProceed();
-    }
-
-    public PollingCheck(long timeout) {
-        mTimeout = timeout;
-    }
-
-    protected abstract boolean check();
-
-    /**
-     * Start running the polling check.
-     */
-    public void run() {
-        if (check()) {
-            return;
-        }
-
-        long timeout = mTimeout;
-        while (timeout > 0) {
-            try {
-                Thread.sleep(TIME_SLICE);
-            } catch (InterruptedException e) {
-                throw new AssertionError("unexpected InterruptedException");
-            }
-
-            if (check()) {
-                return;
-            }
-
-            timeout -= TIME_SLICE;
-        }
-
-        fail("unexpected timeout");
-    }
-
-    /**
-     * Instantiate and start polling for a given condition with a default 3000ms timeout.
-     * @param condition The condition to check for success.
-     */
-    public static void waitFor(final PollingCheckCondition condition) {
-        new PollingCheck(DEFAULT_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return condition.canProceed();
-            }
-        }.run();
-    }
-
-    /**
-     * Instantiate and start polling for a given condition.
-     * @param timeout Time out in ms
-     * @param condition The condition to check for success.
-     */
-    public static void waitFor(long timeout, final PollingCheckCondition condition) {
-        new PollingCheck(timeout) {
-            @Override
-            protected boolean check() {
-                return condition.canProceed();
-            }
-        }.run();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java
deleted file mode 100644
index 1fd6712..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.common;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class TestUtils {
-
-    public static final int TIMEOUT_MS = 1000;
-    public static final int PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS = 3000;
-
-    /**
-     * Compares contents of two bundles.
-     *
-     * @param a a bundle
-     * @param b another bundle
-     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
-     *     incorrect if any bundle contains a bundle.
-     */
-    public static boolean equals(Bundle a, Bundle b) {
-        return contains(a, b) && contains(b, a);
-    }
-
-    /**
-     * Checks whether a Bundle contains another bundle.
-     *
-     * @param a a bundle
-     * @param b another bundle
-     * @return {@code true} if a contains b. {@code false} otherwise. This may be incorrect if any
-     *      bundle contains a bundle.
-     */
-    @SuppressWarnings("deprecation")
-    public static boolean contains(Bundle a, Bundle b) {
-        if (a == b) {
-            return true;
-        }
-        if (a == null || b == null) {
-            return b == null;
-        }
-        if (!a.keySet().containsAll(b.keySet())) {
-            return false;
-        }
-        for (String key : b.keySet()) {
-            if (!equals(a.get(key), b.get(key))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    // Copied code from ObjectsCompat.java due to build dependency problem of
-    // previous version of support lib.
-    public static boolean equals(Object a, Object b) {
-        return Objects.equals(a, b);
-    }
-
-    /**
-     * Create a bundle for testing purpose.
-     *
-     * @return the newly created bundle.
-     */
-    public static Bundle createTestBundle() {
-        Bundle bundle = new Bundle();
-        bundle.putString("test_key", "test_value");
-        return bundle;
-    }
-
-    /**
-     * When testing with any fake lists, get the expected media ID of index {@param index}.
-     */
-    public static String getMediaIdInFakeList(int index) {
-        // Set the media as the index with leading zeros.
-        return String.format("%08d", index);
-    }
-
-    /**
-     * Handler that always waits until the Runnable finishes.
-     */
-    public static class SyncHandler extends Handler {
-        public SyncHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void postAndSync(final Runnable runnable) throws InterruptedException {
-            if (getLooper() == Looper.myLooper()) {
-                runnable.run();
-            } else {
-                final CountDownLatch latch = new CountDownLatch(1);
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        runnable.run();
-                        latch.countDown();
-                    }
-                });
-                assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-    }
-
-    private TestUtils() {
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/build.gradle b/media2/media2-session/version-compat-tests/current/client/build.gradle
deleted file mode 100644
index 9f0c72d..0000000
--- a/media2/media2-session/version-compat-tests/current/client/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    androidTestImplementation(project(":media2:media2-session"))
-    androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-}
-
-android {
-    namespace "androidx.media2.test.client"
-}
-
-androidx {
-    failOnDeprecationWarnings = false
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 1cb0b1d3..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application android:supportsRtl="true">
-        <activity
-            android:name="androidx.media2.test.client.SurfaceActivity"
-            android:exported="true" />
-
-        <service
-            android:name="androidx.media2.test.client.MediaControllerProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA2_CONTROLLER" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.client.MediaControllerCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_CONTROLLER_COMPAT" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.client.MediaBrowserCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_BROWSER_COMPAT" />
-            </intent-filter>
-        </service>
-    </application>
-
-    <queries>
-        <package android:name="androidx.media2.test.service.test" />
-    </queries>
-</manifest>
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java
deleted file mode 100644
index 8961f98..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.ConnectionCallback;
-import android.support.v4.media.MediaBrowserCompat.ItemCallback;
-import android.support.v4.media.MediaBrowserCompat.SearchCallback;
-import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaBrowserCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaBrowserCompat} and calls its methods
- * according to the service app's requests.
- */
-public class MediaBrowserCompatProviderService extends Service {
-    private static final String TAG = "MediaBrowserCompatProviderService";
-
-    Map<String, MediaBrowserCompat> mMediaBrowserCompatMap = new HashMap<>();
-    Map<String, TestBrowserConnectionCallback> mConnectionCallbackMap = new HashMap<>();
-    RemoteMediaBrowserCompatStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaBrowserCompatStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_BROWSER_COMPAT.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    private class RemoteMediaBrowserCompatStub extends IRemoteMediaBrowserCompat.Stub {
-        @Override
-        public void create(final String browserId, final ComponentName componentName)
-                throws RemoteException {
-            try {
-                final TestBrowserConnectionCallback callback = new TestBrowserConnectionCallback();
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        MediaBrowserCompat browser = new MediaBrowserCompat(
-                                MediaBrowserCompatProviderService.this, componentName, callback,
-                                new Bundle() /* rootHints */);
-
-                        mMediaBrowserCompatMap.put(browserId, browser);
-                        mConnectionCallbackMap.put(browserId, callback);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaMediaBrowserCompat",
-                        ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaBrowserCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void connect(String browserId, boolean waitForConnection) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.connect();
-
-            if (waitForConnection) {
-                TestBrowserConnectionCallback callback = mConnectionCallbackMap.get(browserId);
-
-                boolean connected = false;
-                try {
-                    connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException ex) {
-                    Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-                }
-
-                if (!connected) {
-                    Log.e(TAG, "Could not connect to the given browser service.");
-                }
-            }
-        }
-
-        @Override
-        public void disconnect(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.disconnect();
-        }
-
-        @Override
-        public boolean isConnected(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.isConnected();
-        }
-
-        @Override
-        public ComponentName getServiceComponent(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getServiceComponent();
-        }
-
-        @Override
-        public String getRoot(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getRoot();
-        }
-
-        @Override
-        public Bundle getExtras(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getExtras();
-        }
-
-        @Override
-        public Bundle getConnectedSessionToken(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            Bundle tokenBundle = new Bundle();
-            tokenBundle.putParcelable(KEY_SESSION_COMPAT_TOKEN, browser.getSessionToken());
-            return tokenBundle;
-        }
-
-        @Override
-        public void subscribe(String browserId, String parentId, Bundle options)
-                throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.subscribe(parentId, options, new SubscriptionCallback() {});
-        }
-
-        @Override
-        public void unsubscribe(String browserId, String parentId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.unsubscribe(parentId);
-        }
-
-        @Override
-        public void getItem(String browserId, String mediaId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.getItem(mediaId, new ItemCallback() {});
-        }
-
-        @Override
-        public void search(String browserId, String query, Bundle extras) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.search(query, extras, new SearchCallback() {});
-        }
-
-        @Override
-        public void sendCustomAction(String browserId, String action, Bundle extras)
-                throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.sendCustomAction(action, extras, null /* customActionCallback */);
-        }
-    }
-
-    private class TestBrowserConnectionCallback extends ConnectionCallback {
-        private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-        @Override
-        public void onConnected() {
-            mConnectionLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
deleted file mode 100644
index b3d38b9..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_ARGUMENTS;
-
-import android.app.Service;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaControllerCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaControllerCompat} and calls its methods
- * according to the service app's requests.
- */
-public class MediaControllerCompatProviderService extends Service {
-    private static final String TAG = "MediaControllerCompatProviderService";
-
-    Map<String, MediaControllerCompat> mMediaControllerCompatMap = new HashMap<>();
-    RemoteMediaControllerCompatStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaControllerCompatStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_CONTROLLER_COMPAT.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    private class RemoteMediaControllerCompatStub extends IRemoteMediaControllerCompat.Stub {
-
-        @Override
-        public void create(String controllerId, Bundle tokenBundle, boolean waitForConnection) {
-            MediaSessionCompat.Token token = (MediaSessionCompat.Token) getParcelable(tokenBundle);
-            MediaControllerCompat controller = new MediaControllerCompat(
-                    MediaControllerCompatProviderService.this, token);
-
-            final TestControllerCallback callback = new TestControllerCallback();
-            controller.registerCallback(callback, mHandler);
-
-            mMediaControllerCompatMap.put(controllerId, controller);
-
-            if (!waitForConnection) {
-                return;
-            }
-
-            boolean connected = false;
-            try {
-                connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-            }
-
-            if (!connected) {
-                Log.e(TAG, "Could not connect to the given session.");
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaControllerCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void addQueueItem(String controllerId, Bundle descriptionBundle)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.addQueueItem(desc);
-        }
-
-        @Override
-        public void addQueueItemWithIndex(String controllerId, Bundle descriptionBundle, int index)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.addQueueItem(desc, index);
-        }
-
-        @Override
-        public void removeQueueItem(String controllerId, Bundle descriptionBundle)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.removeQueueItem(desc);
-        }
-
-        @Override
-        public void setVolumeTo(String controllerId, int value, int flags)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.setVolumeTo(value, flags);
-        }
-
-        @Override
-        public void adjustVolume(String controllerId, int direction, int flags)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.adjustVolume(direction, flags);
-        }
-
-        @Override
-        public void sendCommand(String controllerId, String command, Bundle params,
-                ResultReceiver cb) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.sendCommand(command, params, cb);
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaControllerCompat.TransportControls methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void prepare(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepare();
-        }
-
-        @Override
-        public void prepareFromMediaId(String controllerId, String mediaId, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromMediaId(mediaId, extras);
-        }
-
-        @Override
-        public void prepareFromSearch(String controllerId, String query, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromSearch(query, extras);
-        }
-
-        @Override
-        public void prepareFromUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromUri(uri, extras);
-        }
-
-        @Override
-        public void play(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().play();
-        }
-
-        @Override
-        public void playFromMediaId(String controllerId, String mediaId, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromMediaId(mediaId, extras);
-        }
-
-        @Override
-        public void playFromSearch(String controllerId, String query, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromSearch(query, extras);
-        }
-
-        @Override
-        public void playFromUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromUri(uri, extras);
-        }
-
-        @Override
-        public void skipToQueueItem(String controllerId, long id) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToQueueItem(id);
-        }
-
-        @Override
-        public void pause(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().pause();
-        }
-
-        @Override
-        public void stop(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().stop();
-        }
-
-        @Override
-        public void seekTo(String controllerId, long pos) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().seekTo(pos);
-        }
-
-        @Override
-        public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setPlaybackSpeed(speed);
-        }
-
-        @Override
-        public void fastForward(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().fastForward();
-        }
-
-        @Override
-        public void skipToNext(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToNext();
-        }
-
-        @Override
-        public void rewind(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().rewind();
-        }
-
-        @Override
-        public void skipToPrevious(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToPrevious();
-        }
-
-        @Override
-        public void setRating(String controllerId, Bundle ratingBundle) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
-            controller.getTransportControls().setRating(rating);
-        }
-
-        @Override
-        public void setRatingWithExtras(String controllerId, Bundle ratingBundle, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
-            controller.getTransportControls().setRating(rating, extras);
-        }
-
-        @Override
-        public void setCaptioningEnabled(String controllerId, boolean enabled)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setCaptioningEnabled(enabled);
-        }
-
-        @Override
-        public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void sendCustomAction(String controllerId, Bundle customActionBundle, Bundle args)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            PlaybackStateCompat.CustomAction customAction =
-                    (PlaybackStateCompat.CustomAction) getParcelable(customActionBundle);
-            controller.getTransportControls().sendCustomAction(customAction, args);
-        }
-
-        @Override
-        public void sendCustomActionWithName(String controllerId, String action, Bundle args)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().sendCustomAction(action, args);
-        }
-
-        @SuppressWarnings("deprecation")
-        private Parcelable getParcelable(Bundle bundle) {
-            bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            return bundle.getParcelable(KEY_ARGUMENTS);
-        }
-    }
-
-    private class TestControllerCallback extends MediaControllerCompat.Callback {
-        private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-        @Override
-        public void onSessionReady() {
-            super.onSessionReady();
-            mConnectionLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java
deleted file mode 100644
index 3ce5bbf..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaController;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaController} and calls its methods
- * according to the service app's requests.
- */
-public class MediaControllerProviderService extends Service {
-    private static final String TAG = "MediaControllerProviderService";
-
-    Map<String, MediaController> mMediaControllerMap = new HashMap<>();
-    RemoteMediaControllerStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaControllerStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA2_CONTROLLER.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaController controller : mMediaControllerMap.values()) {
-            controller.close();
-        }
-    }
-
-    private class RemoteMediaControllerStub extends IRemoteMediaController.Stub {
-        @Override
-        public void create(final boolean isBrowser, final String controllerId,
-                ParcelImpl tokenParcelable, final Bundle connectionHints, boolean waitForConnection)
-                throws RemoteException {
-            final SessionToken token = MediaParcelUtils.fromParcelable(tokenParcelable);
-            final TestControllerCallback callback = new TestControllerCallback();
-
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        Context context = MediaControllerProviderService.this;
-                        MediaController controller;
-                        if (isBrowser) {
-                            MediaBrowser.Builder builder = new MediaBrowser.Builder(context)
-                                    .setSessionToken(token)
-                                    .setControllerCallback(mExecutor, callback);
-                            if (connectionHints != null) {
-                                builder.setConnectionHints(connectionHints);
-                            }
-                            controller = builder.build();
-                        } else {
-                            MediaController.Builder builder = new MediaController.Builder(context)
-                                    .setSessionToken(token)
-                                    .setControllerCallback(mExecutor, callback);
-                            if (connectionHints != null) {
-                                builder.setConnectionHints(connectionHints);
-                            }
-                            controller = builder.build();
-
-                        }
-                        mMediaControllerMap.put(controllerId, controller);
-                    }
-                });
-            } catch (Exception ex) {
-                Log.e(TAG, "Exception occurred while creating MediaController.", ex);
-            }
-
-            if (!waitForConnection) {
-                return;
-            }
-
-            boolean connected = false;
-            try {
-                connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-            }
-
-            if (!connected) {
-                Log.e(TAG, "Could not connect to the given session.");
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaController methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public ParcelImpl getConnectedSessionToken(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            return MediaParcelUtils.toParcelable(controller.getConnectedToken());
-        }
-
-        @Override
-        public void play(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.play();
-        }
-
-        @Override
-        public void pause(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.pause();
-        }
-
-        @Override
-        public void prepare(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.prepare();
-        }
-
-        @Override
-        public void seekTo(String controllerId, long pos) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.seekTo(pos);
-        }
-
-        @Override
-        public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setPlaybackSpeed(speed);
-        }
-
-        @Override
-        public void setPlaylist(String controllerId, List<String> list, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setPlaylist(list, (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void createAndSetFakePlaylist(String controllerId, int size, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            List<String> list = new ArrayList<>();
-            for (int i = 0; i < size; i++) {
-                // Make media ID of each item same with its index.
-                list.add(TestUtils.getMediaIdInFakeList(i));
-            }
-            controller.setPlaylist(list, (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void setMediaItem(String controllerId, String mediaId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setMediaItem(mediaId);
-        }
-
-        @Override
-        public void setMediaUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setMediaUri(uri, extras);
-        }
-
-        @Override
-        public void updatePlaylistMetadata(String controllerId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.updatePlaylistMetadata(
-                    (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void addPlaylistItem(String controllerId, int index, String mediaId)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.addPlaylistItem(index, mediaId);
-        }
-
-        @Override
-        public void removePlaylistItem(String controllerId, int index) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.removePlaylistItem(index);
-        }
-
-        @Override
-        public void replacePlaylistItem(String controllerId, int index, String mediaId)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.replacePlaylistItem(index, mediaId);
-        }
-
-        @Override
-        public void movePlaylistItem(String controllerId, int fromIdx, int toIdx)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.movePlaylistItem(fromIdx, toIdx);
-        }
-
-        @Override
-        public void skipToPreviousItem(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToPreviousPlaylistItem();
-        }
-
-        @Override
-        public void skipToNextItem(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToNextPlaylistItem();
-        }
-
-        @Override
-        public void skipToPlaylistItem(String controllerId, int index) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToPlaylistItem(index);
-        }
-
-        @Override
-        public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setVolumeTo(value, flags);
-        }
-
-        @Override
-        public void adjustVolume(String controllerId, int direction, int flags)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.adjustVolume(direction, flags);
-        }
-
-        @Override
-        public void sendCustomCommand(String controllerId, ParcelImpl command, Bundle args)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.sendCustomCommand((SessionCommand) MediaParcelUtils.fromParcelable(command),
-                    args);
-        }
-
-        @Override
-        public void fastForward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.fastForward();
-        }
-
-        @Override
-        public void rewind(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.rewind();
-        }
-
-        @Override
-        public void skipForward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipForward();
-        }
-
-        @Override
-        public void skipBackward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipBackward();
-        }
-
-        @Override
-        public void setRating(String controllerId, String mediaId, ParcelImpl rating)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setRating(mediaId, ParcelUtils.<Rating>fromParcelable(rating));
-        }
-
-        @Override
-        public void close(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.close();
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaBrowser methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void getLibraryRoot(String controllerId, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getLibraryRoot((LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void subscribe(String controllerId, String parentId, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.subscribe(parentId,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void unsubscribe(String controllerId, String parentId) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.unsubscribe(parentId);
-        }
-
-        @Override
-        public void getChildren(String controllerId, String parentId, int page, int pageSize,
-                ParcelImpl libraryParams) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getChildren(parentId, page, pageSize,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void getItem(String controllerId, String mediaId) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getItem(mediaId);
-        }
-
-        @Override
-        public void search(String controllerId, String query, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.search(query, (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void getSearchResult(String controllerId, String query, int page, int pageSize,
-                ParcelImpl libraryParams) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getSearchResult(query, page, pageSize,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        private class TestControllerCallback extends MediaBrowser.BrowserCallback {
-            private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-            @Override
-            public void onConnected(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup allowedCommands) {
-                super.onConnected(controller, allowedCommands);
-                mConnectionLatch.countDown();
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java
deleted file mode 100644
index d8a7721..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.KEY_SERVICE_VERSION;
-import static androidx.media2.test.common.CommonConstants.VERSION_TOT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utilities for tests.
- */
-public final class MediaTestUtils {
-
-    // Temporaily commenting out, since we don't have the Mock services yet.
-//    /**
-//     * Finds the session with id in this test package.
-//     *
-//     * @param context
-//     * @param id
-//     * @return
-//     */
-//    public static SessionToken2 getServiceToken(Context context, String id) {
-//        switch (id) {
-//            case MockMediaSessionService.ID:
-//                return new SessionToken2(context, new ComponentName(
-//                        context.getPackageName(), MockMediaSessionService.class.getName()));
-//            case MockMediaLibraryService.ID:
-//                return new SessionToken2(context, new ComponentName(
-//                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-//        }
-//        fail("Unknown id=" + id);
-//        return null;
-//    }
-
-    /**
-     * Create a list of {@link FileMediaItem} for testing purpose.
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size list size
-     * @return the newly created playlist
-     */
-    public static List<MediaItem> createFileMediaItems(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                    .setMetadata(new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                    caller + "_item_" + (i + 1)).build())
-                    .build());
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem createFileMediaItemWithMetadata() {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(createMetadata())
-                .build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
-        return new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId).build();
-    }
-
-    public static List<SessionPlayer.TrackInfo> createTrackInfoList() {
-        List<SessionPlayer.TrackInfo> list = new ArrayList<>();
-        list.add(createTrackInfo(0, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO));
-        list.add(createTrackInfo(1, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO));
-        list.add(createTrackInfo(2, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-        return list;
-    }
-
-    public static SessionPlayer.TrackInfo createTrackInfo(int index, int trackType) {
-        MediaFormat format = null;
-        if (trackType == SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-            format = new MediaFormat();
-            format.setString(MediaFormat.KEY_LANGUAGE, "eng");
-            format.setString(MediaFormat.KEY_MIME, "text/cea-608");
-            format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
-            format.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 0);
-            format.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
-        }
-        return new SessionPlayer.TrackInfo(index, trackType, format);
-    }
-
-    public static List<ParcelImpl> convertToParcelImplList(List<MediaItem> list) {
-        if (list == null) {
-            return null;
-        }
-        List<ParcelImpl> result = new ArrayList<>();
-        for (MediaItem item : list) {
-            result.add(MediaParcelUtils.toParcelable(item));
-        }
-        return result;
-    }
-
-    public static LibraryParams createLibraryParams() {
-        String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
-
-        Bundle extras = new Bundle();
-        extras.putString(callingTestName, callingTestName);
-        return new LibraryParams.Builder().setExtras(extras).build();
-    }
-
-    public static void assertLibraryParamsEquals(LibraryParams a, LibraryParams b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertEquals(a.isRecent(), b.isRecent());
-            assertEquals(a.isOffline(), b.isOffline());
-            assertEquals(a.isSuggested(), b.isSuggested());
-            assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
-        }
-    }
-
-    public static void assertLibraryParamsEquals(LibraryParams params, Bundle rootExtras) {
-        if (params == null || rootExtras == null) {
-            assertEquals(params, rootExtras);
-        } else {
-            assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
-            assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
-            assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
-            assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
-        }
-    }
-
-    public static void assertMediaItemHasId(MediaItem item, String expectedId) {
-        assertNotNull(item);
-        assertNotNull(item.getMetadata());
-        assertEquals(expectedId, item.getMetadata().getString(
-                MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    public static void assertPaginatedListHasIds(List<MediaItem> paginatedList,
-            List<String> fullIdList, int page, int pageSize) {
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, fullIdList.size());
-        // Compare the given results with originals.
-        for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-            int relativeIndex = originalIndex - fromIndex;
-            assertMediaItemHasId(paginatedList.get(relativeIndex), fullIdList.get(originalIndex));
-        }
-    }
-
-    public static void assertMediaIdEquals(MediaItem a, MediaItem b) {
-        assertEquals(a.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
-                b.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    public static void assertMediaIdEquals(List<MediaItem> a, List<MediaItem> b) {
-        assertEquals(a.size(), b.size());
-        for (int i = 0; i < a.size(); i++) {
-            assertMediaIdEquals(a.get(i), b.get(i));
-        }
-    }
-
-    public static void assertNotMediaItemSubclass(List<MediaItem> list) {
-        for (MediaItem item : list) {
-            assertNotMediaItemSubclass(item);
-        }
-    }
-
-    public static void assertNotMediaItemSubclass(MediaItem item) {
-        assertEquals(MediaItem.class, item.getClass());
-    }
-
-    public static boolean isServiceToT() {
-        String serviceVersion = InstrumentationRegistry.getArguments()
-                .getString(KEY_SERVICE_VERSION, "");
-        return VERSION_TOT.equals(serviceVersion);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
deleted file mode 100644
index e9aeac1..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
+++ /dev/null
@@ -1,666 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_SESSION;
-import static androidx.media2.test.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERED_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERING_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MAX_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MEDIA_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_SPEED;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
-import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
-import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
-import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
-import static androidx.media2.test.common.CommonConstants.MEDIA2_SESSION_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaSession} in the service app's MediaSessionProviderService.
- * Users can run {@link MediaSession} methods remotely with this object.
- */
-public class RemoteMediaSession {
-    private static final String TAG = "RemoteMediaSession";
-
-    private final Context mContext;
-    private final String mSessionId;
-    private final Bundle mTokenExtras;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaSession mBinder;
-    private RemoteMockPlayer mRemotePlayer;
-    private CountDownLatch mCountDownLatch;
-
-    /**
-     * Create a {@link MediaSession} in the service app.
-     * Should NOT be called in main thread.
-     */
-    public RemoteMediaSession(@NonNull String sessionId, @NonNull Context context,
-            @Nullable Bundle tokenExtras) {
-        mSessionId = sessionId;
-        mContext = context;
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        mTokenExtras = tokenExtras;
-
-        if (!connect()) {
-            fail("Failed to connect to the MediaSessionProviderService.");
-        }
-        create();
-    }
-
-    public void cleanUp() {
-        close();
-        disconnect();
-    }
-
-    /**
-     * Gets {@link RemoteMockPlayer} for interact with the remote MockPlayer.
-     * Users can run MockPlayer methods remotely with this object.
-     */
-    public RemoteMockPlayer getMockPlayer() {
-        return mRemotePlayer;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaSession methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Gets {@link SessionToken} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link SessionToken} object if succeeded, {@code null} if failed.
-     */
-    public SessionToken getToken() {
-        SessionToken token = null;
-        try {
-            token = MediaParcelUtils.fromParcelable(mBinder.getToken(mSessionId));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionId=" + mSessionId);
-        }
-        return token;
-    }
-
-    /**
-     * Gets {@link MediaSessionCompat.Token} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link SessionToken} object if succeeded, {@code null} if failed.
-     */
-    public MediaSessionCompat.Token getCompatToken() {
-        MediaSessionCompat.Token token = null;
-        try {
-            Bundle bundle = mBinder.getCompatToken(mSessionId);
-            if (bundle != null) {
-                bundle.setClassLoader(MediaSession.class.getClassLoader());
-            }
-            token = MediaSessionCompat.Token.fromBundle(bundle);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session compat token. sessionId=" + mSessionId);
-        }
-        return token;
-    }
-
-    public void updatePlayer(@NonNull Bundle config) {
-        try {
-            mBinder.updatePlayer(mSessionId, config);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call updatePlayerConnector()");
-        }
-    }
-
-    public void broadcastCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.broadcastCustomCommand(mSessionId,
-                    MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call broadcastCustomCommand()");
-        }
-    }
-
-    public void sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.sendCustomCommand(
-                    mSessionId, null, MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomCommand2()");
-        }
-    }
-
-    public void close() {
-        try {
-            mBinder.close(mSessionId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call close()");
-        }
-    }
-
-    public void setAllowedCommands(@NonNull SessionCommandGroup commands) {
-        try {
-            mBinder.setAllowedCommands(mSessionId, null,
-                    MediaParcelUtils.toParcelable(commands));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setAllowedCommands()");
-        }
-    }
-
-    public void setCustomLayout(@NonNull List<CommandButton> layout) {
-        try {
-            List<ParcelImpl> parcelList = new ArrayList<>();
-            for (CommandButton btn : layout) {
-                parcelList.add(MediaParcelUtils.toParcelable(btn));
-            }
-            mBinder.setCustomLayout(mSessionId, null, parcelList);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setCustomLayout()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // RemoteMockPlayer methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public class RemoteMockPlayer {
-
-        public void setPlayerState(int state) {
-            try {
-                mBinder.setPlayerState(mSessionId, state);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentPosition()");
-            }
-        }
-
-        public void setCurrentPosition(long pos) {
-            try {
-                mBinder.setCurrentPosition(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentPosition()");
-            }
-        }
-
-        public void setBufferedPosition(long pos) {
-            try {
-                mBinder.setBufferedPosition(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setBufferedPosition()");
-            }
-        }
-
-        public void setDuration(long duration) {
-            try {
-                mBinder.setDuration(mSessionId, duration);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setDuration()");
-            }
-        }
-
-        public void setPlaybackSpeed(float speed) {
-            try {
-                mBinder.setPlaybackSpeed(mSessionId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaybackSpeed()");
-            }
-        }
-
-        public void notifySeekCompleted(long pos) {
-            try {
-                mBinder.notifySeekCompleted(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifySeekCompleted()");
-            }
-        }
-
-        public void notifyBufferingStateChanged(int itemIndex, int buffState) {
-            try {
-                mBinder.notifyBufferingStateChanged(mSessionId, itemIndex, buffState);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyBufferingStateChanged()");
-            }
-        }
-
-        public void notifyPlayerStateChanged(int state) {
-            try {
-                mBinder.notifyPlayerStateChanged(mSessionId, state);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlayerStateChanged()");
-            }
-        }
-
-        public void notifyPlaybackSpeedChanged(float speed) {
-            try {
-                mBinder.notifyPlaybackSpeedChanged(mSessionId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaybackSpeedChanged()");
-            }
-        }
-
-        public void notifyCurrentMediaItemChanged(int index) {
-            try {
-                mBinder.notifyCurrentMediaItemChanged(mSessionId, index);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyCurrentMediaItemChanged()");
-            }
-        }
-
-        public void notifyAudioAttributesChanged(AudioAttributesCompat attrs) {
-            try {
-                mBinder.notifyAudioAttributesChanged(
-                        mSessionId, MediaParcelUtils.toParcelable(attrs));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyAudioAttributesChanged()");
-            }
-        }
-
-        public void setPlaylist(List<MediaItem> playlist) {
-            try {
-                mBinder.setPlaylist(
-                        mSessionId, MediaTestUtils.convertToParcelImplList(playlist));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylist()");
-            }
-        }
-
-        public void setCurrentMediaItemMetadata(MediaMetadata metadata) {
-            try {
-                mBinder.setCurrentMediaItemMetadata(
-                        mSessionId, MediaParcelUtils.toParcelable(metadata));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentMediaItemMetadata()");
-            }
-        }
-
-        /**
-         * Service app will automatically create a playlist of size {@param size},
-         * and sets the list to the player.
-         *
-         * Each item's media ID will be {@link TestUtils#getMediaIdInFakeList(int)}.
-         */
-        public void createAndSetFakePlaylist(int size) {
-            try {
-                mBinder.createAndSetFakePlaylist(mSessionId, size);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call createAndSetFakePlaylist()");
-            }
-        }
-
-        public void setPlaylistWithFakeItem(List<MediaItem> playlist) {
-            try {
-                mBinder.setPlaylistWithFakeItem(
-                        mSessionId, MediaTestUtils.convertToParcelImplList(playlist));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistWithFakeItem()");
-            }
-        }
-
-        public void setPlaylistMetadata(MediaMetadata metadata) {
-            try {
-                mBinder.setPlaylistMetadata(mSessionId, MediaParcelUtils.toParcelable(metadata));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistMetadata()");
-            }
-        }
-
-        public void setPlaylistMetadataWithLargeBitmaps(int count, int width, int height) {
-            try {
-                mBinder.setPlaylistMetadataWithLargeBitmaps(mSessionId, count, width, height);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistMetadataWithLargeBitmaps()");
-            }
-        }
-
-        public void setRepeatMode(int repeatMode) {
-            try {
-                mBinder.setRepeatMode(mSessionId, repeatMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRepeatMode()");
-            }
-        }
-
-        public void setShuffleMode(int shuffleMode) {
-            try {
-                mBinder.setShuffleMode(mSessionId, shuffleMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setShuffleMode()");
-            }
-        }
-
-        public void setCurrentMediaItem(int index) {
-            try {
-                mBinder.setCurrentMediaItem(mSessionId, index);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentMediaItem()");
-            }
-        }
-
-        public void notifyPlaylistChanged() {
-            try {
-                mBinder.notifyPlaylistChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaylistChanged()");
-            }
-        }
-
-        public void notifyPlaylistMetadataChanged() {
-            try {
-                mBinder.notifyPlaylistMetadataChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaylistMetadataChanged()");
-            }
-        }
-
-        public void notifyShuffleModeChanged() {
-            try {
-                mBinder.notifyShuffleModeChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyShuffleModeChanged()");
-            }
-        }
-
-        public void notifyRepeatModeChanged() {
-            try {
-                mBinder.notifyRepeatModeChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyRepeatModeChanged()");
-            }
-        }
-
-        public void notifyPlaybackCompleted() {
-            try {
-                mBinder.notifyPlaybackCompleted(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaybackCompleted()");
-            }
-        }
-
-        public void notifyVideoSizeChanged(@NonNull VideoSize videoSize) {
-            try {
-                mBinder.notifyVideoSizeChanged(mSessionId,
-                        MediaParcelUtils.toParcelable(videoSize));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyVideoSizeChanged()");
-            }
-        }
-
-        public boolean surfaceExists() throws RemoteException {
-            return mBinder.surfaceExists(mSessionId);
-        }
-
-        public void notifyTracksChanged(List<SessionPlayer.TrackInfo> tracks) {
-            try {
-                mBinder.notifyTrackInfoChanged(mSessionId,
-                        MediaParcelUtils.toParcelableList(tracks));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackInfoChanged()");
-            }
-        }
-
-        public void notifyTrackSelected(SessionPlayer.TrackInfo trackInfo) {
-            try {
-                mBinder.notifyTrackSelected(mSessionId,
-                        MediaParcelUtils.toParcelable(trackInfo));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackSelected()");
-            }
-        }
-
-        public void notifyTrackDeselected(SessionPlayer.TrackInfo trackInfo) {
-            try {
-                mBinder.notifyTrackDeselected(mSessionId,
-                        MediaParcelUtils.toParcelable(trackInfo));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackDeselected()");
-            }
-        }
-
-        public void notifySubtitleData(@NonNull MediaItem item,
-                @NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
-            try {
-                mBinder.notifySubtitleData(mSessionId,
-                        MediaParcelUtils.toParcelable(item),
-                        MediaParcelUtils.toParcelable(track),
-                        MediaParcelUtils.toParcelable(data));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifySubtitleData");
-            }
-        }
-
-        public void notifyVolumeChanged(int volume) throws RemoteException {
-            mBinder.notifyVolumeChanged(mSessionId, volume);
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to service app's MediaSessionProviderService.
-     * Should NOT be called in main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA2_SESSION);
-        intent.setComponent(MEDIA2_SESSION_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed binding to the MediaSessionProviderService of the service app");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from service app's MediaSessionProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-        }
-        mServiceConnection = null;
-    }
-
-    /**
-     * Create a {@link MediaSession} in the service app.
-     * Should be used after successful connection through {@link #connect}.
-     */
-    private void create() {
-        try {
-            mBinder.create(mSessionId, mTokenExtras);
-            mRemotePlayer = new RemoteMockPlayer();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionId=" + mSessionId);
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to service app's MediaSessionProviderService.");
-            mBinder = IRemoteMediaSession.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from the service.");
-        }
-    }
-
-    /**
-     * Builder to build a {@link Bundle} which represents a configuration of {@link SessionPlayer}
-     * in order to create a new mock player in the service app. The bundle can be passed to
-     * {@link #updatePlayer(Bundle)}.
-     */
-    public static final class MockPlayerConfigBuilder {
-
-        private final Bundle mBundle;
-
-        public MockPlayerConfigBuilder() {
-            mBundle = new Bundle();
-        }
-
-        public MockPlayerConfigBuilder setPlayerState(@SessionPlayer.PlayerState int state) {
-            mBundle.putInt(KEY_PLAYER_STATE, state);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setBufferingState(@SessionPlayer.BuffState int buffState) {
-            mBundle.putInt(KEY_BUFFERING_STATE, buffState);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentPosition(long pos) {
-            mBundle.putLong(KEY_CURRENT_POSITION, pos);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setBufferedPosition(long buffPos) {
-            mBundle.putLong(KEY_BUFFERED_POSITION, buffPos);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaybackSpeed(float speed) {
-            mBundle.putFloat(KEY_PLAYBACK_SPEED, speed);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setAudioAttributes(
-                @Nullable AudioAttributesCompat audioAttributes) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(audioAttributes);
-            mBundle.putParcelable(KEY_AUDIO_ATTRIBUTES, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaylist(@NonNull List<MediaItem> playlist) {
-            ParcelImplListSlice listSlice = new ParcelImplListSlice(
-                    MediaTestUtils.convertToParcelImplList(playlist));
-            mBundle.putParcelable(KEY_PLAYLIST, listSlice);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaylistMetadata(@Nullable MediaMetadata metadata) {
-            ParcelUtils.putVersionedParcelable(mBundle, KEY_PLAYLIST_METADATA, metadata);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentMediaItem(@Nullable MediaItem item) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(item);
-            mBundle.putParcelable(KEY_MEDIA_ITEM, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setVideoSize(@NonNull VideoSize videoSize) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(videoSize);
-            mBundle.putParcelable(KEY_VIDEO_SIZE, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setTrackInfo(@NonNull List<SessionPlayer.TrackInfo> tracks) {
-            ParcelUtils.putVersionedParcelableList(mBundle, KEY_TRACK_INFO, tracks);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setVolumeControlType(
-                @RemoteSessionPlayer.VolumeControlType int volumeControlType) {
-            mBundle.putInt(KEY_VOLUME_CONTROL_TYPE, volumeControlType);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setMaxVolume(int maxVolume) {
-            mBundle.putInt(KEY_MAX_VOLUME, maxVolume);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentVolume(int currentVolume) {
-            mBundle.putInt(KEY_CURRENT_VOLUME, currentVolume);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setShuffleMode(int shuffleMode) {
-            mBundle.putInt(KEY_SHUFFLE_MODE, shuffleMode);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setRepeatMode(int repeatMode) {
-            mBundle.putInt(KEY_REPEAT_MODE, repeatMode);
-            return this;
-        }
-
-        public Bundle build() {
-            return mBundle;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
deleted file mode 100644
index 9f6b7ce..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_METADATA_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_QUEUE;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-import static androidx.media2.test.common.CommonConstants.MEDIA_SESSION_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.test.common.IRemoteMediaSessionCompat;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaSessionCompat} in the service app's
- * MediaSessionCompatProviderService.
- * Users can run {@link MediaSessionCompat} methods remotely with this object.
- */
-public class RemoteMediaSessionCompat {
-    private static final String TAG = "RemoteMediaSessionCompat";
-
-    private final Context mContext;
-    private final String mSessionTag;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaSessionCompat mBinder;
-    private final CountDownLatch mCountDownLatch;
-
-    /**
-     * Create a {@link MediaSessionCompat} in the service app.
-     * Should NOT be called in main thread.
-     */
-    public RemoteMediaSessionCompat(@NonNull String sessionTag, Context context) {
-        mSessionTag = sessionTag;
-        mContext = context;
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-
-        if (!connect()) {
-            fail("Failed to connect to the MediaSessionCompatProviderService.");
-        }
-        create();
-    }
-
-    public void cleanUp() {
-        release();
-        disconnect();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaSessionCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Gets {@link MediaSessionCompat.Token} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link MediaSessionCompat.Token} object if succeeded, {@code null} if failed.
-     */
-    @SuppressWarnings("deprecation")
-    public MediaSessionCompat.Token getSessionToken() {
-        MediaSessionCompat.Token token = null;
-        try {
-            Bundle bundle = mBinder.getSessionToken(mSessionTag);
-            if (bundle != null) {
-                bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-                token = bundle.getParcelable(KEY_SESSION_COMPAT_TOKEN);
-            }
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionTag=" + mSessionTag);
-        }
-        return token;
-    }
-
-    public void release() {
-        try {
-            mBinder.release(mSessionTag);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call release()");
-        }
-    }
-
-    public void setPlaybackToLocal(int stream) {
-        try {
-            mBinder.setPlaybackToLocal(mSessionTag, stream);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackToLocal()");
-        }
-    }
-
-    /**
-     * Since we cannot pass VolumeProviderCompat directly,
-     * we pass volumeControl, maxVolume, currentVolume instead.
-     */
-    public void setPlaybackToRemote(int volumeControl, int maxVolume, int currentVolume) {
-        try {
-            mBinder.setPlaybackToRemote(mSessionTag, volumeControl, maxVolume, currentVolume);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackToRemote()");
-        }
-    }
-
-    public void setPlaybackState(PlaybackStateCompat state) {
-        try {
-            mBinder.setPlaybackState(mSessionTag,
-                    createBundleWithParcelable(KEY_PLAYBACK_STATE_COMPAT, state));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackState()");
-        }
-    }
-
-    public void setMetadata(MediaMetadataCompat metadata) {
-        try {
-            mBinder.setMetadata(mSessionTag,
-                    createBundleWithParcelable(KEY_METADATA_COMPAT, metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMetadata()");
-        }
-    }
-
-    public void setQueue(List<QueueItem> queue) {
-        try {
-            Bundle bundle = new Bundle();
-            ArrayList<QueueItem> queueAsArrayList = new ArrayList<>(queue);
-            bundle.putParcelableArrayList(KEY_QUEUE, queueAsArrayList);
-            mBinder.setQueue(mSessionTag, bundle);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setQueue()");
-        }
-    }
-
-    public void setQueueTitle(CharSequence title) {
-        try {
-            mBinder.setQueueTitle(mSessionTag, title);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setQueueTitle()");
-        }
-    }
-
-    public void setRepeatMode(int repeatMode) {
-        try {
-            mBinder.setRepeatMode(mSessionTag, repeatMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRepeatMode()");
-        }
-    }
-
-    public void setShuffleMode(int shuffleMode) {
-        try {
-            mBinder.setShuffleMode(mSessionTag, shuffleMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setShuffleMode()");
-        }
-    }
-
-    public void setSessionActivity(PendingIntent intent) {
-        try {
-            mBinder.setSessionActivity(mSessionTag, intent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setSessionActivity()");
-        }
-    }
-
-    public void setFlags(int flags) {
-        try {
-            mBinder.setFlags(mSessionTag, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setFlags()");
-        }
-    }
-
-    public void setRatingType(int type) {
-        try {
-            mBinder.setRatingType(mSessionTag, type);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRatingType()");
-        }
-    }
-
-    public void sendSessionEvent(String event, Bundle extras) {
-        try {
-            mBinder.sendSessionEvent(mSessionTag, event, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendSessionEvent()");
-        }
-    }
-
-    public void setCaptioningEnabled(boolean enabled) {
-        try {
-            mBinder.setCaptioningEnabled(mSessionTag, enabled);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setCaptioningEnabled()");
-        }
-    }
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to service app's MediaSessionCompatProviderService.
-     * Should NOT be called in main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA_SESSION_COMPAT);
-        intent.setComponent(MEDIA_SESSION_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed binding to the MediaSessionCompatProviderService of the "
-                    + "service app");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from service app's MediaSessionCompatProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-        }
-        mServiceConnection = null;
-    }
-
-    /**
-     * Create a {@link MediaSessionCompat} in the service app.
-     * Should be used after successful connection through {@link #connect}.
-     */
-    private void create() {
-        try {
-            mBinder.create(mSessionTag);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionTag=" + mSessionTag);
-        }
-    }
-
-    private Bundle createBundleWithParcelable(String key, Parcelable value) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(key, value);
-        return bundle;
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to service app's MediaSessionCompatProviderService.");
-            mBinder = IRemoteMediaSessionCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from the service.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
deleted file mode 100644
index d2e6d81..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
+++ /dev/null
@@ -1,41 +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.media2.test.client;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.media2.test.client.test.R;
-
-public class SurfaceActivity extends Activity {
-    private SurfaceHolder mHolder;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_surface);
-
-        SurfaceView surfaceView = findViewById(R.id.surface_view);
-        mHolder = surfaceView.getHolder();
-    }
-
-    public SurfaceHolder getSurfaceHolder() {
-        return mHolder;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
deleted file mode 100644
index 444fc6f..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assume.assumeTrue;
-
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.LibraryResult;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.common.MediaBrowserConstants;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaBrowser.BrowserCallback}.
- * <p>
- * This test inherits {@link MediaControllerCallbackTest} to ensure that inherited APIs from
- * {@link MediaController} works cleanly.
- */
-// TODO: (internal cleanup) Move tests that aren't related with callbacks.
-@FlakyTest(bugId = 202942942)
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaBrowserCallbackTest extends MediaControllerCallbackTest {
-    private static final String TAG = "MediaBrowserCallbackTest";
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-    }
-
-    @Override
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints, @Nullable final TestBrowserCallback callback)
-            throws InterruptedException {
-        assertNotNull("Test bug", token);
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaBrowser.Builder builder = new MediaBrowser.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-
-    final MediaBrowser createBrowser() throws InterruptedException {
-        return createBrowser(null, null);
-    }
-
-    final MediaBrowser createBrowser(@Nullable Bundle connectionHints,
-            @Nullable BrowserCallback callback) throws InterruptedException {
-        final SessionToken token = new SessionToken(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-        return (MediaBrowser) createController(token, true, connectionHints, callback);
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void getLibraryRoot() throws Exception {
-        final LibraryParams params = new LibraryParams.Builder()
-                .setOffline(true).setRecent(true).setExtras(new Bundle()).build();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-        LibraryResult result = browser.getLibraryRoot(params)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaMetadata metadata = result.getMediaItem().getMetadata();
-        assertEquals(ROOT_ID, metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-        assertTrue(TestUtils.equals(ROOT_EXTRAS, result.getLibraryParams().getExtras()));
-    }
-
-    @Test
-    public void getItem() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-
-        LibraryResult result = createBrowser().getItem(mediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaTestUtils.assertMediaItemHasId(result.getMediaItem(), mediaId);
-    }
-
-    @Test
-    public void getItem_unknownId() throws Exception {
-        final String mediaId = "random_media_id";
-
-        LibraryResult result = createBrowser().getItem(mediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_ERROR_BAD_VALUE, result.getResultCode());
-        assertNull(result.getMediaItem());
-    }
-
-    @Test
-    public void getItem_nullResult() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
-
-        try {
-            LibraryResult result = createBrowser().getItem(mediaId)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        } catch (TimeoutException e) {
-            // May happen.
-        }
-    }
-
-    @Test
-    public void getItem_invalidResult() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
-
-        try {
-            LibraryResult result = createBrowser().getItem(mediaId)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        } catch (TimeoutException e) {
-            // May happen.
-        }
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void getChildren() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID;
-        final int page = 4;
-        final int pageSize = 10;
-        final LibraryParams params = MediaTestUtils.createLibraryParams();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-
-        LibraryResult result = browser.getChildren(parentId, page, pageSize, params)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getLibraryParams());
-
-        MediaTestUtils.assertPaginatedListHasIds(
-                result.getMediaItems(), MediaBrowserConstants.GET_CHILDREN_RESULT,
-                page, pageSize);
-    }
-
-    @Test
-    @LargeTest
-    public void getChildren_withLongList() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final LibraryParams params = MediaTestUtils.createLibraryParams();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-
-        LibraryResult result = browser.getChildren(parentId, page, pageSize, params)
-                .get(10, TimeUnit.SECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getLibraryParams());
-
-        List<MediaItem> list = result.getMediaItems();
-        assertEquals(LONG_LIST_COUNT, list.size());
-        for (int i = 0; i < result.getMediaItems().size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), list.get(i).getMediaId());
-        }
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void getChildren_emptyResult() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
-
-        MediaBrowser browser = createBrowser();
-        LibraryResult result = browser.getChildren(parentId, 1, 1, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertEquals(0, result.getMediaItems().size());
-    }
-
-    @Test
-    public void getChildren_nullResult() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
-
-        MediaBrowser browser = createBrowser();
-        LibraryResult result = browser.getChildren(parentId, 1, 1, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getMediaItems());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void searchCallbacks() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY;
-        final int page = 4;
-        final int pageSize = 10;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latchForSearch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(MediaBrowser browser,
-                    String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.SEARCH_RESULT_COUNT, itemCount);
-                latchForSearch.countDown();
-            }
-        };
-
-        // Request the search.
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // Get the search result.
-        result = browser.getSearchResult(query, page, pageSize, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaTestUtils.assertPaginatedListHasIds(result.getMediaItems(),
-                MediaBrowserConstants.SEARCH_RESULT, page, pageSize);
-    }
-
-    @Test
-    @LargeTest
-    public void searchCallbacks_withLongList() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.LONG_LIST_COUNT, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        result = browser.getSearchResult(query, page, pageSize, testParams)
-                .get(10, TimeUnit.SECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        List<MediaItem> list = result.getMediaItems();
-        for (int i = 0; i < list.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), list.get(i).getMediaId());
-        }
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    @LargeTest
-    public void onSearchResultChanged_searchTakesTime() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.SEARCH_RESULT_COUNT, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(MediaBrowserConstants.SEARCH_TIME_IN_MS + TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void onSearchResultChanged_emptyResult() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(0, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void onChildrenChanged_calledWhenSubscribed() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged().
-        final String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                assertEquals(expectedParentId, parentId);
-                assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
-                MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(expectedParentId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged() in its callback onSubscribe().
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
-        final String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                assertEquals(expectedParentId, parentId);
-                assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
-                MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(expectedParentId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe().
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged().
-        final String subscribedMediaId =
-                SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                // Unexpected call.
-                fail();
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(subscribedMediaId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged() in its callback onSubscribe(), but with a different media ID.
-        // Therefore, onChildrenChanged() should not be called.
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onChildrenChanged_notCalledWhenNotSubscribed2() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
-        final String subscribedMediaId =
-                SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                // Unexpected call.
-                fail();
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(subscribedMediaId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe(),
-        // but with a different media ID.
-        // Therefore, onChildrenChanged() should not be called.
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void setExpectedLibraryParam(MediaBrowser browser, LibraryParams params)
-            throws Exception {
-        SessionCommand command = new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null);
-        Bundle args = new Bundle();
-        ParcelUtils.putVersionedParcelable(args, CUSTOM_ACTION_ASSERT_PARAMS, params);
-        SessionResult result = browser.sendCustomCommand(command, args)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java
deleted file mode 100644
index 78dd71b..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaBrowserConstants.CHILDREN_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.CustomActionCallback;
-import android.support.v4.media.MediaBrowserCompat.ItemCallback;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaBrowserCompat.SearchCallback;
-import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaBrowserCompat} works well with {@link MediaLibraryService}.
- */
-@LargeTest
-public class MediaBrowserCompatWithMediaLibraryServiceTest extends
-        MediaBrowserCompatWithMediaSessionServiceTest {
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Override
-    ComponentName getServiceComponent() {
-        return MOCK_MEDIA2_LIBRARY_SERVICE;
-    }
-
-    @Test
-    public void getRoot() throws InterruptedException {
-        // The MockMediaLibraryService gives MediaBrowserConstants.ROOT_ID as root ID, and
-        // MediaBrowserConstants.ROOT_EXTRAS as extras.
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mBrowserCompat = new MediaBrowserCompat(mContext, getServiceComponent(),
-                        mConnectionCallback, null /* rootHint */);
-            }
-        });
-        connectAndWait();
-        assertEquals(ROOT_ID, mBrowserCompat.getRoot());
-
-        // Note: Cannot use equals() here because browser compat's extra contains server version,
-        // extra binder, and extra messenger.
-        assertTrue(TestUtils.contains(mBrowserCompat.getExtras(), ROOT_EXTRAS));
-    }
-
-    @Test
-    public void getItem() throws InterruptedException {
-        final String mediaId = MEDIA_ID_GET_ITEM;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.getItem(mediaId, new ItemCallback() {
-            @Override
-            public void onItemLoaded(MediaItem item) {
-                assertEquals(mediaId, item.getMediaId());
-                assertNotNull(item);
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getItem_nullResult() throws InterruptedException {
-        final String mediaId = "random_media_id";
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.getItem(mediaId, new ItemCallback() {
-            @Override
-            public void onItemLoaded(MediaItem item) {
-                assertNull(item);
-                latch.countDown();
-            }
-
-            @Override
-            public void onError(@NonNull String itemId) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren() throws InterruptedException {
-        final String testParentId = PARENT_ID;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertEquals(testParentId, parentId);
-                assertNotNull(children);
-                assertEquals(GET_CHILDREN_RESULT.size(), children.size());
-
-                // Compare the given results with originals.
-                for (int i = 0; i < children.size(); i++) {
-                    assertEquals(GET_CHILDREN_RESULT.get(i), children.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle option) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_withLongList() throws InterruptedException {
-        final String testParentId = PARENT_ID_LONG_LIST;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertEquals(testParentId, parentId);
-                assertNotNull(children);
-                assertTrue(children.size() < LONG_LIST_COUNT);
-
-                // Compare the given results with originals.
-                for (int i = 0; i < children.size(); i++) {
-                    assertEquals(TestUtils.getMediaIdInFakeList(i), children.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle option) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void getChildren_withPagination() throws InterruptedException {
-        final String testParentId = PARENT_ID;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle extras = new Bundle();
-        extras.putString(testParentId, testParentId);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final Bundle option = new Bundle();
-        option.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        option.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-        mBrowserCompat.subscribe(testParentId, option, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(page, option.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(pageSize, option.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertNotNull(children);
-
-                int fromIndex = page * pageSize;
-                int toIndex = Math.min((page + 1) * pageSize, CHILDREN_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    assertEquals(GET_CHILDREN_RESULT.get(originalIndex),
-                            children.get(relativeIndex).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_emptyResult() throws InterruptedException {
-        final String testParentId = PARENT_ID_NO_CHILDREN;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertNotNull(children);
-                assertEquals(0, children.size());
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_nullResult() throws InterruptedException {
-        final String testParentId = PARENT_ID_ERROR;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onError(@NonNull String parentId) {
-                assertEquals(testParentId, parentId);
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle options) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                int expectedSize = Math.max(
-                        Math.min(pageSize, SEARCH_RESULT_COUNT - pageSize * page),
-                        0);
-                assertEquals(expectedSize, items.size());
-
-                int fromIndex = page * pageSize;
-                int toIndex = Math.min((page + 1) * pageSize, SEARCH_RESULT_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    assertEquals(
-                            SEARCH_RESULT.get(originalIndex),
-                            items.get(relativeIndex).getMediaId());
-                }
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search_withLongList() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-
-                assertNotNull(items);
-                assertTrue(items.size() < LONG_LIST_COUNT);
-                for (int i = 0; i < items.size(); i++) {
-                    assertEquals(TestUtils.getMediaIdInFakeList(i), items.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void search_emptyResult() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_EMPTY_RESULT;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                assertNotNull(items);
-                assertEquals(0, items.size());
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search_error() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_ERROR;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onError(@NonNull String query, Bundle extras) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                latch.countDown();
-            }
-
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe() throws InterruptedException {
-//        final String testParentId = "testSubscribeId";
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(0, page);
-//                assertEquals(Integer.MAX_VALUE, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                assertMediaItemListEquals(testList, children);
-//                latch.countDown();
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe_withExtras() throws InterruptedException {
-//        final String testParentId = "testSubscribe_withExtras";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(0, page);
-//                assertEquals(Integer.MAX_VALUE, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                assertMediaItemListEquals(testList, children);
-//                assertTrue(TestUtils.equals(testExtras, options));
-//                latch.countDown();
-//                super.onChildrenLoaded(parentId, children, options);
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe_withPagination() throws InterruptedException {
-//        final String testParentId = "testSubscribe_pagination_ID";
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//        final int testPage = 2;
-//        final int testPageSize = 3;
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, testPage);
-//        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, testPageSize);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(testPage, page);
-//                assertEquals(testPageSize, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                fail();
-//            }
-//
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                assertEquals(testParentId, parentId);
-//                assertMediaItemListEquals(testList, children);
-//                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-//                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-//                latch.countDown();
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//
-//            @Override
-//            public void onError(String parentId, Bundle options) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribeAndUnsubscribe() throws InterruptedException {
-//        final String testParentId = "testUnsubscribe";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//
-//        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-//        final CountDownLatch unsubscribeLatch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                    subscribeLatch.countDown();
-//                }
-//            }
-//
-//            @Override
-//            public void onUnsubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    unsubscribeLatch.countDown();
-//                }
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {});
-//        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-//        // Subscription is needed for MediaBrowserCompat to send unsubscribe request.
-//        mBrowserCompat.unsubscribe(testParentId);
-//        assertTrue(unsubscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Split this test to here and MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void notifyChildrenChanged() throws InterruptedException {
-//        final String testSubscribedParentId = "testNotifyChildrenChanged";
-//        final String testUnsubscribedParentId = "testNotifyChildrenChanged22";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testSubscribedParentId, testSubscribedParentId);
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    subscribeLatch.countDown();
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller, String parentId, int page, int pageSize,
-//                    Bundle extras) {
-//                assertEquals(testSubscribedParentId, parentId);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        final CountDownLatch onChildrenLoadedLatch = new CountDownLatch(2);
-//        mBrowserCompat.subscribe(testSubscribedParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                assertEquals(testSubscribedParentId, parentId);
-//                onChildrenLoadedLatch.countDown();
-//            }
-//
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                super.onChildrenLoaded(parentId, children, options);
-//            }
-//        });
-//        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-//        MediaLibrarySession librarySession = (MediaLibrarySession)
-//                TestServiceRegistry.getInstance().getServiceInstance().getSession();
-//        librarySession.notifyChildrenChanged(testSubscribedParentId, testList.size(), null);
-//        librarySession.notifyChildrenChanged(testUnsubscribedParentId, testList.size(), null);
-//        assertFalse(onChildrenLoadedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
-    @Test
-    public void customAction() throws InterruptedException {
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args_key", "args_value");
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.sendCustomAction(CUSTOM_ACTION, testArgs, new CustomActionCallback() {
-            @Override
-            public void onResult(String action, Bundle extras, Bundle resultData) {
-                assertEquals(CUSTOM_ACTION, action);
-                assertTrue(TestUtils.equals(testArgs, extras));
-                assertTrue(TestUtils.equals(CUSTOM_ACTION_EXTRAS, resultData));
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
-    @Test
-    public void customAction_rejected() throws InterruptedException {
-        // This action will not be allowed by the library session.
-        final String testAction = "random_custom_action";
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.sendCustomAction(testAction, null, new CustomActionCallback() {
-            @Override
-            public void onResult(String action, Bundle extras, Bundle resultData) {
-                latch.countDown();
-            }
-        });
-        assertFalse("BrowserCompat shouldn't receive custom command",
-                latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java
deleted file mode 100644
index f9872bc..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-
-import androidx.media2.session.MediaSessionService;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaBrowserCompat} works well with {@link MediaSessionService}.
- */
-@FlakyTest(bugId = 202942942)
-@LargeTest
-public class MediaBrowserCompatWithMediaSessionServiceTest extends MediaSessionTestBase {
-    MediaBrowserCompat mBrowserCompat;
-    TestConnectionCallback mConnectionCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        // Ignore all tests, see b/236961183
-        assumeTrue(false);
-        super.setUp();
-        mConnectionCallback = new TestConnectionCallback();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Make browser's internal handler to be initialized with test thread.
-                mBrowserCompat = new MediaBrowserCompat(
-                        mContext, getServiceComponent(), mConnectionCallback, null);
-            }
-        });
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mBrowserCompat != null) {
-            mBrowserCompat.disconnect();
-            mBrowserCompat = null;
-        }
-    }
-
-    ComponentName getServiceComponent() {
-        return MOCK_MEDIA2_SESSION_SERVICE;
-    }
-
-    void connectAndWait() throws InterruptedException {
-        mBrowserCompat.connect();
-        assertTrue(mConnectionCallback.mConnectedLatch.await(
-                BROWSER_COMPAT_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void connect() throws InterruptedException {
-        connectAndWait();
-        assertNotEquals(0, mConnectionCallback.mFailedLatch.getCount());
-    }
-
-    @Ignore
-    @Test
-    public void connect_rejected() throws InterruptedException {
-        // TODO: Connect the browser to the session service whose onConnect() returns null.
-        assertTrue(mConnectionCallback.mFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNotEquals(0, mConnectionCallback.mConnectedLatch.getCount());
-    }
-
-    @Test
-    public void getSessionToken() throws Exception {
-        connectAndWait();
-        MediaControllerCompat controller = new MediaControllerCompat(mContext,
-                mBrowserCompat.getSessionToken());
-        assertEquals(mBrowserCompat.getServiceComponent().getPackageName(),
-                controller.getPackageName());
-    }
-
-    class TestConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        public final CountDownLatch mConnectedLatch = new CountDownLatch(1);
-        public final CountDownLatch mSuspendedLatch = new CountDownLatch(1);
-        public final CountDownLatch mFailedLatch = new CountDownLatch(1);
-
-        TestConnectionCallback() {
-            super();
-        }
-
-        @Override
-        public void onConnected() {
-            super.onConnected();
-            mConnectedLatch.countDown();
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            super.onConnectionSuspended();
-            mSuspendedLatch.countDown();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            super.onConnectionFailed();
-            mFailedLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java
deleted file mode 100644
index 305be10..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.SessionToken;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaBrowser}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaBrowserTest extends MediaControllerTest {
-
-    @Override
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints,
-            @NonNull final TestBrowserCallback callback) throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaBrowser.Builder builder = new MediaBrowser.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-
-    /**
-     * Test if the {@link TestBrowserCallback} wraps the callback proxy without missing any method.
-     */
-    @Test
-    public void testBrowserCallback() {
-        Method[] methods = TestBrowserCallback.class.getMethods();
-        assertNotNull(methods);
-        for (int i = 0; i < methods.length; i++) {
-            // For any methods in the controller callback, TestBrowserCallback should have
-            // overridden the method and call matching API in the callback proxy.
-            assertNotEquals("TestBrowserCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    BrowserCallback.class, methods[i].getDeclaringClass());
-            assertNotEquals("TestBrowserCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    ControllerCallback.class, methods[i].getDeclaringClass());
-        }
-    }
-
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
deleted file mode 100644
index 373cdc5..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
-import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_608;
-
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_NULL_ITEM;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController.ControllerCallback}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerCallbackTest extends MediaSessionTestBase {
-
-    RemoteMediaSession mRemoteSession2;
-    MediaController mController;
-
-    final List<RemoteMediaSession> mRemoteSessionList = new ArrayList<>();
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mRemoteSession2 = createRemoteMediaSession(DEFAULT_TEST_NAME);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        for (int i = 0; i < mRemoteSessionList.size(); i++) {
-            RemoteMediaSession session = mRemoteSessionList.get(i);
-            if (session != null) {
-                session.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void connection_sessionAccepts() throws InterruptedException {
-        // createController() uses controller callback to wait until the controller becomes
-        // available.
-        MediaController controller = createController(mRemoteSession2.getToken());
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void connection_sessionRejects() throws InterruptedException {
-        RemoteMediaSession session =
-                createRemoteMediaSession(TEST_CONTROLLER_CALLBACK_SESSION_REJECTS);
-
-        MediaController controller = createController(session.getToken(),
-                false /* waitForConnect */, null, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false /* expected */);
-        waitForDisconnect(controller, true /* expected */);
-
-        session.cleanUp();
-    }
-
-    @Test
-    public void connection_toLibraryService() throws InterruptedException {
-        // See b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        SessionToken token = new SessionToken(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-        MediaController controller = createController(token);
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void connection_sessionClosed() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession2.getToken());
-
-        mRemoteSession2.close();
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void connection_controllerClosed() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession2.getToken());
-
-        controller.close();
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    @LargeTest
-    public void noInteractionAfterSessionClose_session() throws InterruptedException {
-        SessionToken token = mRemoteSession2.getToken();
-        mController = createController(token);
-        testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
-    }
-
-    @Test
-    @LargeTest
-    public void noInteractionAfterControllerClose_session() throws InterruptedException {
-        final SessionToken token = mRemoteSession2.getToken();
-        mController = createController(token);
-
-        mController.close();
-        // close is done immediately for session.
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
-    }
-
-    @Test
-    @LargeTest
-    public void connection_withLongPlaylist() throws InterruptedException {
-        // See b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final int playlistSize = 5000;
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(playlistSize);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mRemoteSession2.getToken())
-                .setControllerCallback(sHandlerExecutor, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onConnected(@NonNull MediaController controller,
-                            @NonNull SessionCommandGroup allowedCommands) {
-                        super.onConnected(controller, allowedCommands);
-                        latch.countDown();
-                    }
-                })
-                .build();
-        assertNotNull(controller);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-
-        // After connection, getPlaylist() should return the playlist which is set to the player.
-        List<MediaItem> playlist = controller.getPlaylist();
-        assertNotNull(playlist);
-        assertEquals(playlistSize, playlist.size());
-        for (int i = 0; i < playlist.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), playlist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void controllerCallback_sessionUpdatePlayer() throws InterruptedException {
-        final int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_RING).build();
-        final CountDownLatch latch = new CountDownLatch(3);
-        mController = createController(mRemoteSession2.getToken(),
-                true /* waitForConnect */, null, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlayerStateChanged(@NonNull MediaController controller,
-                            int state) {
-                        assertEquals(mController, controller);
-                        assertEquals(testState, state);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> list, MediaMetadata metadata) {
-                        assertEquals(mController, controller);
-                        MediaTestUtils.assertNotMediaItemSubclass(list);
-                        MediaTestUtils.assertMediaIdEquals(testPlaylist, list);
-                        assertNull(metadata);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull MediaController.PlaybackInfo info) {
-                        assertEquals(mController, controller);
-                        assertEquals(testAudioAttributes, info.getAudioAttributes());
-                        latch.countDown();
-                    }
-                });
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setAudioAttributes(testAudioAttributes)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(null)
-                .setCurrentMediaItem(null)
-                .build();
-
-        mRemoteSession2.updatePlayer(playerConfig);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged() throws Exception {
-        final int listSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(listSize);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int currentItemIndex = 3;
-        final MediaItem currentItem = list.get(currentItemIndex);
-        final CountDownLatch latchForControllerCallback = new CountDownLatch(3);
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem item) {
-                        switch ((int) latchForControllerCallback.getCount()) {
-                            case 3:
-                                // No check needed..
-                                break;
-                            case 2:
-                                MediaTestUtils.assertNotMediaItemSubclass(item);
-                                assertEquals(currentItem.getMediaId(), item.getMediaId());
-                                break;
-                            case 1:
-                                assertNull(item);
-                        }
-                        latchForControllerCallback.countDown();
-                    }
-                });
-        // Player notifies with the unknown item. Still OK.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_UNKONWN_ITEM);
-
-        // Known ITEM should be notified through the onCurrentMediaItemChanged.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(currentItemIndex);
-
-        // Null ITEM becomes null MediaItem.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_NULL_ITEM);
-        assertTrue(latchForControllerCallback.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_withDuration() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        if (getDuration(currentMediaItem) == testDuration) {
-                            // When current media item's duration is set, also test no other
-                            // media item has duration.
-                            int listSize = controller.getPlaylist().size();
-                            for (int i = 0; i < listSize; i++) {
-                                if (i != testCurrentItemIndex) {
-                                    assertNotEquals(testDuration,
-                                            getDuration(controller.getPlaylist().get(i)));
-                                }
-                            }
-                            latch.countDown();
-                        }
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_notCalledWithSameValue() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    private String mPreviousMediaId;
-                    private long mPreviousDuration;
-
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        String mediaId = currentMediaItem.getMetadata().getMediaId();
-                        long duration =
-                                currentMediaItem.getMetadata().getLong(
-                                        MediaMetadata.METADATA_KEY_DURATION);
-                        if (TextUtils.equals(mediaId, mPreviousMediaId)
-                                && duration == mPreviousDuration) {
-                            // Error!
-                            latch.countDown();
-                        }
-                        mPreviousMediaId = mediaId;
-                        mPreviousDuration = duration;
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_withUpdatedMetadata() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-        final String testDisplayTitle = "testDisplayTitle";
-        final MediaMetadata testMetadata =
-                new MediaMetadata.Builder(list.get(testCurrentItemIndex).getMetadata())
-                        .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testDisplayTitle)
-                        .build();
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        assertNotNull(currentMediaItem.getMetadata());
-                        if (TextUtils.equals(testDisplayTitle,
-                                currentMediaItem.getMetadata().getText(
-                                        MediaMetadata.METADATA_KEY_DISPLAY_TITLE))) {
-                            if (getDuration(currentMediaItem) == testDuration) {
-                                latch.countDown();
-                            }
-                        }
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().setCurrentMediaItemMetadata(testMetadata);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_resetsCurrentPosition() throws Exception {
-        int testCurrentItemIndex = 1;
-        String testCurrentItemId = TestUtils.getMediaIdInFakeList(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(/* size= */ 2);
-        mRemoteSession2.getMockPlayer().setCurrentPosition(123L);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                /* waitForConnect= */ true, /* connectionHints= */ null,
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            @Nullable MediaItem currentMediaItem) {
-                        if (currentMediaItem != null
-                                && testCurrentItemId.equals(currentMediaItem.getMediaId())) {
-                            controller.setTimeDiff(0L);
-                            latch.countDown();
-                        }
-                    }
-                });
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(0L, controller.getCurrentPosition());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaybackSpeed()}.
-     */
-    @Test
-    public void onPlaybackSpeedChanged() throws Exception {
-        final float speed = 1.5f;
-        mRemoteSession2.getMockPlayer().setPlaybackSpeed(speed);
-
-        final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
-        MediaController controller = createController(
-                mRemoteSession2.getToken(), true, null, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackSpeedChanged(@NonNull MediaController controller,
-                            float speedOut) {
-                        assertEquals(speed, speedOut, 0.0f);
-                        latchForControllerCallback.countDown();
-                    }
-                });
-        mRemoteSession2.getMockPlayer().notifyPlaybackSpeedChanged(speed);
-        assertTrue(latchForControllerCallback.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaybackInfo()}.
-     */
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byPlayerChange() throws Exception {
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VOLUME_CONTROL_ABSOLUTE;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-                assertEquals(attrs, info.getAudioAttributes());
-                assertEquals(volumeControlType, info.getPlaybackType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(volumeControlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession2.updatePlayer(playerConfig);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        assertEquals(volumeControlType, info.getControlType());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byAudioAttributesChange() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
-                .setUsage(AudioAttributesCompat.USAGE_MEDIA)
-                .build();
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull PlaybackInfo info) {
-                        assertNotNull(info.getAudioAttributes());
-                        assertEquals(attrs, info.getAudioAttributes());
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyAudioAttributesChanged(attrs);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byVolumeChange() throws Exception {
-        Bundle config = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mRemoteSession2.updatePlayer(config);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicReference<PlaybackInfo> playbackInfoRef = new AtomicReference<>();
-        MediaController.ControllerCallback callback = new MediaController.ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                playbackInfoRef.set(info);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                /* waitForConnect= */ true, /* connectionHints= */ null, callback);
-
-        int targetVolume = 3;
-        mRemoteSession2.getMockPlayer().notifyVolumeChanged(targetVolume);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetVolume, playbackInfoRef.get().getCurrentVolume());
-        assertEquals(targetVolume, controller.getPlaybackInfo().getCurrentVolume());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaylist()}.
-     */
-    @Test
-    public void onPlaylistChanged() throws InterruptedException {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNotNull(playlist);
-                        MediaTestUtils.assertNotMediaItemSubclass(playlist);
-                        MediaTestUtils.assertMediaIdEquals(testList, playlist);
-                        listFromCallback.set(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        mRemoteSession2.getMockPlayer().setPlaylist(testList);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-    }
-
-    @Test
-    public void onPlaylistChanged_nullList() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNull(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        mRemoteSession2.getMockPlayer().setPlaylist(null);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getPlaylist());
-    }
-
-    @Test
-    @LargeTest
-    public void onPlaylistChanged_longList() throws InterruptedException {
-        final int listSize = 5000;
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNotNull(playlist);
-                        assertEquals(listSize, playlist.size());
-                        for (int i = 0; i < playlist.size(); i++) {
-                            assertEquals(TestUtils.getMediaIdInFakeList(i),
-                                    playlist.get(i).getMediaId());
-                        }
-                        listFromCallback.set(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(listSize);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaylistMetadata()}.
-     */
-    @Test
-    public void onPlaylistMetadataChanged() throws InterruptedException {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        final AtomicReference<MediaMetadata> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                            MediaMetadata metadata) {
-                        assertNotNull(metadata);
-                        assertEquals(testMetadata.getMediaId(), metadata.getMediaId());
-                        metadataFromCallback.set(metadata);
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setPlaylistMetadata(testMetadata);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaylistMetadataChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(metadataFromCallback.get().getMediaId(),
-                controller.getPlaylistMetadata().getMediaId());
-    }
-
-    @Test
-    @LargeTest
-    public void onPlaylistMetadataChanged_withManyLargeImages() throws InterruptedException {
-        final int imageCount = 20;
-        final int originalWidth = 1024;
-        final int originalHeight = 1024;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                            MediaMetadata metadata) {
-                        assertNotNull(metadata);
-                        Set<String> keySet = metadata.keySet();
-                        assertEquals(imageCount, keySet.size());
-                        for (String key : keySet) {
-                            Bitmap value = metadata.getBitmap(key);
-                            assertTrue("Bitmap should have been scaled down.",
-                                    originalWidth > value.getWidth()
-                                            && originalHeight > value.getHeight());
-                        }
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setPlaylistMetadataWithLargeBitmaps(imageCount, originalWidth, originalHeight);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaylistMetadataChanged();
-        if (Build.VERSION.SDK_INT <= 19) {
-            // Due to the GC, time takes longer than expected.
-            // It seems to be due to the Dalvik GC mechanism.
-            assertTrue(latch.await(10, TimeUnit.SECONDS));
-        } else {
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        }
-    }
-
-    /**
-     * This also tests {@link MediaController#getShuffleMode()}.
-     */
-    @Test
-    public void onShuffleModeChanged() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onShuffleModeChanged(@NonNull MediaController controller,
-                            int shuffleMode) {
-                        assertEquals(testShuffleMode, shuffleMode);
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setShuffleMode(testShuffleMode);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-    }
-
-    /**
-     * This also tests {@link MediaController#getRepeatMode()}.
-     */
-    @Test
-    public void onRepeatModeChanged() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onRepeatModeChanged(@NonNull MediaController controller,
-                            int repeatMode) {
-                        assertEquals(testRepeatMode, repeatMode);
-                        latch.countDown();
-                    }
-                };
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setRepeatMode(testRepeatMode);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void onPlaybackCompleted() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackCompleted(@NonNull MediaController controller) {
-                        latch.countDown();
-                    }
-                };
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaybackCompleted();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSeekCompleted() throws InterruptedException {
-        final long testSeekPosition = 400;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onSeekCompleted(@NonNull MediaController controller, long position) {
-                controller.setTimeDiff(0L);
-                assertEquals(testSeekPosition, position);
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifySeekCompleted(testSeekPosition);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onBufferingStateChanged() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final int targetItemIndex = 0;
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        final long testBufferingPosition = 500;
-        final long testPosition = 300;
-        final long testTimeDiff = 100;
-
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                MediaTestUtils.assertNotMediaItemSubclass(item);
-                assertEquals(testPlaylist.get(targetItemIndex).getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                assertEquals(testPosition + testTimeDiff, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setBufferedPosition(testBufferingPosition);
-        player.setCurrentPosition(testPosition);
-        player.setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        // Since we cannot pass the DataSourceDesc directly, send the item index so that the player
-        // can select which item's state change should be notified.
-        player.notifyBufferingStateChanged(targetItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onBufferingStateChanged_bufferingAndStarved() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final int targetItemIndex = 0;
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
-        final long testBufferingPosition = 300;
-        final long testPosition = 300;
-        final long testTimeDiff = 100;
-
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                MediaTestUtils.assertNotMediaItemSubclass(item);
-                assertEquals(testPlaylist.get(targetItemIndex).getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setBufferedPosition(testBufferingPosition);
-        player.setCurrentPosition(testPosition);
-        player.setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        // Since we cannot pass the DataSourceDesc directly, send the item index so that the player
-        // can select which item's state change should be notified.
-        player.notifyBufferingStateChanged(targetItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlayerStateChanged_playing() throws InterruptedException {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long testPosition = 500;
-        final long testTimeDiff = 100;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                assertEquals(testPlayerState, state);
-                assertEquals(testPlayerState, controller.getPlayerState());
-                assertEquals(testPosition + testTimeDiff, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(testPlayerState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlayerStateChanged_paused() throws InterruptedException {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        final long testPosition = 500;
-        final long testTimeDiff = 100;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlayerStateChanged(@NonNull MediaController controller,
-                            int state) {
-                        controller.setTimeDiff(testTimeDiff);
-                        assertEquals(testPlayerState, state);
-                        assertEquals(testPlayerState, controller.getPlayerState());
-                        assertEquals(testPosition, controller.getCurrentPosition());
-                        latch.countDown();
-                    }
-                };
-
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(testPlayerState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * This also tests {@link MediaController#getAllowedCommands()}.
-     */
-    @Test
-    public void onAllowedCommandsChanged() throws InterruptedException {
-        final SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY));
-        builder.addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PAUSE));
-        final SessionCommandGroup commands = builder.build();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup commandsOut) {
-                assertEquals(commands, commandsOut);
-                latch.countDown();
-            }
-        };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.setAllowedCommands(commands);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(commands, controller.getAllowedCommands());
-    }
-
-    @Test
-    public void onCustomCommand() throws InterruptedException {
-        final String testCommandAction = "test_action";
-        final SessionCommand testCommand = new SessionCommand(testCommandAction, null);
-        final Bundle testArgs = TestUtils.createTestBundle();
-
-        final CountDownLatch primaryLatch = new CountDownLatch(2);
-        final CountDownLatch extraLatch = new CountDownLatch(1);
-        final MediaController.ControllerCallback primaryCallback =
-                new MediaController.ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(testCommand, command);
-                assertTrue(TestUtils.equals(testArgs, args));
-                primaryLatch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        final MediaController.ControllerCallback extraCallback =
-                new MediaController.ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                extraLatch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback);
-
-        // TODO(jaewan): Test with multiple controllers
-        mRemoteSession2.broadcastCustomCommand(testCommand, testArgs);
-
-        // TODO(jaewan): Test receivers as well.
-        mRemoteSession2.sendCustomCommand(testCommand, testArgs);
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertFalse("Extra ControllerCallback shouldn't be called",
-                extraLatch.await(300, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCustomLayoutChanged() throws InterruptedException {
-        final List<MediaSession.CommandButton> buttons = new ArrayList<>();
-
-        MediaSession.CommandButton button = new MediaSession.CommandButton.Builder()
-                .setCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY))
-                .setDisplayName("button")
-                .build();
-        buttons.add(button);
-
-        final CountDownLatch primaryLatch = new CountDownLatch(1);
-        final CountDownLatch extraLatch = new CountDownLatch(1);
-        final MediaController.ControllerCallback primaryCallback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<MediaSession.CommandButton> layout) {
-                assertEquals(layout.size(), buttons.size());
-                for (int i = 0; i < layout.size(); i++) {
-                    assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
-                    assertEquals(layout.get(i).getDisplayName(), buttons.get(i).getDisplayName());
-                }
-                primaryLatch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        final MediaController.ControllerCallback extraCallback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<MediaSession.CommandButton> layout) {
-                extraLatch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback);
-
-        mRemoteSession2.setCustomLayout(buttons);
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertFalse("Extra ControllerCallback shouldn't be called",
-                extraLatch.await(300, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onVideoSizeChanged() throws InterruptedException {
-        final VideoSize testSize = new VideoSize(100, 42);
-        final CountDownLatch latch = new CountDownLatch(2);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onVideoSizeChanged(@NonNull MediaController controller,
-                            @NonNull MediaItem item, @NonNull VideoSize videoSize) {
-                        assertNotNull(item);
-                        assertEquals(testSize, videoSize);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onVideoSizeChanged(@NonNull MediaController controller,
-                            @NonNull VideoSize videoSize) {
-                        assertEquals(testSize, videoSize);
-                        latch.countDown();
-                    }
-                };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_UNKONWN_ITEM);
-        mRemoteSession2.getMockPlayer().notifyVideoSizeChanged(testSize);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onTracksChanged() throws InterruptedException {
-        List<SessionPlayer.TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        AtomicReference<List<SessionPlayer.TrackInfo>> returnedTracksRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTracksChanged(@NonNull MediaController controller,
-                            @NonNull List<TrackInfo> tracks) {
-                        returnedTracksRef.set(tracks);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTracksChanged(testTracks);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTracks, returnedTracksRef.get());
-    }
-
-    @Test
-    public void onTrackSelected() throws InterruptedException {
-        SessionPlayer.TrackInfo testTrack = MediaTestUtils.createTrackInfo(1,
-                SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        AtomicReference<SessionPlayer.TrackInfo> returnedTrackRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackSelected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        returnedTrackRef.set(trackInfo);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTrackSelected(testTrack);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, returnedTrackRef.get());
-    }
-
-    @Test
-    public void onTrackDeselected() throws InterruptedException {
-        SessionPlayer.TrackInfo testTrack = MediaTestUtils.createTrackInfo(1,
-                SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        AtomicReference<SessionPlayer.TrackInfo> returnedTrackRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackDeselected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        returnedTrackRef.set(trackInfo);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTrackDeselected(testTrack);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, returnedTrackRef.get());
-    }
-
-    @Test
-    public void onSubtitleData() throws InterruptedException {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_LANGUAGE, "und");
-        format.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "onSubtitleData").build();
-        final MediaItem testItem = new MediaItem.Builder().setMetadata(metadata).build();
-        final TrackInfo testTrack = new TrackInfo(1, TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, format);
-        final SubtitleData testData = new SubtitleData(123, 456,
-                new byte[] { 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onSubtitleData(@NonNull MediaController controller,
-                            @NonNull MediaItem item, @NonNull TrackInfo track,
-                            @NonNull SubtitleData data) {
-                        MediaTestUtils.assertMediaIdEquals(testItem, item);
-                        assertEquals(testTrack, track);
-                        assertEquals(testData, data);
-                        latch.countDown();
-                    }
-                };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifySubtitleData(testItem, testTrack, testData);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void testControllerAfterSessionIsClosed(String id) throws InterruptedException {
-        // This cause session service to be died.
-        mRemoteSession2.close();
-        waitForDisconnect(mController, true);
-        testNoInteraction();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        // Recreated session has different session stub, so previously created controller
-        // shouldn't be available.
-        mRemoteSession2 = createRemoteMediaSession(id);
-        testNoInteraction();
-    }
-
-    // Test that mSession and mController doesn't interact.
-    // Note that this method can be called after the mSession is died, so mSession may not have
-    // valid player.
-    private void testNoInteraction() throws InterruptedException {
-        // TODO: check that calls from the controller to session shouldn't be delivered.
-
-        // Calls from the session to controller shouldn't be delivered.
-        final CountDownLatch latch = new CountDownLatch(1);
-        setRunnableForOnCustomCommand(mController, new Runnable() {
-            @Override
-            public void run() {
-                latch.countDown();
-            }
-        });
-        SessionCommand customCommand = new SessionCommand("testNoInteraction", null);
-
-        mRemoteSession2.broadcastCustomCommand(customCommand, null);
-
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        setRunnableForOnCustomCommand(mController, null);
-    }
-
-    private static long getDuration(MediaItem item) {
-        if (item == null || item.getMetadata() == null) {
-            return SessionPlayer.UNKNOWN_TIME;
-        }
-        return item.getMetadata().getLong(MediaMetadata.METADATA_KEY_DURATION);
-    }
-
-    RemoteMediaSession createRemoteMediaSession(String id) {
-        RemoteMediaSession session = new RemoteMediaSession(id, mContext, null);
-        mRemoteSessionList.add(session);
-        return session;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
deleted file mode 100644
index ce03de5..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ /dev/null
@@ -1,799 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.HeartRating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.ThumbRating;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests for {@link MediaControllerCompat.Callback} with {@link MediaSession}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerCompatCallbackWithMediaSessionTest extends MediaSessionTestBase {
-    private static final String TAG = "MCCCallbackTestWithMS2";
-
-    private static final long TIMEOUT_MS = 1000L;
-    private static final float EPSILON = 1e-6f;
-
-    private RemoteMediaSession mSession;
-    private MediaControllerCompat mControllerCompat;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        mSession = new RemoteMediaSession(TAG, mContext, null);
-        mControllerCompat = new MediaControllerCompat(mContext, mSession.getCompatToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void gettersAfterConnected() throws Exception {
-        int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        int testBufferingPosition = 1500;
-        float testSpeed = 1.5f;
-        List<MediaItem> testPlaylist = new ArrayList<>();
-        for (int i = 0; i < 3; i++) {
-            testPlaylist.add(new MediaItem.Builder()
-                    .setMetadata(new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "id=" + i)
-                            .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new HeartRating())
-                            .build())
-                    .build());
-        }
-        int testItemIndex = 0;
-        String testPlaylistTitle = "testPlaylistTitle";
-        MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setBufferedPosition(testBufferingPosition)
-                .setPlaybackSpeed(testSpeed)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(testPlaylistMetadata)
-                .setCurrentMediaItem(testPlaylist.get(testItemIndex))
-                .setShuffleMode(testShuffleMode)
-                .setRepeatMode(testRepeatMode)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        MediaControllerCompat controller =
-                new MediaControllerCompat(mContext, mSession.getCompatToken());
-        CountDownLatch latch = new CountDownLatch(1);
-        controller.registerCallback(
-                new MediaControllerCompat.Callback() {
-                    @Override
-                    public void onSessionReady() {
-                        latch.countDown();
-                    }
-                }, sHandler);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertEquals(testState, MediaUtils.convertToPlayerState(controller.getPlaybackState()));
-        assertEquals(testBufferingPosition, controller.getPlaybackState().getBufferedPosition());
-        assertEquals(testSpeed, controller.getPlaybackState().getPlaybackSpeed(), EPSILON);
-
-        assertEquals(testPlaylist.get(testItemIndex).getMediaId(),
-                controller.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-
-        List<QueueItem> queue = controller.getQueue();
-        assertNotNull(queue);
-        assertEquals(testPlaylist.size(), queue.size());
-        for (int i = 0; i < testPlaylist.size(); i++) {
-            assertEquals(testPlaylist.get(i).getMediaId(),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(testPlaylistTitle, controller.getQueueTitle().toString());
-        assertEquals(RatingCompat.RATING_HEART, controller.getRatingType());
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void repeatModeChange() throws Exception {
-        int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicInteger repeatModeRef = new AtomicInteger();
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onRepeatModeChanged(int repeatMode) {
-                repeatModeRef.set(repeatMode);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setRepeatMode(testRepeatMode);
-        mSession.getMockPlayer().notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, repeatModeRef.get());
-        assertEquals(testRepeatMode, mControllerCompat.getRepeatMode());
-    }
-
-    @Test
-    public void shuffleModeChange() throws Exception {
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicInteger shuffleModeRef = new AtomicInteger();
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onShuffleModeChanged(int shuffleMode) {
-                shuffleModeRef.set(shuffleMode);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setShuffleMode(testShuffleMode);
-        mSession.getMockPlayer().notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, shuffleModeRef.get());
-        assertEquals(testShuffleMode, mControllerCompat.getShuffleMode());
-    }
-
-    @Test
-    public void close() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onSessionDestroyed() {
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void updatePlayer() throws Exception {
-        int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        int testBufferingPosition = 1500;
-        float testSpeed = 1.5f;
-        List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        int testItemIndex = 0;
-        String testPlaylistTitle = "testPlaylistTitle";
-        MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        AtomicInteger shuffleModeRef = new AtomicInteger();
-        AtomicInteger repeatModeRef = new AtomicInteger();
-        CountDownLatch latchForPlaybackState = new CountDownLatch(1);
-        CountDownLatch latchForMetadata = new CountDownLatch(1);
-        CountDownLatch latchForQueue = new CountDownLatch(2);
-        CountDownLatch latchForShuffleMode = new CountDownLatch(1);
-        CountDownLatch latchForRepeatMode = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latchForPlaybackState.countDown();
-            }
-
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) {
-                metadataRef.set(metadata);
-                latchForMetadata.countDown();
-            }
-
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latchForQueue.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latchForQueue.countDown();
-            }
-
-            @Override
-            public void onShuffleModeChanged(int shuffleMode) {
-                shuffleModeRef.set(shuffleMode);
-                latchForShuffleMode.countDown();
-            }
-
-            @Override
-            public void onRepeatModeChanged(int repeatMode) {
-                repeatModeRef.set(repeatMode);
-                latchForRepeatMode.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setBufferedPosition(testBufferingPosition)
-                .setPlaybackSpeed(testSpeed)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(testPlaylistMetadata)
-                .setCurrentMediaItem(testPlaylist.get(testItemIndex))
-                .setShuffleMode(testShuffleMode)
-                .setRepeatMode(testRepeatMode)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        assertTrue(latchForPlaybackState.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testState, MediaUtils.convertToPlayerState(playbackStateRef.get()));
-        assertEquals(testBufferingPosition, playbackStateRef.get().getBufferedPosition());
-        assertEquals(testSpeed, playbackStateRef.get().getPlaybackSpeed(), EPSILON);
-
-        assertTrue(latchForMetadata.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testPlaylist.get(testItemIndex).getMediaId(),
-                metadataRef.get().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-
-        assertTrue(latchForQueue.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-        assertEquals(testPlaylist.size(), queue.size());
-        for (int i = 0; i < testPlaylist.size(); i++) {
-            assertEquals(testPlaylist.get(i).getMediaId(),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(testPlaylistTitle, queueTitleRef.get().toString());
-
-        assertTrue(latchForShuffleMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, shuffleModeRef.get());
-        assertTrue(latchForRepeatMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, repeatModeRef.get());
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeChangedToRemote() throws Exception {
-        int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        int maxVolume = 25;
-        int currentVolume = 10;
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(controlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-        assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                info.getPlaybackType());
-        assertEquals(controlType, info.getVolumeControl());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeChangedToLocal() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int legacyStream = AudioManager.STREAM_RING;
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(legacyStream).build();
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType() == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfigToUpdate = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mSession.updatePlayer(playerConfigToUpdate);
-
-        // In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local.
-        if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    info.getPlaybackType());
-            assertEquals(legacyStream, info.getAudioAttributes().getLegacyStreamType());
-        }
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeNotChanged_local() throws Exception {
-        int legacyStream = AudioManager.STREAM_RING;
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(legacyStream).build();
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType() == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
-        if (Build.VERSION.SDK_INT >= 21) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    info.getPlaybackType());
-            assertEquals(legacyStream, info.getAudioAttributes().getLegacyStreamType());
-        }
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeNotChanged_remote() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        int maxVolume = 25;
-        int currentVolume = 1;
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfigToUpdate = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(controlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .build();
-        mSession.updatePlayer(playerConfigToUpdate);
-
-        // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
-        if (Build.VERSION.SDK_INT >= 21) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                    info.getPlaybackType());
-            assertEquals(controlType, info.getVolumeControl());
-            assertEquals(maxVolume, info.getMaxVolume());
-            assertEquals(currentVolume, info.getCurrentVolume());
-        }
-    }
-
-    @Test
-    public void playerStateChange() throws Exception {
-        int targetState = SessionPlayer.PLAYER_STATE_PLAYING;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onSessionReady() {
-                latch.countDown();
-            }
-
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().notifyPlayerStateChanged(targetState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetState, MediaUtils.convertToPlayerState(playbackStateRef.get()));
-        assertEquals(targetState,
-                MediaUtils.convertToPlayerState(mControllerCompat.getPlaybackState()));
-    }
-
-    @Test
-    public void playbackSpeedChange() throws Exception {
-        float speed = 1.5f;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaybackSpeed(speed);
-        mSession.getMockPlayer().notifyPlaybackSpeedChanged(speed);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, playbackStateRef.get().getPlaybackSpeed(), EPSILON);
-        assertEquals(speed, mControllerCompat.getPlaybackState().getPlaybackSpeed(), EPSILON);
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void bufferingStateChange() throws Exception {
-        List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        int testItemIndex = 0;
-        int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        long testBufferingPosition = 500;
-        mSession.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setBufferedPosition(testBufferingPosition);
-        mSession.getMockPlayer().notifyBufferingStateChanged(testItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testBufferingPosition, playbackStateRef.get().getBufferedPosition());
-        assertEquals(testBufferingPosition,
-                mControllerCompat.getPlaybackState().getBufferedPosition());
-    }
-
-    @Test
-    public void seekComplete() throws Exception {
-        long testSeekPosition = 1300;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setCurrentPosition(testSeekPosition);
-        mSession.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mSession.getMockPlayer().notifySeekCompleted(testSeekPosition);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testSeekPosition, playbackStateRef.get().getPosition());
-        assertEquals(testSeekPosition, mControllerCompat.getPlaybackState().getPosition());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void currentMediaItemChange() throws Exception {
-        int testItemIndex = 3;
-        long testPosition = 1234;
-        String displayTitle = "displayTitle";
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle)
-                .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new ThumbRating())
-                .build();
-        MediaItem currentMediaItem = new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(metadata)
-                .build();
-
-        List<MediaItem> playlist = MediaTestUtils.createFileMediaItems(5);
-        playlist.set(testItemIndex, currentMediaItem);
-        mSession.getMockPlayer().setPlaylistWithFakeItem(playlist);
-
-        AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latchForMetadata = new CountDownLatch(1);
-        CountDownLatch latchForPlaybackState = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) {
-                metadataRef.set(metadata);
-                latchForMetadata.countDown();
-            }
-
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latchForPlaybackState.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setCurrentMediaItem(testItemIndex);
-        mSession.getMockPlayer().setCurrentPosition(testPosition);
-        mSession.getMockPlayer().notifyCurrentMediaItemChanged(testItemIndex);
-
-        assertTrue(latchForMetadata.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(displayTitle,
-                metadataRef.get().getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE));
-        assertEquals(displayTitle,
-                mControllerCompat.getMetadata().getString(
-                        MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE));
-        assertTrue(latchForPlaybackState.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testPosition, playbackStateRef.get().getPosition());
-        assertEquals(testPosition, mControllerCompat.getPlaybackState().getPosition());
-        assertEquals(MediaUtils.convertToQueueItemId(testItemIndex),
-                playbackStateRef.get().getActiveQueueItemId());
-        assertEquals(MediaUtils.convertToQueueItemId(testItemIndex),
-                mControllerCompat.getPlaybackState().getActiveQueueItemId());
-        assertEquals(RatingCompat.RATING_THUMB_UP_DOWN, mControllerCompat.getRatingType());
-    }
-
-    @Test
-    public void playlistAndPlaylistMetadataChange() throws Exception {
-        List<MediaItem> playlist = MediaTestUtils.createFileMediaItems(5);
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaylist(playlist);
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-        assertEquals(playlist.size(), queue.size());
-        for (int i = 0; i < playlist.size(); i++) {
-            assertEquals(playlist.get(i).getMediaId(), queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Test
-    public void playlistAndPlaylistMetadataChange_longList() throws Exception {
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        int listSize = 5000;
-        mSession.getMockPlayer().createAndSetFakePlaylist(listSize);
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-
-        if (Build.VERSION.SDK_INT >= 21) {
-            assertEquals(listSize, queue.size());
-        } else {
-            // Below API 21, only the initial part of the playlist is sent to the
-            // MediaControllerCompat when the list is too long.
-            assertTrue(queue.size() < listSize);
-        }
-        for (int i = 0; i < queue.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Test
-    public void playlistMetadataChange() throws Exception {
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistMetadataChanged();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void onAudioInfoChanged_isCalled_byVolumeChange() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int targetVolume = 3;
-        CountDownLatch targetVolumeNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getCurrentVolume() == targetVolume) {
-                    targetVolumeNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().notifyVolumeChanged(targetVolume);
-
-        assertTrue(targetVolumeNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetVolume, mControllerCompat.getPlaybackInfo().getCurrentVolume());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
deleted file mode 100644
index 0b2119b..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
+++ /dev/null
@@ -1,811 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.PercentageRating;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSessionCompat;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController} interacting with {@link MediaSessionCompat}.
- *
- * TODO: Pull out callback tests to a separate file (i.e. MediaControllerLegacyCallbackTest).
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@MediumTest
-public class MediaControllerLegacyTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaControllerLegacyTest";
-    private static final long EXPECTED_TIMEOUT_MS = 100;
-
-    AudioManager mAudioManager;
-    RemoteMediaSessionCompat mSession;
-    MediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mSession = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, mContext);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.cleanUp();
-        }
-        if (mController != null) {
-            mController.close();
-        }
-    }
-
-    @Test
-    public void gettersAfterConnected() throws Exception {
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-        final long timeDiff = 102;
-        final float speed = 0.5f;
-        final int shuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final int repeatMode = SessionPlayer.REPEAT_MODE_ALL;
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                MediaTestUtils.createMetadata());
-
-        mSession.setPlaybackState(
-                new PlaybackStateCompat.Builder()
-                        .setState(PlaybackStateCompat.STATE_PLAYING, position, speed)
-                        .setBufferedPosition(bufferedPosition).build());
-        mSession.setMetadata(metadata);
-        mSession.setShuffleMode(shuffleMode);
-        mSession.setRepeatMode(repeatMode);
-        mSession.setRatingType(RatingCompat.RATING_PERCENTAGE);
-
-        mController = createController(mSession.getSessionToken());
-        mController.setTimeDiff(timeDiff);
-
-        assertEquals(SessionPlayer.PLAYER_STATE_PLAYING, mController.getPlayerState());
-        assertEquals(SessionPlayer.BUFFERING_STATE_COMPLETE,
-                mController.getBufferingState());
-        assertEquals(bufferedPosition, mController.getBufferedPosition());
-        assertEquals(speed, mController.getPlaybackSpeed(), 0.0f);
-        assertEquals((double) position + (speed * timeDiff),
-                (double) mController.getCurrentPosition(), 100.0 /* 100 ms */);
-        assertEquals(metadata.getDescription().getMediaId(),
-                mController.getCurrentMediaItem().getMediaId());
-        Rating rating = mController.getCurrentMediaItem().getMetadata()
-                .getRating(MediaMetadata.METADATA_KEY_USER_RATING);
-        assertTrue(rating instanceof PercentageRating);
-        assertFalse(rating.isRated());
-        assertEquals(shuffleMode, mController.getShuffleMode());
-        assertEquals(repeatMode, mController.getRepeatMode());
-    }
-
-    @Test
-    public void getPackageName() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        assertEquals(SERVICE_PACKAGE_NAME, mController.getConnectedToken().getPackageName());
-    }
-
-    @Test
-    public void getSessionActivity() throws Exception {
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        PendingIntent pi = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-        mSession.setSessionActivity(pi);
-
-        mController = createController(mSession.getSessionToken());
-        PendingIntent sessionActivityOut = mController.getSessionActivity();
-        assertNotNull(sessionActivityOut);
-        if (Build.VERSION.SDK_INT >= 17) {
-            // PendingIntent#getCreatorPackage() is added in API 17.
-            assertEquals(mContext.getPackageName(), sessionActivityOut.getCreatorPackage());
-        }
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getRepeatMode() throws Exception {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setRepeatMode(testRepeatMode);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, mController.getRepeatMode());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getShuffleMode() throws Exception {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setShuffleMode(testShuffleMode);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, mController.getShuffleMode());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onPlaylistChanged(
-     * MediaController, List, MediaMetadata)}.
-     */
-    @Ignore("b/202942942")
-    @Test
-    public void getPlaylist() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller,
-                    List<MediaItem> playlist, MediaMetadata metadata) {
-                assertNotNull(playlist);
-                assertEquals(testList.size(), playlist.size());
-                for (int i = 0; i < playlist.size(); i++) {
-                    assertEquals(testList.get(i).getMediaId(), playlist.get(i).getMediaId());
-                }
-                listFromCallback.set(playlist);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setQueue(testQueue);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listFromCallback.get(), mController.getPlaylist());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void getPlaylistMetadata() throws Exception {
-        final AtomicReference<MediaMetadata> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final CharSequence queueTitle = "test queue title";
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                    MediaMetadata metadata) {
-                assertEquals(queueTitle.toString(),
-                        metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
-                metadataFromCallback.set(metadata);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setQueueTitle(queueTitle);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(metadataFromCallback.get(), mController.getPlaylistMetadata());
-    }
-
-    @Test
-    public void getCurrentMediaItemAfterConnected() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-        assertNull(mController.getCurrentMediaItem());
-    }
-
-    @Test
-    public void getCurrentMediaItemAfterConnected_metadata() throws Exception {
-        final String testMediaId = "testGetCurrentMediaItemWhenConnected_metadata";
-        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
-                .build();
-        mSession.setMetadata(metadata);
-
-        mController = createController(mSession.getSessionToken(), true, null);
-        assertEquals(testMediaId, mController.getCurrentMediaItem().getMediaId());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void setMediaUri_resultSetAfterPrepare() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri = Uri.parse("androidx://test");
-        ListenableFuture<SessionResult> future =
-                mController.setMediaUri(testUri, /* extras= */ null);
-
-        SessionResult result;
-        try {
-            result = future.get(EXPECTED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            fail("TimeoutException is expected");
-        } catch (TimeoutException e) {
-            // expected.
-        }
-
-        mController.prepare();
-
-        result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void setMediaUri_resultSetAfterPlay() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri = Uri.parse("androidx://test");
-        ListenableFuture<SessionResult> future =
-                mController.setMediaUri(testUri, /* extras= */ null);
-
-        SessionResult result;
-        try {
-            result = future.get(EXPECTED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            fail("TimeoutException is expected");
-        } catch (TimeoutException e) {
-            // expected.
-        }
-
-        mController.play();
-
-        result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void setMediaUris_multipleCalls_previousCallReturnsResultInfoSkipped() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri1 = Uri.parse("androidx://test1");
-        Uri testUri2 = Uri.parse("androidx://test2");
-        ListenableFuture<SessionResult> future1 =
-                mController.setMediaUri(testUri1, /* extras= */ null);
-        ListenableFuture<SessionResult> future2 =
-                mController.setMediaUri(testUri2, /* extras= */ null);
-
-        mController.prepare();
-
-        SessionResult result1 = future1.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        SessionResult result2 = future2.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_INFO_SKIPPED, result1.getResultCode());
-        assertEquals(RESULT_SUCCESS, result2.getResultCode());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onCurrentMediaItemChanged_byMetadataChange()
-            throws Exception {
-        final String testMediaId = "testControllerCallback_onCurrentMediaItemChanged_bySetMetadata";
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                    MediaItem item) {
-                MediaTestUtils.assertMediaItemHasId(item, testMediaId);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
-                .build();
-        mSession.setMetadata(metadata);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onCurrentMediaItemChanged_byActiveQueueItemChange()
-            throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        mSession.setQueue(testQueue);
-
-        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
-
-        // Set the current active queue item to index 'oldItemIndex'.
-        final int oldItemIndex = 0;
-        builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
-        mSession.setPlaybackState(builder.build());
-
-        final int newItemIndex = 1;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                    MediaItem item) {
-                MediaTestUtils.assertMediaIdEquals(testList.get(newItemIndex), item);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        // The new playbackState will tell the controller that the active queue item is changed to
-        // 'newItemIndex'.
-        builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
-        mSession.setPlaybackState(builder.build());
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onSeekCompleted() throws Exception {
-        final long testSeekPosition = 400;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onSeekCompleted(@NonNull MediaController controller, long position) {
-                assertEquals(testSeekPosition, position);
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testSeekPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onBufferingCompleted() throws Exception {
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(1);
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                testPlaylist.get(0).getMetadata());
-
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        final long testBufferingPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                assertEquals(metadata.getDescription().getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setMetadata(metadata);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_BUFFERING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(0)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(testBufferingPosition)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-    
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onBufferingStarved() throws Exception {
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(1);
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                testPlaylist.get(0).getMetadata());
-
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
-        final long testBufferingPosition = 0;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                assertEquals(metadata.getDescription().getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setMetadata(metadata);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 100 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(500)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(0L);
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_BUFFERING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(testBufferingPosition)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlayerStateChanged() throws Exception {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                assertEquals(testPlayerState, state);
-                assertEquals(testPlayerState, controller.getPlayerState());
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_NONE, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlaybackSpeedChanged() throws Exception {
-        final float testSpeed = 3.0f;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
-                assertEquals(testSpeed, speed, 0.0f);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0 /* position */,
-                        testSpeed /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToRemote()
-            throws Exception {
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        final int maxVolume = 100;
-        final int currentVolume = 45;
-
-        final AtomicReference<MediaController.PlaybackInfo> infoOut = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull MediaController.PlaybackInfo info) {
-                // Here, we are intentionally avoid using assertEquals(), since this callback
-                // can be called many times which of them have inaccurate values.
-                Log.d(TAG, "Given playbackType=" + info.getPlaybackType()
-                        + " controlType=" + info.getControlType()
-                        + " maxVolume=" + info.getMaxVolume()
-                        + " currentVolume=" + info.getCurrentVolume()
-                        + " audioAttrs=" + info.getAudioAttributes());
-                if (MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE == info.getPlaybackType()
-                        && volumeControlType == info.getControlType()
-                        && maxVolume == info.getMaxVolume()
-                        && currentVolume == info.getCurrentVolume()) {
-                    infoOut.set(info);
-                    latch.countDown();
-                }
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(infoOut.get(), mController.getPlaybackInfo());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToLocal()
-            throws Exception {
-        if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
-            // In API 21 and 22, onAudioInfoChanged is not called.
-            return;
-        }
-        mSession.setPlaybackToRemote(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 100, 45);
-
-        final int testLocalStreamType = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(testLocalStreamType);
-        final int currentVolume = mAudioManager.getStreamVolume(testLocalStreamType);
-
-        final AtomicReference<MediaController.PlaybackInfo> infoOut = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull MediaController.PlaybackInfo info) {
-                assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                        info.getPlaybackType());
-                assertEquals(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE, info.getControlType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                infoOut.set(info);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackToLocal(testLocalStreamType);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(infoOut.get(), mController.getPlaybackInfo());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onCustomCommand() throws Exception {
-        final String event = "testControllerCallback_onCustomCommand";
-        final Bundle extras = TestUtils.createTestBundle();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(event, command.getCustomAction());
-                assertTrue(TestUtils.equals(extras, args));
-                latch.countDown();
-                return null;
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.sendSessionEvent(event, extras);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onSetCustomLayout() throws Exception {
-        final CustomAction testCustomAction1 =
-                new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
-        final CustomAction testCustomAction2 =
-                new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<CommandButton> layout) {
-                assertEquals(1, layout.size());
-                CommandButton button = layout.get(0);
-
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(testCustomAction1.getAction(),
-                                button.getCommand().getCustomAction());
-                        assertEquals(testCustomAction1.getName(), button.getDisplayName());
-                        assertEquals(testCustomAction1.getIcon(), button.getIconResId());
-                        break;
-                    case 1:
-                        assertEquals(testCustomAction2.getAction(),
-                                button.getCommand().getCustomAction());
-                        assertEquals(testCustomAction2.getName(), button.getDisplayName());
-                        assertEquals(testCustomAction2.getIcon(), button.getIconResId());
-                        break;
-                }
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction1).build());
-        // onSetCustomLayout will be called when its connected
-        mController = createController(mSession.getSessionToken(), true, callback);
-        // onSetCustomLayout will be called again when the custom action in the playback state is
-        // changed.
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction2).build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onAllowedCommandChanged() throws Exception {
-        final CustomAction testCustomAction1 =
-                new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
-        final CustomAction testCustomAction2 =
-                new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup commands) {
-                assertFalse(commands.hasCommand(new SessionCommand(
-                        testCustomAction1.getAction(), testCustomAction1.getExtras())));
-                assertTrue(commands.hasCommand(new SessionCommand(
-                        testCustomAction2.getAction(), testCustomAction2.getExtras())));
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction1).build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction2).build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onConnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onDisconnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mSession.release();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void controllerCallback_close() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mController.close();
-        waitForDisconnect(mController, true);
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void close_twice() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mController.close();
-        mController.close();
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void isConnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        assertTrue(mController.isConnected());
-
-        mSession.release();
-        waitForDisconnect(mController, true);
-        assertFalse(mController.isConnected());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void close_beforeConnected() throws InterruptedException {
-        MediaController controller = createController(mSession.getSessionToken(), false, null);
-
-        // Should not crash.
-        controller.close();
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void controllerCallback_onCustomCommand_bySetCaptioningEnabled() throws Exception {
-        final String sessionCommandOnCaptioningEnabledChanged =
-                "android.media.session.command.ON_CAPTIONING_ENALBED_CHANGED";
-        final String argumentCaptioningEnabled = "androidx.media2.argument.CAPTIONING_ENABLED";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(sessionCommandOnCaptioningEnabledChanged, command.getCustomAction());
-                assertEquals(true, args.getBoolean(argumentCaptioningEnabled, false));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setCaptioningEnabled(true);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void constructorWithoutCallback() throws InterruptedException {
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionCompatToken(mSession.getSessionToken())
-                .build();
-        PollingCheck.waitFor(TIMEOUT_MS, () -> controller.isConnected());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
deleted file mode 100644
index 39777f1..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.ControllerCallbackRunnable;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.CustomParcelable;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerTest extends MediaSessionTestBase {
-
-    static final String TAG = "MediaControllerTest";
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    final List<RemoteMediaSession> mRemoteSessionList = new ArrayList<>();
-
-    AudioManager mAudioManager;
-    RemoteMediaSession mRemoteSession;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mRemoteSession = createRemoteMediaSession(DEFAULT_TEST_NAME, null);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        for (int i = 0; i < mRemoteSessionList.size(); i++) {
-            RemoteMediaSession session = mRemoteSessionList.get(i);
-            if (session != null) {
-                session.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void builder() {
-        MediaController.Builder builder;
-
-        try {
-            builder = new MediaController.Builder(null);
-            fail("null context shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setSessionToken(null);
-            fail("null token shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setSessionCompatToken(null);
-            fail("null compat token shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setControllerCallback(null, null);
-            fail("null executor or null callback shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            Bundle connectionHints = new Bundle();
-            connectionHints.putParcelable("key", new CustomParcelable(1));
-            builder = new MediaController.Builder(mContext);
-            builder.setConnectionHints(connectionHints);
-            fail("custom parcelables shouldn't be allowed for connectionHints");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mRemoteSession.getToken())
-                .setControllerCallback(sHandlerExecutor, new ControllerCallback() {})
-                .build();
-        controller.close();
-    }
-
-    @Test
-    public void getSessionActivity() throws InterruptedException {
-        RemoteMediaSession session = createRemoteMediaSession(TEST_GET_SESSION_ACTIVITY, null);
-
-        MediaController controller = createController(session.getToken());
-        PendingIntent sessionActivity = controller.getSessionActivity();
-        assertNotNull(sessionActivity);
-        if (Build.VERSION.SDK_INT >= 17) {
-            // PendingIntent#getCreatorPackage() is added in API 17.
-            assertEquals(SERVICE_PACKAGE_NAME, sessionActivity.getCreatorPackage());
-
-            // TODO: Add getPid/getUid in MediaControllerProviderService and compare them.
-            // assertEquals(mRemoteSession.getUid(), sessionActivity.getCreatorUid());
-        }
-        session.cleanUp();
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume_afterStreamTypeChanged() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        int oldStream = AudioManager.STREAM_MUSIC;
-        int volumeForOldStream = mAudioManager.getStreamVolume(oldStream);
-
-        int stream = AudioManager.STREAM_ALARM;
-        int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = createController(mRemoteSession.getToken(),
-                true /* waitForConnect */, null /* connectionHints */, new ControllerCallback() {
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull PlaybackInfo info) {
-                        AudioAttributesCompat attrs = info.getAudioAttributes();
-                        if (attrs != null && attrs.getLegacyStreamType() == stream) {
-                            latch.countDown();
-                        }
-                    }
-                });
-
-        AudioAttributesCompat oldAttrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(oldStream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(oldAttrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        mRemoteSession.getMockPlayer().notifyAudioAttributesChanged(attrs);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        int originalVolume = mAudioManager.getStreamVolume(stream);
-        int targetVolume = originalVolume == minVolume ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        PollingCheck.waitFor(VOLUME_CHANGE_TIMEOUT_MS,
-                () -> targetVolume == mAudioManager.getStreamVolume(stream));
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-
-        assertEquals(volumeForOldStream, mAudioManager.getStreamVolume(oldStream));
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void getPackageName() throws Exception {
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertEquals(SERVICE_PACKAGE_NAME, controller.getConnectedToken().getPackageName());
-    }
-
-    @Test
-    public void getTokenExtras() throws Exception {
-        Bundle testTokenExtras = TestUtils.createTestBundle();
-        RemoteMediaSession session = createRemoteMediaSession("testGetExtras", testTokenExtras);
-
-        MediaController controller = createController(session.getToken());
-        SessionToken connectedToken = controller.getConnectedToken();
-        assertNotNull(connectedToken);
-        assertTrue(TestUtils.equals(testTokenExtras, connectedToken.getExtras()));
-    }
-
-    @Test
-    public void isConnected() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertTrue(controller.isConnected());
-
-        mRemoteSession.close();
-        waitForDisconnect(controller, true);
-        assertFalse(controller.isConnected());
-    }
-
-    @Test
-    public void close_beforeConnected() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, null /* callback */);
-        controller.close();
-    }
-
-    @Test
-    public void close_twice() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken());
-        controller.close();
-        controller.close();
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void gettersAfterConnected() throws InterruptedException {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-        final float speed = 0.5f;
-        final long timeDiff = 102;
-        final MediaItem currentMediaItem = MediaTestUtils.createFileMediaItemWithMetadata();
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(state)
-                .setBufferingState(bufferingState)
-                .setCurrentPosition(position)
-                .setBufferedPosition(bufferedPosition)
-                .setPlaybackSpeed(speed)
-                .setCurrentMediaItem(currentMediaItem)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        controller.setTimeDiff(timeDiff);
-        assertEquals(state, controller.getPlayerState());
-        assertEquals(bufferedPosition, controller.getBufferedPosition());
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-        assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
-        MediaTestUtils.assertNotMediaItemSubclass(controller.getCurrentMediaItem());
-        MediaTestUtils.assertMediaIdEquals(currentMediaItem, controller.getCurrentMediaItem());
-    }
-
-    @Test
-    public void getPlaybackInfo() throws Exception {
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final MediaController controller = createController(mRemoteSession.getToken());
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-
-        int localVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            localVolumeControlType = VOLUME_CONTROL_FIXED;
-        }
-        assertEquals(localVolumeControlType, info.getControlType());
-        assertEquals(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
-                info.getMaxVolume());
-        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
-                info.getCurrentVolume());
-    }
-
-    @Test
-    public void getVideoSize() throws InterruptedException {
-        VideoSize testSize = new VideoSize(100, 42);
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVideoSize(testSize)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertEquals(testSize, controller.getVideoSize());
-    }
-
-    @Test
-    public void getTracks() throws Exception {
-        List<SessionPlayer.TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setTrackInfo(testTracks)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        List<SessionPlayer.TrackInfo> testTracksFromController = controller.getTracks();
-        assertEquals(testTracks, testTracksFromController);
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void selectDeselectTrackAndGetSelectedTrack() throws Exception {
-        CountDownLatch selectTrackLatch = new CountDownLatch(1);
-        CountDownLatch deselectTrackLatch = new CountDownLatch(1);
-        AtomicReference<SessionPlayer.TrackInfo> selectedTrackRef = new AtomicReference<>();
-        AtomicReference<SessionPlayer.TrackInfo> deselectedTrackRef = new AtomicReference<>();
-
-        List<TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        TrackInfo testTrack = testTracks.get(2);
-        int testTrackType = testTrack.getTrackType();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setTrackInfo(testTracks)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-        MediaController controller = createController(mRemoteSession.getToken(), true, null,
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackSelected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        selectedTrackRef.set(trackInfo);
-                        selectTrackLatch.countDown();
-                    }
-
-                    @Override
-                    public void onTrackDeselected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        deselectedTrackRef.set(trackInfo);
-                        deselectTrackLatch.countDown();
-                    }
-                });
-        assertNull(controller.getSelectedTrack(testTrackType));
-
-        controller.selectTrack(testTrack);
-        assertTrue(selectTrackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, selectedTrackRef.get());
-        assertEquals(testTrack, controller.getSelectedTrack(testTrackType));
-
-        controller.deselectTrack(testTrack);
-        assertTrue(deselectTrackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, deselectedTrackRef.get());
-        assertNull(controller.getSelectedTrack(testTrackType));
-    }
-
-    /**
-     * It tests {@link MediaController#registerExtraCallback(Executor, ControllerCallback)} and
-     * {@link MediaController#unregisterExtraCallback(ControllerCallback)}.
-     */
-    @Test
-    public void registerExtraCallback() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, null);
-        ControllerCallback testCallback1 = new ControllerCallback() {};
-        ControllerCallback testCallback2 = new ControllerCallback() {};
-
-        List<Pair<ControllerCallback, Executor>> callbacks =
-                controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(0, callbacks.size());
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback1, callbacks.get(0).first);
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-
-        controller.unregisterExtraCallback(testCallback2);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback2);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(2, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback1, callbacks.get(0).first);
-        assertNotNull(callbacks.get(1));
-        assertSame(testCallback2, callbacks.get(1).first);
-
-        controller.unregisterExtraCallback(testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback2, callbacks.get(0).first);
-    }
-
-    @Test
-    public void notifyControllerCallback() throws InterruptedException {
-        final CountDownLatch primaryLatch = new CountDownLatch(1);
-        ControllerCallback primaryCallback = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                primaryLatch.countDown();
-            }
-        };
-        final CountDownLatch extraLatch1 = new CountDownLatch(1);
-        ControllerCallback extraCallback1 = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                extraLatch1.countDown();
-            }
-        };
-        final CountDownLatch extraLatch2 = new CountDownLatch(1);
-        ControllerCallback extraCallback2 = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                extraLatch2.countDown();
-            }
-        };
-        final MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback1);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback2);
-        controller.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onPlaybackCompleted(controller);
-            }
-        });
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(extraLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(extraLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void futuresCompleted_AllowedCommandsChange() throws Exception {
-        RemoteMediaSession session = mRemoteSession;
-        MediaController controller = createController(session.getToken());
-
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        SessionCommand fastForwardCommand = new SessionCommand(
-                SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD);
-        SessionCommand customCommand = new SessionCommand("custom", null);
-
-        int trials = 100;
-        CountDownLatch latch = new CountDownLatch(trials * 2);
-
-        for (int trial = 0; trial < trials; trial++) {
-            if (trial % 2 == 0) {
-                builder.addCommand(fastForwardCommand);
-                builder.addCommand(customCommand);
-            } else {
-                builder.removeCommand(fastForwardCommand);
-                builder.removeCommand(customCommand);
-            }
-            session.setAllowedCommands(builder.build());
-
-            controller.fastForward()
-                    .addListener(latch::countDown, Runnable::run);
-            controller.sendCustomCommand(customCommand, null)
-                    .addListener(latch::countDown, Runnable::run);
-        }
-
-        assertTrue("All futures should be completed", latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void play_returnsSessionResultWithMediaItem() throws Exception {
-        RemoteMediaSession session = mRemoteSession;
-        session.getMockPlayer().createAndSetFakePlaylist(/* size= */ 1);
-        session.getMockPlayer().setCurrentMediaItem(/* index= */ 0);
-
-        MediaController controller = createController(session.getToken());
-        SessionResult result = controller.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotNull(result.getMediaItem());
-    }
-
-    @Ignore("b/202942942")
-    @Test
-    public void getPlaylistMetadata_returnsPlaylistMetadataOfPlayerInSession() throws Exception {
-        MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlaylistMetadata(testMetadata)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        MediaMetadata metadata = controller.getPlaylistMetadata();
-        assertEquals(testMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
-                metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    @Test
-    public void getBufferingState_returnsBufferingStateOfPlayerInSession() throws Exception {
-        int testBufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setBufferingState(testBufferingState)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        int bufferingState = controller.getBufferingState();
-        assertEquals(testBufferingState, bufferingState);
-    }
-
-    RemoteMediaSession createRemoteMediaSession(String id, Bundle tokenExtras) {
-        RemoteMediaSession session = new RemoteMediaSession(id, mContext, tokenExtras);
-        mRemoteSessionList.add(session);
-        return session;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
deleted file mode 100644
index a6670c7..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
+++ /dev/null
@@ -1,142 +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.media2.test.client.tests;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.os.Build;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.client.SurfaceActivity;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaController#setSurface(Surface)}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaController_SurfaceTest extends MediaSessionTestBase {
-    private static final String TAG = "MC_SurfaceTest";
-
-    private Instrumentation mInstrumentation;
-    private SurfaceActivity mActivity;
-    private RemoteMediaSession mRemoteSession;
-
-    @Rule
-    public ActivityTestRule<SurfaceActivity> mActivityRule =
-            new ActivityTestRule<>(SurfaceActivity.class);
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/230354064
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.getActivity();
-
-        setKeepScreenOn();
-
-        mRemoteSession = new RemoteMediaSession(DEFAULT_TEST_NAME, mContext, null);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mRemoteSession != null) {
-            mRemoteSession.cleanUp();
-        }
-    }
-
-    @Test
-    public void setSurface() throws Exception {
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Set
-        final Surface testSurface = mActivity.getSurfaceHolder().getSurface();
-        SessionResult result = controller.setSurface(testSurface)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mRemoteSession.getMockPlayer().surfaceExists());
-
-        // Reset
-        result = controller.setSurface(null).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertFalse(mRemoteSession.getMockPlayer().surfaceExists());
-    }
-
-    private void setKeepScreenOn() throws Exception {
-        try {
-            setKeepScreenOnOrThrow();
-        } catch (Throwable tr) {
-            throw new Exception(tr);
-        }
-    }
-
-    private void setKeepScreenOnOrThrow() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (Build.VERSION.SDK_INT >= 27) {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                    mActivity.setTurnScreenOn(true);
-                    mActivity.setShowWhenLocked(true);
-                    KeyguardManager keyguardManager = (KeyguardManager)
-                            mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-                    keyguardManager.requestDismissKeyguard(mActivity, null);
-                } else {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-                }
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
deleted file mode 100644
index ea17c74..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSessionTestBase {
-    static final int TIMEOUT_MS = 1000;
-    static final int BROWSER_COMPAT_CONNECT_TIMEOUT_MS = 3000;
-
-    static SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private Map<MediaController, TestBrowserCallback> mControllers = new HashMap<>();
-
-    interface TestControllerCallbackInterface {
-        void waitForConnect(boolean expect) throws InterruptedException;
-        void waitForDisconnect(boolean expect) throws InterruptedException;
-        void setRunnableForOnCustomCommand(Runnable runnable);
-    }
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    SyncHandler handler;
-                    synchronized (MediaSessionTestBase.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (MediaController controller : mControllers.keySet()) {
-            controller.close();
-        }
-        mControllers.clear();
-    }
-
-    final MediaController createController(@NonNull MediaSessionCompat.Token token)
-            throws InterruptedException {
-        return createController(token, true, null);
-    }
-
-    final MediaController createController(@NonNull MediaSessionCompat.Token token,
-            boolean waitForConnect, @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestBrowserCallback testCallback = new TestBrowserCallback(callback);
-        MediaController controller = onCreateController(token, testCallback);
-        mControllers.put(controller, testCallback);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    final MediaController createController(@NonNull SessionToken token)
-            throws InterruptedException {
-        return createController(token, true, null, null);
-    }
-
-    final MediaController createController(@NonNull SessionToken token,
-            boolean waitForConnect, @Nullable Bundle connectionHints,
-            @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestBrowserCallback testCallback = new TestBrowserCallback(callback);
-        MediaController controller = onCreateController(token, connectionHints, testCallback);
-        mControllers.put(controller, testCallback);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    final TestControllerCallbackInterface getTestControllerCallbackInterface(
-            MediaController controller) {
-        return mControllers.get(controller);
-    }
-
-    final void waitForConnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForConnect(expected);
-    }
-
-    final void waitForDisconnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForDisconnect(expected);
-    }
-
-    final void setRunnableForOnCustomCommand(MediaController controller,
-            Runnable runnable) {
-        getTestControllerCallbackInterface(controller).setRunnableForOnCustomCommand(runnable);
-    }
-
-    MediaController onCreateController(@NonNull final MediaSessionCompat.Token token,
-            @NonNull final TestBrowserCallback callback) throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                controller.set(new MediaController.Builder(mContext)
-                        .setSessionCompatToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback)
-                        .build());
-            }
-        });
-        return controller.get();
-    }
-
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints, @NonNull final TestBrowserCallback callback)
-            throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaController.Builder builder = new MediaController.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java
deleted file mode 100644
index c49ed88..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.Context;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.media2.test.client.RemoteMediaSessionCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test {@link RemoteMediaSessionCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaSessionCompatTest {
-
-    private Context mContext;
-    private RemoteMediaSessionCompat mRemoteSessionCompat;
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mRemoteSessionCompat = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, mContext);
-    }
-
-    @After
-    public void cleanUp() {
-        mRemoteSessionCompat.cleanUp();
-    }
-
-    @Test
-    @SmallTest
-    public void gettingToken() {
-        MediaSessionCompat.Token token = mRemoteSessionCompat.getSessionToken();
-        assertNotNull(token);
-    }
-
-    @Test
-    @SmallTest
-    public void creatingControllerCompat() throws Exception {
-        MediaSessionCompat.Token token = mRemoteSessionCompat.getSessionToken();
-        assertNotNull(token);
-        MediaControllerCompat controller = new MediaControllerCompat(mContext, token);
-        assertEquals(SERVICE_PACKAGE_NAME, controller.getPackageName());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java
deleted file mode 100644
index 2a3fe9c..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.Executor;
-
-/** Test {@link RemoteMediaSession}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaSessionTest {
-
-    private Context mContext;
-    private RemoteMediaSession mRemoteSession2;
-    private Bundle mTokenExtras;
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mTokenExtras = TestUtils.createTestBundle();
-        mRemoteSession2 = new RemoteMediaSession(DEFAULT_TEST_NAME, mContext, mTokenExtras);
-    }
-
-    @After
-    public void cleanUp() {
-        if (mRemoteSession2 != null) {
-            mRemoteSession2.cleanUp();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void gettingToken() {
-        SessionToken token = mRemoteSession2.getToken();
-        assertNotNull(token);
-        assertEquals(SERVICE_PACKAGE_NAME, token.getPackageName());
-        assertTrue(TestUtils.equals(mTokenExtras, token.getExtras()));
-    }
-
-    @Test
-    @SmallTest
-    public void creatingController() {
-        SessionToken token = mRemoteSession2.getToken();
-        assertNotNull(token);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(token)
-                .setControllerCallback(new Executor() {
-                    @Override
-                    public void execute(Runnable command) {
-                        command.run();
-                    }
-                }, new MediaController.ControllerCallback() {})
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
deleted file mode 100644
index dee07c0..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
+++ /dev/null
@@ -1,242 +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.media2.test.client.tests;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.os.Bundle;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.tests.MediaSessionTestBase.TestControllerCallbackInterface;
-import androidx.media2.test.common.TestUtils;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A proxy class for {@link BrowserCallback} which implements
- * {@link TestControllerCallbackInterface}.
- */
-public class TestBrowserCallback extends BrowserCallback
-        implements TestControllerCallbackInterface {
-
-    public final ControllerCallback mCallbackProxy;
-    public final CountDownLatch connectLatch = new CountDownLatch(1);
-    public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-    @GuardedBy("this")
-    private Runnable mOnCustomCommandRunnable;
-
-    TestBrowserCallback(@Nullable ControllerCallback callbackProxy) {
-        mCallbackProxy = callbackProxy == null ? new BrowserCallback() {} : callbackProxy;
-    }
-
-    @CallSuper
-    @Override
-    public void onConnected(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        connectLatch.countDown();
-        mCallbackProxy.onConnected(controller, commands);
-    }
-
-    @CallSuper
-    @Override
-    public void onDisconnected(@NonNull MediaController controller) {
-        disconnectLatch.countDown();
-        mCallbackProxy.onDisconnected(controller);
-    }
-
-    @Override
-    public void waitForConnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(connectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(connectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Override
-    public void waitForDisconnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(disconnectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(disconnectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @NonNull
-    @Override
-    public SessionResult onCustomCommand(@NonNull MediaController controller,
-            @NonNull SessionCommand command, Bundle args) {
-        synchronized (this) {
-            if (mOnCustomCommandRunnable != null) {
-                mOnCustomCommandRunnable.run();
-            }
-        }
-        return mCallbackProxy.onCustomCommand(controller, command, args);
-    }
-
-    @Override
-    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-            @NonNull MediaController.PlaybackInfo info) {
-        mCallbackProxy.onPlaybackInfoChanged(controller, info);
-    }
-
-    @Override
-    public int onSetCustomLayout(@NonNull MediaController controller,
-            @NonNull List<CommandButton> layout) {
-        return mCallbackProxy.onSetCustomLayout(controller, layout);
-    }
-
-    @Override
-    public void onAllowedCommandsChanged(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        mCallbackProxy.onAllowedCommandsChanged(controller, commands);
-    }
-
-    @Override
-    public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-        mCallbackProxy.onPlayerStateChanged(controller, state);
-    }
-
-    @Override
-    public void onSeekCompleted(@NonNull MediaController controller, long position) {
-        mCallbackProxy.onSeekCompleted(controller, position);
-    }
-
-    @Override
-    public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
-        mCallbackProxy.onPlaybackSpeedChanged(controller, speed);
-    }
-
-    @Override
-    public void onBufferingStateChanged(@NonNull MediaController controller,
-            @NonNull MediaItem item, int state) {
-        mCallbackProxy.onBufferingStateChanged(controller, item, state);
-    }
-
-    @Override
-    public void onCurrentMediaItemChanged(@NonNull MediaController controller, MediaItem item) {
-        mCallbackProxy.onCurrentMediaItemChanged(controller, item);
-    }
-
-    @Override
-    public void onPlaylistChanged(@NonNull MediaController controller,
-            List<MediaItem> list, MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistChanged(controller, list, metadata);
-    }
-
-    @Override
-    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-            MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistMetadataChanged(controller, metadata);
-    }
-
-    @Override
-    public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-        mCallbackProxy.onShuffleModeChanged(controller, shuffleMode);
-    }
-
-    @Override
-    public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-        mCallbackProxy.onRepeatModeChanged(controller, repeatMode);
-    }
-
-    @Override
-    public void onPlaybackCompleted(@NonNull MediaController controller) {
-        mCallbackProxy.onPlaybackCompleted(controller);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, item, videoSize);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, videoSize);
-    }
-
-    @Override
-    public void onTracksChanged(@NonNull MediaController controller,
-            @NonNull List<SessionPlayer.TrackInfo> tracks) {
-        mCallbackProxy.onTracksChanged(controller, tracks);
-    }
-
-    @Override
-    public void onTrackSelected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackSelected(controller, trackInfo);
-    }
-
-    @Override
-    public void onTrackDeselected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackDeselected(controller, trackInfo);
-    }
-
-    @Override
-    public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
-        mCallbackProxy.onSubtitleData(controller, item, track, data);
-    }
-
-    @Override
-    public void onChildrenChanged(@NonNull MediaBrowser browser, @NonNull String parentId,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onChildrenChanged(
-                browser, parentId, itemCount, params);
-    }
-
-    @Override
-    public void onSearchResultChanged(@NonNull MediaBrowser browser, @NonNull String query,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onSearchResultChanged(
-                browser, query, itemCount, params);
-    }
-
-    @Override
-    public void setRunnableForOnCustomCommand(Runnable runnable) {
-        synchronized (this) {
-            mOnCustomCommandRunnable = runnable;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml b/media2/media2-session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml
deleted file mode 100644
index 7945f97..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:keepScreenOn="true">
-    <SurfaceView
-        android:id="@+id/surface_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </SurfaceView>
-</LinearLayout>
diff --git a/media2/media2-session/version-compat-tests/current/service/build.gradle b/media2/media2-session/version-compat-tests/current/service/build.gradle
deleted file mode 100644
index 2984c33..0000000
--- a/media2/media2-session/version-compat-tests/current/service/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2018 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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    androidTestImplementation(project(":media2:media2-session"))
-    androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-}
-
-android {
-    namespace "androidx.media2.test.service"
-}
-
-androidx {
-    failOnDeprecationWarnings = false
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
deleted file mode 100644
index 1b23246..0000000
--- a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index d06e43a..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application>
-        <receiver
-            android:name="androidx.media.session.MediaButtonReceiver"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
-            </intent-filter>
-        </receiver>
-
-        <service
-            android:name="androidx.media2.test.service.MediaSessionProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA2_SESSION" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MediaSessionCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_SESSION_COMPAT" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaSessionService"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaSessionService" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaLibraryService"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaLibraryService" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaBrowserServiceCompat"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
-            </intent-filter>
-        </service>
-
-    </application>
-
-    <queries>
-        <package android:name="androidx.media2.test.client.test" />
-    </queries>
-
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-</manifest>
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java
deleted file mode 100644
index 7c1e23d..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_METADATA_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_QUEUE;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.test.common.IRemoteMediaSessionCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * A Service that creates {@link MediaSessionCompat} and calls its methods according to the
- * client app's requests.
- */
-public class MediaSessionCompatProviderService extends Service {
-    private static final String TAG = "MediaSessionCompatProviderService";
-
-    Map<String, MediaSessionCompat> mSessionMap = new HashMap<>();
-    RemoteMediaSessionCompatStub mSessionBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mSessionBinder = new RemoteMediaSessionCompatStub();
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_SESSION_COMPAT.equals(intent.getAction())) {
-            return mSessionBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaSessionCompat session : mSessionMap.values()) {
-            session.release();
-        }
-    }
-
-    private class RemoteMediaSessionCompatStub extends IRemoteMediaSessionCompat.Stub {
-        @Override
-        public void create(final String sessionTag) throws RemoteException {
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaSessionCompat session = new MediaSessionCompat(
-                                MediaSessionCompatProviderService.this, sessionTag);
-                        mSessionMap.put(sessionTag, session);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaSessionCompat", ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaSessionCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public Bundle getSessionToken(String sessionTag) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            Bundle result = new Bundle();
-            result.putParcelable(KEY_SESSION_COMPAT_TOKEN, session.getSessionToken());
-            return result;
-        }
-
-        @Override
-        public void setPlaybackToLocal(String sessionTag, int stream) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setPlaybackToLocal(stream);
-        }
-
-        @Override
-        public void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume,
-                int currentVolume) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setPlaybackToRemote(new VolumeProviderCompat(
-                    volumeControl, maxVolume, currentVolume) {
-                @Override
-                public void onSetVolumeTo(int volume) {
-                    setCurrentVolume(volume);
-                }
-
-                @Override
-                public void onAdjustVolume(int direction) {
-                    setCurrentVolume(getCurrentVolume() + direction);
-                }
-            });
-        }
-
-        @Override
-        public void release(String sessionTag) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.release();
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setPlaybackState(String sessionTag, Bundle stateBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            stateBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            PlaybackStateCompat state = stateBundle.getParcelable(KEY_PLAYBACK_STATE_COMPAT);
-            session.setPlaybackState(state);
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setMetadata(String sessionTag, Bundle metadataBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            metadataBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            MediaMetadataCompat metadata = metadataBundle.getParcelable(KEY_METADATA_COMPAT);
-            session.setMetadata(metadata);
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setQueue(String sessionTag, Bundle queueBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            queueBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            List<QueueItem> queue = queueBundle.getParcelableArrayList(KEY_QUEUE);
-            session.setQueue(queue);
-        }
-
-        @Override
-        public void setQueueTitle(String sessionTag, CharSequence title) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setQueueTitle(title);
-        }
-
-        @Override
-        public void setRepeatMode(String sessionTag, int repeatMode) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setShuffleMode(String sessionTag, int shuffleMode) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void setSessionActivity(String sessionTag, PendingIntent pi) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setSessionActivity(pi);
-        }
-
-        @Override
-        public void setFlags(String sessionTag, int flags) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setFlags(flags);
-        }
-
-        @Override
-        public void setRatingType(String sessionTag, int type) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setRatingType(type);
-        }
-
-        @Override
-        public void sendSessionEvent(String sessionTag, String event, Bundle extras)
-                throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.sendSessionEvent(event, extras);
-        }
-
-        @Override
-        public void setCaptioningEnabled(String sessionTag, boolean enabled)
-                throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setCaptioningEnabled(enabled);
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
deleted file mode 100644
index 1ebbb876..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
+++ /dev/null
@@ -1,603 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_SESSION;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_NULL_ITEM;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERED_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERING_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MAX_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MEDIA_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_SPEED;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
-import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
-import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
-import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.IRemoteMediaSession;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * A Service that creates {@link MediaSession} and calls its methods according to the client app's
- * requests.
- */
-public class MediaSessionProviderService extends Service {
-    private static final String TAG = "MediaSessionProviderService";
-
-    Map<String, MediaSession> mSessionMap = new HashMap<>();
-    RemoteMediaSessionStub mSessionBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mSessionBinder = new RemoteMediaSessionStub();
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA2_SESSION.equals(intent.getAction())) {
-            return mSessionBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaSession session : mSessionMap.values()) {
-            session.close();
-        }
-    }
-
-    private class RemoteMediaSessionStub extends IRemoteMediaSession.Stub {
-        @Override
-        public void create(final String sessionId, final Bundle tokenExtras)
-                throws RemoteException {
-            final MediaSession.Builder builder =
-                    new MediaSession.Builder(MediaSessionProviderService.this, new MockPlayer(0))
-                            .setId(sessionId);
-
-            if (tokenExtras != null) {
-                builder.setExtras(tokenExtras);
-            }
-
-            switch (sessionId) {
-                case TEST_GET_SESSION_ACTIVITY: {
-                    final Intent sessionActivity = new Intent(MediaSessionProviderService.this,
-                            MockActivity.class);
-                    PendingIntent pendingIntent = PendingIntent.getActivity(
-                            MediaSessionProviderService.this,
-                            0 /* requestCode */,
-                            sessionActivity,
-                            Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-                    builder.setSessionActivity(pendingIntent);
-                    break;
-                }
-                case TEST_CONTROLLER_CALLBACK_SESSION_REJECTS: {
-                    builder.setSessionCallback(mExecutor, new MediaSession.SessionCallback() {
-                        @Override
-                        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                                @NonNull MediaSession.ControllerInfo controller) {
-                            return null;
-                        }
-                    });
-                    break;
-                }
-                case TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST: {
-                    builder.setSessionCallback(mExecutor, new MediaSession.SessionCallback() {
-                        @Override
-                        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                                @NonNull MediaSession.ControllerInfo controller) {
-                            SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                                    .addCommand(new SessionCommand(
-                                            SessionCommand
-                                                    .COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA))
-                                    .build();
-                            return commands;
-                        }
-                    });
-                    break;
-                }
-            }
-
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        MediaSession session = builder.build();
-                        mSessionMap.put(sessionId, session);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaSession", ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaSession methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public ParcelImpl getToken(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            return session != null
-                    ? MediaParcelUtils.toParcelable(session.getToken()) : null;
-        }
-
-        @Override
-        public Bundle getCompatToken(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            return session.getSessionCompat().getSessionToken().toBundle();
-        }
-
-        @Override
-        public void updatePlayer(String sessionId, @NonNull Bundle config) throws RemoteException {
-            config.setClassLoader(MediaSession.class.getClassLoader());
-            if (config != null) {
-                config.setClassLoader(MediaSession.class.getClassLoader());
-            }
-            MediaSession session = mSessionMap.get(sessionId);
-            session.updatePlayer(createMockPlayer(config));
-        }
-
-        @SuppressWarnings("deprecation")
-        private SessionPlayer createMockPlayer(Bundle config) {
-            SessionPlayer player;
-            if (config.containsKey(KEY_VOLUME_CONTROL_TYPE)) {
-                // Remote player
-                player = new MockRemotePlayer(
-                        config.getInt(KEY_VOLUME_CONTROL_TYPE),
-                        config.getInt(KEY_MAX_VOLUME),
-                        config.getInt(KEY_CURRENT_VOLUME));
-            } else {
-                // Local player
-                MockPlayer localPlayer = new MockPlayer(0);
-                localPlayer.mLastPlayerState = config.getInt(KEY_PLAYER_STATE);
-                localPlayer.mLastBufferingState = config.getInt(KEY_BUFFERING_STATE);
-                localPlayer.mCurrentPosition = config.getLong(KEY_CURRENT_POSITION);
-                localPlayer.mBufferedPosition = config.getLong(KEY_BUFFERED_POSITION);
-                localPlayer.mPlaybackSpeed = config.getFloat(KEY_PLAYBACK_SPEED);
-                localPlayer.mShuffleMode = config.getInt(KEY_SHUFFLE_MODE);
-                localPlayer.mRepeatMode = config.getInt(KEY_REPEAT_MODE);
-
-                ParcelImplListSlice listSlice = config.getParcelable(KEY_PLAYLIST);
-                if (listSlice != null) {
-                    localPlayer.mPlaylist = MediaTestUtils.convertToMediaItems(listSlice.getList());
-                }
-                localPlayer.mCurrentMediaItem =
-                        MediaTestUtils.convertToMediaItem(config.getParcelable(KEY_MEDIA_ITEM));
-                localPlayer.mMetadata = ParcelUtils.getVersionedParcelable(config,
-                        KEY_PLAYLIST_METADATA);
-                ParcelImpl videoSize = config.getParcelable(KEY_VIDEO_SIZE);
-                if (videoSize != null) {
-                    localPlayer.mVideoSize = MediaParcelUtils.fromParcelable(videoSize);
-                }
-                List<SessionPlayer.TrackInfo> trackInfos =
-                        ParcelUtils.getVersionedParcelableList(config, KEY_TRACK_INFO);
-                localPlayer.mTracks = trackInfos;
-                player = localPlayer;
-            }
-            ParcelImpl attrImpl = config.getParcelable(KEY_AUDIO_ATTRIBUTES);
-            if (attrImpl != null) {
-                AudioAttributesCompat attr = MediaParcelUtils.fromParcelable(attrImpl);
-                if (attr != null) {
-                    player.setAudioAttributes(attr);
-                }
-            }
-            return player;
-        }
-
-        @Override
-        public void broadcastCustomCommand(String sessionId, ParcelImpl command, Bundle args)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            session.broadcastCustomCommand(
-                    (SessionCommand) MediaParcelUtils.fromParcelable(command), args);
-        }
-
-        @Override
-        public void sendCustomCommand(String sessionId, Bundle controller, ParcelImpl command,
-                Bundle args) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            session.sendCustomCommand(info,
-                    (SessionCommand) MediaParcelUtils.fromParcelable(command),
-                    args);
-        }
-
-        @Override
-        public void close(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            session.close();
-        }
-
-        @Override
-        public void setAllowedCommands(String sessionId, Bundle controller, ParcelImpl commands)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            session.setAllowedCommands(info,
-                    (SessionCommandGroup) MediaParcelUtils.fromParcelable(commands));
-        }
-
-        @Override
-        public void setCustomLayout(String sessionId, Bundle controller, List<ParcelImpl> layout)
-                throws RemoteException {
-            if (layout == null) {
-                return;
-            }
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            List<MediaSession.CommandButton> buttons = new ArrayList<>();
-            for (ParcelImpl parcel : layout) {
-                if (parcel != null) {
-                    buttons.add((MediaSession.CommandButton) ParcelUtils.fromParcelable(parcel));
-                }
-            }
-            session.setCustomLayout(info, buttons);
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MockPlayer methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void setPlayerState(String sessionId, int state) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mLastPlayerState = state;
-        }
-
-        @Override
-        public void setCurrentPosition(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentPosition = pos;
-        }
-
-        @Override
-        public void setBufferedPosition(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mBufferedPosition = pos;
-        }
-
-        @Override
-        public void setDuration(String sessionId, long duration) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mDuration = duration;
-        }
-
-        @Override
-        public void setPlaybackSpeed(String sessionId, float speed) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mPlaybackSpeed = speed;
-        }
-
-        @Override
-        public void notifySeekCompleted(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifySeekCompleted(pos);
-        }
-
-        @Override
-        public void notifyBufferingStateChanged(String sessionId, int itemIndex, int buffState)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyBufferingStateChanged(
-                    player.getPlaylist().get(itemIndex), buffState);
-        }
-
-        @Override
-        public void notifyPlayerStateChanged(String sessionId, int state) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlayerStateChanged(state);
-        }
-
-        @Override
-        public void notifyPlaybackSpeedChanged(String sessionId, float speed)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaybackSpeedChanged(speed);
-        }
-
-        @Override
-        public void notifyCurrentMediaItemChanged(String sessionId, int index)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            switch (index) {
-                case INDEX_FOR_UNKONWN_ITEM:
-                    player.notifyCurrentMediaItemChanged(
-                            new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1)).build());
-                    break;
-                case INDEX_FOR_NULL_ITEM:
-                    player.notifyCurrentMediaItemChanged(null);
-                    break;
-                default:
-                    player.notifyCurrentMediaItemChanged(
-                            player.getPlaylist().get(index));
-                    break;
-            }
-        }
-
-        @Override
-        public void notifyAudioAttributesChanged(String sessionId, ParcelImpl attrs)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyAudioAttributesChanged(
-                    (AudioAttributesCompat) MediaParcelUtils.fromParcelable(attrs));
-        }
-
-        @Override
-        public void notifyTrackInfoChanged(String sessionId, List<ParcelImpl> trackInfoParcelList)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            List<SessionPlayer.TrackInfo> tracks =
-                    MediaParcelUtils.fromParcelableList(trackInfoParcelList);
-            player.notifyTracksChanged(tracks);
-        }
-
-        @Override
-        public void notifyTrackSelected(String sessionId, ParcelImpl trackInfo)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyTrackSelected(
-                    (SessionPlayer.TrackInfo) MediaParcelUtils.fromParcelable(trackInfo));
-        }
-
-        @Override
-        public void notifyTrackDeselected(String sessionId, ParcelImpl trackInfo)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyTrackDeselected(
-                    (SessionPlayer.TrackInfo) MediaParcelUtils.fromParcelable(trackInfo));
-        }
-
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MockPlaylistAgent methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void setPlaylist(String sessionId, List<ParcelImpl> playlist)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mPlaylist = MediaTestUtils.convertToMediaItems(playlist);
-        }
-
-        @Override
-        public void setCurrentMediaItemMetadata(String sessionId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentMediaItem.setMetadata(MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void createAndSetFakePlaylist(String sessionId, int size) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-
-            List<MediaItem> list = new ArrayList<>();
-            for (int i = 0; i < size; i++) {
-                list.add(new MediaItem.Builder()
-                        .setMetadata(new MediaMetadata.Builder()
-                                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                        TestUtils.getMediaIdInFakeList(i)).build())
-                        .build());
-            }
-            player.mPlaylist = list;
-        }
-
-        @Override
-        public void setPlaylistWithFakeItem(String sessionId, List<ParcelImpl> playlist)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-
-            List<MediaItem> list = new ArrayList<>();
-            for (ParcelImpl parcel : playlist) {
-                MediaItem item = MediaParcelUtils.fromParcelable(parcel);
-                list.add(new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                        .setMetadata(item.getMetadata())
-                        .build());
-            }
-            player.mPlaylist = list;
-        }
-
-        @Override
-        public void setPlaylistMetadata(String sessionId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mMetadata = MediaParcelUtils.fromParcelable(metadata);
-        }
-
-        @Override
-        public void setPlaylistMetadataWithLargeBitmaps(String sessionId, int count, int width,
-                int height) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
-            MediaMetadata.Builder builder = new MediaMetadata.Builder();
-            for (int i = 0; i < count; i++) {
-                builder.putBitmap(TestUtils.getMediaIdInFakeList(i), bitmap);
-            }
-            player.mMetadata = builder.build();
-        }
-
-        @Override
-        public void setShuffleMode(String sessionId, int shuffleMode)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mShuffleMode = shuffleMode;
-        }
-
-        @Override
-        public void setRepeatMode(String sessionId, int repeatMode) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mRepeatMode = repeatMode;
-        }
-
-        @Override
-        public void setCurrentMediaItem(String sessionId, int index)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentMediaItem = player.mPlaylist.get(index);
-        }
-
-        @Override
-        public void notifyPlaylistChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaylistChanged();
-        }
-
-        @Override
-        public void notifyPlaylistMetadataChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaylistMetadataChanged();
-        }
-
-        @Override
-        public void notifyShuffleModeChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyShuffleModeChanged();
-        }
-
-        @Override
-        public void notifyRepeatModeChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyRepeatModeChanged();
-        }
-
-        @Override
-        public void notifyPlaybackCompleted(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaybackCompleted();
-        }
-
-        @Override
-        public void notifyVideoSizeChanged(String sessionId, ParcelImpl videoSize) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            VideoSize videoSizeObj = MediaParcelUtils.fromParcelable(videoSize);
-            player.notifyVideoSizeChanged(videoSizeObj);
-        }
-
-        @Override
-        public boolean surfaceExists(String sessionId) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            return player.surfaceExists();
-        }
-
-        @Override
-        public void notifySubtitleData(String sessionId, ParcelImpl item, ParcelImpl track,
-                ParcelImpl data) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            MediaItem itemObj = MediaParcelUtils.fromParcelable(item);
-            SessionPlayer.TrackInfo trackObj = MediaParcelUtils.fromParcelable(track);
-            SubtitleData dataObj = MediaParcelUtils.fromParcelable(data);
-            player.notifySubtitleData(itemObj, trackObj, dataObj);
-        }
-
-        @Override
-        public void notifyVolumeChanged(String sessionId, int volume) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockRemotePlayer player = (MockRemotePlayer) session.getPlayer();
-            player.mCurrentVolume = volume;
-            player.notifyVolumeChanged();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
deleted file mode 100644
index 0550226..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.CommonConstants.KEY_CLIENT_VERSION;
-import static androidx.media2.test.common.CommonConstants.VERSION_TOT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Utilities for tests.
- */
-public final class MediaTestUtils {
-
-    private static final String TAG = "MediaTestUtils";
-
-    // Temporaily commenting out, since we don't have the Mock services yet.
-//    /**
-//     * Finds the session with id in this test package.
-//     *
-//     * @param context
-//     * @param id
-//     * @return
-//     */
-//    public static SessionToken getServiceToken(Context context, String id) {
-//        switch (id) {
-//            case MockMediaSessionService2.ID:
-//                return new SessionToken(context, new ComponentName(
-//                        context.getPackageName(), MockMediaSessionService2.class.getName()));
-//            case MockMediaLibraryService.ID:
-//                return new SessionToken(context, new ComponentName(
-//                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-//        }
-//        fail("Unknown id=" + id);
-//        return null;
-//    }
-
-    /**
-     * Create a playlist for testing purpose
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size list size
-     * @return the newly created playlist
-     */
-    public static List<MediaItem> createPlaylist(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(createMediaItem(caller + "_item_" + (i + 1)));
-        }
-        return list;
-    }
-
-    public static MediaItem createMediaItem(String id) {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(new MediaMetadata.Builder()
-                        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id)
-                        .putLong(MediaMetadata.METADATA_KEY_BROWSABLE,
-                                MediaMetadata.BROWSABLE_TYPE_NONE)
-                        .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                        .build())
-                .build();
-    }
-
-    public static List<String> createMediaIds(int size) {
-        final List<String> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(caller + "_item_" + (i + 1));
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem createMediaItemWithMetadata() {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(createMetadata())
-                .build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
-        return new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(MediaMetadata.METADATA_KEY_BROWSABLE, MediaMetadata.BROWSABLE_TYPE_NONE)
-                .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                .build();
-    }
-
-    /**
-     * Converts the List of {@link ParcelImpl} to the list of {@link MediaItem} with the
-     * {@link #convertToMediaItem(ParcelImpl)}.
-     * <p>
-     * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
-     * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
-     * instance across the process.
-     *
-     * @param list
-     * @return
-     */
-    public static List<MediaItem> convertToMediaItems(List<ParcelImpl> list) {
-        if (list == null) {
-            return null;
-        }
-
-        List<MediaItem> result = new ArrayList<>();
-        for (ParcelImpl parcel : list) {
-            result.add(convertToMediaItem(parcel));
-        }
-        return result;
-    }
-
-    /**
-     * Converts the {@link ParcelImpl} to {@link MediaItem}, by creating {@link UriMediaItem}.
-     * <p>
-     * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
-     * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
-     * instance across the process.
-     *
-     * @param item
-     * @return
-     */
-    public static MediaItem convertToMediaItem(ParcelImpl item) {
-        if (item == null) {
-            return null;
-        }
-        MediaItem mediaItem = MediaParcelUtils.fromParcelable(item);
-        if (mediaItem == null) {
-            return null;
-        }
-        String mediaId = mediaItem.getMediaId();
-        return new UriMediaItem.Builder(Uri.parse("android://" + mediaId))
-                .setStartPosition(mediaItem.getStartPosition())
-                .setEndPosition(mediaItem.getEndPosition())
-                .setMetadata(mediaItem.getMetadata()).build();
-    }
-
-    public static ControllerInfo getTestControllerInfo(MediaSession session) {
-        if (session == null) {
-            return null;
-        }
-        for (ControllerInfo info : session.getConnectedControllers()) {
-            if (CLIENT_PACKAGE_NAME.equals(info.getPackageName())) {
-                return info;
-            }
-        }
-        Log.e(TAG, "Test controller was not found in connected controllers. session=" + session);
-        return null;
-    }
-
-    // Note: It's not assertEquals() to avoid issue with the static import of JUnit's assertEquals.
-    // Otherwise, this API hides the statically imported JUnit's assertEquals and compile will fail.
-    public static void assertMediaMetadataEquals(MediaMetadata expected, MediaMetadata actual) {
-        if (expected == null || actual == null) {
-            assertEquals(expected, actual);
-        } else {
-            Set<String> expectedKeySet = expected.keySet();
-            Set<String> actualKeySet = actual.keySet();
-
-            assertEquals(expectedKeySet, actualKeySet);
-            for (String key : expectedKeySet) {
-                assertEquals(expected.getObject(key), actual.getObject(key));
-            }
-        }
-    }
-
-    public static LibraryParams createLibraryParams() {
-        String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
-
-        Bundle extras = new Bundle();
-        extras.putString(callingTestName, callingTestName);
-        return new LibraryParams.Builder().setExtras(extras).build();
-    }
-
-    public static void assertEqualLibraryParams(LibraryParams a, LibraryParams b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
-        }
-    }
-
-    public static void assertEqualLibraryParams(LibraryParams params, Bundle rootExtras) {
-        if (params == null || rootExtras == null) {
-            assertEquals(params, rootExtras);
-        } else {
-            assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
-            assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
-            assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
-            assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
-        }
-    }
-
-    public static boolean isClientToT() {
-        String clientVersion = InstrumentationRegistry.getArguments()
-                .getString(KEY_CLIENT_VERSION, "");
-        return VERSION_TOT.equals(clientVersion);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java
deleted file mode 100644
index 2489f6e..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.Callback;
-
-import androidx.annotation.GuardedBy;
-import androidx.media.MediaBrowserServiceCompat;
-
-import java.lang.reflect.Method;
-import java.util.List;
-
-/**
- * Mock implementation of the media browser service.
- */
-public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-    private static final String TAG = "MockMediaBrowserServiceCompat";
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static volatile MockMediaBrowserServiceCompat sInstance;
-    @GuardedBy("sLock")
-    private static volatile Proxy sServiceProxy;
-
-    private MediaSessionCompat mSessionCompat;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        synchronized (sLock) {
-            sInstance = this;
-        }
-        mSessionCompat = new MediaSessionCompat(this, TAG);
-        mSessionCompat.setCallback(new Callback() { });
-        mSessionCompat.setActive(true);
-        setSessionToken(mSessionCompat.getSessionToken());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mSessionCompat.release();
-        synchronized (sLock) {
-            sInstance = null;
-            // Note: Don't reset sServiceProxy.
-            //       When a test is finished and its next test is running, this service will be
-            //       destroyed and re-created for the next test. When it happens, onDestroy() may be
-            //       called after the next test's proxy has set because onDestroy() and tests run on
-            //       the different threads.
-            //       So keep sServiceProxy for the next test.
-        }
-    }
-
-    public static MockMediaBrowserServiceCompat getInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    public static void setMediaBrowserServiceProxy(Proxy proxy) {
-        synchronized (sLock) {
-            sServiceProxy = proxy;
-        }
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName) {
-        return isProxyOverridesMethod(methodName, -1);
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName, int paramCount) {
-        synchronized (sLock) {
-            if (sServiceProxy == null) {
-                return false;
-            }
-            Method[] methods = sServiceProxy.getClass().getMethods();
-            if (methods == null) {
-                return false;
-            }
-            for (int i = 0; i < methods.length; i++) {
-                if (methods[i].getName().equals(methodName)) {
-                    if (paramCount < 0
-                            || (methods[i].getParameterTypes() != null
-                            && methods[i].getParameterTypes().length == paramCount)) {
-                        // Found method. Check if it overrides
-                        return methods[i].getDeclaringClass() != Proxy.class;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        if (!CLIENT_PACKAGE_NAME.equals(clientPackageName)) {
-            // Test only -- reject any other request.
-            return null;
-        }
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onGetRoot")) {
-                return sServiceProxy.onGetRoot(clientPackageName, clientUid, rootHints);
-            }
-        }
-        return new BrowserRoot("stub", null);
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 2)) {
-                sServiceProxy.onLoadChildren(parentId, result);
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result, Bundle options) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 3)) {
-                sServiceProxy.onLoadChildren(parentId, result, options);
-                return;
-            }
-        }
-        super.onLoadChildren(parentId, result, options);
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadItem")) {
-                sServiceProxy.onLoadItem(itemId, result);
-                return;
-            }
-        }
-        super.onLoadItem(itemId, result);
-    }
-
-    @Override
-    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onSearch")) {
-                sServiceProxy.onSearch(query, extras, result);
-                return;
-            }
-        }
-        super.onSearch(query, extras, result);
-    }
-
-    @Override
-    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onCustomAction")) {
-                sServiceProxy.onCustomAction(action, extras, result);
-                return;
-            }
-        }
-        super.onCustomAction(action, extras, result);
-    }
-
-    public static class Proxy {
-        public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-            return new BrowserRoot("stub", null);
-        }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result) { }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                Bundle options) { }
-
-        public void onLoadItem(String itemId, Result<MediaItem> result) { }
-
-        public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) { }
-
-        public void onCustomAction(String action, Bundle extras, Result<Bundle> result) { }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
deleted file mode 100644
index cb8b580..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_MIXED;
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_TIME_IN_MS;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.service.MediaTestUtils.assertEqualLibraryParams;
-
-import android.app.Service;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.LibraryResult;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-public class MockMediaLibraryService extends MediaLibraryService {
-    /**
-     * ID of the session that this service will create.
-     */
-    public static final String ID = "TestLibrary";
-    public static final MediaItem ROOT_ITEM = new MediaItem.Builder()
-            .setMetadata(new MediaMetadata.Builder()
-                    .putString(METADATA_KEY_MEDIA_ID, ROOT_ID)
-                    .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                    .putLong(METADATA_KEY_PLAYABLE, 0)
-                    .build()).build();
-    public static final LibraryParams ROOT_PARAMS = new LibraryParams.Builder()
-            .setExtras(ROOT_EXTRAS).build();
-    private static final LibraryParams NOTIFY_CHILDREN_CHANGED_PARAMS = new LibraryParams.Builder()
-            .setExtras(NOTIFY_CHILDREN_CHANGED_EXTRAS).build();
-
-    private static final String TAG = "MockMediaLibrarySvc2";
-
-    @GuardedBy("MockMediaLibraryService.class")
-    private static boolean sAssertLibraryParams;
-    @GuardedBy("MockMediaLibraryService.class")
-    private static LibraryParams sExpectedParams;
-
-    MediaLibrarySession mSession;
-    SyncHandler mHandler;
-    HandlerThread mHandlerThread;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new SyncHandler(mHandlerThread.getLooper());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        synchronized (MockMediaLibraryService.class) {
-            sAssertLibraryParams = false;
-            sExpectedParams = null;
-        }
-        mHandler.getLooper().quitSafely();
-        mHandler = null;
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Override
-    public MediaLibrarySession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return (MediaLibrarySession) onGetSessionHandler.onGetSession(controllerInfo);
-        }
-
-        final MockPlayer player = new MockPlayer(1);
-        final Executor executor = new Executor() {
-            @Override
-            public void execute(Runnable runnable) {
-                mHandler.post(runnable);
-            }
-        };
-
-        MediaLibrarySessionCallback callback = registry.getSessionCallback();
-        mSession = new MediaLibrarySession.Builder(MockMediaLibraryService.this, player, executor,
-                callback != null ? callback : new TestLibrarySessionCallback())
-                .setId(ID)
-                .setThrowsWhenInvalidReturn(false)
-                .build();
-        return mSession;
-    }
-
-    /**
-     * This changes the visibility of {@link Service#attachBaseContext(Context)} to public.
-     * This is a workaround for creating {@link MediaLibrarySession} without starting a service.
-     */
-    @Override
-    public void attachBaseContext(Context base) {
-        super.attachBaseContext(base);
-    }
-
-    public static void setAssertLibraryParams(LibraryParams expectedParams) {
-        synchronized (MockMediaLibraryService.class) {
-            sAssertLibraryParams = true;
-            sExpectedParams = expectedParams;
-        }
-    }
-
-    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
-
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            if (!CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                return null;
-            }
-            SessionCommandGroup group = super.onConnect(session, controller);
-            SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder(group);
-            builder.addCommand(new SessionCommand(CUSTOM_ACTION, null));
-            builder.addCommand(new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null));
-            return builder.build();
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetLibraryRoot(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, LibraryParams params) {
-            assertLibraryParams(params);
-            return new LibraryResult(RESULT_SUCCESS, ROOT_ITEM, ROOT_PARAMS);
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetItem(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String mediaId) {
-            switch (mediaId) {
-                case MEDIA_ID_GET_ITEM:
-                    return new LibraryResult(RESULT_SUCCESS, createMediaItem(mediaId), null);
-                case MEDIA_ID_GET_NULL_ITEM:
-                    return new LibraryResult(RESULT_SUCCESS);
-                case MEDIA_ID_GET_INVALID_ITEM:
-                    // No browsable
-                    MediaMetadata metadata =  new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                            .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                            .build();
-                    return new LibraryResult(RESULT_SUCCESS,
-                            new MediaItem.Builder().setMetadata(metadata).build(), null);
-            }
-            return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetChildren(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String parentId, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (PARENT_ID.equals(parentId)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), null);
-            } else if (PARENT_ID_LONG_LIST.equals(parentId)) {
-                List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
-                MediaItem.Builder builder = new MediaItem.Builder();
-                for (int i = 0; i < LONG_LIST_COUNT; i++) {
-                    list.add(builder
-                            .setMetadata(new MediaMetadata.Builder()
-                                    .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                            TestUtils.getMediaIdInFakeList(i))
-                                    .putLong(MediaMetadata.METADATA_KEY_BROWSABLE,
-                                            MediaMetadata.BROWSABLE_TYPE_NONE)
-                                    .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                                    .build())
-                            .build());
-                }
-                return new LibraryResult(RESULT_SUCCESS, list, null);
-            } else if (PARENT_ID_ERROR.equals(parentId)) {
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-            // Includes the case of PARENT_ID_NO_CHILDREN.
-            return new LibraryResult(RESULT_SUCCESS, new ArrayList<MediaItem>(), null);
-        }
-
-        @Override
-        public int onSearch(@NonNull MediaLibrarySession session,
-                @NonNull final ControllerInfo controllerInfo, @NonNull final String query,
-                final LibraryParams params) {
-            assertLibraryParams(params);
-            if (SEARCH_QUERY.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
-                        params);
-            } else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, LONG_LIST_COUNT, params);
-            } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
-                // Searching takes some time. Notify after 5 seconds.
-                Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSession.notifySearchResultChanged(
-                                controllerInfo, query, SEARCH_RESULT_COUNT, params);
-                    }
-                }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
-            } else {
-                // SEARCH_QUERY_EMPTY_RESULT and SEARCH_QUERY_ERROR will be handled here.
-                mSession.notifySearchResultChanged(controllerInfo, query, 0, params);
-            }
-            return RESULT_SUCCESS;
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetSearchResult(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull String query, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (SEARCH_QUERY.equals(query)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(SEARCH_RESULT, page, pageSize), null);
-            } else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
-                List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
-                MediaItem.Builder builder = new MediaItem.Builder();
-                for (int i = 0; i < LONG_LIST_COUNT; i++) {
-                    list.add(createMediaItem(TestUtils.getMediaIdInFakeList(i)));
-                }
-                return new LibraryResult(RESULT_SUCCESS, list, null);
-            } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
-                return new LibraryResult(RESULT_SUCCESS, new ArrayList<MediaItem>(), null);
-            } else {
-                // SEARCH_QUERY_ERROR will be handled here.
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-        }
-
-        @Override
-        public int onSubscribe(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String parentId,
-                LibraryParams params) {
-            assertLibraryParams(params);
-            final String unsubscribedId = "unsubscribedId";
-            switch (parentId) {
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL:
-                    mSession.notifyChildrenChanged(
-                            parentId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE:
-                    mSession.notifyChildrenChanged(
-                            MediaTestUtils.getTestControllerInfo(mSession),
-                            parentId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID:
-                    mSession.notifyChildrenChanged(
-                            unsubscribedId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID:
-                    mSession.notifyChildrenChanged(
-                            MediaTestUtils.getTestControllerInfo(mSession),
-                            unsubscribedId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-            }
-            return RESULT_ERROR_BAD_VALUE;
-        }
-
-        @NonNull
-        @Override
-        public SessionResult onCustomCommand(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand sessionCommand,
-                Bundle args) {
-            switch (sessionCommand.getCustomAction()) {
-                case CUSTOM_ACTION:
-                    return new SessionResult(
-                            RESULT_SUCCESS, CUSTOM_ACTION_EXTRAS);
-                case CUSTOM_ACTION_ASSERT_PARAMS:
-                    LibraryParams params = ParcelUtils.getVersionedParcelable(args,
-                            CUSTOM_ACTION_ASSERT_PARAMS);
-                    setAssertLibraryParams(params);
-                    return new SessionResult(RESULT_SUCCESS, null);
-            }
-            return new SessionResult(RESULT_ERROR_BAD_VALUE, null);
-        }
-
-        private void assertLibraryParams(LibraryParams params) {
-            synchronized (MockMediaLibraryService.class) {
-                if (sAssertLibraryParams) {
-                    assertEqualLibraryParams(sExpectedParams, params);
-                }
-            }
-        }
-    }
-
-    private List<MediaItem> getPaginatedResult(List<String> items, int page, int pageSize) {
-        if (items == null) {
-            return null;
-        } else if (items.size() == 0) {
-            return new ArrayList<>();
-        }
-
-        final int totalItemCount = items.size();
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, totalItemCount);
-
-        List<String> paginatedMediaIdList = new ArrayList<>();
-        try {
-            // The case of (fromIndex >= totalItemCount) will throw exception below.
-            paginatedMediaIdList = items.subList(fromIndex, toIndex);
-        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
-            Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
-                    + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
-        }
-
-        // Create a list of MediaItem from the list of media IDs.
-        List<MediaItem> result = new ArrayList<>();
-        for (int i = 0; i < paginatedMediaIdList.size(); i++) {
-            result.add(createMediaItem(paginatedMediaIdList.get(i)));
-        }
-        return result;
-    }
-
-    private MediaItem createMediaItem(String mediaId) {
-        MediaMetadata metadata =  new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(MediaMetadata.METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                .build();
-        return new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java
deleted file mode 100644
index 7d23701..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-
-import java.util.concurrent.Executors;
-
-public class MockMediaSessionService extends MediaSessionService {
-    /**
-     * ID of the session that this service will create.
-     */
-    public static final String ID = "TestSession";
-    public MediaSession mSession2;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Override
-    public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return onGetSessionHandler.onGetSession(controllerInfo);
-        }
-
-        if (mSession2 == null) {
-            MediaSession.SessionCallback callback = registry.getSessionCallback();
-            mSession2 = new MediaSession.Builder(MockMediaSessionService.this, new MockPlayer(0))
-                    .setId(ID)
-                    .setSessionCallback(Executors.newSingleThreadExecutor(),
-                            callback != null ? callback : new TestSessionCallback())
-                    .build();
-        }
-        return mSession2;
-    }
-
-    private class TestSessionCallback extends MediaSession.SessionCallback {
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull MediaSession.ControllerInfo controller) {
-            if (TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName())) {
-                return super.onConnect(session, controller);
-            }
-            return null;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
deleted file mode 100644
index 662fefd..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
+++ /dev/null
@@ -1,652 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * A mock implementation of {@link SessionPlayer} for testing.
- */
-public class MockPlayer extends SessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mCountDownLatch;
-    public final boolean mChangePlayerStateWithTransportControl;
-
-    public boolean mPlayCalled;
-    public boolean mPauseCalled;
-    public boolean mPrepareCalled;
-    public boolean mSeekToCalled;
-    public boolean mSetPlaybackSpeedCalled;
-    public long mSeekPosition;
-    public long mCurrentPosition;
-    public long mBufferedPosition;
-    public float mPlaybackSpeed = 1.0f;
-    @PlayerState
-    public int mLastPlayerState;
-    @BuffState
-    public int mLastBufferingState;
-    public long mDuration;
-    public List<TrackInfo> mTracks;
-
-    public List<MediaItem> mPlaylist;
-    public MediaMetadata mMetadata;
-    public MediaItem mCurrentMediaItem;
-    public MediaItem mItem;
-    public int mIndex = -1;
-    public int mIndex2 = -1;
-    @RepeatMode
-    public int mRepeatMode = -1;
-    @ShuffleMode
-    public int mShuffleMode = -1;
-    public VideoSize mVideoSize = new VideoSize(0, 0);
-    public Surface mSurface;
-
-    public boolean mSetPlaylistCalled;
-    public boolean mUpdatePlaylistMetadataCalled;
-    public boolean mAddPlaylistItemCalled;
-    public boolean mRemovePlaylistItemCalled;
-    public boolean mReplacePlaylistItemCalled;
-    public boolean mMovePlaylistItemCalled;
-    public boolean mSkipToPlaylistItemCalled;
-    public boolean mSkipToPreviousItemCalled;
-    public boolean mSkipToNextItemCalled;
-    public boolean mSetRepeatModeCalled;
-    public boolean mSetShuffleModeCalled;
-
-    private AudioAttributesCompat mAudioAttributes;
-
-    public MockPlayer(int count) {
-        this(count, false);
-    }
-
-    public MockPlayer(boolean changePlayerStateWithTransportControl) {
-        this(0, changePlayerStateWithTransportControl);
-    }
-
-    private MockPlayer(int count, boolean changePlayerStateWithTransportControl) {
-        mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
-        mChangePlayerStateWithTransportControl = changePlayerStateWithTransportControl;
-        // This prevents MS2#play() from triggering SessionPlayer#prepare().
-        mLastPlayerState = PLAYER_STATE_PAUSED;
-
-        // Sets default audio attributes to prevent setVolume() from being called with the play().
-        mAudioAttributes = new AudioAttributesCompat.Builder()
-                .setUsage(AudioAttributesCompat.USAGE_MEDIA).build();
-    }
-
-    @Override
-    public void close() {
-        // no-op
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        mPlayCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PLAYING);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        mPauseCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        mPrepareCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long pos) {
-        mSeekToCalled = true;
-        mSeekPosition = pos;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPlayerState() {
-        return mLastPlayerState;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return mCurrentPosition;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return mPlaybackSpeed;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return mLastBufferingState;
-    }
-
-    @Override
-    public long getDuration() {
-        return mDuration;
-    }
-
-    public void notifyPlayerStateChanged(final int state) {
-        mLastPlayerState = state;
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlayerStateChanged(MockPlayer.this, state);
-                }
-            });
-        }
-    }
-
-    public void notifyCurrentMediaItemChanged(final MediaItem item) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onCurrentMediaItemChanged(MockPlayer.this, item);
-                }
-            });
-        }
-    }
-
-    public void notifyBufferingStateChanged(final MediaItem item,
-            final @BuffState int buffState) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onBufferingStateChanged(MockPlayer.this, item, buffState);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaybackSpeedChanged(final float speed) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaybackSpeedChanged(MockPlayer.this, speed);
-                }
-            });
-        }
-    }
-
-    public void notifySeekCompleted(final long position) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSeekCompleted(MockPlayer.this, position);
-                }
-            });
-        }
-    }
-
-    public void notifyAudioAttributesChanged(final AudioAttributesCompat attrs) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onAudioAttributesChanged(MockPlayer.this, attrs);
-                }
-            });
-        }
-    }
-
-    public void notifyTracksChanged(final List<TrackInfo> tracks) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTracksChanged(MockPlayer.this, tracks);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackSelected(final TrackInfo trackInfo) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackSelected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackDeselected(final TrackInfo trackInfo) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackDeselected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        mAudioAttributes = attributes;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float speed) {
-        mSetPlaybackSpeedCalled = true;
-        mPlaybackSpeed = speed;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////
-    // Playlist APIs
-    /////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return mPlaylist;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        mItem = item;
-        ArrayList<MediaItem> list = new ArrayList<>();
-        list.add(item);
-        return setPlaylist(list, null);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(
-            @NonNull List<MediaItem> list, MediaMetadata metadata) {
-        mSetPlaylistCalled = true;
-        mPlaylist = list;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return mMetadata;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        mUpdatePlaylistMetadataCalled = true;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return mCurrentMediaItem;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        if (mPlaylist == null) {
-            return ITEM_NONE;
-        }
-        return mPlaylist.indexOf(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        // TODO: reflect repeat & shuffle modes
-        int currentIdx = getCurrentMediaItemIndex();
-        if (currentIdx == ITEM_NONE || currentIdx == 0) {
-            return ITEM_NONE;
-        }
-        return currentIdx--;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        // TODO: reflect repeat & shuffle modes
-        int currentIdx = getCurrentMediaItemIndex();
-        if (currentIdx == ITEM_NONE || currentIdx == mPlaylist.size() - 1) {
-            return ITEM_NONE;
-        }
-        return currentIdx++;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mAddPlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        // TODO: check for invalid index
-        mRemovePlaylistItemCalled = true;
-        mIndex = index;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mReplacePlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> movePlaylistItem(int fromIndex, int toIndex) {
-        // TODO: check for invalid index
-        mMovePlaylistItemCalled = true;
-        mIndex = fromIndex;
-        mIndex2 = toIndex;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        // TODO: check for invalid index
-        mSkipToPlaylistItemCalled = true;
-        mIndex = index;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        // TODO: reflect repeat & shuffle modes
-        mSkipToPreviousItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        // TODO: reflect repeat & shuffle modes
-        mSkipToNextItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return mRepeatMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        mSetRepeatModeCalled = true;
-        mRepeatMode = repeatMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return mShuffleMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        mSetShuffleModeCalled = true;
-        mShuffleMode = shuffleMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        if (mTracks == null) {
-            return new ArrayList<TrackInfo>();
-        }
-        return mTracks;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> selectTrack(@NonNull TrackInfo trackInfo) {
-        notifyTrackSelected(trackInfo);
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> deselectTrack(@NonNull TrackInfo trackInfo) {
-        notifyTrackDeselected(trackInfo);
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    public void notifyShuffleModeChanged() {
-        final int shuffleMode = mShuffleMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onShuffleModeChanged(MockPlayer.this, shuffleMode);
-                }
-            });
-        }
-    }
-
-    public void notifyRepeatModeChanged() {
-        final int repeatMode = mRepeatMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onRepeatModeChanged(MockPlayer.this, repeatMode);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaybackCompleted() {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaybackCompleted(MockPlayer.this);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistChanged() {
-        final List<MediaItem> list = mPlaylist;
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistChanged(MockPlayer.this, list, metadata);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistMetadataChanged() {
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistMetadataChanged(MockPlayer.this, metadata);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        if (mVideoSize == null) {
-            mVideoSize = new VideoSize(0, 0);
-        }
-        return mVideoSize;
-    }
-
-    public void notifyVideoSizeChanged(@NonNull final VideoSize videoSize) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onVideoSizeChanged(MockPlayer.this, videoSize);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setSurface(@Nullable Surface surface) {
-        mSurface = surface;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    public boolean surfaceExists() {
-        return mSurface != null;
-    }
-
-    public void notifySubtitleData(@NonNull final MediaItem item, @NonNull final TrackInfo track,
-            @NonNull final SubtitleData data) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSubtitleData(MockPlayer.this, item, track, data);
-                }
-            });
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java
deleted file mode 100644
index 3de1721..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.RemoteSessionPlayer;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * Mock implementation of {@link RemoteSessionPlayer}.
- */
-public class MockRemotePlayer extends RemoteSessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mLatch = new CountDownLatch(1);
-    public boolean mSetVolumeToCalled;
-    public boolean mAdjustVolumeCalled;
-    public @VolumeControlType int mControlType;
-    public int mCurrentVolume;
-    public int mMaxVolume;
-    public int mDirection;
-    public AudioAttributesCompat mAttributes;
-
-    public MockRemotePlayer(int controlType, int maxVolume, int currentVolume) {
-        mControlType = controlType;
-        mMaxVolume = maxVolume;
-        mCurrentVolume = currentVolume;
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> setVolume(int volume) {
-        mSetVolumeToCalled = true;
-        mCurrentVolume = volume;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> adjustVolume(int direction) {
-        mAdjustVolumeCalled = true;
-        mDirection = direction;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @Override
-    public int getVolume() {
-        return mCurrentVolume;
-    }
-
-    @Override
-    public int getMaxVolume() {
-        return mMaxVolume;
-    }
-
-    @Override
-    public int getVolumeControlType() {
-        return mControlType;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long position) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        mAttributes = attributes;
-        return new SyncListenableFuture(null);
-    }
-
-    @Override
-    public int getPlayerState() {
-        return 0;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return 0;
-    }
-
-    @Override
-    public long getDuration() {
-        return 0;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return 0;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return 0;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return 0;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(@NonNull List<MediaItem> list,
-            MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return mAttributes;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        return null;
-    }
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return null;
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return null;
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return 0;
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return 0;
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return null;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    public void notifyVolumeChanged() {
-        int volume = mCurrentVolume;
-        for (Pair<PlayerCallback, Executor> pair : getCallbacks()) {
-            if (!(pair.first instanceof RemoteSessionPlayer.Callback)) {
-                continue;
-            }
-            RemoteSessionPlayer.Callback callback = (RemoteSessionPlayer.Callback) pair.first;
-            Executor executor = pair.second;
-            executor.execute(() -> callback.onVolumeChanged(this, volume));
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java
deleted file mode 100644
index e8827e6..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionToken;
-
-/**
- * Represents remote {@link MediaBrowser} the client app's MediaControllerService.
- * Users can run {@link MediaBrowser} methods remotely with this object.
- */
-public class RemoteMediaBrowser extends RemoteMediaController {
-
-    /**
-     * Create a {@link MediaBrowser} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param waitForConnection true if the remote browser needs to wait for the connection,
-     *                          false otherwise.
-     * @param connectionHints connection hints
-     */
-    public RemoteMediaBrowser(Context context, SessionToken token, boolean waitForConnection,
-            Bundle connectionHints) {
-        super(context, token, connectionHints, waitForConnection);
-    }
-
-    /**
-     * {@link MediaBrowser} methods.
-     */
-
-    public void getLibraryRoot(@Nullable LibraryParams params) {
-        try {
-            mBinder.getLibraryRoot(mControllerId, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getLibraryRoot()");
-        }
-    }
-
-    public void subscribe(@NonNull String parentId, @Nullable LibraryParams params) {
-        try {
-            mBinder.subscribe(mControllerId, parentId, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call subscribe()");
-        }
-    }
-
-    public void unsubscribe(@NonNull String parentId) {
-        try {
-            mBinder.unsubscribe(mControllerId, parentId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call unsubscribe()");
-        }
-    }
-
-    public void getChildren(@NonNull String parentId, int page, int pageSize,
-            @Nullable LibraryParams params) {
-        try {
-            mBinder.getChildren(mControllerId, parentId, page, pageSize,
-                    MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getChildren()");
-        }
-    }
-
-    public void getItem(@NonNull String mediaId) {
-        try {
-            mBinder.getItem(mControllerId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getItem()");
-        }
-    }
-
-    public void search(@NonNull String query, @Nullable LibraryParams params) {
-        try {
-            mBinder.search(mControllerId, query, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call search()");
-        }
-    }
-
-    public void getSearchResult(@NonNull String query, int page, int pageSize,
-            @Nullable LibraryParams params) {
-        try {
-            mBinder.getSearchResult(mControllerId, query, page, pageSize,
-                    MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getSearchResult()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Create a {@link MediaBrowser} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if this method needs to wait for the connection,
-     */
-    void create(SessionToken token, Bundle connectionHints, boolean waitForConnection) {
-        try {
-            mBinder.create(true /* isBrowser */, mControllerId,
-                    MediaParcelUtils.toParcelable(token), connectionHints, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default browser with given token.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
deleted file mode 100644
index ab16657..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaBrowserCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaBrowserCompat;
-
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaBrowserCompat} the client app's MediaBrowserCompatProviderService.
- * Users can run {@link MediaBrowserCompat} methods remotely with this object.
- */
-public class RemoteMediaBrowserCompat {
-    private static final String TAG = "RemoteMediaBrowserCompat";
-
-    private final String mBrowserId;
-    private final Context mContext;
-    private final CountDownLatch mCountDownLatch;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaBrowserCompat mBinder;
-
-    /**
-     * Create a {@link MediaBrowserCompat} in the client app.
-     * Should NOT be called main thread.
-     */
-    public RemoteMediaBrowserCompat(Context context, ComponentName serviceComponent) {
-        mContext = context;
-        mBrowserId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connectToService()) {
-            fail("Failed to connect to the MediaBrowserCompatProviderService.");
-        }
-        create(serviceComponent);
-    }
-
-    public void cleanUp() {
-        disconnect();
-        disconnectFromService();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaBrowserCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connect to the given media browser service.
-     *
-     * @param waitForConnection true if the remote browser needs to wait for the connection,
-     *                          false otherwise.
-     */
-    public void connect(boolean waitForConnection) {
-        try {
-            mBinder.connect(mBrowserId, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call connect()");
-        }
-    }
-
-    public void disconnect() {
-        try {
-            mBinder.disconnect(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call disconnect()");
-        }
-    }
-
-    public boolean isConnected() {
-        try {
-            return mBinder.isConnected(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call isConnected()");
-            return false;
-        }
-    }
-
-    public ComponentName getServiceComponent() {
-        try {
-            return mBinder.getServiceComponent(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getServiceComponent()");
-            return null;
-        }
-    }
-
-    public String getRoot() {
-        try {
-            return mBinder.getRoot(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getRoot()");
-            return null;
-        }
-    }
-
-    public Bundle getExtras() {
-        try {
-            return mBinder.getExtras(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getExtras()");
-            return null;
-        }
-    }
-
-    public Bundle getConnectedSessionToken() {
-        try {
-            return mBinder.getConnectedSessionToken(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getConnectedToken()");
-            return null;
-        }
-    }
-
-    public void subscribe(String parentId, Bundle options) {
-        try {
-            mBinder.subscribe(mBrowserId, parentId, options);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call subscribe()");
-        }
-    }
-
-    public void unsubscribe(String parentId) {
-        try {
-            mBinder.unsubscribe(mBrowserId, parentId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call unsubscribe()");
-        }
-    }
-
-    public void getItem(String mediaId) {
-        try {
-            mBinder.getItem(mBrowserId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getItem()");
-        }
-    }
-
-    public void search(String query, Bundle extras) {
-        try {
-            mBinder.search(mBrowserId, query, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call search()");
-        }
-    }
-
-    public void sendCustomAction(String action, Bundle extras) {
-        try {
-            mBinder.sendCustomAction(mBrowserId, action, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomAction()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaBrowserCompatProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connectToService() {
-        final Intent intent = new Intent(ACTION_MEDIA_BROWSER_COMPAT);
-        intent.setComponent(MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaBrowserCompatProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaBrowserCompatProviderService.
-     */
-    private void disconnectFromService() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaBrowserCompat} in the client app.
-     * Should be used after successful connection through {@link #connectToService()}.
-     */
-    private void create(ComponentName serviceComponent) {
-        try {
-            mBinder.create(mBrowserId, serviceComponent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default browser with given serviceComponent.");
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaBrowserCompatProviderService.");
-            mBinder = IRemoteMediaBrowserCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaBrowserCompatProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
deleted file mode 100644
index 5d1eb05..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
-import static androidx.media2.test.common.CommonConstants.MEDIA2_CONTROLLER_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaController;
-import androidx.media2.test.common.TestUtils;
-
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaController} the client app's MediaControllerProviderService.
- * Users can run {@link MediaController} methods remotely with this object.
- */
-public class RemoteMediaController {
-    static final String TAG = "RemoteMediaController";
-
-    final String mControllerId;
-    final Context mContext;
-    final CountDownLatch mCountDownLatch;
-
-    ServiceConnection mServiceConnection;
-    IRemoteMediaController mBinder;
-
-    /**
-     * Create a {@link MediaController} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if the remote controller needs to wait for the connection,
-     */
-    public RemoteMediaController(Context context, SessionToken token,
-            Bundle connectionHints, boolean waitForConnection) {
-        mContext = context;
-        mControllerId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connect()) {
-            fail("Failed to connect to the MediaControllerProviderService.");
-        }
-        create(token, connectionHints, waitForConnection);
-    }
-
-    public void cleanUp() {
-        close();
-        disconnect();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaController methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public SessionToken getConnectedSessionToken() {
-        try {
-            return MediaParcelUtils.fromParcelable(mBinder.getConnectedSessionToken(mControllerId));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getConnectedToken()");
-            return null;
-        }
-    }
-
-    public void play() {
-        try {
-            mBinder.play(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call play()");
-        }
-    }
-
-    public void pause() {
-        try {
-            mBinder.pause(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call pause()");
-        }
-    }
-
-    public void prepare() {
-        try {
-            mBinder.prepare(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call prepare()");
-        }
-    }
-
-    public void seekTo(long pos) {
-        try {
-            mBinder.seekTo(mControllerId, pos);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call seekTo()");
-        }
-    }
-
-    public void setPlaybackSpeed(float speed) {
-        try {
-            mBinder.setPlaybackSpeed(mControllerId, speed);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackSpeed()");
-        }
-    }
-
-    public void setPlaylist(@NonNull List<String> list, @Nullable MediaMetadata metadata) {
-        try {
-            mBinder.setPlaylist(mControllerId, list, MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaylist()");
-        }
-    }
-
-    /**
-     * Client app will automatically create a playlist of size {@param size},
-     * and call MediaController#setPlaylist() with the list.
-     *
-     * Each item's media ID will be {@link TestUtils#getMediaIdInFakeList(int)}.
-     */
-    public void createAndSetFakePlaylist(int size, @Nullable MediaMetadata metadata) {
-        try {
-            mBinder.createAndSetFakePlaylist(mControllerId, size,
-                    MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call createAndSetFakePlaylist()");
-        }
-    }
-
-    public void setMediaItem(@NonNull String mediaId) {
-        try {
-            mBinder.setMediaItem(mControllerId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMediaItem()");
-        }
-    }
-
-    public void setMediaUri(@NonNull Uri uri, @Nullable Bundle extras) {
-        try {
-            mBinder.setMediaUri(mControllerId, uri, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMediaUri()");
-        }
-    }
-
-    public void updatePlaylistMetadata(@Nullable MediaMetadata metadata) {
-        try {
-            mBinder.updatePlaylistMetadata(mControllerId, MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call updatePlaylistMetadata()");
-        }
-    }
-
-    public void addPlaylistItem(int index, @NonNull String mediaId) {
-        try {
-            mBinder.addPlaylistItem(mControllerId, index, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addPlaylistItem()");
-        }
-    }
-
-    public void removePlaylistItem(int index) {
-        try {
-            mBinder.removePlaylistItem(mControllerId, index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call removePlaylistItem()");
-        }
-    }
-
-    public void replacePlaylistItem(int index, @NonNull String media) {
-        try {
-            mBinder.replacePlaylistItem(mControllerId, index, media);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call replacePlaylistItem()");
-        }
-    }
-
-    public void movePlaylistItem(int fromIdx, int toIdx) {
-        try {
-            mBinder.movePlaylistItem(mControllerId, fromIdx, toIdx);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call movePlaylistItem()");
-        }
-    }
-
-    public void skipToPreviousItem() {
-        try {
-            mBinder.skipToPreviousItem(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToPreviousPlaylistItem()");
-        }
-    }
-
-    public void skipToNextItem() {
-        try {
-            mBinder.skipToNextItem(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToNextPlaylistItem()");
-        }
-    }
-
-    public void skipToPlaylistItem(int index) {
-        try {
-            mBinder.skipToPlaylistItem(mControllerId, index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToPlaylistItem()");
-        }
-    }
-
-    public void setShuffleMode(int shuffleMode) {
-        try {
-            mBinder.setShuffleMode(mControllerId, shuffleMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setShuffleMode()");
-        }
-    }
-
-    public void setRepeatMode(int repeatMode) {
-        try {
-            mBinder.setRepeatMode(mControllerId, repeatMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRepeatMode()");
-        }
-    }
-
-    public void setVolumeTo(int value, int flags) {
-        try {
-            mBinder.setVolumeTo(mControllerId, value, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setVolumeTo()");
-        }
-    }
-
-    public void adjustVolume(int direction, int flags) {
-        try {
-            mBinder.adjustVolume(mControllerId, direction, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call adjustVolume()");
-        }
-    }
-
-    public void sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.sendCustomCommand(mControllerId, MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomCommand()");
-        }
-    }
-
-    public void fastForward() {
-        try {
-            mBinder.fastForward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call fastForward()");
-        }
-    }
-
-    public void rewind() {
-        try {
-            mBinder.rewind(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call rewind()");
-        }
-    }
-
-    public void skipForward() {
-        try {
-            mBinder.skipForward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipForward()");
-        }
-    }
-
-    public void skipBackward() {
-        try {
-            mBinder.skipBackward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipBackward()");
-        }
-    }
-
-    public void setRating(@NonNull String mediaId, @NonNull Rating rating) {
-        try {
-            mBinder.setRating(mControllerId, mediaId, MediaParcelUtils.toParcelable(rating));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRating()");
-        }
-    }
-
-    public void close() {
-        try {
-            mBinder.close(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call close()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaControllerProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA2_CONTROLLER);
-        intent.setComponent(MEDIA2_CONTROLLER_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaControllerProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaControllerProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaController} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if this method needs to wait for the connection,
-     */
-    void create(SessionToken token, Bundle connectionHints, boolean waitForConnection) {
-        try {
-            mBinder.create(false /* isBrowser */, mControllerId,
-                    MediaParcelUtils.toParcelable(token), connectionHints, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default controller with given token.");
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaControllerProviderService.");
-            mBinder = IRemoteMediaController.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaControllerProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
deleted file mode 100644
index 121185fc..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_ARGUMENTS;
-import static androidx.media2.test.common.CommonConstants.MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaControllerCompat;
-
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaControllerCompat} the client app's
- * MediaControllerCompatProviderService.
- * <p>
- * Users can run {@link MediaControllerCompat} methods remotely with this object.
- */
-public class RemoteMediaControllerCompat {
-    static final String TAG = "RemoteMediaControllerCompat";
-
-    final String mControllerId;
-    final Context mContext;
-    final CountDownLatch mCountDownLatch;
-
-    ServiceConnection mServiceConnection;
-    IRemoteMediaControllerCompat mBinder;
-    TransportControls mTransportControls;
-
-    /**
-     * Create a {@link MediaControllerCompat} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param waitForConnection true if the remote controller needs to wait for the connection,
-     *                          false otherwise.
-     */
-    public RemoteMediaControllerCompat(Context context, MediaSessionCompat.Token token,
-            boolean waitForConnection) {
-        mContext = context;
-        mControllerId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connect()) {
-            fail("Failed to connect to the MediaControllerCompatProviderService.");
-        }
-        create(token, waitForConnection);
-    }
-
-    public void cleanUp() {
-        disconnect();
-    }
-
-    /**
-     * Gets {@link TransportControls} for interact with the remote MockPlayer.
-     * Users can run MockPlayer methods remotely with this object.
-     */
-    public TransportControls getTransportControls() {
-        return mTransportControls;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaControllerCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public void addQueueItem(MediaDescriptionCompat description) {
-        try {
-            mBinder.addQueueItem(mControllerId, createBundleWithParcelable(description));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addQueueItem()");
-        }
-    }
-
-    public void addQueueItem(MediaDescriptionCompat description, int index) {
-        try {
-            mBinder.addQueueItemWithIndex(
-                    mControllerId, createBundleWithParcelable(description), index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addQueueItemWithIndex()");
-        }
-    }
-
-    public void removeQueueItem(MediaDescriptionCompat description) {
-        try {
-            mBinder.removeQueueItem(mControllerId, createBundleWithParcelable(description));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call removeQueueItem()");
-        }
-    }
-
-    public void setVolumeTo(int value, int flags) {
-        try {
-            mBinder.setVolumeTo(mControllerId, value, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setVolumeTo()");
-        }
-    }
-
-    public void adjustVolume(int direction, int flags) {
-        try {
-            mBinder.adjustVolume(mControllerId, direction, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call adjustVolume()");
-        }
-    }
-
-    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
-        try {
-            mBinder.sendCommand(mControllerId, command, params, cb);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCommand()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaControllerCompat.TransportControls methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public class TransportControls {
-        public void prepare() {
-            try {
-                mBinder.prepare(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepare()");
-            }
-        }
-
-        public void prepareFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.prepareFromMediaId(mControllerId, mediaId, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromMediaId()");
-            }
-        }
-
-        public void prepareFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.prepareFromSearch(mControllerId, query, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromSearch()");
-            }
-        }
-
-        public void prepareFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.prepareFromUri(mControllerId, uri, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromUri()");
-            }
-        }
-
-        public void play() {
-            try {
-                mBinder.play(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call play()");
-            }
-        }
-
-        public void playFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.playFromMediaId(mControllerId, mediaId, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromMediaId()");
-            }
-        }
-
-        public void playFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.playFromSearch(mControllerId, query, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromSearch()");
-            }
-        }
-
-        public void playFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.playFromUri(mControllerId, uri, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromUri()");
-            }
-        }
-
-        public void skipToQueueItem(long id) {
-            try {
-                mBinder.skipToQueueItem(mControllerId, id);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToQueueItem()");
-            }
-        }
-
-        public void pause() {
-            try {
-                mBinder.pause(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call pause()");
-            }
-        }
-
-        public void stop() {
-            try {
-                mBinder.stop(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call stop()");
-            }
-        }
-
-        public void seekTo(long pos) {
-            try {
-                mBinder.seekTo(mControllerId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call seekTo()");
-            }
-        }
-
-        public void setPlaybackSpeed(float speed) {
-            try {
-                mBinder.setPlaybackSpeed(mControllerId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaybackSpeed()");
-            }
-        }
-
-        public void fastForward() {
-            try {
-                mBinder.fastForward(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call fastForward()");
-            }
-        }
-
-        public void skipToNext() {
-            try {
-                mBinder.skipToNext(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToNext()");
-            }
-        }
-
-        public void rewind() {
-            try {
-                mBinder.rewind(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call rewind()");
-            }
-        }
-
-        public void skipToPrevious() {
-            try {
-                mBinder.skipToPrevious(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToPrevious()");
-            }
-        }
-
-        public void setRating(RatingCompat rating) {
-            try {
-                mBinder.setRating(mControllerId, createBundleWithParcelable(rating));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRating()");
-            }
-        }
-
-        public void setRating(RatingCompat rating, Bundle extras) {
-            try {
-                mBinder.setRatingWithExtras(
-                        mControllerId, createBundleWithParcelable(rating), extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRatingWithExtras()");
-            }
-        }
-
-        public void setCaptioningEnabled(boolean enabled) {
-            try {
-                mBinder.setCaptioningEnabled(mControllerId, enabled);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCaptioningEnabled()");
-            }
-        }
-
-        public void setRepeatMode(int repeatMode) {
-            try {
-                mBinder.setRepeatMode(mControllerId, repeatMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRepeatMode()");
-            }
-        }
-
-        public void setShuffleMode(int shuffleMode) {
-            try {
-                mBinder.setShuffleMode(mControllerId, shuffleMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setShuffleMode()");
-            }
-        }
-
-        public void sendCustomAction(PlaybackStateCompat.CustomAction customAction, Bundle args) {
-            try {
-                mBinder.sendCustomAction(
-                        mControllerId, createBundleWithParcelable(customAction), args);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call sendCustomAction()");
-            }
-        }
-
-        public void sendCustomAction(String action, Bundle args) {
-            try {
-                mBinder.sendCustomActionWithName(mControllerId, action, args);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call sendCustomActionWithName()");
-            }
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaControllerCompatProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA_CONTROLLER_COMPAT);
-        intent.setComponent(MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaControllerCompatProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaControllerCompatProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaControllerCompat} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param waitForConnection true if this method needs to wait for the connection,
-     *                          false otherwise.
-     */
-    void create(MediaSessionCompat.Token token, boolean waitForConnection) {
-        try {
-            mBinder.create(mControllerId, createBundleWithParcelable(token), waitForConnection);
-            mTransportControls = new TransportControls();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default controller with given token.");
-        }
-    }
-
-    private Bundle createBundleWithParcelable(Parcelable parcelable) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(KEY_ARGUMENTS, parcelable);
-        return bundle;
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaControllerCompatProviderService.");
-            mBinder = IRemoteMediaControllerCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaControllerCompatProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java
deleted file mode 100644
index d081002..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Implements {@link ListenableFuture} for synchrous calls.
- */
-public class SyncListenableFuture implements ListenableFuture<SessionPlayer.PlayerResult> {
-    private final SessionPlayer.PlayerResult mResult;
-
-    SyncListenableFuture(MediaItem item) {
-        mResult = new SessionPlayer.PlayerResult(RESULT_SUCCESS, item);
-    }
-
-    @Override
-    public void addListener(Runnable listener, Executor executor) {
-        executor.execute(listener);
-    }
-
-    @Override
-    public boolean cancel(boolean b) {
-        return false;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return false;
-    }
-
-    @Override
-    public boolean isDone() {
-        return true;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get() throws InterruptedException, ExecutionException {
-        return mResult;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get(long l, TimeUnit timeUnit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return mResult;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java
deleted file mode 100644
index 435328b..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static org.junit.Assert.fail;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Keeps the instance of currently running {@link MockMediaSessionService}. And also provides
- * a way to control them in one place.
- * <p>
- * It only support only one service at a time.
- */
-public class TestServiceRegistry {
-    private static final String TAG = "TestServiceRegistry";
-    private static final boolean DEBUG = true;
-
-    @GuardedBy("TestServiceRegistry.class")
-    private static TestServiceRegistry sInstance;
-
-    private final ConditionVariable mServiceSet;
-
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaSessionService mService;
-    @GuardedBy("TestServiceRegistry.class")
-    private SyncHandler mHandler;
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaLibrarySessionCallback mSessionCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private SessionServiceCallback mSessionServiceCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private OnGetSessionHandler mOnGetSessionHandler;
-
-    private TestServiceRegistry() {
-        this.mServiceSet = new ConditionVariable();
-    }
-
-
-    /**
-     * Callback for session service's lifecyle (onCreate() / onDestroy())
-     */
-    public interface SessionServiceCallback {
-        void onCreated();
-        void onDestroyed();
-    }
-
-    public static TestServiceRegistry getInstance() {
-        synchronized (TestServiceRegistry.class) {
-            if (sInstance == null) {
-                sInstance = new TestServiceRegistry();
-            }
-            return sInstance;
-        }
-    }
-
-    public void setOnGetSessionHandler(OnGetSessionHandler onGetSessionHandler) {
-        synchronized (TestServiceRegistry.class) {
-            mOnGetSessionHandler = onGetSessionHandler;
-        }
-    }
-
-    public OnGetSessionHandler getOnGetSessionHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mOnGetSessionHandler;
-        }
-    }
-
-    public void setHandler(Handler handler) {
-        synchronized (TestServiceRegistry.class) {
-            mHandler = new SyncHandler(handler.getLooper());
-        }
-    }
-
-    public Handler getHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mHandler;
-        }
-    }
-
-    public void setSessionServiceCallback(SessionServiceCallback sessionServiceCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionServiceCallback = sessionServiceCallback;
-        }
-    }
-
-    public void setSessionCallback(MediaLibrarySessionCallback sessionCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionCallback = sessionCallback;
-        }
-    }
-
-    public MediaLibrarySessionCallback getSessionCallback() {
-        synchronized (TestServiceRegistry.class) {
-            return mSessionCallback;
-        }
-    }
-
-    public void setServiceInstance(MediaSessionService service) {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                fail("Previous service instance is still running. Clean up manually to ensure"
-                        + " previously running service doesn't break current test");
-            }
-            if (DEBUG) {
-                Log.d(TAG, "setServiceInstance(): service=" + service);
-                if (service != null) {
-                    Log.d(TAG, "setServiceInstance(): service=" + service + ", session size="
-                            + service.getSessions().size());
-                    for (MediaSession session : service.getSessions()) {
-                        Log.d(TAG, "   session id=" + session.getId());
-                    }
-                } else {
-                    Log.d(TAG, "setServiceInstance, service=" + service);
-                }
-            }
-            mService = service;
-            if (service != null) {
-                mServiceSet.open();
-            }
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onCreated();
-            }
-        }
-    }
-
-    public MediaSessionService getServiceInstanceBlocking() throws TimeoutException {
-        if (!mServiceSet.block(5_000)) {
-            throw new TimeoutException(
-                    "Timed out waiting for TestServiceRegistry.setServiceInstance() to be called.");
-        }
-        synchronized (TestServiceRegistry.class) {
-            return mService;
-        }
-    }
-
-    public void cleanUp() {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "cleanUp(): service=" + mService + ", session size="
-                            + mService.getSessions().size());
-                    for (MediaSession session : mService.getSessions()) {
-                        Log.d(TAG, "   session id=" + session.getId());
-                    }
-                }
-                // TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
-                List<MediaSession> sessions = mService.getSessions();
-                for (int i = 0; i < sessions.size(); i++) {
-                    sessions.get(i).close();
-                }
-                // stopSelf() would not kill service while the binder connection established by
-                // bindService() exists, and close() above will do the job instead.
-                // So stopSelf() isn't really needed, but just for sure.
-                mService.stopSelf();
-                mServiceSet.close();
-                mService = null;
-            } else if (DEBUG) {
-                Log.d(TAG, "cleanUp(): service=" + mService);
-            }
-            if (mHandler != null) {
-                mHandler.removeCallbacksAndMessages(null);
-            }
-            mSessionCallback = null;
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onDestroyed();
-                mSessionServiceCallback = null;
-            }
-            mOnGetSessionHandler = null;
-        }
-    }
-
-    public interface OnGetSessionHandler {
-        MediaSession onGetSession(ControllerInfo controllerInfo);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
deleted file mode 100644
index 5d56165..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaDescriptionCompat;
-
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media.MediaBrowserServiceCompat.Result;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat.Proxy;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaBrowser} with {@link MediaBrowserServiceCompat}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@LargeTest
-public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest extends MediaSessionTestBase {
-    private SessionToken mToken;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mToken = new SessionToken(mContext, new ComponentName(
-                mContext, MockMediaBrowserServiceCompat.class));
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onGetRootCalledByGetLibraryRoot() throws InterruptedException {
-        final String testMediaId = "testOnGetRootCalledByGetLibraryRoot";
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testMediaId, testMediaId);
-        final LibraryParams testParams = new LibraryParams.Builder()
-                .setSuggested(true).setExtras(testExtras).build();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
-                    Bundle rootHints) {
-                assertEquals(CLIENT_PACKAGE_NAME, clientPackageName);
-                if (rootHints != null && rootHints.keySet().contains(testMediaId)) {
-                    MediaTestUtils.assertEqualLibraryParams(testParams, rootHints);
-                    // This should happen because getLibraryRoot() is called with testExtras.
-                    latch.countDown();
-                }
-                // For other random connection requests.
-                return new BrowserRoot("rootId", null);
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getLibraryRoot(testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadItemCalledByGetItem() throws InterruptedException {
-        final String testMediaId = "test_media_item";
-        final MediaItem testItem = createMediaItem(testMediaId);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadItem(String itemId, Result<MediaItem> result) {
-                assertEquals(testMediaId, itemId);
-                result.sendResult(testItem);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getItem(testMediaId);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenWithoutOptionsCalledByGetChildren() throws InterruptedException {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaItem> testFullMediaItemList = createMediaItems(
-                (testPage + 1) * testPageSize);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-                assertEquals(testParentId, parentId);
-                result.sendResult(testFullMediaItemList);
-                latch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getChildren(testParentId, testPage, testPageSize, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenWithOptionsCalledByGetChildren() throws InterruptedException {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaItem> testMediaItemList = createMediaItems(testPageSize);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-                fail("This isn't expected to be called");
-            }
-
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                    Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertEquals(2, options.keySet().size());
-                result.sendResult(testMediaItemList);
-                latch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getChildren(testParentId, testPage, testPageSize, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenCalledBySubscribe() throws InterruptedException {
-        final String testParentId = "testOnLoadChildrenCalledBySubscribe";
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                    Bundle option) {
-                assertEquals(testParentId, parentId);
-                MediaTestUtils.assertEqualLibraryParams(testParams, option);
-                result.sendResult(null);
-                subscribeLatch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.subscribe(testParentId, testParams);
-        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSearchCalledBySearch() throws InterruptedException {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-        final List<MediaItem> testFullSearchResult = createMediaItems(
-                (testPage + 1) * testPageSize + 3);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-                assertEquals(testQuery, query);
-                MediaTestUtils.assertEqualLibraryParams(testParams, extras);
-                result.sendResult(testFullSearchResult);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.search(testQuery, testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSearchCalledByGetSearchResult() throws InterruptedException {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-                assertEquals(testQuery, query);
-                MediaTestUtils.assertEqualLibraryParams(testParams, extras);
-                assertEquals(testPage, extras.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                result.sendResult(null);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private static MediaItem createMediaItem(String mediaId) {
-        final MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId).setTitle("title: " + mediaId).build();
-        return new MediaItem(desc, MediaItem.FLAG_PLAYABLE);
-    }
-
-    private static List<MediaItem> createMediaItems(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[2].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(createMediaItem(caller + "_child_" + i));
-        }
-        return list;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
deleted file mode 100644
index 48753c7..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.test.R;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Tests {@link MediaItem} and its subclasses.
- */
-@RunWith(Parameterized.class)
-@SmallTest
-public class MediaItemTest {
-    private final MediaItemFactory mItemFactory;
-    private final Class<?> mItemBuilderClass;
-    private Context mContext;
-    private MediaItem mTestItem;
-
-    private static final MediaItemFactory sMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putLong("MediaItemTest", 1).build();
-            return new MediaItem.Builder()
-                    .setMetadata(testMetadata)
-                    .setStartPosition(1)
-                    .setEndPosition(10)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sUriMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putString("MediaItemTest", "MediaItemTest").build();
-            return new UriMediaItem.Builder(Uri.parse("test://test"))
-                    .setMetadata(testMetadata)
-                    .setStartPosition(1)
-                    .setEndPosition(1000)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sCallbackMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putText("MediaItemTest", "testtest").build();
-            final DataSourceCallback callback = new DataSourceCallback() {
-                @Override
-                public int readAt(long position, @NonNull byte[] buffer, int offset, int size)
-                        throws IOException {
-                    return 0;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return 0;
-                }
-
-                @Override
-                public void close() throws IOException {
-                    // no-op
-                }
-            };
-            return new CallbackMediaItem.Builder(callback)
-                    .setMetadata(testMetadata)
-                    .setStartPosition(0)
-                    .setEndPosition(0)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sFileMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            int resId = R.raw.midi8sec;
-            try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
-                return new FileMediaItem.Builder(
-                        ParcelFileDescriptor.dup(afd.getFileDescriptor())).build();
-            } catch (Exception e) {
-                return null;
-            }
-        }
-    };
-
-    @Parameterized.Parameters
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][]{
-                {sMediaItemFactory, MediaItem.Builder.class},
-                {sUriMediaItemFactory, UriMediaItem.Builder.class},
-                {sCallbackMediaItemFactory, CallbackMediaItem.Builder.class},
-                {sFileMediaItemFactory, FileMediaItem.Builder.class}});
-    }
-
-    public MediaItemTest(MediaItemFactory factory, Class<?> builderClass) {
-        mItemFactory = factory;
-        mItemBuilderClass = builderClass;
-    }
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mTestItem = mItemFactory.create(mContext);
-    }
-
-    @Test
-    public void subclass_sameProcess() {
-        final ParcelImpl parcel = MediaParcelUtils.toParcelable(mTestItem);
-
-        final MediaItem testRemoteItem = MediaParcelUtils.fromParcelable(parcel);
-        assertEquals(mTestItem, testRemoteItem);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void subclass_acrossProcessWithMediaUtils() {
-        final Parcel p = Parcel.obtain();
-        try {
-            // Mocks the binder call across the processes by using writeParcelable/readParcelable
-            // which only happens between processes. Code snippets are copied from
-            // VersionedParcelIntegTest#parcelCopy.
-            p.writeParcelable(MediaParcelUtils.toParcelable(mTestItem), 0);
-            p.setDataPosition(0);
-            final MediaItem testRemoteItem = MediaParcelUtils.fromParcelable(
-                    (ParcelImpl) p.readParcelable(MediaItem.class.getClassLoader()));
-
-            assertEquals(MediaItem.class, testRemoteItem.getClass());
-            assertEquals(mTestItem.getStartPosition(), testRemoteItem.getStartPosition());
-            assertEquals(mTestItem.getEndPosition(), testRemoteItem.getEndPosition());
-            MediaTestUtils.assertMediaMetadataEquals(
-                    mTestItem.getMetadata(), testRemoteItem.getMetadata());
-        } finally {
-            p.recycle();
-        }
-    }
-
-    @Test
-    public void subclass_acrossProcessWithParcelUtils() {
-        if (mTestItem.getClass() == MediaItem.class) {
-            return;
-        }
-        final Parcel p = Parcel.obtain();
-        try {
-            // Mocks the binder call across the processes by using writeParcelable/readParcelable
-            // which only happens between processes. Code snippets are copied from
-            // VersionedParcelIntegTest#parcelCopy.
-            p.writeParcelable(ParcelUtils.toParcelable(mTestItem), 0);
-            fail("Write to parcel should throw RuntimeException for subclass of MediaItem");
-        } catch (RuntimeException e) {
-            // Expected.
-        } finally {
-            p.recycle();
-        }
-    }
-
-    /**
-     * Tests whether the methods in MediaItem.Builder have been hidden in subclasses by overriding
-     * them all.
-     */
-    @Test
-    public void subclass_overriddenAllMethods() throws Exception {
-        Method[] mediaItemBuilderMethods = MediaItem.Builder.class.getDeclaredMethods();
-        for (int i = 0; i < mediaItemBuilderMethods.length; i++) {
-            Method mediaItemBuilderMethod = mediaItemBuilderMethods[i];
-            if (!Modifier.isPublic(mediaItemBuilderMethod.getModifiers())) {
-                continue;
-            }
-            Method subclassMethod = mItemBuilderClass.getMethod(
-                    mediaItemBuilderMethod.getName(), mediaItemBuilderMethod.getParameterTypes());
-            assertEquals(subclassMethod.getDeclaringClass(), mItemBuilderClass);
-        }
-    }
-
-    interface MediaItemFactory {
-        MediaItem create(Context context);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java
deleted file mode 100644
index c158a7a..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession;
-import androidx.media2.session.MediaSession;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockMediaLibraryService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaLibrarySession.MediaLibrarySessionCallback}.
- *
- * TODO: Make this class extend MediaSessionCallbackTest.
- * TODO: Create MediaLibrarySessionTest which extends MediaSessionTest.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MediaLibrarySessionCallbackTest extends MediaSessionTestBase {
-
-    MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(0);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onSubscribe() throws InterruptedException {
-        final String testParentId = "testSubscribeId";
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
-                new MediaLibrarySession.MediaLibrarySessionCallback() {
-                    @Override
-                    public int onSubscribe(@NonNull MediaLibrarySession session,
-                            @NonNull MediaSession.ControllerInfo controller,
-                            @NonNull String parentId, LibraryParams params) {
-                        assertEquals(testParentId, parentId);
-                        MediaTestUtils.assertEqualLibraryParams(testParams, params);
-                        latch.countDown();
-                        return RESULT_SUCCESS;
-                    }
-        };
-
-        MockMediaLibraryService service = new MockMediaLibraryService();
-        service.attachBaseContext(mContext);
-
-        try (MediaLibrarySession session = new MediaLibrarySession.Builder(
-                service, mPlayer, sHandlerExecutor, sessionCallback)
-                .setId("testOnSubscribe")
-                .build()) {
-            RemoteMediaBrowser browser = createRemoteBrowser(session.getToken());
-            browser.subscribe(testParentId, testParams);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onUnsubscribe() throws InterruptedException {
-        final String testParentId = "testUnsubscribeId";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
-                new MediaLibrarySession.MediaLibrarySessionCallback() {
-                    @Override
-                    public int onUnsubscribe(@NonNull MediaLibrarySession session,
-                            @NonNull MediaSession.ControllerInfo controller,
-                            @NonNull String parentId) {
-                        assertEquals(testParentId, parentId);
-                        latch.countDown();
-                        return RESULT_SUCCESS;
-                    }
-                };
-
-        MockMediaLibraryService service = new MockMediaLibraryService();
-        service.attachBaseContext(mContext);
-
-        try (MediaLibrarySession session = new MediaLibrarySession.Builder(
-                service, mPlayer, sHandlerExecutor, sessionCallback)
-                .setId("testOnUnsubscribe")
-                .build()) {
-            RemoteMediaBrowser browser = createRemoteBrowser(session.getToken());
-            browser.unsubscribe(testParentId);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
deleted file mode 100644
index 0c6f8ab..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_RATING;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaMetadata.Builder;
-import androidx.media2.common.Rating;
-import androidx.media2.session.HeartRating;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.ThumbRating;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MediaMetadataTest {
-    @Test
-    public void builder() {
-        final String title = "title";
-        final long discNumber = 10;
-        final Rating rating = new ThumbRating(true);
-
-        Builder builder = new Builder();
-        builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
-        builder.putLong(MediaMetadata.METADATA_KEY_DISC_NUMBER, discNumber);
-        builder.putRating(METADATA_KEY_USER_RATING, rating);
-
-        MediaMetadata metadata = builder.build();
-        assertEquals(title, metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
-        assertEquals(discNumber, metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
-        assertEquals(rating, metadata.getRating(METADATA_KEY_USER_RATING));
-    }
-
-    @Test
-    public void setExtra() {
-        final Bundle extras = new Bundle();
-        extras.putString("MediaMetadataTest", "testBuilder");
-
-        Builder builder = new Builder();
-        try {
-            builder.putLong(MediaMetadata.METADATA_KEY_EXTRAS, 1);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        builder.setExtras(extras);
-        MediaMetadata metadata = builder.build();
-        assertTrue(TestUtils.equals(extras, metadata.getExtras()));
-    }
-
-    @Test
-    public void parcelling_withSmallBitmap_bitmapPreservedAfterParcelled() {
-        // A small bitmap (160kB) that doesn't need to be scaled down.
-        final int testBitmapSize = 200;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                testBitmapSize, testBitmapSize, Bitmap.Config.ARGB_8888);
-        testBitmap.setPixel(2, 2, Color.GREEN);
-        String testKey = MediaMetadata.METADATA_KEY_ALBUM_ART;
-        MediaMetadata metadata = new Builder().putBitmap(testKey, testBitmap).build();
-
-        Parcel parcel = Parcel.obtain();
-        try {
-            // Test twice to ensure internal cache works correctly.
-            for (int i = 0; i < 2; i++) {
-                ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-                parcelImpl.writeToParcel(parcel, 0 /* flags */);
-                parcel.setDataPosition(0);
-
-                MediaMetadata metadataFromParcel =
-                        ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-                assertEquals(testBitmap, metadata.getBitmap(testKey));
-                assertEquals(testBitmapSize, testBitmap.getHeight());
-                assertEquals(testBitmapSize, testBitmap.getWidth());
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parcelling_withLargeBitmap_bitmapPreservedAfterParcelled() {
-        // A large bitmap (4MB) which exceeds the binder limit. Scaling down would happen.
-        final int testBitmapSize = 1024;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                testBitmapSize, testBitmapSize, Bitmap.Config.ARGB_8888);
-        testBitmap.setPixel(2, 2, Color.GREEN);
-        String testKey = MediaMetadata.METADATA_KEY_ALBUM_ART;
-        MediaMetadata metadata = new Builder().putBitmap(testKey, testBitmap).build();
-
-        Parcel parcel = Parcel.obtain();
-        try {
-            // Test twice to ensure internal cache works correctly.
-            for (int i = 0; i < 2; i++) {
-                ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-                parcelImpl.writeToParcel(parcel, 0 /* flags */);
-                parcel.setDataPosition(0);
-
-                MediaMetadata metadataFromParcel =
-                        ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-                assertEquals(testBitmap, metadata.getBitmap(testKey));
-                assertEquals(testBitmapSize, testBitmap.getHeight());
-                assertEquals(testBitmapSize, testBitmap.getWidth());
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parceling_withSmallBitmaps() {
-        final int bitmapCount = 100;
-        final List<String> keyList = new ArrayList<>(bitmapCount);
-        final String bitmapKeyPrefix = "bitmap_";
-        for (int i = 0; i < bitmapCount; i++) {
-            keyList.add(bitmapKeyPrefix + i);
-        }
-
-        // A small bitmap about 160kB.
-        final int originalWidth = 200;
-        final int originalHeight = 200;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        Builder builder = new Builder();
-        for (int i = 0; i < keyList.size(); i++) {
-            builder.putBitmap(keyList.get(i), testBitmap);
-        }
-
-        MediaMetadata metadata = builder.build();
-        ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-
-        // Bitmaps will not be scaled down since they are small.
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcelImpl.writeToParcel(parcel, 0 /* flags */);
-            parcel.setDataPosition(0);
-
-            MediaMetadata metadataFromParcel =
-                    ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-
-            // Check the bitmap list from the metadata.
-            Set<String> keySet = metadataFromParcel.keySet();
-            assertTrue(keySet.containsAll(keyList));
-            assertTrue(keyList.containsAll(keySet));
-
-            for (String key : keySet) {
-                Bitmap bitmap = metadataFromParcel.getBitmap(key);
-                assertNotNull(bitmap);
-                int newWidth = bitmap.getWidth();
-                int newHeight = bitmap.getHeight();
-                // The bitmaps should not have been scaled down.
-                assertEquals(newWidth, originalWidth);
-                assertEquals(newHeight, originalHeight);
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parceling_withLargeBitmaps() {
-        final int bitmapCount = 100;
-        final List<String> keyList = new ArrayList<>(bitmapCount);
-        final String bitmapKeyPrefix = "bitmap_";
-        for (int i = 0; i < bitmapCount; i++) {
-            keyList.add(bitmapKeyPrefix + i);
-        }
-
-        // A large bitmap (64MB) which exceeds the binder limit.
-        final int originalWidth = 4096;
-        final int originalHeight = 4096;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        Builder builder = new Builder();
-        for (int i = 0; i < keyList.size(); i++) {
-            builder.putBitmap(keyList.get(i), testBitmap);
-        }
-
-        MediaMetadata metadata = builder.build();
-        ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-
-        // Bitmaps will be scaled down when the metadata is written to parcel.
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcelImpl.writeToParcel(parcel, 0 /* flags */);
-            parcel.setDataPosition(0);
-
-            MediaMetadata metadataFromParcel =
-                    ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-
-            // Check the bitmap list from the metadata.
-            Set<String> keySet = metadataFromParcel.keySet();
-            assertTrue(keySet.containsAll(keyList));
-            assertTrue(keyList.containsAll(keySet));
-
-            for (String key : keySet) {
-                Bitmap bitmap = metadataFromParcel.getBitmap(key);
-                assertNotNull(bitmap);
-                int newWidth = bitmap.getWidth();
-                int newHeight = bitmap.getHeight();
-                assertTrue("Resulting bitmap (size=" + newWidth + "x" + newHeight + ") was not "
-                                + "scaled down. ",
-                        newWidth < originalWidth && newHeight < originalHeight);
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaMetadataCompat() {
-        HeartRating testRating = new HeartRating(true);
-        long testState = MediaMetadata.STATUS_DOWNLOADING;
-        String testCustomKey = "android.media.test";
-        String testCustomValue = "customValue";
-        MediaMetadata testMetadata = new Builder()
-                .putRating(METADATA_KEY_RATING, testRating)
-                .putLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS, testState)
-                .putString(testCustomKey, testCustomValue)
-                .build();
-
-        MediaMetadataCompat compat = MediaUtils.convertToMediaMetadataCompat(testMetadata);
-        assertEquals(3, compat.keySet().size());
-        RatingCompat returnedRating = compat.getRating(MediaMetadataCompat.METADATA_KEY_RATING);
-        assertEquals(RatingCompat.RATING_HEART, returnedRating.getRatingStyle());
-        assertTrue(returnedRating.hasHeart());
-        assertEquals(MediaDescriptionCompat.STATUS_DOWNLOADING,
-                compat.getLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS));
-        assertEquals(testCustomValue, compat.getString(testCustomKey));
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaItem_withoutUserRating() {
-        RatingCompat testRating = RatingCompat.newHeartRating(true);
-        long testState = MediaDescriptionCompat.STATUS_DOWNLOADING;
-        String testCustomKey = "android.media.test";
-        String testCustomValue = "customValue";
-        MediaMetadataCompat testMetadataCompat = new MediaMetadataCompat.Builder()
-                .putRating(MediaMetadataCompat.METADATA_KEY_RATING, testRating)
-                .putLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS, testState)
-                .putString(testCustomKey, testCustomValue)
-                .build();
-
-        MediaItem item = MediaUtils.convertToMediaItem(
-                testMetadataCompat, RatingCompat.RATING_HEART);
-        Rating returnedRating = item.getMetadata().getRating(METADATA_KEY_RATING);
-        assertTrue(returnedRating instanceof HeartRating);
-        assertTrue(returnedRating.isRated());
-        assertEquals(testRating.hasHeart(), ((HeartRating) returnedRating).hasHeart());
-        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
-        assertTrue(returnedUserRating instanceof HeartRating);
-        assertFalse(returnedUserRating.isRated());
-        assertEquals(MediaMetadata.STATUS_DOWNLOADING,
-                item.getMetadata().getLong(MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS));
-        assertFalse(item.getMetadata().containsKey(
-                MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS));
-        assertEquals(testCustomValue, item.getMetadata().getString(testCustomKey));
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaItem_withUserRating() {
-        RatingCompat testRating = RatingCompat.newHeartRating(true);
-        MediaMetadataCompat testMetadataCompat = new MediaMetadataCompat.Builder()
-                .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, testRating)
-                .build();
-
-        MediaItem item = MediaUtils.convertToMediaItem(
-                testMetadataCompat, RatingCompat.RATING_HEART);
-        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
-        assertTrue(returnedUserRating instanceof HeartRating);
-        assertTrue(returnedUserRating.isRated());
-        assertTrue(((HeartRating) returnedUserRating).hasHeart());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
deleted file mode 100644
index c0f71e8..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaSession.SessionCallback}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionCallbackTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaSessionCallbackTest";
-
-    MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onPostConnect_afterConnected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public void onPostConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                latch.countDown();
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnPostConnect_afterConnected").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onPostConnect_afterConnectionRejected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                return null;
-            }
-
-            @Override
-            public void onPostConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                latch.countDown();
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnPostConnect_afterConnectionRejected").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onCommandRequest() throws InterruptedException {
-        mPlayer = new MockPlayer(1);
-
-        final MockOnCommandCallback callback = new MockOnCommandCallback();
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCommandRequest")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.pause();
-            assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(mPlayer.mPauseCalled);
-            assertEquals(1, callback.commands.size());
-            assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                    (long) callback.commands.get(0).getCommandCode());
-
-            controller.play();
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-            assertFalse(mPlayer.mPauseCalled);
-            assertEquals(2, callback.commands.size());
-            assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                    (long) callback.commands.get(1).getCommandCode());
-        }
-    }
-
-    @Test
-    public void onCreateMediaItem() throws InterruptedException {
-        mPlayer = new MockPlayer(1);
-
-        final List<String> list = MediaTestUtils.createMediaIds(3);
-        final List<MediaItem> convertedList = MediaTestUtils.createPlaylist(list.size());
-
-        final MockOnCommandCallback callback = new MockOnCommandCallback() {
-            @Override
-            public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                for (int i = 0; i < list.size(); i++) {
-                    if (Objects.equals(mediaId, list.get(i))) {
-                        return convertedList.get(i);
-                    }
-                }
-                fail();
-                return null;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCreateMediaItem")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setPlaylist(list, null);
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            List<MediaItem> playerList = mPlayer.getPlaylist();
-            assertEquals(convertedList.size(), playerList.size());
-            for (int i = 0; i < playerList.size(); i++) {
-                String expected = convertedList.get(i).getMetadata().getString(
-                        MediaMetadata.METADATA_KEY_MEDIA_ID);
-                String actual = playerList.get(i).getMetadata().getString(
-                        MediaMetadata.METADATA_KEY_MEDIA_ID);
-                assertEquals(expected, actual);
-            }
-        }
-    }
-
-    @Test
-    public void onCustomCommand() throws InterruptedException {
-        // TODO(jaewan): Need to revisit with the permission.
-        final SessionCommand testCommand = new SessionCommand("testCustomCommand", null);
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "testOnCustomCommand");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                        .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
-                        .addCommand(testCommand)
-                        .build();
-                return commands;
-            }
-
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaSession session,
-                    @NonNull MediaSession.ControllerInfo controller,
-                    @NonNull SessionCommand sessionCommand, Bundle args) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testCommand, sessionCommand);
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCustomCommand")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.sendCustomCommand(testCommand, testArgs);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onFastForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onFastForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnFastForward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.fastForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onRewind() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnRewind").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.rewind();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSkipForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSkipForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSkipForward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.skipForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSkipBackward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSkipBackward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSkipBackward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.skipBackward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSetMediaUri() throws InterruptedException {
-        final Uri testUri = Uri.parse("foo://boo");
-        final Bundle testExtras = TestUtils.createTestBundle();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testUri, uri);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSetMediaUri")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setMediaUri(testUri, testExtras);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSetRating() throws InterruptedException {
-        final float ratingValue = 3.5f;
-        final Rating testRating = new StarRating(5, ratingValue);
-        final String testMediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSetRating(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaId,
-                    @NonNull Rating rating) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testMediaId, mediaId);
-                assertEquals(testRating, rating);
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSetRating").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setRating(testMediaId, testRating);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onConnect() throws InterruptedException {
-        final AtomicReference<Bundle> connectionHints = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testOnConnect")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        // TODO: Get uid of client app's and compare.
-                        if (!CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return null;
-                        }
-                        connectionHints.set(controller.getConnectionHints());
-                        latch.countDown();
-                        return super.onConnect(session, controller);
-                    }
-                }).build()) {
-            Bundle testConnectionHints = new Bundle();
-            testConnectionHints.putString("test_key", "test_value");
-
-            RemoteMediaController controller = createRemoteController(
-                    session.getToken(), false  /* waitForConnection */, testConnectionHints);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(TestUtils.equals(testConnectionHints, connectionHints.get()));
-        }
-    }
-
-    @Test
-    public void onDisconnected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testOnDisconnected")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                        // TODO: Get uid of client app's and compare.
-                        latch.countDown();
-                    }
-                }).build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.close();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-
-    // TODO(jaewan): Add test for service connect rejection, when we differentiate session
-    //               active/inactive and connection accept/refuse
-    class TestSessionCallback extends MediaSession.SessionCallback {
-        CountDownLatch mLatch;
-
-        void resetLatchCount(int count) {
-            mLatch = new CountDownLatch(count);
-        }
-    }
-
-    public class MockOnCommandCallback extends MediaSession.SessionCallback {
-        public final ArrayList<SessionCommand> commands = new ArrayList<>();
-
-        @Override
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull SessionCommand command) {
-            // TODO: Get uid of client app's and compare.
-            assertEquals(CLIENT_PACKAGE_NAME, controllerInfo.getPackageName());
-            assertFalse(controllerInfo.isTrusted());
-            commands.add(command);
-            if (command.getCommandCode() == SessionCommand.COMMAND_CODE_PLAYER_PAUSE) {
-                return RESULT_ERROR_INVALID_STATE;
-            }
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
deleted file mode 100644
index e02ba78..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
+++ /dev/null
@@ -1,1074 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.MockRemotePlayer;
-import androidx.media2.test.service.RemoteMediaControllerCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link SessionCallback} working with {@link MediaControllerCompat}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@FlakyTest(bugId = 202942942)
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionCallbackWithMediaControllerCompatTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaSessionCallbackTestWithMediaControllerCompat";
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
-            (Build.VERSION.SDK_INT < 21 || Build.VERSION.SDK_INT >= 24)
-                    ? CLIENT_PACKAGE_NAME : LEGACY_CONTROLLER;
-
-    PendingIntent mIntent;
-    MediaSession mSession;
-    RemoteMediaControllerCompat mController;
-    MockPlayer mPlayer;
-    AudioManager mAudioManager;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-
-        mPlayer = new MockPlayer(1);
-        if (mSession != null && !mSession.isClosed()) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setId(TAG)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                        return MediaTestUtils.createMediaItem(mediaId);
-                    }
-                })
-                .setSessionActivity(mIntent)
-                .build();
-        mController = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-        if (mController != null) {
-            mController.cleanUp();
-            mController = null;
-        }
-    }
-
-    @Test
-    public void disconnectedAfterTimeout() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        CountDownLatch disconnectedLatch = new CountDownLatch(1);
-        RemoteMediaControllerCompat controller = null;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("disconnectedAfterTimeout")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    private ControllerInfo mConnectedController;
-
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            mConnectedController = controller;
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TestUtils.equals(mConnectedController, controller)) {
-                            disconnectedLatch.countDown();
-                        }
-                    }
-                })
-                .build()) {
-            // Make onDisconnected() to be called immediately after the connection.
-            session.setLegacyControllerConnectionTimeoutMs(0);
-            controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompatToken(), /* waitForConnection= */ true);
-            // Invoke any command for session to recognize the controller compat.
-            controller.getTransportControls().seekTo(111);
-            assertTrue(disconnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            if (controller != null) {
-                controller.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void connectedCallbackAfterDisconnectedByTimeout() throws Exception {
-        CountDownLatch connectedLatch = new CountDownLatch(2);
-        CountDownLatch disconnectedLatch = new CountDownLatch(1);
-        RemoteMediaControllerCompat controller = null;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("connectedCallbackAfterDisconnectedByTimeout")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    private ControllerInfo mConnectedController;
-
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            mConnectedController = controller;
-                            connectedLatch.countDown();
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TestUtils.equals(mConnectedController, controller)) {
-                            disconnectedLatch.countDown();
-                        }
-                    }
-                })
-                .build()) {
-            // Make onDisconnected() to be called immediately after the connection.
-            session.setLegacyControllerConnectionTimeoutMs(0);
-            controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompatToken(), /* waitForConnection= */ true);
-            // Invoke any command for session to recognize the controller compat.
-            controller.getTransportControls().seekTo(111);
-            assertTrue(disconnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Test whenter onConnect() is called again after the onDisconnected().
-            controller.getTransportControls().seekTo(111);
-
-            assertTrue(connectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            if (controller != null) {
-                controller.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void play() {
-        mController.getTransportControls().play();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pause() {
-        mController.getTransportControls().pause();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void stop() {
-        // MediaControllerCompat#stop() will call MediaSession#pause() and MediaSession#seekTo(0).
-        // Therefore, the latch's initial count is 2.
-        MockPlayer player = new MockPlayer(2);
-        player.mCurrentPosition = 1530;
-        mSession.updatePlayer(player);
-
-        mController.getTransportControls().stop();
-        try {
-            assertTrue(player.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(player.mPauseCalled);
-        assertTrue(player.mSeekToCalled);
-        assertEquals(0, player.mSeekPosition);
-    }
-
-    @Test
-    public void prepare() {
-        mController.getTransportControls().prepare();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() {
-        final long seekPosition = 12125L;
-        mController.getTransportControls().seekTo(seekPosition);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeed() {
-        final float testSpeed = 2.0f;
-        mController.getTransportControls().setPlaybackSpeed(testSpeed);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
-        assertEquals(testSpeed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void addQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Prepare an item to add.
-        final String mediaId = "media_id";
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId)
-                .build();
-        mController.addQueueItem(desc);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(mediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void addQueueItemWithIndex() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Prepare an item to add.
-        final int testIndex = 0;
-        final String mediaId = "media_id";
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId)
-                .build();
-        mController.addQueueItem(desc, testIndex);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertEquals(mediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void removeQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Select an item to remove.
-        final int targetIndex = 3;
-        final MediaItem targetItem = playlist.get(targetIndex);
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(targetItem.getMediaId())
-                .build();
-        mController.removeQueueItem(desc);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void skipToPrevious() throws InterruptedException {
-        mController.getTransportControls().skipToPrevious();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNext() throws InterruptedException {
-        mController.getTransportControls().skipToNext();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Get Queue from local MediaControllerCompat.
-        List<QueueItem> queue = mSession.getSessionCompat().getController().getQueue();
-        final int targetIndex = 3;
-        mController.getTransportControls().skipToQueueItem(queue.get(targetIndex).getQueueId());
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void setShuffleMode() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mController.getTransportControls().setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatMode() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mController.getTransportControls().setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void setVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int targetVolume = 50;
-        mController.setVolumeTo(targetVolume, 0 /* flags */);
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mSetVolumeToCalled);
-        assertEquals(targetVolume, (int) remotePlayer.mCurrentVolume);
-    }
-
-    @Test
-    public void adjustVolume() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        mController.adjustVolume(direction, 0 /* flags */);
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mAdjustVolumeCalled);
-        assertEquals(direction, remotePlayer.mDirection);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        MockPlayer player = new MockPlayer(0);
-        player.setAudioAttributes(attrs);
-
-        // Replace with another player rather than setting the audio attribute of the existing
-        // player for making changes to take effect immediately.
-        mSession.updatePlayer(player);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        MockPlayer player = new MockPlayer(0);
-        player.setAudioAttributes(attrs);
-        // Replace with another player rather than setting the audio attribute of the existing
-        // player for making changes to take effect immediately.
-        mSession.updatePlayer(player);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void sendCommand() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        // TODO(jaewan): Need to revisit with the permission.
-        final String testCommand = "test_command";
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "test_args");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup commands = super.onConnect(session, controller);
-                SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder(commands);
-                builder.addCommand(new SessionCommand(testCommand, null));
-                return builder.build();
-            }
-
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller,
-                    @NonNull SessionCommand sessionCommand, Bundle args) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testCommand, sessionCommand.getCustomAction());
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        mSession.close();
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
-        final RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        controller.sendCommand(testCommand, testArgs, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_sessionRejects() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                return null;
-            }
-        };
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-            }
-        });
-
-        // Session will not accept the controller's commands.
-        RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        controller.getTransportControls().play();
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void fastForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onFastForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testFastForward").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().fastForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void rewind() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testRewind").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().rewind();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void prepareFromMediaUri() throws InterruptedException {
-        final Uri mediaId = Uri.parse("foo://bar");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(mediaId, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromMediaUri").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromUri(mediaId, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromMediaUri() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final Uri request = Uri.parse("foo://bar");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(request, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromMediaUri").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromUri(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void prepareFromMediaId() throws InterruptedException {
-        final String request = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/prepareFromMediaId?id=" + request,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromMediaId").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromMediaId(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromMediaId() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final String mediaId = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/playFromMediaId?id=" + mediaId,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromMediaId").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromMediaId(mediaId, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void prepareFromSearch() throws InterruptedException {
-        final String query = "test_query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/prepareFromSearch?query=" + query,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromSearch").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromSearch(query, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromSearch() throws InterruptedException {
-        final String query = "test_query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/playFromSearch?query=" + query,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromSearch").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromSearch(query, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void setRating() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final int ratingType = RatingCompat.RATING_5_STARS;
-        final float ratingValue = 3.5f;
-        final RatingCompat rating = RatingCompat.newStarRating(ratingType, ratingValue);
-        final String mediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetRating(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaIdOut,
-                    @NonNull Rating ratingOut) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(mediaId, mediaIdOut);
-                assertEquals(MediaUtils.convertToRating(rating), ratingOut);
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-
-        mPlayer.mCurrentMediaItem = MediaTestUtils.createMediaItem(mediaId);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testSetRating").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().setRating(rating);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onCommandCallback() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        final ArrayList<SessionCommand> commands = new ArrayList<>();
-        final CountDownLatch latchForPause = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onCommandRequest(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controllerInfo, @NonNull SessionCommand command) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controllerInfo.getPackageName());
-                assertFalse(controllerInfo.isTrusted());
-                commands.add(command);
-                if (command.getCommandCode() == SessionCommand.COMMAND_CODE_PLAYER_PAUSE) {
-                    latchForPause.countDown();
-                    return RESULT_ERROR_INVALID_STATE;
-                }
-                return RESULT_SUCCESS;
-            }
-        };
-
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mPlayer = new MockPlayer(1);
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setId("testOnCommandCallback")
-                        .setSessionCallback(sHandlerExecutor, callback).build();
-            }
-        });
-        RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-
-        controller.getTransportControls().pause();
-        assertTrue(latchForPause.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(1, commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                (long) commands.get(0).getCommandCode());
-
-        controller.getTransportControls().play();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(2, commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                (long) commands.get(1).getCommandCode());
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    @LargeTest
-    public void deadlock() throws InterruptedException {
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = null;
-            }
-        });
-
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    mSession = new MediaSession.Builder(mContext, player)
-                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                            .setId("testDeadlock").build();
-                }
-            });
-            final RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, mSession.getSessionCompat().getSessionToken(), true);
-            testHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final int state = SessionPlayer.PLAYER_STATE_ERROR;
-                    for (int i = 0; i < 100; i++) {
-                        // triggers call from session to controller.
-                        player.notifyPlayerStateChanged(state);
-                        // triggers call from controller to session.
-                        controller.getTransportControls().play();
-
-                        // Repeat above
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().pause();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().stop();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().skipToNext();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().skipToPrevious();
-                    }
-                    // This may hang if deadlock happens.
-                    latch.countDown();
-                }
-            });
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Clean up here because sessionHandler will be removed afterwards.
-                        mSession.close();
-                        mSession = null;
-                    }
-                });
-            }
-
-            if (Build.VERSION.SDK_INT >= 18) {
-                sessionThread.quitSafely();
-                testThread.quitSafely();
-            } else {
-                sessionThread.quit();
-                testThread.quit();
-            }
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void controllerAfterSessionIsGone() throws InterruptedException {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        mSession.close();
-        testSessionCallbackIsNotCalled();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        // Recreated session has different session stub, so previously created controller
-        // shouldn't be available.
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .setId(TAG)
-                .build();
-        testSessionCallbackIsNotCalled();
-    }
-
-    void testSessionCallbackIsNotCalled() throws InterruptedException {
-        mController.getTransportControls().play();
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java
deleted file mode 100644
index 4229ce1..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java
+++ /dev/null
@@ -1,1080 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
-
-import static androidx.media2.session.MediaConstants.MEDIA_URI_AUTHORITY;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_PATH_SET_MEDIA_URI;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_ID;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_URI;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_SCHEME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionToken;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSessionCompatCallbackWithMediaControllerTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaControllerTest";
-
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    PendingIntent mIntent;
-    MediaSessionCompat mSession;
-    MediaSessionCallback mSessionCallback;
-    AudioManager mAudioManager;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-
-        mSessionCallback = new MediaSessionCallback();
-        mSession = new MediaSessionCompat(mContext, TAG + "Compat");
-        mSession.setCallback(mSessionCallback, sHandler);
-        mSession.setSessionActivity(mIntent);
-        mSession.setActive(true);
-
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-    }
-
-    private RemoteMediaController createControllerAndWaitConnection() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<SessionToken> sessionToken2 = new AtomicReference<>();
-        SessionToken.createSessionToken(mContext, mSession.getSessionToken(),
-                (compatToken, sessionToken) -> {
-                    assertTrue(sessionToken.isLegacySession());
-                    sessionToken2.set(sessionToken);
-                    latch.countDown();
-                });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        return createRemoteController(sessionToken2.get(), true, null);
-    }
-
-    @Test
-    public void play() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.play();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void pause() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.pause();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnPauseCalled);
-    }
-
-    @Test
-    public void prepare() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.prepare();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        final long seekPosition = 12125L;
-        controller.seekTo(seekPosition);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSeekToCalled);
-        assertEquals(seekPosition, mSessionCallback.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeed() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        final float testSpeed = 2.0f;
-        controller.setPlaybackSpeed(testSpeed);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetPlaybackSpeedCalled);
-        assertEquals(testSpeed, mSessionCallback.mSpeed, 0.0f);
-    }
-
-    @Test
-    public void addPlaylistItem() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final String testMediaId = "testAddPlaylistItem";
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int testIndex = 1;
-        controller.addPlaylistItem(testIndex, testMediaId);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
-
-        assertEquals(testIndex, mSessionCallback.mQueueIndex);
-        assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
-        assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
-    }
-
-    @Test
-    public void removePlaylistItem() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final MediaItem itemToRemove = testList.get(1);
-        controller.removePlaylistItem(1);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForRemove);
-        assertEquals(itemToRemove.getMediaId(),
-                mSessionCallback.mQueueDescriptionForRemove.getMediaId());
-    }
-
-    @Test
-    public void replacePlaylistItem() throws Exception {
-        final int testReplaceIndex = 1;
-        // replace = remove + add
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final String testMediaId = "testReplacePlaylistItem";
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        mSessionCallback.reset(2);
-        controller.replacePlaylistItem(testReplaceIndex, testMediaId);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
-        assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForRemove);
-        assertEquals(testList.get(testReplaceIndex).getMediaId(),
-                mSessionCallback.mQueueDescriptionForRemove.getMediaId());
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
-        assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
-    }
-
-    @Test
-    public void skipToPreviousItem() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.skipToPreviousItem();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToPreviousCalled);
-    }
-
-    @Test
-    public void skipToNextItem() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.skipToNextItem();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToNextCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItem() throws Exception {
-        final int testSkipToIndex = 1;
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        mSessionCallback.reset(1);
-        controller.skipToPlaylistItem(testSkipToIndex);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToQueueItemCalled);
-        assertEquals(testQueue.get(testSkipToIndex).getQueueId(), mSessionCallback.mQueueItemId);
-    }
-
-    @Test
-    public void setShuffleMode() throws Exception {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        mSession.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setShuffleMode(testShuffleMode);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mSessionCallback.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatMode() throws Exception {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_ALL;
-
-        mSession.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setRepeatMode(testRepeatMode);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mSessionCallback.mRepeatMode);
-    }
-
-    @Test
-    public void setVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-        mSession.setPlaybackToRemote(volumeProvider);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int targetVolume = 50;
-        controller.setVolumeTo(targetVolume, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mSetVolumeToCalled);
-        assertEquals(targetVolume, volumeProvider.mVolume);
-    }
-
-    @Test
-    public void adjustVolume() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-        mSession.setPlaybackToRemote(volumeProvider);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        controller.adjustVolume(direction, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mAdjustVolumeCalled);
-        assertEquals(direction, volumeProvider.mDirection);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-        // Set stream of the session.
-        mSession.setPlaybackToLocal(stream);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-        // Set stream of the session.
-        mSession.setPlaybackToLocal(stream);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void sendCustomCommand() throws Exception {
-        final String command = "test_custom_command";
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "test_args");
-        final SessionCommand testCommand = new SessionCommand(command, null);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.sendCustomCommand(testCommand, testArgs);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnCommandCalled);
-        assertEquals(command, mSessionCallback.mCommand);
-        assertTrue(TestUtils.equals(testArgs, mSessionCallback.mExtras));
-    }
-
-    @Test
-    public void fastForward() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.fastForward();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnFastForwardCalled);
-    }
-
-    @Test
-    public void rewind() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.rewind();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnRewindCalled);
-    }
-
-    @FlakyTest(bugId = 204596299)
-    @Test
-    public void setRating() throws Exception {
-        final float ratingValue = 3.5f;
-        final Rating rating2 = new StarRating(5, ratingValue);
-        final String mediaId = "media_id";
-        final MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId).build();
-        mSession.setMetadata(metadata);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setRating(mediaId, rating2);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetRatingCalled);
-        assertEquals(rating2, MediaUtils.convertToRating(mSessionCallback.mRating));
-    }
-
-    @Test
-    public void setMediaUri_ignored() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(Uri.parse("androidx://test?test=xx"), /* extras= */ null);
-
-        assertFalse(mSessionCallback.await(TIMEOUT_MS));
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromMediaId() throws Exception {
-        String testMediaId = "anyMediaId";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromMediaIdCalled);
-        assertEquals(testMediaId, mSessionCallback.mMediaId);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mQuery);
-        assertNull(mSessionCallback.mUri);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromSearch() throws Exception {
-        String testSearchQuery = "anyQuery";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromSearchCalled);
-        assertEquals(testSearchQuery, mSessionCallback.mQuery);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mUri);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromUri() throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(
-                                MEDIA_URI_QUERY_URI, testMediaUri.toString()).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_withoutFormattingFollowedByPrepare_callsPrepareFromUri()
-            throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(testMediaUri, testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromMediaId() throws Exception {
-        String testMediaId = "anyMediaId";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromMediaIdCalled);
-        assertEquals(testMediaId, mSessionCallback.mMediaId);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mQuery);
-        assertNull(mSessionCallback.mUri);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromSearch() throws Exception {
-        String testSearchQuery = "anyQuery";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromSearchCalled);
-        assertEquals(testSearchQuery, mSessionCallback.mQuery);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mUri);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromUri() throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(
-                                MEDIA_URI_QUERY_URI, testMediaUri.toString()).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_withoutFormattingFollowedByPlay_callsPlayFromUri()
-            throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(testMediaUri, testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepareTwice_callsPrepareFromUriAndPrepare()
-            throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        controller.setMediaUri(Uri.parse("androidx://test"), null);
-
-        controller.prepare();
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertTrue(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlayTwice_callsPlayFromUriAndPlay() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        controller.setMediaUri(Uri.parse("androidx://test"), /* extras= */ null);
-
-        controller.play();
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(1, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_multipleCalls_skipped() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        Uri testUri1 = Uri.parse("androidx://test1");
-        Uri testUri2 = Uri.parse("androidx://test2");
-        controller.setMediaUri(testUri1, /* extras= */ null);
-        controller.setMediaUri(testUri2, /* extras= */ null);
-        controller.prepare();
-
-        assertFalse(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testUri2, mSessionCallback.mUri);
-    }
-
-    private void setPlaybackState(int state) {
-        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
-        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
-                .setState(state, 0L, 0.0f).build();
-        mSession.setPlaybackState(playbackState);
-    }
-
-    class TestVolumeProvider extends VolumeProviderCompat {
-        final CountDownLatch mLatch = new CountDownLatch(1);
-        boolean mSetVolumeToCalled;
-        boolean mAdjustVolumeCalled;
-        int mVolume;
-        int mDirection;
-
-        TestVolumeProvider(int controlType, int maxVolume, int currentVolume) {
-            super(controlType, maxVolume, currentVolume);
-        }
-
-        @Override
-        public void onSetVolumeTo(int volume) {
-            mSetVolumeToCalled = true;
-            mVolume = volume;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAdjustVolume(int direction) {
-            mAdjustVolumeCalled = true;
-            mDirection = direction;
-            mLatch.countDown();
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSessionCompat.Callback {
-        private CountDownLatch mLatch = new CountDownLatch(1);
-        private long mSeekPosition;
-        private float mSpeed;
-        private long mQueueItemId;
-        private RatingCompat mRating;
-        private String mMediaId;
-        private String mQuery;
-        private Uri mUri;
-        private String mAction;
-        private String mCommand;
-        private Bundle mExtras;
-        private ResultReceiver mCommandCallback;
-        private boolean mCaptioningEnabled;
-        private int mRepeatMode;
-        private int mShuffleMode;
-        private int mQueueIndex;
-        private MediaDescriptionCompat mQueueDescriptionForAdd;
-        private MediaDescriptionCompat mQueueDescriptionForRemove;
-
-        private int mOnPlayCalledCount;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-        private boolean mOnSeekToCalled;
-        private boolean mOnSetPlaybackSpeedCalled;
-        private boolean mOnSkipToQueueItemCalled;
-        private boolean mOnSetRatingCalled;
-        private boolean mOnPlayFromMediaIdCalled;
-        private boolean mOnPlayFromSearchCalled;
-        private boolean mOnPlayFromUriCalled;
-        private boolean mOnCustomActionCalled;
-        private boolean mOnCommandCalled;
-        private boolean mOnPrepareCalled;
-        private boolean mOnPrepareFromMediaIdCalled;
-        private boolean mOnPrepareFromSearchCalled;
-        private boolean mOnPrepareFromUriCalled;
-        private boolean mOnSetCaptioningEnabledCalled;
-        private boolean mOnSetRepeatModeCalled;
-        private boolean mOnSetShuffleModeCalled;
-        private boolean mOnAddQueueItemCalled;
-        private boolean mOnAddQueueItemAtCalled;
-        private boolean mOnRemoveQueueItemCalled;
-
-        public void reset(int count) {
-            mLatch = new CountDownLatch(count);
-            mSeekPosition = -1;
-            mSpeed = -1.0f;
-            mQueueItemId = -1;
-            mRating = null;
-            mMediaId = null;
-            mQuery = null;
-            mUri = null;
-            mAction = null;
-            mExtras = null;
-            mCommand = null;
-            mCommandCallback = null;
-            mCaptioningEnabled = false;
-            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
-            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
-            mQueueIndex = -1;
-            mQueueDescriptionForAdd = null;
-            mQueueDescriptionForRemove = null;
-
-            mOnPlayCalledCount = 0;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-            mOnSkipToQueueItemCalled = false;
-            mOnSeekToCalled = false;
-            mOnSetPlaybackSpeedCalled = false;
-            mOnSetRatingCalled = false;
-            mOnPlayFromMediaIdCalled = false;
-            mOnPlayFromSearchCalled = false;
-            mOnPlayFromUriCalled = false;
-            mOnCustomActionCalled = false;
-            mOnCommandCalled = false;
-            mOnPrepareCalled = false;
-            mOnPrepareFromMediaIdCalled = false;
-            mOnPrepareFromSearchCalled = false;
-            mOnPrepareFromUriCalled = false;
-            mOnSetCaptioningEnabledCalled = false;
-            mOnSetRepeatModeCalled = false;
-            mOnSetShuffleModeCalled = false;
-            mOnAddQueueItemCalled = false;
-            mOnAddQueueItemAtCalled = false;
-            mOnRemoveQueueItemCalled = false;
-        }
-
-        public boolean await(long timeoutMs) {
-            try {
-                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        @Override
-        public void onPlay() {
-            mOnPlayCalledCount++;
-            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPause() {
-            mOnPauseCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onStop() {
-            mOnStopCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onFastForward() {
-            mOnFastForwardCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRewind() {
-            mOnRewindCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            mOnSkipToPreviousCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToNext() {
-            mOnSkipToNextCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSeekTo(long pos) {
-            mOnSeekToCalled = true;
-            mSeekPosition = pos;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetPlaybackSpeed(float speed) {
-            mOnSetPlaybackSpeedCalled = true;
-            mSpeed = speed;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetRating(RatingCompat rating) {
-            mOnSetRatingCalled = true;
-            mRating = rating;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            mOnPlayFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromSearch(String query, Bundle extras) {
-            mOnPlayFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromUri(Uri uri, Bundle extras) {
-            mOnPlayFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onCustomAction(String action, Bundle extras) {
-            mOnCustomActionCalled = true;
-            mAction = action;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToQueueItem(long id) {
-            mOnSkipToQueueItemCalled = true;
-            mQueueItemId = id;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
-            mOnCommandCalled = true;
-            mCommand = command;
-            mExtras = extras;
-            mCommandCallback = cb;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepare() {
-            mOnPrepareCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
-            mOnPrepareFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromSearch(String query, Bundle extras) {
-            mOnPrepareFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromUri(Uri uri, Bundle extras) {
-            mOnPrepareFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetRepeatMode(int repeatMode) {
-            mOnSetRepeatModeCalled = true;
-            mRepeatMode = repeatMode;
-            mSession.setRepeatMode(repeatMode);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description) {
-            mOnAddQueueItemCalled = true;
-            mQueueDescriptionForAdd = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
-            mOnAddQueueItemAtCalled = true;
-            mQueueIndex = index;
-            mQueueDescriptionForAdd = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRemoveQueueItem(MediaDescriptionCompat description) {
-            mOnRemoveQueueItemCalled = true;
-            mQueueDescriptionForRemove = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetCaptioningEnabled(boolean enabled) {
-            mOnSetCaptioningEnabledCalled = true;
-            mCaptioningEnabled = enabled;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetShuffleMode(int shuffleMode) {
-            mOnSetShuffleModeCalled = true;
-            mShuffleMode = shuffleMode;
-            mSession.setShuffleMode(shuffleMode);
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java
deleted file mode 100644
index b015985..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.media2.session.MediaSessionManager;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Tests {@link MediaSessionManagerTest}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSessionManagerTest extends MediaTestBase {
-    private Context mContext;
-
-    private static final ComponentName MOCK_BROWSER_SERVICE_COMPAT_NAME = new ComponentName(
-            SERVICE_PACKAGE_NAME, MockMediaBrowserServiceCompat.class.getCanonicalName());
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @Test
-    public void getSessionServiceTokens() {
-        boolean hasMockBrowserServiceCompat = false;
-        boolean hasMockSessionService2 = false;
-        boolean hasMockLibraryService2 = false;
-        MediaSessionManager sessionManager = MediaSessionManager.getInstance(mContext);
-        Set<SessionToken> serviceTokens = sessionManager.getSessionServiceTokens();
-        for (SessionToken token : serviceTokens) {
-            ComponentName componentName = token.getComponentName();
-            if (MOCK_BROWSER_SERVICE_COMPAT_NAME.equals(componentName)) {
-                hasMockBrowserServiceCompat = true;
-            } else if (MOCK_MEDIA2_SESSION_SERVICE.equals(componentName)) {
-                hasMockSessionService2 = true;
-            } else if (MOCK_MEDIA2_LIBRARY_SERVICE.equals(componentName)) {
-                hasMockLibraryService2 = true;
-            }
-        }
-        assertTrue(hasMockBrowserServiceCompat);
-        assertTrue(hasMockSessionService2);
-        assertTrue(hasMockLibraryService2);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
deleted file mode 100644
index b9520b8..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.media2.test.service.TestServiceRegistry;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Manual test of {@link MediaSessionService} for showing/removing notification
- * when the playback is started/ended.
- * <p>
- * This test is a manual test, which means the one who runs this test should keep looking at the
- * device and check whether the notification is shown/removed.
- */
-@LargeTest
-public class MediaSessionServiceNotificationTest extends MediaSessionTestBase {
-    private static final long NOTIFICATION_SHOW_TIME_MS = 15000;
-
-    MediaSession mSession;
-    MockPlayer mPlayer;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(true);
-        TestServiceRegistry.getInstance().setHandler(sHandler);
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    @Ignore("Comment out this line and manually run the test.")
-    public void notification() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                    mSession = session;
-                    // Change the player and playlist agent with ours.
-                    session.updatePlayer(mPlayer);
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        // Create a controller to start the service.
-        RemoteMediaController controller = createRemoteController(
-                new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
-
-        // Set current media item.
-        Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
-                androidx.media2.test.service.R.drawable.big_buck_bunny);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
-                .putText(METADATA_KEY_ARTIST, "Test Artist Name")
-                .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-        mPlayer.mCurrentMediaItem = new MediaItem.Builder()
-                        .setMetadata(metadata)
-                        .build();
-
-        // Notification should be shown. Clicking play/pause button will change the player state.
-        // When playing, the notification will not be removed by swiping horizontally.
-        // When paused, the notification can be swiped away.
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-    }
-
-    @Test
-    @Ignore("Comment out this line and manually run the test.")
-    public void notificationUpdatedWhenCurrentMediaItemChanged() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                    mSession = session;
-                    // Change the player and playlist agent with ours.
-                    session.updatePlayer(mPlayer);
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        // Create a controller to start the service.
-        RemoteMediaController controller = createRemoteController(
-                new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
-
-        // Set current media item.
-        Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
-                androidx.media2.test.service.R.drawable.big_buck_bunny);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
-                .putText(METADATA_KEY_ARTIST, "Test Artist Name")
-                .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-        mPlayer.mCurrentMediaItem = new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        // At this point, the notification should be shown.
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-
-        // Set a new media item. (current media item is changed)
-        MediaMetadata newMetadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "New media ID")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "New Song Name")
-                .putText(METADATA_KEY_ARTIST, "New Artist Name")
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-
-        MediaItem newItem = new MediaItem.Builder().setMetadata(newMetadata).build();
-        mPlayer.mCurrentMediaItem = newItem;
-
-        // Calling this should update the notification with the new metadata.
-        mPlayer.notifyCurrentMediaItemChanged(newItem);
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
deleted file mode 100644
index 91617b9..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MockMediaSessionService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.media2.test.service.TestServiceRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSessionService}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@MediumTest
-public class MediaSessionServiceTest extends MediaSessionTestBase {
-    private SessionToken mToken;
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        TestServiceRegistry.getInstance().cleanUp();
-        TestServiceRegistry.getInstance().setHandler(sHandler);
-        mToken = new SessionToken(mContext,
-                new ComponentName(mContext, MockMediaSessionService.class));
-    }
-
-    @Override
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * is called when controller tries to connect, with the proper arguments.
-     */
-    @Test
-    public void onGetSessionIsCalled() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        controllerInfoList.add(controllerInfo);
-                        latch.countDown();
-                        return null;
-                    }
-                });
-
-        Bundle testHints = new Bundle();
-        testHints.putString("test_key", "test_value");
-        RemoteMediaController controller = createRemoteController(
-                mToken, /*waitForConnection=*/false, testHints);
-
-        // onGetSession() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(CLIENT_PACKAGE_NAME, controllerInfoList.get(0).getPackageName());
-        assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-    }
-
-
-    /**
-     * Tests whether the controller is connected to the session which is returned from
-     * {@link MediaSessionService#onGetSession(ControllerInfo)}.
-     * Also checks whether the connection hints are properly passed to
-     * {@link MediaSession.SessionCallback#onConnect(MediaSession, ControllerInfo)}.
-     */
-    @Test
-    public void onGetSession_returnsSession() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try (MediaSession testSession = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId("testOnGetSession_returnsSession")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        controllerInfoList.add(controller);
-                        latch.countDown();
-                        return new SessionCommandGroup.Builder().build();
-                    }
-                }).build()) {
-
-            TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                    new TestServiceRegistry.OnGetSessionHandler() {
-                        @Override
-                        public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                            return testSession;
-                        }
-                    });
-
-            Bundle testHints = new Bundle();
-            testHints.putString("test_key", "test_value");
-            RemoteMediaController controller = createRemoteController(mToken, true, testHints);
-
-            // MediaSession.SessionCallback#onConnect() should be called.
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(CLIENT_PACKAGE_NAME, controllerInfoList.get(0).getPackageName());
-            assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-
-            // The controller should be connected to the right session.
-            assertNotEquals(mToken, controller.getConnectedSessionToken());
-            assertEquals(testSession.getToken(), controller.getConnectedSessionToken());
-        }
-    }
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * can return different sessions for different controllers.
-     */
-    @Test
-    @Ignore("Flaky: b/291281118")
-    public void onGetSession_returnsDifferentSessions() {
-        final List<SessionToken> tokens = new ArrayList<>();
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        MediaSession session = createMediaSession(
-                                "testOnGetSession_returnsDifferentSessions"
-                                        + System.currentTimeMillis());
-                        tokens.add(session.getToken());
-                        return session;
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-
-        assertNotEquals(controller1.getConnectedSessionToken(),
-                controller2.getConnectedSessionToken());
-        assertEquals(tokens.get(0), controller1.getConnectedSessionToken());
-        assertEquals(tokens.get(1), controller2.getConnectedSessionToken());
-    }
-
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * can reject incoming connection by returning null.
-     */
-    @Test
-    public void onGetSession_rejectsConnection() throws InterruptedException {
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        return null;
-                    }
-                });
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mToken)
-                .setControllerCallback(sHandlerExecutor, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onDisconnected(@NonNull MediaController controller) {
-                        latch.countDown();
-                    }
-                })
-                .build();
-
-        // MediaController2.ControllerCallback#onDisconnected() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getConnectedToken());
-        controller.close();
-    }
-
-    @Test
-    public void allControllersDisconnected_oneSession() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(
-                new TestServiceRegistry.SessionServiceCallback() {
-                    @Override
-                    public void onCreated() {
-                        // no-op
-                    }
-
-                    @Override
-                    public void onDestroyed() {
-                        latch.countDown();
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-        controller1.close();
-        controller2.close();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void allControllersDisconnected_multipleSessions() throws InterruptedException {
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        return createMediaSession("testAllControllersDisconnected"
-                                + System.currentTimeMillis());
-                    }
-                });
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(
-                new TestServiceRegistry.SessionServiceCallback() {
-                    @Override
-                    public void onCreated() {
-                        // no-op
-                    }
-
-                    @Override
-                    public void onDestroyed() {
-                        latch.countDown();
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-
-        controller1.close();
-        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
-
-        // Service should be closed only when all controllers are closed.
-        controller2.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getSessions() throws Exception {
-        RemoteMediaController controller = createRemoteController(mToken, true, null);
-        MediaSessionService service =
-                TestServiceRegistry.getInstance().getServiceInstanceBlocking();
-        try (MediaSession session = createMediaSession("testGetSessions")) {
-            service.addSession(session);
-            List<MediaSession> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            service.removeSession(session);
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    @Test
-    public void addSessions_removedWhenClose() throws Exception {
-        RemoteMediaController controller = createRemoteController(mToken, true, null);
-        MediaSessionService service =
-                TestServiceRegistry.getInstance().getServiceInstanceBlocking();
-        try (MediaSession session = createMediaSession("testAddSessions_removedWhenClose")) {
-            service.addSession(session);
-            List<MediaSession> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            session.close();
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    private MediaSession createMediaSession(String id) {
-        return new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId(id)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
deleted file mode 100644
index 621b2e5..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.CustomParcelable;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionTest extends MediaSessionTestBase {
-
-    private static final String TAG = "MediaSessionTest";
-
-    private MediaSession mSession;
-    private MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-
-        if (mSession != null && !mSession.isClosed()) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setId(TAG)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                }).build();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void builder() {
-        MediaSession.Builder builder;
-        try {
-            builder = new MediaSession.Builder(mContext, null);
-            fail("null player shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setId(null);
-            fail("null id shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setExtras(null);
-            fail("null extras shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            Bundle extras = new Bundle();
-            extras.putParcelable("key", new CustomParcelable(1));
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setExtras(extras);
-            fail("custom parcelables shouldn't be allowed for extras");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-    }
-
-    @Test
-    public void getDuration() throws Exception {
-        final long testDuration = 9999;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        mPlayer.mDuration = testDuration;
-        assertEquals(testDuration, mSession.getPlayer().getDuration());
-    }
-
-    @Test
-    public void getPlaybackSpeed() throws Exception {
-        final float speed = 1.5f;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        mPlayer.setPlaybackSpeed(speed);
-        assertEquals(speed, mSession.getPlayer().getPlaybackSpeed(), 0.0f);
-    }
-
-    @Test
-    public void getPlayerState() {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        mPlayer.mLastPlayerState = state;
-        assertEquals(state, mSession.getPlayer().getPlayerState());
-    }
-
-    @Test
-    public void getBufferingState() {
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        mPlayer.mLastBufferingState = bufferingState;
-        assertEquals(bufferingState, mSession.getPlayer().getBufferingState());
-    }
-
-    @Test
-    public void getPosition() {
-        final long position = 150000;
-        mPlayer.mCurrentPosition = position;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        assertEquals(position, mSession.getPlayer().getCurrentPosition());
-    }
-
-    @Test
-    public void getBufferedPosition() {
-        final long bufferedPosition = 900000;
-        mPlayer.mBufferedPosition = bufferedPosition;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        assertEquals(bufferedPosition, mSession.getPlayer().getBufferedPosition());
-    }
-
-    @Test
-    public void getCurrentMediaItem() {
-        MediaItem item = MediaTestUtils.createMediaItemWithMetadata();
-        mPlayer.mCurrentMediaItem = item;
-        assertEquals(item, mSession.getPlayer().getCurrentMediaItem());
-    }
-
-    @Test
-    public void getPlaylist() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mPlayer.mPlaylist = list;
-        assertEquals(list, mSession.getPlayer().getPlaylist());
-    }
-
-    @Test
-    public void getPlaylistMetadata() {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mPlayer.mMetadata = testMetadata;
-        assertEquals(testMetadata, mSession.getPlayer().getPlaylistMetadata());
-    }
-
-    @Test
-    public void getShuffleMode() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mPlayer.setShuffleMode(testShuffleMode);
-        assertEquals(testShuffleMode, mSession.getPlayer().getShuffleMode());
-    }
-
-    @Test
-    public void getRepeatMode() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mPlayer.setRepeatMode(testRepeatMode);
-        assertEquals(testRepeatMode, mSession.getPlayer().getRepeatMode());
-    }
-
-    @Test
-    public void updatePlayer() throws Exception {
-        MockPlayer player = new MockPlayer(0);
-
-        // Test if setPlayer doesn't crash with various situations.
-        mSession.updatePlayer(mPlayer);
-        assertEquals(mPlayer, mSession.getPlayer());
-
-        mSession.updatePlayer(player);
-        assertEquals(player, mSession.getPlayer());
-    }
-
-    @Test
-    public void getCurrentMediaItem_withMetadata_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1023;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mSession.getPlayer().getCurrentMediaItem().getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-        });
-    }
-
-    @Test
-    public void getCurrentMediaItem_withoutMetadata_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1055;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItem("testCurrentMediaItemChanged");
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mSession.getPlayer().getCurrentMediaItem().getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-        });
-    }
-
-    @Test
-    public void getCurrentMediaItem_withMetadataUpdated_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1023;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        String testDisplayTitle = "testDisplayTitle";
-        MediaMetadata testMetadata = new MediaMetadata.Builder(testMediaItem.getMetadata())
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testDisplayTitle).build();
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        mPlayer.mCurrentMediaItem.setMetadata(testMetadata);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mPlayer.mCurrentMediaItem.getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-            assertEquals(testDisplayTitle,
-                    mPlayer.mCurrentMediaItem.getMetadata().getText(
-                            MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
-        });
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    @LargeTest
-    public void deadlock() throws InterruptedException {
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = null;
-            }
-        });
-
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    mSession = new MediaSession.Builder(mContext, mPlayer)
-                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                            .setId("testDeadlock").build();
-                }
-            });
-            final RemoteMediaController controller = createRemoteController(
-                    mSession.getToken());
-            testHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final int state = SessionPlayer.PLAYER_STATE_ERROR;
-                    for (int i = 0; i < 100; i++) {
-                        Log.d(TAG, "testDeadlock for-loop started: index=" + i);
-                        long startTime = SystemClock.elapsedRealtime();
-
-                        // triggers call from session to controller.
-                        player.notifyPlayerStateChanged(state);
-                        long endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "1) Time spent on API call(ms): " + (endTime - startTime));
-
-                        // triggers call from controller to session.
-                        startTime = endTime;
-                        controller.play();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "2) Time spent on API call(ms): " + (endTime - startTime));
-
-                        // Repeat above
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "3) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.pause();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "4) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "5) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.seekTo(0);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "6) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "7) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.skipToNextItem();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "8) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "9) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.skipToPreviousItem();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "10) Time spent on API call(ms): " + (endTime - startTime));
-                    }
-                    // This may hang if deadlock happens.
-                    latch.countDown();
-                }
-            });
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Clean up here because sessionHandler will be removed afterwards.
-                        mSession.close();
-                        mSession = null;
-                    }
-                });
-            }
-
-            if (Build.VERSION.SDK_INT >= 18) {
-                sessionThread.quitSafely();
-                testThread.quitSafely();
-            } else {
-                sessionThread.quit();
-                testThread.quit();
-            }
-        }
-    }
-
-    @Test
-    public void creatingTwoSessionWithSameId() {
-        final String sessionId = "testSessionId";
-        MediaSession session = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId(sessionId)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                .build();
-
-        MediaSession.Builder builderWithSameId =
-                new MediaSession.Builder(mContext, new MockPlayer(0));
-        try {
-            builderWithSameId.setId(sessionId)
-                    .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                    .build();
-            fail("Creating a new session with the same ID in a process should not be allowed");
-        } catch (IllegalStateException e) {
-            // expected. pass-through
-        }
-
-        session.close();
-        // Creating a new session with ID of the closed session is okay.
-        MediaSession sessionWithSameId = builderWithSameId.build();
-        sessionWithSameId.close();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
deleted file mode 100644
index 40529f8..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSessionTestBase extends MediaTestBase {
-    static final int TIMEOUT_MS = 1000;
-    static final int WAIT_TIME_FOR_NO_RESPONSE_MS = 500;
-
-    static SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private List<RemoteMediaController> mControllers = new ArrayList<>();
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    SyncHandler handler;
-                    synchronized (MediaSessionTestBase.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (int i = 0; i < mControllers.size(); i++) {
-            mControllers.get(i).cleanUp();
-        }
-    }
-
-    final RemoteMediaController createRemoteController(SessionToken token) {
-        return createRemoteController(token, true, null);
-    }
-
-    final RemoteMediaController createRemoteController(@NonNull SessionToken token,
-            boolean waitForConnection, Bundle connectionHints) {
-        RemoteMediaController controller = new RemoteMediaController(
-                mContext, token, connectionHints, waitForConnection);
-        mControllers.add(controller);
-        return controller;
-    }
-
-    final RemoteMediaBrowser createRemoteBrowser(@NonNull SessionToken token) {
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(
-                mContext, token, true /* waitForConnection */, null /* connectionHints */);
-        mControllers.add(browser);
-        return browser;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
deleted file mode 100644
index c12f098..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.os.Build;
-import android.view.KeyEvent;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession} whether it handles key events correctly.
- * In order to get the media key events, the player state is set to 'Playing' before every test
- * method.
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSession_KeyEventTest extends MediaSessionTestBase {
-    private static String sExpectedControllerPackageName;
-
-    // Intentionally member variable to prevent GC while playback is running.
-    // Should be only used on the sHandler.
-    private MediaPlayer mMediaPlayer;
-
-    private AudioManager mAudioManager;
-    private MediaSession mSession;
-    private MockPlayer mPlayer;
-    private TestSessionCallback mSessionCallback;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 28 || Build.VERSION.SDK_INT < 21) {
-            sExpectedControllerPackageName = SERVICE_PACKAGE_NAME;
-        } else if (Build.VERSION.SDK_INT >= 24) {
-            // KeyEvent from system service has the package name "android".
-            sExpectedControllerPackageName = "android";
-        } else {
-            // In API 21+, MediaSessionCompat#getCurrentControllerInfo always returns fake info.
-            sExpectedControllerPackageName = LEGACY_CONTROLLER;
-        }
-    }
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mPlayer = new MockPlayer(1);
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        mSessionCallback = new TestSessionCallback();
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, mSessionCallback)
-                .build();
-
-        // Make this test to get priority for handling media key event.
-        // Here's the requirement for an app to receive media key events via MediaSession.
-        // SDK < 26: Playback state should become *playing* for receiving key events.
-        // SDK >= 26: Play a media item in the same process of the session for receiving key
-        //            events.
-        if (Build.VERSION.SDK_INT < 26) {
-            mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        } else {
-            final CountDownLatch latch = new CountDownLatch(1);
-            sHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    // Pick the shortest media to finish within the TIMEOUT_MS.
-                    mMediaPlayer = MediaPlayer.create(mContext, R.raw.camera_click);
-                    mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
-                        @Override
-                        public void onCompletion(MediaPlayer mp) {
-                            if (mMediaPlayer != null) {
-                                mMediaPlayer.release();
-                                mMediaPlayer = null;
-                                latch.countDown();
-                            }
-                        }
-                    });
-                    mMediaPlayer.start();
-                }
-            });
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                if (mMediaPlayer != null) {
-                    mMediaPlayer.release();
-                    mMediaPlayer = null;
-                }
-            }
-        });
-        mSession.close();
-    }
-
-    private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) {
-        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
-        if (doubleTap) {
-            mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-            mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
-        }
-    }
-
-    @Test
-    public void playKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pauseKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void nextKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void previousKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void stopKeyEvent() throws Exception {
-        mPlayer = new MockPlayer(2);
-        mSession.updatePlayer(mPlayer);
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-        assertTrue(mPlayer.mSeekToCalled);
-    }
-
-    @Test
-    public void fastForwardKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false);
-        assertTrue(mSessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mSessionCallback.mFastForwardCalled);
-    }
-
-    @Test
-    public void rewindKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
-        assertTrue(mSessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mSessionCallback.mRewindCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_play() throws Exception {
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PAUSED);
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_pause() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-        assertFalse(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-    }
-
-    private static class TestSessionCallback extends MediaSession.SessionCallback {
-        final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-        boolean mFastForwardCalled;
-        boolean mRewindCalled;
-
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            if (sExpectedControllerPackageName.equals(controller.getPackageName())) {
-                return super.onConnect(session, controller);
-            }
-            return null;
-        }
-
-        @Override
-        public int onFastForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            mFastForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-            mRewindCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java
deleted file mode 100644
index 0a13a95..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java
+++ /dev/null
@@ -1,562 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaSession} receives commands that hasn't allowed.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSession_PermissionTest extends MediaSessionTestBase {
-    private static final String SESSION_ID = "MediaSessionTest_permission";
-
-    private MockPlayer mPlayer;
-    private MediaSession mSession;
-    private MySessionCallback mCallback;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-        mPlayer = null;
-        mCallback = null;
-    }
-
-    private MediaSession createSessionWithAllowedActions(final SessionCommandGroup commands) {
-        mPlayer = new MockPlayer(1);
-        mCallback = new MySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (!TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName())) {
-                    return null;
-                }
-                return commands == null ? new SessionCommandGroup() : commands;
-            }
-        };
-        if (mSession != null) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer).setId(SESSION_ID)
-                .setSessionCallback(sHandlerExecutor, mCallback).build();
-        return mSession;
-    }
-
-    private SessionCommandGroup createCommandGroupWith(int commandCode) {
-        SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                .addCommand(new SessionCommand(commandCode))
-                .build();
-        return commands;
-    }
-
-    private SessionCommandGroup createCommandGroupWithout(int commandCode) {
-        SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
-                .removeCommand(new SessionCommand(commandCode))
-                .build();
-        return commands;
-    }
-
-    private void testOnCommandRequest(int commandCode, PermissionTestTask runnable)
-            throws InterruptedException {
-        createSessionWithAllowedActions(createCommandGroupWith(commandCode));
-        runnable.run(createRemoteController(mSession.getToken()));
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnCommandRequestCalled);
-        assertEquals(commandCode, mCallback.mCommand.getCommandCode());
-
-        createSessionWithAllowedActions(createCommandGroupWithout(commandCode));
-        runnable.run(createRemoteController(mSession.getToken()));
-
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnCommandRequestCalled);
-    }
-
-    @Test
-    public void play() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_PLAY, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.play();
-            }
-        });
-    }
-
-    @Test
-    public void pause() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_PAUSE, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.pause();
-            }
-        });
-    }
-
-    @Test
-    public void seekTo() throws InterruptedException {
-        final long position = 10;
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SEEK_TO, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.seekTo(position);
-            }
-        });
-    }
-
-    @Test
-    public void skipToNext() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToNextItem();
-                    }
-                });
-    }
-
-    @Test
-    public void skipToPrevious() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToPreviousItem();
-                    }
-                });
-    }
-
-    @Test
-    public void skipToPlaylistItem() throws InterruptedException {
-        testOnCommandRequest(
-                COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToPlaylistItem(0);
-                    }
-                });
-    }
-
-    @Test
-    public void setPlaylist() throws InterruptedException {
-        final List<String> list = MediaTestUtils.createMediaIds(2);
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SET_PLAYLIST, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setPlaylist(list, null);
-            }
-        });
-    }
-
-    @Test
-    public void setMediaItem() throws InterruptedException {
-        final String testMediaId = "testSetMediaItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setMediaItem(testMediaId);
-            }
-        });
-    }
-
-    @Test
-    public void updatePlaylistMetadata() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.updatePlaylistMetadata(null);
-                    }
-                });
-    }
-
-    @Test
-    public void addPlaylistItem() throws InterruptedException {
-        final String testMediaId = "testAddPlaylistItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.addPlaylistItem(0, testMediaId);
-            }
-        });
-    }
-
-    @Test
-    public void removePlaylistItem() throws InterruptedException {
-        final MediaItem testItem = MediaTestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.removePlaylistItem(0);
-                    }
-                });
-    }
-
-    @Test
-    public void replacePlaylistItem() throws InterruptedException {
-        final String testMediaId = "testReplacePlaylistItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.replacePlaylistItem(0, testMediaId);
-                    }
-                });
-    }
-
-    @Test
-    public void setVolume() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_VOLUME_SET_VOLUME, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setVolumeTo(0, 0);
-            }
-        });
-    }
-
-    @Test
-    public void adjustVolume() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_VOLUME_ADJUST_VOLUME, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.adjustVolume(0, 0);
-            }
-        });
-    }
-
-    @Test
-    public void fastForward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_FAST_FORWARD));
-        createRemoteController(mSession.getToken()).fastForward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnFastForwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_FAST_FORWARD));
-        createRemoteController(mSession.getToken()).fastForward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnFastForwardCalled);
-    }
-
-    @Test
-    public void rewind() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_REWIND));
-        createRemoteController(mSession.getToken()).rewind();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnRewindCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_REWIND));
-        createRemoteController(mSession.getToken()).rewind();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnRewindCalled);
-    }
-
-    @Test
-    public void skipForward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_FORWARD));
-        createRemoteController(mSession.getToken()).skipForward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSkipForwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_FORWARD));
-        createRemoteController(mSession.getToken()).skipForward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSkipForwardCalled);
-    }
-
-    @Test
-    public void skipBackward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_BACKWARD));
-        createRemoteController(mSession.getToken()).skipBackward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSkipBackwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_BACKWARD));
-        createRemoteController(mSession.getToken()).skipBackward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSkipBackwardCalled);
-    }
-
-    @Test
-    public void setMediaUri() throws InterruptedException {
-        final Uri uri = Uri.parse("media://uri");
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        createRemoteController(mSession.getToken()).setMediaUri(uri, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetMediaUriCalled);
-        assertEquals(uri, mCallback.mUri);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        createRemoteController(mSession.getToken()).setMediaUri(uri, null);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSetMediaUriCalled);
-    }
-
-    @Test
-    public void setRating() throws InterruptedException {
-        final String mediaId = "testSetRating";
-        final Rating rating = new StarRating(5, 3.5f);
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_RATING));
-        createRemoteController(mSession.getToken()).setRating(mediaId, rating);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetRatingCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertEquals(rating, mCallback.mRating);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_RATING));
-        createRemoteController(mSession.getToken()).setRating(mediaId, rating);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSetRatingCalled);
-    }
-
-    @Test
-    public void changingPermissionWithSetAllowedCommands() throws InterruptedException {
-        final String mediaId = "testSetRating";
-        final Rating rating = new StarRating(5, 3.5f);
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_RATING));
-        RemoteMediaController controller = createRemoteController(mSession.getToken());
-        controller.setRating(mediaId, rating);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetRatingCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertEquals(rating, mCallback.mRating);
-        mCallback.reset();
-
-        // Change allowed commands.
-        mSession.setAllowedCommands(getTestControllerInfo(),
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_RATING));
-
-        controller.setRating(mediaId, rating);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private ControllerInfo getTestControllerInfo() {
-        List<ControllerInfo> controllers = mSession.getConnectedControllers();
-        assertNotNull(controllers);
-        for (int i = 0; i < controllers.size(); i++) {
-            if (TextUtils.equals(CLIENT_PACKAGE_NAME, controllers.get(i).getPackageName())) {
-                return controllers.get(i);
-            }
-        }
-        fail("Failed to get test controller info");
-        return null;
-    }
-
-    @FunctionalInterface
-    private interface PermissionTestTask {
-        void run(@NonNull RemoteMediaController controller);
-    }
-
-    public class MySessionCallback extends MediaSession.SessionCallback {
-        public CountDownLatch mCountDownLatch;
-
-        public SessionCommand mCommand;
-        public String mMediaId;
-        public Uri mUri;
-        public Bundle mExtras;
-        public Rating mRating;
-
-        public boolean mOnCommandRequestCalled;
-        public boolean mOnFastForwardCalled;
-        public boolean mOnRewindCalled;
-        public boolean mOnSkipForwardCalled;
-        public boolean mOnSkipBackwardCalled;
-        public boolean mOnSetMediaUriCalled;
-        public boolean mOnSetRatingCalled;
-
-
-        public MySessionCallback() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        public void reset() {
-            mCountDownLatch = new CountDownLatch(1);
-
-            mCommand = null;
-            mMediaId = null;
-            mUri = null;
-            mExtras = null;
-
-            mOnCommandRequestCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSetMediaUriCalled = false;
-            mOnSetRatingCalled = false;
-        }
-
-        @Override
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand command) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnCommandRequestCalled = true;
-            mCommand = command;
-            mCountDownLatch.countDown();
-            return super.onCommandRequest(session, controller, command);
-        }
-
-        @Override
-        public int onFastForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnFastForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnRewindCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSkipForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSkipForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSkipBackward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSkipBackwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSetMediaUri(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSetMediaUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSetRating(@NonNull MediaSession session, @NonNull ControllerInfo controller,
-                @NonNull String mediaId, @NonNull Rating rating) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSetRatingCalled = true;
-            mMediaId = mediaId;
-            mRating = rating;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java
deleted file mode 100644
index 78a89f4..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.BeforeClass;
-
-/**
- * Base class for all media tests.
- */
-abstract class MediaTestBase {
-    @BeforeClass
-    public static void setupMainLooper() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Prepare the main looper if it hasn't.
-                // Some framework APIs always run on the main looper.
-                if (Looper.getMainLooper() == null) {
-                    Looper.prepareMainLooper();
-                }
-
-                // Initialize AudioManager on the main thread to workaround b/78617702 that
-                // audio focus listener is called on the thread where the AudioManager was
-                // originally initialized.
-                // Without posting this, audio focus listeners wouldn't be called because the
-                // listeners would be posted to the test thread (here) where it waits until the
-                // tests are finished.
-                Context context = ApplicationProvider.getApplicationContext();
-                AudioManager manager =
-                        (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            }
-        });
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java
deleted file mode 100644
index 97153fb..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.os.Build;
-
-import androidx.media2.test.service.RemoteMediaBrowserCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test {@link RemoteMediaBrowserCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaBrowserCompatTest extends MediaSessionTestBase {
-    private Context mContext;
-    private RemoteMediaBrowserCompat mRemoteBrowserCompat;
-
-    @Before
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        mContext = ApplicationProvider.getApplicationContext();
-        mRemoteBrowserCompat = new RemoteMediaBrowserCompat(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-    }
-
-    @After
-    public void cleanUp() {
-        if (mRemoteBrowserCompat != null) {
-            mRemoteBrowserCompat.cleanUp();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void connect() throws Exception {
-        mRemoteBrowserCompat.connect(true /* waitForConnection */);
-        assertTrue(mRemoteBrowserCompat.isConnected());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java
deleted file mode 100644
index cfab231..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.os.Build;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.media2.test.service.RemoteMediaControllerCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/** Test {@link RemoteMediaControllerCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaControllerCompatTest extends MediaSessionTestBase {
-    private Context mContext;
-    private MediaSessionCompat mSessionCompat;
-    private RemoteMediaControllerCompat mRemoteControllerCompat;
-
-    @Before
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        mContext = ApplicationProvider.getApplicationContext();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSessionCompat = new MediaSessionCompat(mContext, DEFAULT_TEST_NAME);
-                mSessionCompat.setActive(true);
-            }
-        });
-        mRemoteControllerCompat = new RemoteMediaControllerCompat(
-                mContext, mSessionCompat.getSessionToken(), true /* waitForConnection */);
-    }
-
-    @After
-    public void cleanUp() {
-        if (mSessionCompat != null) {
-            mSessionCompat.release();
-        }
-        if (mRemoteControllerCompat != null) {
-            mRemoteControllerCompat.cleanUp();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void play() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        mSessionCompat.setCallback(new MediaSessionCompat.Callback() {
-            @Override
-            public void onPlay() {
-                latch.countDown();
-            }
-        }, sHandler);
-
-        mRemoteControllerCompat.getTransportControls().play();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java
deleted file mode 100644
index ceddfce..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.media.AudioManager;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.MockRemotePlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether the methods of {@link RemoteSessionPlayer} are triggered by the
- * controller.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RemoteSessionPlayerTest extends MediaSessionTestBase {
-
-    MediaSession mSession;
-    RemoteMediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        // Create this test specific MediaSession to use our own Handler.
-        mSession = new MediaSession.Builder(mContext, new MockPlayer(1))
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                })
-                .setId("RemoteSessionPlayerTest")
-                .build();
-        // Create a default MediaController in client app.
-        mController = createRemoteController(mSession.getToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void setVolumeToByController() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer = new MockRemotePlayer(
-                volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int targetVolume = 50;
-        mController.setVolumeTo(targetVolume, 0 /* flags */);
-
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mSetVolumeToCalled);
-        assertEquals(targetVolume, remotePlayer.mCurrentVolume, 0.001f);
-    }
-
-    @Test
-    public void adjustVolumeByController() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE;
-
-        MockRemotePlayer remotePlayer = new MockRemotePlayer(
-                volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        mController.adjustVolume(direction, 0 /* flags */);
-
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mAdjustVolumeCalled);
-        assertEquals(direction, remotePlayer.mDirection);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
deleted file mode 100644
index 2eaf282..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests {@link SessionCommand} and {@link SessionCommandGroup}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SessionCommandTest {
-    // Prefix for all command codes
-    private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
-
-    private static final List<String> PREFIX_COMMAND_CODES = new ArrayList<>();
-    static {
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_PLAYER_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_VOLUME_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_SESSION_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_LIBRARY_");
-    }
-
-    /**
-     * Test possible typos in naming
-     */
-    @Test
-    public void codes_name() {
-        List<Field> fields = getSessionCommandsFields("");
-        for (int i = 0; i < fields.size(); i++) {
-            String name = fields.get(i).getName();
-
-            boolean matches = false;
-            if (name.startsWith("COMMAND_VERSION_") || name.equals("COMMAND_CODE_CUSTOM")) {
-                matches = true;
-            }
-            if (!matches) {
-                for (int j = 0; j < PREFIX_COMMAND_CODES.size(); j++) {
-                    if (name.startsWith(PREFIX_COMMAND_CODES.get(j))) {
-                        matches = true;
-                        break;
-                    }
-                }
-            }
-            assertTrue("Unexpected constant " + name, matches);
-        }
-    }
-
-    /**
-     * Tests possible code duplications in values
-     */
-    @Test
-    public void codes_valueDuplication() throws IllegalAccessException {
-        List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODE);
-        Set<Integer> values = new HashSet<>();
-        for (int i = 0; i < fields.size(); i++) {
-            Integer value = fields.get(i).getInt(null);
-            assertTrue(values.add(value));
-        }
-    }
-
-    /**
-     * Tests whether codes are continuous
-     */
-    @Test
-    @Ignore
-    public void codes_valueContinuous() throws IllegalAccessException {
-        for (int i = 0; i < PREFIX_COMMAND_CODES.size(); i++) {
-            List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODES.get(i));
-            List<Integer> values = new ArrayList<>();
-            for (int j = 0; j < fields.size(); j++) {
-                values.add(fields.get(j).getInt(null));
-            }
-            Collections.sort(values);
-            for (int j = 1; j < values.size(); j++) {
-                assertEquals(
-                        "Command code isn't continuous. Missing " + (values.get(j - 1) + 1)
-                                + " in " + PREFIX_COMMAND_CODES.get(i),
-                        ((int) values.get(j - 1)) + 1, (int) values.get(j));
-            }
-        }
-    }
-
-    @Test
-    public void addAllPredefinedCommands_withVersion1_notHaveVersion2Commands() {
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1);
-        SessionCommandGroup commands = builder.build();
-        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM));
-    }
-
-    @Test
-    public void addAllPredefinedCommands_withVersion2_hasVersion2Commands() {
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_2);
-        SessionCommandGroup commands = builder.build();
-        assertTrue(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        assertTrue(commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM));
-    }
-
-    private static List<Field> getSessionCommandsFields(String prefix) {
-        final List<Field> list = new ArrayList<>();
-        final Field[] fields = SessionCommand.class.getFields();
-        if (fields != null) {
-            for (int i = 0; i < fields.length; i++) {
-                if (isPublicStaticFinalInt(fields[i])
-                        && fields[i].getName().startsWith(prefix)) {
-                    list.add(fields[i]);
-                }
-            }
-        }
-        return list;
-    }
-
-    private static boolean isPublicStaticFinalInt(Field field) {
-        if (field.getType() != int.class) {
-            return false;
-        }
-        int modifier = field.getModifiers();
-        return Modifier.isPublic(modifier) && Modifier.isStatic(modifier)
-                && Modifier.isFinal(modifier);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java
deleted file mode 100644
index badc035..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether the methods of {@link SessionPlayer} are triggered by the
- * session/controller.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class SessionPlayerTest extends MediaSessionTestBase {
-
-    MediaSession mSession;
-    MockPlayer mPlayer;
-    RemoteMediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        // b/204596299
-        assumeTrue(Build.VERSION.SDK_INT != 17);
-
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                        return MediaTestUtils.createMediaItem(mediaId);
-                    }
-                }).build();
-
-        // Create a default MediaController in client app.
-        mController = createRemoteController(mSession.getToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void playBySession() throws Exception {
-        mSession.getPlayer().play();
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void playByController() {
-        mController.play();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pauseBySession() throws Exception {
-        mSession.getPlayer().pause();
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void pauseByController() {
-        mController.pause();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void prepareBySession() throws Exception {
-        mSession.getPlayer().prepare();
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void prepareByController() {
-        mController.prepare();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekToBySession() throws Exception {
-        final long pos = 1004L;
-        mSession.getPlayer().seekTo(pos);
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(pos, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void seekToByController() {
-        final long seekPosition = 12125L;
-        mController.seekTo(seekPosition);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeedBySession() throws Exception {
-        final float speed = 1.5f;
-        mSession.getPlayer().setPlaybackSpeed(speed);
-        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void setPlaybackSpeedByController() throws Exception {
-        final float speed = 1.5f;
-        mController.setPlaybackSpeed(speed);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void setPlaylistBySession() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mSession.getPlayer().setPlaylist(list, null);
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertSame(list, mPlayer.mPlaylist);
-        assertNull(mPlayer.mMetadata);
-    }
-
-    @Test
-    public void setPlaylistByController() throws InterruptedException {
-        final List<String> list = MediaTestUtils.createMediaIds(2);
-        mController.setPlaylist(list, null /* metadata */);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertNull(mPlayer.mMetadata);
-
-        assertNotNull(mPlayer.mPlaylist);
-        assertEquals(list.size(), mPlayer.mPlaylist.size());
-        for (int i = 0; i < list.size(); i++) {
-            assertEquals(list.get(i), mPlayer.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void setPlaylistByControllerWithLongPlaylist() throws InterruptedException {
-        final int listSize = 5000;
-        // Make client app to generate a long list, and call setPlaylist() with it.
-        mController.createAndSetFakePlaylist(listSize, null /* metadata */);
-        assertTrue(mPlayer.mCountDownLatch.await(10, TimeUnit.SECONDS));
-
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertNull(mPlayer.mMetadata);
-
-        assertNotNull(mPlayer.mPlaylist);
-        assertEquals(listSize, mPlayer.mPlaylist.size());
-        for (int i = 0; i < listSize; i++) {
-            // Each item's media ID will be same as its index.
-            assertEquals(TestUtils.getMediaIdInFakeList(i), mPlayer.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void updatePlaylistMetadataBySession() {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mSession.getPlayer().updatePlaylistMetadata(testMetadata);
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertSame(testMetadata, mPlayer.mMetadata);
-    }
-
-    @Test
-    public void updatePlaylistMetadataByController() throws InterruptedException {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mController.updatePlaylistMetadata(testMetadata);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertNotNull(mPlayer.mMetadata);
-        assertEquals(testMetadata.getMediaId(), mPlayer.mMetadata.getMediaId());
-    }
-
-    @Test
-    public void addPlaylistItemBySession() {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().addPlaylistItem(testIndex, testMediaItem);
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void addPlaylistItemByController() throws InterruptedException {
-        final int testIndex = 12;
-        final String testMediaId = "testAddPlaylistItemByController";
-
-        mController.addPlaylistItem(testIndex, testMediaId);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        // MediaController.addPlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void removePlaylistItemBySession() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mSession.getPlayer().setPlaylist(list, null);
-        mSession.getPlayer().removePlaylistItem(0);
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertSame(0, mPlayer.mIndex);
-    }
-
-    @Test
-    public void removePlaylistItemByController() throws InterruptedException {
-        mPlayer.mPlaylist = MediaTestUtils.createPlaylist(2);
-
-        mController.removePlaylistItem(0);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertEquals(0, mPlayer.mIndex);
-    }
-
-    @Test
-    public void replacePlaylistItemBySession() throws InterruptedException {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().replacePlaylistItem(testIndex, testMediaItem);
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void replacePlaylistItemByController() throws InterruptedException {
-        final int testIndex = 12;
-        final String testMediaId = "testReplacePlaylistItemByController";
-
-        mController.replacePlaylistItem(testIndex, testMediaId);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        // MediaController.replacePlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void movePlaylistItemsBySession() throws InterruptedException {
-        final int fromIdx = 3;
-        final int toIdx = 20;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().movePlaylistItem(fromIdx, toIdx);
-        assertTrue(mPlayer.mMovePlaylistItemCalled);
-        assertEquals(fromIdx, mPlayer.mIndex);
-        assertEquals(toIdx, mPlayer.mIndex2);
-    }
-
-    @Test
-    public void movePlaylistItemByController() throws InterruptedException {
-        final int testIndex1 = 3;
-        final int testIndex2 = 20;
-
-        mController.movePlaylistItem(testIndex1, testIndex2);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mMovePlaylistItemCalled);
-        assertEquals(testIndex1, mPlayer.mIndex);
-        assertEquals(testIndex2, mPlayer.mIndex2);
-    }
-
-    @Test
-    public void skipToPreviousItemBySession() {
-        mSession.getPlayer().skipToPreviousPlaylistItem();
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToPreviousItemByController() throws InterruptedException {
-        mController.skipToPreviousItem();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNextItemBySession() throws Exception {
-        mSession.getPlayer().skipToNextPlaylistItem();
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToNextItemByController() throws InterruptedException {
-        mController.skipToNextItem();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItemBySession() throws Exception {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        int targetIndex = 0;
-        mSession.getPlayer().setPlaylist(list, null);
-        mSession.getPlayer().skipToPlaylistItem(targetIndex);
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertSame(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void skipToPlaylistItemByController() throws InterruptedException {
-        mPlayer.mPlaylist = MediaTestUtils.createPlaylist(3);
-        int targetIndex = 2;
-
-        mController.skipToPlaylistItem(targetIndex);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void setShuffleModeBySession() {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mSession.getPlayer().setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setShuffleModeByController() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mController.setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatModeBySession() {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mSession.getPlayer().setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void setRepeatModeByController() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mController.setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java
deleted file mode 100644
index 098d2ce..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Process;
-
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MockMediaLibraryService;
-import androidx.media2.test.service.MockMediaSessionService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests {@link SessionToken}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SessionTokenTest extends MediaTestBase {
-    private Context mContext;
-    private List<MediaSession> mSessions = new ArrayList<>();
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        for (MediaSession session : mSessions) {
-            if (session != null) {
-                session.close();
-            }
-        }
-    }
-
-    @Test
-    public void constructor_sessionService() {
-        SessionToken token = new SessionToken(mContext, new ComponentName(
-                mContext.getPackageName(),
-                MockMediaSessionService.class.getCanonicalName()));
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
-    }
-
-    @Test
-    public void constructor_libraryService() {
-        ComponentName testComponentName = new ComponentName(mContext.getPackageName(),
-                MockMediaLibraryService.class.getCanonicalName());
-        SessionToken token = new SessionToken(mContext, testComponentName);
-
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
-        assertEquals(testComponentName.getClassName(), token.getServiceName());
-    }
-
-    @Test
-    public void getters_whenCreatedBySession() {
-        Bundle testTokenExtras = TestUtils.createTestBundle();
-        MediaSession session = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId("testGetters_whenCreatedBySession")
-                .setExtras(testTokenExtras)
-                .build();
-        mSessions.add(session);
-        SessionToken token = session.getToken();
-
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_SESSION, token.getType());
-        assertTrue(TestUtils.equals(testTokenExtras, token.getExtras()));
-        assertNull(token.getServiceName());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/res/raw/midi8sec.mid b/media2/media2-session/version-compat-tests/current/service/src/androidTest/res/raw/midi8sec.mid
deleted file mode 100644
index 746aca1..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/res/raw/midi8sec.mid
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/current/service/src/main/res/drawable/big_buck_bunny.jpg b/media2/media2-session/version-compat-tests/current/service/src/main/res/drawable/big_buck_bunny.jpg
deleted file mode 100644
index a1cafdf..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/main/res/drawable/big_buck_bunny.jpg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/current/service/src/main/res/raw/camera_click.ogg b/media2/media2-session/version-compat-tests/current/service/src/main/res/raw/camera_click.ogg
deleted file mode 100644
index b836e10..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/main/res/raw/camera_click.ogg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/previous/client/build.gradle b/media2/media2-session/version-compat-tests/previous/client/build.gradle
deleted file mode 100644
index d16354e..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/build.gradle
+++ /dev/null
@@ -1,38 +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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    androidTestImplementation("androidx.media2:media2-session:1.2.0")
-    androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-}
-
-android {
-    namespace "androidx.media2.test.client"
-}
-
-androidx {
-    failOnDeprecationWarnings = false
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 1cb0b1d3..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application android:supportsRtl="true">
-        <activity
-            android:name="androidx.media2.test.client.SurfaceActivity"
-            android:exported="true" />
-
-        <service
-            android:name="androidx.media2.test.client.MediaControllerProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA2_CONTROLLER" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.client.MediaControllerCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_CONTROLLER_COMPAT" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.client.MediaBrowserCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_BROWSER_COMPAT" />
-            </intent-filter>
-        </service>
-    </application>
-
-    <queries>
-        <package android:name="androidx.media2.test.service.test" />
-    </queries>
-</manifest>
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java
deleted file mode 100644
index 8961f98..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaBrowserCompatProviderService.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.ConnectionCallback;
-import android.support.v4.media.MediaBrowserCompat.ItemCallback;
-import android.support.v4.media.MediaBrowserCompat.SearchCallback;
-import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaBrowserCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaBrowserCompat} and calls its methods
- * according to the service app's requests.
- */
-public class MediaBrowserCompatProviderService extends Service {
-    private static final String TAG = "MediaBrowserCompatProviderService";
-
-    Map<String, MediaBrowserCompat> mMediaBrowserCompatMap = new HashMap<>();
-    Map<String, TestBrowserConnectionCallback> mConnectionCallbackMap = new HashMap<>();
-    RemoteMediaBrowserCompatStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaBrowserCompatStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_BROWSER_COMPAT.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    private class RemoteMediaBrowserCompatStub extends IRemoteMediaBrowserCompat.Stub {
-        @Override
-        public void create(final String browserId, final ComponentName componentName)
-                throws RemoteException {
-            try {
-                final TestBrowserConnectionCallback callback = new TestBrowserConnectionCallback();
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        MediaBrowserCompat browser = new MediaBrowserCompat(
-                                MediaBrowserCompatProviderService.this, componentName, callback,
-                                new Bundle() /* rootHints */);
-
-                        mMediaBrowserCompatMap.put(browserId, browser);
-                        mConnectionCallbackMap.put(browserId, callback);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaMediaBrowserCompat",
-                        ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaBrowserCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void connect(String browserId, boolean waitForConnection) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.connect();
-
-            if (waitForConnection) {
-                TestBrowserConnectionCallback callback = mConnectionCallbackMap.get(browserId);
-
-                boolean connected = false;
-                try {
-                    connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException ex) {
-                    Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-                }
-
-                if (!connected) {
-                    Log.e(TAG, "Could not connect to the given browser service.");
-                }
-            }
-        }
-
-        @Override
-        public void disconnect(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.disconnect();
-        }
-
-        @Override
-        public boolean isConnected(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.isConnected();
-        }
-
-        @Override
-        public ComponentName getServiceComponent(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getServiceComponent();
-        }
-
-        @Override
-        public String getRoot(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getRoot();
-        }
-
-        @Override
-        public Bundle getExtras(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            return browser.getExtras();
-        }
-
-        @Override
-        public Bundle getConnectedSessionToken(String browserId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            Bundle tokenBundle = new Bundle();
-            tokenBundle.putParcelable(KEY_SESSION_COMPAT_TOKEN, browser.getSessionToken());
-            return tokenBundle;
-        }
-
-        @Override
-        public void subscribe(String browserId, String parentId, Bundle options)
-                throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.subscribe(parentId, options, new SubscriptionCallback() {});
-        }
-
-        @Override
-        public void unsubscribe(String browserId, String parentId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.unsubscribe(parentId);
-        }
-
-        @Override
-        public void getItem(String browserId, String mediaId) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.getItem(mediaId, new ItemCallback() {});
-        }
-
-        @Override
-        public void search(String browserId, String query, Bundle extras) throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.search(query, extras, new SearchCallback() {});
-        }
-
-        @Override
-        public void sendCustomAction(String browserId, String action, Bundle extras)
-                throws RemoteException {
-            MediaBrowserCompat browser = mMediaBrowserCompatMap.get(browserId);
-            browser.sendCustomAction(action, extras, null /* customActionCallback */);
-        }
-    }
-
-    private class TestBrowserConnectionCallback extends ConnectionCallback {
-        private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-        @Override
-        public void onConnected() {
-            mConnectionLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
deleted file mode 100644
index b3d38b9..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_ARGUMENTS;
-
-import android.app.Service;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaControllerCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaControllerCompat} and calls its methods
- * according to the service app's requests.
- */
-public class MediaControllerCompatProviderService extends Service {
-    private static final String TAG = "MediaControllerCompatProviderService";
-
-    Map<String, MediaControllerCompat> mMediaControllerCompatMap = new HashMap<>();
-    RemoteMediaControllerCompatStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaControllerCompatStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_CONTROLLER_COMPAT.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    private class RemoteMediaControllerCompatStub extends IRemoteMediaControllerCompat.Stub {
-
-        @Override
-        public void create(String controllerId, Bundle tokenBundle, boolean waitForConnection) {
-            MediaSessionCompat.Token token = (MediaSessionCompat.Token) getParcelable(tokenBundle);
-            MediaControllerCompat controller = new MediaControllerCompat(
-                    MediaControllerCompatProviderService.this, token);
-
-            final TestControllerCallback callback = new TestControllerCallback();
-            controller.registerCallback(callback, mHandler);
-
-            mMediaControllerCompatMap.put(controllerId, controller);
-
-            if (!waitForConnection) {
-                return;
-            }
-
-            boolean connected = false;
-            try {
-                connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-            }
-
-            if (!connected) {
-                Log.e(TAG, "Could not connect to the given session.");
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaControllerCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void addQueueItem(String controllerId, Bundle descriptionBundle)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.addQueueItem(desc);
-        }
-
-        @Override
-        public void addQueueItemWithIndex(String controllerId, Bundle descriptionBundle, int index)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.addQueueItem(desc, index);
-        }
-
-        @Override
-        public void removeQueueItem(String controllerId, Bundle descriptionBundle)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
-            controller.removeQueueItem(desc);
-        }
-
-        @Override
-        public void setVolumeTo(String controllerId, int value, int flags)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.setVolumeTo(value, flags);
-        }
-
-        @Override
-        public void adjustVolume(String controllerId, int direction, int flags)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.adjustVolume(direction, flags);
-        }
-
-        @Override
-        public void sendCommand(String controllerId, String command, Bundle params,
-                ResultReceiver cb) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.sendCommand(command, params, cb);
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaControllerCompat.TransportControls methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void prepare(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepare();
-        }
-
-        @Override
-        public void prepareFromMediaId(String controllerId, String mediaId, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromMediaId(mediaId, extras);
-        }
-
-        @Override
-        public void prepareFromSearch(String controllerId, String query, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromSearch(query, extras);
-        }
-
-        @Override
-        public void prepareFromUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().prepareFromUri(uri, extras);
-        }
-
-        @Override
-        public void play(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().play();
-        }
-
-        @Override
-        public void playFromMediaId(String controllerId, String mediaId, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromMediaId(mediaId, extras);
-        }
-
-        @Override
-        public void playFromSearch(String controllerId, String query, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromSearch(query, extras);
-        }
-
-        @Override
-        public void playFromUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().playFromUri(uri, extras);
-        }
-
-        @Override
-        public void skipToQueueItem(String controllerId, long id) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToQueueItem(id);
-        }
-
-        @Override
-        public void pause(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().pause();
-        }
-
-        @Override
-        public void stop(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().stop();
-        }
-
-        @Override
-        public void seekTo(String controllerId, long pos) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().seekTo(pos);
-        }
-
-        @Override
-        public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setPlaybackSpeed(speed);
-        }
-
-        @Override
-        public void fastForward(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().fastForward();
-        }
-
-        @Override
-        public void skipToNext(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToNext();
-        }
-
-        @Override
-        public void rewind(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().rewind();
-        }
-
-        @Override
-        public void skipToPrevious(String controllerId) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().skipToPrevious();
-        }
-
-        @Override
-        public void setRating(String controllerId, Bundle ratingBundle) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
-            controller.getTransportControls().setRating(rating);
-        }
-
-        @Override
-        public void setRatingWithExtras(String controllerId, Bundle ratingBundle, Bundle extras)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
-            controller.getTransportControls().setRating(rating, extras);
-        }
-
-        @Override
-        public void setCaptioningEnabled(String controllerId, boolean enabled)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setCaptioningEnabled(enabled);
-        }
-
-        @Override
-        public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void sendCustomAction(String controllerId, Bundle customActionBundle, Bundle args)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            PlaybackStateCompat.CustomAction customAction =
-                    (PlaybackStateCompat.CustomAction) getParcelable(customActionBundle);
-            controller.getTransportControls().sendCustomAction(customAction, args);
-        }
-
-        @Override
-        public void sendCustomActionWithName(String controllerId, String action, Bundle args)
-                throws RemoteException {
-            MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
-            controller.getTransportControls().sendCustomAction(action, args);
-        }
-
-        @SuppressWarnings("deprecation")
-        private Parcelable getParcelable(Bundle bundle) {
-            bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            return bundle.getParcelable(KEY_ARGUMENTS);
-        }
-    }
-
-    private class TestControllerCallback extends MediaControllerCompat.Callback {
-        private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-        @Override
-        public void onSessionReady() {
-            super.onSessionReady();
-            mConnectionLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java
deleted file mode 100644
index 3ce5bbf..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaControllerProviderService.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaController;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Service that creates {@link MediaController} and calls its methods
- * according to the service app's requests.
- */
-public class MediaControllerProviderService extends Service {
-    private static final String TAG = "MediaControllerProviderService";
-
-    Map<String, MediaController> mMediaControllerMap = new HashMap<>();
-    RemoteMediaControllerStub mBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBinder = new RemoteMediaControllerStub();
-
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA2_CONTROLLER.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaController controller : mMediaControllerMap.values()) {
-            controller.close();
-        }
-    }
-
-    private class RemoteMediaControllerStub extends IRemoteMediaController.Stub {
-        @Override
-        public void create(final boolean isBrowser, final String controllerId,
-                ParcelImpl tokenParcelable, final Bundle connectionHints, boolean waitForConnection)
-                throws RemoteException {
-            final SessionToken token = MediaParcelUtils.fromParcelable(tokenParcelable);
-            final TestControllerCallback callback = new TestControllerCallback();
-
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        Context context = MediaControllerProviderService.this;
-                        MediaController controller;
-                        if (isBrowser) {
-                            MediaBrowser.Builder builder = new MediaBrowser.Builder(context)
-                                    .setSessionToken(token)
-                                    .setControllerCallback(mExecutor, callback);
-                            if (connectionHints != null) {
-                                builder.setConnectionHints(connectionHints);
-                            }
-                            controller = builder.build();
-                        } else {
-                            MediaController.Builder builder = new MediaController.Builder(context)
-                                    .setSessionToken(token)
-                                    .setControllerCallback(mExecutor, callback);
-                            if (connectionHints != null) {
-                                builder.setConnectionHints(connectionHints);
-                            }
-                            controller = builder.build();
-
-                        }
-                        mMediaControllerMap.put(controllerId, controller);
-                    }
-                });
-            } catch (Exception ex) {
-                Log.e(TAG, "Exception occurred while creating MediaController.", ex);
-            }
-
-            if (!waitForConnection) {
-                return;
-            }
-
-            boolean connected = false;
-            try {
-                connected = callback.mConnectionLatch.await(3000, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while waiting for connection", ex);
-            }
-
-            if (!connected) {
-                Log.e(TAG, "Could not connect to the given session.");
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaController methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public ParcelImpl getConnectedSessionToken(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            return MediaParcelUtils.toParcelable(controller.getConnectedToken());
-        }
-
-        @Override
-        public void play(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.play();
-        }
-
-        @Override
-        public void pause(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.pause();
-        }
-
-        @Override
-        public void prepare(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.prepare();
-        }
-
-        @Override
-        public void seekTo(String controllerId, long pos) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.seekTo(pos);
-        }
-
-        @Override
-        public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setPlaybackSpeed(speed);
-        }
-
-        @Override
-        public void setPlaylist(String controllerId, List<String> list, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setPlaylist(list, (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void createAndSetFakePlaylist(String controllerId, int size, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            List<String> list = new ArrayList<>();
-            for (int i = 0; i < size; i++) {
-                // Make media ID of each item same with its index.
-                list.add(TestUtils.getMediaIdInFakeList(i));
-            }
-            controller.setPlaylist(list, (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void setMediaItem(String controllerId, String mediaId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setMediaItem(mediaId);
-        }
-
-        @Override
-        public void setMediaUri(String controllerId, Uri uri, Bundle extras)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setMediaUri(uri, extras);
-        }
-
-        @Override
-        public void updatePlaylistMetadata(String controllerId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.updatePlaylistMetadata(
-                    (MediaMetadata) MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void addPlaylistItem(String controllerId, int index, String mediaId)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.addPlaylistItem(index, mediaId);
-        }
-
-        @Override
-        public void removePlaylistItem(String controllerId, int index) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.removePlaylistItem(index);
-        }
-
-        @Override
-        public void replacePlaylistItem(String controllerId, int index, String mediaId)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.replacePlaylistItem(index, mediaId);
-        }
-
-        @Override
-        public void movePlaylistItem(String controllerId, int fromIdx, int toIdx)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.movePlaylistItem(fromIdx, toIdx);
-        }
-
-        @Override
-        public void skipToPreviousItem(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToPreviousPlaylistItem();
-        }
-
-        @Override
-        public void skipToNextItem(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToNextPlaylistItem();
-        }
-
-        @Override
-        public void skipToPlaylistItem(String controllerId, int index) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipToPlaylistItem(index);
-        }
-
-        @Override
-        public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setVolumeTo(value, flags);
-        }
-
-        @Override
-        public void adjustVolume(String controllerId, int direction, int flags)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.adjustVolume(direction, flags);
-        }
-
-        @Override
-        public void sendCustomCommand(String controllerId, ParcelImpl command, Bundle args)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.sendCustomCommand((SessionCommand) MediaParcelUtils.fromParcelable(command),
-                    args);
-        }
-
-        @Override
-        public void fastForward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.fastForward();
-        }
-
-        @Override
-        public void rewind(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.rewind();
-        }
-
-        @Override
-        public void skipForward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipForward();
-        }
-
-        @Override
-        public void skipBackward(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.skipBackward();
-        }
-
-        @Override
-        public void setRating(String controllerId, String mediaId, ParcelImpl rating)
-                throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.setRating(mediaId, ParcelUtils.<Rating>fromParcelable(rating));
-        }
-
-        @Override
-        public void close(String controllerId) throws RemoteException {
-            MediaController controller = mMediaControllerMap.get(controllerId);
-            controller.close();
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaBrowser methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void getLibraryRoot(String controllerId, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getLibraryRoot((LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void subscribe(String controllerId, String parentId, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.subscribe(parentId,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void unsubscribe(String controllerId, String parentId) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.unsubscribe(parentId);
-        }
-
-        @Override
-        public void getChildren(String controllerId, String parentId, int page, int pageSize,
-                ParcelImpl libraryParams) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getChildren(parentId, page, pageSize,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void getItem(String controllerId, String mediaId) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getItem(mediaId);
-        }
-
-        @Override
-        public void search(String controllerId, String query, ParcelImpl libraryParams)
-                throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.search(query, (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        @Override
-        public void getSearchResult(String controllerId, String query, int page, int pageSize,
-                ParcelImpl libraryParams) throws RemoteException {
-            MediaBrowser browser = (MediaBrowser) mMediaControllerMap.get(controllerId);
-            browser.getSearchResult(query, page, pageSize,
-                    (LibraryParams) MediaParcelUtils.fromParcelable(libraryParams));
-        }
-
-        private class TestControllerCallback extends MediaBrowser.BrowserCallback {
-            private CountDownLatch mConnectionLatch = new CountDownLatch(1);
-
-            @Override
-            public void onConnected(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup allowedCommands) {
-                super.onConnected(controller, allowedCommands);
-                mConnectionLatch.countDown();
-            }
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java
deleted file mode 100644
index d8a7721..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/MediaTestUtils.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.KEY_SERVICE_VERSION;
-import static androidx.media2.test.common.CommonConstants.VERSION_TOT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utilities for tests.
- */
-public final class MediaTestUtils {
-
-    // Temporaily commenting out, since we don't have the Mock services yet.
-//    /**
-//     * Finds the session with id in this test package.
-//     *
-//     * @param context
-//     * @param id
-//     * @return
-//     */
-//    public static SessionToken2 getServiceToken(Context context, String id) {
-//        switch (id) {
-//            case MockMediaSessionService.ID:
-//                return new SessionToken2(context, new ComponentName(
-//                        context.getPackageName(), MockMediaSessionService.class.getName()));
-//            case MockMediaLibraryService.ID:
-//                return new SessionToken2(context, new ComponentName(
-//                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-//        }
-//        fail("Unknown id=" + id);
-//        return null;
-//    }
-
-    /**
-     * Create a list of {@link FileMediaItem} for testing purpose.
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size list size
-     * @return the newly created playlist
-     */
-    public static List<MediaItem> createFileMediaItems(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                    .setMetadata(new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                    caller + "_item_" + (i + 1)).build())
-                    .build());
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem createFileMediaItemWithMetadata() {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(createMetadata())
-                .build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
-        return new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId).build();
-    }
-
-    public static List<SessionPlayer.TrackInfo> createTrackInfoList() {
-        List<SessionPlayer.TrackInfo> list = new ArrayList<>();
-        list.add(createTrackInfo(0, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO));
-        list.add(createTrackInfo(1, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO));
-        list.add(createTrackInfo(2, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
-        return list;
-    }
-
-    public static SessionPlayer.TrackInfo createTrackInfo(int index, int trackType) {
-        MediaFormat format = null;
-        if (trackType == SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-            format = new MediaFormat();
-            format.setString(MediaFormat.KEY_LANGUAGE, "eng");
-            format.setString(MediaFormat.KEY_MIME, "text/cea-608");
-            format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
-            format.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 0);
-            format.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
-        }
-        return new SessionPlayer.TrackInfo(index, trackType, format);
-    }
-
-    public static List<ParcelImpl> convertToParcelImplList(List<MediaItem> list) {
-        if (list == null) {
-            return null;
-        }
-        List<ParcelImpl> result = new ArrayList<>();
-        for (MediaItem item : list) {
-            result.add(MediaParcelUtils.toParcelable(item));
-        }
-        return result;
-    }
-
-    public static LibraryParams createLibraryParams() {
-        String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
-
-        Bundle extras = new Bundle();
-        extras.putString(callingTestName, callingTestName);
-        return new LibraryParams.Builder().setExtras(extras).build();
-    }
-
-    public static void assertLibraryParamsEquals(LibraryParams a, LibraryParams b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertEquals(a.isRecent(), b.isRecent());
-            assertEquals(a.isOffline(), b.isOffline());
-            assertEquals(a.isSuggested(), b.isSuggested());
-            assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
-        }
-    }
-
-    public static void assertLibraryParamsEquals(LibraryParams params, Bundle rootExtras) {
-        if (params == null || rootExtras == null) {
-            assertEquals(params, rootExtras);
-        } else {
-            assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
-            assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
-            assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
-            assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
-        }
-    }
-
-    public static void assertMediaItemHasId(MediaItem item, String expectedId) {
-        assertNotNull(item);
-        assertNotNull(item.getMetadata());
-        assertEquals(expectedId, item.getMetadata().getString(
-                MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    public static void assertPaginatedListHasIds(List<MediaItem> paginatedList,
-            List<String> fullIdList, int page, int pageSize) {
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, fullIdList.size());
-        // Compare the given results with originals.
-        for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-            int relativeIndex = originalIndex - fromIndex;
-            assertMediaItemHasId(paginatedList.get(relativeIndex), fullIdList.get(originalIndex));
-        }
-    }
-
-    public static void assertMediaIdEquals(MediaItem a, MediaItem b) {
-        assertEquals(a.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
-                b.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    public static void assertMediaIdEquals(List<MediaItem> a, List<MediaItem> b) {
-        assertEquals(a.size(), b.size());
-        for (int i = 0; i < a.size(); i++) {
-            assertMediaIdEquals(a.get(i), b.get(i));
-        }
-    }
-
-    public static void assertNotMediaItemSubclass(List<MediaItem> list) {
-        for (MediaItem item : list) {
-            assertNotMediaItemSubclass(item);
-        }
-    }
-
-    public static void assertNotMediaItemSubclass(MediaItem item) {
-        assertEquals(MediaItem.class, item.getClass());
-    }
-
-    public static boolean isServiceToT() {
-        String serviceVersion = InstrumentationRegistry.getArguments()
-                .getString(KEY_SERVICE_VERSION, "");
-        return VERSION_TOT.equals(serviceVersion);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
deleted file mode 100644
index e9aeac1..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
+++ /dev/null
@@ -1,666 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_SESSION;
-import static androidx.media2.test.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERED_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERING_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MAX_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MEDIA_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_SPEED;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
-import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
-import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
-import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
-import static androidx.media2.test.common.CommonConstants.MEDIA2_SESSION_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaSession} in the service app's MediaSessionProviderService.
- * Users can run {@link MediaSession} methods remotely with this object.
- */
-public class RemoteMediaSession {
-    private static final String TAG = "RemoteMediaSession";
-
-    private final Context mContext;
-    private final String mSessionId;
-    private final Bundle mTokenExtras;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaSession mBinder;
-    private RemoteMockPlayer mRemotePlayer;
-    private CountDownLatch mCountDownLatch;
-
-    /**
-     * Create a {@link MediaSession} in the service app.
-     * Should NOT be called in main thread.
-     */
-    public RemoteMediaSession(@NonNull String sessionId, @NonNull Context context,
-            @Nullable Bundle tokenExtras) {
-        mSessionId = sessionId;
-        mContext = context;
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        mTokenExtras = tokenExtras;
-
-        if (!connect()) {
-            fail("Failed to connect to the MediaSessionProviderService.");
-        }
-        create();
-    }
-
-    public void cleanUp() {
-        close();
-        disconnect();
-    }
-
-    /**
-     * Gets {@link RemoteMockPlayer} for interact with the remote MockPlayer.
-     * Users can run MockPlayer methods remotely with this object.
-     */
-    public RemoteMockPlayer getMockPlayer() {
-        return mRemotePlayer;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaSession methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Gets {@link SessionToken} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link SessionToken} object if succeeded, {@code null} if failed.
-     */
-    public SessionToken getToken() {
-        SessionToken token = null;
-        try {
-            token = MediaParcelUtils.fromParcelable(mBinder.getToken(mSessionId));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionId=" + mSessionId);
-        }
-        return token;
-    }
-
-    /**
-     * Gets {@link MediaSessionCompat.Token} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link SessionToken} object if succeeded, {@code null} if failed.
-     */
-    public MediaSessionCompat.Token getCompatToken() {
-        MediaSessionCompat.Token token = null;
-        try {
-            Bundle bundle = mBinder.getCompatToken(mSessionId);
-            if (bundle != null) {
-                bundle.setClassLoader(MediaSession.class.getClassLoader());
-            }
-            token = MediaSessionCompat.Token.fromBundle(bundle);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session compat token. sessionId=" + mSessionId);
-        }
-        return token;
-    }
-
-    public void updatePlayer(@NonNull Bundle config) {
-        try {
-            mBinder.updatePlayer(mSessionId, config);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call updatePlayerConnector()");
-        }
-    }
-
-    public void broadcastCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.broadcastCustomCommand(mSessionId,
-                    MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call broadcastCustomCommand()");
-        }
-    }
-
-    public void sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.sendCustomCommand(
-                    mSessionId, null, MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomCommand2()");
-        }
-    }
-
-    public void close() {
-        try {
-            mBinder.close(mSessionId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call close()");
-        }
-    }
-
-    public void setAllowedCommands(@NonNull SessionCommandGroup commands) {
-        try {
-            mBinder.setAllowedCommands(mSessionId, null,
-                    MediaParcelUtils.toParcelable(commands));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setAllowedCommands()");
-        }
-    }
-
-    public void setCustomLayout(@NonNull List<CommandButton> layout) {
-        try {
-            List<ParcelImpl> parcelList = new ArrayList<>();
-            for (CommandButton btn : layout) {
-                parcelList.add(MediaParcelUtils.toParcelable(btn));
-            }
-            mBinder.setCustomLayout(mSessionId, null, parcelList);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setCustomLayout()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // RemoteMockPlayer methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public class RemoteMockPlayer {
-
-        public void setPlayerState(int state) {
-            try {
-                mBinder.setPlayerState(mSessionId, state);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentPosition()");
-            }
-        }
-
-        public void setCurrentPosition(long pos) {
-            try {
-                mBinder.setCurrentPosition(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentPosition()");
-            }
-        }
-
-        public void setBufferedPosition(long pos) {
-            try {
-                mBinder.setBufferedPosition(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setBufferedPosition()");
-            }
-        }
-
-        public void setDuration(long duration) {
-            try {
-                mBinder.setDuration(mSessionId, duration);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setDuration()");
-            }
-        }
-
-        public void setPlaybackSpeed(float speed) {
-            try {
-                mBinder.setPlaybackSpeed(mSessionId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaybackSpeed()");
-            }
-        }
-
-        public void notifySeekCompleted(long pos) {
-            try {
-                mBinder.notifySeekCompleted(mSessionId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifySeekCompleted()");
-            }
-        }
-
-        public void notifyBufferingStateChanged(int itemIndex, int buffState) {
-            try {
-                mBinder.notifyBufferingStateChanged(mSessionId, itemIndex, buffState);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyBufferingStateChanged()");
-            }
-        }
-
-        public void notifyPlayerStateChanged(int state) {
-            try {
-                mBinder.notifyPlayerStateChanged(mSessionId, state);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlayerStateChanged()");
-            }
-        }
-
-        public void notifyPlaybackSpeedChanged(float speed) {
-            try {
-                mBinder.notifyPlaybackSpeedChanged(mSessionId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaybackSpeedChanged()");
-            }
-        }
-
-        public void notifyCurrentMediaItemChanged(int index) {
-            try {
-                mBinder.notifyCurrentMediaItemChanged(mSessionId, index);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyCurrentMediaItemChanged()");
-            }
-        }
-
-        public void notifyAudioAttributesChanged(AudioAttributesCompat attrs) {
-            try {
-                mBinder.notifyAudioAttributesChanged(
-                        mSessionId, MediaParcelUtils.toParcelable(attrs));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyAudioAttributesChanged()");
-            }
-        }
-
-        public void setPlaylist(List<MediaItem> playlist) {
-            try {
-                mBinder.setPlaylist(
-                        mSessionId, MediaTestUtils.convertToParcelImplList(playlist));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylist()");
-            }
-        }
-
-        public void setCurrentMediaItemMetadata(MediaMetadata metadata) {
-            try {
-                mBinder.setCurrentMediaItemMetadata(
-                        mSessionId, MediaParcelUtils.toParcelable(metadata));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentMediaItemMetadata()");
-            }
-        }
-
-        /**
-         * Service app will automatically create a playlist of size {@param size},
-         * and sets the list to the player.
-         *
-         * Each item's media ID will be {@link TestUtils#getMediaIdInFakeList(int)}.
-         */
-        public void createAndSetFakePlaylist(int size) {
-            try {
-                mBinder.createAndSetFakePlaylist(mSessionId, size);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call createAndSetFakePlaylist()");
-            }
-        }
-
-        public void setPlaylistWithFakeItem(List<MediaItem> playlist) {
-            try {
-                mBinder.setPlaylistWithFakeItem(
-                        mSessionId, MediaTestUtils.convertToParcelImplList(playlist));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistWithFakeItem()");
-            }
-        }
-
-        public void setPlaylistMetadata(MediaMetadata metadata) {
-            try {
-                mBinder.setPlaylistMetadata(mSessionId, MediaParcelUtils.toParcelable(metadata));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistMetadata()");
-            }
-        }
-
-        public void setPlaylistMetadataWithLargeBitmaps(int count, int width, int height) {
-            try {
-                mBinder.setPlaylistMetadataWithLargeBitmaps(mSessionId, count, width, height);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaylistMetadataWithLargeBitmaps()");
-            }
-        }
-
-        public void setRepeatMode(int repeatMode) {
-            try {
-                mBinder.setRepeatMode(mSessionId, repeatMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRepeatMode()");
-            }
-        }
-
-        public void setShuffleMode(int shuffleMode) {
-            try {
-                mBinder.setShuffleMode(mSessionId, shuffleMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setShuffleMode()");
-            }
-        }
-
-        public void setCurrentMediaItem(int index) {
-            try {
-                mBinder.setCurrentMediaItem(mSessionId, index);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCurrentMediaItem()");
-            }
-        }
-
-        public void notifyPlaylistChanged() {
-            try {
-                mBinder.notifyPlaylistChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaylistChanged()");
-            }
-        }
-
-        public void notifyPlaylistMetadataChanged() {
-            try {
-                mBinder.notifyPlaylistMetadataChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaylistMetadataChanged()");
-            }
-        }
-
-        public void notifyShuffleModeChanged() {
-            try {
-                mBinder.notifyShuffleModeChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyShuffleModeChanged()");
-            }
-        }
-
-        public void notifyRepeatModeChanged() {
-            try {
-                mBinder.notifyRepeatModeChanged(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyRepeatModeChanged()");
-            }
-        }
-
-        public void notifyPlaybackCompleted() {
-            try {
-                mBinder.notifyPlaybackCompleted(mSessionId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyPlaybackCompleted()");
-            }
-        }
-
-        public void notifyVideoSizeChanged(@NonNull VideoSize videoSize) {
-            try {
-                mBinder.notifyVideoSizeChanged(mSessionId,
-                        MediaParcelUtils.toParcelable(videoSize));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyVideoSizeChanged()");
-            }
-        }
-
-        public boolean surfaceExists() throws RemoteException {
-            return mBinder.surfaceExists(mSessionId);
-        }
-
-        public void notifyTracksChanged(List<SessionPlayer.TrackInfo> tracks) {
-            try {
-                mBinder.notifyTrackInfoChanged(mSessionId,
-                        MediaParcelUtils.toParcelableList(tracks));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackInfoChanged()");
-            }
-        }
-
-        public void notifyTrackSelected(SessionPlayer.TrackInfo trackInfo) {
-            try {
-                mBinder.notifyTrackSelected(mSessionId,
-                        MediaParcelUtils.toParcelable(trackInfo));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackSelected()");
-            }
-        }
-
-        public void notifyTrackDeselected(SessionPlayer.TrackInfo trackInfo) {
-            try {
-                mBinder.notifyTrackDeselected(mSessionId,
-                        MediaParcelUtils.toParcelable(trackInfo));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifyTrackDeselected()");
-            }
-        }
-
-        public void notifySubtitleData(@NonNull MediaItem item,
-                @NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
-            try {
-                mBinder.notifySubtitleData(mSessionId,
-                        MediaParcelUtils.toParcelable(item),
-                        MediaParcelUtils.toParcelable(track),
-                        MediaParcelUtils.toParcelable(data));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call notifySubtitleData");
-            }
-        }
-
-        public void notifyVolumeChanged(int volume) throws RemoteException {
-            mBinder.notifyVolumeChanged(mSessionId, volume);
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to service app's MediaSessionProviderService.
-     * Should NOT be called in main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA2_SESSION);
-        intent.setComponent(MEDIA2_SESSION_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed binding to the MediaSessionProviderService of the service app");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from service app's MediaSessionProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-        }
-        mServiceConnection = null;
-    }
-
-    /**
-     * Create a {@link MediaSession} in the service app.
-     * Should be used after successful connection through {@link #connect}.
-     */
-    private void create() {
-        try {
-            mBinder.create(mSessionId, mTokenExtras);
-            mRemotePlayer = new RemoteMockPlayer();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionId=" + mSessionId);
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to service app's MediaSessionProviderService.");
-            mBinder = IRemoteMediaSession.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from the service.");
-        }
-    }
-
-    /**
-     * Builder to build a {@link Bundle} which represents a configuration of {@link SessionPlayer}
-     * in order to create a new mock player in the service app. The bundle can be passed to
-     * {@link #updatePlayer(Bundle)}.
-     */
-    public static final class MockPlayerConfigBuilder {
-
-        private final Bundle mBundle;
-
-        public MockPlayerConfigBuilder() {
-            mBundle = new Bundle();
-        }
-
-        public MockPlayerConfigBuilder setPlayerState(@SessionPlayer.PlayerState int state) {
-            mBundle.putInt(KEY_PLAYER_STATE, state);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setBufferingState(@SessionPlayer.BuffState int buffState) {
-            mBundle.putInt(KEY_BUFFERING_STATE, buffState);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentPosition(long pos) {
-            mBundle.putLong(KEY_CURRENT_POSITION, pos);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setBufferedPosition(long buffPos) {
-            mBundle.putLong(KEY_BUFFERED_POSITION, buffPos);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaybackSpeed(float speed) {
-            mBundle.putFloat(KEY_PLAYBACK_SPEED, speed);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setAudioAttributes(
-                @Nullable AudioAttributesCompat audioAttributes) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(audioAttributes);
-            mBundle.putParcelable(KEY_AUDIO_ATTRIBUTES, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaylist(@NonNull List<MediaItem> playlist) {
-            ParcelImplListSlice listSlice = new ParcelImplListSlice(
-                    MediaTestUtils.convertToParcelImplList(playlist));
-            mBundle.putParcelable(KEY_PLAYLIST, listSlice);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setPlaylistMetadata(@Nullable MediaMetadata metadata) {
-            ParcelUtils.putVersionedParcelable(mBundle, KEY_PLAYLIST_METADATA, metadata);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentMediaItem(@Nullable MediaItem item) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(item);
-            mBundle.putParcelable(KEY_MEDIA_ITEM, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setVideoSize(@NonNull VideoSize videoSize) {
-            Parcelable parcelable = MediaParcelUtils.toParcelable(videoSize);
-            mBundle.putParcelable(KEY_VIDEO_SIZE, parcelable);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setTrackInfo(@NonNull List<SessionPlayer.TrackInfo> tracks) {
-            ParcelUtils.putVersionedParcelableList(mBundle, KEY_TRACK_INFO, tracks);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setVolumeControlType(
-                @RemoteSessionPlayer.VolumeControlType int volumeControlType) {
-            mBundle.putInt(KEY_VOLUME_CONTROL_TYPE, volumeControlType);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setMaxVolume(int maxVolume) {
-            mBundle.putInt(KEY_MAX_VOLUME, maxVolume);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setCurrentVolume(int currentVolume) {
-            mBundle.putInt(KEY_CURRENT_VOLUME, currentVolume);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setShuffleMode(int shuffleMode) {
-            mBundle.putInt(KEY_SHUFFLE_MODE, shuffleMode);
-            return this;
-        }
-
-        public MockPlayerConfigBuilder setRepeatMode(int repeatMode) {
-            mBundle.putInt(KEY_REPEAT_MODE, repeatMode);
-            return this;
-        }
-
-        public Bundle build() {
-            return mBundle;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
deleted file mode 100644
index 9f6b7ce..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_METADATA_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_QUEUE;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-import static androidx.media2.test.common.CommonConstants.MEDIA_SESSION_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.test.common.IRemoteMediaSessionCompat;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaSessionCompat} in the service app's
- * MediaSessionCompatProviderService.
- * Users can run {@link MediaSessionCompat} methods remotely with this object.
- */
-public class RemoteMediaSessionCompat {
-    private static final String TAG = "RemoteMediaSessionCompat";
-
-    private final Context mContext;
-    private final String mSessionTag;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaSessionCompat mBinder;
-    private final CountDownLatch mCountDownLatch;
-
-    /**
-     * Create a {@link MediaSessionCompat} in the service app.
-     * Should NOT be called in main thread.
-     */
-    public RemoteMediaSessionCompat(@NonNull String sessionTag, Context context) {
-        mSessionTag = sessionTag;
-        mContext = context;
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-
-        if (!connect()) {
-            fail("Failed to connect to the MediaSessionCompatProviderService.");
-        }
-        create();
-    }
-
-    public void cleanUp() {
-        release();
-        disconnect();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaSessionCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Gets {@link MediaSessionCompat.Token} from the service app.
-     * Should be used after the creation of the session through {@link #create()}.
-     *
-     * @return A {@link MediaSessionCompat.Token} object if succeeded, {@code null} if failed.
-     */
-    @SuppressWarnings("deprecation")
-    public MediaSessionCompat.Token getSessionToken() {
-        MediaSessionCompat.Token token = null;
-        try {
-            Bundle bundle = mBinder.getSessionToken(mSessionTag);
-            if (bundle != null) {
-                bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-                token = bundle.getParcelable(KEY_SESSION_COMPAT_TOKEN);
-            }
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionTag=" + mSessionTag);
-        }
-        return token;
-    }
-
-    public void release() {
-        try {
-            mBinder.release(mSessionTag);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call release()");
-        }
-    }
-
-    public void setPlaybackToLocal(int stream) {
-        try {
-            mBinder.setPlaybackToLocal(mSessionTag, stream);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackToLocal()");
-        }
-    }
-
-    /**
-     * Since we cannot pass VolumeProviderCompat directly,
-     * we pass volumeControl, maxVolume, currentVolume instead.
-     */
-    public void setPlaybackToRemote(int volumeControl, int maxVolume, int currentVolume) {
-        try {
-            mBinder.setPlaybackToRemote(mSessionTag, volumeControl, maxVolume, currentVolume);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackToRemote()");
-        }
-    }
-
-    public void setPlaybackState(PlaybackStateCompat state) {
-        try {
-            mBinder.setPlaybackState(mSessionTag,
-                    createBundleWithParcelable(KEY_PLAYBACK_STATE_COMPAT, state));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackState()");
-        }
-    }
-
-    public void setMetadata(MediaMetadataCompat metadata) {
-        try {
-            mBinder.setMetadata(mSessionTag,
-                    createBundleWithParcelable(KEY_METADATA_COMPAT, metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMetadata()");
-        }
-    }
-
-    public void setQueue(List<QueueItem> queue) {
-        try {
-            Bundle bundle = new Bundle();
-            ArrayList<QueueItem> queueAsArrayList = new ArrayList<>(queue);
-            bundle.putParcelableArrayList(KEY_QUEUE, queueAsArrayList);
-            mBinder.setQueue(mSessionTag, bundle);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setQueue()");
-        }
-    }
-
-    public void setQueueTitle(CharSequence title) {
-        try {
-            mBinder.setQueueTitle(mSessionTag, title);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setQueueTitle()");
-        }
-    }
-
-    public void setRepeatMode(int repeatMode) {
-        try {
-            mBinder.setRepeatMode(mSessionTag, repeatMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRepeatMode()");
-        }
-    }
-
-    public void setShuffleMode(int shuffleMode) {
-        try {
-            mBinder.setShuffleMode(mSessionTag, shuffleMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setShuffleMode()");
-        }
-    }
-
-    public void setSessionActivity(PendingIntent intent) {
-        try {
-            mBinder.setSessionActivity(mSessionTag, intent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setSessionActivity()");
-        }
-    }
-
-    public void setFlags(int flags) {
-        try {
-            mBinder.setFlags(mSessionTag, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setFlags()");
-        }
-    }
-
-    public void setRatingType(int type) {
-        try {
-            mBinder.setRatingType(mSessionTag, type);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRatingType()");
-        }
-    }
-
-    public void sendSessionEvent(String event, Bundle extras) {
-        try {
-            mBinder.sendSessionEvent(mSessionTag, event, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendSessionEvent()");
-        }
-    }
-
-    public void setCaptioningEnabled(boolean enabled) {
-        try {
-            mBinder.setCaptioningEnabled(mSessionTag, enabled);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setCaptioningEnabled()");
-        }
-    }
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to service app's MediaSessionCompatProviderService.
-     * Should NOT be called in main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA_SESSION_COMPAT);
-        intent.setComponent(MEDIA_SESSION_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed binding to the MediaSessionCompatProviderService of the "
-                    + "service app");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from service app's MediaSessionCompatProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-        }
-        mServiceConnection = null;
-    }
-
-    /**
-     * Create a {@link MediaSessionCompat} in the service app.
-     * Should be used after successful connection through {@link #connect}.
-     */
-    private void create() {
-        try {
-            mBinder.create(mSessionTag);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to get session token. sessionTag=" + mSessionTag);
-        }
-    }
-
-    private Bundle createBundleWithParcelable(String key, Parcelable value) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(key, value);
-        return bundle;
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to service app's MediaSessionCompatProviderService.");
-            mBinder = IRemoteMediaSessionCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from the service.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
deleted file mode 100644
index d2e6d81..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
+++ /dev/null
@@ -1,41 +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.media2.test.client;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.media2.test.client.test.R;
-
-public class SurfaceActivity extends Activity {
-    private SurfaceHolder mHolder;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_surface);
-
-        SurfaceView surfaceView = findViewById(R.id.surface_view);
-        mHolder = surfaceView.getHolder();
-    }
-
-    public SurfaceHolder getSurfaceHolder() {
-        return mHolder;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
deleted file mode 100644
index bd51557..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.LibraryResult;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.common.MediaBrowserConstants;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaBrowser.BrowserCallback}.
- * <p>
- * This test inherits {@link MediaControllerCallbackTest} to ensure that inherited APIs from
- * {@link MediaController} works cleanly.
- */
-// TODO: (internal cleanup) Move tests that aren't related with callbacks.
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaBrowserCallbackTest extends MediaControllerCallbackTest {
-    private static final String TAG = "MediaBrowserCallbackTest";
-
-    @Override
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints, @Nullable final TestBrowserCallback callback)
-            throws InterruptedException {
-        assertNotNull("Test bug", token);
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaBrowser.Builder builder = new MediaBrowser.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-
-    final MediaBrowser createBrowser() throws InterruptedException {
-        return createBrowser(null, null);
-    }
-
-    final MediaBrowser createBrowser(@Nullable Bundle connectionHints,
-            @Nullable BrowserCallback callback) throws InterruptedException {
-        final SessionToken token = new SessionToken(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-        return (MediaBrowser) createController(token, true, connectionHints, callback);
-    }
-
-    @Test
-    public void getLibraryRoot() throws Exception {
-        final LibraryParams params = new LibraryParams.Builder()
-                .setOffline(true).setRecent(true).setExtras(new Bundle()).build();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-        LibraryResult result = browser.getLibraryRoot(params)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaMetadata metadata = result.getMediaItem().getMetadata();
-        assertEquals(ROOT_ID, metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-        assertTrue(TestUtils.equals(ROOT_EXTRAS, result.getLibraryParams().getExtras()));
-    }
-
-    @Test
-    public void getItem() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-
-        LibraryResult result = createBrowser().getItem(mediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaTestUtils.assertMediaItemHasId(result.getMediaItem(), mediaId);
-    }
-
-    @Test
-    public void getItem_unknownId() throws Exception {
-        final String mediaId = "random_media_id";
-
-        LibraryResult result = createBrowser().getItem(mediaId)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_ERROR_BAD_VALUE, result.getResultCode());
-        assertNull(result.getMediaItem());
-    }
-
-    @Test
-    public void getItem_nullResult() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
-
-        try {
-            LibraryResult result = createBrowser().getItem(mediaId)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        } catch (TimeoutException e) {
-            // May happen.
-        }
-    }
-
-    @Test
-    public void getItem_invalidResult() throws Exception {
-        final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
-
-        try {
-            LibraryResult result = createBrowser().getItem(mediaId)
-                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        } catch (TimeoutException e) {
-            // May happen.
-        }
-    }
-
-    @Test
-    public void getChildren() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID;
-        final int page = 4;
-        final int pageSize = 10;
-        final LibraryParams params = MediaTestUtils.createLibraryParams();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-
-        LibraryResult result = browser.getChildren(parentId, page, pageSize, params)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getLibraryParams());
-
-        MediaTestUtils.assertPaginatedListHasIds(
-                result.getMediaItems(), MediaBrowserConstants.GET_CHILDREN_RESULT,
-                page, pageSize);
-    }
-
-    @Test
-    @LargeTest
-    public void getChildren_withLongList() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final LibraryParams params = MediaTestUtils.createLibraryParams();
-
-        MediaBrowser browser = createBrowser();
-        setExpectedLibraryParam(browser, params);
-
-        LibraryResult result = browser.getChildren(parentId, page, pageSize, params)
-                .get(10, TimeUnit.SECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getLibraryParams());
-
-        List<MediaItem> list = result.getMediaItems();
-        assertEquals(LONG_LIST_COUNT, list.size());
-        for (int i = 0; i < result.getMediaItems().size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), list.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void getChildren_emptyResult() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
-
-        MediaBrowser browser = createBrowser();
-        LibraryResult result = browser.getChildren(parentId, 1, 1, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        assertEquals(0, result.getMediaItems().size());
-    }
-
-    @Test
-    public void getChildren_nullResult() throws Exception {
-        final String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
-
-        MediaBrowser browser = createBrowser();
-        LibraryResult result = browser.getChildren(parentId, 1, 1, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotEquals(RESULT_SUCCESS, result.getResultCode());
-        assertNull(result.getMediaItems());
-    }
-
-    @Test
-    public void searchCallbacks() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY;
-        final int page = 4;
-        final int pageSize = 10;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latchForSearch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(MediaBrowser browser,
-                    String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.SEARCH_RESULT_COUNT, itemCount);
-                latchForSearch.countDown();
-            }
-        };
-
-        // Request the search.
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // Get the search result.
-        result = browser.getSearchResult(query, page, pageSize, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        MediaTestUtils.assertPaginatedListHasIds(result.getMediaItems(),
-                MediaBrowserConstants.SEARCH_RESULT, page, pageSize);
-    }
-
-    @Test
-    @LargeTest
-    public void searchCallbacks_withLongList() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.LONG_LIST_COUNT, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        result = browser.getSearchResult(query, page, pageSize, testParams)
-                .get(10, TimeUnit.SECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-        List<MediaItem> list = result.getMediaItems();
-        for (int i = 0; i < list.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), list.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void onSearchResultChanged_searchTakesTime() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(MediaBrowserConstants.SEARCH_RESULT_COUNT, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(MediaBrowserConstants.SEARCH_TIME_IN_MS + TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void onSearchResultChanged_emptyResult() throws Exception {
-        final String query = MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
-                assertEquals(query, queryOut);
-                MediaTestUtils.assertLibraryParamsEquals(testParams, params);
-                assertEquals(0, itemCount);
-                latch.countDown();
-            }
-        };
-
-        MediaBrowser browser = createBrowser(null, callback);
-        setExpectedLibraryParam(browser, testParams);
-        LibraryResult result = browser.search(query, testParams)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void onChildrenChanged_calledWhenSubscribed() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged().
-        final String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                assertEquals(expectedParentId, parentId);
-                assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
-                MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(expectedParentId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged() in its callback onSubscribe().
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
-        final String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                assertEquals(expectedParentId, parentId);
-                assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
-                MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(expectedParentId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe().
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged().
-        final String subscribedMediaId =
-                SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                // Unexpected call.
-                fail();
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(subscribedMediaId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged() in its callback onSubscribe(), but with a different media ID.
-        // Therefore, onChildrenChanged() should not be called.
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onChildrenChanged_notCalledWhenNotSubscribed2() throws Exception {
-        // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
-        final String subscribedMediaId =
-                SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
-            @Override
-            public void onChildrenChanged(MediaBrowser browser, String parentId,
-                    int itemCount, LibraryParams params) {
-                // Unexpected call.
-                fail();
-                latch.countDown();
-            }
-        };
-
-        LibraryResult result = createBrowser(null, controllerCallbackProxy)
-                .subscribe(subscribedMediaId, null)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-
-        // The MediaLibrarySession in MockMediaLibraryService is supposed to call
-        // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe(),
-        // but with a different media ID.
-        // Therefore, onChildrenChanged() should not be called.
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void setExpectedLibraryParam(MediaBrowser browser, LibraryParams params)
-            throws Exception {
-        SessionCommand command = new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null);
-        Bundle args = new Bundle();
-        ParcelUtils.putVersionedParcelable(args, CUSTOM_ACTION_ASSERT_PARAMS, params);
-        SessionResult result = browser.sendCustomCommand(command, args)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java
deleted file mode 100644
index 7e8518b..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaLibraryServiceTest.java
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaBrowserConstants.CHILDREN_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.CustomActionCallback;
-import android.support.v4.media.MediaBrowserCompat.ItemCallback;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaBrowserCompat.SearchCallback;
-import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaBrowserCompat} works well with {@link MediaLibraryService}.
- */
-@LargeTest
-public class MediaBrowserCompatWithMediaLibraryServiceTest extends
-        MediaBrowserCompatWithMediaSessionServiceTest {
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Override
-    ComponentName getServiceComponent() {
-        return MOCK_MEDIA2_LIBRARY_SERVICE;
-    }
-
-    @Test
-    public void getRoot() throws InterruptedException {
-        // The MockMediaLibraryService gives MediaBrowserConstants.ROOT_ID as root ID, and
-        // MediaBrowserConstants.ROOT_EXTRAS as extras.
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mBrowserCompat = new MediaBrowserCompat(mContext, getServiceComponent(),
-                        mConnectionCallback, null /* rootHint */);
-            }
-        });
-        connectAndWait();
-        assertEquals(ROOT_ID, mBrowserCompat.getRoot());
-
-        // Note: Cannot use equals() here because browser compat's extra contains server version,
-        // extra binder, and extra messenger.
-        assertTrue(TestUtils.contains(mBrowserCompat.getExtras(), ROOT_EXTRAS));
-    }
-
-    @Test
-    public void getItem() throws InterruptedException {
-        final String mediaId = MEDIA_ID_GET_ITEM;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.getItem(mediaId, new ItemCallback() {
-            @Override
-            public void onItemLoaded(MediaItem item) {
-                assertEquals(mediaId, item.getMediaId());
-                assertNotNull(item);
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getItem_nullResult() throws InterruptedException {
-        final String mediaId = "random_media_id";
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.getItem(mediaId, new ItemCallback() {
-            @Override
-            public void onItemLoaded(MediaItem item) {
-                assertNull(item);
-                latch.countDown();
-            }
-
-            @Override
-            public void onError(@NonNull String itemId) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren() throws InterruptedException {
-        final String testParentId = PARENT_ID;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertEquals(testParentId, parentId);
-                assertNotNull(children);
-                assertEquals(GET_CHILDREN_RESULT.size(), children.size());
-
-                // Compare the given results with originals.
-                for (int i = 0; i < children.size(); i++) {
-                    assertEquals(GET_CHILDREN_RESULT.get(i), children.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle option) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_withLongList() throws InterruptedException {
-        final String testParentId = PARENT_ID_LONG_LIST;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertEquals(testParentId, parentId);
-                assertNotNull(children);
-                assertTrue(children.size() < LONG_LIST_COUNT);
-
-                // Compare the given results with originals.
-                for (int i = 0; i < children.size(); i++) {
-                    assertEquals(TestUtils.getMediaIdInFakeList(i), children.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle option) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void getChildren_withPagination() throws InterruptedException {
-        final String testParentId = PARENT_ID;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle extras = new Bundle();
-        extras.putString(testParentId, testParentId);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final Bundle option = new Bundle();
-        option.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        option.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-        mBrowserCompat.subscribe(testParentId, option, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(page, option.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(pageSize, option.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertNotNull(children);
-
-                int fromIndex = page * pageSize;
-                int toIndex = Math.min((page + 1) * pageSize, CHILDREN_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    assertEquals(GET_CHILDREN_RESULT.get(originalIndex),
-                            children.get(relativeIndex).getMediaId());
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_emptyResult() throws InterruptedException {
-        final String testParentId = PARENT_ID_NO_CHILDREN;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children) {
-                assertNotNull(children);
-                assertEquals(0, children.size());
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getChildren_nullResult() throws InterruptedException {
-        final String testParentId = PARENT_ID_ERROR;
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-            @Override
-            public void onError(@NonNull String parentId) {
-                assertEquals(testParentId, parentId);
-                latch.countDown();
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId,
-                    @NonNull List<MediaItem> children, @NonNull Bundle options) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                int expectedSize = Math.max(
-                        Math.min(pageSize, SEARCH_RESULT_COUNT - pageSize * page),
-                        0);
-                assertEquals(expectedSize, items.size());
-
-                int fromIndex = page * pageSize;
-                int toIndex = Math.min((page + 1) * pageSize, SEARCH_RESULT_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    assertEquals(
-                            SEARCH_RESULT.get(originalIndex),
-                            items.get(relativeIndex).getMediaId());
-                }
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search_withLongList() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_LONG_LIST;
-        final int page = 0;
-        final int pageSize = Integer.MAX_VALUE;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-
-                assertNotNull(items);
-                assertTrue(items.size() < LONG_LIST_COUNT);
-                for (int i = 0; i < items.size(); i++) {
-                    assertEquals(TestUtils.getMediaIdInFakeList(i), items.get(i).getMediaId());
-                }
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void search_emptyResult() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_EMPTY_RESULT;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                assertNotNull(items);
-                assertEquals(0, items.size());
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void search_error() throws InterruptedException {
-        final String testQuery = SEARCH_QUERY_ERROR;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testQuery, testQuery);
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.search(testQuery, testExtras, new SearchCallback() {
-            @Override
-            public void onError(@NonNull String query, Bundle extras) {
-                assertEquals(testQuery, query);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                latch.countDown();
-            }
-
-            @Override
-            public void onSearchResult(@NonNull String query, Bundle extras,
-                    @NonNull List<MediaItem> items) {
-                fail();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe() throws InterruptedException {
-//        final String testParentId = "testSubscribeId";
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(0, page);
-//                assertEquals(Integer.MAX_VALUE, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                assertMediaItemListEquals(testList, children);
-//                latch.countDown();
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe_withExtras() throws InterruptedException {
-//        final String testParentId = "testSubscribe_withExtras";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(0, page);
-//                assertEquals(Integer.MAX_VALUE, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                assertMediaItemListEquals(testList, children);
-//                assertTrue(TestUtils.equals(testExtras, options));
-//                latch.countDown();
-//                super.onChildrenLoaded(parentId, children, options);
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribe_withPagination() throws InterruptedException {
-//        final String testParentId = "testSubscribe_pagination_ID";
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//        final int testPage = 2;
-//        final int testPageSize = 3;
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, testPage);
-//        testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, testPageSize);
-//
-//        final CountDownLatch latch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller,
-//                    String parentId, int page, int pageSize, Bundle extras) {
-//                assertEquals(testParentId, parentId);
-//                assertEquals(testPage, page);
-//                assertEquals(testPageSize, pageSize);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                fail();
-//            }
-//
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                assertEquals(testParentId, parentId);
-//                assertMediaItemListEquals(testList, children);
-//                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-//                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-//                latch.countDown();
-//            }
-//
-//            @Override
-//            public void onError(String parentId) {
-//                fail();
-//            }
-//
-//            @Override
-//            public void onError(String parentId, Bundle options) {
-//                fail();
-//            }
-//        });
-//        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Move this test to MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void subscribeAndUnsubscribe() throws InterruptedException {
-//        final String testParentId = "testUnsubscribe";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testParentId, testParentId);
-//
-//        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-//        final CountDownLatch unsubscribeLatch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    assertTrue(TestUtils.equals(testExtras, extras));
-//                    subscribeLatch.countDown();
-//                }
-//            }
-//
-//            @Override
-//            public void onUnsubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId) {
-//                if (Process.myUid() == info.getUid()) {
-//                    assertEquals(testParentId, parentId);
-//                    unsubscribeLatch.countDown();
-//                }
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        mBrowserCompat.subscribe(testParentId, testExtras, new SubscriptionCallback() {});
-//        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-//        // Subscription is needed for MediaBrowserCompat to send unsubscribe request.
-//        mBrowserCompat.unsubscribe(testParentId);
-//        assertTrue(unsubscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore("TODO: Split this test to here and MediaLibrarySessionLegacyCallbackTest.")
-    @Test
-    public void notifyChildrenChanged() throws InterruptedException {
-//        final String testSubscribedParentId = "testNotifyChildrenChanged";
-//        final String testUnsubscribedParentId = "testNotifyChildrenChanged22";
-//        final Bundle testExtras = new Bundle();
-//        testExtras.putString(testSubscribedParentId, testSubscribedParentId);
-//        final List<MediaItem> testList = TestUtils.createMediaItems(3);
-//
-//        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-//        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-//            @Override
-//            public void onSubscribe(@NonNull MediaLibrarySession session,
-//                    @NonNull ControllerInfo info, @NonNull String parentId,
-//                    @Nullable Bundle extras) {
-//                if (Process.myUid() == info.getUid()) {
-//                    subscribeLatch.countDown();
-//                }
-//            }
-//
-//            @Override
-//            public List<MediaItem> onGetChildren(MediaLibrarySession session,
-//                    ControllerInfo controller, String parentId, int page, int pageSize,
-//                    Bundle extras) {
-//                assertEquals(testSubscribedParentId, parentId);
-//                return testList;
-//            }
-//        };
-//        TestServiceRegistry.getInstance().setSessionCallback(callback);
-//
-//        connectAndWait();
-//        final CountDownLatch onChildrenLoadedLatch = new CountDownLatch(2);
-//        mBrowserCompat.subscribe(testSubscribedParentId, testExtras, new SubscriptionCallback() {
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-//                assertEquals(testSubscribedParentId, parentId);
-//                onChildrenLoadedLatch.countDown();
-//            }
-//
-//            @Override
-//            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-//                    Bundle options) {
-//                super.onChildrenLoaded(parentId, children, options);
-//            }
-//        });
-//        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-//        MediaLibrarySession librarySession = (MediaLibrarySession)
-//                TestServiceRegistry.getInstance().getServiceInstance().getSession();
-//        librarySession.notifyChildrenChanged(testSubscribedParentId, testList.size(), null);
-//        librarySession.notifyChildrenChanged(testUnsubscribedParentId, testList.size(), null);
-//        assertFalse(onChildrenLoadedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
-    @FlakyTest(bugId = 236961183)
-    @Test
-    public void customAction() throws InterruptedException {
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args_key", "args_value");
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.sendCustomAction(CUSTOM_ACTION, testArgs, new CustomActionCallback() {
-            @Override
-            public void onResult(String action, Bundle extras, Bundle resultData) {
-                assertEquals(CUSTOM_ACTION, action);
-                assertTrue(TestUtils.equals(testArgs, extras));
-                assertTrue(TestUtils.equals(CUSTOM_ACTION_EXTRAS, resultData));
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
-    @FlakyTest(bugId = 236961183)
-    @Test
-    public void customAction_rejected() throws InterruptedException {
-        // This action will not be allowed by the library session.
-        final String testAction = "random_custom_action";
-
-        connectAndWait();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mBrowserCompat.sendCustomAction(testAction, null, new CustomActionCallback() {
-            @Override
-            public void onResult(String action, Bundle extras, Bundle resultData) {
-                latch.countDown();
-            }
-        });
-        assertFalse("BrowserCompat shouldn't receive custom command",
-                latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java
deleted file mode 100644
index 8dc7f70..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCompatWithMediaSessionServiceTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-
-import androidx.media2.session.MediaSessionService;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaBrowserCompat} works well with {@link MediaSessionService}.
- */
-@LargeTest
-public class MediaBrowserCompatWithMediaSessionServiceTest extends MediaSessionTestBase {
-    MediaBrowserCompat mBrowserCompat;
-    TestConnectionCallback mConnectionCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        // Ignore all tests, see b/236961183
-        assumeTrue(false);
-        super.setUp();
-        mConnectionCallback = new TestConnectionCallback();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Make browser's internal handler to be initialized with test thread.
-                mBrowserCompat = new MediaBrowserCompat(
-                        mContext, getServiceComponent(), mConnectionCallback, null);
-            }
-        });
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mBrowserCompat != null) {
-            mBrowserCompat.disconnect();
-            mBrowserCompat = null;
-        }
-    }
-
-    ComponentName getServiceComponent() {
-        return MOCK_MEDIA2_SESSION_SERVICE;
-    }
-
-    void connectAndWait() throws InterruptedException {
-        mBrowserCompat.connect();
-        assertTrue(mConnectionCallback.mConnectedLatch.await(
-                BROWSER_COMPAT_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @FlakyTest(bugId = 236961183)
-    @Test
-    public void connect() throws InterruptedException {
-        connectAndWait();
-        assertNotEquals(0, mConnectionCallback.mFailedLatch.getCount());
-    }
-
-    @Ignore
-    @Test
-    public void connect_rejected() throws InterruptedException {
-        // TODO: Connect the browser to the session service whose onConnect() returns null.
-        assertTrue(mConnectionCallback.mFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNotEquals(0, mConnectionCallback.mConnectedLatch.getCount());
-    }
-
-    @Test
-    public void getSessionToken() throws Exception {
-        connectAndWait();
-        MediaControllerCompat controller = new MediaControllerCompat(mContext,
-                mBrowserCompat.getSessionToken());
-        assertEquals(mBrowserCompat.getServiceComponent().getPackageName(),
-                controller.getPackageName());
-    }
-
-    class TestConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        public final CountDownLatch mConnectedLatch = new CountDownLatch(1);
-        public final CountDownLatch mSuspendedLatch = new CountDownLatch(1);
-        public final CountDownLatch mFailedLatch = new CountDownLatch(1);
-
-        TestConnectionCallback() {
-            super();
-        }
-
-        @Override
-        public void onConnected() {
-            super.onConnected();
-            mConnectedLatch.countDown();
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            super.onConnectionSuspended();
-            mSuspendedLatch.countDown();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            super.onConnectionFailed();
-            mFailedLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java
deleted file mode 100644
index 305be10..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.SessionToken;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaBrowser}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaBrowserTest extends MediaControllerTest {
-
-    @Override
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints,
-            @NonNull final TestBrowserCallback callback) throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaBrowser.Builder builder = new MediaBrowser.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-
-    /**
-     * Test if the {@link TestBrowserCallback} wraps the callback proxy without missing any method.
-     */
-    @Test
-    public void testBrowserCallback() {
-        Method[] methods = TestBrowserCallback.class.getMethods();
-        assertNotNull(methods);
-        for (int i = 0; i < methods.length; i++) {
-            // For any methods in the controller callback, TestBrowserCallback should have
-            // overridden the method and call matching API in the callback proxy.
-            assertNotEquals("TestBrowserCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    BrowserCallback.class, methods[i].getDeclaringClass());
-            assertNotEquals("TestBrowserCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    ControllerCallback.class, methods[i].getDeclaringClass());
-        }
-    }
-
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
deleted file mode 100644
index 9b98b12..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
+++ /dev/null
@@ -1,1257 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
-import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_608;
-
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_NULL_ITEM;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController.ControllerCallback}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerCallbackTest extends MediaSessionTestBase {
-
-    RemoteMediaSession mRemoteSession2;
-    MediaController mController;
-
-    final List<RemoteMediaSession> mRemoteSessionList = new ArrayList<>();
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mRemoteSession2 = createRemoteMediaSession(DEFAULT_TEST_NAME);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        for (int i = 0; i < mRemoteSessionList.size(); i++) {
-            RemoteMediaSession session = mRemoteSessionList.get(i);
-            if (session != null) {
-                session.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void connection_sessionAccepts() throws InterruptedException {
-        // createController() uses controller callback to wait until the controller becomes
-        // available.
-        MediaController controller = createController(mRemoteSession2.getToken());
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void connection_sessionRejects() throws InterruptedException {
-        RemoteMediaSession session =
-                createRemoteMediaSession(TEST_CONTROLLER_CALLBACK_SESSION_REJECTS);
-
-        MediaController controller = createController(session.getToken(),
-                false /* waitForConnect */, null, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false /* expected */);
-        waitForDisconnect(controller, true /* expected */);
-
-        session.cleanUp();
-    }
-
-    @Test
-    public void connection_toLibraryService() throws InterruptedException {
-        SessionToken token = new SessionToken(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-        MediaController controller = createController(token);
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void connection_sessionClosed() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession2.getToken());
-
-        mRemoteSession2.close();
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void connection_controllerClosed() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession2.getToken());
-
-        controller.close();
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    @LargeTest
-    public void noInteractionAfterSessionClose_session() throws InterruptedException {
-        SessionToken token = mRemoteSession2.getToken();
-        mController = createController(token);
-        testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
-    }
-
-    @Test
-    @LargeTest
-    public void noInteractionAfterControllerClose_session() throws InterruptedException {
-        final SessionToken token = mRemoteSession2.getToken();
-        mController = createController(token);
-
-        mController.close();
-        // close is done immediately for session.
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
-    }
-
-    @Test
-    @LargeTest
-    public void connection_withLongPlaylist() throws InterruptedException {
-        final int playlistSize = 5000;
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(playlistSize);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mRemoteSession2.getToken())
-                .setControllerCallback(sHandlerExecutor, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onConnected(@NonNull MediaController controller,
-                            @NonNull SessionCommandGroup allowedCommands) {
-                        super.onConnected(controller, allowedCommands);
-                        latch.countDown();
-                    }
-                })
-                .build();
-        assertNotNull(controller);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-
-        // After connection, getPlaylist() should return the playlist which is set to the player.
-        List<MediaItem> playlist = controller.getPlaylist();
-        assertNotNull(playlist);
-        assertEquals(playlistSize, playlist.size());
-        for (int i = 0; i < playlist.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i), playlist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void controllerCallback_sessionUpdatePlayer() throws InterruptedException {
-        final int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_RING).build();
-        final CountDownLatch latch = new CountDownLatch(3);
-        mController = createController(mRemoteSession2.getToken(),
-                true /* waitForConnect */, null, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlayerStateChanged(@NonNull MediaController controller,
-                            int state) {
-                        assertEquals(mController, controller);
-                        assertEquals(testState, state);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> list, MediaMetadata metadata) {
-                        assertEquals(mController, controller);
-                        MediaTestUtils.assertNotMediaItemSubclass(list);
-                        MediaTestUtils.assertMediaIdEquals(testPlaylist, list);
-                        assertNull(metadata);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull MediaController.PlaybackInfo info) {
-                        assertEquals(mController, controller);
-                        assertEquals(testAudioAttributes, info.getAudioAttributes());
-                        latch.countDown();
-                    }
-                });
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setAudioAttributes(testAudioAttributes)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(null)
-                .setCurrentMediaItem(null)
-                .build();
-
-        mRemoteSession2.updatePlayer(playerConfig);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged() throws Exception {
-        final int listSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(listSize);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int currentItemIndex = 3;
-        final MediaItem currentItem = list.get(currentItemIndex);
-        final CountDownLatch latchForControllerCallback = new CountDownLatch(3);
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem item) {
-                        switch ((int) latchForControllerCallback.getCount()) {
-                            case 3:
-                                // No check needed..
-                                break;
-                            case 2:
-                                MediaTestUtils.assertNotMediaItemSubclass(item);
-                                assertEquals(currentItem.getMediaId(), item.getMediaId());
-                                break;
-                            case 1:
-                                assertNull(item);
-                        }
-                        latchForControllerCallback.countDown();
-                    }
-                });
-        // Player notifies with the unknown item. Still OK.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_UNKONWN_ITEM);
-
-        // Known ITEM should be notified through the onCurrentMediaItemChanged.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(currentItemIndex);
-
-        // Null ITEM becomes null MediaItem.
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_NULL_ITEM);
-        assertTrue(latchForControllerCallback.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_withDuration() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        if (getDuration(currentMediaItem) == testDuration) {
-                            // When current media item's duration is set, also test no other
-                            // media item has duration.
-                            int listSize = controller.getPlaylist().size();
-                            for (int i = 0; i < listSize; i++) {
-                                if (i != testCurrentItemIndex) {
-                                    assertNotEquals(testDuration,
-                                            getDuration(controller.getPlaylist().get(i)));
-                                }
-                            }
-                            latch.countDown();
-                        }
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_notCalledWithSameValue() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    private String mPreviousMediaId;
-                    private long mPreviousDuration;
-
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        String mediaId = currentMediaItem.getMetadata().getMediaId();
-                        long duration =
-                                currentMediaItem.getMetadata().getLong(
-                                        MediaMetadata.METADATA_KEY_DURATION);
-                        if (TextUtils.equals(mediaId, mPreviousMediaId)
-                                && duration == mPreviousDuration) {
-                            // Error!
-                            latch.countDown();
-                        }
-                        mPreviousMediaId = mediaId;
-                        mPreviousDuration = duration;
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_withUpdatedMetadata() throws Exception {
-        final int testListSize = 5;
-        final List<MediaItem> list = MediaTestUtils.createFileMediaItems(testListSize);
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_IDLE);
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(list);
-
-        final int testCurrentItemIndex = 3;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final long testDuration = 10123;
-        final String testDisplayTitle = "testDisplayTitle";
-        final MediaMetadata testMetadata =
-                new MediaMetadata.Builder(list.get(testCurrentItemIndex).getMetadata())
-                        .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testDisplayTitle)
-                        .build();
-
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                true, null /* connectionHints */, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            MediaItem currentMediaItem) {
-                        assertNotNull(currentMediaItem.getMetadata());
-                        if (TextUtils.equals(testDisplayTitle,
-                                currentMediaItem.getMetadata().getText(
-                                        MediaMetadata.METADATA_KEY_DISPLAY_TITLE))) {
-                            if (getDuration(currentMediaItem) == testDuration) {
-                                latch.countDown();
-                            }
-                        }
-                    }
-                });
-
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setDuration(testDuration);
-        // This make session to trust duration from the player.
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(
-                SessionPlayer.PLAYER_STATE_PLAYING);
-        mRemoteSession2.getMockPlayer().setCurrentMediaItemMetadata(testMetadata);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCurrentMediaItemChanged_resetsCurrentPosition() throws Exception {
-        int testCurrentItemIndex = 1;
-        String testCurrentItemId = TestUtils.getMediaIdInFakeList(testCurrentItemIndex);
-
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(/* size= */ 2);
-        mRemoteSession2.getMockPlayer().setCurrentPosition(123L);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                /* waitForConnect= */ true, /* connectionHints= */ null,
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                            @Nullable MediaItem currentMediaItem) {
-                        if (currentMediaItem != null
-                                && testCurrentItemId.equals(currentMediaItem.getMediaId())) {
-                            controller.setTimeDiff(0L);
-                            latch.countDown();
-                        }
-                    }
-                });
-        mRemoteSession2.getMockPlayer().setCurrentMediaItem(testCurrentItemIndex);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(testCurrentItemIndex);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(0L, controller.getCurrentPosition());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaybackSpeed()}.
-     */
-    @Test
-    public void onPlaybackSpeedChanged() throws Exception {
-        final float speed = 1.5f;
-        mRemoteSession2.getMockPlayer().setPlaybackSpeed(speed);
-
-        final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
-        MediaController controller = createController(
-                mRemoteSession2.getToken(), true, null, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackSpeedChanged(@NonNull MediaController controller,
-                            float speedOut) {
-                        assertEquals(speed, speedOut, 0.0f);
-                        latchForControllerCallback.countDown();
-                    }
-                });
-        mRemoteSession2.getMockPlayer().notifyPlaybackSpeedChanged(speed);
-        assertTrue(latchForControllerCallback.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaybackInfo()}.
-     */
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byPlayerChange() throws Exception {
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VOLUME_CONTROL_ABSOLUTE;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-                assertEquals(attrs, info.getAudioAttributes());
-                assertEquals(volumeControlType, info.getPlaybackType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(volumeControlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession2.updatePlayer(playerConfig);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        assertEquals(volumeControlType, info.getControlType());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byAudioAttributesChange() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
-                .setUsage(AudioAttributesCompat.USAGE_MEDIA)
-                .build();
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull PlaybackInfo info) {
-                        assertNotNull(info.getAudioAttributes());
-                        assertEquals(attrs, info.getAudioAttributes());
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyAudioAttributesChanged(attrs);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlaybackInfoChanged_isCalled_byVolumeChange() throws Exception {
-        Bundle config = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mRemoteSession2.updatePlayer(config);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicReference<PlaybackInfo> playbackInfoRef = new AtomicReference<>();
-        MediaController.ControllerCallback callback = new MediaController.ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull PlaybackInfo info) {
-                playbackInfoRef.set(info);
-                latch.countDown();
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(),
-                /* waitForConnect= */ true, /* connectionHints= */ null, callback);
-
-        int targetVolume = 3;
-        mRemoteSession2.getMockPlayer().notifyVolumeChanged(targetVolume);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetVolume, playbackInfoRef.get().getCurrentVolume());
-        assertEquals(targetVolume, controller.getPlaybackInfo().getCurrentVolume());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaylist()}.
-     */
-    @Test
-    public void onPlaylistChanged() throws InterruptedException {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNotNull(playlist);
-                        MediaTestUtils.assertNotMediaItemSubclass(playlist);
-                        MediaTestUtils.assertMediaIdEquals(testList, playlist);
-                        listFromCallback.set(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        mRemoteSession2.getMockPlayer().setPlaylist(testList);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-    }
-
-    @Test
-    public void onPlaylistChanged_nullList() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNull(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-
-        mRemoteSession2.getMockPlayer().setPlaylist(null);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getPlaylist());
-    }
-
-    @Test
-    @LargeTest
-    public void onPlaylistChanged_longList() throws InterruptedException {
-        final int listSize = 5000;
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistChanged(@NonNull MediaController controller,
-                            List<MediaItem> playlist, MediaMetadata metadata) {
-                        assertNotNull(playlist);
-                        assertEquals(listSize, playlist.size());
-                        for (int i = 0; i < playlist.size(); i++) {
-                            assertEquals(TestUtils.getMediaIdInFakeList(i),
-                                    playlist.get(i).getMediaId());
-                        }
-                        listFromCallback.set(playlist);
-                        latch.countDown();
-                    }
-                };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().createAndSetFakePlaylist(listSize);
-        mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-        assertEquals(listFromCallback.get(), controller.getPlaylist());
-    }
-
-    /**
-     * This also tests {@link MediaController#getPlaylistMetadata()}.
-     */
-    @Test
-    public void onPlaylistMetadataChanged() throws InterruptedException {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        final AtomicReference<MediaMetadata> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                            MediaMetadata metadata) {
-                        assertNotNull(metadata);
-                        assertEquals(testMetadata.getMediaId(), metadata.getMediaId());
-                        metadataFromCallback.set(metadata);
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setPlaylistMetadata(testMetadata);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaylistMetadataChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(metadataFromCallback.get().getMediaId(),
-                controller.getPlaylistMetadata().getMediaId());
-    }
-
-    @Test
-    @LargeTest
-    public void onPlaylistMetadataChanged_withManyLargeImages() throws InterruptedException {
-        final int imageCount = 20;
-        final int originalWidth = 1024;
-        final int originalHeight = 1024;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                            MediaMetadata metadata) {
-                        assertNotNull(metadata);
-                        Set<String> keySet = metadata.keySet();
-                        assertEquals(imageCount, keySet.size());
-                        for (String key : keySet) {
-                            Bitmap value = metadata.getBitmap(key);
-                            assertTrue("Bitmap should have been scaled down.",
-                                    originalWidth > value.getWidth()
-                                            && originalHeight > value.getHeight());
-                        }
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setPlaylistMetadataWithLargeBitmaps(imageCount, originalWidth, originalHeight);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaylistMetadataChanged();
-        if (Build.VERSION.SDK_INT <= 19) {
-            // Due to the GC, time takes longer than expected.
-            // It seems to be due to the Dalvik GC mechanism.
-            assertTrue(latch.await(10, TimeUnit.SECONDS));
-        } else {
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        }
-    }
-
-    /**
-     * This also tests {@link MediaController#getShuffleMode()}.
-     */
-    @Test
-    public void onShuffleModeChanged() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onShuffleModeChanged(@NonNull MediaController controller,
-                            int shuffleMode) {
-                        assertEquals(testShuffleMode, shuffleMode);
-                        latch.countDown();
-                    }
-                };
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setShuffleMode(testShuffleMode);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-    }
-
-    /**
-     * This also tests {@link MediaController#getRepeatMode()}.
-     */
-    @Test
-    public void onRepeatModeChanged() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onRepeatModeChanged(@NonNull MediaController controller,
-                            int repeatMode) {
-                        assertEquals(testRepeatMode, repeatMode);
-                        latch.countDown();
-                    }
-                };
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setRepeatMode(testRepeatMode);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void onPlaybackCompleted() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlaybackCompleted(@NonNull MediaController controller) {
-                        latch.countDown();
-                    }
-                };
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        player.notifyPlaybackCompleted();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSeekCompleted() throws InterruptedException {
-        final long testSeekPosition = 400;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onSeekCompleted(@NonNull MediaController controller, long position) {
-                controller.setTimeDiff(0L);
-                assertEquals(testSeekPosition, position);
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifySeekCompleted(testSeekPosition);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onBufferingStateChanged() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final int targetItemIndex = 0;
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        final long testBufferingPosition = 500;
-        final long testPosition = 300;
-        final long testTimeDiff = 100;
-
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                MediaTestUtils.assertNotMediaItemSubclass(item);
-                assertEquals(testPlaylist.get(targetItemIndex).getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                assertEquals(testPosition + testTimeDiff, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setBufferedPosition(testBufferingPosition);
-        player.setCurrentPosition(testPosition);
-        player.setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        // Since we cannot pass the DataSourceDesc directly, send the item index so that the player
-        // can select which item's state change should be notified.
-        player.notifyBufferingStateChanged(targetItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onBufferingStateChanged_bufferingAndStarved() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        final int targetItemIndex = 0;
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
-        final long testBufferingPosition = 300;
-        final long testPosition = 300;
-        final long testTimeDiff = 100;
-
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                MediaTestUtils.assertNotMediaItemSubclass(item);
-                assertEquals(testPlaylist.get(targetItemIndex).getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        RemoteMediaSession.RemoteMockPlayer player = mRemoteSession2.getMockPlayer();
-        player.setBufferedPosition(testBufferingPosition);
-        player.setCurrentPosition(testPosition);
-        player.setPlayerState(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        // Since we cannot pass the DataSourceDesc directly, send the item index so that the player
-        // can select which item's state change should be notified.
-        player.notifyBufferingStateChanged(targetItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlayerStateChanged_playing() throws InterruptedException {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long testPosition = 500;
-        final long testTimeDiff = 100;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                controller.setTimeDiff(testTimeDiff);
-                assertEquals(testPlayerState, state);
-                assertEquals(testPlayerState, controller.getPlayerState());
-                assertEquals(testPosition + testTimeDiff, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(testPlayerState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onPlayerStateChanged_paused() throws InterruptedException {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        final long testPosition = 500;
-        final long testTimeDiff = 100;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onPlayerStateChanged(@NonNull MediaController controller,
-                            int state) {
-                        controller.setTimeDiff(testTimeDiff);
-                        assertEquals(testPlayerState, state);
-                        assertEquals(testPlayerState, controller.getPlayerState());
-                        assertEquals(testPosition, controller.getCurrentPosition());
-                        latch.countDown();
-                    }
-                };
-
-        mRemoteSession2.getMockPlayer().setCurrentPosition(testPosition);
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyPlayerStateChanged(testPlayerState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * This also tests {@link MediaController#getAllowedCommands()}.
-     */
-    @Test
-    public void onAllowedCommandsChanged() throws InterruptedException {
-        final SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY));
-        builder.addCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PAUSE));
-        final SessionCommandGroup commands = builder.build();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup commandsOut) {
-                assertEquals(commands, commandsOut);
-                latch.countDown();
-            }
-        };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.setAllowedCommands(commands);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(commands, controller.getAllowedCommands());
-    }
-
-    @Test
-    public void onCustomCommand() throws InterruptedException {
-        final String testCommandAction = "test_action";
-        final SessionCommand testCommand = new SessionCommand(testCommandAction, null);
-        final Bundle testArgs = TestUtils.createTestBundle();
-
-        final CountDownLatch primaryLatch = new CountDownLatch(2);
-        final CountDownLatch extraLatch = new CountDownLatch(1);
-        final MediaController.ControllerCallback primaryCallback =
-                new MediaController.ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(testCommand, command);
-                assertTrue(TestUtils.equals(testArgs, args));
-                primaryLatch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        final MediaController.ControllerCallback extraCallback =
-                new MediaController.ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                extraLatch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback);
-
-        // TODO(jaewan): Test with multiple controllers
-        mRemoteSession2.broadcastCustomCommand(testCommand, testArgs);
-
-        // TODO(jaewan): Test receivers as well.
-        mRemoteSession2.sendCustomCommand(testCommand, testArgs);
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertFalse("Extra ControllerCallback shouldn't be called",
-                extraLatch.await(300, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onCustomLayoutChanged() throws InterruptedException {
-        final List<MediaSession.CommandButton> buttons = new ArrayList<>();
-
-        MediaSession.CommandButton button = new MediaSession.CommandButton.Builder()
-                .setCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_PLAY))
-                .setDisplayName("button")
-                .build();
-        buttons.add(button);
-
-        final CountDownLatch primaryLatch = new CountDownLatch(1);
-        final CountDownLatch extraLatch = new CountDownLatch(1);
-        final MediaController.ControllerCallback primaryCallback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<MediaSession.CommandButton> layout) {
-                assertEquals(layout.size(), buttons.size());
-                for (int i = 0; i < layout.size(); i++) {
-                    assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
-                    assertEquals(layout.get(i).getDisplayName(), buttons.get(i).getDisplayName());
-                }
-                primaryLatch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        final MediaController.ControllerCallback extraCallback =
-                new MediaController.ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<MediaSession.CommandButton> layout) {
-                extraLatch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback);
-
-        mRemoteSession2.setCustomLayout(buttons);
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertFalse("Extra ControllerCallback shouldn't be called",
-                extraLatch.await(300, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onVideoSizeChanged() throws InterruptedException {
-        final VideoSize testSize = new VideoSize(100, 42);
-        final CountDownLatch latch = new CountDownLatch(2);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onVideoSizeChanged(@NonNull MediaController controller,
-                            @NonNull MediaItem item, @NonNull VideoSize videoSize) {
-                        assertNotNull(item);
-                        assertEquals(testSize, videoSize);
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onVideoSizeChanged(@NonNull MediaController controller,
-                            @NonNull VideoSize videoSize) {
-                        assertEquals(testSize, videoSize);
-                        latch.countDown();
-                    }
-                };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_UNKONWN_ITEM);
-        mRemoteSession2.getMockPlayer().notifyVideoSizeChanged(testSize);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onTracksChanged() throws InterruptedException {
-        List<SessionPlayer.TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        AtomicReference<List<SessionPlayer.TrackInfo>> returnedTracksRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTracksChanged(@NonNull MediaController controller,
-                            @NonNull List<TrackInfo> tracks) {
-                        returnedTracksRef.set(tracks);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTracksChanged(testTracks);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTracks, returnedTracksRef.get());
-    }
-
-    @Test
-    public void onTrackSelected() throws InterruptedException {
-        SessionPlayer.TrackInfo testTrack = MediaTestUtils.createTrackInfo(1,
-                SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        AtomicReference<SessionPlayer.TrackInfo> returnedTrackRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackSelected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        returnedTrackRef.set(trackInfo);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTrackSelected(testTrack);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, returnedTrackRef.get());
-    }
-
-    @Test
-    public void onTrackDeselected() throws InterruptedException {
-        SessionPlayer.TrackInfo testTrack = MediaTestUtils.createTrackInfo(1,
-                SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        AtomicReference<SessionPlayer.TrackInfo> returnedTrackRef = new AtomicReference<>();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackDeselected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        returnedTrackRef.set(trackInfo);
-                        latch.countDown();
-                    }
-                };
-        createController(mRemoteSession2.getToken(), true, null, callback);
-        mRemoteSession2.getMockPlayer().notifyTrackDeselected(testTrack);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, returnedTrackRef.get());
-    }
-
-    @Test
-    public void onSubtitleData() throws InterruptedException {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_LANGUAGE, "und");
-        format.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "onSubtitleData").build();
-        final MediaItem testItem = new MediaItem.Builder().setMetadata(metadata).build();
-        final TrackInfo testTrack = new TrackInfo(1, TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, format);
-        final SubtitleData testData = new SubtitleData(123, 456,
-                new byte[] { 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 });
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaController.ControllerCallback callback =
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onSubtitleData(@NonNull MediaController controller,
-                            @NonNull MediaItem item, @NonNull TrackInfo track,
-                            @NonNull SubtitleData data) {
-                        MediaTestUtils.assertMediaIdEquals(testItem, item);
-                        assertEquals(testTrack, track);
-                        assertEquals(testData, data);
-                        latch.countDown();
-                    }
-                };
-
-        MediaController controller = createController(mRemoteSession2.getToken(), true, null,
-                callback);
-        mRemoteSession2.getMockPlayer().notifySubtitleData(testItem, testTrack, testData);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void testControllerAfterSessionIsClosed(String id) throws InterruptedException {
-        // This cause session service to be died.
-        mRemoteSession2.close();
-        waitForDisconnect(mController, true);
-        testNoInteraction();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        // Recreated session has different session stub, so previously created controller
-        // shouldn't be available.
-        mRemoteSession2 = createRemoteMediaSession(id);
-        testNoInteraction();
-    }
-
-    // Test that mSession and mController doesn't interact.
-    // Note that this method can be called after the mSession is died, so mSession may not have
-    // valid player.
-    private void testNoInteraction() throws InterruptedException {
-        // TODO: check that calls from the controller to session shouldn't be delivered.
-
-        // Calls from the session to controller shouldn't be delivered.
-        final CountDownLatch latch = new CountDownLatch(1);
-        setRunnableForOnCustomCommand(mController, new Runnable() {
-            @Override
-            public void run() {
-                latch.countDown();
-            }
-        });
-        SessionCommand customCommand = new SessionCommand("testNoInteraction", null);
-
-        mRemoteSession2.broadcastCustomCommand(customCommand, null);
-
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        setRunnableForOnCustomCommand(mController, null);
-    }
-
-    private static long getDuration(MediaItem item) {
-        if (item == null || item.getMetadata() == null) {
-            return SessionPlayer.UNKNOWN_TIME;
-        }
-        return item.getMetadata().getLong(MediaMetadata.METADATA_KEY_DURATION);
-    }
-
-    RemoteMediaSession createRemoteMediaSession(String id) {
-        RemoteMediaSession session = new RemoteMediaSession(id, mContext, null);
-        mRemoteSessionList.add(session);
-        return session;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
deleted file mode 100644
index 24ed446..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.HeartRating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.ThumbRating;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests for {@link MediaControllerCompat.Callback} with {@link MediaSession}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerCompatCallbackWithMediaSessionTest extends MediaSessionTestBase {
-    private static final String TAG = "MCCCallbackTestWithMS2";
-
-    private static final long TIMEOUT_MS = 1000L;
-    private static final float EPSILON = 1e-6f;
-
-    private RemoteMediaSession mSession;
-    private MediaControllerCompat mControllerCompat;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mSession = new RemoteMediaSession(TAG, mContext, null);
-        mControllerCompat = new MediaControllerCompat(mContext, mSession.getCompatToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        mSession.close();
-    }
-
-    @Test
-    public void gettersAfterConnected() throws Exception {
-        int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        int testBufferingPosition = 1500;
-        float testSpeed = 1.5f;
-        List<MediaItem> testPlaylist = new ArrayList<>();
-        for (int i = 0; i < 3; i++) {
-            testPlaylist.add(new MediaItem.Builder()
-                    .setMetadata(new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "id=" + i)
-                            .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new HeartRating())
-                            .build())
-                    .build());
-        }
-        int testItemIndex = 0;
-        String testPlaylistTitle = "testPlaylistTitle";
-        MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setBufferedPosition(testBufferingPosition)
-                .setPlaybackSpeed(testSpeed)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(testPlaylistMetadata)
-                .setCurrentMediaItem(testPlaylist.get(testItemIndex))
-                .setShuffleMode(testShuffleMode)
-                .setRepeatMode(testRepeatMode)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        MediaControllerCompat controller =
-                new MediaControllerCompat(mContext, mSession.getCompatToken());
-        CountDownLatch latch = new CountDownLatch(1);
-        controller.registerCallback(
-                new MediaControllerCompat.Callback() {
-                    @Override
-                    public void onSessionReady() {
-                        latch.countDown();
-                    }
-                }, sHandler);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertEquals(testState, MediaUtils.convertToPlayerState(controller.getPlaybackState()));
-        assertEquals(testBufferingPosition, controller.getPlaybackState().getBufferedPosition());
-        assertEquals(testSpeed, controller.getPlaybackState().getPlaybackSpeed(), EPSILON);
-
-        assertEquals(testPlaylist.get(testItemIndex).getMediaId(),
-                controller.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-
-        List<QueueItem> queue = controller.getQueue();
-        assertNotNull(queue);
-        assertEquals(testPlaylist.size(), queue.size());
-        for (int i = 0; i < testPlaylist.size(); i++) {
-            assertEquals(testPlaylist.get(i).getMediaId(),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(testPlaylistTitle, controller.getQueueTitle().toString());
-        assertEquals(RatingCompat.RATING_HEART, controller.getRatingType());
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void repeatModeChange() throws Exception {
-        int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicInteger repeatModeRef = new AtomicInteger();
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onRepeatModeChanged(int repeatMode) {
-                repeatModeRef.set(repeatMode);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setRepeatMode(testRepeatMode);
-        mSession.getMockPlayer().notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, repeatModeRef.get());
-        assertEquals(testRepeatMode, mControllerCompat.getRepeatMode());
-    }
-
-    @Test
-    public void shuffleModeChange() throws Exception {
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicInteger shuffleModeRef = new AtomicInteger();
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onShuffleModeChanged(int shuffleMode) {
-                shuffleModeRef.set(shuffleMode);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setShuffleMode(testShuffleMode);
-        mSession.getMockPlayer().notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, shuffleModeRef.get());
-        assertEquals(testShuffleMode, mControllerCompat.getShuffleMode());
-    }
-
-    @Test
-    public void close() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onSessionDestroyed() {
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void updatePlayer() throws Exception {
-        int testState = SessionPlayer.PLAYER_STATE_PLAYING;
-        int testBufferingPosition = 1500;
-        float testSpeed = 1.5f;
-        List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        int testItemIndex = 0;
-        String testPlaylistTitle = "testPlaylistTitle";
-        MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
-        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
-        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        AtomicInteger shuffleModeRef = new AtomicInteger();
-        AtomicInteger repeatModeRef = new AtomicInteger();
-        CountDownLatch latchForPlaybackState = new CountDownLatch(1);
-        CountDownLatch latchForMetadata = new CountDownLatch(1);
-        CountDownLatch latchForQueue = new CountDownLatch(2);
-        CountDownLatch latchForShuffleMode = new CountDownLatch(1);
-        CountDownLatch latchForRepeatMode = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latchForPlaybackState.countDown();
-            }
-
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) {
-                metadataRef.set(metadata);
-                latchForMetadata.countDown();
-            }
-
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latchForQueue.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latchForQueue.countDown();
-            }
-
-            @Override
-            public void onShuffleModeChanged(int shuffleMode) {
-                shuffleModeRef.set(shuffleMode);
-                latchForShuffleMode.countDown();
-            }
-
-            @Override
-            public void onRepeatModeChanged(int repeatMode) {
-                repeatModeRef.set(repeatMode);
-                latchForRepeatMode.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(testState)
-                .setBufferedPosition(testBufferingPosition)
-                .setPlaybackSpeed(testSpeed)
-                .setPlaylist(testPlaylist)
-                .setPlaylistMetadata(testPlaylistMetadata)
-                .setCurrentMediaItem(testPlaylist.get(testItemIndex))
-                .setShuffleMode(testShuffleMode)
-                .setRepeatMode(testRepeatMode)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        assertTrue(latchForPlaybackState.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testState, MediaUtils.convertToPlayerState(playbackStateRef.get()));
-        assertEquals(testBufferingPosition, playbackStateRef.get().getBufferedPosition());
-        assertEquals(testSpeed, playbackStateRef.get().getPlaybackSpeed(), EPSILON);
-
-        assertTrue(latchForMetadata.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testPlaylist.get(testItemIndex).getMediaId(),
-                metadataRef.get().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-
-        assertTrue(latchForQueue.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-        assertEquals(testPlaylist.size(), queue.size());
-        for (int i = 0; i < testPlaylist.size(); i++) {
-            assertEquals(testPlaylist.get(i).getMediaId(),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(testPlaylistTitle, queueTitleRef.get().toString());
-
-        assertTrue(latchForShuffleMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, shuffleModeRef.get());
-        assertTrue(latchForRepeatMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, repeatModeRef.get());
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeChangedToRemote() throws Exception {
-        int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        int maxVolume = 25;
-        int currentVolume = 10;
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(controlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-        assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                info.getPlaybackType());
-        assertEquals(controlType, info.getVolumeControl());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeChangedToLocal() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int legacyStream = AudioManager.STREAM_RING;
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(legacyStream).build();
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType() == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfigToUpdate = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mSession.updatePlayer(playerConfigToUpdate);
-
-        // In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local.
-        if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    info.getPlaybackType());
-            assertEquals(legacyStream, info.getAudioAttributes().getLegacyStreamType());
-        }
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeNotChanged_local() throws Exception {
-        int legacyStream = AudioManager.STREAM_RING;
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(legacyStream).build();
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType() == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
-        if (Build.VERSION.SDK_INT >= 21) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                        && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    info.getPlaybackType());
-            assertEquals(legacyStream, info.getAudioAttributes().getLegacyStreamType());
-        }
-    }
-
-    @Test
-    public void updatePlayer_playbackTypeNotChanged_remote() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        int maxVolume = 25;
-        int currentVolume = 1;
-
-        CountDownLatch playbackInfoNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume) {
-                    playbackInfoNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        Bundle playerConfigToUpdate = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(controlType)
-                .setMaxVolume(maxVolume)
-                .setCurrentVolume(currentVolume)
-                .build();
-        mSession.updatePlayer(playerConfigToUpdate);
-
-        // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
-        if (Build.VERSION.SDK_INT >= 21) {
-            PollingCheck.waitFor(TIMEOUT_MS, () -> {
-                MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-                return info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && info.getVolumeControl() == controlType
-                        && info.getMaxVolume() == maxVolume
-                        && info.getCurrentVolume() == currentVolume;
-            });
-        } else {
-            assertTrue(playbackInfoNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            MediaControllerCompat.PlaybackInfo info = mControllerCompat.getPlaybackInfo();
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                    info.getPlaybackType());
-            assertEquals(controlType, info.getVolumeControl());
-            assertEquals(maxVolume, info.getMaxVolume());
-            assertEquals(currentVolume, info.getCurrentVolume());
-        }
-    }
-
-    @Test
-    public void playerStateChange() throws Exception {
-        int targetState = SessionPlayer.PLAYER_STATE_PLAYING;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onSessionReady() {
-                latch.countDown();
-            }
-
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().notifyPlayerStateChanged(targetState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetState, MediaUtils.convertToPlayerState(playbackStateRef.get()));
-        assertEquals(targetState,
-                MediaUtils.convertToPlayerState(mControllerCompat.getPlaybackState()));
-    }
-
-    @Test
-    public void playbackSpeedChange() throws Exception {
-        float speed = 1.5f;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaybackSpeed(speed);
-        mSession.getMockPlayer().notifyPlaybackSpeedChanged(speed);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, playbackStateRef.get().getPlaybackSpeed(), EPSILON);
-        assertEquals(speed, mControllerCompat.getPlaybackState().getPlaybackSpeed(), EPSILON);
-    }
-
-    @Test
-    public void bufferingStateChange() throws Exception {
-        List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(3);
-        int testItemIndex = 0;
-        int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        long testBufferingPosition = 500;
-        mSession.getMockPlayer().setPlaylistWithFakeItem(testPlaylist);
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setBufferedPosition(testBufferingPosition);
-        mSession.getMockPlayer().notifyBufferingStateChanged(testItemIndex, testBufferingState);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testBufferingPosition, playbackStateRef.get().getBufferedPosition());
-        assertEquals(testBufferingPosition,
-                mControllerCompat.getPlaybackState().getBufferedPosition());
-    }
-
-    @Test
-    public void seekComplete() throws Exception {
-        long testSeekPosition = 1300;
-
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setCurrentPosition(testSeekPosition);
-        mSession.getMockPlayer().setPlayerState(SessionPlayer.PLAYER_STATE_PAUSED);
-        mSession.getMockPlayer().notifySeekCompleted(testSeekPosition);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testSeekPosition, playbackStateRef.get().getPosition());
-        assertEquals(testSeekPosition, mControllerCompat.getPlaybackState().getPosition());
-    }
-
-    @FlakyTest(bugId = 187338985)
-    @Test
-    public void currentMediaItemChange() throws Exception {
-        int testItemIndex = 3;
-        long testPosition = 1234;
-        String displayTitle = "displayTitle";
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle)
-                .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new ThumbRating())
-                .build();
-        MediaItem currentMediaItem = new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(metadata)
-                .build();
-
-        List<MediaItem> playlist = MediaTestUtils.createFileMediaItems(5);
-        playlist.set(testItemIndex, currentMediaItem);
-        mSession.getMockPlayer().setPlaylistWithFakeItem(playlist);
-
-        AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
-        AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
-        CountDownLatch latchForMetadata = new CountDownLatch(1);
-        CountDownLatch latchForPlaybackState = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) {
-                metadataRef.set(metadata);
-                latchForMetadata.countDown();
-            }
-
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                playbackStateRef.set(state);
-                latchForPlaybackState.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setCurrentMediaItem(testItemIndex);
-        mSession.getMockPlayer().setCurrentPosition(testPosition);
-        mSession.getMockPlayer().notifyCurrentMediaItemChanged(testItemIndex);
-
-        assertTrue(latchForMetadata.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(displayTitle,
-                metadataRef.get().getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE));
-        assertEquals(displayTitle,
-                mControllerCompat.getMetadata().getString(
-                        MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE));
-        assertTrue(latchForPlaybackState.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testPosition, playbackStateRef.get().getPosition());
-        assertEquals(testPosition, mControllerCompat.getPlaybackState().getPosition());
-        assertEquals(MediaUtils.convertToQueueItemId(testItemIndex),
-                playbackStateRef.get().getActiveQueueItemId());
-        assertEquals(MediaUtils.convertToQueueItemId(testItemIndex),
-                mControllerCompat.getPlaybackState().getActiveQueueItemId());
-        assertEquals(RatingCompat.RATING_THUMB_UP_DOWN, mControllerCompat.getRatingType());
-    }
-
-    @Test
-    public void playlistAndPlaylistMetadataChange() throws Exception {
-        List<MediaItem> playlist = MediaTestUtils.createFileMediaItems(5);
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaylist(playlist);
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-        assertEquals(playlist.size(), queue.size());
-        for (int i = 0; i < playlist.size(); i++) {
-            assertEquals(playlist.get(i).getMediaId(), queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Test
-    public void playlistAndPlaylistMetadataChange_longList() throws Exception {
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(2);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        int listSize = 5000;
-        mSession.getMockPlayer().createAndSetFakePlaylist(listSize);
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistChanged();
-
-        assertTrue(latch.await(3, TimeUnit.SECONDS));
-
-        List<QueueItem> queue = mControllerCompat.getQueue();
-        assertNotNull(queue);
-
-        if (Build.VERSION.SDK_INT >= 21) {
-            assertEquals(listSize, queue.size());
-        } else {
-            // Below API 21, only the initial part of the playlist is sent to the
-            // MediaControllerCompat when the list is too long.
-            assertTrue(queue.size() < listSize);
-        }
-        for (int i = 0; i < queue.size(); i++) {
-            assertEquals(TestUtils.getMediaIdInFakeList(i),
-                    queue.get(i).getDescription().getMediaId());
-        }
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Test
-    public void playlistMetadataChange() throws Exception {
-        String playlistTitle = "playlistTitle";
-        MediaMetadata playlistMetadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
-
-        AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                queueTitleRef.set(title);
-                latch.countDown();
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().setPlaylistMetadata(playlistMetadata);
-        mSession.getMockPlayer().notifyPlaylistMetadataChanged();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(playlistTitle, queueTitleRef.get().toString());
-    }
-
-    @Test
-    public void onAudioInfoChanged_isCalled_byVolumeChange() throws Exception {
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVolumeControlType(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE)
-                .setMaxVolume(10)
-                .setCurrentVolume(1)
-                .build();
-        mSession.updatePlayer(playerConfig);
-
-        int targetVolume = 3;
-        CountDownLatch targetVolumeNotified = new CountDownLatch(1);
-        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
-            @Override
-            public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-                if (info.getCurrentVolume() == targetVolume) {
-                    targetVolumeNotified.countDown();
-                }
-            }
-        };
-        mControllerCompat.registerCallback(callback, sHandler);
-
-        mSession.getMockPlayer().notifyVolumeChanged(targetVolume);
-
-        assertTrue(targetVolumeNotified.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetVolume, mControllerCompat.getPlaybackInfo().getCurrentVolume());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
deleted file mode 100644
index 58a7218..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.common.BaseResult.RESULT_INFO_SKIPPED;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.PercentageRating;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSessionCompat;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController} interacting with {@link MediaSessionCompat}.
- *
- * TODO: Pull out callback tests to a separate file (i.e. MediaControllerLegacyCallbackTest).
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@MediumTest
-public class MediaControllerLegacyTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaControllerLegacyTest";
-    private static final long EXPECTED_TIMEOUT_MS = 100;
-
-    AudioManager mAudioManager;
-    RemoteMediaSessionCompat mSession;
-    MediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mSession = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, mContext);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        mSession.cleanUp();
-        if (mController != null) {
-            mController.close();
-        }
-    }
-
-    @Test
-    public void gettersAfterConnected() throws Exception {
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-        final long timeDiff = 102;
-        final float speed = 0.5f;
-        final int shuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final int repeatMode = SessionPlayer.REPEAT_MODE_ALL;
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                MediaTestUtils.createMetadata());
-
-        mSession.setPlaybackState(
-                new PlaybackStateCompat.Builder()
-                        .setState(PlaybackStateCompat.STATE_PLAYING, position, speed)
-                        .setBufferedPosition(bufferedPosition).build());
-        mSession.setMetadata(metadata);
-        mSession.setShuffleMode(shuffleMode);
-        mSession.setRepeatMode(repeatMode);
-        mSession.setRatingType(RatingCompat.RATING_PERCENTAGE);
-
-        mController = createController(mSession.getSessionToken());
-        mController.setTimeDiff(timeDiff);
-
-        assertEquals(SessionPlayer.PLAYER_STATE_PLAYING, mController.getPlayerState());
-        assertEquals(SessionPlayer.BUFFERING_STATE_COMPLETE,
-                mController.getBufferingState());
-        assertEquals(bufferedPosition, mController.getBufferedPosition());
-        assertEquals(speed, mController.getPlaybackSpeed(), 0.0f);
-        assertEquals((double) position + (speed * timeDiff),
-                (double) mController.getCurrentPosition(), 100.0 /* 100 ms */);
-        assertEquals(metadata.getDescription().getMediaId(),
-                mController.getCurrentMediaItem().getMediaId());
-        Rating rating = mController.getCurrentMediaItem().getMetadata()
-                .getRating(MediaMetadata.METADATA_KEY_USER_RATING);
-        assertTrue(rating instanceof PercentageRating);
-        assertFalse(rating.isRated());
-        assertEquals(shuffleMode, mController.getShuffleMode());
-        assertEquals(repeatMode, mController.getRepeatMode());
-    }
-
-    @Test
-    public void getPackageName() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        assertEquals(SERVICE_PACKAGE_NAME, mController.getConnectedToken().getPackageName());
-    }
-
-    @Test
-    public void getSessionActivity() throws Exception {
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        PendingIntent pi = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-        mSession.setSessionActivity(pi);
-
-        mController = createController(mSession.getSessionToken());
-        PendingIntent sessionActivityOut = mController.getSessionActivity();
-        assertNotNull(sessionActivityOut);
-        if (Build.VERSION.SDK_INT >= 17) {
-            // PendingIntent#getCreatorPackage() is added in API 17.
-            assertEquals(mContext.getPackageName(), sessionActivityOut.getCreatorPackage());
-        }
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getRepeatMode() throws Exception {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setRepeatMode(testRepeatMode);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, mController.getRepeatMode());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController, int)}.
-     */
-    @Test
-    public void getShuffleMode() throws Exception {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setShuffleMode(testShuffleMode);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, mController.getShuffleMode());
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onPlaylistChanged(
-     * MediaController, List, MediaMetadata)}.
-     */
-    @Test
-    public void getPlaylist() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final AtomicReference<List<MediaItem>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(@NonNull MediaController controller,
-                    List<MediaItem> playlist, MediaMetadata metadata) {
-                assertNotNull(playlist);
-                assertEquals(testList.size(), playlist.size());
-                for (int i = 0; i < playlist.size(); i++) {
-                    assertEquals(testList.get(i).getMediaId(), playlist.get(i).getMediaId());
-                }
-                listFromCallback.set(playlist);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setQueue(testQueue);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listFromCallback.get(), mController.getPlaylist());
-    }
-
-    @Test
-    public void getPlaylistMetadata() throws Exception {
-        final AtomicReference<MediaMetadata> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final CharSequence queueTitle = "test queue title";
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-                    MediaMetadata metadata) {
-                assertEquals(queueTitle.toString(),
-                        metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
-                metadataFromCallback.set(metadata);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        mSession.setQueueTitle(queueTitle);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(metadataFromCallback.get(), mController.getPlaylistMetadata());
-    }
-
-    @Test
-    public void getCurrentMediaItemAfterConnected() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-        assertNull(mController.getCurrentMediaItem());
-    }
-
-    @Test
-    public void getCurrentMediaItemAfterConnected_metadata() throws Exception {
-        final String testMediaId = "testGetCurrentMediaItemWhenConnected_metadata";
-        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
-                .build();
-        mSession.setMetadata(metadata);
-
-        mController = createController(mSession.getSessionToken(), true, null);
-        assertEquals(testMediaId, mController.getCurrentMediaItem().getMediaId());
-    }
-
-    @Test
-    public void setMediaUri_resultSetAfterPrepare() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri = Uri.parse("androidx://test");
-        ListenableFuture<SessionResult> future =
-                mController.setMediaUri(testUri, /* extras= */ null);
-
-        SessionResult result;
-        try {
-            result = future.get(EXPECTED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            fail("TimeoutException is expected");
-        } catch (TimeoutException e) {
-            // expected.
-        }
-
-        mController.prepare();
-
-        result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void setMediaUri_resultSetAfterPlay() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri = Uri.parse("androidx://test");
-        ListenableFuture<SessionResult> future =
-                mController.setMediaUri(testUri, /* extras= */ null);
-
-        SessionResult result;
-        try {
-            result = future.get(EXPECTED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            fail("TimeoutException is expected");
-        } catch (TimeoutException e) {
-            // expected.
-        }
-
-        mController.play();
-
-        result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_SUCCESS, result.getResultCode());
-    }
-
-    @Test
-    public void setMediaUris_multipleCalls_previousCallReturnsResultInfoSkipped() throws Exception {
-        mController = createController(mSession.getSessionToken(), true, null);
-
-        Uri testUri1 = Uri.parse("androidx://test1");
-        Uri testUri2 = Uri.parse("androidx://test2");
-        ListenableFuture<SessionResult> future1 =
-                mController.setMediaUri(testUri1, /* extras= */ null);
-        ListenableFuture<SessionResult> future2 =
-                mController.setMediaUri(testUri2, /* extras= */ null);
-
-        mController.prepare();
-
-        SessionResult result1 = future1.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        SessionResult result2 = future2.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(RESULT_INFO_SKIPPED, result1.getResultCode());
-        assertEquals(RESULT_SUCCESS, result2.getResultCode());
-    }
-
-    @Test
-    public void controllerCallback_onCurrentMediaItemChanged_byMetadataChange()
-            throws Exception {
-        final String testMediaId = "testControllerCallback_onCurrentMediaItemChanged_bySetMetadata";
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                    MediaItem item) {
-                MediaTestUtils.assertMediaItemHasId(item, testMediaId);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
-                .build();
-        mSession.setMetadata(metadata);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onCurrentMediaItemChanged_byActiveQueueItemChange()
-            throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createFileMediaItems(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        mSession.setQueue(testQueue);
-
-        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
-
-        // Set the current active queue item to index 'oldItemIndex'.
-        final int oldItemIndex = 0;
-        builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
-        mSession.setPlaybackState(builder.build());
-
-        final int newItemIndex = 1;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                    MediaItem item) {
-                MediaTestUtils.assertMediaIdEquals(testList.get(newItemIndex), item);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-
-        // The new playbackState will tell the controller that the active queue item is changed to
-        // 'newItemIndex'.
-        builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
-        mSession.setPlaybackState(builder.build());
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onSeekCompleted() throws Exception {
-        final long testSeekPosition = 400;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onSeekCompleted(@NonNull MediaController controller, long position) {
-                assertEquals(testSeekPosition, position);
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testSeekPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onBufferingCompleted() throws Exception {
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(1);
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                testPlaylist.get(0).getMetadata());
-
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        final long testBufferingPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                assertEquals(metadata.getDescription().getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setMetadata(metadata);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_BUFFERING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(0)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(testBufferingPosition)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onBufferingStarved() throws Exception {
-        final List<MediaItem> testPlaylist = MediaTestUtils.createFileMediaItems(1);
-        final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
-                testPlaylist.get(0).getMetadata());
-
-        final int testBufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
-        final long testBufferingPosition = 0;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onBufferingStateChanged(@NonNull MediaController controller,
-                    @NonNull MediaItem item, int state) {
-                assertEquals(metadata.getDescription().getMediaId(), item.getMediaId());
-                assertEquals(testBufferingState, state);
-                assertEquals(testBufferingState, controller.getBufferingState());
-                assertEquals(testBufferingPosition, controller.getBufferedPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setMetadata(metadata);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 100 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(500)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(0L);
-
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_BUFFERING, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .setBufferedPosition(testBufferingPosition)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlayerStateChanged() throws Exception {
-        final int testPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
-        final long testPosition = 500;
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-                assertEquals(testPlayerState, state);
-                assertEquals(testPlayerState, controller.getPlayerState());
-                assertEquals(testPosition, controller.getCurrentPosition());
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_NONE, 0 /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mController.setTimeDiff(Long.valueOf(0));
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, testPosition /* position */,
-                        1f /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlaybackSpeedChanged() throws Exception {
-        final float testSpeed = 3.0f;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
-                assertEquals(testSpeed, speed, 0.0f);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0 /* position */,
-                        testSpeed /* playbackSpeed */)
-                .build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToRemote()
-            throws Exception {
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        final int maxVolume = 100;
-        final int currentVolume = 45;
-
-        final AtomicReference<MediaController.PlaybackInfo> infoOut = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull MediaController.PlaybackInfo info) {
-                // Here, we are intentionally avoid using assertEquals(), since this callback
-                // can be called many times which of them have inaccurate values.
-                Log.d(TAG, "Given playbackType=" + info.getPlaybackType()
-                        + " controlType=" + info.getControlType()
-                        + " maxVolume=" + info.getMaxVolume()
-                        + " currentVolume=" + info.getCurrentVolume()
-                        + " audioAttrs=" + info.getAudioAttributes());
-                if (MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE == info.getPlaybackType()
-                        && volumeControlType == info.getControlType()
-                        && maxVolume == info.getMaxVolume()
-                        && currentVolume == info.getCurrentVolume()) {
-                    infoOut.set(info);
-                    latch.countDown();
-                }
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(infoOut.get(), mController.getPlaybackInfo());
-    }
-
-    @Test
-    public void controllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToLocal()
-            throws Exception {
-        if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
-            // In API 21 and 22, onAudioInfoChanged is not called.
-            return;
-        }
-        mSession.setPlaybackToRemote(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 100, 45);
-
-        final int testLocalStreamType = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(testLocalStreamType);
-        final int currentVolume = mAudioManager.getStreamVolume(testLocalStreamType);
-
-        final AtomicReference<MediaController.PlaybackInfo> infoOut = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                    @NonNull MediaController.PlaybackInfo info) {
-                assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                        info.getPlaybackType());
-                assertEquals(RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE, info.getControlType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                infoOut.set(info);
-                latch.countDown();
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackToLocal(testLocalStreamType);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(infoOut.get(), mController.getPlaybackInfo());
-    }
-
-    @Test
-    public void controllerCallback_onCustomCommand() throws Exception {
-        final String event = "testControllerCallback_onCustomCommand";
-        final Bundle extras = TestUtils.createTestBundle();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(event, command.getCustomAction());
-                assertTrue(TestUtils.equals(extras, args));
-                latch.countDown();
-                return null;
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.sendSessionEvent(event, extras);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onSetCustomLayout() throws Exception {
-        final CustomAction testCustomAction1 =
-                new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
-        final CustomAction testCustomAction2 =
-                new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public int onSetCustomLayout(@NonNull MediaController controller,
-                    @NonNull List<CommandButton> layout) {
-                assertEquals(1, layout.size());
-                CommandButton button = layout.get(0);
-
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(testCustomAction1.getAction(),
-                                button.getCommand().getCustomAction());
-                        assertEquals(testCustomAction1.getName(), button.getDisplayName());
-                        assertEquals(testCustomAction1.getIcon(), button.getIconResId());
-                        break;
-                    case 1:
-                        assertEquals(testCustomAction2.getAction(),
-                                button.getCommand().getCustomAction());
-                        assertEquals(testCustomAction2.getName(), button.getDisplayName());
-                        assertEquals(testCustomAction2.getIcon(), button.getIconResId());
-                        break;
-                }
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction1).build());
-        // onSetCustomLayout will be called when its connected
-        mController = createController(mSession.getSessionToken(), true, callback);
-        // onSetCustomLayout will be called again when the custom action in the playback state is
-        // changed.
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction2).build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onAllowedCommandChanged() throws Exception {
-        final CustomAction testCustomAction1 =
-                new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
-        final CustomAction testCustomAction2 =
-                new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(@NonNull MediaController controller,
-                    @NonNull SessionCommandGroup commands) {
-                assertFalse(commands.hasCommand(new SessionCommand(
-                        testCustomAction1.getAction(), testCustomAction1.getExtras())));
-                assertTrue(commands.hasCommand(new SessionCommand(
-                        testCustomAction2.getAction(), testCustomAction2.getExtras())));
-                latch.countDown();
-            }
-        };
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction1).build());
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .addCustomAction(testCustomAction2).build());
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_onConnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-    }
-
-    @Test
-    public void controllerCallback_onDisconnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mSession.release();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void controllerCallback_close() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mController.close();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void close_twice() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        mController.close();
-        mController.close();
-    }
-
-    @Test
-    public void isConnected() throws Exception {
-        mController = createController(mSession.getSessionToken());
-        assertTrue(mController.isConnected());
-
-        mSession.release();
-        waitForDisconnect(mController, true);
-        assertFalse(mController.isConnected());
-    }
-
-    @Test
-    public void close_beforeConnected() throws InterruptedException {
-        MediaController controller = createController(mSession.getSessionToken(), false, null);
-
-        // Should not crash.
-        controller.close();
-    }
-
-    @Test
-    public void controllerCallback_onCustomCommand_bySetCaptioningEnabled() throws Exception {
-        final String sessionCommandOnCaptioningEnabledChanged =
-                "android.media.session.command.ON_CAPTIONING_ENALBED_CHANGED";
-        final String argumentCaptioningEnabled = "androidx.media2.argument.CAPTIONING_ENABLED";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            @NonNull
-            public SessionResult onCustomCommand(@NonNull MediaController controller,
-                    @NonNull SessionCommand command, Bundle args) {
-                assertEquals(sessionCommandOnCaptioningEnabledChanged, command.getCustomAction());
-                assertEquals(true, args.getBoolean(argumentCaptioningEnabled, false));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        mController = createController(mSession.getSessionToken(), true, callback);
-        mSession.setCaptioningEnabled(true);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void constructorWithoutCallback() throws InterruptedException {
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionCompatToken(mSession.getSessionToken())
-                .build();
-        PollingCheck.waitFor(TIMEOUT_MS, () -> controller.isConnected());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
deleted file mode 100644
index db3719c..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED;
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SessionPlayer.TrackInfo;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaController.ControllerCallbackRunnable;
-import androidx.media2.session.MediaController.PlaybackInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.MediaTestUtils;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.CustomParcelable;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControllerTest extends MediaSessionTestBase {
-
-    static final String TAG = "MediaControllerTest";
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    final List<RemoteMediaSession> mRemoteSessionList = new ArrayList<>();
-
-    AudioManager mAudioManager;
-    RemoteMediaSession mRemoteSession;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mRemoteSession = createRemoteMediaSession(DEFAULT_TEST_NAME, null);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        for (int i = 0; i < mRemoteSessionList.size(); i++) {
-            RemoteMediaSession session = mRemoteSessionList.get(i);
-            if (session != null) {
-                session.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void builder() {
-        MediaController.Builder builder;
-
-        try {
-            builder = new MediaController.Builder(null);
-            fail("null context shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setSessionToken(null);
-            fail("null token shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setSessionCompatToken(null);
-            fail("null compat token shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            builder = new MediaController.Builder(mContext);
-            builder.setControllerCallback(null, null);
-            fail("null executor or null callback shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-
-        try {
-            Bundle connectionHints = new Bundle();
-            connectionHints.putParcelable("key", new CustomParcelable(1));
-            builder = new MediaController.Builder(mContext);
-            builder.setConnectionHints(connectionHints);
-            // TODO(b/220842943): Re-enable for T and beyond once the version of media2-session
-            // used in version-compat-tests/previous/client/build.gradle is one that includes
-            // https://r.android.com/1950077.
-            if (Build.VERSION.SDK_INT < 33) {
-                fail("custom parcelables shouldn't be allowed for connectionHints");
-            }
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mRemoteSession.getToken())
-                .setControllerCallback(sHandlerExecutor, new ControllerCallback() {})
-                .build();
-        controller.close();
-    }
-
-    @Test
-    public void getSessionActivity() throws InterruptedException {
-        RemoteMediaSession session = createRemoteMediaSession(TEST_GET_SESSION_ACTIVITY, null);
-
-        MediaController controller = createController(session.getToken());
-        PendingIntent sessionActivity = controller.getSessionActivity();
-        assertNotNull(sessionActivity);
-        if (Build.VERSION.SDK_INT >= 17) {
-            // PendingIntent#getCreatorPackage() is added in API 17.
-            assertEquals(SERVICE_PACKAGE_NAME, sessionActivity.getCreatorPackage());
-
-            // TODO: Add getPid/getUid in MediaControllerProviderService and compare them.
-            // assertEquals(mRemoteSession.getUid(), sessionActivity.getCreatorUid());
-        }
-        session.cleanUp();
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume_afterStreamTypeChanged() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        int oldStream = AudioManager.STREAM_MUSIC;
-        int volumeForOldStream = mAudioManager.getStreamVolume(oldStream);
-
-        int stream = AudioManager.STREAM_ALARM;
-        int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = createController(mRemoteSession.getToken(),
-                true /* waitForConnect */, null /* connectionHints */, new ControllerCallback() {
-                    @Override
-                    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-                            @NonNull PlaybackInfo info) {
-                        AudioAttributesCompat attrs = info.getAudioAttributes();
-                        if (attrs != null && attrs.getLegacyStreamType() == stream) {
-                            latch.countDown();
-                        }
-                    }
-                });
-
-        AudioAttributesCompat oldAttrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(oldStream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(oldAttrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        mRemoteSession.getMockPlayer().notifyAudioAttributesChanged(attrs);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        int originalVolume = mAudioManager.getStreamVolume(stream);
-        int targetVolume = originalVolume == minVolume ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        PollingCheck.waitFor(VOLUME_CHANGE_TIMEOUT_MS,
-                () -> targetVolume == mAudioManager.getStreamVolume(stream));
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-
-        assertEquals(volumeForOldStream, mAudioManager.getStreamVolume(oldStream));
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream).build();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void getPackageName() throws Exception {
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertEquals(SERVICE_PACKAGE_NAME, controller.getConnectedToken().getPackageName());
-    }
-
-    @Test
-    public void getTokenExtras() throws Exception {
-        Bundle testTokenExtras = TestUtils.createTestBundle();
-        RemoteMediaSession session = createRemoteMediaSession("testGetExtras", testTokenExtras);
-
-        MediaController controller = createController(session.getToken());
-        SessionToken connectedToken = controller.getConnectedToken();
-        assertNotNull(connectedToken);
-        assertTrue(TestUtils.equals(testTokenExtras, connectedToken.getExtras()));
-    }
-
-    @Test
-    public void isConnected() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertTrue(controller.isConnected());
-
-        mRemoteSession.close();
-        waitForDisconnect(controller, true);
-        assertFalse(controller.isConnected());
-    }
-
-    @Test
-    public void close_beforeConnected() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, null /* callback */);
-        controller.close();
-    }
-
-    @Test
-    public void close_twice() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken());
-        controller.close();
-        controller.close();
-    }
-
-    @Test
-    public void gettersAfterConnected() throws InterruptedException {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-        final float speed = 0.5f;
-        final long timeDiff = 102;
-        final MediaItem currentMediaItem = MediaTestUtils.createFileMediaItemWithMetadata();
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlayerState(state)
-                .setBufferingState(bufferingState)
-                .setCurrentPosition(position)
-                .setBufferedPosition(bufferedPosition)
-                .setPlaybackSpeed(speed)
-                .setCurrentMediaItem(currentMediaItem)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        controller.setTimeDiff(timeDiff);
-        assertEquals(state, controller.getPlayerState());
-        assertEquals(bufferedPosition, controller.getBufferedPosition());
-        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
-        assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
-        MediaTestUtils.assertNotMediaItemSubclass(controller.getCurrentMediaItem());
-        MediaTestUtils.assertMediaIdEquals(currentMediaItem, controller.getCurrentMediaItem());
-    }
-
-    @Test
-    public void getPlaybackInfo() throws Exception {
-        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setAudioAttributes(attrs)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        final MediaController controller = createController(mRemoteSession.getToken());
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-
-        int localVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            localVolumeControlType = VOLUME_CONTROL_FIXED;
-        }
-        assertEquals(localVolumeControlType, info.getControlType());
-        assertEquals(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
-                info.getMaxVolume());
-        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
-                info.getCurrentVolume());
-    }
-
-    @Test
-    public void getVideoSize() throws InterruptedException {
-        VideoSize testSize = new VideoSize(100, 42);
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setVideoSize(testSize)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-        MediaController controller = createController(mRemoteSession.getToken());
-        assertEquals(testSize, controller.getVideoSize());
-    }
-
-    @Test
-    public void getTracks() throws Exception {
-        List<SessionPlayer.TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setTrackInfo(testTracks)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        List<SessionPlayer.TrackInfo> testTracksFromController = controller.getTracks();
-        assertEquals(testTracks, testTracksFromController);
-    }
-
-    @Test
-    public void selectDeselectTrackAndGetSelectedTrack() throws Exception {
-        CountDownLatch selectTrackLatch = new CountDownLatch(1);
-        CountDownLatch deselectTrackLatch = new CountDownLatch(1);
-        AtomicReference<SessionPlayer.TrackInfo> selectedTrackRef = new AtomicReference<>();
-        AtomicReference<SessionPlayer.TrackInfo> deselectedTrackRef = new AtomicReference<>();
-
-        List<TrackInfo> testTracks = MediaTestUtils.createTrackInfoList();
-        TrackInfo testTrack = testTracks.get(2);
-        int testTrackType = testTrack.getTrackType();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setTrackInfo(testTracks)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-        MediaController controller = createController(mRemoteSession.getToken(), true, null,
-                new MediaController.ControllerCallback() {
-                    @Override
-                    public void onTrackSelected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        selectedTrackRef.set(trackInfo);
-                        selectTrackLatch.countDown();
-                    }
-
-                    @Override
-                    public void onTrackDeselected(@NonNull MediaController controller,
-                            @NonNull SessionPlayer.TrackInfo trackInfo) {
-                        deselectedTrackRef.set(trackInfo);
-                        deselectTrackLatch.countDown();
-                    }
-                });
-        assertNull(controller.getSelectedTrack(testTrackType));
-
-        controller.selectTrack(testTrack);
-        assertTrue(selectTrackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, selectedTrackRef.get());
-        assertEquals(testTrack, controller.getSelectedTrack(testTrackType));
-
-        controller.deselectTrack(testTrack);
-        assertTrue(deselectTrackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testTrack, deselectedTrackRef.get());
-        assertNull(controller.getSelectedTrack(testTrackType));
-    }
-
-    /**
-     * It tests {@link MediaController#registerExtraCallback(Executor, ControllerCallback)} and
-     * {@link MediaController#unregisterExtraCallback(ControllerCallback)}.
-     */
-    @Test
-    public void registerExtraCallback() throws InterruptedException {
-        MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, null);
-        ControllerCallback testCallback1 = new ControllerCallback() {};
-        ControllerCallback testCallback2 = new ControllerCallback() {};
-
-        List<Pair<ControllerCallback, Executor>> callbacks =
-                controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(0, callbacks.size());
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback1, callbacks.get(0).first);
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-
-        controller.unregisterExtraCallback(testCallback2);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-
-        controller.registerExtraCallback(sHandlerExecutor, testCallback2);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(2, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback1, callbacks.get(0).first);
-        assertNotNull(callbacks.get(1));
-        assertSame(testCallback2, callbacks.get(1).first);
-
-        controller.unregisterExtraCallback(testCallback1);
-        callbacks = controller.getExtraControllerCallbacks();
-        assertNotNull(callbacks);
-        assertEquals(1, callbacks.size());
-        assertNotNull(callbacks.get(0));
-        assertSame(testCallback2, callbacks.get(0).first);
-    }
-
-    @Test
-    public void notifyControllerCallback() throws InterruptedException {
-        final CountDownLatch primaryLatch = new CountDownLatch(1);
-        ControllerCallback primaryCallback = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                primaryLatch.countDown();
-            }
-        };
-        final CountDownLatch extraLatch1 = new CountDownLatch(1);
-        ControllerCallback extraCallback1 = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                extraLatch1.countDown();
-            }
-        };
-        final CountDownLatch extraLatch2 = new CountDownLatch(1);
-        ControllerCallback extraCallback2 = new ControllerCallback() {
-            @Override
-            public void onPlaybackCompleted(@NonNull MediaController controller) {
-                extraLatch2.countDown();
-            }
-        };
-        final MediaController controller = createController(mRemoteSession.getToken(),
-                false /* waitForConnect */, null, primaryCallback);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback1);
-        controller.registerExtraCallback(sHandlerExecutor, extraCallback2);
-        controller.notifyAllControllerCallbacks(new ControllerCallbackRunnable() {
-            @Override
-            public void run(@NonNull ControllerCallback callback) {
-                callback.onPlaybackCompleted(controller);
-            }
-        });
-        assertTrue(primaryLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(extraLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(extraLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void futuresCompleted_AllowedCommandsChange() throws Exception {
-        RemoteMediaSession session = mRemoteSession;
-        MediaController controller = createController(session.getToken());
-
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        SessionCommand fastForwardCommand = new SessionCommand(
-                SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD);
-        SessionCommand customCommand = new SessionCommand("custom", null);
-
-        int trials = 100;
-        CountDownLatch latch = new CountDownLatch(trials * 2);
-
-        for (int trial = 0; trial < trials; trial++) {
-            if (trial % 2 == 0) {
-                builder.addCommand(fastForwardCommand);
-                builder.addCommand(customCommand);
-            } else {
-                builder.removeCommand(fastForwardCommand);
-                builder.removeCommand(customCommand);
-            }
-            session.setAllowedCommands(builder.build());
-
-            controller.fastForward()
-                    .addListener(latch::countDown, Runnable::run);
-            controller.sendCustomCommand(customCommand, null)
-                    .addListener(latch::countDown, Runnable::run);
-        }
-
-        assertTrue("All futures should be completed", latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void play_returnsSessionResultWithMediaItem() throws Exception {
-        RemoteMediaSession session = mRemoteSession;
-        session.getMockPlayer().createAndSetFakePlaylist(/* size= */ 1);
-        session.getMockPlayer().setCurrentMediaItem(/* index= */ 0);
-
-        MediaController controller = createController(session.getToken());
-        SessionResult result = controller.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertNotNull(result.getMediaItem());
-    }
-
-    @Test
-    public void getPlaylistMetadata_returnsPlaylistMetadataOfPlayerInSession() throws Exception {
-        MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setPlaylistMetadata(testMetadata)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        MediaMetadata metadata = controller.getPlaylistMetadata();
-        assertEquals(testMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
-                metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
-    }
-
-    @Test
-    public void getBufferingState_returnsBufferingStateOfPlayerInSession() throws Exception {
-        int testBufferingState = SessionPlayer.BUFFERING_STATE_COMPLETE;
-        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
-                .setBufferingState(testBufferingState)
-                .build();
-        mRemoteSession.updatePlayer(playerConfig);
-
-        MediaController controller = createController(mRemoteSession.getToken());
-        int bufferingState = controller.getBufferingState();
-        assertEquals(testBufferingState, bufferingState);
-    }
-
-    RemoteMediaSession createRemoteMediaSession(String id, Bundle tokenExtras) {
-        RemoteMediaSession session = new RemoteMediaSession(id, mContext, tokenExtras);
-        mRemoteSessionList.add(session);
-        return session;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
deleted file mode 100644
index e284724..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
+++ /dev/null
@@ -1,135 +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.media2.test.client.tests;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.os.Build;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.client.SurfaceActivity;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaController#setSurface(Surface)}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaController_SurfaceTest extends MediaSessionTestBase {
-    private static final String TAG = "MC_SurfaceTest";
-
-    private Instrumentation mInstrumentation;
-    private SurfaceActivity mActivity;
-    private RemoteMediaSession mRemoteSession;
-
-    @Rule
-    public ActivityTestRule<SurfaceActivity> mActivityRule =
-            new ActivityTestRule<>(SurfaceActivity.class);
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.getActivity();
-
-        setKeepScreenOn();
-
-        mRemoteSession = new RemoteMediaSession(DEFAULT_TEST_NAME, mContext, null);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-
-        mRemoteSession.cleanUp();
-    }
-
-    @Test
-    public void setSurface() throws Exception {
-        MediaController controller = createController(mRemoteSession.getToken());
-
-        // Set
-        final Surface testSurface = mActivity.getSurfaceHolder().getSurface();
-        SessionResult result = controller.setSurface(testSurface)
-                .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertTrue(mRemoteSession.getMockPlayer().surfaceExists());
-
-        // Reset
-        result = controller.setSurface(null).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
-        assertFalse(mRemoteSession.getMockPlayer().surfaceExists());
-    }
-
-    private void setKeepScreenOn() throws Exception {
-        try {
-            setKeepScreenOnOrThrow();
-        } catch (Throwable tr) {
-            throw new Exception(tr);
-        }
-    }
-
-    private void setKeepScreenOnOrThrow() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (Build.VERSION.SDK_INT >= 27) {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                    mActivity.setTurnScreenOn(true);
-                    mActivity.setShowWhenLocked(true);
-                    KeyguardManager keyguardManager = (KeyguardManager)
-                            mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-                    keyguardManager.requestDismissKeyguard(mActivity, null);
-                } else {
-                    mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-                }
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
deleted file mode 100644
index ea17c74..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSessionTestBase {
-    static final int TIMEOUT_MS = 1000;
-    static final int BROWSER_COMPAT_CONNECT_TIMEOUT_MS = 3000;
-
-    static SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private Map<MediaController, TestBrowserCallback> mControllers = new HashMap<>();
-
-    interface TestControllerCallbackInterface {
-        void waitForConnect(boolean expect) throws InterruptedException;
-        void waitForDisconnect(boolean expect) throws InterruptedException;
-        void setRunnableForOnCustomCommand(Runnable runnable);
-    }
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    SyncHandler handler;
-                    synchronized (MediaSessionTestBase.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (MediaController controller : mControllers.keySet()) {
-            controller.close();
-        }
-        mControllers.clear();
-    }
-
-    final MediaController createController(@NonNull MediaSessionCompat.Token token)
-            throws InterruptedException {
-        return createController(token, true, null);
-    }
-
-    final MediaController createController(@NonNull MediaSessionCompat.Token token,
-            boolean waitForConnect, @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestBrowserCallback testCallback = new TestBrowserCallback(callback);
-        MediaController controller = onCreateController(token, testCallback);
-        mControllers.put(controller, testCallback);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    final MediaController createController(@NonNull SessionToken token)
-            throws InterruptedException {
-        return createController(token, true, null, null);
-    }
-
-    final MediaController createController(@NonNull SessionToken token,
-            boolean waitForConnect, @Nullable Bundle connectionHints,
-            @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestBrowserCallback testCallback = new TestBrowserCallback(callback);
-        MediaController controller = onCreateController(token, connectionHints, testCallback);
-        mControllers.put(controller, testCallback);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    final TestControllerCallbackInterface getTestControllerCallbackInterface(
-            MediaController controller) {
-        return mControllers.get(controller);
-    }
-
-    final void waitForConnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForConnect(expected);
-    }
-
-    final void waitForDisconnect(MediaController controller, boolean expected)
-            throws InterruptedException {
-        getTestControllerCallbackInterface(controller).waitForDisconnect(expected);
-    }
-
-    final void setRunnableForOnCustomCommand(MediaController controller,
-            Runnable runnable) {
-        getTestControllerCallbackInterface(controller).setRunnableForOnCustomCommand(runnable);
-    }
-
-    MediaController onCreateController(@NonNull final MediaSessionCompat.Token token,
-            @NonNull final TestBrowserCallback callback) throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                controller.set(new MediaController.Builder(mContext)
-                        .setSessionCompatToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback)
-                        .build());
-            }
-        });
-        return controller.get();
-    }
-
-    MediaController onCreateController(@NonNull final SessionToken token,
-            @Nullable final Bundle connectionHints, @NonNull final TestBrowserCallback callback)
-            throws InterruptedException {
-        final AtomicReference<MediaController> controller = new AtomicReference<>();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                // Create controller on the test handler, for changing MediaBrowserCompat's Handler
-                // Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
-                // and commands wouldn't be run if tests codes waits on the test handler.
-                MediaController.Builder builder = new MediaController.Builder(mContext)
-                        .setSessionToken(token)
-                        .setControllerCallback(sHandlerExecutor, callback);
-                if (connectionHints != null) {
-                    builder.setConnectionHints(connectionHints);
-                }
-                controller.set(builder.build());
-            }
-        });
-        return controller.get();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java
deleted file mode 100644
index c49ed88..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionCompatTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.Context;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.media2.test.client.RemoteMediaSessionCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test {@link RemoteMediaSessionCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaSessionCompatTest {
-
-    private Context mContext;
-    private RemoteMediaSessionCompat mRemoteSessionCompat;
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mRemoteSessionCompat = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, mContext);
-    }
-
-    @After
-    public void cleanUp() {
-        mRemoteSessionCompat.cleanUp();
-    }
-
-    @Test
-    @SmallTest
-    public void gettingToken() {
-        MediaSessionCompat.Token token = mRemoteSessionCompat.getSessionToken();
-        assertNotNull(token);
-    }
-
-    @Test
-    @SmallTest
-    public void creatingControllerCompat() throws Exception {
-        MediaSessionCompat.Token token = mRemoteSessionCompat.getSessionToken();
-        assertNotNull(token);
-        MediaControllerCompat controller = new MediaControllerCompat(mContext, token);
-        assertEquals(SERVICE_PACKAGE_NAME, controller.getPackageName());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java
deleted file mode 100644
index 2a3fe9c..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/RemoteMediaSessionTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.client.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.client.RemoteMediaSession;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.Executor;
-
-/** Test {@link RemoteMediaSession}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaSessionTest {
-
-    private Context mContext;
-    private RemoteMediaSession mRemoteSession2;
-    private Bundle mTokenExtras;
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mTokenExtras = TestUtils.createTestBundle();
-        mRemoteSession2 = new RemoteMediaSession(DEFAULT_TEST_NAME, mContext, mTokenExtras);
-    }
-
-    @After
-    public void cleanUp() {
-        if (mRemoteSession2 != null) {
-            mRemoteSession2.cleanUp();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void gettingToken() {
-        SessionToken token = mRemoteSession2.getToken();
-        assertNotNull(token);
-        assertEquals(SERVICE_PACKAGE_NAME, token.getPackageName());
-        assertTrue(TestUtils.equals(mTokenExtras, token.getExtras()));
-    }
-
-    @Test
-    @SmallTest
-    public void creatingController() {
-        SessionToken token = mRemoteSession2.getToken();
-        assertNotNull(token);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(token)
-                .setControllerCallback(new Executor() {
-                    @Override
-                    public void execute(Runnable command) {
-                        command.run();
-                    }
-                }, new MediaController.ControllerCallback() {})
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
deleted file mode 100644
index dee07c0..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
+++ /dev/null
@@ -1,242 +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.media2.test.client.tests;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.os.Bundle;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaBrowser.BrowserCallback;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaController.ControllerCallback;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.session.MediaSession.CommandButton;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.client.tests.MediaSessionTestBase.TestControllerCallbackInterface;
-import androidx.media2.test.common.TestUtils;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A proxy class for {@link BrowserCallback} which implements
- * {@link TestControllerCallbackInterface}.
- */
-public class TestBrowserCallback extends BrowserCallback
-        implements TestControllerCallbackInterface {
-
-    public final ControllerCallback mCallbackProxy;
-    public final CountDownLatch connectLatch = new CountDownLatch(1);
-    public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-    @GuardedBy("this")
-    private Runnable mOnCustomCommandRunnable;
-
-    TestBrowserCallback(@Nullable ControllerCallback callbackProxy) {
-        mCallbackProxy = callbackProxy == null ? new BrowserCallback() {} : callbackProxy;
-    }
-
-    @CallSuper
-    @Override
-    public void onConnected(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        connectLatch.countDown();
-        mCallbackProxy.onConnected(controller, commands);
-    }
-
-    @CallSuper
-    @Override
-    public void onDisconnected(@NonNull MediaController controller) {
-        disconnectLatch.countDown();
-        mCallbackProxy.onDisconnected(controller);
-    }
-
-    @Override
-    public void waitForConnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(connectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(connectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Override
-    public void waitForDisconnect(boolean expect) throws InterruptedException {
-        if (expect) {
-            assertTrue(disconnectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } else {
-            assertFalse(disconnectLatch.await(
-                    TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @NonNull
-    @Override
-    public SessionResult onCustomCommand(@NonNull MediaController controller,
-            @NonNull SessionCommand command, Bundle args) {
-        synchronized (this) {
-            if (mOnCustomCommandRunnable != null) {
-                mOnCustomCommandRunnable.run();
-            }
-        }
-        return mCallbackProxy.onCustomCommand(controller, command, args);
-    }
-
-    @Override
-    public void onPlaybackInfoChanged(@NonNull MediaController controller,
-            @NonNull MediaController.PlaybackInfo info) {
-        mCallbackProxy.onPlaybackInfoChanged(controller, info);
-    }
-
-    @Override
-    public int onSetCustomLayout(@NonNull MediaController controller,
-            @NonNull List<CommandButton> layout) {
-        return mCallbackProxy.onSetCustomLayout(controller, layout);
-    }
-
-    @Override
-    public void onAllowedCommandsChanged(@NonNull MediaController controller,
-            @NonNull SessionCommandGroup commands) {
-        mCallbackProxy.onAllowedCommandsChanged(controller, commands);
-    }
-
-    @Override
-    public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
-        mCallbackProxy.onPlayerStateChanged(controller, state);
-    }
-
-    @Override
-    public void onSeekCompleted(@NonNull MediaController controller, long position) {
-        mCallbackProxy.onSeekCompleted(controller, position);
-    }
-
-    @Override
-    public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
-        mCallbackProxy.onPlaybackSpeedChanged(controller, speed);
-    }
-
-    @Override
-    public void onBufferingStateChanged(@NonNull MediaController controller,
-            @NonNull MediaItem item, int state) {
-        mCallbackProxy.onBufferingStateChanged(controller, item, state);
-    }
-
-    @Override
-    public void onCurrentMediaItemChanged(@NonNull MediaController controller, MediaItem item) {
-        mCallbackProxy.onCurrentMediaItemChanged(controller, item);
-    }
-
-    @Override
-    public void onPlaylistChanged(@NonNull MediaController controller,
-            List<MediaItem> list, MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistChanged(controller, list, metadata);
-    }
-
-    @Override
-    public void onPlaylistMetadataChanged(@NonNull MediaController controller,
-            MediaMetadata metadata) {
-        mCallbackProxy.onPlaylistMetadataChanged(controller, metadata);
-    }
-
-    @Override
-    public void onShuffleModeChanged(@NonNull MediaController controller, int shuffleMode) {
-        mCallbackProxy.onShuffleModeChanged(controller, shuffleMode);
-    }
-
-    @Override
-    public void onRepeatModeChanged(@NonNull MediaController controller, int repeatMode) {
-        mCallbackProxy.onRepeatModeChanged(controller, repeatMode);
-    }
-
-    @Override
-    public void onPlaybackCompleted(@NonNull MediaController controller) {
-        mCallbackProxy.onPlaybackCompleted(controller);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, item, videoSize);
-    }
-
-    @Override
-    public void onVideoSizeChanged(@NonNull MediaController controller,
-            @NonNull VideoSize videoSize) {
-        mCallbackProxy.onVideoSizeChanged(controller, videoSize);
-    }
-
-    @Override
-    public void onTracksChanged(@NonNull MediaController controller,
-            @NonNull List<SessionPlayer.TrackInfo> tracks) {
-        mCallbackProxy.onTracksChanged(controller, tracks);
-    }
-
-    @Override
-    public void onTrackSelected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackSelected(controller, trackInfo);
-    }
-
-    @Override
-    public void onTrackDeselected(@NonNull MediaController controller,
-            @NonNull SessionPlayer.TrackInfo trackInfo) {
-        mCallbackProxy.onTrackDeselected(controller, trackInfo);
-    }
-
-    @Override
-    public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
-            @NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
-        mCallbackProxy.onSubtitleData(controller, item, track, data);
-    }
-
-    @Override
-    public void onChildrenChanged(@NonNull MediaBrowser browser, @NonNull String parentId,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onChildrenChanged(
-                browser, parentId, itemCount, params);
-    }
-
-    @Override
-    public void onSearchResultChanged(@NonNull MediaBrowser browser, @NonNull String query,
-            int itemCount, @Nullable MediaLibraryService.LibraryParams params) {
-        ((BrowserCallback) mCallbackProxy).onSearchResultChanged(
-                browser, query, itemCount, params);
-    }
-
-    @Override
-    public void setRunnableForOnCustomCommand(Runnable runnable) {
-        synchronized (this) {
-            mOnCustomCommandRunnable = runnable;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/res/layout/activity_surface.xml b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/res/layout/activity_surface.xml
deleted file mode 100644
index 7945f97..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/res/layout/activity_surface.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:keepScreenOn="true">
-    <SurfaceView
-        android:id="@+id/surface_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </SurfaceView>
-</LinearLayout>
diff --git a/media2/media2-session/version-compat-tests/previous/service/build.gradle b/media2/media2-session/version-compat-tests/previous/service/build.gradle
deleted file mode 100644
index 001262f..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/build.gradle
+++ /dev/null
@@ -1,37 +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.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    androidTestImplementation("androidx.media2:media2-session:1.2.0")
-    androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-}
-
-android {
-    namespace "androidx.media2.test.service"
-}
-
-androidx {
-    failOnDeprecationWarnings = false
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
deleted file mode 100644
index 1b23246..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TIMEOUT_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index d06e43a..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application>
-        <receiver
-            android:name="androidx.media.session.MediaButtonReceiver"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
-            </intent-filter>
-        </receiver>
-
-        <service
-            android:name="androidx.media2.test.service.MediaSessionProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA2_SESSION" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MediaSessionCompatProviderService"
-            android:exported="true">
-            <intent-filter>
-                <!-- Keep sync with CommonConstants.java -->
-                <action android:name="androidx.media.test.action.MEDIA_SESSION_COMPAT" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaSessionService"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaSessionService" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaLibraryService"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="androidx.media2.session.MediaLibraryService" />
-            </intent-filter>
-        </service>
-
-        <service
-            android:name="androidx.media2.test.service.MockMediaBrowserServiceCompat"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
-            </intent-filter>
-        </service>
-
-    </application>
-
-    <queries>
-        <package android:name="androidx.media2.test.client.test" />
-    </queries>
-
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-</manifest>
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java
deleted file mode 100644
index 7c1e23d..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionCompatProviderService.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_METADATA_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_QUEUE;
-import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.test.common.IRemoteMediaSessionCompat;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * A Service that creates {@link MediaSessionCompat} and calls its methods according to the
- * client app's requests.
- */
-public class MediaSessionCompatProviderService extends Service {
-    private static final String TAG = "MediaSessionCompatProviderService";
-
-    Map<String, MediaSessionCompat> mSessionMap = new HashMap<>();
-    RemoteMediaSessionCompatStub mSessionBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mSessionBinder = new RemoteMediaSessionCompatStub();
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA_SESSION_COMPAT.equals(intent.getAction())) {
-            return mSessionBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaSessionCompat session : mSessionMap.values()) {
-            session.release();
-        }
-    }
-
-    private class RemoteMediaSessionCompatStub extends IRemoteMediaSessionCompat.Stub {
-        @Override
-        public void create(final String sessionTag) throws RemoteException {
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaSessionCompat session = new MediaSessionCompat(
-                                MediaSessionCompatProviderService.this, sessionTag);
-                        mSessionMap.put(sessionTag, session);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaSessionCompat", ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaSessionCompat methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public Bundle getSessionToken(String sessionTag) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            Bundle result = new Bundle();
-            result.putParcelable(KEY_SESSION_COMPAT_TOKEN, session.getSessionToken());
-            return result;
-        }
-
-        @Override
-        public void setPlaybackToLocal(String sessionTag, int stream) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setPlaybackToLocal(stream);
-        }
-
-        @Override
-        public void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume,
-                int currentVolume) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setPlaybackToRemote(new VolumeProviderCompat(
-                    volumeControl, maxVolume, currentVolume) {
-                @Override
-                public void onSetVolumeTo(int volume) {
-                    setCurrentVolume(volume);
-                }
-
-                @Override
-                public void onAdjustVolume(int direction) {
-                    setCurrentVolume(getCurrentVolume() + direction);
-                }
-            });
-        }
-
-        @Override
-        public void release(String sessionTag) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.release();
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setPlaybackState(String sessionTag, Bundle stateBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            stateBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            PlaybackStateCompat state = stateBundle.getParcelable(KEY_PLAYBACK_STATE_COMPAT);
-            session.setPlaybackState(state);
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setMetadata(String sessionTag, Bundle metadataBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            metadataBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            MediaMetadataCompat metadata = metadataBundle.getParcelable(KEY_METADATA_COMPAT);
-            session.setMetadata(metadata);
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void setQueue(String sessionTag, Bundle queueBundle) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            queueBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            List<QueueItem> queue = queueBundle.getParcelableArrayList(KEY_QUEUE);
-            session.setQueue(queue);
-        }
-
-        @Override
-        public void setQueueTitle(String sessionTag, CharSequence title) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setQueueTitle(title);
-        }
-
-        @Override
-        public void setRepeatMode(String sessionTag, int repeatMode) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setRepeatMode(repeatMode);
-        }
-
-        @Override
-        public void setShuffleMode(String sessionTag, int shuffleMode) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setShuffleMode(shuffleMode);
-        }
-
-        @Override
-        public void setSessionActivity(String sessionTag, PendingIntent pi) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setSessionActivity(pi);
-        }
-
-        @Override
-        public void setFlags(String sessionTag, int flags) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setFlags(flags);
-        }
-
-        @Override
-        public void setRatingType(String sessionTag, int type) throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setRatingType(type);
-        }
-
-        @Override
-        public void sendSessionEvent(String sessionTag, String event, Bundle extras)
-                throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.sendSessionEvent(event, extras);
-        }
-
-        @Override
-        public void setCaptioningEnabled(String sessionTag, boolean enabled)
-                throws RemoteException {
-            MediaSessionCompat session = mSessionMap.get(sessionTag);
-            session.setCaptioningEnabled(enabled);
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
deleted file mode 100644
index 1ebbb876..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
+++ /dev/null
@@ -1,603 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_SESSION;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_NULL_ITEM;
-import static androidx.media2.test.common.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERED_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_BUFFERING_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_POSITION;
-import static androidx.media2.test.common.CommonConstants.KEY_CURRENT_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MAX_VOLUME;
-import static androidx.media2.test.common.CommonConstants.KEY_MEDIA_ITEM;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYBACK_SPEED;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
-import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
-import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
-import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
-import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
-import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
-import static androidx.media2.test.common.MediaSessionConstants.TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.ParcelImplListSlice;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.IRemoteMediaSession;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * A Service that creates {@link MediaSession} and calls its methods according to the client app's
- * requests.
- */
-public class MediaSessionProviderService extends Service {
-    private static final String TAG = "MediaSessionProviderService";
-
-    Map<String, MediaSession> mSessionMap = new HashMap<>();
-    RemoteMediaSessionStub mSessionBinder;
-
-    SyncHandler mHandler;
-    Executor mExecutor;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mSessionBinder = new RemoteMediaSessionStub();
-        mHandler = new SyncHandler(getMainLooper());
-        mExecutor = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                mHandler.post(command);
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION_MEDIA2_SESSION.equals(intent.getAction())) {
-            return mSessionBinder;
-        }
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        for (MediaSession session : mSessionMap.values()) {
-            session.close();
-        }
-    }
-
-    private class RemoteMediaSessionStub extends IRemoteMediaSession.Stub {
-        @Override
-        public void create(final String sessionId, final Bundle tokenExtras)
-                throws RemoteException {
-            final MediaSession.Builder builder =
-                    new MediaSession.Builder(MediaSessionProviderService.this, new MockPlayer(0))
-                            .setId(sessionId);
-
-            if (tokenExtras != null) {
-                builder.setExtras(tokenExtras);
-            }
-
-            switch (sessionId) {
-                case TEST_GET_SESSION_ACTIVITY: {
-                    final Intent sessionActivity = new Intent(MediaSessionProviderService.this,
-                            MockActivity.class);
-                    PendingIntent pendingIntent = PendingIntent.getActivity(
-                            MediaSessionProviderService.this,
-                            0 /* requestCode */,
-                            sessionActivity,
-                            Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-                    builder.setSessionActivity(pendingIntent);
-                    break;
-                }
-                case TEST_CONTROLLER_CALLBACK_SESSION_REJECTS: {
-                    builder.setSessionCallback(mExecutor, new MediaSession.SessionCallback() {
-                        @Override
-                        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                                @NonNull MediaSession.ControllerInfo controller) {
-                            return null;
-                        }
-                    });
-                    break;
-                }
-                case TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST: {
-                    builder.setSessionCallback(mExecutor, new MediaSession.SessionCallback() {
-                        @Override
-                        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                                @NonNull MediaSession.ControllerInfo controller) {
-                            SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                                    .addCommand(new SessionCommand(
-                                            SessionCommand
-                                                    .COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA))
-                                    .build();
-                            return commands;
-                        }
-                    });
-                    break;
-                }
-            }
-
-            try {
-                mHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        MediaSession session = builder.build();
-                        mSessionMap.put(sessionId, session);
-                    }
-                });
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException occurred while creating MediaSession", ex);
-            }
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MediaSession methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public ParcelImpl getToken(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            return session != null
-                    ? MediaParcelUtils.toParcelable(session.getToken()) : null;
-        }
-
-        @Override
-        public Bundle getCompatToken(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            return session.getSessionCompat().getSessionToken().toBundle();
-        }
-
-        @Override
-        public void updatePlayer(String sessionId, @NonNull Bundle config) throws RemoteException {
-            config.setClassLoader(MediaSession.class.getClassLoader());
-            if (config != null) {
-                config.setClassLoader(MediaSession.class.getClassLoader());
-            }
-            MediaSession session = mSessionMap.get(sessionId);
-            session.updatePlayer(createMockPlayer(config));
-        }
-
-        @SuppressWarnings("deprecation")
-        private SessionPlayer createMockPlayer(Bundle config) {
-            SessionPlayer player;
-            if (config.containsKey(KEY_VOLUME_CONTROL_TYPE)) {
-                // Remote player
-                player = new MockRemotePlayer(
-                        config.getInt(KEY_VOLUME_CONTROL_TYPE),
-                        config.getInt(KEY_MAX_VOLUME),
-                        config.getInt(KEY_CURRENT_VOLUME));
-            } else {
-                // Local player
-                MockPlayer localPlayer = new MockPlayer(0);
-                localPlayer.mLastPlayerState = config.getInt(KEY_PLAYER_STATE);
-                localPlayer.mLastBufferingState = config.getInt(KEY_BUFFERING_STATE);
-                localPlayer.mCurrentPosition = config.getLong(KEY_CURRENT_POSITION);
-                localPlayer.mBufferedPosition = config.getLong(KEY_BUFFERED_POSITION);
-                localPlayer.mPlaybackSpeed = config.getFloat(KEY_PLAYBACK_SPEED);
-                localPlayer.mShuffleMode = config.getInt(KEY_SHUFFLE_MODE);
-                localPlayer.mRepeatMode = config.getInt(KEY_REPEAT_MODE);
-
-                ParcelImplListSlice listSlice = config.getParcelable(KEY_PLAYLIST);
-                if (listSlice != null) {
-                    localPlayer.mPlaylist = MediaTestUtils.convertToMediaItems(listSlice.getList());
-                }
-                localPlayer.mCurrentMediaItem =
-                        MediaTestUtils.convertToMediaItem(config.getParcelable(KEY_MEDIA_ITEM));
-                localPlayer.mMetadata = ParcelUtils.getVersionedParcelable(config,
-                        KEY_PLAYLIST_METADATA);
-                ParcelImpl videoSize = config.getParcelable(KEY_VIDEO_SIZE);
-                if (videoSize != null) {
-                    localPlayer.mVideoSize = MediaParcelUtils.fromParcelable(videoSize);
-                }
-                List<SessionPlayer.TrackInfo> trackInfos =
-                        ParcelUtils.getVersionedParcelableList(config, KEY_TRACK_INFO);
-                localPlayer.mTracks = trackInfos;
-                player = localPlayer;
-            }
-            ParcelImpl attrImpl = config.getParcelable(KEY_AUDIO_ATTRIBUTES);
-            if (attrImpl != null) {
-                AudioAttributesCompat attr = MediaParcelUtils.fromParcelable(attrImpl);
-                if (attr != null) {
-                    player.setAudioAttributes(attr);
-                }
-            }
-            return player;
-        }
-
-        @Override
-        public void broadcastCustomCommand(String sessionId, ParcelImpl command, Bundle args)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            session.broadcastCustomCommand(
-                    (SessionCommand) MediaParcelUtils.fromParcelable(command), args);
-        }
-
-        @Override
-        public void sendCustomCommand(String sessionId, Bundle controller, ParcelImpl command,
-                Bundle args) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            session.sendCustomCommand(info,
-                    (SessionCommand) MediaParcelUtils.fromParcelable(command),
-                    args);
-        }
-
-        @Override
-        public void close(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            session.close();
-        }
-
-        @Override
-        public void setAllowedCommands(String sessionId, Bundle controller, ParcelImpl commands)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            session.setAllowedCommands(info,
-                    (SessionCommandGroup) MediaParcelUtils.fromParcelable(commands));
-        }
-
-        @Override
-        public void setCustomLayout(String sessionId, Bundle controller, List<ParcelImpl> layout)
-                throws RemoteException {
-            if (layout == null) {
-                return;
-            }
-            MediaSession session = mSessionMap.get(sessionId);
-            ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
-            List<MediaSession.CommandButton> buttons = new ArrayList<>();
-            for (ParcelImpl parcel : layout) {
-                if (parcel != null) {
-                    buttons.add((MediaSession.CommandButton) ParcelUtils.fromParcelable(parcel));
-                }
-            }
-            session.setCustomLayout(info, buttons);
-        }
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MockPlayer methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void setPlayerState(String sessionId, int state) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mLastPlayerState = state;
-        }
-
-        @Override
-        public void setCurrentPosition(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentPosition = pos;
-        }
-
-        @Override
-        public void setBufferedPosition(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mBufferedPosition = pos;
-        }
-
-        @Override
-        public void setDuration(String sessionId, long duration) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mDuration = duration;
-        }
-
-        @Override
-        public void setPlaybackSpeed(String sessionId, float speed) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mPlaybackSpeed = speed;
-        }
-
-        @Override
-        public void notifySeekCompleted(String sessionId, long pos) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifySeekCompleted(pos);
-        }
-
-        @Override
-        public void notifyBufferingStateChanged(String sessionId, int itemIndex, int buffState)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyBufferingStateChanged(
-                    player.getPlaylist().get(itemIndex), buffState);
-        }
-
-        @Override
-        public void notifyPlayerStateChanged(String sessionId, int state) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlayerStateChanged(state);
-        }
-
-        @Override
-        public void notifyPlaybackSpeedChanged(String sessionId, float speed)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaybackSpeedChanged(speed);
-        }
-
-        @Override
-        public void notifyCurrentMediaItemChanged(String sessionId, int index)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            switch (index) {
-                case INDEX_FOR_UNKONWN_ITEM:
-                    player.notifyCurrentMediaItemChanged(
-                            new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1)).build());
-                    break;
-                case INDEX_FOR_NULL_ITEM:
-                    player.notifyCurrentMediaItemChanged(null);
-                    break;
-                default:
-                    player.notifyCurrentMediaItemChanged(
-                            player.getPlaylist().get(index));
-                    break;
-            }
-        }
-
-        @Override
-        public void notifyAudioAttributesChanged(String sessionId, ParcelImpl attrs)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyAudioAttributesChanged(
-                    (AudioAttributesCompat) MediaParcelUtils.fromParcelable(attrs));
-        }
-
-        @Override
-        public void notifyTrackInfoChanged(String sessionId, List<ParcelImpl> trackInfoParcelList)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            List<SessionPlayer.TrackInfo> tracks =
-                    MediaParcelUtils.fromParcelableList(trackInfoParcelList);
-            player.notifyTracksChanged(tracks);
-        }
-
-        @Override
-        public void notifyTrackSelected(String sessionId, ParcelImpl trackInfo)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyTrackSelected(
-                    (SessionPlayer.TrackInfo) MediaParcelUtils.fromParcelable(trackInfo));
-        }
-
-        @Override
-        public void notifyTrackDeselected(String sessionId, ParcelImpl trackInfo)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyTrackDeselected(
-                    (SessionPlayer.TrackInfo) MediaParcelUtils.fromParcelable(trackInfo));
-        }
-
-
-        ////////////////////////////////////////////////////////////////////////////////
-        // MockPlaylistAgent methods
-        ////////////////////////////////////////////////////////////////////////////////
-
-        @Override
-        public void setPlaylist(String sessionId, List<ParcelImpl> playlist)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mPlaylist = MediaTestUtils.convertToMediaItems(playlist);
-        }
-
-        @Override
-        public void setCurrentMediaItemMetadata(String sessionId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentMediaItem.setMetadata(MediaParcelUtils.fromParcelable(metadata));
-        }
-
-        @Override
-        public void createAndSetFakePlaylist(String sessionId, int size) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-
-            List<MediaItem> list = new ArrayList<>();
-            for (int i = 0; i < size; i++) {
-                list.add(new MediaItem.Builder()
-                        .setMetadata(new MediaMetadata.Builder()
-                                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                        TestUtils.getMediaIdInFakeList(i)).build())
-                        .build());
-            }
-            player.mPlaylist = list;
-        }
-
-        @Override
-        public void setPlaylistWithFakeItem(String sessionId, List<ParcelImpl> playlist)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-
-            List<MediaItem> list = new ArrayList<>();
-            for (ParcelImpl parcel : playlist) {
-                MediaItem item = MediaParcelUtils.fromParcelable(parcel);
-                list.add(new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                        .setMetadata(item.getMetadata())
-                        .build());
-            }
-            player.mPlaylist = list;
-        }
-
-        @Override
-        public void setPlaylistMetadata(String sessionId, ParcelImpl metadata)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mMetadata = MediaParcelUtils.fromParcelable(metadata);
-        }
-
-        @Override
-        public void setPlaylistMetadataWithLargeBitmaps(String sessionId, int count, int width,
-                int height) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
-            MediaMetadata.Builder builder = new MediaMetadata.Builder();
-            for (int i = 0; i < count; i++) {
-                builder.putBitmap(TestUtils.getMediaIdInFakeList(i), bitmap);
-            }
-            player.mMetadata = builder.build();
-        }
-
-        @Override
-        public void setShuffleMode(String sessionId, int shuffleMode)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mShuffleMode = shuffleMode;
-        }
-
-        @Override
-        public void setRepeatMode(String sessionId, int repeatMode) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mRepeatMode = repeatMode;
-        }
-
-        @Override
-        public void setCurrentMediaItem(String sessionId, int index)
-                throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.mCurrentMediaItem = player.mPlaylist.get(index);
-        }
-
-        @Override
-        public void notifyPlaylistChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaylistChanged();
-        }
-
-        @Override
-        public void notifyPlaylistMetadataChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaylistMetadataChanged();
-        }
-
-        @Override
-        public void notifyShuffleModeChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyShuffleModeChanged();
-        }
-
-        @Override
-        public void notifyRepeatModeChanged(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyRepeatModeChanged();
-        }
-
-        @Override
-        public void notifyPlaybackCompleted(String sessionId) throws RemoteException {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            player.notifyPlaybackCompleted();
-        }
-
-        @Override
-        public void notifyVideoSizeChanged(String sessionId, ParcelImpl videoSize) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            VideoSize videoSizeObj = MediaParcelUtils.fromParcelable(videoSize);
-            player.notifyVideoSizeChanged(videoSizeObj);
-        }
-
-        @Override
-        public boolean surfaceExists(String sessionId) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            return player.surfaceExists();
-        }
-
-        @Override
-        public void notifySubtitleData(String sessionId, ParcelImpl item, ParcelImpl track,
-                ParcelImpl data) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockPlayer player = (MockPlayer) session.getPlayer();
-            MediaItem itemObj = MediaParcelUtils.fromParcelable(item);
-            SessionPlayer.TrackInfo trackObj = MediaParcelUtils.fromParcelable(track);
-            SubtitleData dataObj = MediaParcelUtils.fromParcelable(data);
-            player.notifySubtitleData(itemObj, trackObj, dataObj);
-        }
-
-        @Override
-        public void notifyVolumeChanged(String sessionId, int volume) {
-            MediaSession session = mSessionMap.get(sessionId);
-            MockRemotePlayer player = (MockRemotePlayer) session.getPlayer();
-            player.mCurrentVolume = volume;
-            player.notifyVolumeChanged();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
deleted file mode 100644
index 0550226..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.CommonConstants.KEY_CLIENT_VERSION;
-import static androidx.media2.test.common.CommonConstants.VERSION_TOT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.versionedparcelable.ParcelImpl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Utilities for tests.
- */
-public final class MediaTestUtils {
-
-    private static final String TAG = "MediaTestUtils";
-
-    // Temporaily commenting out, since we don't have the Mock services yet.
-//    /**
-//     * Finds the session with id in this test package.
-//     *
-//     * @param context
-//     * @param id
-//     * @return
-//     */
-//    public static SessionToken getServiceToken(Context context, String id) {
-//        switch (id) {
-//            case MockMediaSessionService2.ID:
-//                return new SessionToken(context, new ComponentName(
-//                        context.getPackageName(), MockMediaSessionService2.class.getName()));
-//            case MockMediaLibraryService.ID:
-//                return new SessionToken(context, new ComponentName(
-//                        context.getPackageName(), MockMediaLibraryService.class.getName()));
-//        }
-//        fail("Unknown id=" + id);
-//        return null;
-//    }
-
-    /**
-     * Create a playlist for testing purpose
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size list size
-     * @return the newly created playlist
-     */
-    public static List<MediaItem> createPlaylist(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(createMediaItem(caller + "_item_" + (i + 1)));
-        }
-        return list;
-    }
-
-    public static MediaItem createMediaItem(String id) {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(new MediaMetadata.Builder()
-                        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id)
-                        .putLong(MediaMetadata.METADATA_KEY_BROWSABLE,
-                                MediaMetadata.BROWSABLE_TYPE_NONE)
-                        .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                        .build())
-                .build();
-    }
-
-    public static List<String> createMediaIds(int size) {
-        final List<String> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(caller + "_item_" + (i + 1));
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem createMediaItemWithMetadata() {
-        return new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
-                .setMetadata(createMetadata())
-                .build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
-        return new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(MediaMetadata.METADATA_KEY_BROWSABLE, MediaMetadata.BROWSABLE_TYPE_NONE)
-                .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                .build();
-    }
-
-    /**
-     * Converts the List of {@link ParcelImpl} to the list of {@link MediaItem} with the
-     * {@link #convertToMediaItem(ParcelImpl)}.
-     * <p>
-     * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
-     * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
-     * instance across the process.
-     *
-     * @param list
-     * @return
-     */
-    public static List<MediaItem> convertToMediaItems(List<ParcelImpl> list) {
-        if (list == null) {
-            return null;
-        }
-
-        List<MediaItem> result = new ArrayList<>();
-        for (ParcelImpl parcel : list) {
-            result.add(convertToMediaItem(parcel));
-        }
-        return result;
-    }
-
-    /**
-     * Converts the {@link ParcelImpl} to {@link MediaItem}, by creating {@link UriMediaItem}.
-     * <p>
-     * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
-     * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
-     * instance across the process.
-     *
-     * @param item
-     * @return
-     */
-    public static MediaItem convertToMediaItem(ParcelImpl item) {
-        if (item == null) {
-            return null;
-        }
-        MediaItem mediaItem = MediaParcelUtils.fromParcelable(item);
-        if (mediaItem == null) {
-            return null;
-        }
-        String mediaId = mediaItem.getMediaId();
-        return new UriMediaItem.Builder(Uri.parse("android://" + mediaId))
-                .setStartPosition(mediaItem.getStartPosition())
-                .setEndPosition(mediaItem.getEndPosition())
-                .setMetadata(mediaItem.getMetadata()).build();
-    }
-
-    public static ControllerInfo getTestControllerInfo(MediaSession session) {
-        if (session == null) {
-            return null;
-        }
-        for (ControllerInfo info : session.getConnectedControllers()) {
-            if (CLIENT_PACKAGE_NAME.equals(info.getPackageName())) {
-                return info;
-            }
-        }
-        Log.e(TAG, "Test controller was not found in connected controllers. session=" + session);
-        return null;
-    }
-
-    // Note: It's not assertEquals() to avoid issue with the static import of JUnit's assertEquals.
-    // Otherwise, this API hides the statically imported JUnit's assertEquals and compile will fail.
-    public static void assertMediaMetadataEquals(MediaMetadata expected, MediaMetadata actual) {
-        if (expected == null || actual == null) {
-            assertEquals(expected, actual);
-        } else {
-            Set<String> expectedKeySet = expected.keySet();
-            Set<String> actualKeySet = actual.keySet();
-
-            assertEquals(expectedKeySet, actualKeySet);
-            for (String key : expectedKeySet) {
-                assertEquals(expected.getObject(key), actual.getObject(key));
-            }
-        }
-    }
-
-    public static LibraryParams createLibraryParams() {
-        String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
-
-        Bundle extras = new Bundle();
-        extras.putString(callingTestName, callingTestName);
-        return new LibraryParams.Builder().setExtras(extras).build();
-    }
-
-    public static void assertEqualLibraryParams(LibraryParams a, LibraryParams b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        } else {
-            assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
-        }
-    }
-
-    public static void assertEqualLibraryParams(LibraryParams params, Bundle rootExtras) {
-        if (params == null || rootExtras == null) {
-            assertEquals(params, rootExtras);
-        } else {
-            assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
-            assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
-            assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
-            assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
-        }
-    }
-
-    public static boolean isClientToT() {
-        String clientVersion = InstrumentationRegistry.getArguments()
-                .getString(KEY_CLIENT_VERSION, "");
-        return VERSION_TOT.equals(clientVersion);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java
deleted file mode 100644
index 2489f6e..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.Callback;
-
-import androidx.annotation.GuardedBy;
-import androidx.media.MediaBrowserServiceCompat;
-
-import java.lang.reflect.Method;
-import java.util.List;
-
-/**
- * Mock implementation of the media browser service.
- */
-public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-    private static final String TAG = "MockMediaBrowserServiceCompat";
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static volatile MockMediaBrowserServiceCompat sInstance;
-    @GuardedBy("sLock")
-    private static volatile Proxy sServiceProxy;
-
-    private MediaSessionCompat mSessionCompat;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        synchronized (sLock) {
-            sInstance = this;
-        }
-        mSessionCompat = new MediaSessionCompat(this, TAG);
-        mSessionCompat.setCallback(new Callback() { });
-        mSessionCompat.setActive(true);
-        setSessionToken(mSessionCompat.getSessionToken());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mSessionCompat.release();
-        synchronized (sLock) {
-            sInstance = null;
-            // Note: Don't reset sServiceProxy.
-            //       When a test is finished and its next test is running, this service will be
-            //       destroyed and re-created for the next test. When it happens, onDestroy() may be
-            //       called after the next test's proxy has set because onDestroy() and tests run on
-            //       the different threads.
-            //       So keep sServiceProxy for the next test.
-        }
-    }
-
-    public static MockMediaBrowserServiceCompat getInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    public static void setMediaBrowserServiceProxy(Proxy proxy) {
-        synchronized (sLock) {
-            sServiceProxy = proxy;
-        }
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName) {
-        return isProxyOverridesMethod(methodName, -1);
-    }
-
-    private static boolean isProxyOverridesMethod(String methodName, int paramCount) {
-        synchronized (sLock) {
-            if (sServiceProxy == null) {
-                return false;
-            }
-            Method[] methods = sServiceProxy.getClass().getMethods();
-            if (methods == null) {
-                return false;
-            }
-            for (int i = 0; i < methods.length; i++) {
-                if (methods[i].getName().equals(methodName)) {
-                    if (paramCount < 0
-                            || (methods[i].getParameterTypes() != null
-                            && methods[i].getParameterTypes().length == paramCount)) {
-                        // Found method. Check if it overrides
-                        return methods[i].getDeclaringClass() != Proxy.class;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        if (!CLIENT_PACKAGE_NAME.equals(clientPackageName)) {
-            // Test only -- reject any other request.
-            return null;
-        }
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onGetRoot")) {
-                return sServiceProxy.onGetRoot(clientPackageName, clientUid, rootHints);
-            }
-        }
-        return new BrowserRoot("stub", null);
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 2)) {
-                sServiceProxy.onLoadChildren(parentId, result);
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void onLoadChildren(String parentId, Result<List<MediaItem>> result, Bundle options) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadChildren", 3)) {
-                sServiceProxy.onLoadChildren(parentId, result, options);
-                return;
-            }
-        }
-        super.onLoadChildren(parentId, result, options);
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onLoadItem")) {
-                sServiceProxy.onLoadItem(itemId, result);
-                return;
-            }
-        }
-        super.onLoadItem(itemId, result);
-    }
-
-    @Override
-    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onSearch")) {
-                sServiceProxy.onSearch(query, extras, result);
-                return;
-            }
-        }
-        super.onSearch(query, extras, result);
-    }
-
-    @Override
-    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
-        synchronized (sLock) {
-            if (isProxyOverridesMethod("onCustomAction")) {
-                sServiceProxy.onCustomAction(action, extras, result);
-                return;
-            }
-        }
-        super.onCustomAction(action, extras, result);
-    }
-
-    public static class Proxy {
-        public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-            return new BrowserRoot("stub", null);
-        }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result) { }
-
-        public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                Bundle options) { }
-
-        public void onLoadItem(String itemId, Result<MediaItem> result) { }
-
-        public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) { }
-
-        public void onCustomAction(String action, Bundle extras, Result<Bundle> result) { }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
deleted file mode 100644
index cb8b580..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_MIXED;
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
-import static androidx.media2.test.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.LONG_LIST_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_ERROR;
-import static androidx.media2.test.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_EXTRAS;
-import static androidx.media2.test.common.MediaBrowserConstants.ROOT_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
-import static androidx.media2.test.common.MediaBrowserConstants.SEARCH_TIME_IN_MS;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
-import static androidx.media2.test.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media2.test.service.MediaTestUtils.assertEqualLibraryParams;
-
-import android.app.Service;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.LibraryResult;
-import androidx.media2.session.MediaLibraryService;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.versionedparcelable.ParcelUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-public class MockMediaLibraryService extends MediaLibraryService {
-    /**
-     * ID of the session that this service will create.
-     */
-    public static final String ID = "TestLibrary";
-    public static final MediaItem ROOT_ITEM = new MediaItem.Builder()
-            .setMetadata(new MediaMetadata.Builder()
-                    .putString(METADATA_KEY_MEDIA_ID, ROOT_ID)
-                    .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
-                    .putLong(METADATA_KEY_PLAYABLE, 0)
-                    .build()).build();
-    public static final LibraryParams ROOT_PARAMS = new LibraryParams.Builder()
-            .setExtras(ROOT_EXTRAS).build();
-    private static final LibraryParams NOTIFY_CHILDREN_CHANGED_PARAMS = new LibraryParams.Builder()
-            .setExtras(NOTIFY_CHILDREN_CHANGED_EXTRAS).build();
-
-    private static final String TAG = "MockMediaLibrarySvc2";
-
-    @GuardedBy("MockMediaLibraryService.class")
-    private static boolean sAssertLibraryParams;
-    @GuardedBy("MockMediaLibraryService.class")
-    private static LibraryParams sExpectedParams;
-
-    MediaLibrarySession mSession;
-    SyncHandler mHandler;
-    HandlerThread mHandlerThread;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new SyncHandler(mHandlerThread.getLooper());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        synchronized (MockMediaLibraryService.class) {
-            sAssertLibraryParams = false;
-            sExpectedParams = null;
-        }
-        mHandler.getLooper().quitSafely();
-        mHandler = null;
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Override
-    public MediaLibrarySession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return (MediaLibrarySession) onGetSessionHandler.onGetSession(controllerInfo);
-        }
-
-        final MockPlayer player = new MockPlayer(1);
-        final Executor executor = new Executor() {
-            @Override
-            public void execute(Runnable runnable) {
-                mHandler.post(runnable);
-            }
-        };
-
-        MediaLibrarySessionCallback callback = registry.getSessionCallback();
-        mSession = new MediaLibrarySession.Builder(MockMediaLibraryService.this, player, executor,
-                callback != null ? callback : new TestLibrarySessionCallback())
-                .setId(ID)
-                .setThrowsWhenInvalidReturn(false)
-                .build();
-        return mSession;
-    }
-
-    /**
-     * This changes the visibility of {@link Service#attachBaseContext(Context)} to public.
-     * This is a workaround for creating {@link MediaLibrarySession} without starting a service.
-     */
-    @Override
-    public void attachBaseContext(Context base) {
-        super.attachBaseContext(base);
-    }
-
-    public static void setAssertLibraryParams(LibraryParams expectedParams) {
-        synchronized (MockMediaLibraryService.class) {
-            sAssertLibraryParams = true;
-            sExpectedParams = expectedParams;
-        }
-    }
-
-    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
-
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            if (!CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                return null;
-            }
-            SessionCommandGroup group = super.onConnect(session, controller);
-            SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder(group);
-            builder.addCommand(new SessionCommand(CUSTOM_ACTION, null));
-            builder.addCommand(new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null));
-            return builder.build();
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetLibraryRoot(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, LibraryParams params) {
-            assertLibraryParams(params);
-            return new LibraryResult(RESULT_SUCCESS, ROOT_ITEM, ROOT_PARAMS);
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetItem(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String mediaId) {
-            switch (mediaId) {
-                case MEDIA_ID_GET_ITEM:
-                    return new LibraryResult(RESULT_SUCCESS, createMediaItem(mediaId), null);
-                case MEDIA_ID_GET_NULL_ITEM:
-                    return new LibraryResult(RESULT_SUCCESS);
-                case MEDIA_ID_GET_INVALID_ITEM:
-                    // No browsable
-                    MediaMetadata metadata =  new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                            .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                            .build();
-                    return new LibraryResult(RESULT_SUCCESS,
-                            new MediaItem.Builder().setMetadata(metadata).build(), null);
-            }
-            return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetChildren(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String parentId, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (PARENT_ID.equals(parentId)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), null);
-            } else if (PARENT_ID_LONG_LIST.equals(parentId)) {
-                List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
-                MediaItem.Builder builder = new MediaItem.Builder();
-                for (int i = 0; i < LONG_LIST_COUNT; i++) {
-                    list.add(builder
-                            .setMetadata(new MediaMetadata.Builder()
-                                    .putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
-                                            TestUtils.getMediaIdInFakeList(i))
-                                    .putLong(MediaMetadata.METADATA_KEY_BROWSABLE,
-                                            MediaMetadata.BROWSABLE_TYPE_NONE)
-                                    .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                                    .build())
-                            .build());
-                }
-                return new LibraryResult(RESULT_SUCCESS, list, null);
-            } else if (PARENT_ID_ERROR.equals(parentId)) {
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-            // Includes the case of PARENT_ID_NO_CHILDREN.
-            return new LibraryResult(RESULT_SUCCESS, new ArrayList<MediaItem>(), null);
-        }
-
-        @Override
-        public int onSearch(@NonNull MediaLibrarySession session,
-                @NonNull final ControllerInfo controllerInfo, @NonNull final String query,
-                final LibraryParams params) {
-            assertLibraryParams(params);
-            if (SEARCH_QUERY.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
-                        params);
-            } else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, LONG_LIST_COUNT, params);
-            } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
-                // Searching takes some time. Notify after 5 seconds.
-                Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSession.notifySearchResultChanged(
-                                controllerInfo, query, SEARCH_RESULT_COUNT, params);
-                    }
-                }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
-            } else {
-                // SEARCH_QUERY_EMPTY_RESULT and SEARCH_QUERY_ERROR will be handled here.
-                mSession.notifySearchResultChanged(controllerInfo, query, 0, params);
-            }
-            return RESULT_SUCCESS;
-        }
-
-        @NonNull
-        @Override
-        public LibraryResult onGetSearchResult(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull String query, int page,
-                int pageSize, LibraryParams params) {
-            assertLibraryParams(params);
-            if (SEARCH_QUERY.equals(query)) {
-                return new LibraryResult(RESULT_SUCCESS,
-                        getPaginatedResult(SEARCH_RESULT, page, pageSize), null);
-            } else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
-                List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
-                MediaItem.Builder builder = new MediaItem.Builder();
-                for (int i = 0; i < LONG_LIST_COUNT; i++) {
-                    list.add(createMediaItem(TestUtils.getMediaIdInFakeList(i)));
-                }
-                return new LibraryResult(RESULT_SUCCESS, list, null);
-            } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
-                return new LibraryResult(RESULT_SUCCESS, new ArrayList<MediaItem>(), null);
-            } else {
-                // SEARCH_QUERY_ERROR will be handled here.
-                return new LibraryResult(RESULT_ERROR_BAD_VALUE);
-            }
-        }
-
-        @Override
-        public int onSubscribe(@NonNull MediaLibrarySession session,
-                @NonNull ControllerInfo controller, @NonNull String parentId,
-                LibraryParams params) {
-            assertLibraryParams(params);
-            final String unsubscribedId = "unsubscribedId";
-            switch (parentId) {
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL:
-                    mSession.notifyChildrenChanged(
-                            parentId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE:
-                    mSession.notifyChildrenChanged(
-                            MediaTestUtils.getTestControllerInfo(mSession),
-                            parentId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID:
-                    mSession.notifyChildrenChanged(
-                            unsubscribedId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-                case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID:
-                    mSession.notifyChildrenChanged(
-                            MediaTestUtils.getTestControllerInfo(mSession),
-                            unsubscribedId,
-                            NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
-                            NOTIFY_CHILDREN_CHANGED_PARAMS);
-                    return RESULT_SUCCESS;
-            }
-            return RESULT_ERROR_BAD_VALUE;
-        }
-
-        @NonNull
-        @Override
-        public SessionResult onCustomCommand(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand sessionCommand,
-                Bundle args) {
-            switch (sessionCommand.getCustomAction()) {
-                case CUSTOM_ACTION:
-                    return new SessionResult(
-                            RESULT_SUCCESS, CUSTOM_ACTION_EXTRAS);
-                case CUSTOM_ACTION_ASSERT_PARAMS:
-                    LibraryParams params = ParcelUtils.getVersionedParcelable(args,
-                            CUSTOM_ACTION_ASSERT_PARAMS);
-                    setAssertLibraryParams(params);
-                    return new SessionResult(RESULT_SUCCESS, null);
-            }
-            return new SessionResult(RESULT_ERROR_BAD_VALUE, null);
-        }
-
-        private void assertLibraryParams(LibraryParams params) {
-            synchronized (MockMediaLibraryService.class) {
-                if (sAssertLibraryParams) {
-                    assertEqualLibraryParams(sExpectedParams, params);
-                }
-            }
-        }
-    }
-
-    private List<MediaItem> getPaginatedResult(List<String> items, int page, int pageSize) {
-        if (items == null) {
-            return null;
-        } else if (items.size() == 0) {
-            return new ArrayList<>();
-        }
-
-        final int totalItemCount = items.size();
-        int fromIndex = page * pageSize;
-        int toIndex = Math.min((page + 1) * pageSize, totalItemCount);
-
-        List<String> paginatedMediaIdList = new ArrayList<>();
-        try {
-            // The case of (fromIndex >= totalItemCount) will throw exception below.
-            paginatedMediaIdList = items.subList(fromIndex, toIndex);
-        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
-            Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
-                    + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
-        }
-
-        // Create a list of MediaItem from the list of media IDs.
-        List<MediaItem> result = new ArrayList<>();
-        for (int i = 0; i < paginatedMediaIdList.size(); i++) {
-            result.add(createMediaItem(paginatedMediaIdList.get(i)));
-        }
-        return result;
-    }
-
-    private MediaItem createMediaItem(String mediaId) {
-        MediaMetadata metadata =  new MediaMetadata.Builder()
-                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                .putLong(MediaMetadata.METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1)
-                .build();
-        return new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java
deleted file mode 100644
index 7d23701..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaSessionService.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-
-import java.util.concurrent.Executors;
-
-public class MockMediaSessionService extends MediaSessionService {
-    /**
-     * ID of the session that this service will create.
-     */
-    public static final String ID = "TestSession";
-    public MediaSession mSession2;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Override
-    public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
-        TestServiceRegistry registry = TestServiceRegistry.getInstance();
-        TestServiceRegistry.OnGetSessionHandler onGetSessionHandler =
-                registry.getOnGetSessionHandler();
-        if (onGetSessionHandler != null) {
-            return onGetSessionHandler.onGetSession(controllerInfo);
-        }
-
-        if (mSession2 == null) {
-            MediaSession.SessionCallback callback = registry.getSessionCallback();
-            mSession2 = new MediaSession.Builder(MockMediaSessionService.this, new MockPlayer(0))
-                    .setId(ID)
-                    .setSessionCallback(Executors.newSingleThreadExecutor(),
-                            callback != null ? callback : new TestSessionCallback())
-                    .build();
-        }
-        return mSession2;
-    }
-
-    private class TestSessionCallback extends MediaSession.SessionCallback {
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull MediaSession.ControllerInfo controller) {
-            if (TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName())) {
-                return super.onConnect(session, controller);
-            }
-            return null;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
deleted file mode 100644
index 662fefd..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
+++ /dev/null
@@ -1,652 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.SubtitleData;
-import androidx.media2.common.VideoSize;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * A mock implementation of {@link SessionPlayer} for testing.
- */
-public class MockPlayer extends SessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mCountDownLatch;
-    public final boolean mChangePlayerStateWithTransportControl;
-
-    public boolean mPlayCalled;
-    public boolean mPauseCalled;
-    public boolean mPrepareCalled;
-    public boolean mSeekToCalled;
-    public boolean mSetPlaybackSpeedCalled;
-    public long mSeekPosition;
-    public long mCurrentPosition;
-    public long mBufferedPosition;
-    public float mPlaybackSpeed = 1.0f;
-    @PlayerState
-    public int mLastPlayerState;
-    @BuffState
-    public int mLastBufferingState;
-    public long mDuration;
-    public List<TrackInfo> mTracks;
-
-    public List<MediaItem> mPlaylist;
-    public MediaMetadata mMetadata;
-    public MediaItem mCurrentMediaItem;
-    public MediaItem mItem;
-    public int mIndex = -1;
-    public int mIndex2 = -1;
-    @RepeatMode
-    public int mRepeatMode = -1;
-    @ShuffleMode
-    public int mShuffleMode = -1;
-    public VideoSize mVideoSize = new VideoSize(0, 0);
-    public Surface mSurface;
-
-    public boolean mSetPlaylistCalled;
-    public boolean mUpdatePlaylistMetadataCalled;
-    public boolean mAddPlaylistItemCalled;
-    public boolean mRemovePlaylistItemCalled;
-    public boolean mReplacePlaylistItemCalled;
-    public boolean mMovePlaylistItemCalled;
-    public boolean mSkipToPlaylistItemCalled;
-    public boolean mSkipToPreviousItemCalled;
-    public boolean mSkipToNextItemCalled;
-    public boolean mSetRepeatModeCalled;
-    public boolean mSetShuffleModeCalled;
-
-    private AudioAttributesCompat mAudioAttributes;
-
-    public MockPlayer(int count) {
-        this(count, false);
-    }
-
-    public MockPlayer(boolean changePlayerStateWithTransportControl) {
-        this(0, changePlayerStateWithTransportControl);
-    }
-
-    private MockPlayer(int count, boolean changePlayerStateWithTransportControl) {
-        mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
-        mChangePlayerStateWithTransportControl = changePlayerStateWithTransportControl;
-        // This prevents MS2#play() from triggering SessionPlayer#prepare().
-        mLastPlayerState = PLAYER_STATE_PAUSED;
-
-        // Sets default audio attributes to prevent setVolume() from being called with the play().
-        mAudioAttributes = new AudioAttributesCompat.Builder()
-                .setUsage(AudioAttributesCompat.USAGE_MEDIA).build();
-    }
-
-    @Override
-    public void close() {
-        // no-op
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        mPlayCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PLAYING);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        mPauseCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        mPrepareCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        if (mChangePlayerStateWithTransportControl) {
-            notifyPlayerStateChanged(PLAYER_STATE_PAUSED);
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long pos) {
-        mSeekToCalled = true;
-        mSeekPosition = pos;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPlayerState() {
-        return mLastPlayerState;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return mCurrentPosition;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return mPlaybackSpeed;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return mLastBufferingState;
-    }
-
-    @Override
-    public long getDuration() {
-        return mDuration;
-    }
-
-    public void notifyPlayerStateChanged(final int state) {
-        mLastPlayerState = state;
-
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlayerStateChanged(MockPlayer.this, state);
-                }
-            });
-        }
-    }
-
-    public void notifyCurrentMediaItemChanged(final MediaItem item) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onCurrentMediaItemChanged(MockPlayer.this, item);
-                }
-            });
-        }
-    }
-
-    public void notifyBufferingStateChanged(final MediaItem item,
-            final @BuffState int buffState) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onBufferingStateChanged(MockPlayer.this, item, buffState);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaybackSpeedChanged(final float speed) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaybackSpeedChanged(MockPlayer.this, speed);
-                }
-            });
-        }
-    }
-
-    public void notifySeekCompleted(final long position) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSeekCompleted(MockPlayer.this, position);
-                }
-            });
-        }
-    }
-
-    public void notifyAudioAttributesChanged(final AudioAttributesCompat attrs) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onAudioAttributesChanged(MockPlayer.this, attrs);
-                }
-            });
-        }
-    }
-
-    public void notifyTracksChanged(final List<TrackInfo> tracks) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTracksChanged(MockPlayer.this, tracks);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackSelected(final TrackInfo trackInfo) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackSelected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    public void notifyTrackDeselected(final TrackInfo trackInfo) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onTrackDeselected(MockPlayer.this, trackInfo);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        mAudioAttributes = attributes;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float speed) {
-        mSetPlaybackSpeedCalled = true;
-        mPlaybackSpeed = speed;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////
-    // Playlist APIs
-    /////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return mPlaylist;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        mItem = item;
-        ArrayList<MediaItem> list = new ArrayList<>();
-        list.add(item);
-        return setPlaylist(list, null);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(
-            @NonNull List<MediaItem> list, MediaMetadata metadata) {
-        mSetPlaylistCalled = true;
-        mPlaylist = list;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return mMetadata;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        mUpdatePlaylistMetadataCalled = true;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return mCurrentMediaItem;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        if (mPlaylist == null) {
-            return ITEM_NONE;
-        }
-        return mPlaylist.indexOf(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        // TODO: reflect repeat & shuffle modes
-        int currentIdx = getCurrentMediaItemIndex();
-        if (currentIdx == ITEM_NONE || currentIdx == 0) {
-            return ITEM_NONE;
-        }
-        return currentIdx--;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        // TODO: reflect repeat & shuffle modes
-        int currentIdx = getCurrentMediaItemIndex();
-        if (currentIdx == ITEM_NONE || currentIdx == mPlaylist.size() - 1) {
-            return ITEM_NONE;
-        }
-        return currentIdx++;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mAddPlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        // TODO: check for invalid index
-        mRemovePlaylistItemCalled = true;
-        mIndex = index;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        // TODO: check for invalid index
-        mReplacePlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> movePlaylistItem(int fromIndex, int toIndex) {
-        // TODO: check for invalid index
-        mMovePlaylistItemCalled = true;
-        mIndex = fromIndex;
-        mIndex2 = toIndex;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        // TODO: check for invalid index
-        mSkipToPlaylistItemCalled = true;
-        mIndex = index;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        // TODO: reflect repeat & shuffle modes
-        mSkipToPreviousItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        // TODO: reflect repeat & shuffle modes
-        mSkipToNextItemCalled = true;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return mRepeatMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        mSetRepeatModeCalled = true;
-        mRepeatMode = repeatMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return mShuffleMode;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        mSetShuffleModeCalled = true;
-        mShuffleMode = shuffleMode;
-        mCountDownLatch.countDown();
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public List<TrackInfo> getTracks() {
-        if (mTracks == null) {
-            return new ArrayList<TrackInfo>();
-        }
-        return mTracks;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> selectTrack(@NonNull TrackInfo trackInfo) {
-        notifyTrackSelected(trackInfo);
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> deselectTrack(@NonNull TrackInfo trackInfo) {
-        notifyTrackDeselected(trackInfo);
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    public void notifyShuffleModeChanged() {
-        final int shuffleMode = mShuffleMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onShuffleModeChanged(MockPlayer.this, shuffleMode);
-                }
-            });
-        }
-    }
-
-    public void notifyRepeatModeChanged() {
-        final int repeatMode = mRepeatMode;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onRepeatModeChanged(MockPlayer.this, repeatMode);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaybackCompleted() {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaybackCompleted(MockPlayer.this);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistChanged() {
-        final List<MediaItem> list = mPlaylist;
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistChanged(MockPlayer.this, list, metadata);
-                }
-            });
-        }
-    }
-
-    public void notifyPlaylistMetadataChanged() {
-        final MediaMetadata metadata = mMetadata;
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onPlaylistMetadataChanged(MockPlayer.this, metadata);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public VideoSize getVideoSize() {
-        if (mVideoSize == null) {
-            mVideoSize = new VideoSize(0, 0);
-        }
-        return mVideoSize;
-    }
-
-    public void notifyVideoSizeChanged(@NonNull final VideoSize videoSize) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onVideoSizeChanged(MockPlayer.this, videoSize);
-                }
-            });
-        }
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setSurface(@Nullable Surface surface) {
-        mSurface = surface;
-        return new SyncListenableFuture(mCurrentMediaItem);
-    }
-
-    public boolean surfaceExists() {
-        return mSurface != null;
-    }
-
-    public void notifySubtitleData(@NonNull final MediaItem item, @NonNull final TrackInfo track,
-            @NonNull final SubtitleData data) {
-        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
-        for (Pair<PlayerCallback, Executor> pair : callbacks) {
-            final PlayerCallback callback = pair.first;
-            pair.second.execute(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onSubtitleData(MockPlayer.this, item, track, data);
-                }
-            });
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java
deleted file mode 100644
index 3de1721..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockRemotePlayer.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.session.RemoteSessionPlayer;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * Mock implementation of {@link RemoteSessionPlayer}.
- */
-public class MockRemotePlayer extends RemoteSessionPlayer {
-    private static final int ITEM_NONE = -1;
-
-    public final CountDownLatch mLatch = new CountDownLatch(1);
-    public boolean mSetVolumeToCalled;
-    public boolean mAdjustVolumeCalled;
-    public @VolumeControlType int mControlType;
-    public int mCurrentVolume;
-    public int mMaxVolume;
-    public int mDirection;
-    public AudioAttributesCompat mAttributes;
-
-    public MockRemotePlayer(int controlType, int maxVolume, int currentVolume) {
-        mControlType = controlType;
-        mMaxVolume = maxVolume;
-        mCurrentVolume = currentVolume;
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> setVolume(int volume) {
-        mSetVolumeToCalled = true;
-        mCurrentVolume = volume;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<PlayerResult> adjustVolume(int direction) {
-        mAdjustVolumeCalled = true;
-        mDirection = direction;
-        mLatch.countDown();
-        return new SyncListenableFuture(null);
-    }
-
-    @Override
-    public int getVolume() {
-        return mCurrentVolume;
-    }
-
-    @Override
-    public int getMaxVolume() {
-        return mMaxVolume;
-    }
-
-    @Override
-    public int getVolumeControlType() {
-        return mControlType;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> play() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> pause() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> prepare() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> seekTo(long position) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setAudioAttributes(
-            @NonNull AudioAttributesCompat attributes) {
-        mAttributes = attributes;
-        return new SyncListenableFuture(null);
-    }
-
-    @Override
-    public int getPlayerState() {
-        return 0;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return 0;
-    }
-
-    @Override
-    public long getDuration() {
-        return 0;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return 0;
-    }
-
-    @Override
-    public int getBufferingState() {
-        return 0;
-    }
-
-    @Override
-    public float getPlaybackSpeed() {
-        return 0;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setPlaylist(@NonNull List<MediaItem> list,
-            MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    public AudioAttributesCompat getAudioAttributes() {
-        return mAttributes;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> addPlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, @NonNull MediaItem item) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
-        return null;
-    }
-
-    @Override
-    @NonNull
-    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
-        return null;
-    }
-
-    @Override
-    public List<MediaItem> getPlaylist() {
-        return null;
-    }
-
-    @Override
-    public MediaMetadata getPlaylistMetadata() {
-        return null;
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return 0;
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return 0;
-    }
-
-    @Override
-    public MediaItem getCurrentMediaItem() {
-        return null;
-    }
-
-    @Override
-    public int getCurrentMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getPreviousMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    @Override
-    public int getNextMediaItemIndex() {
-        return ITEM_NONE;
-    }
-
-    public void notifyVolumeChanged() {
-        int volume = mCurrentVolume;
-        for (Pair<PlayerCallback, Executor> pair : getCallbacks()) {
-            if (!(pair.first instanceof RemoteSessionPlayer.Callback)) {
-                continue;
-            }
-            RemoteSessionPlayer.Callback callback = (RemoteSessionPlayer.Callback) pair.first;
-            Executor executor = pair.second;
-            executor.execute(() -> callback.onVolumeChanged(this, volume));
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java
deleted file mode 100644
index e8827e6..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowser.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionToken;
-
-/**
- * Represents remote {@link MediaBrowser} the client app's MediaControllerService.
- * Users can run {@link MediaBrowser} methods remotely with this object.
- */
-public class RemoteMediaBrowser extends RemoteMediaController {
-
-    /**
-     * Create a {@link MediaBrowser} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param waitForConnection true if the remote browser needs to wait for the connection,
-     *                          false otherwise.
-     * @param connectionHints connection hints
-     */
-    public RemoteMediaBrowser(Context context, SessionToken token, boolean waitForConnection,
-            Bundle connectionHints) {
-        super(context, token, connectionHints, waitForConnection);
-    }
-
-    /**
-     * {@link MediaBrowser} methods.
-     */
-
-    public void getLibraryRoot(@Nullable LibraryParams params) {
-        try {
-            mBinder.getLibraryRoot(mControllerId, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getLibraryRoot()");
-        }
-    }
-
-    public void subscribe(@NonNull String parentId, @Nullable LibraryParams params) {
-        try {
-            mBinder.subscribe(mControllerId, parentId, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call subscribe()");
-        }
-    }
-
-    public void unsubscribe(@NonNull String parentId) {
-        try {
-            mBinder.unsubscribe(mControllerId, parentId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call unsubscribe()");
-        }
-    }
-
-    public void getChildren(@NonNull String parentId, int page, int pageSize,
-            @Nullable LibraryParams params) {
-        try {
-            mBinder.getChildren(mControllerId, parentId, page, pageSize,
-                    MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getChildren()");
-        }
-    }
-
-    public void getItem(@NonNull String mediaId) {
-        try {
-            mBinder.getItem(mControllerId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getItem()");
-        }
-    }
-
-    public void search(@NonNull String query, @Nullable LibraryParams params) {
-        try {
-            mBinder.search(mControllerId, query, MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call search()");
-        }
-    }
-
-    public void getSearchResult(@NonNull String query, int page, int pageSize,
-            @Nullable LibraryParams params) {
-        try {
-            mBinder.getSearchResult(mControllerId, query, page, pageSize,
-                    MediaParcelUtils.toParcelable(params));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getSearchResult()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Create a {@link MediaBrowser} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if this method needs to wait for the connection,
-     */
-    void create(SessionToken token, Bundle connectionHints, boolean waitForConnection) {
-        try {
-            mBinder.create(true /* isBrowser */, mControllerId,
-                    MediaParcelUtils.toParcelable(token), connectionHints, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default browser with given token.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
deleted file mode 100644
index ab16657..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.support.v4.media.MediaBrowserCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaBrowserCompat;
-
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaBrowserCompat} the client app's MediaBrowserCompatProviderService.
- * Users can run {@link MediaBrowserCompat} methods remotely with this object.
- */
-public class RemoteMediaBrowserCompat {
-    private static final String TAG = "RemoteMediaBrowserCompat";
-
-    private final String mBrowserId;
-    private final Context mContext;
-    private final CountDownLatch mCountDownLatch;
-
-    private ServiceConnection mServiceConnection;
-    private IRemoteMediaBrowserCompat mBinder;
-
-    /**
-     * Create a {@link MediaBrowserCompat} in the client app.
-     * Should NOT be called main thread.
-     */
-    public RemoteMediaBrowserCompat(Context context, ComponentName serviceComponent) {
-        mContext = context;
-        mBrowserId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connectToService()) {
-            fail("Failed to connect to the MediaBrowserCompatProviderService.");
-        }
-        create(serviceComponent);
-    }
-
-    public void cleanUp() {
-        disconnect();
-        disconnectFromService();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaBrowserCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connect to the given media browser service.
-     *
-     * @param waitForConnection true if the remote browser needs to wait for the connection,
-     *                          false otherwise.
-     */
-    public void connect(boolean waitForConnection) {
-        try {
-            mBinder.connect(mBrowserId, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call connect()");
-        }
-    }
-
-    public void disconnect() {
-        try {
-            mBinder.disconnect(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call disconnect()");
-        }
-    }
-
-    public boolean isConnected() {
-        try {
-            return mBinder.isConnected(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call isConnected()");
-            return false;
-        }
-    }
-
-    public ComponentName getServiceComponent() {
-        try {
-            return mBinder.getServiceComponent(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getServiceComponent()");
-            return null;
-        }
-    }
-
-    public String getRoot() {
-        try {
-            return mBinder.getRoot(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getRoot()");
-            return null;
-        }
-    }
-
-    public Bundle getExtras() {
-        try {
-            return mBinder.getExtras(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getExtras()");
-            return null;
-        }
-    }
-
-    public Bundle getConnectedSessionToken() {
-        try {
-            return mBinder.getConnectedSessionToken(mBrowserId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getConnectedToken()");
-            return null;
-        }
-    }
-
-    public void subscribe(String parentId, Bundle options) {
-        try {
-            mBinder.subscribe(mBrowserId, parentId, options);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call subscribe()");
-        }
-    }
-
-    public void unsubscribe(String parentId) {
-        try {
-            mBinder.unsubscribe(mBrowserId, parentId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call unsubscribe()");
-        }
-    }
-
-    public void getItem(String mediaId) {
-        try {
-            mBinder.getItem(mBrowserId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getItem()");
-        }
-    }
-
-    public void search(String query, Bundle extras) {
-        try {
-            mBinder.search(mBrowserId, query, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call search()");
-        }
-    }
-
-    public void sendCustomAction(String action, Bundle extras) {
-        try {
-            mBinder.sendCustomAction(mBrowserId, action, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomAction()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaBrowserCompatProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connectToService() {
-        final Intent intent = new Intent(ACTION_MEDIA_BROWSER_COMPAT);
-        intent.setComponent(MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaBrowserCompatProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaBrowserCompatProviderService.
-     */
-    private void disconnectFromService() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaBrowserCompat} in the client app.
-     * Should be used after successful connection through {@link #connectToService()}.
-     */
-    private void create(ComponentName serviceComponent) {
-        try {
-            mBinder.create(mBrowserId, serviceComponent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default browser with given serviceComponent.");
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaBrowserCompatProviderService.");
-            mBinder = IRemoteMediaBrowserCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaBrowserCompatProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
deleted file mode 100644
index 5d1eb05..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
-import static androidx.media2.test.common.CommonConstants.MEDIA2_CONTROLLER_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.IRemoteMediaController;
-import androidx.media2.test.common.TestUtils;
-
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaController} the client app's MediaControllerProviderService.
- * Users can run {@link MediaController} methods remotely with this object.
- */
-public class RemoteMediaController {
-    static final String TAG = "RemoteMediaController";
-
-    final String mControllerId;
-    final Context mContext;
-    final CountDownLatch mCountDownLatch;
-
-    ServiceConnection mServiceConnection;
-    IRemoteMediaController mBinder;
-
-    /**
-     * Create a {@link MediaController} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if the remote controller needs to wait for the connection,
-     */
-    public RemoteMediaController(Context context, SessionToken token,
-            Bundle connectionHints, boolean waitForConnection) {
-        mContext = context;
-        mControllerId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connect()) {
-            fail("Failed to connect to the MediaControllerProviderService.");
-        }
-        create(token, connectionHints, waitForConnection);
-    }
-
-    public void cleanUp() {
-        close();
-        disconnect();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaController methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public SessionToken getConnectedSessionToken() {
-        try {
-            return MediaParcelUtils.fromParcelable(mBinder.getConnectedSessionToken(mControllerId));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call getConnectedToken()");
-            return null;
-        }
-    }
-
-    public void play() {
-        try {
-            mBinder.play(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call play()");
-        }
-    }
-
-    public void pause() {
-        try {
-            mBinder.pause(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call pause()");
-        }
-    }
-
-    public void prepare() {
-        try {
-            mBinder.prepare(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call prepare()");
-        }
-    }
-
-    public void seekTo(long pos) {
-        try {
-            mBinder.seekTo(mControllerId, pos);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call seekTo()");
-        }
-    }
-
-    public void setPlaybackSpeed(float speed) {
-        try {
-            mBinder.setPlaybackSpeed(mControllerId, speed);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaybackSpeed()");
-        }
-    }
-
-    public void setPlaylist(@NonNull List<String> list, @Nullable MediaMetadata metadata) {
-        try {
-            mBinder.setPlaylist(mControllerId, list, MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setPlaylist()");
-        }
-    }
-
-    /**
-     * Client app will automatically create a playlist of size {@param size},
-     * and call MediaController#setPlaylist() with the list.
-     *
-     * Each item's media ID will be {@link TestUtils#getMediaIdInFakeList(int)}.
-     */
-    public void createAndSetFakePlaylist(int size, @Nullable MediaMetadata metadata) {
-        try {
-            mBinder.createAndSetFakePlaylist(mControllerId, size,
-                    MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call createAndSetFakePlaylist()");
-        }
-    }
-
-    public void setMediaItem(@NonNull String mediaId) {
-        try {
-            mBinder.setMediaItem(mControllerId, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMediaItem()");
-        }
-    }
-
-    public void setMediaUri(@NonNull Uri uri, @Nullable Bundle extras) {
-        try {
-            mBinder.setMediaUri(mControllerId, uri, extras);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setMediaUri()");
-        }
-    }
-
-    public void updatePlaylistMetadata(@Nullable MediaMetadata metadata) {
-        try {
-            mBinder.updatePlaylistMetadata(mControllerId, MediaParcelUtils.toParcelable(metadata));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call updatePlaylistMetadata()");
-        }
-    }
-
-    public void addPlaylistItem(int index, @NonNull String mediaId) {
-        try {
-            mBinder.addPlaylistItem(mControllerId, index, mediaId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addPlaylistItem()");
-        }
-    }
-
-    public void removePlaylistItem(int index) {
-        try {
-            mBinder.removePlaylistItem(mControllerId, index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call removePlaylistItem()");
-        }
-    }
-
-    public void replacePlaylistItem(int index, @NonNull String media) {
-        try {
-            mBinder.replacePlaylistItem(mControllerId, index, media);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call replacePlaylistItem()");
-        }
-    }
-
-    public void movePlaylistItem(int fromIdx, int toIdx) {
-        try {
-            mBinder.movePlaylistItem(mControllerId, fromIdx, toIdx);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call movePlaylistItem()");
-        }
-    }
-
-    public void skipToPreviousItem() {
-        try {
-            mBinder.skipToPreviousItem(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToPreviousPlaylistItem()");
-        }
-    }
-
-    public void skipToNextItem() {
-        try {
-            mBinder.skipToNextItem(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToNextPlaylistItem()");
-        }
-    }
-
-    public void skipToPlaylistItem(int index) {
-        try {
-            mBinder.skipToPlaylistItem(mControllerId, index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipToPlaylistItem()");
-        }
-    }
-
-    public void setShuffleMode(int shuffleMode) {
-        try {
-            mBinder.setShuffleMode(mControllerId, shuffleMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setShuffleMode()");
-        }
-    }
-
-    public void setRepeatMode(int repeatMode) {
-        try {
-            mBinder.setRepeatMode(mControllerId, repeatMode);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRepeatMode()");
-        }
-    }
-
-    public void setVolumeTo(int value, int flags) {
-        try {
-            mBinder.setVolumeTo(mControllerId, value, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setVolumeTo()");
-        }
-    }
-
-    public void adjustVolume(int direction, int flags) {
-        try {
-            mBinder.adjustVolume(mControllerId, direction, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call adjustVolume()");
-        }
-    }
-
-    public void sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args) {
-        try {
-            mBinder.sendCustomCommand(mControllerId, MediaParcelUtils.toParcelable(command), args);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCustomCommand()");
-        }
-    }
-
-    public void fastForward() {
-        try {
-            mBinder.fastForward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call fastForward()");
-        }
-    }
-
-    public void rewind() {
-        try {
-            mBinder.rewind(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call rewind()");
-        }
-    }
-
-    public void skipForward() {
-        try {
-            mBinder.skipForward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipForward()");
-        }
-    }
-
-    public void skipBackward() {
-        try {
-            mBinder.skipBackward(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call skipBackward()");
-        }
-    }
-
-    public void setRating(@NonNull String mediaId, @NonNull Rating rating) {
-        try {
-            mBinder.setRating(mControllerId, mediaId, MediaParcelUtils.toParcelable(rating));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setRating()");
-        }
-    }
-
-    public void close() {
-        try {
-            mBinder.close(mControllerId);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call close()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaControllerProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA2_CONTROLLER);
-        intent.setComponent(MEDIA2_CONTROLLER_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaControllerProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaControllerProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaController} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param connectionHints connection hints
-     * @param waitForConnection true if this method needs to wait for the connection,
-     */
-    void create(SessionToken token, Bundle connectionHints, boolean waitForConnection) {
-        try {
-            mBinder.create(false /* isBrowser */, mControllerId,
-                    MediaParcelUtils.toParcelable(token), connectionHints, waitForConnection);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default controller with given token.");
-        }
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaControllerProviderService.");
-            mBinder = IRemoteMediaController.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaControllerProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
deleted file mode 100644
index 121185fc..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
-import static androidx.media2.test.common.CommonConstants.KEY_ARGUMENTS;
-import static androidx.media2.test.common.CommonConstants.MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
-
-import static junit.framework.TestCase.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media2.test.common.IRemoteMediaControllerCompat;
-
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents remote {@link MediaControllerCompat} the client app's
- * MediaControllerCompatProviderService.
- * <p>
- * Users can run {@link MediaControllerCompat} methods remotely with this object.
- */
-public class RemoteMediaControllerCompat {
-    static final String TAG = "RemoteMediaControllerCompat";
-
-    final String mControllerId;
-    final Context mContext;
-    final CountDownLatch mCountDownLatch;
-
-    ServiceConnection mServiceConnection;
-    IRemoteMediaControllerCompat mBinder;
-    TransportControls mTransportControls;
-
-    /**
-     * Create a {@link MediaControllerCompat} in the client app.
-     * Should NOT be called main thread.
-     *
-     * @param waitForConnection true if the remote controller needs to wait for the connection,
-     *                          false otherwise.
-     */
-    public RemoteMediaControllerCompat(Context context, MediaSessionCompat.Token token,
-            boolean waitForConnection) {
-        mContext = context;
-        mControllerId = UUID.randomUUID().toString();
-        mCountDownLatch = new CountDownLatch(1);
-        mServiceConnection = new MyServiceConnection();
-        if (!connect()) {
-            fail("Failed to connect to the MediaControllerCompatProviderService.");
-        }
-        create(token, waitForConnection);
-    }
-
-    public void cleanUp() {
-        disconnect();
-    }
-
-    /**
-     * Gets {@link TransportControls} for interact with the remote MockPlayer.
-     * Users can run MockPlayer methods remotely with this object.
-     */
-    public TransportControls getTransportControls() {
-        return mTransportControls;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaControllerCompat methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public void addQueueItem(MediaDescriptionCompat description) {
-        try {
-            mBinder.addQueueItem(mControllerId, createBundleWithParcelable(description));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addQueueItem()");
-        }
-    }
-
-    public void addQueueItem(MediaDescriptionCompat description, int index) {
-        try {
-            mBinder.addQueueItemWithIndex(
-                    mControllerId, createBundleWithParcelable(description), index);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call addQueueItemWithIndex()");
-        }
-    }
-
-    public void removeQueueItem(MediaDescriptionCompat description) {
-        try {
-            mBinder.removeQueueItem(mControllerId, createBundleWithParcelable(description));
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call removeQueueItem()");
-        }
-    }
-
-    public void setVolumeTo(int value, int flags) {
-        try {
-            mBinder.setVolumeTo(mControllerId, value, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call setVolumeTo()");
-        }
-    }
-
-    public void adjustVolume(int direction, int flags) {
-        try {
-            mBinder.adjustVolume(mControllerId, direction, flags);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call adjustVolume()");
-        }
-    }
-
-    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
-        try {
-            mBinder.sendCommand(mControllerId, command, params, cb);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to call sendCommand()");
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // MediaControllerCompat.TransportControls methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    public class TransportControls {
-        public void prepare() {
-            try {
-                mBinder.prepare(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepare()");
-            }
-        }
-
-        public void prepareFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.prepareFromMediaId(mControllerId, mediaId, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromMediaId()");
-            }
-        }
-
-        public void prepareFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.prepareFromSearch(mControllerId, query, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromSearch()");
-            }
-        }
-
-        public void prepareFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.prepareFromUri(mControllerId, uri, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call prepareFromUri()");
-            }
-        }
-
-        public void play() {
-            try {
-                mBinder.play(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call play()");
-            }
-        }
-
-        public void playFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.playFromMediaId(mControllerId, mediaId, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromMediaId()");
-            }
-        }
-
-        public void playFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.playFromSearch(mControllerId, query, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromSearch()");
-            }
-        }
-
-        public void playFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.playFromUri(mControllerId, uri, extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call playFromUri()");
-            }
-        }
-
-        public void skipToQueueItem(long id) {
-            try {
-                mBinder.skipToQueueItem(mControllerId, id);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToQueueItem()");
-            }
-        }
-
-        public void pause() {
-            try {
-                mBinder.pause(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call pause()");
-            }
-        }
-
-        public void stop() {
-            try {
-                mBinder.stop(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call stop()");
-            }
-        }
-
-        public void seekTo(long pos) {
-            try {
-                mBinder.seekTo(mControllerId, pos);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call seekTo()");
-            }
-        }
-
-        public void setPlaybackSpeed(float speed) {
-            try {
-                mBinder.setPlaybackSpeed(mControllerId, speed);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setPlaybackSpeed()");
-            }
-        }
-
-        public void fastForward() {
-            try {
-                mBinder.fastForward(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call fastForward()");
-            }
-        }
-
-        public void skipToNext() {
-            try {
-                mBinder.skipToNext(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToNext()");
-            }
-        }
-
-        public void rewind() {
-            try {
-                mBinder.rewind(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call rewind()");
-            }
-        }
-
-        public void skipToPrevious() {
-            try {
-                mBinder.skipToPrevious(mControllerId);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call skipToPrevious()");
-            }
-        }
-
-        public void setRating(RatingCompat rating) {
-            try {
-                mBinder.setRating(mControllerId, createBundleWithParcelable(rating));
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRating()");
-            }
-        }
-
-        public void setRating(RatingCompat rating, Bundle extras) {
-            try {
-                mBinder.setRatingWithExtras(
-                        mControllerId, createBundleWithParcelable(rating), extras);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRatingWithExtras()");
-            }
-        }
-
-        public void setCaptioningEnabled(boolean enabled) {
-            try {
-                mBinder.setCaptioningEnabled(mControllerId, enabled);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setCaptioningEnabled()");
-            }
-        }
-
-        public void setRepeatMode(int repeatMode) {
-            try {
-                mBinder.setRepeatMode(mControllerId, repeatMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setRepeatMode()");
-            }
-        }
-
-        public void setShuffleMode(int shuffleMode) {
-            try {
-                mBinder.setShuffleMode(mControllerId, shuffleMode);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call setShuffleMode()");
-            }
-        }
-
-        public void sendCustomAction(PlaybackStateCompat.CustomAction customAction, Bundle args) {
-            try {
-                mBinder.sendCustomAction(
-                        mControllerId, createBundleWithParcelable(customAction), args);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call sendCustomAction()");
-            }
-        }
-
-        public void sendCustomAction(String action, Bundle args) {
-            try {
-                mBinder.sendCustomActionWithName(mControllerId, action, args);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to call sendCustomActionWithName()");
-            }
-        }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////
-    // Non-public methods
-    ////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Connects to client app's MediaControllerCompatProviderService.
-     * Should NOT be called main thread.
-     *
-     * @return true if connected successfully, false if failed to connect.
-     */
-    private boolean connect() {
-        final Intent intent = new Intent(ACTION_MEDIA_CONTROLLER_COMPAT);
-        intent.setComponent(MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE);
-
-        boolean bound = false;
-        try {
-            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to bind to the MediaControllerCompatProviderService.");
-        }
-
-        if (bound) {
-            try {
-                mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
-                        TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
-            }
-        }
-        return mBinder != null;
-    }
-
-    /**
-     * Disconnects from client app's MediaControllerCompatProviderService.
-     */
-    private void disconnect() {
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Create a {@link MediaControllerCompat} in the client app.
-     * Should be used after successful connection through {@link #connect()}.
-     *
-     * @param waitForConnection true if this method needs to wait for the connection,
-     *                          false otherwise.
-     */
-    void create(MediaSessionCompat.Token token, boolean waitForConnection) {
-        try {
-            mBinder.create(mControllerId, createBundleWithParcelable(token), waitForConnection);
-            mTransportControls = new TransportControls();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to create default controller with given token.");
-        }
-    }
-
-    private Bundle createBundleWithParcelable(Parcelable parcelable) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(KEY_ARGUMENTS, parcelable);
-        return bundle;
-    }
-
-    class MyServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "Connected to client app's MediaControllerCompatProviderService.");
-            mBinder = IRemoteMediaControllerCompat.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "Disconnected from client app's MediaControllerCompatProviderService.");
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java
deleted file mode 100644
index d081002..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/SyncListenableFuture.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.SessionPlayer;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Implements {@link ListenableFuture} for synchrous calls.
- */
-public class SyncListenableFuture implements ListenableFuture<SessionPlayer.PlayerResult> {
-    private final SessionPlayer.PlayerResult mResult;
-
-    SyncListenableFuture(MediaItem item) {
-        mResult = new SessionPlayer.PlayerResult(RESULT_SUCCESS, item);
-    }
-
-    @Override
-    public void addListener(Runnable listener, Executor executor) {
-        executor.execute(listener);
-    }
-
-    @Override
-    public boolean cancel(boolean b) {
-        return false;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return false;
-    }
-
-    @Override
-    public boolean isDone() {
-        return true;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get() throws InterruptedException, ExecutionException {
-        return mResult;
-    }
-
-    @Override
-    public SessionPlayer.PlayerResult get(long l, TimeUnit timeUnit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return mResult;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java
deleted file mode 100644
index eadb61d..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/TestServiceRegistry.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service;
-
-import static org.junit.Assert.fail;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Keeps the instance of currently running {@link MockMediaSessionService}. And also provides
- * a way to control them in one place.
- * <p>
- * It only support only one service at a time.
- */
-public class TestServiceRegistry {
-    private static final String TAG = "TestServiceRegistry";
-    private static final boolean DEBUG = true;
-
-    @GuardedBy("TestServiceRegistry.class")
-    private static TestServiceRegistry sInstance;
-
-    private final ConditionVariable mServiceSet;
-
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaSessionService mService;
-    @GuardedBy("TestServiceRegistry.class")
-    private SyncHandler mHandler;
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaLibrarySessionCallback mSessionCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private SessionServiceCallback mSessionServiceCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private OnGetSessionHandler mOnGetSessionHandler;
-
-    private TestServiceRegistry() {
-        this.mServiceSet = new ConditionVariable();
-    }
-
-    /**
-     * Callback for session service's lifecyle (onCreate() / onDestroy())
-     */
-    public interface SessionServiceCallback {
-        void onCreated();
-        void onDestroyed();
-    }
-
-    public static TestServiceRegistry getInstance() {
-        synchronized (TestServiceRegistry.class) {
-            if (sInstance == null) {
-                sInstance = new TestServiceRegistry();
-            }
-            return sInstance;
-        }
-    }
-
-    public void setOnGetSessionHandler(OnGetSessionHandler onGetSessionHandler) {
-        synchronized (TestServiceRegistry.class) {
-            mOnGetSessionHandler = onGetSessionHandler;
-        }
-    }
-
-    public OnGetSessionHandler getOnGetSessionHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mOnGetSessionHandler;
-        }
-    }
-
-    public void setHandler(Handler handler) {
-        synchronized (TestServiceRegistry.class) {
-            mHandler = new SyncHandler(handler.getLooper());
-        }
-    }
-
-    public Handler getHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mHandler;
-        }
-    }
-
-    public void setSessionServiceCallback(SessionServiceCallback sessionServiceCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionServiceCallback = sessionServiceCallback;
-        }
-    }
-
-    public void setSessionCallback(MediaLibrarySessionCallback sessionCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionCallback = sessionCallback;
-        }
-    }
-
-    public MediaLibrarySessionCallback getSessionCallback() {
-        synchronized (TestServiceRegistry.class) {
-            return mSessionCallback;
-        }
-    }
-
-    public void setServiceInstance(MediaSessionService service) {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                fail("Previous service instance is still running. Clean up manually to ensure"
-                        + " previously running service doesn't break current test");
-            }
-            if (DEBUG) {
-                Log.d(TAG, "setServiceInstance(): service=" + service);
-                if (service != null) {
-                    Log.d(TAG, "setServiceInstance(): service=" + service + ", session size="
-                            + service.getSessions().size());
-                    for (MediaSession session : service.getSessions()) {
-                        Log.d(TAG, "   session id=" + session.getId());
-                    }
-                } else {
-                    Log.d(TAG, "setServiceInstance, service=" + service);
-                }
-            }
-            mService = service;
-            if (service != null) {
-                mServiceSet.open();
-            }
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onCreated();
-            }
-        }
-    }
-
-    public MediaSessionService getServiceInstanceBlocking() throws TimeoutException {
-        if (!mServiceSet.block(5_000)) {
-            throw new TimeoutException(
-                    "Timed out waiting for TestServiceRegistry.setServiceInstance() to be called.");
-        }
-        synchronized (TestServiceRegistry.class) {
-            return mService;
-        }
-    }
-
-    public void cleanUp() {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "cleanUp(): service=" + mService + ", session size="
-                            + mService.getSessions().size());
-                    for (MediaSession session : mService.getSessions()) {
-                        Log.d(TAG, "   session id=" + session.getId());
-                    }
-                }
-                // TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
-                List<MediaSession> sessions = mService.getSessions();
-                for (int i = 0; i < sessions.size(); i++) {
-                    sessions.get(i).close();
-                }
-                // stopSelf() would not kill service while the binder connection established by
-                // bindService() exists, and close() above will do the job instead.
-                // So stopSelf() isn't really needed, but just for sure.
-                mService.stopSelf();
-                mServiceSet.close();
-                mService = null;
-            } else if (DEBUG) {
-                Log.d(TAG, "cleanUp(): service=" + mService);
-            }
-            if (mHandler != null) {
-                mHandler.removeCallbacksAndMessages(null);
-            }
-            mSessionCallback = null;
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onDestroyed();
-                mSessionServiceCallback = null;
-            }
-            mOnGetSessionHandler = null;
-        }
-    }
-
-    public interface OnGetSessionHandler {
-        MediaSession onGetSession(ControllerInfo controllerInfo);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
deleted file mode 100644
index 5d56165..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaDescriptionCompat;
-
-import androidx.media.MediaBrowserServiceCompat;
-import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
-import androidx.media.MediaBrowserServiceCompat.Result;
-import androidx.media2.session.MediaBrowser;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat.Proxy;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaBrowser} with {@link MediaBrowserServiceCompat}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@LargeTest
-public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest extends MediaSessionTestBase {
-    private SessionToken mToken;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mToken = new SessionToken(mContext, new ComponentName(
-                mContext, MockMediaBrowserServiceCompat.class));
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onGetRootCalledByGetLibraryRoot() throws InterruptedException {
-        final String testMediaId = "testOnGetRootCalledByGetLibraryRoot";
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testMediaId, testMediaId);
-        final LibraryParams testParams = new LibraryParams.Builder()
-                .setSuggested(true).setExtras(testExtras).build();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
-                    Bundle rootHints) {
-                assertEquals(CLIENT_PACKAGE_NAME, clientPackageName);
-                if (rootHints != null && rootHints.keySet().contains(testMediaId)) {
-                    MediaTestUtils.assertEqualLibraryParams(testParams, rootHints);
-                    // This should happen because getLibraryRoot() is called with testExtras.
-                    latch.countDown();
-                }
-                // For other random connection requests.
-                return new BrowserRoot("rootId", null);
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getLibraryRoot(testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadItemCalledByGetItem() throws InterruptedException {
-        final String testMediaId = "test_media_item";
-        final MediaItem testItem = createMediaItem(testMediaId);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadItem(String itemId, Result<MediaItem> result) {
-                assertEquals(testMediaId, itemId);
-                result.sendResult(testItem);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getItem(testMediaId);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenWithoutOptionsCalledByGetChildren() throws InterruptedException {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaItem> testFullMediaItemList = createMediaItems(
-                (testPage + 1) * testPageSize);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-                assertEquals(testParentId, parentId);
-                result.sendResult(testFullMediaItemList);
-                latch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getChildren(testParentId, testPage, testPageSize, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenWithOptionsCalledByGetChildren() throws InterruptedException {
-        final String testParentId = "test_media_parent";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final List<MediaItem> testMediaItemList = createMediaItems(testPageSize);
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
-                fail("This isn't expected to be called");
-            }
-
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                    Bundle options) {
-                assertEquals(testParentId, parentId);
-                assertEquals(testPage, options.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                assertEquals(2, options.keySet().size());
-                result.sendResult(testMediaItemList);
-                latch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getChildren(testParentId, testPage, testPageSize, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onLoadChildrenCalledBySubscribe() throws InterruptedException {
-        final String testParentId = "testOnLoadChildrenCalledBySubscribe";
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-        final CountDownLatch subscribeLatch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
-                    Bundle option) {
-                assertEquals(testParentId, parentId);
-                MediaTestUtils.assertEqualLibraryParams(testParams, option);
-                result.sendResult(null);
-                subscribeLatch.countDown();
-            }
-        });
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.subscribe(testParentId, testParams);
-        assertTrue(subscribeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSearchCalledBySearch() throws InterruptedException {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-        final List<MediaItem> testFullSearchResult = createMediaItems(
-                (testPage + 1) * testPageSize + 3);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-                assertEquals(testQuery, query);
-                MediaTestUtils.assertEqualLibraryParams(testParams, extras);
-                result.sendResult(testFullSearchResult);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.search(testQuery, testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void onSearchCalledByGetSearchResult() throws InterruptedException {
-        final String testQuery = "search_query";
-        final int testPage = 2;
-        final int testPageSize = 4;
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(new Proxy() {
-            @Override
-            public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-                assertEquals(testQuery, query);
-                MediaTestUtils.assertEqualLibraryParams(testParams, extras);
-                assertEquals(testPage, extras.getInt(MediaBrowserCompat.EXTRA_PAGE));
-                assertEquals(testPageSize, extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-                result.sendResult(null);
-                latch.countDown();
-            }
-        });
-
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(mContext, mToken, true, null);
-        browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private static MediaItem createMediaItem(String mediaId) {
-        final MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId).setTitle("title: " + mediaId).build();
-        return new MediaItem(desc, MediaItem.FLAG_PLAYABLE);
-    }
-
-    private static List<MediaItem> createMediaItems(int size) {
-        final List<MediaItem> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[2].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(createMediaItem(caller + "_child_" + i));
-        }
-        return list;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
deleted file mode 100644
index 48753c7..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.CallbackMediaItem;
-import androidx.media2.common.DataSourceCallback;
-import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaParcelUtils;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.test.R;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Tests {@link MediaItem} and its subclasses.
- */
-@RunWith(Parameterized.class)
-@SmallTest
-public class MediaItemTest {
-    private final MediaItemFactory mItemFactory;
-    private final Class<?> mItemBuilderClass;
-    private Context mContext;
-    private MediaItem mTestItem;
-
-    private static final MediaItemFactory sMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putLong("MediaItemTest", 1).build();
-            return new MediaItem.Builder()
-                    .setMetadata(testMetadata)
-                    .setStartPosition(1)
-                    .setEndPosition(10)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sUriMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putString("MediaItemTest", "MediaItemTest").build();
-            return new UriMediaItem.Builder(Uri.parse("test://test"))
-                    .setMetadata(testMetadata)
-                    .setStartPosition(1)
-                    .setEndPosition(1000)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sCallbackMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            final MediaMetadata testMetadata = new MediaMetadata.Builder()
-                    .putText("MediaItemTest", "testtest").build();
-            final DataSourceCallback callback = new DataSourceCallback() {
-                @Override
-                public int readAt(long position, @NonNull byte[] buffer, int offset, int size)
-                        throws IOException {
-                    return 0;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return 0;
-                }
-
-                @Override
-                public void close() throws IOException {
-                    // no-op
-                }
-            };
-            return new CallbackMediaItem.Builder(callback)
-                    .setMetadata(testMetadata)
-                    .setStartPosition(0)
-                    .setEndPosition(0)
-                    .build();
-        }
-    };
-
-    private static final MediaItemFactory sFileMediaItemFactory = new MediaItemFactory() {
-        @Override
-        public MediaItem create(Context context) {
-            int resId = R.raw.midi8sec;
-            try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
-                return new FileMediaItem.Builder(
-                        ParcelFileDescriptor.dup(afd.getFileDescriptor())).build();
-            } catch (Exception e) {
-                return null;
-            }
-        }
-    };
-
-    @Parameterized.Parameters
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][]{
-                {sMediaItemFactory, MediaItem.Builder.class},
-                {sUriMediaItemFactory, UriMediaItem.Builder.class},
-                {sCallbackMediaItemFactory, CallbackMediaItem.Builder.class},
-                {sFileMediaItemFactory, FileMediaItem.Builder.class}});
-    }
-
-    public MediaItemTest(MediaItemFactory factory, Class<?> builderClass) {
-        mItemFactory = factory;
-        mItemBuilderClass = builderClass;
-    }
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mTestItem = mItemFactory.create(mContext);
-    }
-
-    @Test
-    public void subclass_sameProcess() {
-        final ParcelImpl parcel = MediaParcelUtils.toParcelable(mTestItem);
-
-        final MediaItem testRemoteItem = MediaParcelUtils.fromParcelable(parcel);
-        assertEquals(mTestItem, testRemoteItem);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void subclass_acrossProcessWithMediaUtils() {
-        final Parcel p = Parcel.obtain();
-        try {
-            // Mocks the binder call across the processes by using writeParcelable/readParcelable
-            // which only happens between processes. Code snippets are copied from
-            // VersionedParcelIntegTest#parcelCopy.
-            p.writeParcelable(MediaParcelUtils.toParcelable(mTestItem), 0);
-            p.setDataPosition(0);
-            final MediaItem testRemoteItem = MediaParcelUtils.fromParcelable(
-                    (ParcelImpl) p.readParcelable(MediaItem.class.getClassLoader()));
-
-            assertEquals(MediaItem.class, testRemoteItem.getClass());
-            assertEquals(mTestItem.getStartPosition(), testRemoteItem.getStartPosition());
-            assertEquals(mTestItem.getEndPosition(), testRemoteItem.getEndPosition());
-            MediaTestUtils.assertMediaMetadataEquals(
-                    mTestItem.getMetadata(), testRemoteItem.getMetadata());
-        } finally {
-            p.recycle();
-        }
-    }
-
-    @Test
-    public void subclass_acrossProcessWithParcelUtils() {
-        if (mTestItem.getClass() == MediaItem.class) {
-            return;
-        }
-        final Parcel p = Parcel.obtain();
-        try {
-            // Mocks the binder call across the processes by using writeParcelable/readParcelable
-            // which only happens between processes. Code snippets are copied from
-            // VersionedParcelIntegTest#parcelCopy.
-            p.writeParcelable(ParcelUtils.toParcelable(mTestItem), 0);
-            fail("Write to parcel should throw RuntimeException for subclass of MediaItem");
-        } catch (RuntimeException e) {
-            // Expected.
-        } finally {
-            p.recycle();
-        }
-    }
-
-    /**
-     * Tests whether the methods in MediaItem.Builder have been hidden in subclasses by overriding
-     * them all.
-     */
-    @Test
-    public void subclass_overriddenAllMethods() throws Exception {
-        Method[] mediaItemBuilderMethods = MediaItem.Builder.class.getDeclaredMethods();
-        for (int i = 0; i < mediaItemBuilderMethods.length; i++) {
-            Method mediaItemBuilderMethod = mediaItemBuilderMethods[i];
-            if (!Modifier.isPublic(mediaItemBuilderMethod.getModifiers())) {
-                continue;
-            }
-            Method subclassMethod = mItemBuilderClass.getMethod(
-                    mediaItemBuilderMethod.getName(), mediaItemBuilderMethod.getParameterTypes());
-            assertEquals(subclassMethod.getDeclaringClass(), mItemBuilderClass);
-        }
-    }
-
-    interface MediaItemFactory {
-        MediaItem create(Context context);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java
deleted file mode 100644
index c158a7a..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaLibrarySessionCallbackTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaLibraryService.LibraryParams;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession;
-import androidx.media2.session.MediaSession;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockMediaLibraryService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaLibrarySession.MediaLibrarySessionCallback}.
- *
- * TODO: Make this class extend MediaSessionCallbackTest.
- * TODO: Create MediaLibrarySessionTest which extends MediaSessionTest.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MediaLibrarySessionCallbackTest extends MediaSessionTestBase {
-
-    MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(0);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onSubscribe() throws InterruptedException {
-        final String testParentId = "testSubscribeId";
-        final LibraryParams testParams = MediaTestUtils.createLibraryParams();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
-                new MediaLibrarySession.MediaLibrarySessionCallback() {
-                    @Override
-                    public int onSubscribe(@NonNull MediaLibrarySession session,
-                            @NonNull MediaSession.ControllerInfo controller,
-                            @NonNull String parentId, LibraryParams params) {
-                        assertEquals(testParentId, parentId);
-                        MediaTestUtils.assertEqualLibraryParams(testParams, params);
-                        latch.countDown();
-                        return RESULT_SUCCESS;
-                    }
-        };
-
-        MockMediaLibraryService service = new MockMediaLibraryService();
-        service.attachBaseContext(mContext);
-
-        try (MediaLibrarySession session = new MediaLibrarySession.Builder(
-                service, mPlayer, sHandlerExecutor, sessionCallback)
-                .setId("testOnSubscribe")
-                .build()) {
-            RemoteMediaBrowser browser = createRemoteBrowser(session.getToken());
-            browser.subscribe(testParentId, testParams);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onUnsubscribe() throws InterruptedException {
-        final String testParentId = "testUnsubscribeId";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
-                new MediaLibrarySession.MediaLibrarySessionCallback() {
-                    @Override
-                    public int onUnsubscribe(@NonNull MediaLibrarySession session,
-                            @NonNull MediaSession.ControllerInfo controller,
-                            @NonNull String parentId) {
-                        assertEquals(testParentId, parentId);
-                        latch.countDown();
-                        return RESULT_SUCCESS;
-                    }
-                };
-
-        MockMediaLibraryService service = new MockMediaLibraryService();
-        service.attachBaseContext(mContext);
-
-        try (MediaLibrarySession session = new MediaLibrarySession.Builder(
-                service, mPlayer, sHandlerExecutor, sessionCallback)
-                .setId("testOnUnsubscribe")
-                .build()) {
-            RemoteMediaBrowser browser = createRemoteBrowser(session.getToken());
-            browser.unsubscribe(testParentId);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
deleted file mode 100644
index 0c6f8ab..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_RATING;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.MediaMetadata.Builder;
-import androidx.media2.common.Rating;
-import androidx.media2.session.HeartRating;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.ThumbRating;
-import androidx.media2.test.common.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MediaMetadataTest {
-    @Test
-    public void builder() {
-        final String title = "title";
-        final long discNumber = 10;
-        final Rating rating = new ThumbRating(true);
-
-        Builder builder = new Builder();
-        builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
-        builder.putLong(MediaMetadata.METADATA_KEY_DISC_NUMBER, discNumber);
-        builder.putRating(METADATA_KEY_USER_RATING, rating);
-
-        MediaMetadata metadata = builder.build();
-        assertEquals(title, metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
-        assertEquals(discNumber, metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
-        assertEquals(rating, metadata.getRating(METADATA_KEY_USER_RATING));
-    }
-
-    @Test
-    public void setExtra() {
-        final Bundle extras = new Bundle();
-        extras.putString("MediaMetadataTest", "testBuilder");
-
-        Builder builder = new Builder();
-        try {
-            builder.putLong(MediaMetadata.METADATA_KEY_EXTRAS, 1);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        builder.setExtras(extras);
-        MediaMetadata metadata = builder.build();
-        assertTrue(TestUtils.equals(extras, metadata.getExtras()));
-    }
-
-    @Test
-    public void parcelling_withSmallBitmap_bitmapPreservedAfterParcelled() {
-        // A small bitmap (160kB) that doesn't need to be scaled down.
-        final int testBitmapSize = 200;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                testBitmapSize, testBitmapSize, Bitmap.Config.ARGB_8888);
-        testBitmap.setPixel(2, 2, Color.GREEN);
-        String testKey = MediaMetadata.METADATA_KEY_ALBUM_ART;
-        MediaMetadata metadata = new Builder().putBitmap(testKey, testBitmap).build();
-
-        Parcel parcel = Parcel.obtain();
-        try {
-            // Test twice to ensure internal cache works correctly.
-            for (int i = 0; i < 2; i++) {
-                ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-                parcelImpl.writeToParcel(parcel, 0 /* flags */);
-                parcel.setDataPosition(0);
-
-                MediaMetadata metadataFromParcel =
-                        ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-                assertEquals(testBitmap, metadata.getBitmap(testKey));
-                assertEquals(testBitmapSize, testBitmap.getHeight());
-                assertEquals(testBitmapSize, testBitmap.getWidth());
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parcelling_withLargeBitmap_bitmapPreservedAfterParcelled() {
-        // A large bitmap (4MB) which exceeds the binder limit. Scaling down would happen.
-        final int testBitmapSize = 1024;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                testBitmapSize, testBitmapSize, Bitmap.Config.ARGB_8888);
-        testBitmap.setPixel(2, 2, Color.GREEN);
-        String testKey = MediaMetadata.METADATA_KEY_ALBUM_ART;
-        MediaMetadata metadata = new Builder().putBitmap(testKey, testBitmap).build();
-
-        Parcel parcel = Parcel.obtain();
-        try {
-            // Test twice to ensure internal cache works correctly.
-            for (int i = 0; i < 2; i++) {
-                ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-                parcelImpl.writeToParcel(parcel, 0 /* flags */);
-                parcel.setDataPosition(0);
-
-                MediaMetadata metadataFromParcel =
-                        ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-                assertEquals(testBitmap, metadata.getBitmap(testKey));
-                assertEquals(testBitmapSize, testBitmap.getHeight());
-                assertEquals(testBitmapSize, testBitmap.getWidth());
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parceling_withSmallBitmaps() {
-        final int bitmapCount = 100;
-        final List<String> keyList = new ArrayList<>(bitmapCount);
-        final String bitmapKeyPrefix = "bitmap_";
-        for (int i = 0; i < bitmapCount; i++) {
-            keyList.add(bitmapKeyPrefix + i);
-        }
-
-        // A small bitmap about 160kB.
-        final int originalWidth = 200;
-        final int originalHeight = 200;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        Builder builder = new Builder();
-        for (int i = 0; i < keyList.size(); i++) {
-            builder.putBitmap(keyList.get(i), testBitmap);
-        }
-
-        MediaMetadata metadata = builder.build();
-        ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-
-        // Bitmaps will not be scaled down since they are small.
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcelImpl.writeToParcel(parcel, 0 /* flags */);
-            parcel.setDataPosition(0);
-
-            MediaMetadata metadataFromParcel =
-                    ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-
-            // Check the bitmap list from the metadata.
-            Set<String> keySet = metadataFromParcel.keySet();
-            assertTrue(keySet.containsAll(keyList));
-            assertTrue(keyList.containsAll(keySet));
-
-            for (String key : keySet) {
-                Bitmap bitmap = metadataFromParcel.getBitmap(key);
-                assertNotNull(bitmap);
-                int newWidth = bitmap.getWidth();
-                int newHeight = bitmap.getHeight();
-                // The bitmaps should not have been scaled down.
-                assertEquals(newWidth, originalWidth);
-                assertEquals(newHeight, originalHeight);
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void parceling_withLargeBitmaps() {
-        final int bitmapCount = 100;
-        final List<String> keyList = new ArrayList<>(bitmapCount);
-        final String bitmapKeyPrefix = "bitmap_";
-        for (int i = 0; i < bitmapCount; i++) {
-            keyList.add(bitmapKeyPrefix + i);
-        }
-
-        // A large bitmap (64MB) which exceeds the binder limit.
-        final int originalWidth = 4096;
-        final int originalHeight = 4096;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        Builder builder = new Builder();
-        for (int i = 0; i < keyList.size(); i++) {
-            builder.putBitmap(keyList.get(i), testBitmap);
-        }
-
-        MediaMetadata metadata = builder.build();
-        ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(metadata);
-
-        // Bitmaps will be scaled down when the metadata is written to parcel.
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcelImpl.writeToParcel(parcel, 0 /* flags */);
-            parcel.setDataPosition(0);
-
-            MediaMetadata metadataFromParcel =
-                    ParcelUtils.fromParcelable(ParcelImpl.CREATOR.createFromParcel(parcel));
-
-            // Check the bitmap list from the metadata.
-            Set<String> keySet = metadataFromParcel.keySet();
-            assertTrue(keySet.containsAll(keyList));
-            assertTrue(keyList.containsAll(keySet));
-
-            for (String key : keySet) {
-                Bitmap bitmap = metadataFromParcel.getBitmap(key);
-                assertNotNull(bitmap);
-                int newWidth = bitmap.getWidth();
-                int newHeight = bitmap.getHeight();
-                assertTrue("Resulting bitmap (size=" + newWidth + "x" + newHeight + ") was not "
-                                + "scaled down. ",
-                        newWidth < originalWidth && newHeight < originalHeight);
-            }
-        } finally {
-            parcel.recycle();
-        }
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaMetadataCompat() {
-        HeartRating testRating = new HeartRating(true);
-        long testState = MediaMetadata.STATUS_DOWNLOADING;
-        String testCustomKey = "android.media.test";
-        String testCustomValue = "customValue";
-        MediaMetadata testMetadata = new Builder()
-                .putRating(METADATA_KEY_RATING, testRating)
-                .putLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS, testState)
-                .putString(testCustomKey, testCustomValue)
-                .build();
-
-        MediaMetadataCompat compat = MediaUtils.convertToMediaMetadataCompat(testMetadata);
-        assertEquals(3, compat.keySet().size());
-        RatingCompat returnedRating = compat.getRating(MediaMetadataCompat.METADATA_KEY_RATING);
-        assertEquals(RatingCompat.RATING_HEART, returnedRating.getRatingStyle());
-        assertTrue(returnedRating.hasHeart());
-        assertEquals(MediaDescriptionCompat.STATUS_DOWNLOADING,
-                compat.getLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS));
-        assertEquals(testCustomValue, compat.getString(testCustomKey));
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaItem_withoutUserRating() {
-        RatingCompat testRating = RatingCompat.newHeartRating(true);
-        long testState = MediaDescriptionCompat.STATUS_DOWNLOADING;
-        String testCustomKey = "android.media.test";
-        String testCustomValue = "customValue";
-        MediaMetadataCompat testMetadataCompat = new MediaMetadataCompat.Builder()
-                .putRating(MediaMetadataCompat.METADATA_KEY_RATING, testRating)
-                .putLong(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS, testState)
-                .putString(testCustomKey, testCustomValue)
-                .build();
-
-        MediaItem item = MediaUtils.convertToMediaItem(
-                testMetadataCompat, RatingCompat.RATING_HEART);
-        Rating returnedRating = item.getMetadata().getRating(METADATA_KEY_RATING);
-        assertTrue(returnedRating instanceof HeartRating);
-        assertTrue(returnedRating.isRated());
-        assertEquals(testRating.hasHeart(), ((HeartRating) returnedRating).hasHeart());
-        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
-        assertTrue(returnedUserRating instanceof HeartRating);
-        assertFalse(returnedUserRating.isRated());
-        assertEquals(MediaMetadata.STATUS_DOWNLOADING,
-                item.getMetadata().getLong(MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS));
-        assertFalse(item.getMetadata().containsKey(
-                MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS));
-        assertEquals(testCustomValue, item.getMetadata().getString(testCustomKey));
-    }
-
-    @Test
-    public void mediaUtils_convertToMediaItem_withUserRating() {
-        RatingCompat testRating = RatingCompat.newHeartRating(true);
-        MediaMetadataCompat testMetadataCompat = new MediaMetadataCompat.Builder()
-                .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, testRating)
-                .build();
-
-        MediaItem item = MediaUtils.convertToMediaItem(
-                testMetadataCompat, RatingCompat.RATING_HEART);
-        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
-        assertTrue(returnedUserRating instanceof HeartRating);
-        assertTrue(returnedUserRating.isRated());
-        assertTrue(((HeartRating) returnedUserRating).hasHeart());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
deleted file mode 100644
index c0f71e8..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaSession.SessionCallback}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionCallbackTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaSessionCallbackTest";
-
-    MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    public void onPostConnect_afterConnected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public void onPostConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                latch.countDown();
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnPostConnect_afterConnected").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onPostConnect_afterConnectionRejected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                return null;
-            }
-
-            @Override
-            public void onPostConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                latch.countDown();
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnPostConnect_afterConnectionRejected").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onCommandRequest() throws InterruptedException {
-        mPlayer = new MockPlayer(1);
-
-        final MockOnCommandCallback callback = new MockOnCommandCallback();
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCommandRequest")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.pause();
-            assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(mPlayer.mPauseCalled);
-            assertEquals(1, callback.commands.size());
-            assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                    (long) callback.commands.get(0).getCommandCode());
-
-            controller.play();
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-            assertFalse(mPlayer.mPauseCalled);
-            assertEquals(2, callback.commands.size());
-            assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                    (long) callback.commands.get(1).getCommandCode());
-        }
-    }
-
-    @Test
-    public void onCreateMediaItem() throws InterruptedException {
-        mPlayer = new MockPlayer(1);
-
-        final List<String> list = MediaTestUtils.createMediaIds(3);
-        final List<MediaItem> convertedList = MediaTestUtils.createPlaylist(list.size());
-
-        final MockOnCommandCallback callback = new MockOnCommandCallback() {
-            @Override
-            public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                for (int i = 0; i < list.size(); i++) {
-                    if (Objects.equals(mediaId, list.get(i))) {
-                        return convertedList.get(i);
-                    }
-                }
-                fail();
-                return null;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCreateMediaItem")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setPlaylist(list, null);
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            List<MediaItem> playerList = mPlayer.getPlaylist();
-            assertEquals(convertedList.size(), playerList.size());
-            for (int i = 0; i < playerList.size(); i++) {
-                String expected = convertedList.get(i).getMetadata().getString(
-                        MediaMetadata.METADATA_KEY_MEDIA_ID);
-                String actual = playerList.get(i).getMetadata().getString(
-                        MediaMetadata.METADATA_KEY_MEDIA_ID);
-                assertEquals(expected, actual);
-            }
-        }
-    }
-
-    @Test
-    public void onCustomCommand() throws InterruptedException {
-        // TODO(jaewan): Need to revisit with the permission.
-        final SessionCommand testCommand = new SessionCommand("testCustomCommand", null);
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "testOnCustomCommand");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                        .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
-                        .addCommand(testCommand)
-                        .build();
-                return commands;
-            }
-
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaSession session,
-                    @NonNull MediaSession.ControllerInfo controller,
-                    @NonNull SessionCommand sessionCommand, Bundle args) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testCommand, sessionCommand);
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnCustomCommand")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.sendCustomCommand(testCommand, testArgs);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onFastForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onFastForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnFastForward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.fastForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onRewind() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnRewind").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.rewind();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSkipForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSkipForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSkipForward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.skipForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSkipBackward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSkipBackward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSkipBackward").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.skipBackward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSetMediaUri() throws InterruptedException {
-        final Uri testUri = Uri.parse("foo://boo");
-        final Bundle testExtras = TestUtils.createTestBundle();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testUri, uri);
-                assertTrue(TestUtils.equals(testExtras, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSetMediaUri")
-                .build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setMediaUri(testUri, testExtras);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onSetRating() throws InterruptedException {
-        final float ratingValue = 3.5f;
-        final Rating testRating = new StarRating(5, ratingValue);
-        final String testMediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession.SessionCallback callback = new MediaSession.SessionCallback() {
-            @Override
-            public int onSetRating(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaId,
-                    @NonNull Rating rating) {
-                assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testMediaId, mediaId);
-                assertEquals(testRating, rating);
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testOnSetRating").build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-
-            controller.setRating(testMediaId, testRating);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onConnect() throws InterruptedException {
-        final AtomicReference<Bundle> connectionHints = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testOnConnect")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        // TODO: Get uid of client app's and compare.
-                        if (!CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return null;
-                        }
-                        connectionHints.set(controller.getConnectionHints());
-                        latch.countDown();
-                        return super.onConnect(session, controller);
-                    }
-                }).build()) {
-            Bundle testConnectionHints = new Bundle();
-            testConnectionHints.putString("test_key", "test_value");
-
-            RemoteMediaController controller = createRemoteController(
-                    session.getToken(), false  /* waitForConnection */, testConnectionHints);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(TestUtils.equals(testConnectionHints, connectionHints.get()));
-        }
-    }
-
-    @Test
-    public void onDisconnected() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("testOnDisconnected")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
-                        // TODO: Get uid of client app's and compare.
-                        latch.countDown();
-                    }
-                }).build()) {
-            RemoteMediaController controller = createRemoteController(session.getToken());
-            controller.close();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-
-    // TODO(jaewan): Add test for service connect rejection, when we differentiate session
-    //               active/inactive and connection accept/refuse
-    class TestSessionCallback extends MediaSession.SessionCallback {
-        CountDownLatch mLatch;
-
-        void resetLatchCount(int count) {
-            mLatch = new CountDownLatch(count);
-        }
-    }
-
-    public class MockOnCommandCallback extends MediaSession.SessionCallback {
-        public final ArrayList<SessionCommand> commands = new ArrayList<>();
-
-        @Override
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controllerInfo, @NonNull SessionCommand command) {
-            // TODO: Get uid of client app's and compare.
-            assertEquals(CLIENT_PACKAGE_NAME, controllerInfo.getPackageName());
-            assertFalse(controllerInfo.isTrusted());
-            commands.add(command);
-            if (command.getCommandCode() == SessionCommand.COMMAND_CODE_PLAYER_PAUSE) {
-                return RESULT_ERROR_INVALID_STATE;
-            }
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
deleted file mode 100644
index 1501054..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
+++ /dev/null
@@ -1,1035 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
-import static androidx.media2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionResult;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.MockRemotePlayer;
-import androidx.media2.test.service.RemoteMediaControllerCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link SessionCallback} working with {@link MediaControllerCompat}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionCallbackWithMediaControllerCompatTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaSessionCallbackTestWithMediaControllerCompat";
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
-            (Build.VERSION.SDK_INT < 21 || Build.VERSION.SDK_INT >= 24)
-                    ? CLIENT_PACKAGE_NAME : LEGACY_CONTROLLER;
-
-    PendingIntent mIntent;
-    MediaSession mSession;
-    RemoteMediaControllerCompat mController;
-    MockPlayer mPlayer;
-    AudioManager mAudioManager;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-
-        mPlayer = new MockPlayer(1);
-        if (mSession != null && !mSession.isClosed()) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setId(TAG)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                        return MediaTestUtils.createMediaItem(mediaId);
-                    }
-                })
-                .setSessionActivity(mIntent)
-                .build();
-        mController = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-        if (mController != null) {
-            mController.cleanUp();
-            mController = null;
-        }
-    }
-
-    @Test
-    public void disconnectedAfterTimeout() throws Exception {
-        CountDownLatch disconnectedLatch = new CountDownLatch(1);
-        RemoteMediaControllerCompat controller = null;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("disconnectedAfterTimeout")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    private ControllerInfo mConnectedController;
-
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            mConnectedController = controller;
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TestUtils.equals(mConnectedController, controller)) {
-                            disconnectedLatch.countDown();
-                        }
-                    }
-                })
-                .build()) {
-            // Make onDisconnected() to be called immediately after the connection.
-            session.setLegacyControllerConnectionTimeoutMs(0);
-            controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompatToken(), /* waitForConnection= */ true);
-            // Invoke any command for session to recognize the controller compat.
-            controller.getTransportControls().seekTo(111);
-            assertTrue(disconnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            if (controller != null) {
-                controller.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void connectedCallbackAfterDisconnectedByTimeout() throws Exception {
-        CountDownLatch connectedLatch = new CountDownLatch(2);
-        CountDownLatch disconnectedLatch = new CountDownLatch(1);
-        RemoteMediaControllerCompat controller = null;
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setId("connectedCallbackAfterDisconnectedByTimeout")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    private ControllerInfo mConnectedController;
-
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            mConnectedController = controller;
-                            connectedLatch.countDown();
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onDisconnected(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        if (TestUtils.equals(mConnectedController, controller)) {
-                            disconnectedLatch.countDown();
-                        }
-                    }
-                })
-                .build()) {
-            // Make onDisconnected() to be called immediately after the connection.
-            session.setLegacyControllerConnectionTimeoutMs(0);
-            controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompatToken(), /* waitForConnection= */ true);
-            // Invoke any command for session to recognize the controller compat.
-            controller.getTransportControls().seekTo(111);
-            assertTrue(disconnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Test whenter onConnect() is called again after the onDisconnected().
-            controller.getTransportControls().seekTo(111);
-
-            assertTrue(connectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            if (controller != null) {
-                controller.cleanUp();
-            }
-        }
-    }
-
-    @Test
-    public void play() {
-        mController.getTransportControls().play();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pause() {
-        mController.getTransportControls().pause();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void stop() {
-        // MediaControllerCompat#stop() will call MediaSession#pause() and MediaSession#seekTo(0).
-        // Therefore, the latch's initial count is 2.
-        MockPlayer player = new MockPlayer(2);
-        player.mCurrentPosition = 1530;
-        mSession.updatePlayer(player);
-
-        mController.getTransportControls().stop();
-        try {
-            assertTrue(player.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(player.mPauseCalled);
-        assertTrue(player.mSeekToCalled);
-        assertEquals(0, player.mSeekPosition);
-    }
-
-    @Test
-    public void prepare() {
-        mController.getTransportControls().prepare();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() {
-        final long seekPosition = 12125L;
-        mController.getTransportControls().seekTo(seekPosition);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeed() {
-        final float testSpeed = 2.0f;
-        mController.getTransportControls().setPlaybackSpeed(testSpeed);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
-        assertEquals(testSpeed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void addQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Prepare an item to add.
-        final String mediaId = "media_id";
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId)
-                .build();
-        mController.addQueueItem(desc);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(mediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void addQueueItemWithIndex() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Prepare an item to add.
-        final int testIndex = 0;
-        final String mediaId = "media_id";
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId)
-                .build();
-        mController.addQueueItem(desc, testIndex);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertEquals(mediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void removeQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Select an item to remove.
-        final int targetIndex = 3;
-        final MediaItem targetItem = playlist.get(targetIndex);
-        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder()
-                .setMediaId(targetItem.getMediaId())
-                .build();
-        mController.removeQueueItem(desc);
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void skipToPrevious() throws InterruptedException {
-        mController.getTransportControls().skipToPrevious();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNext() throws InterruptedException {
-        mController.getTransportControls().skipToNext();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToQueueItem() throws InterruptedException {
-        final int playlistSize = 10;
-
-        List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize);
-        mPlayer.mPlaylist = playlist;
-        mPlayer.notifyPlaylistChanged();
-        // Wait some time for setting the playlist.
-        Thread.sleep(TIMEOUT_MS);
-
-        // Get Queue from local MediaControllerCompat.
-        List<QueueItem> queue = mSession.getSessionCompat().getController().getQueue();
-        final int targetIndex = 3;
-        mController.getTransportControls().skipToQueueItem(queue.get(targetIndex).getQueueId());
-
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void setShuffleMode() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mController.getTransportControls().setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatMode() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mController.getTransportControls().setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void setVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int targetVolume = 50;
-        mController.setVolumeTo(targetVolume, 0 /* flags */);
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mSetVolumeToCalled);
-        assertEquals(targetVolume, (int) remotePlayer.mCurrentVolume);
-    }
-
-    @Test
-    public void adjustVolume() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer =
-                new MockRemotePlayer(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        mController.adjustVolume(direction, 0 /* flags */);
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mAdjustVolumeCalled);
-        assertEquals(direction, remotePlayer.mDirection);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        MockPlayer player = new MockPlayer(0);
-        player.setAudioAttributes(attrs);
-
-        // Replace with another player rather than setting the audio attribute of the existing
-        // player for making changes to take effect immediately.
-        mSession.updatePlayer(player);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-
-        // Set stream of the session.
-        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
-                .setLegacyStreamType(stream)
-                .build();
-        MockPlayer player = new MockPlayer(0);
-        player.setAudioAttributes(attrs);
-        // Replace with another player rather than setting the audio attribute of the existing
-        // player for making changes to take effect immediately.
-        mSession.updatePlayer(player);
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void sendCommand() throws InterruptedException {
-        // TODO(jaewan): Need to revisit with the permission.
-        final String testCommand = "test_command";
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "test_args");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                SessionCommandGroup commands = super.onConnect(session, controller);
-                SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder(commands);
-                builder.addCommand(new SessionCommand(testCommand, null));
-                return builder.build();
-            }
-
-            @NonNull
-            @Override
-            public SessionResult onCustomCommand(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller,
-                    @NonNull SessionCommand sessionCommand, Bundle args) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(testCommand, sessionCommand.getCustomAction());
-                assertTrue(TestUtils.equals(testArgs, args));
-                latch.countDown();
-                return new SessionResult(RESULT_SUCCESS, null);
-            }
-        };
-        mSession.close();
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
-        final RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        controller.sendCommand(testCommand, testArgs, null);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void controllerCallback_sessionRejects() throws Exception {
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                return null;
-            }
-        };
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-            }
-        });
-
-        // Session will not accept the controller's commands.
-        RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-        controller.getTransportControls().play();
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void fastForward() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onFastForward(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testFastForward").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().fastForward();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void rewind() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testRewind").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().rewind();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void prepareFromMediaUri() throws InterruptedException {
-        final Uri mediaId = Uri.parse("foo://bar");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(mediaId, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromMediaUri").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromUri(mediaId, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromMediaUri() throws InterruptedException {
-        final Uri request = Uri.parse("foo://bar");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(request, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromMediaUri").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromUri(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void prepareFromMediaId() throws InterruptedException {
-        final String request = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/prepareFromMediaId?id=" + request,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromMediaId").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromMediaId(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromMediaId() throws InterruptedException {
-        final String mediaId = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/playFromMediaId?id=" + mediaId,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromMediaId").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromMediaId(mediaId, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void prepareFromSearch() throws InterruptedException {
-        final String query = "test_query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/prepareFromSearch?query=" + query,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromSearch").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().prepareFromSearch(query, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPrepareCalled);
-        }
-    }
-
-    @Test
-    public void playFromSearch() throws InterruptedException {
-        final String query = "test_query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetMediaUri(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals("androidx://media2-session/playFromSearch?query=" + query,
-                        uri.toString());
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromSearch").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().playFromSearch(query, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(mPlayer.mPlayCalled);
-        }
-    }
-
-    @Test
-    public void setRating() throws InterruptedException {
-        final int ratingType = RatingCompat.RATING_5_STARS;
-        final float ratingValue = 3.5f;
-        final RatingCompat rating = RatingCompat.newStarRating(ratingType, ratingValue);
-        final String mediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onSetRating(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller, @NonNull String mediaIdOut,
-                    @NonNull Rating ratingOut) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
-                assertEquals(mediaId, mediaIdOut);
-                assertEquals(MediaUtils.convertToRating(rating), ratingOut);
-                latch.countDown();
-                return RESULT_SUCCESS;
-            }
-        };
-
-        mPlayer.mCurrentMediaItem = MediaTestUtils.createMediaItem(mediaId);
-        try (MediaSession session = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testSetRating").build()) {
-            RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, session.getSessionCompat().getSessionToken(), true);
-            controller.getTransportControls().setRating(rating);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void onCommandCallback() throws InterruptedException {
-        final ArrayList<SessionCommand> commands = new ArrayList<>();
-        final CountDownLatch latchForPause = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public int onCommandRequest(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controllerInfo, @NonNull SessionCommand command) {
-                assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controllerInfo.getPackageName());
-                assertFalse(controllerInfo.isTrusted());
-                commands.add(command);
-                if (command.getCommandCode() == SessionCommand.COMMAND_CODE_PLAYER_PAUSE) {
-                    latchForPause.countDown();
-                    return RESULT_ERROR_INVALID_STATE;
-                }
-                return RESULT_SUCCESS;
-            }
-        };
-
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mPlayer = new MockPlayer(1);
-                mSession = new MediaSession.Builder(mContext, mPlayer)
-                        .setId("testOnCommandCallback")
-                        .setSessionCallback(sHandlerExecutor, callback).build();
-            }
-        });
-        RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                mContext, mSession.getSessionCompat().getSessionToken(), true);
-
-        controller.getTransportControls().pause();
-        assertTrue(latchForPause.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(1, commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
-                (long) commands.get(0).getCommandCode());
-
-        controller.getTransportControls().play();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(2, commands.size());
-        assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PLAY,
-                (long) commands.get(1).getCommandCode());
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    @LargeTest
-    public void deadlock() throws InterruptedException {
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = null;
-            }
-        });
-
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    mSession = new MediaSession.Builder(mContext, player)
-                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                            .setId("testDeadlock").build();
-                }
-            });
-            final RemoteMediaControllerCompat controller = new RemoteMediaControllerCompat(
-                    mContext, mSession.getSessionCompat().getSessionToken(), true);
-            testHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final int state = SessionPlayer.PLAYER_STATE_ERROR;
-                    for (int i = 0; i < 100; i++) {
-                        // triggers call from session to controller.
-                        player.notifyPlayerStateChanged(state);
-                        // triggers call from controller to session.
-                        controller.getTransportControls().play();
-
-                        // Repeat above
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().pause();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().stop();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().skipToNext();
-                        player.notifyPlayerStateChanged(state);
-                        controller.getTransportControls().skipToPrevious();
-                    }
-                    // This may hang if deadlock happens.
-                    latch.countDown();
-                }
-            });
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Clean up here because sessionHandler will be removed afterwards.
-                        mSession.close();
-                        mSession = null;
-                    }
-                });
-            }
-
-            if (Build.VERSION.SDK_INT >= 18) {
-                sessionThread.quitSafely();
-                testThread.quitSafely();
-            } else {
-                sessionThread.quit();
-                testThread.quit();
-            }
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void controllerAfterSessionIsGone() throws InterruptedException {
-        mSession.close();
-        testSessionCallbackIsNotCalled();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        // Recreated session has different session stub, so previously created controller
-        // shouldn't be available.
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .setId(TAG)
-                .build();
-        testSessionCallbackIsNotCalled();
-    }
-
-    void testSessionCallbackIsNotCalled() throws InterruptedException {
-        mController.getTransportControls().play();
-        assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java
deleted file mode 100644
index 3a25481..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackWithMediaControllerTest.java
+++ /dev/null
@@ -1,1074 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
-
-import static androidx.media2.session.MediaConstants.MEDIA_URI_AUTHORITY;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_PATH_SET_MEDIA_URI;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_ID;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_QUERY_URI;
-import static androidx.media2.session.MediaConstants.MEDIA_URI_SCHEME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import androidx.media.VolumeProviderCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaUtils;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionToken;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.common.MockActivity;
-import androidx.media2.test.common.PollingCheck;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link MediaController}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSessionCompatCallbackWithMediaControllerTest extends MediaSessionTestBase {
-    private static final String TAG = "MediaControllerTest";
-
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-    private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
-
-    PendingIntent mIntent;
-    MediaSessionCompat mSession;
-    MediaSessionCallback mSessionCallback;
-    AudioManager mAudioManager;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity,
-                Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0);
-
-        mSessionCallback = new MediaSessionCallback();
-        mSession = new MediaSessionCompat(mContext, TAG + "Compat");
-        mSession.setCallback(mSessionCallback, sHandler);
-        mSession.setSessionActivity(mIntent);
-        mSession.setActive(true);
-
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-    }
-
-    private RemoteMediaController createControllerAndWaitConnection() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<SessionToken> sessionToken2 = new AtomicReference<>();
-        SessionToken.createSessionToken(mContext, mSession.getSessionToken(),
-                (compatToken, sessionToken) -> {
-                    assertTrue(sessionToken.isLegacySession());
-                    sessionToken2.set(sessionToken);
-                    latch.countDown();
-                });
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        return createRemoteController(sessionToken2.get(), true, null);
-    }
-
-    @Test
-    public void play() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.play();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void pause() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.pause();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnPauseCalled);
-    }
-
-    @Test
-    public void prepare() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.prepare();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void seekTo() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        final long seekPosition = 12125L;
-        controller.seekTo(seekPosition);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSeekToCalled);
-        assertEquals(seekPosition, mSessionCallback.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeed() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        final float testSpeed = 2.0f;
-        controller.setPlaybackSpeed(testSpeed);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetPlaybackSpeedCalled);
-        assertEquals(testSpeed, mSessionCallback.mSpeed, 0.0f);
-    }
-
-    @Test
-    public void addPlaylistItem() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final String testMediaId = "testAddPlaylistItem";
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int testIndex = 1;
-        controller.addPlaylistItem(testIndex, testMediaId);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
-
-        assertEquals(testIndex, mSessionCallback.mQueueIndex);
-        assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
-        assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
-    }
-
-    @Test
-    public void removePlaylistItem() throws Exception {
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final MediaItem itemToRemove = testList.get(1);
-        controller.removePlaylistItem(1);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForRemove);
-        assertEquals(itemToRemove.getMediaId(),
-                mSessionCallback.mQueueDescriptionForRemove.getMediaId());
-    }
-
-    @Test
-    public void replacePlaylistItem() throws Exception {
-        final int testReplaceIndex = 1;
-        // replace = remove + add
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-        final String testMediaId = "testReplacePlaylistItem";
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        mSessionCallback.reset(2);
-        controller.replacePlaylistItem(testReplaceIndex, testMediaId);
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
-        assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForRemove);
-        assertEquals(testList.get(testReplaceIndex).getMediaId(),
-                mSessionCallback.mQueueDescriptionForRemove.getMediaId());
-
-        assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
-        assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
-    }
-
-    @Test
-    public void skipToPreviousItem() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.skipToPreviousItem();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToPreviousCalled);
-    }
-
-    @Test
-    public void skipToNextItem() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.skipToNextItem();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToNextCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItem() throws Exception {
-        final int testSkipToIndex = 1;
-        final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
-        final List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
-
-        mSession.setQueue(testQueue);
-        mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        mSessionCallback.reset(1);
-        controller.skipToPlaylistItem(testSkipToIndex);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSkipToQueueItemCalled);
-        assertEquals(testQueue.get(testSkipToIndex).getQueueId(), mSessionCallback.mQueueItemId);
-    }
-
-    @Test
-    public void setShuffleMode() throws Exception {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-
-        mSession.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setShuffleMode(testShuffleMode);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mSessionCallback.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatMode() throws Exception {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_ALL;
-
-        mSession.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setRepeatMode(testRepeatMode);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mSessionCallback.mRepeatMode);
-    }
-
-    @Test
-    public void setVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-        mSession.setPlaybackToRemote(volumeProvider);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int targetVolume = 50;
-        controller.setVolumeTo(targetVolume, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mSetVolumeToCalled);
-        assertEquals(targetVolume, volumeProvider.mVolume);
-    }
-
-    @Test
-    public void adjustVolume() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-        mSession.setPlaybackToRemote(volumeProvider);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        controller.adjustVolume(direction, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mAdjustVolumeCalled);
-        assertEquals(direction, volumeProvider.mDirection);
-    }
-
-    @Test
-    public void setVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-        // Set stream of the session.
-        mSession.setPlaybackToLocal(stream);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void adjustVolumeWithLocalVolume() throws Exception {
-        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
-            // This test is not eligible for this device.
-            return;
-        }
-
-        // Here, we intentionally choose STREAM_ALARM in order not to consider
-        // 'Do Not Disturb' or 'Volume limit'.
-        final int stream = AudioManager.STREAM_ALARM;
-        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-        final int minVolume =
-                Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
-        Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
-        if (maxVolume <= minVolume) {
-            return;
-        }
-        // Set stream of the session.
-        mSession.setPlaybackToLocal(stream);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-
-        final int originalVolume = mAudioManager.getStreamVolume(stream);
-        final int direction = originalVolume == minVolume
-                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
-        final int targetVolume = originalVolume + direction;
-        Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
-
-        controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
-        new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                return targetVolume == mAudioManager.getStreamVolume(stream);
-            }
-        }.run();
-
-        // Set back to original volume.
-        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
-    }
-
-    @Test
-    public void sendCustomCommand() throws Exception {
-        final String command = "test_custom_command";
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "test_args");
-        final SessionCommand testCommand = new SessionCommand(command, null);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.sendCustomCommand(testCommand, testArgs);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnCommandCalled);
-        assertEquals(command, mSessionCallback.mCommand);
-        assertTrue(TestUtils.equals(testArgs, mSessionCallback.mExtras));
-    }
-
-    @Test
-    public void fastForward() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.fastForward();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnFastForwardCalled);
-    }
-
-    @Test
-    public void rewind() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.rewind();
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertEquals(true, mSessionCallback.mOnRewindCalled);
-    }
-
-    @Test
-    public void setRating() throws Exception {
-        final float ratingValue = 3.5f;
-        final Rating rating2 = new StarRating(5, ratingValue);
-        final String mediaId = "media_id";
-        final MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId).build();
-        mSession.setMetadata(metadata);
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setRating(mediaId, rating2);
-        assertTrue(mSessionCallback.await(TIME_OUT_MS));
-        assertTrue(mSessionCallback.mOnSetRatingCalled);
-        assertEquals(rating2, MediaUtils.convertToRating(mSessionCallback.mRating));
-    }
-
-    @Test
-    public void setMediaUri_ignored() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(Uri.parse("androidx://test?test=xx"), /* extras= */ null);
-
-        assertFalse(mSessionCallback.await(TIMEOUT_MS));
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromMediaId() throws Exception {
-        String testMediaId = "anyMediaId";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromMediaIdCalled);
-        assertEquals(testMediaId, mSessionCallback.mMediaId);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mQuery);
-        assertNull(mSessionCallback.mUri);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromSearch() throws Exception {
-        String testSearchQuery = "anyQuery";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromSearchCalled);
-        assertEquals(testSearchQuery, mSessionCallback.mQuery);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mUri);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepare_callsPrepareFromUri() throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(
-                                MEDIA_URI_QUERY_URI, testMediaUri.toString()).build(),
-                testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_withoutFormattingFollowedByPrepare_callsPrepareFromUri()
-            throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(testMediaUri, testExtras);
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertFalse(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromMediaId() throws Exception {
-        String testMediaId = "anyMediaId";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromMediaIdCalled);
-        assertEquals(testMediaId, mSessionCallback.mMediaId);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mQuery);
-        assertNull(mSessionCallback.mUri);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromSearch() throws Exception {
-        String testSearchQuery = "anyQuery";
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromSearchCalled);
-        assertEquals(testSearchQuery, mSessionCallback.mQuery);
-        assertTrue(TestUtils.equals(testExtras, mSessionCallback.mExtras));
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mUri);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlay_callsPlayFromUri() throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(
-                new Uri.Builder()
-                        .scheme(MEDIA_URI_SCHEME)
-                        .authority(MEDIA_URI_AUTHORITY)
-                        .path(MEDIA_URI_PATH_SET_MEDIA_URI)
-                        .appendQueryParameter(
-                                MEDIA_URI_QUERY_URI, testMediaUri.toString()).build(),
-                testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_withoutFormattingFollowedByPlay_callsPlayFromUri()
-            throws Exception {
-        Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
-        Bundle testExtras = new Bundle();
-        testExtras.putString("testKey", "testValue");
-
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(1);
-
-        controller.setMediaUri(testMediaUri, testExtras);
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(testMediaUri, mSessionCallback.mUri);
-        assertNull(mSessionCallback.mMediaId);
-        assertNull(mSessionCallback.mQuery);
-        assertEquals(0, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_followedByPrepareTwice_callsPrepareFromUriAndPrepare()
-            throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        controller.setMediaUri(Uri.parse("androidx://test"), null);
-
-        controller.prepare();
-        controller.prepare();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertTrue(mSessionCallback.mOnPrepareCalled);
-    }
-
-    @Test
-    public void setMediaUri_followedByPlayTwice_callsPlayFromUriAndPlay() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        controller.setMediaUri(Uri.parse("androidx://test"), /* extras= */ null);
-
-        controller.play();
-        controller.play();
-
-        assertTrue(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPlayFromUriCalled);
-        assertEquals(1, mSessionCallback.mOnPlayCalledCount);
-    }
-
-    @Test
-    public void setMediaUri_multipleCalls_skipped() throws Exception {
-        RemoteMediaController controller = createControllerAndWaitConnection();
-        mSessionCallback.reset(2);
-
-        Uri testUri1 = Uri.parse("androidx://test1");
-        Uri testUri2 = Uri.parse("androidx://test2");
-        controller.setMediaUri(testUri1, /* extras= */ null);
-        controller.setMediaUri(testUri2, /* extras= */ null);
-        controller.prepare();
-
-        assertFalse(mSessionCallback.await(TIMEOUT_MS));
-        assertTrue(mSessionCallback.mOnPrepareFromUriCalled);
-        assertEquals(testUri2, mSessionCallback.mUri);
-    }
-
-    private void setPlaybackState(int state) {
-        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
-        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
-                .setState(state, 0L, 0.0f).build();
-        mSession.setPlaybackState(playbackState);
-    }
-
-    class TestVolumeProvider extends VolumeProviderCompat {
-        final CountDownLatch mLatch = new CountDownLatch(1);
-        boolean mSetVolumeToCalled;
-        boolean mAdjustVolumeCalled;
-        int mVolume;
-        int mDirection;
-
-        TestVolumeProvider(int controlType, int maxVolume, int currentVolume) {
-            super(controlType, maxVolume, currentVolume);
-        }
-
-        @Override
-        public void onSetVolumeTo(int volume) {
-            mSetVolumeToCalled = true;
-            mVolume = volume;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAdjustVolume(int direction) {
-            mAdjustVolumeCalled = true;
-            mDirection = direction;
-            mLatch.countDown();
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSessionCompat.Callback {
-        private CountDownLatch mLatch = new CountDownLatch(1);
-        private long mSeekPosition;
-        private float mSpeed;
-        private long mQueueItemId;
-        private RatingCompat mRating;
-        private String mMediaId;
-        private String mQuery;
-        private Uri mUri;
-        private String mAction;
-        private String mCommand;
-        private Bundle mExtras;
-        private ResultReceiver mCommandCallback;
-        private boolean mCaptioningEnabled;
-        private int mRepeatMode;
-        private int mShuffleMode;
-        private int mQueueIndex;
-        private MediaDescriptionCompat mQueueDescriptionForAdd;
-        private MediaDescriptionCompat mQueueDescriptionForRemove;
-
-        private int mOnPlayCalledCount;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-        private boolean mOnSeekToCalled;
-        private boolean mOnSetPlaybackSpeedCalled;
-        private boolean mOnSkipToQueueItemCalled;
-        private boolean mOnSetRatingCalled;
-        private boolean mOnPlayFromMediaIdCalled;
-        private boolean mOnPlayFromSearchCalled;
-        private boolean mOnPlayFromUriCalled;
-        private boolean mOnCustomActionCalled;
-        private boolean mOnCommandCalled;
-        private boolean mOnPrepareCalled;
-        private boolean mOnPrepareFromMediaIdCalled;
-        private boolean mOnPrepareFromSearchCalled;
-        private boolean mOnPrepareFromUriCalled;
-        private boolean mOnSetCaptioningEnabledCalled;
-        private boolean mOnSetRepeatModeCalled;
-        private boolean mOnSetShuffleModeCalled;
-        private boolean mOnAddQueueItemCalled;
-        private boolean mOnAddQueueItemAtCalled;
-        private boolean mOnRemoveQueueItemCalled;
-
-        public void reset(int count) {
-            mLatch = new CountDownLatch(count);
-            mSeekPosition = -1;
-            mSpeed = -1.0f;
-            mQueueItemId = -1;
-            mRating = null;
-            mMediaId = null;
-            mQuery = null;
-            mUri = null;
-            mAction = null;
-            mExtras = null;
-            mCommand = null;
-            mCommandCallback = null;
-            mCaptioningEnabled = false;
-            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
-            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
-            mQueueIndex = -1;
-            mQueueDescriptionForAdd = null;
-            mQueueDescriptionForRemove = null;
-
-            mOnPlayCalledCount = 0;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-            mOnSkipToQueueItemCalled = false;
-            mOnSeekToCalled = false;
-            mOnSetPlaybackSpeedCalled = false;
-            mOnSetRatingCalled = false;
-            mOnPlayFromMediaIdCalled = false;
-            mOnPlayFromSearchCalled = false;
-            mOnPlayFromUriCalled = false;
-            mOnCustomActionCalled = false;
-            mOnCommandCalled = false;
-            mOnPrepareCalled = false;
-            mOnPrepareFromMediaIdCalled = false;
-            mOnPrepareFromSearchCalled = false;
-            mOnPrepareFromUriCalled = false;
-            mOnSetCaptioningEnabledCalled = false;
-            mOnSetRepeatModeCalled = false;
-            mOnSetShuffleModeCalled = false;
-            mOnAddQueueItemCalled = false;
-            mOnAddQueueItemAtCalled = false;
-            mOnRemoveQueueItemCalled = false;
-        }
-
-        public boolean await(long timeoutMs) {
-            try {
-                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        @Override
-        public void onPlay() {
-            mOnPlayCalledCount++;
-            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPause() {
-            mOnPauseCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onStop() {
-            mOnStopCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onFastForward() {
-            mOnFastForwardCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRewind() {
-            mOnRewindCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            mOnSkipToPreviousCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToNext() {
-            mOnSkipToNextCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSeekTo(long pos) {
-            mOnSeekToCalled = true;
-            mSeekPosition = pos;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetPlaybackSpeed(float speed) {
-            mOnSetPlaybackSpeedCalled = true;
-            mSpeed = speed;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetRating(RatingCompat rating) {
-            mOnSetRatingCalled = true;
-            mRating = rating;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            mOnPlayFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromSearch(String query, Bundle extras) {
-            mOnPlayFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromUri(Uri uri, Bundle extras) {
-            mOnPlayFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onCustomAction(String action, Bundle extras) {
-            mOnCustomActionCalled = true;
-            mAction = action;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToQueueItem(long id) {
-            mOnSkipToQueueItemCalled = true;
-            mQueueItemId = id;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
-            mOnCommandCalled = true;
-            mCommand = command;
-            mExtras = extras;
-            mCommandCallback = cb;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepare() {
-            mOnPrepareCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
-            mOnPrepareFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromSearch(String query, Bundle extras) {
-            mOnPrepareFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromUri(Uri uri, Bundle extras) {
-            mOnPrepareFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetRepeatMode(int repeatMode) {
-            mOnSetRepeatModeCalled = true;
-            mRepeatMode = repeatMode;
-            mSession.setRepeatMode(repeatMode);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description) {
-            mOnAddQueueItemCalled = true;
-            mQueueDescriptionForAdd = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
-            mOnAddQueueItemAtCalled = true;
-            mQueueIndex = index;
-            mQueueDescriptionForAdd = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRemoveQueueItem(MediaDescriptionCompat description) {
-            mOnRemoveQueueItemCalled = true;
-            mQueueDescriptionForRemove = description;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetCaptioningEnabled(boolean enabled) {
-            mOnSetCaptioningEnabledCalled = true;
-            mCaptioningEnabled = enabled;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSetShuffleMode(int shuffleMode) {
-            mOnSetShuffleModeCalled = true;
-            mShuffleMode = shuffleMode;
-            mSession.setShuffleMode(shuffleMode);
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java
deleted file mode 100644
index b015985..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionManagerTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.media2.session.MediaSessionManager;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MockMediaBrowserServiceCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Tests {@link MediaSessionManagerTest}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSessionManagerTest extends MediaTestBase {
-    private Context mContext;
-
-    private static final ComponentName MOCK_BROWSER_SERVICE_COMPAT_NAME = new ComponentName(
-            SERVICE_PACKAGE_NAME, MockMediaBrowserServiceCompat.class.getCanonicalName());
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @Test
-    public void getSessionServiceTokens() {
-        boolean hasMockBrowserServiceCompat = false;
-        boolean hasMockSessionService2 = false;
-        boolean hasMockLibraryService2 = false;
-        MediaSessionManager sessionManager = MediaSessionManager.getInstance(mContext);
-        Set<SessionToken> serviceTokens = sessionManager.getSessionServiceTokens();
-        for (SessionToken token : serviceTokens) {
-            ComponentName componentName = token.getComponentName();
-            if (MOCK_BROWSER_SERVICE_COMPAT_NAME.equals(componentName)) {
-                hasMockBrowserServiceCompat = true;
-            } else if (MOCK_MEDIA2_SESSION_SERVICE.equals(componentName)) {
-                hasMockSessionService2 = true;
-            } else if (MOCK_MEDIA2_LIBRARY_SERVICE.equals(componentName)) {
-                hasMockLibraryService2 = true;
-            }
-        }
-        assertTrue(hasMockBrowserServiceCompat);
-        assertTrue(hasMockSessionService2);
-        assertTrue(hasMockLibraryService2);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
deleted file mode 100644
index b9520b8..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.common.MediaMetadata.BROWSABLE_TYPE_NONE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_BROWSABLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
-import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.media2.test.service.TestServiceRegistry;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Manual test of {@link MediaSessionService} for showing/removing notification
- * when the playback is started/ended.
- * <p>
- * This test is a manual test, which means the one who runs this test should keep looking at the
- * device and check whether the notification is shown/removed.
- */
-@LargeTest
-public class MediaSessionServiceNotificationTest extends MediaSessionTestBase {
-    private static final long NOTIFICATION_SHOW_TIME_MS = 15000;
-
-    MediaSession mSession;
-    MockPlayer mPlayer;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(true);
-        TestServiceRegistry.getInstance().setHandler(sHandler);
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-    }
-
-    @Test
-    @Ignore("Comment out this line and manually run the test.")
-    public void notification() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                    mSession = session;
-                    // Change the player and playlist agent with ours.
-                    session.updatePlayer(mPlayer);
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        // Create a controller to start the service.
-        RemoteMediaController controller = createRemoteController(
-                new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
-
-        // Set current media item.
-        Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
-                androidx.media2.test.service.R.drawable.big_buck_bunny);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
-                .putText(METADATA_KEY_ARTIST, "Test Artist Name")
-                .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-        mPlayer.mCurrentMediaItem = new MediaItem.Builder()
-                        .setMetadata(metadata)
-                        .build();
-
-        // Notification should be shown. Clicking play/pause button will change the player state.
-        // When playing, the notification will not be removed by swiping horizontally.
-        // When paused, the notification can be swiped away.
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-    }
-
-    @Test
-    @Ignore("Comment out this line and manually run the test.")
-    public void notificationUpdatedWhenCurrentMediaItemChanged() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                    mSession = session;
-                    // Change the player and playlist agent with ours.
-                    session.updatePlayer(mPlayer);
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        // Create a controller to start the service.
-        RemoteMediaController controller = createRemoteController(
-                new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
-
-        // Set current media item.
-        Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
-                androidx.media2.test.service.R.drawable.big_buck_bunny);
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
-                .putText(METADATA_KEY_ARTIST, "Test Artist Name")
-                .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-        mPlayer.mCurrentMediaItem = new MediaItem.Builder()
-                .setMetadata(metadata)
-                .build();
-
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        // At this point, the notification should be shown.
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-
-        // Set a new media item. (current media item is changed)
-        MediaMetadata newMetadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, "New media ID")
-                .putText(METADATA_KEY_DISPLAY_TITLE, "New Song Name")
-                .putText(METADATA_KEY_ARTIST, "New Artist Name")
-                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
-                .putLong(METADATA_KEY_PLAYABLE, 1)
-                .build();
-
-        MediaItem newItem = new MediaItem.Builder().setMetadata(newMetadata).build();
-        mPlayer.mCurrentMediaItem = newItem;
-
-        // Calling this should update the notification with the new metadata.
-        mPlayer.notifyCurrentMediaItemChanged(newItem);
-        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
deleted file mode 100644
index 0341a15..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.MediaSessionService;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MockMediaSessionService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.media2.test.service.TestServiceRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSessionService}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@MediumTest
-public class MediaSessionServiceTest extends MediaSessionTestBase {
-    private SessionToken mToken;
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        TestServiceRegistry.getInstance().cleanUp();
-        TestServiceRegistry.getInstance().setHandler(sHandler);
-        mToken = new SessionToken(mContext,
-                new ComponentName(mContext, MockMediaSessionService.class));
-    }
-
-    @Override
-    @After
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * is called when controller tries to connect, with the proper arguments.
-     */
-    @Test
-    public void onGetSessionIsCalled() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        controllerInfoList.add(controllerInfo);
-                        latch.countDown();
-                        return null;
-                    }
-                });
-
-        Bundle testHints = new Bundle();
-        testHints.putString("test_key", "test_value");
-        RemoteMediaController controller = createRemoteController(
-                mToken, /*waitForConnection=*/false, testHints);
-
-        // onGetSession() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(CLIENT_PACKAGE_NAME, controllerInfoList.get(0).getPackageName());
-        assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-    }
-
-
-    /**
-     * Tests whether the controller is connected to the session which is returned from
-     * {@link MediaSessionService#onGetSession(ControllerInfo)}.
-     * Also checks whether the connection hints are properly passed to
-     * {@link MediaSession.SessionCallback#onConnect(MediaSession, ControllerInfo)}.
-     */
-    @Test
-    public void onGetSession_returnsSession() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try (MediaSession testSession = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId("testOnGetSession_returnsSession")
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller) {
-                        controllerInfoList.add(controller);
-                        latch.countDown();
-                        return new SessionCommandGroup.Builder().build();
-                    }
-                }).build()) {
-
-            TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                    new TestServiceRegistry.OnGetSessionHandler() {
-                        @Override
-                        public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                            return testSession;
-                        }
-                    });
-
-            Bundle testHints = new Bundle();
-            testHints.putString("test_key", "test_value");
-            RemoteMediaController controller = createRemoteController(mToken, true, testHints);
-
-            // MediaSession.SessionCallback#onConnect() should be called.
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(CLIENT_PACKAGE_NAME, controllerInfoList.get(0).getPackageName());
-            assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-
-            // The controller should be connected to the right session.
-            assertNotEquals(mToken, controller.getConnectedSessionToken());
-            assertEquals(testSession.getToken(), controller.getConnectedSessionToken());
-        }
-    }
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * can return different sessions for different controllers.
-     */
-    @Test
-    @Ignore("Flaky: b/291281118")
-    public void onGetSession_returnsDifferentSessions() {
-        final List<SessionToken> tokens = new ArrayList<>();
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        MediaSession session = createMediaSession(
-                                "testOnGetSession_returnsDifferentSessions"
-                                        + System.currentTimeMillis());
-                        tokens.add(session.getToken());
-                        return session;
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-
-        assertNotEquals(controller1.getConnectedSessionToken(),
-                controller2.getConnectedSessionToken());
-        assertEquals(tokens.get(0), controller1.getConnectedSessionToken());
-        assertEquals(tokens.get(1), controller2.getConnectedSessionToken());
-    }
-
-
-    /**
-     * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)}
-     * can reject incoming connection by returning null.
-     */
-    @Test
-    public void onGetSession_rejectsConnection() throws InterruptedException {
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        return null;
-                    }
-                });
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaController controller = new MediaController.Builder(mContext)
-                .setSessionToken(mToken)
-                .setControllerCallback(sHandlerExecutor, new MediaController.ControllerCallback() {
-                    @Override
-                    public void onDisconnected(@NonNull MediaController controller) {
-                        latch.countDown();
-                    }
-                })
-                .build();
-
-        // MediaController2.ControllerCallback#onDisconnected() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getConnectedToken());
-        controller.close();
-    }
-
-    @Test
-    public void allControllersDisconnected_oneSession() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(
-                new TestServiceRegistry.SessionServiceCallback() {
-                    @Override
-                    public void onCreated() {
-                        // no-op
-                    }
-
-                    @Override
-                    public void onDestroyed() {
-                        latch.countDown();
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-        controller1.close();
-        controller2.close();
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void allControllersDisconnected_multipleSessions() throws InterruptedException {
-        TestServiceRegistry.getInstance().setOnGetSessionHandler(
-                new TestServiceRegistry.OnGetSessionHandler() {
-                    @Override
-                    public MediaSession onGetSession(ControllerInfo controllerInfo) {
-                        return createMediaSession("testAllControllersDisconnected"
-                                + System.currentTimeMillis());
-                    }
-                });
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(
-                new TestServiceRegistry.SessionServiceCallback() {
-                    @Override
-                    public void onCreated() {
-                        // no-op
-                    }
-
-                    @Override
-                    public void onDestroyed() {
-                        latch.countDown();
-                    }
-                });
-
-        RemoteMediaController controller1 = createRemoteController(mToken, true, null);
-        RemoteMediaController controller2 = createRemoteController(mToken, true, null);
-
-        controller1.close();
-        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
-
-        // Service should be closed only when all controllers are closed.
-        controller2.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void getSessions() throws Exception {
-        RemoteMediaController controller = createRemoteController(mToken, true, null);
-        MediaSessionService service =
-                TestServiceRegistry.getInstance().getServiceInstanceBlocking();
-        try (MediaSession session = createMediaSession("testGetSessions")) {
-            service.addSession(session);
-            List<MediaSession> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            service.removeSession(session);
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    @Test
-    public void addSessions_removedWhenClose() throws Exception {
-        RemoteMediaController controller = createRemoteController(mToken, true, null);
-        MediaSessionService service =
-                TestServiceRegistry.getInstance().getServiceInstanceBlocking();
-        try (MediaSession session = createMediaSession("testAddSessions_removedWhenClose")) {
-            service.addSession(session);
-            List<MediaSession> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            session.close();
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    private MediaSession createMediaSession(String id) {
-        return new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId(id)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                .build();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
deleted file mode 100644
index d67e84a..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.SessionCallback;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.CustomParcelable;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession}.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSessionTest extends MediaSessionTestBase {
-
-    private static final String TAG = "MediaSessionTest";
-
-    private MediaSession mSession;
-    private MockPlayer mPlayer;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-
-        if (mSession != null && !mSession.isClosed()) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setId(TAG)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                }).build();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void builder() {
-        MediaSession.Builder builder;
-        try {
-            builder = new MediaSession.Builder(mContext, null);
-            fail("null player shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setId(null);
-            fail("null id shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setExtras(null);
-            fail("null extras shouldn't be allowed");
-        } catch (NullPointerException e) {
-            // expected. pass-through
-        }
-        try {
-            Bundle extras = new Bundle();
-            extras.putParcelable("key", new CustomParcelable(1));
-            builder = new MediaSession.Builder(mContext, mPlayer);
-            builder.setExtras(extras);
-            // TODO(b/220842943): Re-enable for T and beyond once the version of media2-session
-            // used in version-compat-tests/previous/service/build.gradle is one that includes
-            // https://r.android.com/1950077.
-            if (Build.VERSION.SDK_INT < 33) {
-                fail("custom parcelables shouldn't be allowed for extras");
-            }
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-    }
-
-    @Test
-    public void getDuration() throws Exception {
-        final long testDuration = 9999;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        mPlayer.mDuration = testDuration;
-        assertEquals(testDuration, mSession.getPlayer().getDuration());
-    }
-
-    @Test
-    public void getPlaybackSpeed() throws Exception {
-        final float speed = 1.5f;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        mPlayer.setPlaybackSpeed(speed);
-        assertEquals(speed, mSession.getPlayer().getPlaybackSpeed(), 0.0f);
-    }
-
-    @Test
-    public void getPlayerState() {
-        final int state = SessionPlayer.PLAYER_STATE_PLAYING;
-        mPlayer.mLastPlayerState = state;
-        assertEquals(state, mSession.getPlayer().getPlayerState());
-    }
-
-    @Test
-    public void getBufferingState() {
-        final int bufferingState = SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
-        mPlayer.mLastBufferingState = bufferingState;
-        assertEquals(bufferingState, mSession.getPlayer().getBufferingState());
-    }
-
-    @Test
-    public void getPosition() {
-        final long position = 150000;
-        mPlayer.mCurrentPosition = position;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        assertEquals(position, mSession.getPlayer().getCurrentPosition());
-    }
-
-    @Test
-    public void getBufferedPosition() {
-        final long bufferedPosition = 900000;
-        mPlayer.mBufferedPosition = bufferedPosition;
-        mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PAUSED;
-        assertEquals(bufferedPosition, mSession.getPlayer().getBufferedPosition());
-    }
-
-    @Test
-    public void getCurrentMediaItem() {
-        MediaItem item = MediaTestUtils.createMediaItemWithMetadata();
-        mPlayer.mCurrentMediaItem = item;
-        assertEquals(item, mSession.getPlayer().getCurrentMediaItem());
-    }
-
-    @Test
-    public void getPlaylist() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mPlayer.mPlaylist = list;
-        assertEquals(list, mSession.getPlayer().getPlaylist());
-    }
-
-    @Test
-    public void getPlaylistMetadata() {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mPlayer.mMetadata = testMetadata;
-        assertEquals(testMetadata, mSession.getPlayer().getPlaylistMetadata());
-    }
-
-    @Test
-    public void getShuffleMode() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mPlayer.setShuffleMode(testShuffleMode);
-        assertEquals(testShuffleMode, mSession.getPlayer().getShuffleMode());
-    }
-
-    @Test
-    public void getRepeatMode() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mPlayer.setRepeatMode(testRepeatMode);
-        assertEquals(testRepeatMode, mSession.getPlayer().getRepeatMode());
-    }
-
-    @Test
-    public void updatePlayer() throws Exception {
-        MockPlayer player = new MockPlayer(0);
-
-        // Test if setPlayer doesn't crash with various situations.
-        mSession.updatePlayer(mPlayer);
-        assertEquals(mPlayer, mSession.getPlayer());
-
-        mSession.updatePlayer(player);
-        assertEquals(player, mSession.getPlayer());
-    }
-
-    @Test
-    public void getCurrentMediaItem_withMetadata_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1023;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mSession.getPlayer().getCurrentMediaItem().getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-        });
-    }
-
-    @Test
-    public void getCurrentMediaItem_withoutMetadata_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1055;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItem("testCurrentMediaItemChanged");
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mSession.getPlayer().getCurrentMediaItem().getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-        });
-    }
-
-    @Test
-    public void getCurrentMediaItem_withMetadataUpdated_returnsMediaItemWithDuration()
-            throws InterruptedException {
-        long testDuration = 1023;
-        MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        String testDisplayTitle = "testDisplayTitle";
-        MediaMetadata testMetadata = new MediaMetadata.Builder(testMediaItem.getMetadata())
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testDisplayTitle).build();
-
-        mPlayer.mDuration = testDuration;
-        mPlayer.mCurrentMediaItem = testMediaItem;
-        mPlayer.notifyCurrentMediaItemChanged(testMediaItem);
-        mPlayer.mCurrentMediaItem.setMetadata(testMetadata);
-        sHandler.postAndSync(() -> {
-            assertEquals(testDuration,
-                    mPlayer.mCurrentMediaItem.getMetadata().getLong(
-                            MediaMetadata.METADATA_KEY_DURATION));
-            assertEquals(testDisplayTitle,
-                    mPlayer.mCurrentMediaItem.getMetadata().getText(
-                            MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
-        });
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    @LargeTest
-    public void deadlock() throws InterruptedException {
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession.close();
-                mSession = null;
-            }
-        });
-
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    mSession = new MediaSession.Builder(mContext, mPlayer)
-                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                            .setId("testDeadlock").build();
-                }
-            });
-            final RemoteMediaController controller = createRemoteController(
-                    mSession.getToken());
-            testHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final int state = SessionPlayer.PLAYER_STATE_ERROR;
-                    for (int i = 0; i < 100; i++) {
-                        Log.d(TAG, "testDeadlock for-loop started: index=" + i);
-                        long startTime = SystemClock.elapsedRealtime();
-
-                        // triggers call from session to controller.
-                        player.notifyPlayerStateChanged(state);
-                        long endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "1) Time spent on API call(ms): " + (endTime - startTime));
-
-                        // triggers call from controller to session.
-                        startTime = endTime;
-                        controller.play();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "2) Time spent on API call(ms): " + (endTime - startTime));
-
-                        // Repeat above
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "3) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.pause();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "4) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "5) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.seekTo(0);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "6) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "7) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.skipToNextItem();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "8) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        player.notifyPlayerStateChanged(state);
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "9) Time spent on API call(ms): " + (endTime - startTime));
-
-                        startTime = endTime;
-                        controller.skipToPreviousItem();
-                        endTime = SystemClock.elapsedRealtime();
-                        Log.d(TAG, "10) Time spent on API call(ms): " + (endTime - startTime));
-                    }
-                    // This may hang if deadlock happens.
-                    latch.countDown();
-                }
-            });
-            assertTrue(latch.await(3, TimeUnit.SECONDS));
-        } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Clean up here because sessionHandler will be removed afterwards.
-                        mSession.close();
-                        mSession = null;
-                    }
-                });
-            }
-
-            if (Build.VERSION.SDK_INT >= 18) {
-                sessionThread.quitSafely();
-                testThread.quitSafely();
-            } else {
-                sessionThread.quit();
-                testThread.quit();
-            }
-        }
-    }
-
-    @Test
-    public void creatingTwoSessionWithSameId() {
-        final String sessionId = "testSessionId";
-        MediaSession session = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId(sessionId)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                .build();
-
-        MediaSession.Builder builderWithSameId =
-                new MediaSession.Builder(mContext, new MockPlayer(0));
-        try {
-            builderWithSameId.setId(sessionId)
-                    .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {})
-                    .build();
-            fail("Creating a new session with the same ID in a process should not be allowed");
-        } catch (IllegalStateException e) {
-            // expected. pass-through
-        }
-
-        session.close();
-        // Creating a new session with ID of the closed session is okay.
-        MediaSession sessionWithSameId = builderWithSameId.build();
-        sessionWithSameId.close();
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
deleted file mode 100644
index 40529f8..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils.SyncHandler;
-import androidx.media2.test.service.RemoteMediaBrowser;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSessionTestBase extends MediaTestBase {
-    static final int TIMEOUT_MS = 1000;
-    static final int WAIT_TIME_FOR_NO_RESPONSE_MS = 500;
-
-    static SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private List<RemoteMediaController> mControllers = new ArrayList<>();
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = new Executor() {
-                @Override
-                public void execute(Runnable runnable) {
-                    SyncHandler handler;
-                    synchronized (MediaSessionTestBase.class) {
-                        handler = sHandler;
-                    }
-                    if (handler != null) {
-                        handler.post(runnable);
-                    }
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSessionTestBase.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (int i = 0; i < mControllers.size(); i++) {
-            mControllers.get(i).cleanUp();
-        }
-    }
-
-    final RemoteMediaController createRemoteController(SessionToken token) {
-        return createRemoteController(token, true, null);
-    }
-
-    final RemoteMediaController createRemoteController(@NonNull SessionToken token,
-            boolean waitForConnection, Bundle connectionHints) {
-        RemoteMediaController controller = new RemoteMediaController(
-                mContext, token, connectionHints, waitForConnection);
-        mControllers.add(controller);
-        return controller;
-    }
-
-    final RemoteMediaBrowser createRemoteBrowser(@NonNull SessionToken token) {
-        RemoteMediaBrowser browser = new RemoteMediaBrowser(
-                mContext, token, true /* waitForConnection */, null /* connectionHints */);
-        mControllers.add(browser);
-        return browser;
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
deleted file mode 100644
index c12f098..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.SERVICE_PACKAGE_NAME;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.os.Build;
-import android.view.KeyEvent;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession} whether it handles key events correctly.
- * In order to get the media key events, the player state is set to 'Playing' before every test
- * method.
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSession_KeyEventTest extends MediaSessionTestBase {
-    private static String sExpectedControllerPackageName;
-
-    // Intentionally member variable to prevent GC while playback is running.
-    // Should be only used on the sHandler.
-    private MediaPlayer mMediaPlayer;
-
-    private AudioManager mAudioManager;
-    private MediaSession mSession;
-    private MockPlayer mPlayer;
-    private TestSessionCallback mSessionCallback;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 28 || Build.VERSION.SDK_INT < 21) {
-            sExpectedControllerPackageName = SERVICE_PACKAGE_NAME;
-        } else if (Build.VERSION.SDK_INT >= 24) {
-            // KeyEvent from system service has the package name "android".
-            sExpectedControllerPackageName = "android";
-        } else {
-            // In API 21+, MediaSessionCompat#getCurrentControllerInfo always returns fake info.
-            sExpectedControllerPackageName = LEGACY_CONTROLLER;
-        }
-    }
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mPlayer = new MockPlayer(1);
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-
-        mSessionCallback = new TestSessionCallback();
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, mSessionCallback)
-                .build();
-
-        // Make this test to get priority for handling media key event.
-        // Here's the requirement for an app to receive media key events via MediaSession.
-        // SDK < 26: Playback state should become *playing* for receiving key events.
-        // SDK >= 26: Play a media item in the same process of the session for receiving key
-        //            events.
-        if (Build.VERSION.SDK_INT < 26) {
-            mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-        } else {
-            final CountDownLatch latch = new CountDownLatch(1);
-            sHandler.postAndSync(new Runnable() {
-                @Override
-                public void run() {
-                    // Pick the shortest media to finish within the TIMEOUT_MS.
-                    mMediaPlayer = MediaPlayer.create(mContext, R.raw.camera_click);
-                    mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
-                        @Override
-                        public void onCompletion(MediaPlayer mp) {
-                            if (mMediaPlayer != null) {
-                                mMediaPlayer.release();
-                                mMediaPlayer = null;
-                                latch.countDown();
-                            }
-                        }
-                    });
-                    mMediaPlayer.start();
-                }
-            });
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                if (mMediaPlayer != null) {
-                    mMediaPlayer.release();
-                    mMediaPlayer = null;
-                }
-            }
-        });
-        mSession.close();
-    }
-
-    private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) {
-        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
-        if (doubleTap) {
-            mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-            mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
-        }
-    }
-
-    @Test
-    public void playKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pauseKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void nextKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void previousKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void stopKeyEvent() throws Exception {
-        mPlayer = new MockPlayer(2);
-        mSession.updatePlayer(mPlayer);
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-        assertTrue(mPlayer.mSeekToCalled);
-    }
-
-    @Test
-    public void fastForwardKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false);
-        assertTrue(mSessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mSessionCallback.mFastForwardCalled);
-    }
-
-    @Test
-    public void rewindKeyEvent() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
-        assertTrue(mSessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mSessionCallback.mRewindCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_play() throws Exception {
-        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PAUSED);
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_pause() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Exception {
-        dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-        assertFalse(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-    }
-
-    private static class TestSessionCallback extends MediaSession.SessionCallback {
-        final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-        boolean mFastForwardCalled;
-        boolean mRewindCalled;
-
-        @Override
-        public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            if (sExpectedControllerPackageName.equals(controller.getPackageName())) {
-                return super.onConnect(session, controller);
-            }
-            return null;
-        }
-
-        @Override
-        public int onFastForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            mFastForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-            mRewindCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java
deleted file mode 100644
index 26f6b95..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_PermissionTest.java
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PLAY;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_PLAYLIST;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_BACKWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_SKIP_FORWARD;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
-import static androidx.media2.session.SessionCommand.COMMAND_CODE_VOLUME_SET_VOLUME;
-import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.Rating;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.session.StarRating;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaSession} receives commands that hasn't allowed.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaSession_PermissionTest extends MediaSessionTestBase {
-    private static final String SESSION_ID = "MediaSessionTest_permission";
-
-    private MockPlayer mPlayer;
-    private MediaSession mSession;
-    private MySessionCallback mCallback;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-        mPlayer = null;
-        mCallback = null;
-    }
-
-    private MediaSession createSessionWithAllowedActions(final SessionCommandGroup commands) {
-        mPlayer = new MockPlayer(1);
-        mCallback = new MySessionCallback() {
-            @Override
-            public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                    @NonNull ControllerInfo controller) {
-                if (!TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName())) {
-                    return null;
-                }
-                return commands == null ? new SessionCommandGroup() : commands;
-            }
-        };
-        if (mSession != null) {
-            mSession.close();
-        }
-        mSession = new MediaSession.Builder(mContext, mPlayer).setId(SESSION_ID)
-                .setSessionCallback(sHandlerExecutor, mCallback).build();
-        return mSession;
-    }
-
-    private SessionCommandGroup createCommandGroupWith(int commandCode) {
-        SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                .addCommand(new SessionCommand(commandCode))
-                .build();
-        return commands;
-    }
-
-    private SessionCommandGroup createCommandGroupWithout(int commandCode) {
-        SessionCommandGroup commands = new SessionCommandGroup.Builder()
-                .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
-                .removeCommand(new SessionCommand(commandCode))
-                .build();
-        return commands;
-    }
-
-    private void testOnCommandRequest(int commandCode, PermissionTestTask runnable)
-            throws InterruptedException {
-        createSessionWithAllowedActions(createCommandGroupWith(commandCode));
-        runnable.run(createRemoteController(mSession.getToken()));
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnCommandRequestCalled);
-        assertEquals(commandCode, mCallback.mCommand.getCommandCode());
-
-        createSessionWithAllowedActions(createCommandGroupWithout(commandCode));
-        runnable.run(createRemoteController(mSession.getToken()));
-
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnCommandRequestCalled);
-    }
-
-    @Test
-    public void play() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_PLAY, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.play();
-            }
-        });
-    }
-
-    @Test
-    public void pause() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_PAUSE, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.pause();
-            }
-        });
-    }
-
-    @Test
-    public void seekTo() throws InterruptedException {
-        final long position = 10;
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SEEK_TO, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.seekTo(position);
-            }
-        });
-    }
-
-    @Test
-    public void skipToNext() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToNextItem();
-                    }
-                });
-    }
-
-    @Test
-    public void skipToPrevious() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToPreviousItem();
-                    }
-                });
-    }
-
-    @Test
-    public void skipToPlaylistItem() throws InterruptedException {
-        testOnCommandRequest(
-                COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.skipToPlaylistItem(0);
-                    }
-                });
-    }
-
-    @Test
-    public void setPlaylist() throws InterruptedException {
-        final List<String> list = MediaTestUtils.createMediaIds(2);
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SET_PLAYLIST, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setPlaylist(list, null);
-            }
-        });
-    }
-
-    @Test
-    public void setMediaItem() throws InterruptedException {
-        final String testMediaId = "testSetMediaItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setMediaItem(testMediaId);
-            }
-        });
-    }
-
-    @Test
-    public void updatePlaylistMetadata() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.updatePlaylistMetadata(null);
-                    }
-                });
-    }
-
-    @Test
-    public void addPlaylistItem() throws InterruptedException {
-        final String testMediaId = "testAddPlaylistItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.addPlaylistItem(0, testMediaId);
-            }
-        });
-    }
-
-    @Test
-    public void removePlaylistItem() throws InterruptedException {
-        final MediaItem testItem = MediaTestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.removePlaylistItem(0);
-                    }
-                });
-    }
-
-    @Test
-    public void replacePlaylistItem() throws InterruptedException {
-        final String testMediaId = "testReplacePlaylistItem";
-        testOnCommandRequest(COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
-                new PermissionTestTask() {
-                    @Override
-                    public void run(RemoteMediaController controller) {
-                        controller.replacePlaylistItem(0, testMediaId);
-                    }
-                });
-    }
-
-    @Test
-    public void setVolume() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_VOLUME_SET_VOLUME, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.setVolumeTo(0, 0);
-            }
-        });
-    }
-
-    @Test
-    public void adjustVolume() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_VOLUME_ADJUST_VOLUME, new PermissionTestTask() {
-            @Override
-            public void run(RemoteMediaController controller) {
-                controller.adjustVolume(0, 0);
-            }
-        });
-    }
-
-    @Test
-    public void fastForward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_FAST_FORWARD));
-        createRemoteController(mSession.getToken()).fastForward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnFastForwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_FAST_FORWARD));
-        createRemoteController(mSession.getToken()).fastForward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnFastForwardCalled);
-    }
-
-    @Test
-    public void rewind() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_REWIND));
-        createRemoteController(mSession.getToken()).rewind();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnRewindCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_REWIND));
-        createRemoteController(mSession.getToken()).rewind();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnRewindCalled);
-    }
-
-    @Test
-    public void skipForward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_FORWARD));
-        createRemoteController(mSession.getToken()).skipForward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSkipForwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_FORWARD));
-        createRemoteController(mSession.getToken()).skipForward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSkipForwardCalled);
-    }
-
-    @Test
-    public void skipBackward() throws InterruptedException {
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_BACKWARD));
-        createRemoteController(mSession.getToken()).skipBackward();
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSkipBackwardCalled);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_BACKWARD));
-        createRemoteController(mSession.getToken()).skipBackward();
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSkipBackwardCalled);
-    }
-
-    @Test
-    public void setMediaUri() throws InterruptedException {
-        final Uri uri = Uri.parse("media://uri");
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        createRemoteController(mSession.getToken()).setMediaUri(uri, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetMediaUriCalled);
-        assertEquals(uri, mCallback.mUri);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        createRemoteController(mSession.getToken()).setMediaUri(uri, null);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSetMediaUriCalled);
-    }
-
-    @Test
-    public void setRating() throws InterruptedException {
-        final String mediaId = "testSetRating";
-        final Rating rating = new StarRating(5, 3.5f);
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_RATING));
-        createRemoteController(mSession.getToken()).setRating(mediaId, rating);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetRatingCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertEquals(rating, mCallback.mRating);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_RATING));
-        createRemoteController(mSession.getToken()).setRating(mediaId, rating);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnSetRatingCalled);
-    }
-
-    @Test
-    public void changingPermissionWithSetAllowedCommands() throws InterruptedException {
-        final String mediaId = "testSetRating";
-        final Rating rating = new StarRating(5, 3.5f);
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_SET_RATING));
-        RemoteMediaController controller = createRemoteController(mSession.getToken());
-        controller.setRating(mediaId, rating);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnSetRatingCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertEquals(rating, mCallback.mRating);
-        mCallback.reset();
-
-        // Change allowed commands.
-        mSession.setAllowedCommands(getTestControllerInfo(),
-                createCommandGroupWithout(COMMAND_CODE_SESSION_SET_RATING));
-
-        controller.setRating(mediaId, rating);
-        assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private ControllerInfo getTestControllerInfo() {
-        List<ControllerInfo> controllers = mSession.getConnectedControllers();
-        assertNotNull(controllers);
-        for (int i = 0; i < controllers.size(); i++) {
-            if (TextUtils.equals(CLIENT_PACKAGE_NAME, controllers.get(i).getPackageName())) {
-                return controllers.get(i);
-            }
-        }
-        fail("Failed to get test controller info");
-        return null;
-    }
-
-    @FunctionalInterface
-    private interface PermissionTestTask {
-        void run(@NonNull RemoteMediaController controller);
-    }
-
-    public class MySessionCallback extends MediaSession.SessionCallback {
-        public CountDownLatch mCountDownLatch;
-
-        public SessionCommand mCommand;
-        public String mMediaId;
-        public Uri mUri;
-        public Bundle mExtras;
-        public Rating mRating;
-
-        public boolean mOnCommandRequestCalled;
-        public boolean mOnFastForwardCalled;
-        public boolean mOnRewindCalled;
-        public boolean mOnSkipForwardCalled;
-        public boolean mOnSkipBackwardCalled;
-        public boolean mOnSetMediaUriCalled;
-        public boolean mOnSetRatingCalled;
-
-
-        public MySessionCallback() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        public void reset() {
-            mCountDownLatch = new CountDownLatch(1);
-
-            mCommand = null;
-            mMediaId = null;
-            mUri = null;
-            mExtras = null;
-
-            mOnCommandRequestCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSetMediaUriCalled = false;
-            mOnSetRatingCalled = false;
-        }
-
-        @Override
-        public int onCommandRequest(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull SessionCommand command) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnCommandRequestCalled = true;
-            mCommand = command;
-            mCountDownLatch.countDown();
-            return super.onCommandRequest(session, controller, command);
-        }
-
-        @Override
-        public int onFastForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnFastForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onRewind(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnRewindCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSkipForward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSkipForwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSkipBackward(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSkipBackwardCalled = true;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSetMediaUri(@NonNull MediaSession session,
-                @NonNull ControllerInfo controller, @NonNull Uri uri, Bundle extras) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSetMediaUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-
-        @Override
-        public int onSetRating(@NonNull MediaSession session, @NonNull ControllerInfo controller,
-                @NonNull String mediaId, @NonNull Rating rating) {
-            assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
-            mOnSetRatingCalled = true;
-            mMediaId = mediaId;
-            mRating = rating;
-            mCountDownLatch.countDown();
-            return RESULT_SUCCESS;
-        }
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java
deleted file mode 100644
index 78a89f4..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaTestBase.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.BeforeClass;
-
-/**
- * Base class for all media tests.
- */
-abstract class MediaTestBase {
-    @BeforeClass
-    public static void setupMainLooper() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Prepare the main looper if it hasn't.
-                // Some framework APIs always run on the main looper.
-                if (Looper.getMainLooper() == null) {
-                    Looper.prepareMainLooper();
-                }
-
-                // Initialize AudioManager on the main thread to workaround b/78617702 that
-                // audio focus listener is called on the thread where the AudioManager was
-                // originally initialized.
-                // Without posting this, audio focus listeners wouldn't be called because the
-                // listeners would be posted to the test thread (here) where it waits until the
-                // tests are finished.
-                Context context = ApplicationProvider.getApplicationContext();
-                AudioManager manager =
-                        (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            }
-        });
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java
deleted file mode 100644
index ca732bb..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaBrowserCompatTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-
-import androidx.media2.test.service.RemoteMediaBrowserCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test {@link RemoteMediaBrowserCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaBrowserCompatTest extends MediaSessionTestBase {
-    private Context mContext;
-    private RemoteMediaBrowserCompat mRemoteBrowserCompat;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-        mRemoteBrowserCompat = new RemoteMediaBrowserCompat(mContext, MOCK_MEDIA2_LIBRARY_SERVICE);
-    }
-
-    @After
-    public void cleanUp() {
-        if (mRemoteBrowserCompat != null) {
-            mRemoteBrowserCompat.cleanUp();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void connect() throws Exception {
-        mRemoteBrowserCompat.connect(true /* waitForConnection */);
-        assertTrue(mRemoteBrowserCompat.isConnected());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java
deleted file mode 100644
index 85bfa2c..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteMediaControllerCompatTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.media2.test.service.RemoteMediaControllerCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/** Test {@link RemoteMediaControllerCompat}. */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-public class RemoteMediaControllerCompatTest extends MediaSessionTestBase {
-    private Context mContext;
-    private MediaSessionCompat mSessionCompat;
-    private RemoteMediaControllerCompat mRemoteControllerCompat;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-        sHandler.postAndSync(new Runnable() {
-            @Override
-            public void run() {
-                mSessionCompat = new MediaSessionCompat(mContext, DEFAULT_TEST_NAME);
-                mSessionCompat.setActive(true);
-            }
-        });
-        mRemoteControllerCompat = new RemoteMediaControllerCompat(
-                mContext, mSessionCompat.getSessionToken(), true /* waitForConnection */);
-    }
-
-    @After
-    public void cleanUp() {
-        mSessionCompat.release();
-        mRemoteControllerCompat.cleanUp();
-    }
-
-    @Test
-    @SmallTest
-    public void play() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        mSessionCompat.setCallback(new MediaSessionCompat.Callback() {
-            @Override
-            public void onPlay() {
-                latch.countDown();
-            }
-        }, sHandler);
-
-        mRemoteControllerCompat.getTransportControls().play();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java
deleted file mode 100644
index 2fcd401..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/RemoteSessionPlayerTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioManager;
-
-import androidx.annotation.NonNull;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.MockRemotePlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether the methods of {@link RemoteSessionPlayer} are triggered by the
- * controller.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RemoteSessionPlayerTest extends MediaSessionTestBase {
-
-    MediaSession mSession;
-    RemoteMediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        // Create this test specific MediaSession to use our own Handler.
-        mSession = new MediaSession.Builder(mContext, new MockPlayer(1))
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                })
-                .setId("RemoteSessionPlayerTest")
-                .build();
-        // Create a default MediaController in client app.
-        mController = createRemoteController(mSession.getToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void setVolumeToByController() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE;
-        MockRemotePlayer remotePlayer = new MockRemotePlayer(
-                volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int targetVolume = 50;
-        mController.setVolumeTo(targetVolume, 0 /* flags */);
-
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mSetVolumeToCalled);
-        assertEquals(targetVolume, remotePlayer.mCurrentVolume, 0.001f);
-    }
-
-    @Test
-    public void adjustVolumeByController() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE;
-
-        MockRemotePlayer remotePlayer = new MockRemotePlayer(
-                volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(remotePlayer);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        mController.adjustVolume(direction, 0 /* flags */);
-
-        assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(remotePlayer.mAdjustVolumeCalled);
-        assertEquals(direction, remotePlayer.mDirection);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
deleted file mode 100644
index 2eaf282..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import androidx.media2.session.SessionCommand;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests {@link SessionCommand} and {@link SessionCommandGroup}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SessionCommandTest {
-    // Prefix for all command codes
-    private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
-
-    private static final List<String> PREFIX_COMMAND_CODES = new ArrayList<>();
-    static {
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_PLAYER_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_VOLUME_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_SESSION_");
-        PREFIX_COMMAND_CODES.add("COMMAND_CODE_LIBRARY_");
-    }
-
-    /**
-     * Test possible typos in naming
-     */
-    @Test
-    public void codes_name() {
-        List<Field> fields = getSessionCommandsFields("");
-        for (int i = 0; i < fields.size(); i++) {
-            String name = fields.get(i).getName();
-
-            boolean matches = false;
-            if (name.startsWith("COMMAND_VERSION_") || name.equals("COMMAND_CODE_CUSTOM")) {
-                matches = true;
-            }
-            if (!matches) {
-                for (int j = 0; j < PREFIX_COMMAND_CODES.size(); j++) {
-                    if (name.startsWith(PREFIX_COMMAND_CODES.get(j))) {
-                        matches = true;
-                        break;
-                    }
-                }
-            }
-            assertTrue("Unexpected constant " + name, matches);
-        }
-    }
-
-    /**
-     * Tests possible code duplications in values
-     */
-    @Test
-    public void codes_valueDuplication() throws IllegalAccessException {
-        List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODE);
-        Set<Integer> values = new HashSet<>();
-        for (int i = 0; i < fields.size(); i++) {
-            Integer value = fields.get(i).getInt(null);
-            assertTrue(values.add(value));
-        }
-    }
-
-    /**
-     * Tests whether codes are continuous
-     */
-    @Test
-    @Ignore
-    public void codes_valueContinuous() throws IllegalAccessException {
-        for (int i = 0; i < PREFIX_COMMAND_CODES.size(); i++) {
-            List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODES.get(i));
-            List<Integer> values = new ArrayList<>();
-            for (int j = 0; j < fields.size(); j++) {
-                values.add(fields.get(j).getInt(null));
-            }
-            Collections.sort(values);
-            for (int j = 1; j < values.size(); j++) {
-                assertEquals(
-                        "Command code isn't continuous. Missing " + (values.get(j - 1) + 1)
-                                + " in " + PREFIX_COMMAND_CODES.get(i),
-                        ((int) values.get(j - 1)) + 1, (int) values.get(j));
-            }
-        }
-    }
-
-    @Test
-    public void addAllPredefinedCommands_withVersion1_notHaveVersion2Commands() {
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1);
-        SessionCommandGroup commands = builder.build();
-        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM));
-    }
-
-    @Test
-    public void addAllPredefinedCommands_withVersion2_hasVersion2Commands() {
-        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
-        builder.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_2);
-        SessionCommandGroup commands = builder.build();
-        assertTrue(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI));
-        assertTrue(commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM));
-    }
-
-    private static List<Field> getSessionCommandsFields(String prefix) {
-        final List<Field> list = new ArrayList<>();
-        final Field[] fields = SessionCommand.class.getFields();
-        if (fields != null) {
-            for (int i = 0; i < fields.length; i++) {
-                if (isPublicStaticFinalInt(fields[i])
-                        && fields[i].getName().startsWith(prefix)) {
-                    list.add(fields[i]);
-                }
-            }
-        }
-        return list;
-    }
-
-    private static boolean isPublicStaticFinalInt(Field field) {
-        if (field.getType() != int.class) {
-            return false;
-        }
-        int modifier = field.getModifiers();
-        return Modifier.isPublic(modifier) && Modifier.isStatic(modifier)
-                && Modifier.isFinal(modifier);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java
deleted file mode 100644
index da96a72..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionPlayerTest.java
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static androidx.media2.test.common.CommonConstants.CLIENT_PACKAGE_NAME;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import androidx.annotation.NonNull;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.MediaSession.ControllerInfo;
-import androidx.media2.session.SessionCommandGroup;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MediaTestUtils;
-import androidx.media2.test.service.MockPlayer;
-import androidx.media2.test.service.RemoteMediaController;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether the methods of {@link SessionPlayer} are triggered by the
- * session/controller.
- */
-@SdkSuppress(maxSdkVersion = 32) // b/244312419
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class SessionPlayerTest extends MediaSessionTestBase {
-
-    MediaSession mSession;
-    MockPlayer mPlayer;
-    RemoteMediaController mController;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(1);
-        mSession = new MediaSession.Builder(mContext, mPlayer)
-                .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
-                    @Override
-                    public SessionCommandGroup onConnect(@NonNull MediaSession session,
-                            @NonNull MediaSession.ControllerInfo controller) {
-                        if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public MediaItem onCreateMediaItem(@NonNull MediaSession session,
-                            @NonNull ControllerInfo controller, @NonNull String mediaId) {
-                        return MediaTestUtils.createMediaItem(mediaId);
-                    }
-                }).build();
-
-        // Create a default MediaController in client app.
-        mController = createRemoteController(mSession.getToken());
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-        }
-    }
-
-    @Test
-    public void playBySession() throws Exception {
-        mSession.getPlayer().play();
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void playByController() {
-        mController.play();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void pauseBySession() throws Exception {
-        mSession.getPlayer().pause();
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void pauseByController() {
-        mController.pause();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Test
-    public void prepareBySession() throws Exception {
-        mSession.getPlayer().prepare();
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void prepareByController() {
-        mController.prepare();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void seekToBySession() throws Exception {
-        final long pos = 1004L;
-        mSession.getPlayer().seekTo(pos);
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(pos, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void seekToByController() {
-        final long seekPosition = 12125L;
-        mController.seekTo(seekPosition);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void setPlaybackSpeedBySession() throws Exception {
-        final float speed = 1.5f;
-        mSession.getPlayer().setPlaybackSpeed(speed);
-        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void setPlaybackSpeedByController() throws Exception {
-        final float speed = 1.5f;
-        mController.setPlaybackSpeed(speed);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
-    }
-
-    @Test
-    public void setPlaylistBySession() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mSession.getPlayer().setPlaylist(list, null);
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertSame(list, mPlayer.mPlaylist);
-        assertNull(mPlayer.mMetadata);
-    }
-
-    @Test
-    public void setPlaylistByController() throws InterruptedException {
-        final List<String> list = MediaTestUtils.createMediaIds(2);
-        mController.setPlaylist(list, null /* metadata */);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertNull(mPlayer.mMetadata);
-
-        assertNotNull(mPlayer.mPlaylist);
-        assertEquals(list.size(), mPlayer.mPlaylist.size());
-        for (int i = 0; i < list.size(); i++) {
-            assertEquals(list.get(i), mPlayer.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void setPlaylistByControllerWithLongPlaylist() throws InterruptedException {
-        final int listSize = 5000;
-        // Make client app to generate a long list, and call setPlaylist() with it.
-        mController.createAndSetFakePlaylist(listSize, null /* metadata */);
-        assertTrue(mPlayer.mCountDownLatch.await(10, TimeUnit.SECONDS));
-
-        assertTrue(mPlayer.mSetPlaylistCalled);
-        assertNull(mPlayer.mMetadata);
-
-        assertNotNull(mPlayer.mPlaylist);
-        assertEquals(listSize, mPlayer.mPlaylist.size());
-        for (int i = 0; i < listSize; i++) {
-            // Each item's media ID will be same as its index.
-            assertEquals(TestUtils.getMediaIdInFakeList(i), mPlayer.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    @Test
-    public void updatePlaylistMetadataBySession() {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mSession.getPlayer().updatePlaylistMetadata(testMetadata);
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertSame(testMetadata, mPlayer.mMetadata);
-    }
-
-    @Test
-    public void updatePlaylistMetadataByController() throws InterruptedException {
-        final MediaMetadata testMetadata = MediaTestUtils.createMetadata();
-        mController.updatePlaylistMetadata(testMetadata);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
-        assertNotNull(mPlayer.mMetadata);
-        assertEquals(testMetadata.getMediaId(), mPlayer.mMetadata.getMediaId());
-    }
-
-    @Test
-    public void addPlaylistItemBySession() {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().addPlaylistItem(testIndex, testMediaItem);
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void addPlaylistItemByController() throws InterruptedException {
-        final int testIndex = 12;
-        final String testMediaId = "testAddPlaylistItemByController";
-
-        mController.addPlaylistItem(testIndex, testMediaId);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        // MediaController.addPlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void removePlaylistItemBySession() {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        mSession.getPlayer().setPlaylist(list, null);
-        mSession.getPlayer().removePlaylistItem(0);
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertSame(0, mPlayer.mIndex);
-    }
-
-    @Test
-    public void removePlaylistItemByController() throws InterruptedException {
-        mPlayer.mPlaylist = MediaTestUtils.createPlaylist(2);
-
-        mController.removePlaylistItem(0);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mRemovePlaylistItemCalled);
-        assertEquals(0, mPlayer.mIndex);
-    }
-
-    @Test
-    public void replacePlaylistItemBySession() throws InterruptedException {
-        final int testIndex = 12;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().replacePlaylistItem(testIndex, testMediaItem);
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        assertEquals(testIndex, mPlayer.mIndex);
-        assertSame(testMediaItem, mPlayer.mItem);
-    }
-
-    @Test
-    public void replacePlaylistItemByController() throws InterruptedException {
-        final int testIndex = 12;
-        final String testMediaId = "testReplacePlaylistItemByController";
-
-        mController.replacePlaylistItem(testIndex, testMediaId);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mReplacePlaylistItemCalled);
-        // MediaController.replacePlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaId, mPlayer.mItem.getMediaId());
-    }
-
-    @Test
-    public void movePlaylistItemsBySession() throws InterruptedException {
-        final int fromIdx = 3;
-        final int toIdx = 20;
-        final MediaItem testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
-        mSession.getPlayer().movePlaylistItem(fromIdx, toIdx);
-        assertTrue(mPlayer.mMovePlaylistItemCalled);
-        assertEquals(fromIdx, mPlayer.mIndex);
-        assertEquals(toIdx, mPlayer.mIndex2);
-    }
-
-    @Test
-    public void movePlaylistItemByController() throws InterruptedException {
-        final int testIndex1 = 3;
-        final int testIndex2 = 20;
-
-        mController.movePlaylistItem(testIndex1, testIndex2);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mMovePlaylistItemCalled);
-        assertEquals(testIndex1, mPlayer.mIndex);
-        assertEquals(testIndex2, mPlayer.mIndex2);
-    }
-
-    @Test
-    public void skipToPreviousItemBySession() {
-        mSession.getPlayer().skipToPreviousPlaylistItem();
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToPreviousItemByController() throws InterruptedException {
-        mController.skipToPreviousItem();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void skipToNextItemBySession() throws Exception {
-        mSession.getPlayer().skipToNextPlaylistItem();
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToNextItemByController() throws InterruptedException {
-        mController.skipToNextItem();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void skipToPlaylistItemBySession() throws Exception {
-        final List<MediaItem> list = MediaTestUtils.createPlaylist(2);
-        int targetIndex = 0;
-        mSession.getPlayer().setPlaylist(list, null);
-        mSession.getPlayer().skipToPlaylistItem(targetIndex);
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertSame(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void skipToPlaylistItemByController() throws InterruptedException {
-        mPlayer.mPlaylist = MediaTestUtils.createPlaylist(3);
-        int targetIndex = 2;
-
-        mController.skipToPlaylistItem(targetIndex);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSkipToPlaylistItemCalled);
-        assertEquals(targetIndex, mPlayer.mIndex);
-    }
-
-    @Test
-    public void setShuffleModeBySession() {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mSession.getPlayer().setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setShuffleModeByController() throws InterruptedException {
-        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
-        mController.setShuffleMode(testShuffleMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mPlayer.mShuffleMode);
-    }
-
-    @Test
-    public void setRepeatModeBySession() {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mSession.getPlayer().setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-
-    @Test
-    public void setRepeatModeByController() throws InterruptedException {
-        final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
-        mController.setRepeatMode(testRepeatMode);
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mPlayer.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mPlayer.mRepeatMode);
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java
deleted file mode 100644
index 098d2ce..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/SessionTokenTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2018 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.media2.test.service.tests;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Process;
-
-import androidx.media2.session.MediaSession;
-import androidx.media2.session.SessionToken;
-import androidx.media2.test.common.TestUtils;
-import androidx.media2.test.service.MockMediaLibraryService;
-import androidx.media2.test.service.MockMediaSessionService;
-import androidx.media2.test.service.MockPlayer;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests {@link SessionToken}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SessionTokenTest extends MediaTestBase {
-    private Context mContext;
-    private List<MediaSession> mSessions = new ArrayList<>();
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        for (MediaSession session : mSessions) {
-            if (session != null) {
-                session.close();
-            }
-        }
-    }
-
-    @Test
-    public void constructor_sessionService() {
-        SessionToken token = new SessionToken(mContext, new ComponentName(
-                mContext.getPackageName(),
-                MockMediaSessionService.class.getCanonicalName()));
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
-    }
-
-    @Test
-    public void constructor_libraryService() {
-        ComponentName testComponentName = new ComponentName(mContext.getPackageName(),
-                MockMediaLibraryService.class.getCanonicalName());
-        SessionToken token = new SessionToken(mContext, testComponentName);
-
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
-        assertEquals(testComponentName.getClassName(), token.getServiceName());
-    }
-
-    @Test
-    public void getters_whenCreatedBySession() {
-        Bundle testTokenExtras = TestUtils.createTestBundle();
-        MediaSession session = new MediaSession.Builder(mContext, new MockPlayer(0))
-                .setId("testGetters_whenCreatedBySession")
-                .setExtras(testTokenExtras)
-                .build();
-        mSessions.add(session);
-        SessionToken token = session.getToken();
-
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken.TYPE_SESSION, token.getType());
-        assertTrue(TestUtils.equals(testTokenExtras, token.getExtras()));
-        assertNull(token.getServiceName());
-    }
-}
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/res/raw/midi8sec.mid b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/res/raw/midi8sec.mid
deleted file mode 100644
index 746aca1..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/res/raw/midi8sec.mid
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/main/res/drawable/big_buck_bunny.jpg b/media2/media2-session/version-compat-tests/previous/service/src/main/res/drawable/big_buck_bunny.jpg
deleted file mode 100644
index a1cafdf..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/main/res/drawable/big_buck_bunny.jpg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/main/res/raw/camera_click.ogg b/media2/media2-session/version-compat-tests/previous/service/src/main/res/raw/camera_click.ogg
deleted file mode 100644
index b836e10..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/main/res/raw/camera_click.ogg
+++ /dev/null
Binary files differ
diff --git a/media2/media2-session/version-compat-tests/runtest.sh b/media2/media2-session/version-compat-tests/runtest.sh
deleted file mode 100755
index 6a393f3..0000000
--- a/media2/media2-session/version-compat-tests/runtest.sh
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/bin/bash
-# Copyright 2017 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.
-
-# A script that runs media2 tests between different versions.
-
-# Usage './runtest.sh <version_combination_number> [option]'
-
-CLIENT_MODULE_NAME_BASE="media2:media2-session:version-compat-tests:client"
-SERVICE_MODULE_NAME_BASE="media2:media2-session:version-compat-tests:service"
-CLIENT_VERSION=""
-SERVICE_VERSION=""
-CLIENT_TEST_TARGET=""
-SERVICE_TEST_TARGET=""
-VERSION_COMBINATION=""
-ERROR_CODE=0
-
-function printRunTestUsage() {
-  echo "Usage: ./runtest.sh <version_combination_number> [option]"
-  echo ""
-  echo "Version combination number:"
-  echo "    1. Client-ToT             / Service-ToT"
-  echo "    2. Client-ToT             / Service-Latest release"
-  echo "    3. Client-Latest release  / Service-ToT"
-  echo "    4. Run all of the above"
-  echo ""
-  echo "Option:"
-  echo "    -t <class/method>: Only run the specific test class/method."
-}
-
-function runTest() {
-  echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Started: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<"
-
-  local CUSTOM_OPTIONS="-Pandroid.testInstrumentationRunnerArguments.client_version=$CLIENT_VERSION"
-  CUSTOM_OPTIONS="$CUSTOM_OPTIONS -Pandroid.testInstrumentationRunnerArguments.service_version=$SERVICE_VERSION"
-
-  local CLIENT_MODULE_NAME="$CLIENT_MODULE_NAME_BASE$([ "$CLIENT_VERSION" = "tot" ] || echo "-previous")"
-  local SERVICE_MODULE_NAME="$SERVICE_MODULE_NAME_BASE$([ "$SERVICE_VERSION" = "tot" ] || echo "-previous")"
-
-  local TEST_DEVICES="$ANDROID_SERIAL"
-  # TODO(b/156594425): Remove following check when previous module depends on lowering minSdk to 16
-  if [ "$CLIENT_VERSION" = "previous" ] || [ "$SERVICE_VERSION" = "previous" ]; then
-    local DEVICES="${ANDROID_SERIAL/,/ }"
-    if [[ -z "${DEVICES}" ]]; then
-      for DEVICE in $($ADB devices | tail -n +2 | awk '{print $1}'); do
-        DEVICES="$DEVICES $DEVICE"
-      done
-    fi
-
-    TEST_DEVICES=""
-    for DEVICE in $DEVICES; do
-      if [[ -z "$DEVICE" ]]; then
-        continue
-      fi
-      # Do not use $($ADB shell getprop ro.build.version.sdk) directly.
-      # It ends with '\r' on the SDK 16, and cause error in arithmetic comparison.
-      DEVICE_SDK_VERSION=$($ADB -s $DEVICE shell getprop ro.build.version.sdk | sed 's/[^0-9]//')
-      if ! [[ "$DEVICE_SDK_VERSION" -ge "19" ]]; then
-        echo "Skipping test on $DEVICE. Only ToT-ToT is supported on the older device (SDK<19)"
-      else
-        TEST_DEVICES="$TEST_DEVICES$DEVICE,"
-      fi
-    done
-    if [[ -z "$TEST_DEVICES" ]]; then
-      echo "No eligible device for test"
-      exit 1
-    fi
-  fi
-
-  if [[ -n "${TEST_DEVICES}" ]]; then
-    TEST_DEVICES="${TEST_DEVICES%,}"
-    echo "Running on ${TEST_DEVICES}"
-  fi
-
-  echo "Building modules"
-  ./gradlew $CLIENT_MODULE_NAME:assembleAndroidTest || { echo "Client build failed. Aborting."; exit 1; }
-  ./gradlew $SERVICE_MODULE_NAME:assembleAndroidTest || { echo "Service build failed. Aborting."; exit 1; }
-
-  if [[ -z "$SERVICE_TEST_TARGET" ]]; then
-    echo "Running client tests"
-    ANDROID_SERIAL=$TEST_DEVICES ./gradlew $SERVICE_MODULE_NAME:installDebugAndroidTest || { echo "Service install failed. Aborting."; exit 1; }
-    ANDROID_SERIAL=$TEST_DEVICES ./gradlew $CLIENT_MODULE_NAME:connectedAndroidTest $CLIENT_TEST_TARGET $CUSTOM_OPTIONS || ERROR_CODE=1
-  fi
-
-  if [[ -z "$CLIENT_TEST_TARGET" ]]; then
-    echo "Running service tests"
-    ANDROID_SERIAL=$TEST_DEVICES ./gradlew $CLIENT_MODULE_NAME:installDebugAndroidTest || { echo "Client install failed. Aborting."; exit 1; }
-    ANDROID_SERIAL=$TEST_DEVICES ./gradlew $SERVICE_MODULE_NAME:connectedAndroidTest $SERVICE_TEST_TARGET $CUSTOM_OPTIONS || ERROR_CODE=1
-  fi
-
-  echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Ended: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<<<"
-}
-
-
-OLD_PWD=$(pwd)
-
-if ! cd "$(echo $OLD_PWD | awk -F'frameworks/support' '{print $1}')"/frameworks/support &> /dev/null
-then
-  echo "Current working directory is $OLD_PWD"
-  echo "Please re-run this script in any folder under frameworks/support."
-  exit 1;
-fi
-
-if [ "`uname`" == "Darwin" ]; then
-  PLATFORM="darwin"
-else
-  PLATFORM="linux"
-fi
-ADB="../../prebuilts/fullsdk-$PLATFORM/platform-tools/adb"
-if [ ! -f "$ADB" ]; then
-  echo "adb not found at $ADB, finding adb in \$PATH..." 1>&2
-  command -v adb > /dev/null 2>&1 || { echo "adb not found in \$PATH" 1>&2; exit 1; }
-  ADB="adb"
-fi
-
-case ${1} in
-  1|2|3|4)
-    VERSION_COMBINATION=${1}
-    shift
-    ;;
-  *)
-    printRunTestUsage
-    exit 1;
-esac
-
-while (( "$#" )); do
-  case ${1} in
-    -t)
-      if [[ ${2} == *"client"* ]]; then
-        CLIENT_TEST_TARGET="-Pandroid.testInstrumentationRunnerArguments.class=${2}"
-      elif [[ ${2} == *"service"* ]]; then
-        SERVICE_TEST_TARGET="-Pandroid.testInstrumentationRunnerArguments.class=${2}"
-      else
-        echo "Wrong test class/method name. Aborting."
-        echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
-        exit 1;
-      fi
-      shift 2
-      ;;
-    *)
-      printRunTestUsage
-      exit 1;
-  esac
-done
-
-case ${VERSION_COMBINATION} in
-  1)
-     CLIENT_VERSION="tot"
-     SERVICE_VERSION="tot"
-     runTest
-     ;;
-  2)
-     CLIENT_VERSION="tot"
-     SERVICE_VERSION="previous"
-     runTest
-     ;;
-  3)
-     CLIENT_VERSION="previous"
-     SERVICE_VERSION="tot"
-     runTest
-     ;;
-  4)
-     CLIENT_VERSION="tot"
-     SERVICE_VERSION="tot"
-     runTest
-
-     CLIENT_VERSION="tot"
-     SERVICE_VERSION="previous"
-     runTest
-
-     CLIENT_VERSION="previous"
-     SERVICE_VERSION="tot"
-     runTest
-     ;;
-esac
-
-echo
-case ${ERROR_CODE} in
-  0)
-    echo -e "\033[1;32mTEST SUCCESSFUL\033[0m: All of tests passed."
-    ;;
-  1)
-    echo -e "\033[1;31mTEST FAILED\033[0m: Some of tests failed."
-    ;;
-esac
-exit $ERROR_CODE
diff --git a/media2/media2-widget/api/1.0.0-beta01.txt b/media2/media2-widget/api/1.0.0-beta01.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/1.0.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/1.0.0-beta02.txt b/media2/media2-widget/api/1.0.0-beta02.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/1.0.0-rc02.txt b/media2/media2-widget/api/1.0.0-rc02.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/1.0.0-rc02.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/1.1.0-beta01.txt b/media2/media2-widget/api/1.1.0-beta01.txt
deleted file mode 100644
index c21654a..0000000
--- a/media2/media2-widget/api/1.1.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/1.2.0-beta01.txt b/media2/media2-widget/api/1.2.0-beta01.txt
deleted file mode 100644
index c21654a..0000000
--- a/media2/media2-widget/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/1.3.0-beta01.txt b/media2/media2-widget/api/1.3.0-beta01.txt
deleted file mode 100644
index 501d5c5..0000000
--- a/media2/media2-widget/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  @Deprecated public class MediaControlView extends android.view.ViewGroup {
-    ctor @Deprecated public MediaControlView(android.content.Context);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public void requestPlayButtonFocus();
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static interface MediaControlView.OnFullScreenListener {
-    method @Deprecated public void onFullScreen(android.view.View, boolean);
-  }
-
-  @Deprecated public class VideoView extends android.view.ViewGroup {
-    ctor @Deprecated public VideoView(android.content.Context);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method @Deprecated public int getViewType();
-    method @Deprecated public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void setViewType(int);
-    field @Deprecated public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field @Deprecated public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  @Deprecated public static interface VideoView.OnViewTypeChangedListener {
-    method @Deprecated public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/api_lint.ignore b/media2/media2-widget/api/api_lint.ignore
deleted file mode 100644
index 3b205f2..0000000
--- a/media2/media2-widget/api/api_lint.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-MissingNullability: androidx.media2.widget.MediaControlView#getAccessibilityClassName():
-    Missing nullability on method `getAccessibilityClassName` return
-MissingNullability: androidx.media2.widget.MediaControlView#onTouchEvent(android.view.MotionEvent) parameter #0:
-    Missing nullability on parameter `ev` in method `onTouchEvent`
-MissingNullability: androidx.media2.widget.MediaControlView#onTrackballEvent(android.view.MotionEvent) parameter #0:
-    Missing nullability on parameter `ev` in method `onTrackballEvent`
-MissingNullability: androidx.media2.widget.VideoView#getAccessibilityClassName():
-    Missing nullability on method `getAccessibilityClassName` return
diff --git a/media2/media2-widget/api/current.txt b/media2/media2-widget/api/current.txt
deleted file mode 100644
index 501d5c5..0000000
--- a/media2/media2-widget/api/current.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  @Deprecated public class MediaControlView extends android.view.ViewGroup {
-    ctor @Deprecated public MediaControlView(android.content.Context);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public void requestPlayButtonFocus();
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static interface MediaControlView.OnFullScreenListener {
-    method @Deprecated public void onFullScreen(android.view.View, boolean);
-  }
-
-  @Deprecated public class VideoView extends android.view.ViewGroup {
-    ctor @Deprecated public VideoView(android.content.Context);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method @Deprecated public int getViewType();
-    method @Deprecated public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void setViewType(int);
-    field @Deprecated public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field @Deprecated public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  @Deprecated public static interface VideoView.OnViewTypeChangedListener {
-    method @Deprecated public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/res-1.0.0-beta01.txt b/media2/media2-widget/api/res-1.0.0-beta01.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.0.0-beta01.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-1.0.0-beta02.txt b/media2/media2-widget/api/res-1.0.0-beta02.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.0.0-beta02.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-1.0.0-rc02.txt b/media2/media2-widget/api/res-1.0.0-rc02.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.0.0-rc02.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-1.1.0-beta01.txt b/media2/media2-widget/api/res-1.1.0-beta01.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.1.0-beta01.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-1.2.0-beta01.txt b/media2/media2-widget/api/res-1.2.0-beta01.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.2.0-beta01.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-1.3.0-beta01.txt b/media2/media2-widget/api/res-1.3.0-beta01.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-1.3.0-beta01.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/res-current.txt b/media2/media2-widget/api/res-current.txt
deleted file mode 100644
index 9015818..0000000
--- a/media2/media2-widget/api/res-current.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-attr enableControlView
-attr viewType
diff --git a/media2/media2-widget/api/restricted_1.0.0-beta01.txt b/media2/media2-widget/api/restricted_1.0.0-beta01.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/restricted_1.0.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_1.0.0-beta02.txt b/media2/media2-widget/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_1.0.0-rc02.txt b/media2/media2-widget/api/restricted_1.0.0-rc02.txt
deleted file mode 100644
index b614f1a..0000000
--- a/media2/media2-widget/api/restricted_1.0.0-rc02.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 3.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_1.1.0-beta01.txt b/media2/media2-widget/api/restricted_1.1.0-beta01.txt
deleted file mode 100644
index c21654a..0000000
--- a/media2/media2-widget/api/restricted_1.1.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_1.2.0-beta01.txt b/media2/media2-widget/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index c21654a..0000000
--- a/media2/media2-widget/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  public class MediaControlView extends android.view.ViewGroup {
-    ctor public MediaControlView(android.content.Context);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method public void requestPlayButtonFocus();
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  public static interface MediaControlView.OnFullScreenListener {
-    method public void onFullScreen(android.view.View, boolean);
-  }
-
-  public class VideoView extends android.view.ViewGroup {
-    ctor public VideoView(android.content.Context);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method public int getViewType();
-    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method public void setMediaController(androidx.media2.session.MediaController);
-    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method public void setPlayer(androidx.media2.common.SessionPlayer);
-    method public void setViewType(int);
-    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  public static interface VideoView.OnViewTypeChangedListener {
-    method public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_1.3.0-beta01.txt b/media2/media2-widget/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index 501d5c5..0000000
--- a/media2/media2-widget/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  @Deprecated public class MediaControlView extends android.view.ViewGroup {
-    ctor @Deprecated public MediaControlView(android.content.Context);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public void requestPlayButtonFocus();
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static interface MediaControlView.OnFullScreenListener {
-    method @Deprecated public void onFullScreen(android.view.View, boolean);
-  }
-
-  @Deprecated public class VideoView extends android.view.ViewGroup {
-    ctor @Deprecated public VideoView(android.content.Context);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method @Deprecated public int getViewType();
-    method @Deprecated public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void setViewType(int);
-    field @Deprecated public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field @Deprecated public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  @Deprecated public static interface VideoView.OnViewTypeChangedListener {
-    method @Deprecated public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/api/restricted_current.txt b/media2/media2-widget/api/restricted_current.txt
deleted file mode 100644
index 501d5c5..0000000
--- a/media2/media2-widget/api/restricted_current.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-// Signature format: 4.0
-package androidx.media2.widget {
-
-  @Deprecated public class MediaControlView extends android.view.ViewGroup {
-    ctor @Deprecated public MediaControlView(android.content.Context);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public void requestPlayButtonFocus();
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-  }
-
-  @Deprecated public static interface MediaControlView.OnFullScreenListener {
-    method @Deprecated public void onFullScreen(android.view.View, boolean);
-  }
-
-  @Deprecated public class VideoView extends android.view.ViewGroup {
-    ctor @Deprecated public VideoView(android.content.Context);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public VideoView(android.content.Context, android.util.AttributeSet?, int);
-    method @Deprecated public androidx.media2.widget.MediaControlView? getMediaControlView();
-    method @Deprecated public int getViewType();
-    method @Deprecated public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
-    method @Deprecated public void setMediaController(androidx.media2.session.MediaController);
-    method @Deprecated public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
-    method @Deprecated public void setPlayer(androidx.media2.common.SessionPlayer);
-    method @Deprecated public void setViewType(int);
-    field @Deprecated public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
-    field @Deprecated public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
-  }
-
-  @Deprecated public static interface VideoView.OnViewTypeChangedListener {
-    method @Deprecated public void onViewTypeChanged(android.view.View, int);
-  }
-
-}
-
diff --git a/media2/media2-widget/build.gradle b/media2/media2-widget/build.gradle
deleted file mode 100644
index b4a53a2..0000000
--- a/media2/media2-widget/build.gradle
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 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.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    api(project(":media2:media2-session"))
-    implementation("androidx.appcompat:appcompat:1.1.0")
-    implementation("androidx.palette:palette:1.0.0")
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation(project(":internal-testutils-runtime"))
-    androidTestImplementation(project(":media2:media2-player"))
-    androidTestImplementation(libs.multidex)
-}
-
-android {
-    defaultConfig {
-        multiDexEnabled true
-    }
-    sourceSets {
-        main.res.srcDirs += "src/main/res-public"
-    }
-    lintOptions {
-	// Lint cannot determine the groupId of androidx.media2:media2widget,
-	// so it fails on calls to other media2 libraries.
-	// Remove once b/136119801 is fixed
-	disable("RestrictedApi")
-    }
-    namespace "androidx.media2.widget"
-}
-
-androidx {
-    name = "Media2 Widget"
-    publish = Publish.SNAPSHOT_AND_RELEASE
-    inceptionYear = "2018"
-    description = "AndroidX Media2 Widget"
-    metalavaK2UastEnabled = true
-}
diff --git a/media2/media2-widget/lint-baseline.xml b/media2/media2-widget/lint-baseline.xml
deleted file mode 100644
index e66d3e9..0000000
--- a/media2/media2-widget/lint-baseline.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 24 (current min is 19): `android.view.PixelCopy.OnPixelCopyFinishedListener`"
-        errorLine1="                    PixelCopy.request(source, dest, new PixelCopy.OnPixelCopyFinishedListener() {"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleTrack.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    private synchronized void takeTime(long timeMs) {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleTrack.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    protected synchronized void clearActiveCues() {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleTrack.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    protected synchronized boolean addCue(Cue cue) {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleTrack.java"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    /**"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleTrack.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(bufferQueueToleranceMs);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(elapsedTimeForSecondScreenshotMs);"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java"/>
-    </issue>
-
-    <issue
-        id="PrivateConstructorForUtilityClass"
-        message="Utility class is missing private constructor"
-        errorLine1="    static class MediaFormatUtil {"
-        errorLine2="                 ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/media2/widget/SubtitleController.java"/>
-    </issue>
-
-</issues>
diff --git a/media2/media2-widget/src/androidTest/AndroidManifest.xml b/media2/media2-widget/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 6d838cc..0000000
--- a/media2/media2-widget/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2018 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <application>
-        <activity
-            android:name="androidx.media2.widget.VideoViewTestActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:exported="false"
-            android:label="VideoViewTestActivity"
-            android:theme="@style/Theme.AppCompat">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:name="androidx.media2.widget.MediaControlViewTestActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:exported="false"
-            android:label="MediaControlViewTestActivity"
-            android:theme="@style/Theme.AppCompat">
-            <intent-filter>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-    </application>
-    <uses-permission android:name="android.permission.INTERNET" />
-</manifest>
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/AspectRatioMatcher.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/AspectRatioMatcher.java
deleted file mode 100644
index 4fec272..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/AspectRatioMatcher.java
+++ /dev/null
@@ -1,62 +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.media2.widget;
-
-import android.view.View;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-
-class AspectRatioMatcher extends TypeSafeMatcher<View> {
-    private static final float EPSILON = .01f;
-
-    private final int mExpectedWidth;
-    private final int mExpectedHeight;
-
-    private AspectRatioMatcher(int expectedWidth, int expectedHeight) {
-        if (expectedWidth <= 0) {
-            throw new IllegalArgumentException("expectedWidth should be greater than zero");
-        }
-        if (expectedHeight <= 0) {
-            throw new IllegalArgumentException("expectedHeight should be greater than zero");
-        }
-        mExpectedWidth = expectedWidth;
-        mExpectedHeight = expectedHeight;
-    }
-
-    @Override
-    protected boolean matchesSafely(View item) {
-        int width = item.getWidth();
-        int height = item.getHeight();
-        if (width <= 0 || height <= 0) {
-            return false;
-        }
-        float expected = (float) mExpectedWidth / mExpectedHeight;
-        float actual = (float) width / height;
-        return Math.abs(expected - actual) < EPSILON;
-    }
-
-    @Override
-    public void describeTo(Description description) {
-        description.appendText("with aspect ratio: ");
-        description.appendValue(mExpectedWidth + "x" + mExpectedHeight);
-    }
-
-    static AspectRatioMatcher withAspectRatio(int width, int height) {
-        return new AspectRatioMatcher(width, height);
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTestActivity.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTestActivity.java
deleted file mode 100644
index 5dd2103..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTestActivity.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.os.Bundle;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.media2.widget.test.R;
-
-/**
- * A minimal application for testing {@link MediaControlView}.
- */
-public class MediaControlViewTestActivity extends FragmentActivity {
-    private SurfaceView mSurfaceView;
-
-    /**
-     * Called with the activity is first created.
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        TestUtils.setKeepScreenOn(this);
-        setContentView(R.layout.mediacontrolviewtest_layout);
-        mSurfaceView = findViewById(R.id.surfaceview);
-    }
-
-    public SurfaceHolder getSurfaceHolder() {
-        if (mSurfaceView == null) return null;
-        return mSurfaceView.getHolder();
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
deleted file mode 100644
index 62a867f..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
+++ /dev/null
@@ -1,611 +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.media2.widget;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
-import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
-import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
-import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.Uri;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link MediaControlView} with a {@link androidx.media2.common.SessionPlayer} or a {@link
- * androidx.media2.session.MediaController}.
- */
-@SuppressWarnings("deprecation")
-@RunWith(Parameterized.class)
-@LargeTest
-public class MediaControlView_WithPlayerTest extends MediaWidgetTestBase {
-    @Parameterized.Parameters(name = "PlayerType={0}")
-    public static List<String> getPlayerTypes() {
-        return Arrays.asList(PLAYER_TYPE_MEDIA_CONTROLLER, PLAYER_TYPE_MEDIA_PLAYER);
-    }
-
-    private static final long FFWD_MS = 30000L;
-    private static final long REW_MS = 10000L;
-
-    private String mPlayerType;
-    private MediaControlViewTestActivity mActivity;
-    private MediaControlView mMediaControlView;
-    private androidx.media2.common.MediaItem mFileSchemeMediaItem;
-
-    @SuppressWarnings("deprecation")
-    @Rule
-    public androidx.test.rule.ActivityTestRule<MediaControlViewTestActivity> mActivityRule =
-            new androidx.test.rule.ActivityTestRule<>(MediaControlViewTestActivity.class);
-
-    public MediaControlView_WithPlayerTest(String playerType) {
-        mPlayerType = playerType;
-    }
-
-    @Before
-    public void setup() throws Throwable {
-        // Ignore all tests, b/202710013
-        assumeTrue(false);
-
-        mActivity = mActivityRule.getActivity();
-        mMediaControlView = mActivity.findViewById(
-                androidx.media2.widget.test.R.id.mediacontrolview);
-
-        Uri fileSchemeUri = getResourceUri(
-                androidx.media2.widget.test.R.raw.test_file_scheme_video);
-        mFileSchemeMediaItem = createTestMediaItem(fileSchemeUri);
-        checkAttachedToWindow(mMediaControlView);
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                closeAll();
-            }
-        });
-    }
-
-    /**
-     * It also tests clicking play button
-     */
-    @Test
-    public void setPlayerOrController_PausedState() throws Throwable {
-        final CountDownLatch latchForPausedState = new CountDownLatch(1);
-        final CountDownLatch latchForPlayingState = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PAUSED) {
-                                    latchForPausedState.countDown();
-                                } else if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PLAYING) {
-                                    latchForPlayingState.countDown();
-                                }
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_play_button_desc)))
-                .perform(click());
-        assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_pause_button_desc)));
-    }
-
-    /**
-     * It also tests clicking pause button
-     */
-    @Test
-    public void setPlayerOrController_PlayingState() throws Throwable {
-        final CountDownLatch latchForPreparedState = new CountDownLatch(1);
-        final CountDownLatch latchForPausedState = new CountDownLatch(1);
-        final CountDownLatch latchForPlayingState = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            private int mState =
-                                    androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (mState == androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE
-                                        && state
-                                                == androidx.media2.common.SessionPlayer
-                                                        .PLAYER_STATE_PAUSED) {
-                                    latchForPreparedState.countDown();
-                                }
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PLAYING) {
-                                    latchForPlayingState.countDown();
-                                }
-                                if (mState
-                                                == androidx.media2.common.SessionPlayer
-                                                        .PLAYER_STATE_PLAYING
-                                        && state
-                                                == androidx.media2.common.SessionPlayer
-                                                        .PLAYER_STATE_PAUSED) {
-                                    latchForPausedState.countDown();
-                                }
-                                mState = state;
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        assertTrue(latchForPreparedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        playerWrapper.play();
-        assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        setPlayerWrapper(playerWrapper);
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_pause_button_desc)))
-                .perform(click());
-        assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_play_button_desc)));
-    }
-
-    @Test
-    public void setPlayerAndController_MultipleTimes() throws Throwable {
-        DefaultPlayerCallback callback1 = new DefaultPlayerCallback();
-        PlayerWrapper wrapper1 = createPlayerWrapper(callback1, mFileSchemeMediaItem, null);
-        assertTrue(callback1.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        DefaultPlayerCallback callback2 = new DefaultPlayerCallback();
-        PlayerWrapper wrapper2 = createPlayerWrapperOfPlayer(callback2, mFileSchemeMediaItem, null);
-        assertTrue(callback2.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        DefaultPlayerCallback callback3 = new DefaultPlayerCallback();
-        PlayerWrapper wrapper3 = createPlayerWrapperOfController(callback3, mFileSchemeMediaItem,
-                null);
-        assertTrue(callback3.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        DefaultPlayerCallback callback4 = new DefaultPlayerCallback();
-        PlayerWrapper wrapper4 = createPlayerWrapper(callback4, mFileSchemeMediaItem, null);
-        assertTrue(callback4.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        setPlayerWrapper(wrapper1);
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click());
-        assertTrue(callback1.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, callback2.mPlayingLatch.getCount());
-        assertEquals(1, callback3.mPlayingLatch.getCount());
-        assertEquals(1, callback4.mPlayingLatch.getCount());
-        callback1.mPlayingLatch = new CountDownLatch(1);
-
-        setPlayerWrapper(wrapper2);
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click());
-        assertTrue(callback2.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, callback1.mPlayingLatch.getCount());
-        assertEquals(1, callback3.mPlayingLatch.getCount());
-        assertEquals(1, callback4.mPlayingLatch.getCount());
-        callback2.mPlayingLatch = new CountDownLatch(1);
-
-        setPlayerWrapper(wrapper3);
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click());
-        assertTrue(callback3.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, callback1.mPlayingLatch.getCount());
-        assertEquals(1, callback2.mPlayingLatch.getCount());
-        assertEquals(1, callback4.mPlayingLatch.getCount());
-        callback3.mPlayingLatch = new CountDownLatch(1);
-
-        setPlayerWrapper(wrapper4);
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click());
-        assertTrue(callback4.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, callback1.mPlayingLatch.getCount());
-        assertEquals(1, callback2.mPlayingLatch.getCount());
-        assertEquals(1, callback3.mPlayingLatch.getCount());
-    }
-
-    @Test
-    public void ffwdButtonClick() throws Throwable {
-        final CountDownLatch latchForPausedState = new CountDownLatch(1);
-        final CountDownLatch latchForFfwd = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            @Override
-                            public void onSeekCompleted(
-                                    @NonNull PlayerWrapper player, long position) {
-                                if (position >= FFWD_MS) {
-                                    latchForFfwd.countDown();
-                                }
-                            }
-
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PAUSED) {
-                                    latchForPausedState.countDown();
-                                }
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.ffwd), isCompletelyDisplayed())).perform(click());
-        assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void rewButtonClick() throws Throwable {
-        final CountDownLatch latchForFfwd = new CountDownLatch(1);
-        final CountDownLatch latchForRew = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            long mExpectedPosition = FFWD_MS;
-                            final long mDelta = 1000L;
-
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PAUSED) {
-                                    mExpectedPosition = FFWD_MS;
-                                    player.seekTo(mExpectedPosition);
-                                }
-                            }
-
-                            @Override
-                            public void onSeekCompleted(
-                                    @NonNull PlayerWrapper player, long position) {
-                                // Ignore the initial seek. Internal MediaPlayer behavior can be
-                                // changed.
-                                if (position == 0 && mExpectedPosition == FFWD_MS) {
-                                    return;
-                                }
-                                assertTrue(equalsSeekPosition(mExpectedPosition, position, mDelta));
-                                if (mExpectedPosition == FFWD_MS) {
-                                    mExpectedPosition = position - REW_MS;
-                                    latchForFfwd.countDown();
-                                } else {
-                                    latchForRew.countDown();
-                                }
-                            }
-
-                            private boolean equalsSeekPosition(
-                                    long expected, long actual, long delta) {
-                                return (actual < expected + delta) && (actual > expected - delta);
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.rew), isCompletelyDisplayed())).perform(click());
-        assertTrue(latchForRew.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void prevNextButtonClick() throws Throwable {
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        final List<androidx.media2.common.MediaItem> playlist = createTestPlaylist();
-        final PlayerWrapper playerWrapper = createPlayerWrapper(callback, null, playlist);
-        setPlayerWrapper(playerWrapper);
-
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(0, playerWrapper.getCurrentMediaItemIndex());
-        onView(allOf(withId(R.id.prev), isCompletelyDisplayed()))
-                .check(matches(not(isEnabled())));
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
-        callback.mItemLatch = new CountDownLatch(1);
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
-
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
-        onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
-        callback.mItemLatch = new CountDownLatch(1);
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
-
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(2, playerWrapper.getCurrentMediaItemIndex());
-        onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed()))
-                .check(matches(not(isEnabled())));
-        callback.mItemLatch = new CountDownLatch(1);
-        onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).perform(click());
-
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
-        onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
-        onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
-    }
-
-    @FlakyTest(bugId = 179623359)
-    @Test
-    public void setMetadataForNonMusicFile() throws Throwable {
-        final String title = "BigBuckBunny";
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        androidx.media2.common.MediaMetadata existingMetadata = mFileSchemeMediaItem.getMetadata();
-        androidx.media2.common.MediaMetadata.Builder metadataBuilder =
-                existingMetadata == null
-                        ? new androidx.media2.common.MediaMetadata.Builder()
-                        : new androidx.media2.common.MediaMetadata.Builder(existingMetadata);
-        androidx.media2.common.MediaMetadata metadata =
-                metadataBuilder
-                        .putString(androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE, title)
-                        .build();
-        mFileSchemeMediaItem.setMetadata(metadata);
-
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            @Override
-                            public void onCurrentMediaItemChanged(
-                                    @NonNull PlayerWrapper player,
-                                    @Nullable androidx.media2.common.MediaItem item) {
-                                if (item != null) {
-                                    assertNotNull(item.getMetadata());
-                                    assertEquals(
-                                            title,
-                                            metadata.getString(
-                                                    androidx.media2.common.MediaMetadata
-                                                            .METADATA_KEY_TITLE));
-                                    latch.countDown();
-                                }
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(withId(R.id.title_text)).check(matches(withText(title)));
-    }
-
-    @Test
-    public void subtitleButtonVisibilityForMusicFile() throws Throwable {
-        Uri uri = getResourceUri(androidx.media2.widget.test.R.raw.test_music);
-        final androidx.media2.common.MediaItem uriMediaItem = createTestMediaItem(uri);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            @Override
-                            void onTracksChanged(
-                                    @NonNull PlayerWrapper player,
-                                    @NonNull
-                                            List<androidx.media2.common.SessionPlayer.TrackInfo>
-                                                    tracks) {
-                                assertNotNull(tracks);
-                                if (tracks.isEmpty()) {
-                                    // This callback can be called before tracks are available after
-                                    // setandroidx.media2.common.MediaItem
-                                    return;
-                                }
-                                latch.countDown();
-                            }
-                        },
-                        uriMediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(withId(R.id.subtitle)).check(matches(not(isDisplayed())));
-    }
-
-    @Test
-    public void updateAndSelectSubtitleTrack() throws Throwable {
-        Uri uri = getResourceUri(
-                androidx.media2.widget.test.R.raw.testvideo_with_2_subtitle_tracks);
-
-        final String subtitleTrackOffText = mContext.getResources().getString(
-                R.string.MediaControlView_subtitle_off_text);
-        final String subtitleTrack1Text = mContext.getResources().getString(
-                R.string.MediaControlView_subtitle_track_number_text, 1);
-
-        final androidx.media2.common.MediaItem mediaItem = createTestMediaItem(uri);
-
-        final CountDownLatch latchForReady = new CountDownLatch(1);
-        final CountDownLatch latchForTrackUpdate = new CountDownLatch(1);
-        final CountDownLatch latchForSubtitleSelect = new CountDownLatch(1);
-        final CountDownLatch latchForSubtitleDeselect = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            private androidx.media2.common.SessionPlayer.TrackInfo
-                                    mFirstSubtitleTrack;
-
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PAUSED) {
-                                    latchForReady.countDown();
-                                }
-                            }
-
-                            @Override
-                            void onTracksChanged(
-                                    @NonNull PlayerWrapper player,
-                                    @NonNull
-                                            List<androidx.media2.common.SessionPlayer.TrackInfo>
-                                                    tracks) {
-                                if (mFirstSubtitleTrack != null) {
-                                    return;
-                                }
-                                assertNotNull(tracks);
-                                for (int i = 0; i < tracks.size(); i++) {
-                                    androidx.media2.common.SessionPlayer.TrackInfo trackInfo =
-                                            tracks.get(i);
-                                    if (trackInfo.getTrackType()
-                                            == androidx.media2.common.SessionPlayer.TrackInfo
-                                                    .MEDIA_TRACK_TYPE_SUBTITLE) {
-                                        mFirstSubtitleTrack = trackInfo;
-                                        latchForTrackUpdate.countDown();
-                                        break;
-                                    }
-                                }
-                            }
-
-                            @Override
-                            public void onTrackSelected(
-                                    @NonNull PlayerWrapper player,
-                                    @NonNull
-                                            androidx.media2.common.SessionPlayer.TrackInfo
-                                                    trackInfo) {
-                                assertEquals(mFirstSubtitleTrack, trackInfo);
-                                latchForSubtitleSelect.countDown();
-                            }
-
-                            @Override
-                            public void onTrackDeselected(
-                                    @NonNull PlayerWrapper player,
-                                    @NonNull
-                                            androidx.media2.common.SessionPlayer.TrackInfo
-                                                    trackInfo) {
-                                assertEquals(mFirstSubtitleTrack, trackInfo);
-                                latchForSubtitleDeselect.countDown();
-                            }
-                        },
-                        mediaItem,
-                        null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(latchForReady.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        // MediaPlayer needs a surface to be set in order to produce subtitle tracks
-        playerWrapper.setSurface(mActivity.getSurfaceHolder().getSurface());
-        assertTrue(latchForTrackUpdate.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        onView(withId(R.id.subtitle)).check(matches(isClickable()));
-        onView(withId(R.id.subtitle)).perform(click());
-        onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup())
-                .check(matches(isCompletelyDisplayed()));
-        onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup()).perform(click());
-        assertTrue(latchForSubtitleSelect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        onView(withId(R.id.subtitle)).check(matches(isClickable()));
-        onView(withId(R.id.subtitle)).perform(click());
-        onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup())
-                .check(matches(isCompletelyDisplayed()));
-        onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup()).perform(click());
-        assertTrue(latchForSubtitleDeselect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void attachViewAndPlayAfterSetPlayerOrController() throws Throwable {
-        final CountDownLatch latchForPausedState = new CountDownLatch(1);
-        final CountDownLatch latchForPlayingState = new CountDownLatch(1);
-        final PlayerWrapper playerWrapper =
-                createPlayerWrapper(
-                        new PlayerWrapper.PlayerCallback() {
-                            @Override
-                            public void onPlayerStateChanged(
-                                    @NonNull PlayerWrapper player, int state) {
-                                if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PAUSED) {
-                                    latchForPausedState.countDown();
-                                } else if (state
-                                        == androidx.media2.common.SessionPlayer
-                                                .PLAYER_STATE_PLAYING) {
-                                    latchForPlayingState.countDown();
-                                }
-                            }
-                        },
-                        mFileSchemeMediaItem,
-                        null);
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewGroup layout = mActivity.findViewById(
-                        androidx.media2.widget.test.R.id.framelayout);
-                layout.removeView(mMediaControlView);
-                mMediaControlView = new MediaControlView(mActivity);
-                if (playerWrapper.mPlayer != null) {
-                    mMediaControlView.setPlayer(playerWrapper.mPlayer);
-                } else if (playerWrapper.mController != null) {
-                    mMediaControlView.setMediaController(playerWrapper.mController);
-                }
-                layout.addView(mMediaControlView);
-            }
-        });
-        checkAttachedToWindow(mMediaControlView);
-        assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_play_button_desc)))
-                .perform(click());
-        assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
-                .check(matches(withContentDescription(R.string.mcv2_pause_button_desc)));
-    }
-
-    private void setPlayerWrapper(final PlayerWrapper playerWrapper) throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (playerWrapper.mPlayer != null) {
-                    mMediaControlView.setPlayer(playerWrapper.mPlayer);
-                } else if (playerWrapper.mController != null) {
-                    mMediaControlView.setMediaController(playerWrapper.mController);
-                }
-            }
-        });
-    }
-
-    private PlayerWrapper createPlayerWrapper(
-            @NonNull PlayerWrapper.PlayerCallback callback,
-            @Nullable androidx.media2.common.MediaItem item,
-            @Nullable List<androidx.media2.common.MediaItem> playlist) {
-        return createPlayerWrapperOfType(callback, item, playlist, mPlayerType);
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
deleted file mode 100644
index 2db77e9..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
+++ /dev/null
@@ -1,118 +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.media2.widget;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link MediaControlView} without any {@link androidx.media2.common.SessionPlayer} or {@link
- * androidx.media2.session.MediaController}.
- */
-@SuppressWarnings("deprecation")
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class MediaControlView_WithoutPlayerTest extends MediaWidgetTestBase {
-    private MediaControlViewTestActivity mActivity;
-    private MediaControlView mMediaControlView;
-
-    @SuppressWarnings("deprecation")
-    @Rule
-    public androidx.test.rule.ActivityTestRule<MediaControlViewTestActivity> mActivityRule =
-            new androidx.test.rule.ActivityTestRule<>(MediaControlViewTestActivity.class);
-
-    @Before
-    public void setup() throws Throwable {
-        // Ignore all tests, b/202710013
-        assumeTrue(false);
-
-        mActivity = mActivityRule.getActivity();
-        mMediaControlView = mActivity.findViewById(
-                androidx.media2.widget.test.R.id.mediacontrolview);
-        checkAttachedToWindow(mMediaControlView);
-    }
-
-    @UiThreadTest
-    @Test
-    public void constructor() {
-        new MediaControlView(mActivity);
-        new MediaControlView(mActivity, null);
-        new MediaControlView(mActivity, null, 0);
-    }
-
-    @Test
-    public void fullScreenListener() throws Throwable {
-        onView(withId(R.id.fullscreen)).check(matches(not(isDisplayed())));
-
-        final CountDownLatch latchOn = new CountDownLatch(1);
-        final CountDownLatch latchOff = new CountDownLatch(1);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mMediaControlView.setOnFullScreenListener(
-                        new MediaControlView.OnFullScreenListener() {
-                            @Override
-                            public void onFullScreen(@NonNull View view, boolean fullScreen) {
-                                if (fullScreen) {
-                                    latchOn.countDown();
-                                } else {
-                                    latchOff.countDown();
-                                }
-                            }
-                        });
-            }
-        });
-        onView(withId(R.id.fullscreen)).check(matches(isCompletelyDisplayed()));
-
-        onView(withId(R.id.fullscreen)).perform(click());
-        assertTrue(latchOn.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(withId(R.id.fullscreen)).perform(click());
-        assertTrue(latchOff.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mMediaControlView.setOnFullScreenListener(null);
-            }
-        });
-        onView(withId(R.id.fullscreen)).check(matches(not(isDisplayed())));
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java
deleted file mode 100644
index bac7e6c..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java
+++ /dev/null
@@ -1,56 +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.media2.widget;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.BeforeClass;
-
-/**
- * Base class for all media tests.
- */
-abstract class MediaTestBase {
-    @BeforeClass
-    public static void setupMainLooper() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @SuppressWarnings("deprecation")
-            @Override
-            public void run() {
-                // Prepare the main looper if it hasn't.
-                // Some framework APIs always run on the main looper.
-                if (Looper.getMainLooper() == null) {
-                    Looper.prepareMainLooper();
-                }
-
-                // Initialize AudioManager on the main thread to workaround b/78617702 that
-                // audio focus listener is called on the thread where the AudioManager was
-                // originally initialized.
-                // Without posting this, audio focus listeners wouldn't be called because the
-                // listeners would be posted to the test thread (here) where it waits until the
-                // tests are finished.
-                Context context = ApplicationProvider.getApplicationContext();
-                AudioManager manager =
-                        (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            }
-        });
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
deleted file mode 100644
index 675beea..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
+++ /dev/null
@@ -1,239 +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.media2.widget;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.view.View;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.media2.widget.test.R;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@SuppressWarnings("deprecation")
-public class MediaWidgetTestBase extends MediaTestBase {
-    static final String PLAYER_TYPE_MEDIA_CONTROLLER = "androidx.media2.session.MediaController";
-    static final String PLAYER_TYPE_MEDIA_PLAYER = "androidx.media2.player.MediaPlayer";
-
-    // Expected success time
-    // Increased timeout to pass on old devices (ex. Nexus4 API 17)
-    static final int WAIT_TIME_MS = 2000;
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private List<androidx.media2.common.SessionPlayer> mPlayers = new ArrayList<>();
-
-    @GuardedBy("mLock")
-    private List<androidx.media2.session.MediaSession> mSessions = new ArrayList<>();
-
-    @GuardedBy("mLock")
-    private List<androidx.media2.session.MediaController> mControllers = new ArrayList<>();
-
-    Context mContext;
-    Executor mMainHandlerExecutor;
-    Executor mSessionCallbackExecutor;
-
-    @Before
-    public void setupWidgetTest() {
-        mContext = ApplicationProvider.getApplicationContext();
-        mMainHandlerExecutor = ContextCompat.getMainExecutor(mContext);
-        mSessionCallbackExecutor = Executors.newFixedThreadPool(1);
-    }
-
-    static void checkAttachedToWindow(View view) throws Exception {
-        if (!view.isAttachedToWindow()) {
-            final CountDownLatch latch = new CountDownLatch(1);
-            View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    latch.countDown();
-                }
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                }
-            };
-            view.addOnAttachStateChangeListener(listener);
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    androidx.media2.common.MediaItem createTestMediaItem() {
-        Uri testVideoUri = getResourceUri(R.raw.testvideo_with_2_subtitle_tracks);
-        return createTestMediaItem(testVideoUri);
-    }
-
-    androidx.media2.common.MediaItem createTestMediaItem(Uri uri) {
-        return createTestMediaItem(uri, "defaultMediaId");
-    }
-
-    androidx.media2.common.MediaItem createTestMediaItem(Uri uri, String mediaId) {
-        androidx.media2.common.MediaMetadata metadata =
-                new androidx.media2.common.MediaMetadata.Builder()
-                        .putText(
-                                androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
-                        .build();
-        return new androidx.media2.common.UriMediaItem.Builder(uri).setMetadata(metadata).build();
-    }
-
-    List<androidx.media2.common.MediaItem> createTestPlaylist() {
-        List<androidx.media2.common.MediaItem> list = new ArrayList<>();
-        list.add(createTestMediaItem(getResourceUri(R.raw.test_file_scheme_video), "id_1"));
-        list.add(createTestMediaItem(getResourceUri(R.raw.test_music), "id_2"));
-        list.add(createTestMediaItem(getResourceUri(R.raw.testvideo_with_2_subtitle_tracks),
-                "id_3"));
-        return list;
-    }
-
-    Uri getResourceUri(@IdRes int resId) {
-        return Uri.parse("android.resource://" + mContext.getPackageName() + "/" + resId);
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    PlayerWrapper createPlayerWrapperOfController(
-            @NonNull PlayerWrapper.PlayerCallback callback,
-            @Nullable androidx.media2.common.MediaItem item,
-            @Nullable List<androidx.media2.common.MediaItem> playlist) {
-        androidx.media2.common.SessionPlayer player =
-                new androidx.media2.player.MediaPlayer(mContext);
-        androidx.media2.session.MediaSession session =
-                new androidx.media2.session.MediaSession.Builder(mContext, player)
-                        .setId(UUID.randomUUID().toString())
-                        .setSessionCallback(
-                                mSessionCallbackExecutor,
-                                new androidx.media2.session.MediaSession.SessionCallback() {})
-                        .build();
-        androidx.media2.session.MediaController controller =
-                new androidx.media2.session.MediaController.Builder(mContext)
-                        .setSessionToken(session.getToken())
-                        .build();
-        synchronized (mLock) {
-            mPlayers.add(player);
-            mSessions.add(session);
-            mControllers.add(controller);
-        }
-        PlayerWrapper wrapper = new PlayerWrapper(controller, mMainHandlerExecutor, callback);
-        wrapper.attachCallback();
-        if (item != null) {
-            player.setMediaItem(item);
-            player.prepare();
-        } else if (playlist != null) {
-            player.setPlaylist(playlist, null);
-            player.prepare();
-        }
-        return wrapper;
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    PlayerWrapper createPlayerWrapperOfPlayer(
-            @NonNull PlayerWrapper.PlayerCallback callback,
-            @Nullable androidx.media2.common.MediaItem item,
-            @Nullable List<androidx.media2.common.MediaItem> playlist) {
-        androidx.media2.common.SessionPlayer player =
-                new androidx.media2.player.MediaPlayer(mContext);
-        synchronized (mLock) {
-            mPlayers.add(player);
-        }
-        PlayerWrapper wrapper = new PlayerWrapper(player, mMainHandlerExecutor, callback);
-        wrapper.attachCallback();
-        if (item != null) {
-            player.setMediaItem(item);
-            player.prepare();
-        } else if (playlist != null) {
-            player.setPlaylist(playlist, null);
-            player.prepare();
-        }
-        return wrapper;
-    }
-
-    PlayerWrapper createPlayerWrapperOfType(
-            @NonNull PlayerWrapper.PlayerCallback callback,
-            @Nullable androidx.media2.common.MediaItem item,
-            @Nullable List<androidx.media2.common.MediaItem> playlist,
-            @NonNull String playerType) {
-        if (PLAYER_TYPE_MEDIA_CONTROLLER.equals(playerType)) {
-            return createPlayerWrapperOfController(callback, item, playlist);
-        } else if (PLAYER_TYPE_MEDIA_PLAYER.equals(playerType)) {
-            return createPlayerWrapperOfPlayer(callback, item, playlist);
-        } else {
-            throw new IllegalArgumentException("unknown playerType " + playerType);
-        }
-    }
-
-    void closeAll() {
-        synchronized (mLock) {
-            for (androidx.media2.session.MediaController controller : mControllers) {
-                controller.close();
-            }
-            for (androidx.media2.session.MediaSession session : mSessions) {
-                session.close();
-            }
-            for (androidx.media2.common.SessionPlayer player : mPlayers) {
-                try {
-                    player.close();
-                } catch (Exception ex) {
-                    // ignore
-                }
-            }
-            mControllers.clear();
-            mSessions.clear();
-            mPlayers.clear();
-        }
-    }
-
-    static class DefaultPlayerCallback extends PlayerWrapper.PlayerCallback {
-        volatile CountDownLatch mItemLatch = new CountDownLatch(1);
-        CountDownLatch mPausedLatch = new CountDownLatch(1);
-        CountDownLatch mPlayingLatch = new CountDownLatch(1);
-        String mPrevId = "placeholderId";
-
-        @Override
-        void onCurrentMediaItemChanged(
-                @NonNull PlayerWrapper player, @Nullable androidx.media2.common.MediaItem item) {
-            if (item != null && !TextUtils.equals(mPrevId, item.getMediaId())) {
-                mPrevId = item.getMediaId();
-                mItemLatch.countDown();
-            }
-        }
-
-        @Override
-        void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
-            if (state == androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED) {
-                mPausedLatch.countDown();
-            } else if (state == androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING) {
-                mPlayingLatch.countDown();
-            }
-        }
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/TestUtils.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/TestUtils.java
deleted file mode 100644
index ffeae29..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/TestUtils.java
+++ /dev/null
@@ -1,45 +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.media2.widget;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Build;
-import android.view.WindowManager;
-
-final class TestUtils {
-    @SuppressWarnings("deprecation")
-    static void setKeepScreenOn(Activity activity) {
-        if (Build.VERSION.SDK_INT >= 27) {
-            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            activity.setTurnScreenOn(true);
-            activity.setShowWhenLocked(true);
-            KeyguardManager keyguardManager =
-                    (KeyguardManager) activity.getSystemService(KEYGUARD_SERVICE);
-            keyguardManager.requestDismissKeyguard(activity, null);
-        } else {
-            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-        }
-    }
-
-    private TestUtils() {}
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/UriUtilTest.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/UriUtilTest.java
deleted file mode 100644
index 85e07ec..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/UriUtilTest.java
+++ /dev/null
@@ -1,45 +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.media2.widget;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test {@link UriUtil}.
- */
-@RunWith(AndroidJUnit4.class)
-public class UriUtilTest {
-    @Ignore("b/202710013")
-    @Test
-    @SmallTest
-    public void isFromNetwork() {
-        assertTrue(UriUtil.isFromNetwork(Uri.parse("http://localhost/video.mp4")));
-        assertTrue(UriUtil.isFromNetwork(Uri.parse("https://localhost/video.mp4")));
-        assertTrue(UriUtil.isFromNetwork(Uri.parse("rtsp://localhost/video.mp4")));
-        assertFalse(UriUtil.isFromNetwork(Uri.parse("file:///video.mp4")));
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTestActivity.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTestActivity.java
deleted file mode 100644
index cf5955a..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTestActivity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.media2.widget.test.R;
-
-/**
- * A minimal application for testing {@link VideoView}.
- */
-public class VideoViewTestActivity extends FragmentActivity {
-    /**
-     * Called with the activity is first created.
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        TestUtils.setKeepScreenOn(this);
-        setContentView(R.layout.videoview_layout);
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
deleted file mode 100644
index b4df7e5..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
+++ /dev/null
@@ -1,518 +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.media2.widget;
-
-import static androidx.media2.widget.AspectRatioMatcher.withAspectRatio;
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.app.Activity;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.view.PixelCopy;
-import android.view.SurfaceView;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.media2.widget.test.R;
-import androidx.test.filters.LargeTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link VideoView} with a {@link androidx.media2.common.SessionPlayer} or a {@link
- * androidx.media2.session.MediaController}.
- */
-@SuppressWarnings("deprecation")
-@RunWith(Parameterized.class)
-@LargeTest
-public class VideoView_WithPlayerTest extends MediaWidgetTestBase {
-    static final String TAG = "VideoView_WithPlayerTest";
-    @Parameterized.Parameters(name = "PlayerType={0}")
-    public static List<String> getPlayerTypes() {
-        return Arrays.asList(PLAYER_TYPE_MEDIA_CONTROLLER, PLAYER_TYPE_MEDIA_PLAYER);
-    }
-    private String mPlayerType;
-    private Activity mActivity;
-    private VideoView mVideoView;
-    private androidx.media2.common.MediaItem mMediaItem;
-    private SynchronousPixelCopy mPixelCopyHelper;
-
-    @SuppressWarnings("deprecation")
-    @Rule
-    public androidx.test.rule.ActivityTestRule<VideoViewTestActivity> mActivityRule =
-            new androidx.test.rule.ActivityTestRule<>(VideoViewTestActivity.class);
-
-    public VideoView_WithPlayerTest(String playerType) {
-        mPlayerType = playerType;
-    }
-
-    @Before
-    public void setup() throws Throwable {
-        // Ignore all tests, b/202710013
-        assumeTrue(false);
-
-        mActivity = mActivityRule.getActivity();
-        mVideoView = mActivity.findViewById(R.id.videoview);
-        mMediaItem = createTestMediaItem();
-        mPixelCopyHelper = new SynchronousPixelCopy();
-        checkAttachedToWindow(mVideoView);
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        if (mActivityRule != null) {
-            mActivityRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    closeAll();
-                }
-            });
-        }
-        if (mPixelCopyHelper != null) {
-            mPixelCopyHelper.release();
-        }
-    }
-
-    @Test
-    public void playVideo() throws Throwable {
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem, null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(1, callback.mPlayingLatch.getCount());
-        assertEquals(
-                androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED,
-                playerWrapper.getPlayerState());
-
-        playerWrapper.play();
-        assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        checkVideoRendering(true);
-    }
-
-    @Test
-    public void playVideoWithMediaItemFromFileDescriptor() throws Throwable {
-        AssetFileDescriptor afd = mContext.getResources()
-                .openRawResourceFd(R.raw.testvideo_with_2_subtitle_tracks);
-        final androidx.media2.common.MediaItem item =
-                new androidx.media2.common.FileMediaItem.Builder(
-                                ParcelFileDescriptor.dup(afd.getFileDescriptor()))
-                        .setFileDescriptorOffset(afd.getStartOffset())
-                        .setFileDescriptorLength(afd.getLength())
-                        .build();
-        afd.close();
-
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(callback, item, null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        playerWrapper.play();
-        assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        checkVideoRendering(true);
-    }
-
-    @Test
-    public void playVideoOnTextureView() throws Throwable {
-        final VideoView.OnViewTypeChangedListener mockViewTypeListener =
-                mock(VideoView.OnViewTypeChangedListener.class);
-        if (setViewTypeMayCrash()) {
-            return;
-        }
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem, null);
-        setPlayerWrapper(playerWrapper);
-
-        // The default view type is surface view.
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setOnViewTypeChangedListener(mockViewTypeListener);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-            }
-        });
-        verify(mockViewTypeListener, timeout(WAIT_TIME_MS))
-                .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW);
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        playerWrapper.play();
-        assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        checkVideoRendering(true);
-    }
-
-    @Test
-    public void playVideoWithVisibilityChange() throws Throwable {
-        final VideoView.OnViewTypeChangedListener mockViewTypeListener =
-                mock(VideoView.OnViewTypeChangedListener.class);
-        if (setViewTypeMayCrash()) {
-            return;
-        }
-
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem, null);
-        setPlayerWrapper(playerWrapper);
-
-        // The default view type is surface view.
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW);
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setOnViewTypeChangedListener(mockViewTypeListener);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-                mVideoView.setVisibility(View.GONE);
-            }
-        });
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        playerWrapper.play();
-        assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        checkVideoRendering(false);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setVisibility(View.VISIBLE);
-            }
-        });
-        // Note: Actual view type change is done when VideoView has a valid surface.
-        verify(mockViewTypeListener, timeout(WAIT_TIME_MS))
-                .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW);
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_TEXTUREVIEW);
-        checkVideoRendering(true);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW);
-            }
-        });
-        verify(mockViewTypeListener, timeout(WAIT_TIME_MS))
-                .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_SURFACEVIEW);
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW);
-        checkVideoRendering(true);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setVisibility(View.GONE);
-            }
-        });
-        // Although it is not flaky, since checkVideoRendering() waits a bit before actual
-        // screen capturing, we might need to define a listener to ensure the player's surface
-        // has been released.
-        checkVideoRendering(false);
-    }
-
-    @Test
-    public void setViewType() throws Throwable {
-        if (setViewTypeMayCrash()) {
-            return;
-        }
-        final VideoView.OnViewTypeChangedListener mockViewTypeListener =
-                mock(VideoView.OnViewTypeChangedListener.class);
-
-        DefaultPlayerCallback callback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem, null);
-        setPlayerWrapper(playerWrapper);
-
-        // The default view type is surface view.
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setOnViewTypeChangedListener(mockViewTypeListener);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-                mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW);
-            }
-        });
-
-        assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        // WAIT_TIME_MS multiplied by the number of operations.
-        assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS * 5, TimeUnit.MILLISECONDS));
-        assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW);
-
-        playerWrapper.play();
-        assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        checkVideoRendering(true);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW);
-            }
-        });
-        verify(mockViewTypeListener, timeout(WAIT_TIME_MS))
-                .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW);
-        checkVideoRendering(true);
-    }
-
-    // @UiThreadTest will be ignored by Parameterized test runner (b/30746303)
-    @Test
-    public void attachedMediaControlView_setPlayerOrController() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                PlayerWrapper playerWrapper = createPlayerWrapper(new DefaultPlayerCallback(),
-                        mMediaItem, null);
-
-                MediaControlView defaultMediaControlView = mVideoView.getMediaControlView();
-                assertNotNull(defaultMediaControlView);
-                try {
-                    if (playerWrapper.mPlayer != null) {
-                        defaultMediaControlView.setPlayer(playerWrapper.mPlayer);
-                    } else if (playerWrapper.mController != null) {
-                        defaultMediaControlView.setMediaController(playerWrapper.mController);
-                    } else {
-                        fail("playerWrapper doesn't have neither mPlayer or mController");
-                    }
-                    fail("setPlayer or setMediaController should not be allowed "
-                            + "for MediaControlView attached to VideoView");
-                } catch (IllegalStateException ex) {
-                    // expected
-                }
-
-                MediaControlView newMediaControlView = new MediaControlView(mContext);
-                mVideoView.setMediaControlView(newMediaControlView, -1);
-                try {
-                    if (playerWrapper.mPlayer != null) {
-                        newMediaControlView.setPlayer(playerWrapper.mPlayer);
-                    } else if (playerWrapper.mController != null) {
-                        newMediaControlView.setMediaController(playerWrapper.mController);
-                    } else {
-                        fail("playerWrapper doesn't have neither mPlayer or mController");
-                    }
-                    fail("setPlayer or setMediaController should not be allowed "
-                            + "for MediaControlView attached to VideoView");
-                } catch (IllegalStateException ex) {
-                    // expected
-                }
-            }
-        });
-    }
-
-    @Test
-    public void aspectRatioOfSurfaceView() throws Throwable {
-        androidx.media2.common.MediaItem testMediaItem =
-                createTestMediaItem(getResourceUri(R.raw.test_file_scheme_video));
-        androidx.media2.common.VideoSize testVideoSize =
-                new androidx.media2.common.VideoSize(352, 288);
-        CountDownLatch latch = new CountDownLatch(1);
-
-        mActivityRule.runOnUiThread(() -> {
-            int parentWidth = mVideoView.getWidth();
-            int parentHeight = mVideoView.getHeight();
-
-            View surfaceView = findVideoSurfaceView();
-            assertNotNull("Couldn't find VideoSurfaceView", surfaceView);
-            surfaceView.addOnLayoutChangeListener(
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                        if (left == 0 && top == 0 && right == parentWidth
-                                && bottom == parentHeight) {
-                            // Ignore layout changes to the default size
-                            return;
-                        }
-                        latch.countDown();
-                    });
-        });
-
-        DefaultPlayerCallback playerCallback = new DefaultPlayerCallback();
-        PlayerWrapper playerWrapper = createPlayerWrapper(playerCallback, testMediaItem, null);
-        setPlayerWrapper(playerWrapper);
-        assertTrue(playerCallback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        playerWrapper.play();
-        assertTrue(playerCallback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        onView(instanceOf(VideoSurfaceView.class)).check(matches(
-                withAspectRatio(testVideoSize.getWidth(), testVideoSize.getHeight())));
-
-        // Unable to test the case for multiple media items with different aspect ratio due to the
-        // flakiness of onandroidx.media2.common.VideoSizeChanged of MediaPlayer (b/144876689,
-        // b/144972397)
-    }
-
-    private void setPlayerWrapper(final PlayerWrapper playerWrapper) throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (playerWrapper.mPlayer != null) {
-                    mVideoView.setPlayer(playerWrapper.mPlayer);
-                } else if (playerWrapper.mController != null) {
-                    mVideoView.setMediaController(playerWrapper.mController);
-                }
-            }
-        });
-    }
-
-    private PlayerWrapper createPlayerWrapper(
-            @NonNull PlayerWrapper.PlayerCallback callback,
-            @Nullable androidx.media2.common.MediaItem item,
-            @Nullable List<androidx.media2.common.MediaItem> playlist) {
-        return createPlayerWrapperOfType(callback, item, playlist, mPlayerType);
-    }
-
-    private void checkVideoRendering(boolean expectRendering) throws InterruptedException {
-        if (Build.VERSION.SDK_INT == 28) {
-            // TODO: This if-block for API 28 should be removed. (b/137321781)
-            return;
-        }
-        if (Build.DEVICE.startsWith("generic_") && Build.VERSION.SDK_INT == 26) {
-            return;
-        }
-        if (Build.DEVICE.equals("fugu") && Build.VERSION.SDK_INT == 24) {
-            return;
-        }
-        if (Build.VERSION.SDK_INT >= 24) {
-            final int bufferQueueToleranceMs = 200;
-            final int elapsedTimeForSecondScreenshotMs = 400;
-
-            // Tolerance until the video buffers are actually queued.
-            Thread.sleep(bufferQueueToleranceMs);
-            Bitmap beforeBitmap = getVideoScreenshot();
-            Thread.sleep(elapsedTimeForSecondScreenshotMs);
-            Bitmap afterBitmap = getVideoScreenshot();
-            assertEquals(expectRendering, !afterBitmap.sameAs(beforeBitmap));
-        }
-    }
-
-    private boolean setViewTypeMayCrash() {
-        // TODO(b/143496920): Remove this method which is a guard to avoid crash.
-        // Need to skip the tests, which call VV#setViewType(), on the emulator with API 26.
-        if (Build.DEVICE.startsWith("generic_") && Build.VERSION.SDK_INT == 26) {
-            return true;
-        }
-        return false;
-    }
-
-    private Bitmap getVideoScreenshot() {
-        Bitmap bitmap = Bitmap.createBitmap(mVideoView.getWidth(),
-                mVideoView.getHeight(), Bitmap.Config.RGB_565);
-        if (mVideoView.getViewType() == mVideoView.VIEW_TYPE_SURFACEVIEW) {
-            if (mVideoView.mSurfaceView.hasAvailableSurface()) {
-                int copyResult = mPixelCopyHelper.request(mVideoView.mSurfaceView, bitmap);
-                if (copyResult != PixelCopy.ERROR_SOURCE_NO_DATA) {
-                    assertEquals("PixelCopy failed.", PixelCopy.SUCCESS, copyResult);
-                }
-            }
-        } else {
-            bitmap = mVideoView.mTextureView.getBitmap(bitmap);
-        }
-        return bitmap;
-    }
-
-    @UiThread
-    private VideoSurfaceView findVideoSurfaceView() {
-        for (int i = 0; i < mVideoView.getChildCount(); i++) {
-            View child = mVideoView.getChildAt(i);
-            if (child instanceof VideoSurfaceView) {
-                return (VideoSurfaceView) child;
-            }
-        }
-        return null;
-    }
-
-    private static class SynchronousPixelCopy {
-        private Handler mHandler;
-        private HandlerThread mHandlerThread;
-        private int mStatus = PixelCopy.SUCCESS;
-
-        SynchronousPixelCopy() {
-            if (Build.VERSION.SDK_INT >= 24) {
-                this.mHandlerThread = new HandlerThread("PixelCopyHelper");
-                mHandlerThread.start();
-                this.mHandler = new Handler(mHandlerThread.getLooper());
-            }
-        }
-
-        public void release() {
-            if (Build.VERSION.SDK_INT >= 24) {
-                if (mHandlerThread.isAlive()) {
-                    mHandlerThread.quitSafely();
-                }
-            }
-        }
-
-        public int request(SurfaceView source, Bitmap dest) {
-            if (Build.VERSION.SDK_INT < 24) {
-                return -1;
-            }
-            synchronized (this) {
-                try {
-                    PixelCopy.request(source, dest, new PixelCopy.OnPixelCopyFinishedListener() {
-                        @Override
-                        public void onPixelCopyFinished(int copyResult) {
-                            synchronized (this) {
-                                mStatus = copyResult;
-                                this.notify();
-                            }
-                        }
-                    }, mHandler);
-                    return getResultLocked();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception occurred when copying a SurfaceView.", e);
-                    return -1;
-                }
-            }
-        }
-
-        private int getResultLocked() {
-            try {
-                this.wait(1000);
-            } catch (InterruptedException e) {
-                /* PixelCopy request didn't complete within 1s */
-                mStatus = PixelCopy.ERROR_TIMEOUT;
-            }
-            return mStatus;
-        }
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithoutPlayerTest.java b/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithoutPlayerTest.java
deleted file mode 100644
index 691a1f7..0000000
--- a/media2/media2-widget/src/androidTest/java/androidx/media2/widget/VideoView_WithoutPlayerTest.java
+++ /dev/null
@@ -1,66 +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.media2.widget;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-
-import androidx.media2.widget.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test {@link VideoView} without any {@link androidx.media2.common.SessionPlayer} or {@link
- * androidx.media2.session.MediaController}.
- */
-@SuppressWarnings("deprecation")
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class VideoView_WithoutPlayerTest extends MediaWidgetTestBase {
-    private Activity mActivity;
-    private VideoView mVideoView;
-
-    @SuppressWarnings("deprecation")
-    @Rule
-    public androidx.test.rule.ActivityTestRule<VideoViewTestActivity> mActivityRule =
-            new androidx.test.rule.ActivityTestRule<>(VideoViewTestActivity.class);
-
-    @Before
-    public void setup() throws Throwable {
-        // Ignore all tests, b/202710013
-        assumeTrue(false);
-
-        mActivity = mActivityRule.getActivity();
-        mVideoView = mActivity.findViewById(R.id.videoview);
-        checkAttachedToWindow(mVideoView);
-    }
-
-    @UiThreadTest
-    @Test
-    public void constructor() {
-        new VideoView(mActivity);
-        new VideoView(mActivity, null);
-        new VideoView(mActivity, null, 0);
-    }
-}
diff --git a/media2/media2-widget/src/androidTest/res/layout/mediacontrolviewtest_layout.xml b/media2/media2-widget/src/androidTest/res/layout/mediacontrolviewtest_layout.xml
deleted file mode 100644
index b12b54e..0000000
--- a/media2/media2-widget/src/androidTest/res/layout/mediacontrolviewtest_layout.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/framelayout"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <SurfaceView
-        android:id="@+id/surfaceview"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-    <androidx.media2.widget.MediaControlView
-        android:id="@+id/mediacontrolview"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
-</FrameLayout>
diff --git a/media2/media2-widget/src/androidTest/res/layout/videoview_layout.xml b/media2/media2-widget/src/androidTest/res/layout/videoview_layout.xml
deleted file mode 100644
index f1935c1..0000000
--- a/media2/media2-widget/src/androidTest/res/layout/videoview_layout.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <androidx.media2.widget.VideoView
-        android:id="@+id/videoview"
-        android:layout_width="160dp"
-        android:layout_height="120dp"/>
-</LinearLayout>
diff --git a/media2/media2-widget/src/androidTest/res/raw/test_file_scheme_video.3gp b/media2/media2-widget/src/androidTest/res/raw/test_file_scheme_video.3gp
deleted file mode 100644
index c955479..0000000
--- a/media2/media2-widget/src/androidTest/res/raw/test_file_scheme_video.3gp
+++ /dev/null
Binary files differ
diff --git a/media2/media2-widget/src/androidTest/res/raw/test_music.mp3 b/media2/media2-widget/src/androidTest/res/raw/test_music.mp3
deleted file mode 100644
index ad25360..0000000
--- a/media2/media2-widget/src/androidTest/res/raw/test_music.mp3
+++ /dev/null
Binary files differ
diff --git a/media2/media2-widget/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4 b/media2/media2-widget/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4
deleted file mode 100644
index b8dce17..0000000
--- a/media2/media2-widget/src/androidTest/res/raw/testvideo_with_2_subtitle_tracks.mp4
+++ /dev/null
Binary files differ
diff --git a/media2/media2-widget/src/androidTest/res/values/donottranslate-strings.xml b/media2/media2-widget/src/androidTest/res/values/donottranslate-strings.xml
deleted file mode 100644
index 400f20c..0000000
--- a/media2/media2-widget/src/androidTest/res/values/donottranslate-strings.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<resources>
-</resources>
\ No newline at end of file
diff --git a/media2/media2-widget/src/androidTest/res/values/themes.xml b/media2/media2-widget/src/androidTest/res/values/themes.xml
deleted file mode 100644
index ff1c3ab..0000000
--- a/media2/media2-widget/src/androidTest/res/values/themes.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<resources>
-
-    <style name="HasWindowTitle">
-        <item name="android:windowNoTitle">false</item>
-    </style>
-
-</resources>
\ No newline at end of file
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/AnimatorUtil.java b/media2/media2-widget/src/main/java/androidx/media2/widget/AnimatorUtil.java
deleted file mode 100644
index 09e01fb..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/AnimatorUtil.java
+++ /dev/null
@@ -1,42 +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.media2.widget;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.view.View;
-
-class AnimatorUtil {
-
-    static ObjectAnimator ofTranslationY(float startValue, float endValue, View target) {
-        return ObjectAnimator.ofFloat(target, "translationY", startValue, endValue);
-    }
-
-    static AnimatorSet ofTranslationYTogether(float startValue, float endValue, View[] targets) {
-        AnimatorSet set = new AnimatorSet();
-        if (targets.length == 0) return set;
-        AnimatorSet.Builder builder = set.play(ofTranslationY(startValue, endValue, targets[0]));
-        for (int i = 1; i < targets.length; i++) {
-            builder.with(ofTranslationY(startValue, endValue, targets[i]));
-        }
-        return set;
-    }
-
-    private AnimatorUtil() {
-    }
-
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/CaptionStyle.java b/media2/media2-widget/src/main/java/androidx/media2/widget/CaptionStyle.java
deleted file mode 100644
index 24cb939..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/CaptionStyle.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2020 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.media2.widget;
-
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.os.Build.VERSION;
-import android.view.accessibility.CaptioningManager;
-
-import androidx.annotation.Nullable;
-
-/**
- * Specifies visual properties for video captions, including foreground and
- * background colors, edge properties, and typeface.
- *
- * Note: This class is copied and trimmed from framework code,
- * android.view.accessibility.CaptioningManager.CaptionStyle,
- * in order to support lower than API level 19 devices.
- */
-final class CaptionStyle {
-    /**
-     * Packed value for a color of 'none' and a cached opacity of 100%.
-     */
-    private static final int COLOR_NONE_OPAQUE = 0x000000FF;
-
-    /**
-     * Packed value for a color of 'default' and opacity of 100%.
-     */
-    public static final int COLOR_UNSPECIFIED = 0x00FFFFFF;
-
-    /** The default caption style used to fill in unspecified values. */
-    public static final CaptionStyle DEFAULT;
-
-    /** Unspecified edge type value. */
-    public static final int EDGE_TYPE_UNSPECIFIED = -1;
-
-    /** Edge type value specifying no character edges. */
-    public static final int EDGE_TYPE_NONE = 0;
-
-    /** Edge type value specifying uniformly outlined character edges. */
-    public static final int EDGE_TYPE_OUTLINE = 1;
-
-    /** Edge type value specifying drop-shadowed character edges. */
-    public static final int EDGE_TYPE_DROP_SHADOW = 2;
-
-    /** Edge type value specifying raised bevel character edges. */
-    public static final int EDGE_TYPE_RAISED = 3;
-
-    /** Edge type value specifying depressed bevel character edges. */
-    public static final int EDGE_TYPE_DEPRESSED = 4;
-
-    /** The preferred foreground color for video captions. */
-    public final int foregroundColor;
-
-    /** The preferred background color for video captions. */
-    public final int backgroundColor;
-
-    /**
-     * The preferred edge type for video captions, one of:
-     * <ul>
-     * <li>{@link #EDGE_TYPE_UNSPECIFIED}
-     * <li>{@link #EDGE_TYPE_NONE}
-     * <li>{@link #EDGE_TYPE_OUTLINE}
-     * <li>{@link #EDGE_TYPE_DROP_SHADOW}
-     * <li>{@link #EDGE_TYPE_RAISED}
-     * <li>{@link #EDGE_TYPE_DEPRESSED}
-     * </ul>
-     */
-    public final int edgeType;
-
-    /**
-     * The preferred edge color for video captions, if using an edge type
-     * other than {@link #EDGE_TYPE_NONE}.
-     */
-    public final int edgeColor;
-
-    /** The preferred window color for video captions. */
-    public final int windowColor;
-
-    private final boolean mHasForegroundColor;
-    private final boolean mHasBackgroundColor;
-    private final boolean mHasEdgeType;
-    private final boolean mHasEdgeColor;
-    private final boolean mHasWindowColor;
-
-    /** Lazily-created typeface based on the raw typeface string. */
-    private Typeface mParsedTypeface;
-
-    CaptionStyle(CaptioningManager.CaptionStyle captionStyle) {
-        this(captionStyle.foregroundColor, captionStyle.backgroundColor, captionStyle.edgeType,
-                captionStyle.edgeColor,
-                VERSION.SDK_INT >= 21 ? captionStyle.windowColor : COLOR_NONE_OPAQUE,
-                captionStyle.getTypeface());
-    }
-
-    CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
-            int windowColor, @Nullable Typeface typeface) {
-        mHasForegroundColor = hasColor(foregroundColor);
-        mHasBackgroundColor = hasColor(backgroundColor);
-        mHasEdgeType = edgeType != EDGE_TYPE_UNSPECIFIED;
-        mHasEdgeColor = hasColor(edgeColor);
-        mHasWindowColor = hasColor(windowColor);
-
-        // Always use valid colors, even when no override is specified, to
-        // ensure backwards compatibility with apps targeting KitKat MR2.
-        this.foregroundColor = mHasForegroundColor ? foregroundColor : Color.WHITE;
-        this.backgroundColor = mHasBackgroundColor ? backgroundColor : Color.BLACK;
-        this.edgeType = mHasEdgeType ? edgeType : EDGE_TYPE_NONE;
-        this.edgeColor = mHasEdgeColor ? edgeColor : Color.BLACK;
-        this.windowColor = mHasWindowColor ? windowColor : COLOR_NONE_OPAQUE;
-
-        mParsedTypeface = typeface;
-    }
-
-    /**
-     * Returns whether a packed color indicates a non-default value.
-     *
-     * @param packedColor the packed color value
-     * @return {@code true} if a non-default value is specified
-     */
-    static boolean hasColor(int packedColor) {
-        // Matches the color packing code from Settings. "Default" packed
-        // colors are indicated by zero alpha and non-zero red/blue. The
-        // cached alpha value used by Settings is stored in green.
-        return (packedColor >>> 24) != 0 || (packedColor & 0xFFFF00) == 0;
-    }
-
-    /**
-     * @return {@code true} if the user has specified a background color
-     *         that should override the application default, {@code false}
-     *         otherwise
-     */
-    boolean hasBackgroundColor() {
-        return mHasBackgroundColor;
-    }
-
-    /**
-     * @return {@code true} if the user has specified a foreground color
-     *         that should override the application default, {@code false}
-     *         otherwise
-     */
-    boolean hasForegroundColor() {
-        return mHasForegroundColor;
-    }
-
-    /**
-     * @return {@code true} if the user has specified an edge type that
-     *         should override the application default, {@code false}
-     *         otherwise
-     */
-    boolean hasEdgeType() {
-        return mHasEdgeType;
-    }
-
-    /**
-     * @return {@code true} if the user has specified an edge color that
-     *         should override the application default, {@code false}
-     *         otherwise
-     */
-    boolean hasEdgeColor() {
-        return mHasEdgeColor;
-    }
-
-    /**
-     * @return {@code true} if the user has specified a window color that
-     *         should override the application default, {@code false}
-     *         otherwise
-     */
-    boolean hasWindowColor() {
-        return mHasWindowColor;
-    }
-
-    /**
-     * @return the preferred {@link Typeface} for video captions, or null if
-     *         not specified
-     */
-    @Nullable
-    public Typeface getTypeface() {
-        return mParsedTypeface;
-    }
-
-    static {
-        DEFAULT = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
-                Color.BLACK, COLOR_NONE_OPAQUE, null);
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CCParser.java b/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CCParser.java
deleted file mode 100644
index 37a0734..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CCParser.java
+++ /dev/null
@@ -1,971 +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.media2.widget;
-
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.text.style.CharacterStyle;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
-import android.text.style.UpdateAppearance;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * CCParser processes CEA-608 closed caption data.
- *
- * It calls back into OnDisplayChangedListener upon
- * display change with styled text for rendering.
- *
- */
-class Cea608CCParser {
-    public static final int MAX_ROWS = 15;
-    public static final int MAX_COLS = 32;
-
-    private static final String TAG = "Cea608CCParser";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final int INVALID = -1;
-
-    // EIA-CEA-608: Table 70 - Control Codes
-    private static final int RCL = 0x20;
-    private static final int BS  = 0x21;
-    // Note: 0x22 (alarm off) and 0x23 (alarm on) are unused code.
-    private static final int DER = 0x24;
-    private static final int RU2 = 0x25;
-    private static final int RU3 = 0x26;
-    private static final int RU4 = 0x27;
-    private static final int FON = 0x28;
-    private static final int RDC = 0x29;
-    private static final int TR  = 0x2a;
-    private static final int RTD = 0x2b;
-    private static final int EDM = 0x2c;
-    private static final int CR  = 0x2d;
-    private static final int ENM = 0x2e;
-    private static final int EOC = 0x2f;
-
-    // Transparent Space
-    private static final char TS = '\u00A0';
-
-    // Captioning Modes
-    private static final int MODE_PAINT_ON = 1;
-    private static final int MODE_ROLL_UP = 2;
-    private static final int MODE_POP_ON = 3;
-    private static final int MODE_TEXT = 4;
-
-    private final DisplayListener mListener;
-
-    private int mMode = MODE_PAINT_ON;
-    private int mRollUpSize = 4;
-    private int mPrevCtrlCode = INVALID;
-
-    private CCMemory mDisplay = new CCMemory();
-    private CCMemory mNonDisplay = new CCMemory();
-    private CCMemory mTextMem = new CCMemory();
-
-    Cea608CCParser(DisplayListener listener) {
-        mListener = listener;
-    }
-
-    public void parse(byte[] data) {
-        CCData[] ccData = CCData.fromByteArray(data);
-
-        for (int i = 0; i < ccData.length; i++) {
-            if (DEBUG) {
-                Log.d(TAG, ccData[i].toString());
-            }
-
-            if (handleCtrlCode(ccData[i])
-                    || handleTabOffsets(ccData[i])
-                    || handlePACCode(ccData[i])
-                    || handleMidRowCode(ccData[i])) {
-                continue;
-            }
-
-            handleDisplayableChars(ccData[i]);
-        }
-    }
-
-    interface DisplayListener {
-        void onDisplayChanged(SpannableStringBuilder[] styledTexts);
-        CaptionStyle getCaptionStyle();
-    }
-
-    private CCMemory getMemory() {
-        // get the CC memory to operate on for current mode
-        switch (mMode) {
-            case MODE_POP_ON:
-                return mNonDisplay;
-            case MODE_TEXT:
-                // TODO(chz): support only caption mode for now,
-                // in text mode, dump everything to text mem.
-                return mTextMem;
-            case MODE_PAINT_ON:
-            case MODE_ROLL_UP:
-                return mDisplay;
-            default:
-                Log.w(TAG, "unrecoginized mode: " + mMode);
-        }
-        return mDisplay;
-    }
-
-    private boolean handleDisplayableChars(CCData ccData) {
-        if (!ccData.isDisplayableChar()) {
-            return false;
-        }
-
-        // Extended char includes 1 automatic backspace
-        if (ccData.isExtendedChar()) {
-            getMemory().bs();
-        }
-
-        getMemory().writeText(ccData.getDisplayText());
-
-        if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
-            updateDisplay();
-        }
-
-        return true;
-    }
-
-    private boolean handleMidRowCode(CCData ccData) {
-        StyleCode m = ccData.getMidRow();
-        if (m != null) {
-            getMemory().writeMidRowCode(m);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean handlePACCode(CCData ccData) {
-        PAC pac = ccData.getPAC();
-
-        if (pac != null) {
-            if (mMode == MODE_ROLL_UP) {
-                getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
-            }
-            getMemory().writePAC(pac);
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean handleTabOffsets(CCData ccData) {
-        int tabs = ccData.getTabOffset();
-
-        if (tabs > 0) {
-            getMemory().tab(tabs);
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean handleCtrlCode(CCData ccData) {
-        int ctrlCode = ccData.getCtrlCode();
-
-        if (mPrevCtrlCode != INVALID && mPrevCtrlCode == ctrlCode) {
-            // discard double ctrl codes (but if there's a 3rd one, we still take that)
-            mPrevCtrlCode = INVALID;
-            return true;
-        }
-
-        switch(ctrlCode) {
-            case RCL:
-                // select pop-on style
-                mMode = MODE_POP_ON;
-                break;
-            case BS:
-                getMemory().bs();
-                break;
-            case DER:
-                getMemory().der();
-                break;
-            case RU2:
-            case RU3:
-            case RU4:
-                mRollUpSize = (ctrlCode - 0x23);
-                // erase memory if currently in other style
-                if (mMode != MODE_ROLL_UP) {
-                    mDisplay.erase();
-                    mNonDisplay.erase();
-                }
-                // select roll-up style
-                mMode = MODE_ROLL_UP;
-                break;
-            case FON:
-                Log.i(TAG, "Flash On");
-                break;
-            case RDC:
-                // select paint-on style
-                mMode = MODE_PAINT_ON;
-                break;
-            case TR:
-                mMode = MODE_TEXT;
-                mTextMem.erase();
-                break;
-            case RTD:
-                mMode = MODE_TEXT;
-                break;
-            case EDM:
-                // erase display memory
-                mDisplay.erase();
-                updateDisplay();
-                break;
-            case CR:
-                if (mMode == MODE_ROLL_UP) {
-                    getMemory().rollUp(mRollUpSize);
-                } else {
-                    getMemory().cr();
-                }
-                if (mMode == MODE_ROLL_UP) {
-                    updateDisplay();
-                }
-                break;
-            case ENM:
-                // erase non-display memory
-                mNonDisplay.erase();
-                break;
-            case EOC:
-                // swap display/non-display memory
-                swapMemory();
-                // switch to pop-on style
-                mMode = MODE_POP_ON;
-                updateDisplay();
-                break;
-            case INVALID:
-            default:
-                mPrevCtrlCode = INVALID;
-                return false;
-        }
-
-        mPrevCtrlCode = ctrlCode;
-
-        // handled
-        return true;
-    }
-
-    private void updateDisplay() {
-        if (mListener != null) {
-            CaptionStyle captionStyle = mListener.getCaptionStyle();
-            mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
-        }
-    }
-
-    private void swapMemory() {
-        CCMemory temp = mDisplay;
-        mDisplay = mNonDisplay;
-        mNonDisplay = temp;
-    }
-
-    private static class StyleCode {
-        static final int COLOR_WHITE = 0;
-        static final int COLOR_INVALID = 7;
-
-        static final int STYLE_ITALICS   = 0x00000001;
-        static final int STYLE_UNDERLINE = 0x00000002;
-
-        static final String[] sColorMap = {
-            "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
-        };
-
-        final int mStyle;
-        final int mColor;
-
-        static StyleCode fromByte(byte data2) {
-            int style = 0;
-            int color = (data2 >> 1) & 0x7;
-
-            if ((data2 & 0x1) != 0) {
-                style |= STYLE_UNDERLINE;
-            }
-
-            if (color == COLOR_INVALID) {
-                // WHITE ITALICS
-                color = COLOR_WHITE;
-                style |= STYLE_ITALICS;
-            }
-
-            return new StyleCode(style, color);
-        }
-
-        StyleCode(int style, int color) {
-            mStyle = style;
-            mColor = color;
-        }
-
-        boolean isItalics() {
-            return (mStyle & STYLE_ITALICS) != 0;
-        }
-
-        boolean isUnderline() {
-            return (mStyle & STYLE_UNDERLINE) != 0;
-        }
-        @Override
-        public String toString() {
-            StringBuilder str = new StringBuilder();
-            str.append("{");
-            str.append(sColorMap[mColor]);
-            if ((mStyle & STYLE_ITALICS) != 0) {
-                str.append(", ITALICS");
-            }
-            if ((mStyle & STYLE_UNDERLINE) != 0) {
-                str.append(", UNDERLINE");
-            }
-            str.append("}");
-
-            return str.toString();
-        }
-    }
-
-    private static class PAC extends StyleCode {
-        final int mRow;
-        final int mCol;
-
-        static PAC fromBytes(byte data1, byte data2) {
-            int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
-            int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
-            int style = 0;
-            if ((data2 & 1) != 0) {
-                style |= STYLE_UNDERLINE;
-            }
-            if ((data2 & 0x10) != 0) {
-                // indent code
-                int indent = (data2 >> 1) & 0x7;
-                return new PAC(row, indent * 4, style, COLOR_WHITE);
-            } else {
-                // style code
-                int color = (data2 >> 1) & 0x7;
-
-                if (color == COLOR_INVALID) {
-                    // WHITE ITALICS
-                    color = COLOR_WHITE;
-                    style |= STYLE_ITALICS;
-                }
-                return new PAC(row, -1, style, color);
-            }
-        }
-
-        PAC(int row, int col, int style, int color) {
-            super(style, color);
-            mRow = row;
-            mCol = col;
-        }
-
-        boolean isIndentPAC() {
-            return (mCol >= 0);
-        }
-
-        int getRow() {
-            return mRow;
-        }
-
-        int getCol() {
-            return mCol;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("{%d, %d}, %s",
-                    mRow, mCol, super.toString());
-        }
-    }
-
-    /**
-     * Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
-     */
-    public static class MutableBackgroundColorSpan extends CharacterStyle
-            implements UpdateAppearance {
-        private int mColor;
-
-        MutableBackgroundColorSpan(int color) {
-            mColor = color;
-        }
-
-        public void setBackgroundColor(int color) {
-            mColor = color;
-        }
-
-        public int getBackgroundColor() {
-            return mColor;
-        }
-
-        @Override
-        public void updateDrawState(@NonNull TextPaint ds) {
-            ds.bgColor = mColor;
-        }
-    }
-
-    /* CCLineBuilder keeps track of displayable chars, as well as
-     * MidRow styles and PACs, for a single line of CC memory.
-     *
-     * It generates styled text via getStyledText() method.
-     */
-    private static class CCLineBuilder {
-        private final StringBuilder mDisplayChars;
-        private final StyleCode[] mMidRowStyles;
-        private final StyleCode[] mPACStyles;
-
-        CCLineBuilder(String str) {
-            mDisplayChars = new StringBuilder(str);
-            mMidRowStyles = new StyleCode[mDisplayChars.length()];
-            mPACStyles = new StyleCode[mDisplayChars.length()];
-        }
-
-        void setCharAt(int index, char ch) {
-            mDisplayChars.setCharAt(index, ch);
-            mMidRowStyles[index] = null;
-        }
-
-        void setMidRowAt(int index, StyleCode m) {
-            mDisplayChars.setCharAt(index, ' ');
-            mMidRowStyles[index] = m;
-        }
-
-        void setPACAt(int index, PAC pac) {
-            mPACStyles[index] = pac;
-        }
-
-        char charAt(int index) {
-            return mDisplayChars.charAt(index);
-        }
-
-        int length() {
-            return mDisplayChars.length();
-        }
-
-        void applyStyleSpan(
-                SpannableStringBuilder styledText,
-                StyleCode s, int start, int end) {
-            if (s.isItalics()) {
-                styledText.setSpan(
-                        new StyleSpan(android.graphics.Typeface.ITALIC),
-                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            if (s.isUnderline()) {
-                styledText.setSpan(
-                        new UnderlineSpan(),
-                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-        }
-
-        SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
-            SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
-            int start = -1, next = 0;
-            int styleStart = -1;
-            StyleCode curStyle = null;
-            while (next < mDisplayChars.length()) {
-                StyleCode newStyle = null;
-                if (mMidRowStyles[next] != null) {
-                    // apply mid-row style change
-                    newStyle = mMidRowStyles[next];
-                } else if (mPACStyles[next] != null && (styleStart < 0 || start < 0)) {
-                    // apply PAC style change, only if:
-                    // 1. no style set, or
-                    // 2. style set, but prev char is none-displayable
-                    newStyle = mPACStyles[next];
-                }
-                if (newStyle != null) {
-                    curStyle = newStyle;
-                    if (styleStart >= 0 && start >= 0) {
-                        applyStyleSpan(styledText, newStyle, styleStart, next);
-                    }
-                    styleStart = next;
-                }
-
-                if (mDisplayChars.charAt(next) != TS) {
-                    if (start < 0) {
-                        start = next;
-                    }
-                } else if (start >= 0) {
-                    int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
-                    int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
-                    styledText.setSpan(
-                            new MutableBackgroundColorSpan(captionStyle.backgroundColor),
-                            expandedStart, expandedEnd,
-                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    if (styleStart >= 0) {
-                        applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
-                    }
-                    start = -1;
-                }
-                next++;
-            }
-
-            return styledText;
-        }
-    }
-
-    /*
-     * CCMemory models a console-style display.
-     */
-    private static class CCMemory {
-        private final String mBlankLine;
-        private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
-        private int mRow;
-        private int mCol;
-
-        CCMemory() {
-            char[] blank = new char[MAX_COLS + 2];
-            Arrays.fill(blank, TS);
-            mBlankLine = new String(blank);
-        }
-
-        void erase() {
-            // erase all lines
-            for (int i = 0; i < mLines.length; i++) {
-                mLines[i] = null;
-            }
-            mRow = MAX_ROWS;
-            mCol = 1;
-        }
-
-        void der() {
-            if (mLines[mRow] != null) {
-                for (int i = 0; i < mCol; i++) {
-                    if (mLines[mRow].charAt(i) != TS) {
-                        for (int j = mCol; j < mLines[mRow].length(); j++) {
-                            mLines[j].setCharAt(j, TS);
-                        }
-                        return;
-                    }
-                }
-                mLines[mRow] = null;
-            }
-        }
-
-        void tab(int tabs) {
-            moveCursorByCol(tabs);
-        }
-
-        void bs() {
-            moveCursorByCol(-1);
-            if (mLines[mRow] != null) {
-                mLines[mRow].setCharAt(mCol, TS);
-                if (mCol == MAX_COLS - 1) {
-                    // Spec recommendation:
-                    // if cursor was at col 32, move cursor
-                    // back to col 31 and erase both col 31&32
-                    mLines[mRow].setCharAt(MAX_COLS, TS);
-                }
-            }
-        }
-
-        void cr() {
-            moveCursorTo(mRow + 1, 1);
-        }
-
-        void rollUp(int windowSize) {
-            int i;
-            for (i = 0; i <= mRow - windowSize; i++) {
-                mLines[i] = null;
-            }
-            int startRow = mRow - windowSize + 1;
-            if (startRow < 1) {
-                startRow = 1;
-            }
-            for (i = startRow; i < mRow; i++) {
-                mLines[i] = mLines[i + 1];
-            }
-            for (i = mRow; i < mLines.length; i++) {
-                // clear base row
-                mLines[i] = null;
-            }
-            // default to col 1, in case PAC is not sent
-            mCol = 1;
-        }
-
-        void writeText(String text) {
-            for (int i = 0; i < text.length(); i++) {
-                getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
-                moveCursorByCol(1);
-            }
-        }
-
-        void writeMidRowCode(StyleCode m) {
-            getLineBuffer(mRow).setMidRowAt(mCol, m);
-            moveCursorByCol(1);
-        }
-
-        void writePAC(PAC pac) {
-            if (pac.isIndentPAC()) {
-                moveCursorTo(pac.getRow(), pac.getCol());
-            } else {
-                moveCursorTo(pac.getRow(), 1);
-            }
-            getLineBuffer(mRow).setPACAt(mCol, pac);
-        }
-
-        SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
-            ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
-            for (int i = 1; i <= MAX_ROWS; i++) {
-                rows.add(mLines[i] != null ? mLines[i].getStyledText(captionStyle) : null);
-            }
-            return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
-        }
-
-        private static int clamp(int x, int min, int max) {
-            return x < min ? min : (x > max ? max : x);
-        }
-
-        private void moveCursorTo(int row, int col) {
-            mRow = clamp(row, 1, MAX_ROWS);
-            mCol = clamp(col, 1, MAX_COLS);
-        }
-
-        private void moveCursorByCol(int col) {
-            mCol = clamp(mCol + col, 1, MAX_COLS);
-        }
-
-        void moveBaselineTo(int baseRow, int windowSize) {
-            if (mRow == baseRow) {
-                return;
-            }
-            int actualWindowSize = windowSize;
-            if (baseRow < actualWindowSize) {
-                actualWindowSize = baseRow;
-            }
-            if (mRow < actualWindowSize) {
-                actualWindowSize = mRow;
-            }
-
-            int i;
-            if (baseRow < mRow) {
-                // copy from bottom to top row
-                for (i = actualWindowSize - 1; i >= 0; i--) {
-                    mLines[baseRow - i] = mLines[mRow - i];
-                }
-            } else {
-                // copy from top to bottom row
-                for (i = 0; i < actualWindowSize; i++) {
-                    mLines[baseRow - i] = mLines[mRow - i];
-                }
-            }
-            // clear rest of the rows
-            for (i = 0; i <= baseRow - windowSize; i++) {
-                mLines[i] = null;
-            }
-            for (i = baseRow + 1; i < mLines.length; i++) {
-                mLines[i] = null;
-            }
-        }
-
-        private CCLineBuilder getLineBuffer(int row) {
-            if (mLines[row] == null) {
-                mLines[row] = new CCLineBuilder(mBlankLine);
-            }
-            return mLines[row];
-        }
-    }
-
-    /*
-     * CCData parses the raw CC byte pair into displayable chars,
-     * misc control codes, Mid-Row or Preamble Address Codes.
-     */
-    private static class CCData {
-        private final byte mType;
-        private final byte mData1;
-        private final byte mData2;
-
-        private static final String[] sCtrlCodeMap = {
-            "RCL", "BS" , "AOF", "AON",
-            "DER", "RU2", "RU3", "RU4",
-            "FON", "RDC", "TR" , "RTD",
-            "EDM", "CR" , "ENM", "EOC",
-        };
-
-        private static final String[] sSpecialCharMap = {
-            "\u00AE",
-            "\u00B0",
-            "\u00BD",
-            "\u00BF",
-            "\u2122",
-            "\u00A2",
-            "\u00A3",
-            "\u266A", // Eighth note
-            "\u00E0",
-            "\u00A0", // Transparent space
-            "\u00E8",
-            "\u00E2",
-            "\u00EA",
-            "\u00EE",
-            "\u00F4",
-            "\u00FB",
-        };
-
-        private static final String[] sSpanishCharMap = {
-            // Spanish and misc chars
-            "\u00C1", // A
-            "\u00C9", // E
-            "\u00D3", // I
-            "\u00DA", // O
-            "\u00DC", // U
-            "\u00FC", // u
-            "\u2018", // opening single quote
-            "\u00A1", // inverted exclamation mark
-            "*",
-            "'",
-            "\u2014", // em dash
-            "\u00A9", // Copyright
-            "\u2120", // Servicemark
-            "\u2022", // round bullet
-            "\u201C", // opening double quote
-            "\u201D", // closing double quote
-            // French
-            "\u00C0",
-            "\u00C2",
-            "\u00C7",
-            "\u00C8",
-            "\u00CA",
-            "\u00CB",
-            "\u00EB",
-            "\u00CE",
-            "\u00CF",
-            "\u00EF",
-            "\u00D4",
-            "\u00D9",
-            "\u00F9",
-            "\u00DB",
-            "\u00AB",
-            "\u00BB"
-        };
-
-        private static final String[] sProtugueseCharMap = {
-            // Portuguese
-            "\u00C3",
-            "\u00E3",
-            "\u00CD",
-            "\u00CC",
-            "\u00EC",
-            "\u00D2",
-            "\u00F2",
-            "\u00D5",
-            "\u00F5",
-            "{",
-            "}",
-            "\\",
-            "^",
-            "_",
-            "|",
-            "~",
-            // German and misc chars
-            "\u00C4",
-            "\u00E4",
-            "\u00D6",
-            "\u00F6",
-            "\u00DF",
-            "\u00A5",
-            "\u00A4",
-            "\u2502", // vertical bar
-            "\u00C5",
-            "\u00E5",
-            "\u00D8",
-            "\u00F8",
-            "\u250C", // top-left corner
-            "\u2510", // top-right corner
-            "\u2514", // lower-left corner
-            "\u2518", // lower-right corner
-        };
-
-        static CCData[] fromByteArray(byte[] data) {
-            CCData[] ccData = new CCData[data.length / 3];
-
-            for (int i = 0; i < ccData.length; i++) {
-                ccData[i] = new CCData(
-                        data[i * 3],
-                        data[i * 3 + 1],
-                        data[i * 3 + 2]);
-            }
-
-            return ccData;
-        }
-
-        CCData(byte type, byte data1, byte data2) {
-            mType = type;
-            mData1 = data1;
-            mData2 = data2;
-        }
-
-        int getCtrlCode() {
-            if ((mData1 == 0x14 || mData1 == 0x1c)
-                    && mData2 >= 0x20 && mData2 <= 0x2f) {
-                return mData2;
-            }
-            return INVALID;
-        }
-
-        StyleCode getMidRow() {
-            // only support standard Mid-row codes, ignore
-            // optional background/foreground mid-row codes
-            if ((mData1 == 0x11 || mData1 == 0x19)
-                    && mData2 >= 0x20 && mData2 <= 0x2f) {
-                return StyleCode.fromByte(mData2);
-            }
-            return null;
-        }
-
-        PAC getPAC() {
-            if ((mData1 & 0x70) == 0x10
-                    && (mData2 & 0x40) == 0x40
-                    && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
-                return PAC.fromBytes(mData1, mData2);
-            }
-            return null;
-        }
-
-        int getTabOffset() {
-            if ((mData1 == 0x17 || mData1 == 0x1f)
-                    && mData2 >= 0x21 && mData2 <= 0x23) {
-                return mData2 & 0x3;
-            }
-            return 0;
-        }
-
-        boolean isDisplayableChar() {
-            return isBasicChar() || isSpecialChar() || isExtendedChar();
-        }
-
-        String getDisplayText() {
-            String str = getBasicChars();
-
-            if (str == null) {
-                str =  getSpecialChar();
-
-                if (str == null) {
-                    str = getExtendedChar();
-                }
-            }
-
-            return str;
-        }
-
-        private String ctrlCodeToString(int ctrlCode) {
-            return sCtrlCodeMap[ctrlCode - 0x20];
-        }
-
-        @SuppressWarnings("ComparisonOutOfRange")
-        private boolean isBasicChar() {
-            return mData1 >= 0x20 && mData1 <= 0x7f;
-        }
-
-        private boolean isSpecialChar() {
-            return ((mData1 == 0x11 || mData1 == 0x19)
-                    && mData2 >= 0x30 && mData2 <= 0x3f);
-        }
-
-        boolean isExtendedChar() {
-            return ((mData1 == 0x12 || mData1 == 0x1A
-                    || mData1 == 0x13 || mData1 == 0x1B)
-                    && mData2 >= 0x20 && mData2 <= 0x3f);
-        }
-
-        private char getBasicChar(byte data) {
-            char c;
-            // replace the non-ASCII ones
-            switch (data) {
-                case 0x2A: c = '\u00E1'; break;
-                case 0x5C: c = '\u00E9'; break;
-                case 0x5E: c = '\u00ED'; break;
-                case 0x5F: c = '\u00F3'; break;
-                case 0x60: c = '\u00FA'; break;
-                case 0x7B: c = '\u00E7'; break;
-                case 0x7C: c = '\u00F7'; break;
-                case 0x7D: c = '\u00D1'; break;
-                case 0x7E: c = '\u00F1'; break;
-                case 0x7F: c = '\u2588'; break; // Full block
-                default: c = (char) data; break;
-            }
-            return c;
-        }
-
-        @SuppressWarnings("ComparisonOutOfRange")
-        private String getBasicChars() {
-            if (mData1 >= 0x20 && mData1 <= 0x7f) {
-                StringBuilder builder = new StringBuilder(2);
-                builder.append(getBasicChar(mData1));
-                if (mData2 >= 0x20 && mData2 <= 0x7f) {
-                    builder.append(getBasicChar(mData2));
-                }
-                return builder.toString();
-            }
-
-            return null;
-        }
-
-        private String getSpecialChar() {
-            if ((mData1 == 0x11 || mData1 == 0x19)
-                    && mData2 >= 0x30 && mData2 <= 0x3f) {
-                return sSpecialCharMap[mData2 - 0x30];
-            }
-
-            return null;
-        }
-
-        private String getExtendedChar() {
-            if ((mData1 == 0x12 || mData1 == 0x1A) && mData2 >= 0x20 && mData2 <= 0x3f) {
-                // 1 Spanish/French char
-                return sSpanishCharMap[mData2 - 0x20];
-            } else if ((mData1 == 0x13 || mData1 == 0x1B) && mData2 >= 0x20 && mData2 <= 0x3f) {
-                // 1 Portuguese/German/Danish char
-                return sProtugueseCharMap[mData2 - 0x20];
-            }
-
-            return null;
-        }
-
-        @Override
-        public String toString() {
-            if (mData1 < 0x10 && mData2 < 0x10) {
-                // Null Pad, ignore
-                return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
-            }
-
-            int ctrlCode = getCtrlCode();
-            if (ctrlCode != INVALID) {
-                return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
-            }
-
-            int tabOffset = getTabOffset();
-            if (tabOffset > 0) {
-                return String.format("[%d]Tab%d", mType, tabOffset);
-            }
-
-            PAC pac = getPAC();
-            if (pac != null) {
-                return String.format("[%d]PAC: %s", mType, pac.toString());
-            }
-
-            StyleCode m = getMidRow();
-            if (m != null) {
-                return String.format("[%d]Mid-row: %s", mType, m.toString());
-            }
-
-            if (isDisplayableChar()) {
-                return String.format("[%d]Displayable: %s (%02x %02x)",
-                        mType, getDisplayText(), mData1, mData2);
-            }
-
-            return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CaptionRenderer.java b/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CaptionRenderer.java
deleted file mode 100644
index 9ea0bff..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea608CaptionRenderer.java
+++ /dev/null
@@ -1,395 +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.media2.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.media.MediaFormat;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.widget.AppCompatTextView;
-
-import java.util.ArrayList;
-
-// Note: This is forked from android.media.Cea608CaptionRenderer since P
-class Cea608CaptionRenderer extends SubtitleController.Renderer {
-    private static final String TAG = "Cea608CaptionRenderer";
-    private final Context mContext;
-    private Cea608CCWidget mCCWidget;
-
-    Cea608CaptionRenderer(@NonNull Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public boolean supports(@NonNull MediaFormat format) {
-        if (format.containsKey(MediaFormat.KEY_MIME)) {
-            String mimeType = format.getString(MediaFormat.KEY_MIME);
-            return MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType);
-        }
-        return false;
-    }
-
-    @Override
-    public @NonNull SubtitleTrack createTrack(@NonNull MediaFormat format) {
-        String mimeType = format.getString(MediaFormat.KEY_MIME);
-        if (MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType)) {
-            if (mCCWidget == null) {
-                mCCWidget = new Cea608CCWidget(mContext);
-            }
-            return new Cea608CaptionTrack(mCCWidget, format);
-        }
-        throw new RuntimeException("No matching format: " + format.toString());
-    }
-
-    static class Cea608CaptionTrack extends SubtitleTrack {
-        private final Cea608CCParser mCCParser;
-        private final Cea608CCWidget mRenderingWidget;
-
-        Cea608CaptionTrack(Cea608CCWidget renderingWidget, MediaFormat format) {
-            super(format);
-
-            mRenderingWidget = renderingWidget;
-            mCCParser = new Cea608CCParser(mRenderingWidget);
-        }
-
-        @Override
-        public void onData(byte[] data, boolean eos, long runID) {
-            mCCParser.parse(data);
-        }
-
-        @Override
-        public RenderingWidget getRenderingWidget() {
-            return mRenderingWidget;
-        }
-
-        @Override
-        public void updateView(ArrayList<Cue> activeCues) {
-            // Overriding with NO-OP, CC rendering by-passes this
-        }
-    }
-
-    /**
-     * Widget capable of rendering CEA-608 closed captions.
-     */
-    class Cea608CCWidget extends ClosedCaptionWidget implements Cea608CCParser.DisplayListener {
-        private static final String PLACEHOLDER_TEXT = "1234567890123456789012345678901234";
-        final Rect mTextBounds = new Rect();
-
-        Cea608CCWidget(Context context) {
-            this(context, null);
-        }
-
-        Cea608CCWidget(Context context, AttributeSet attrs) {
-            this(context, attrs, 0);
-        }
-
-        Cea608CCWidget(Context context, AttributeSet attrs, int defStyle) {
-            super(context, attrs, defStyle);
-        }
-
-        @Override
-        public ClosedCaptionLayout createCaptionLayout(Context context) {
-            return new CCLayout(context);
-        }
-
-        @Override
-        public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
-            ((CCLayout) mClosedCaptionLayout).update(styledTexts);
-
-            if (mListener != null) {
-                mListener.onChanged(this);
-            }
-        }
-
-        @Override
-        public CaptionStyle getCaptionStyle() {
-            return mCaptionStyle;
-        }
-
-        private class CCLineBox extends AppCompatTextView {
-            private static final float FONT_PADDING_RATIO = 0.75f;
-            private static final float EDGE_OUTLINE_RATIO = 0.1f;
-            private static final float EDGE_SHADOW_RATIO = 0.05f;
-            private float mOutlineWidth;
-            private float mShadowRadius;
-            private float mShadowOffset;
-
-            private int mTextColor = Color.WHITE;
-            private int mBgColor = Color.BLACK;
-            private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE;
-            private int mEdgeColor = Color.TRANSPARENT;
-
-            CCLineBox(Context context) {
-                super(context);
-                setGravity(Gravity.CENTER);
-                setBackgroundColor(Color.TRANSPARENT);
-                setTextColor(Color.WHITE);
-                setTypeface(Typeface.MONOSPACE);
-                setVisibility(View.INVISIBLE);
-
-                final Resources res = getContext().getResources();
-
-                // get the default (will be updated later during measure)
-                mOutlineWidth = res.getDimensionPixelSize(
-                        R.dimen.media2_widget_subtitle_outline_width);
-                mShadowRadius = res.getDimensionPixelSize(
-                        R.dimen.media2_widget_subtitle_shadow_radius);
-                mShadowOffset = res.getDimensionPixelSize(
-                        R.dimen.media2_widget_subtitle_shadow_offset);
-            }
-
-            void setCaptionStyle(CaptionStyle captionStyle) {
-                mTextColor = captionStyle.foregroundColor;
-                mBgColor = captionStyle.backgroundColor;
-                mEdgeType = captionStyle.edgeType;
-                mEdgeColor = captionStyle.edgeColor;
-
-                setTextColor(mTextColor);
-                if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
-                    setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
-                } else {
-                    setShadowLayer(0, 0, 0, 0);
-                }
-                invalidate();
-            }
-
-            @Override
-            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                float fontSize = MeasureSpec.getSize(heightMeasureSpec) * FONT_PADDING_RATIO;
-                setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
-
-                mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
-                mShadowRadius = EDGE_SHADOW_RATIO * fontSize + 1.0f;
-                mShadowOffset = mShadowRadius;
-
-                // set font scale in the X direction to match the required width
-                setScaleX(1.0f);
-                getPaint().getTextBounds(PLACEHOLDER_TEXT, 0, PLACEHOLDER_TEXT.length(),
-                        mTextBounds);
-                float actualTextWidth = mTextBounds.width();
-                float requiredTextWidth = MeasureSpec.getSize(widthMeasureSpec);
-                if (actualTextWidth != .0f) {
-                    setScaleX(requiredTextWidth / actualTextWidth);
-                } else {
-                    Log.w(TAG, "onMeasure(): Paint#getTextBounds() returned zero width. Ignored.");
-                }
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
-
-            @Override
-            protected void onDraw(@NonNull Canvas c) {
-                if (mEdgeType == CaptionStyle.EDGE_TYPE_UNSPECIFIED
-                        || mEdgeType == CaptionStyle.EDGE_TYPE_NONE
-                        || mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
-                    // these edge styles don't require a second pass
-                    super.onDraw(c);
-                    return;
-                }
-
-                if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
-                    drawEdgeOutline(c);
-                } else {
-                    // Raised or depressed
-                    drawEdgeRaisedOrDepressed(c);
-                }
-            }
-
-            @SuppressWarnings("WrongCall")
-            private void drawEdgeOutline(Canvas c) {
-                TextPaint textPaint = getPaint();
-
-                Paint.Style previousStyle = textPaint.getStyle();
-                Paint.Join previousJoin = textPaint.getStrokeJoin();
-                float previousWidth = textPaint.getStrokeWidth();
-
-                setTextColor(mEdgeColor);
-                textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-                textPaint.setStrokeJoin(Paint.Join.ROUND);
-                textPaint.setStrokeWidth(mOutlineWidth);
-
-                // Draw outline and background only.
-                super.onDraw(c);
-
-                // Restore original settings.
-                setTextColor(mTextColor);
-                textPaint.setStyle(previousStyle);
-                textPaint.setStrokeJoin(previousJoin);
-                textPaint.setStrokeWidth(previousWidth);
-
-                // Remove the background.
-                setBackgroundSpans(Color.TRANSPARENT);
-                // Draw foreground only.
-                super.onDraw(c);
-                // Restore the background.
-                setBackgroundSpans(mBgColor);
-            }
-
-            @SuppressWarnings("WrongCall")
-            private void drawEdgeRaisedOrDepressed(Canvas c) {
-                TextPaint textPaint = getPaint();
-
-                Paint.Style previousStyle = textPaint.getStyle();
-                textPaint.setStyle(Paint.Style.FILL);
-
-                final boolean raised = mEdgeType == CaptionStyle.EDGE_TYPE_RAISED;
-                final int colorUp = raised ? Color.WHITE : mEdgeColor;
-                final int colorDown = raised ? mEdgeColor : Color.WHITE;
-                final float offset = mShadowRadius / 2f;
-
-                // Draw background and text with shadow up
-                setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
-                super.onDraw(c);
-
-                // Remove the background.
-                setBackgroundSpans(Color.TRANSPARENT);
-
-                // Draw text with shadow down
-                setShadowLayer(mShadowRadius, +offset, +offset, colorDown);
-                super.onDraw(c);
-
-                // Restore settings
-                textPaint.setStyle(previousStyle);
-
-                // Restore the background.
-                setBackgroundSpans(mBgColor);
-            }
-
-            private void setBackgroundSpans(int color) {
-                CharSequence text = getText();
-                if (text instanceof Spannable) {
-                    Spannable spannable = (Spannable) text;
-                    Cea608CCParser.MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
-                            0, spannable.length(), Cea608CCParser.MutableBackgroundColorSpan.class);
-                    for (int i = 0; i < bgSpans.length; i++) {
-                        bgSpans[i].setBackgroundColor(color);
-                    }
-                }
-            }
-        }
-
-        private class CCLayout extends LinearLayout implements ClosedCaptionLayout {
-            private static final int MAX_ROWS = Cea608CCParser.MAX_ROWS;
-            private static final float SAFE_AREA_RATIO = 0.9f;
-
-            private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
-
-            CCLayout(Context context) {
-                super(context);
-                setGravity(Gravity.START);
-                setOrientation(LinearLayout.VERTICAL);
-                for (int i = 0; i < MAX_ROWS; i++) {
-                    mLineBoxes[i] = new CCLineBox(getContext());
-                    addView(mLineBoxes[i], LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-                }
-            }
-
-            @Override
-            public void setCaptionStyle(CaptionStyle captionStyle) {
-                for (int i = 0; i < MAX_ROWS; i++) {
-                    mLineBoxes[i].setCaptionStyle(captionStyle);
-                }
-            }
-
-            @Override
-            public void setFontScale(float fontScale) {
-                // Ignores the font scale changes of the system wide CC preference.
-            }
-
-            void update(SpannableStringBuilder[] textBuffer) {
-                for (int i = 0; i < MAX_ROWS; i++) {
-                    if (textBuffer[i] != null) {
-                        mLineBoxes[i].setText(textBuffer[i], TextView.BufferType.SPANNABLE);
-                        mLineBoxes[i].setVisibility(View.VISIBLE);
-                    } else {
-                        mLineBoxes[i].setVisibility(View.INVISIBLE);
-                    }
-                }
-            }
-
-            @Override
-            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-                int safeWidth = getMeasuredWidth();
-                int safeHeight = getMeasuredHeight();
-
-                // CEA-608 assumes 4:3 video
-                if (safeWidth * 3 >= safeHeight * 4) {
-                    safeWidth = safeHeight * 4 / 3;
-                } else {
-                    safeHeight = safeWidth * 3 / 4;
-                }
-                safeWidth = (int) (safeWidth * SAFE_AREA_RATIO);
-                safeHeight = (int) (safeHeight * SAFE_AREA_RATIO);
-
-                int lineHeight = safeHeight / MAX_ROWS;
-                int lineHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        lineHeight, MeasureSpec.EXACTLY);
-                int lineWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        safeWidth, MeasureSpec.EXACTLY);
-
-                for (int i = 0; i < MAX_ROWS; i++) {
-                    mLineBoxes[i].measure(lineWidthMeasureSpec, lineHeightMeasureSpec);
-                }
-            }
-
-            @Override
-            protected void onLayout(boolean changed, int l, int t, int r, int b) {
-                // safe caption area
-                int viewPortWidth = r - l;
-                int viewPortHeight = b - t;
-                int safeWidth, safeHeight;
-                // CEA-608 assumes 4:3 video
-                if (viewPortWidth * 3 >= viewPortHeight * 4) {
-                    safeWidth = viewPortHeight * 4 / 3;
-                    safeHeight = viewPortHeight;
-                } else {
-                    safeWidth = viewPortWidth;
-                    safeHeight = viewPortWidth * 3 / 4;
-                }
-                safeWidth = (int) (safeWidth * SAFE_AREA_RATIO);
-                safeHeight = (int) (safeHeight * SAFE_AREA_RATIO);
-                int left = (viewPortWidth - safeWidth) / 2;
-                int top = (viewPortHeight - safeHeight) / 2;
-
-                for (int i = 0; i < MAX_ROWS; i++) {
-                    mLineBoxes[i].layout(
-                            left,
-                            top + safeHeight * i / MAX_ROWS,
-                            left + safeWidth,
-                            top + safeHeight * (i + 1) / MAX_ROWS);
-                }
-            }
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CCParser.java b/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CCParser.java
deleted file mode 100644
index b8b2c64..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CCParser.java
+++ /dev/null
@@ -1,897 +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.media2.widget;
-
-import android.graphics.Color;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-
-// Note: This is forked from android.media.Cea708CCParser since P
-/**
- * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV.
- *
- * <p>ATSC DTV closed caption data are carried on picture user data of video streams.
- * This class starts to parse from picture user data payload, so extraction process of user_data
- * from video streams is up to outside of this code.
- *
- * <p>There are 4 steps to decode user_data to provide closed caption services. Step 1 and 2 are
- * done in NuPlayer and libstagefright.
- *
- * <h3>Step 1. user_data -&gt; CcPacket</h3>
- *
- * <p>First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a
- * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data
- * packets must be reassembled in the frame display order, CcPackets are reordered.
- *
- * <h3>Step 2. CcPacket -&gt; DTVCC packet</h3>
- *
- * <p>Each cc_data packet has a one byte for declaring a type of itself and data validity, and the
- * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet.
- * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet
- * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has
- * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled.
- *
- * <h3>Step 3. DTVCC packet -&gt; Service Blocks</h3>
- *
- * <p>A DTVCC packet consists of multiple service blocks. Each service block represents a caption
- * track and has a service number, which ranges from 1 to 63, that denotes caption track identity.
- * In here, we listen at most one chosen caption track by service number. Otherwise, just skip the
- * other service blocks.
- *
- * <h3>Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX},
- * and {@link #parseExt1} methods)</h3>
- *
- * <p>Service block data is actual caption stream. it looks similar to telnet. It uses most parts of
- * ASCII table and consists of specially defined commands and some ASCII control codes which work
- * in a behavior slightly different from their original purpose. ASCII control codes and caption
- * commands are explicit instructions that control the state of a closed caption service and the
- * other ASCII and text codes are implicit instructions that send their characters to buffer.
- *
- * <p>There are 4 main code groups and 4 extended code groups. Both the range of code groups are the
- * same as the range of a byte.
- *
- * <p>4 main code groups: C0, C1, G0, G1
- * <br>4 extended code groups: C2, C3, G2, G3
- *
- * <p>Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group
- * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while
- * {@link #parseExt1} method maps on the extended code groups.
- *
- * <p>The main code groups:
- * <ul>
- * <li>C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA
- *      standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc,
- *      even for the alphanumeric characters instead of ASCII characters.</li>
- * <li>C1 - contains the caption commands. There are 3 categories of a caption command.</li>
- * <ul>
- * <li>Window commands: The window commands control a caption window which is addressable area being
- *                  with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)</li>
- * <li>Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)</li>
- * <li>Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)</li>
- * </ul>
- * <li>G0 - same as printable ASCII character set except music note character.</li>
- * <li>G1 - same as ISO 8859-1 Latin 1 character set.</li>
- * </ul>
- * <p>Most of the extended code groups are being skipped.
- *
- */
-class Cea708CCParser {
-    private static final String TAG = "Cea708CCParser";
-    private static final boolean DEBUG = false;
-
-    private static final String MUSIC_NOTE_CHAR = new String(
-            "\u266B".getBytes(Charset.forName("UTF-8")), Charset.forName("UTF-8"));
-
-    private final StringBuilder mBuilder = new StringBuilder();
-
-    // Assign a placeholder listener in order to avoid null checks.
-    private DisplayListener mListener = new DisplayListener() {
-        @Override
-        public void emitEvent(CaptionEvent event) {
-            // do nothing
-        }
-    };
-
-    /**
-     * {@link Cea708Parser} emits caption event of three different types.
-     * {@link DisplayListener#emitEvent} is invoked with the parameter
-     * {@link CaptionEvent} to pass all the results to an observer of the decoding process .
-     *
-     * <p>{@link CaptionEvent#type} determines the type of the result and
-     * {@link CaptionEvent#obj} contains the output value of a caption event.
-     * The observer must do the casting to the corresponding type.
-     *
-     * <ul><li>{@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer.
-     * {@code obj} must be of {@link String}.</li>
-     *
-     * <li>{@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer.
-     * {@code obj} must be of {@link Character}.</li>
-     *
-     * <li>{@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer.
-     * {@code obj} must be {@code NULL}.</li></ul>
-     */
-    public static final int CAPTION_EMIT_TYPE_BUFFER = 1;
-    public static final int CAPTION_EMIT_TYPE_CONTROL = 2;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15;
-    public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16;
-
-    Cea708CCParser(DisplayListener listener) {
-        if (listener != null) {
-            mListener = listener;
-        }
-    }
-
-    interface DisplayListener {
-        void emitEvent(CaptionEvent event);
-    }
-
-    private void emitCaptionEvent(CaptionEvent captionEvent) {
-        // Emit the existing string buffer before a new event is arrived.
-        emitCaptionBuffer();
-        mListener.emitEvent(captionEvent);
-    }
-
-    private void emitCaptionBuffer() {
-        if (mBuilder.length() > 0) {
-            mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuilder.toString()));
-            mBuilder.setLength(0);
-        }
-    }
-
-    // Step 3. DTVCC packet -> Service Blocks (parseDtvCcPacket method)
-    public void parse(byte[] data) {
-        // From this point, starts to read DTVCC coding layer.
-        // First, identify code groups, which is defined in CEA-708B Section 7.1.
-        int pos = 0;
-        while (pos < data.length) {
-            pos = parseServiceBlockData(data, pos);
-        }
-
-        // Emit the buffer after reading codes.
-        emitCaptionBuffer();
-    }
-
-    // Step 4. Main code groups
-    private int parseServiceBlockData(byte[] data, int pos) {
-        // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
-        int command = data[pos] & 0xff;
-        ++pos;
-        if (command == Const.CODE_C0_EXT1) {
-            if (DEBUG) {
-                Log.d(TAG, String.format("parseServiceBlockData EXT1 %x", command));
-            }
-            pos = parseExt1(data, pos);
-        } else if (command >= Const.CODE_C0_RANGE_START
-                && command <= Const.CODE_C0_RANGE_END) {
-            if (DEBUG) {
-                Log.d(TAG, String.format("parseServiceBlockData C0 %x", command));
-            }
-            pos = parseC0(command, data, pos);
-        } else if (command >= Const.CODE_C1_RANGE_START
-                && command <= Const.CODE_C1_RANGE_END) {
-            if (DEBUG) {
-                Log.d(TAG, String.format("parseServiceBlockData C1 %x", command));
-            }
-            pos = parseC1(command, data, pos);
-        } else if (command >= Const.CODE_G0_RANGE_START
-                && command <= Const.CODE_G0_RANGE_END) {
-            if (DEBUG) {
-                Log.d(TAG, String.format("parseServiceBlockData G0 %x", command));
-            }
-            parseG0(command);
-        } else if (command >= Const.CODE_G1_RANGE_START
-                && command <= Const.CODE_G1_RANGE_END) {
-            if (DEBUG) {
-                Log.d(TAG, String.format("parseServiceBlockData G1 %x", command));
-            }
-            parseG1(command);
-        }
-        return pos;
-    }
-
-    private int parseC0(int commandCode, byte[] data, int pos) {
-        // For the details of C0 code group, see CEA-708B Section 7.4.1.
-        // CL Group: C0 Subset of ASCII Control codes
-        if (commandCode >= Const.CODE_C0_SKIP2_RANGE_START
-                && commandCode <= Const.CODE_C0_SKIP2_RANGE_END) {
-            if (commandCode == Const.CODE_C0_P16) {
-                // P16 escapes next two bytes for the large character maps.(no standard rule)
-                // For Korea broadcasting, express whole letters by using this.
-                try {
-                    if (data[pos] == 0) {
-                        mBuilder.append((char) data[pos + 1]);
-                    } else {
-                        String value = new String(
-                                Arrays.copyOfRange(data, pos, pos + 2), "EUC-KR");
-                        mBuilder.append(value);
-                    }
-                } catch (UnsupportedEncodingException e) {
-                    Log.e(TAG, "P16 Code - Could not find supported encoding", e);
-                }
-            }
-            pos += 2;
-        } else if (commandCode >= Const.CODE_C0_SKIP1_RANGE_START
-                && commandCode <= Const.CODE_C0_SKIP1_RANGE_END) {
-            ++pos;
-        } else {
-            // NUL, BS, FF, CR interpreted as they are in ASCII control codes.
-            // HCR moves the pen location to th beginning of the current line and deletes contents.
-            // FF clears the screen and moves the pen location to (0,0).
-            // ETX is the NULL command which is used to flush text to the current window when no
-            // other command is pending.
-            switch (commandCode) {
-                case Const.CODE_C0_ETX:
-                case Const.CODE_C0_BS:
-                case Const.CODE_C0_FF:
-                case Const.CODE_C0_HCR:
-                    emitCaptionEvent(
-                            new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) commandCode));
-                    break;
-                case Const.CODE_C0_CR:
-                    mBuilder.append('\n');
-                    break;
-                case Const.CODE_C0_NUL:
-                default:
-                    break;
-            }
-        }
-        return pos;
-    }
-
-    private int parseC1(int commandCode, byte[] data, int pos) {
-        // For the details of C1 code group, see CEA-708B Section 8.10.
-        // CR Group: C1 Caption Control Codes
-        switch (commandCode) {
-            case Const.CODE_C1_CW0:
-            case Const.CODE_C1_CW1:
-            case Const.CODE_C1_CW2:
-            case Const.CODE_C1_CW3:
-            case Const.CODE_C1_CW4:
-            case Const.CODE_C1_CW5:
-            case Const.CODE_C1_CW6:
-            case Const.CODE_C1_CW7: {
-                // SetCurrentWindow0-7
-                int windowId = commandCode - Const.CODE_C1_CW0;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_CLW: {
-                // ClearWindows
-                int windowBitmap = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_DSW: {
-                // DisplayWindows
-                int windowBitmap = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_HDW: {
-                // HideWindows
-                int windowBitmap = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_TGW: {
-                // ToggleWindows
-                int windowBitmap = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_DLW: {
-                // DeleteWindows
-                int windowBitmap = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_DLY: {
-                // Delay
-                int tenthsOfSeconds = data[pos] & 0xff;
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds",
-                            tenthsOfSeconds));
-                }
-                break;
-            }
-            case Const.CODE_C1_DLC: {
-                // DelayCancel
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null));
-                if (DEBUG) {
-                    Log.d(TAG, "CaptionCommand DLC");
-                }
-                break;
-            }
-
-            case Const.CODE_C1_RST: {
-                // Reset
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null));
-                if (DEBUG) {
-                    Log.d(TAG, "CaptionCommand RST");
-                }
-                break;
-            }
-
-            case Const.CODE_C1_SPA: {
-                // SetPenAttributes
-                int textTag = (data[pos] & 0xf0) >> 4;
-                int penSize = data[pos] & 0x03;
-                int penOffset = (data[pos] & 0x0c) >> 2;
-                boolean italic = (data[pos + 1] & 0x80) != 0;
-                boolean underline = (data[pos + 1] & 0x40) != 0;
-                int edgeType = (data[pos + 1] & 0x38) >> 3;
-                int fontTag = data[pos + 1] & 0x7;
-                pos += 2;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA,
-                        new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType,
-                                underline, italic)));
-                if (DEBUG) {
-                    Log.d(TAG, String.format(
-                            "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, "
-                                    + "fontTag: %d, edgeType: %d, underline: %s, italic: %s",
-                            penSize, penOffset, textTag, fontTag, edgeType, underline, italic));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_SPC: {
-                // SetPenColor
-                int opacity = (data[pos] & 0xc0) >> 6;
-                int red = (data[pos] & 0x30) >> 4;
-                int green = (data[pos] & 0x0c) >> 2;
-                int blue = data[pos] & 0x03;
-                CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue);
-                ++pos;
-                opacity = (data[pos] & 0xc0) >> 6;
-                red = (data[pos] & 0x30) >> 4;
-                green = (data[pos] & 0x0c) >> 2;
-                blue = data[pos] & 0x03;
-                CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue);
-                ++pos;
-                red = (data[pos] & 0x30) >> 4;
-                green = (data[pos] & 0x0c) >> 2;
-                blue = data[pos] & 0x03;
-                CaptionColor edgeColor = new CaptionColor(
-                        CaptionColor.OPACITY_SOLID, red, green, blue);
-                ++pos;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC,
-                        new CaptionPenColor(foregroundColor, backgroundColor, edgeColor)));
-                if (DEBUG) {
-                    Log.d(TAG, String.format(
-                            "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s",
-                            foregroundColor, backgroundColor, edgeColor));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_SPL: {
-                // SetPenLocation
-                // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats
-                int row = data[pos] & 0x0f;
-                int column = data[pos + 1] & 0x3f;
-                pos += 2;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL,
-                        new CaptionPenLocation(row, column)));
-                if (DEBUG) {
-                    Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d",
-                            row, column));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_SWA: {
-                // SetWindowAttributes
-                int opacity = (data[pos] & 0xc0) >> 6;
-                int red = (data[pos] & 0x30) >> 4;
-                int green = (data[pos] & 0x0c) >> 2;
-                int blue = data[pos] & 0x03;
-                CaptionColor fillColor = new CaptionColor(opacity, red, green, blue);
-                int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5;
-                red = (data[pos + 1] & 0x30) >> 4;
-                green = (data[pos + 1] & 0x0c) >> 2;
-                blue = data[pos + 1] & 0x03;
-                CaptionColor borderColor = new CaptionColor(
-                        CaptionColor.OPACITY_SOLID, red, green, blue);
-                boolean wordWrap = (data[pos + 2] & 0x40) != 0;
-                int printDirection = (data[pos + 2] & 0x30) >> 4;
-                int scrollDirection = (data[pos + 2] & 0x0c) >> 2;
-                int justify = (data[pos + 2] & 0x03);
-                int effectSpeed = (data[pos + 3] & 0xf0) >> 4;
-                int effectDirection = (data[pos + 3] & 0x0c) >> 2;
-                int displayEffect = data[pos + 3] & 0x3;
-                pos += 4;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA,
-                        new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap,
-                                printDirection, scrollDirection, justify,
-                                effectDirection, effectSpeed, displayEffect)));
-                if (DEBUG) {
-                    Log.d(TAG, String.format(
-                            "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d"
-                                    + "wordWrap: %s, printDirection: %d, scrollDirection: %d, "
-                                    + "justify: %s, effectDirection: %d, effectSpeed: %d, "
-                                    + "displayEffect: %d",
-                            fillColor, borderColor, borderType, wordWrap, printDirection,
-                            scrollDirection, justify, effectDirection, effectSpeed, displayEffect));
-                }
-                break;
-            }
-
-            case Const.CODE_C1_DF0:
-            case Const.CODE_C1_DF1:
-            case Const.CODE_C1_DF2:
-            case Const.CODE_C1_DF3:
-            case Const.CODE_C1_DF4:
-            case Const.CODE_C1_DF5:
-            case Const.CODE_C1_DF6:
-            case Const.CODE_C1_DF7: {
-                // DefineWindow0-7
-                int windowId = commandCode - Const.CODE_C1_DF0;
-                boolean visible = (data[pos] & 0x20) != 0;
-                boolean rowLock = (data[pos] & 0x10) != 0;
-                boolean columnLock = (data[pos] & 0x08) != 0;
-                int priority = data[pos] & 0x07;
-                boolean relativePositioning = (data[pos + 1] & 0x80) != 0;
-                int anchorVertical = data[pos + 1] & 0x7f;
-                int anchorHorizontal = data[pos + 2] & 0xff;
-                int anchorId = (data[pos + 3] & 0xf0) >> 4;
-                int rowCount = data[pos + 3] & 0x0f;
-                int columnCount = data[pos + 4] & 0x3f;
-                int windowStyle = (data[pos + 5] & 0x38) >> 3;
-                int penStyle = data[pos + 5] & 0x07;
-                pos += 6;
-                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX,
-                        new CaptionWindow(windowId, visible, rowLock, columnLock, priority,
-                                relativePositioning, anchorVertical, anchorHorizontal, anchorId,
-                                rowCount, columnCount, penStyle, windowStyle)));
-                if (DEBUG) {
-                    Log.d(TAG, String.format(
-                            "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, "
-                                    + "rowLock: %s, visible: %s, anchorVertical: %d, "
-                                    + "relativePositioning: %s, anchorHorizontal: %d, "
-                                    + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, "
-                                    + "windowStyle: %d",
-                            windowId, priority, columnLock, rowLock, visible, anchorVertical,
-                            relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount,
-                            penStyle, windowStyle));
-                }
-                break;
-            }
-
-            default:
-                break;
-        }
-        return pos;
-    }
-
-    private void parseG0(int characterCode) {
-        // For the details of G0 code group, see CEA-708B Section 7.4.3.
-        // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII)
-        if (characterCode == Const.CODE_G0_MUSICNOTE) {
-            // Music note.
-            mBuilder.append(MUSIC_NOTE_CHAR);
-        } else {
-            // Put ASCII code into buffer.
-            mBuilder.append((char) characterCode);
-        }
-    }
-
-    private void parseG1(int characterCode) {
-        // For the details of G1 code group, see CEA-708B Section 7.4.4.
-        // GR Group: G1 ISO 8859-1 Latin 1 Characters
-        // Put ASCII Extended character set into buffer.
-        mBuilder.append((char) characterCode);
-    }
-
-    // Step 4. Extended code groups
-    private int parseExt1(byte[] data, int pos) {
-        // For the details of EXT1 code group, see CEA-708B Section 7.2.
-        int command = data[pos] & 0xff;
-        ++pos;
-        if (command >= Const.CODE_C2_RANGE_START
-                && command <= Const.CODE_C2_RANGE_END) {
-            pos = parseC2(command, pos);
-        } else if (command >= Const.CODE_C3_RANGE_START
-                && command <= Const.CODE_C3_RANGE_END) {
-            pos = parseC3(command, pos);
-        } else if (command >= Const.CODE_G2_RANGE_START
-                && command <= Const.CODE_G2_RANGE_END) {
-            parseG2(command);
-        } else if (command >= Const.CODE_G3_RANGE_START
-                && command <= Const.CODE_G3_RANGE_END) {
-            parseG3(command);
-        }
-        return pos;
-    }
-
-    private int parseC2(int commandCode, int pos) {
-        // For the details of C2 code group, see CEA-708B Section 7.4.7.
-        // Extended Miscellaneous Control Codes
-        // C2 Table : No commands as of CEA-708B. A decoder must skip.
-        if (commandCode >= Const.CODE_C2_SKIP0_RANGE_START
-                && commandCode <= Const.CODE_C2_SKIP0_RANGE_END) {
-            // Do nothing.
-        } else if (commandCode >= Const.CODE_C2_SKIP1_RANGE_START
-                && commandCode <= Const.CODE_C2_SKIP1_RANGE_END) {
-            ++pos;
-        } else if (commandCode >= Const.CODE_C2_SKIP2_RANGE_START
-                && commandCode <= Const.CODE_C2_SKIP2_RANGE_END) {
-            pos += 2;
-        } else if (commandCode >= Const.CODE_C2_SKIP3_RANGE_START
-                && commandCode <= Const.CODE_C2_SKIP3_RANGE_END) {
-            pos += 3;
-        }
-        return pos;
-    }
-
-    private int parseC3(int commandCode, int pos) {
-        // For the details of C3 code group, see CEA-708B Section 7.4.8.
-        // Extended Control Code Set 2
-        // C3 Table : No commands as of CEA-708B. A decoder must skip.
-        if (commandCode >= Const.CODE_C3_SKIP4_RANGE_START
-                && commandCode <= Const.CODE_C3_SKIP4_RANGE_END) {
-            pos += 4;
-        } else if (commandCode >= Const.CODE_C3_SKIP5_RANGE_START
-                && commandCode <= Const.CODE_C3_SKIP5_RANGE_END) {
-            pos += 5;
-        }
-        return pos;
-    }
-
-    private void parseG2(int characterCode) {
-        // For the details of C3 code group, see CEA-708B Section 7.4.5.
-        // Extended Control Code Set 1(G2 Table)
-        switch (characterCode) {
-            case Const.CODE_G2_TSP:
-                // TODO : TSP is the Transparent space
-                break;
-            case Const.CODE_G2_NBTSP:
-                // TODO : NBTSP is Non-Breaking Transparent Space.
-                break;
-            case Const.CODE_G2_BLK:
-                // TODO : BLK indicates a solid block which fills the entire character block
-                // TODO : with a solid foreground color.
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void parseG3(int characterCode) {
-        // For the details of C3 code group, see CEA-708B Section 7.4.6.
-        // Future characters and icons(G3 Table)
-        if (characterCode == Const.CODE_G3_CC) {
-            // TODO : [CC] icon with square corners
-        }
-    }
-
-    /**
-     * Collection of CEA-708 structures.
-     */
-    private static class Const {
-
-        private Const() {
-        }
-
-        // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
-        public static final int CODE_C0_RANGE_START = 0x00;
-        public static final int CODE_C0_RANGE_END = 0x1f;
-        public static final int CODE_C1_RANGE_START = 0x80;
-        public static final int CODE_C1_RANGE_END = 0x9f;
-        public static final int CODE_G0_RANGE_START = 0x20;
-        public static final int CODE_G0_RANGE_END = 0x7f;
-        public static final int CODE_G1_RANGE_START = 0xa0;
-        public static final int CODE_G1_RANGE_END = 0xff;
-        public static final int CODE_C2_RANGE_START = 0x00;
-        public static final int CODE_C2_RANGE_END = 0x1f;
-        public static final int CODE_C3_RANGE_START = 0x80;
-        public static final int CODE_C3_RANGE_END = 0x9f;
-        public static final int CODE_G2_RANGE_START = 0x20;
-        public static final int CODE_G2_RANGE_END = 0x7f;
-        public static final int CODE_G3_RANGE_START = 0xa0;
-        public static final int CODE_G3_RANGE_END = 0xff;
-
-        // The following ranges are defined in CEA-708B Section 7.4.1.
-        public static final int CODE_C0_SKIP2_RANGE_START = 0x18;
-        public static final int CODE_C0_SKIP2_RANGE_END = 0x1f;
-        public static final int CODE_C0_SKIP1_RANGE_START = 0x10;
-        public static final int CODE_C0_SKIP1_RANGE_END = 0x17;
-
-        // The following ranges are defined in CEA-708B Section 7.4.7.
-        public static final int CODE_C2_SKIP0_RANGE_START = 0x00;
-        public static final int CODE_C2_SKIP0_RANGE_END = 0x07;
-        public static final int CODE_C2_SKIP1_RANGE_START = 0x08;
-        public static final int CODE_C2_SKIP1_RANGE_END = 0x0f;
-        public static final int CODE_C2_SKIP2_RANGE_START = 0x10;
-        public static final int CODE_C2_SKIP2_RANGE_END = 0x17;
-        public static final int CODE_C2_SKIP3_RANGE_START = 0x18;
-        public static final int CODE_C2_SKIP3_RANGE_END = 0x1f;
-
-        // The following ranges are defined in CEA-708B Section 7.4.8.
-        public static final int CODE_C3_SKIP4_RANGE_START = 0x80;
-        public static final int CODE_C3_SKIP4_RANGE_END = 0x87;
-        public static final int CODE_C3_SKIP5_RANGE_START = 0x88;
-        public static final int CODE_C3_SKIP5_RANGE_END = 0x8f;
-
-        // The following values are the special characters of CEA-708 spec.
-        public static final int CODE_C0_NUL = 0x00;
-        public static final int CODE_C0_ETX = 0x03;
-        public static final int CODE_C0_BS = 0x08;
-        public static final int CODE_C0_FF = 0x0c;
-        public static final int CODE_C0_CR = 0x0d;
-        public static final int CODE_C0_HCR = 0x0e;
-        public static final int CODE_C0_EXT1 = 0x10;
-        public static final int CODE_C0_P16 = 0x18;
-        public static final int CODE_G0_MUSICNOTE = 0x7f;
-        public static final int CODE_G2_TSP = 0x20;
-        public static final int CODE_G2_NBTSP = 0x21;
-        public static final int CODE_G2_BLK = 0x30;
-        public static final int CODE_G3_CC = 0xa0;
-
-        // The following values are the command bits of CEA-708 spec.
-        public static final int CODE_C1_CW0 = 0x80;
-        public static final int CODE_C1_CW1 = 0x81;
-        public static final int CODE_C1_CW2 = 0x82;
-        public static final int CODE_C1_CW3 = 0x83;
-        public static final int CODE_C1_CW4 = 0x84;
-        public static final int CODE_C1_CW5 = 0x85;
-        public static final int CODE_C1_CW6 = 0x86;
-        public static final int CODE_C1_CW7 = 0x87;
-        public static final int CODE_C1_CLW = 0x88;
-        public static final int CODE_C1_DSW = 0x89;
-        public static final int CODE_C1_HDW = 0x8a;
-        public static final int CODE_C1_TGW = 0x8b;
-        public static final int CODE_C1_DLW = 0x8c;
-        public static final int CODE_C1_DLY = 0x8d;
-        public static final int CODE_C1_DLC = 0x8e;
-        public static final int CODE_C1_RST = 0x8f;
-        public static final int CODE_C1_SPA = 0x90;
-        public static final int CODE_C1_SPC = 0x91;
-        public static final int CODE_C1_SPL = 0x92;
-        public static final int CODE_C1_SWA = 0x97;
-        public static final int CODE_C1_DF0 = 0x98;
-        public static final int CODE_C1_DF1 = 0x99;
-        public static final int CODE_C1_DF2 = 0x9a;
-        public static final int CODE_C1_DF3 = 0x9b;
-        public static final int CODE_C1_DF4 = 0x9c;
-        public static final int CODE_C1_DF5 = 0x9d;
-        public static final int CODE_C1_DF6 = 0x9e;
-        public static final int CODE_C1_DF7 = 0x9f;
-    }
-
-    /**
-     * CEA-708B-specific color.
-     */
-    public static class CaptionColor {
-        public static final int OPACITY_SOLID = 0;
-        public static final int OPACITY_FLASH = 1;
-        public static final int OPACITY_TRANSLUCENT = 2;
-        public static final int OPACITY_TRANSPARENT = 3;
-
-        private static final int[] COLOR_MAP = new int[] { 0x00, 0x0f, 0xf0, 0xff };
-        private static final int[] OPACITY_MAP = new int[] { 0xff, 0xfe, 0x80, 0x00 };
-
-        public final int opacity;
-        public final int red;
-        public final int green;
-        public final int blue;
-
-        CaptionColor(int opacity, int red, int green, int blue) {
-            this.opacity = opacity;
-            this.red = red;
-            this.green = green;
-            this.blue = blue;
-        }
-
-        public int getArgbValue() {
-            return Color.argb(
-                    OPACITY_MAP[opacity], COLOR_MAP[red], COLOR_MAP[green], COLOR_MAP[blue]);
-        }
-    }
-
-    /**
-     * Caption event generated by {@link Cea708CCParser}.
-     */
-    public static class CaptionEvent {
-        public final int type;
-        public final Object obj;
-
-        CaptionEvent(int type, Object obj) {
-            this.type = type;
-            this.obj = obj;
-        }
-    }
-
-    /**
-     * Pen style information.
-     */
-    public static class CaptionPenAttr {
-        // Pen sizes
-        public static final int PEN_SIZE_SMALL = 0;
-        public static final int PEN_SIZE_STANDARD = 1;
-        public static final int PEN_SIZE_LARGE = 2;
-
-        // Offsets
-        public static final int OFFSET_SUBSCRIPT = 0;
-        public static final int OFFSET_NORMAL = 1;
-        public static final int OFFSET_SUPERSCRIPT = 2;
-
-        public final int penSize;
-        public final int penOffset;
-        public final int textTag;
-        public final int fontTag;
-        public final int edgeType;
-        public final boolean underline;
-        public final boolean italic;
-
-        CaptionPenAttr(int penSize, int penOffset, int textTag, int fontTag, int edgeType,
-                boolean underline, boolean italic) {
-            this.penSize = penSize;
-            this.penOffset = penOffset;
-            this.textTag = textTag;
-            this.fontTag = fontTag;
-            this.edgeType = edgeType;
-            this.underline = underline;
-            this.italic = italic;
-        }
-    }
-
-    /**
-     * {@link CaptionColor} objects that indicate the foreground, background, and edge color of a
-     * pen.
-     */
-    public static class CaptionPenColor {
-        public final CaptionColor foregroundColor;
-        public final CaptionColor backgroundColor;
-        public final CaptionColor edgeColor;
-
-        CaptionPenColor(CaptionColor foregroundColor, CaptionColor backgroundColor,
-                CaptionColor edgeColor) {
-            this.foregroundColor = foregroundColor;
-            this.backgroundColor = backgroundColor;
-            this.edgeColor = edgeColor;
-        }
-    }
-
-    /**
-     * Location information of a pen.
-     */
-    public static class CaptionPenLocation {
-        public final int row;
-        public final int column;
-
-        CaptionPenLocation(int row, int column) {
-            this.row = row;
-            this.column = column;
-        }
-    }
-
-    /**
-     * Attributes of a caption window, which is defined in CEA-708B.
-     */
-    public static class CaptionWindowAttr {
-        public final CaptionColor fillColor;
-        public final CaptionColor borderColor;
-        public final int borderType;
-        public final boolean wordWrap;
-        public final int printDirection;
-        public final int scrollDirection;
-        public final int justify;
-        public final int effectDirection;
-        public final int effectSpeed;
-        public final int displayEffect;
-
-        CaptionWindowAttr(CaptionColor fillColor, CaptionColor borderColor, int borderType,
-                boolean wordWrap, int printDirection, int scrollDirection, int justify,
-                int effectDirection,
-                int effectSpeed, int displayEffect) {
-            this.fillColor = fillColor;
-            this.borderColor = borderColor;
-            this.borderType = borderType;
-            this.wordWrap = wordWrap;
-            this.printDirection = printDirection;
-            this.scrollDirection = scrollDirection;
-            this.justify = justify;
-            this.effectDirection = effectDirection;
-            this.effectSpeed = effectSpeed;
-            this.displayEffect = displayEffect;
-        }
-    }
-
-    /**
-     * Construction information of the caption window of CEA-708B.
-     */
-    public static class CaptionWindow {
-        public final int id;
-        public final boolean visible;
-        public final boolean rowLock;
-        public final boolean columnLock;
-        public final int priority;
-        public final boolean relativePositioning;
-        public final int anchorVertical;
-        public final int anchorHorizontal;
-        public final int anchorId;
-        public final int rowCount;
-        public final int columnCount;
-        public final int penStyle;
-        public final int windowStyle;
-
-        CaptionWindow(int id, boolean visible,
-                boolean rowLock, boolean columnLock, int priority, boolean relativePositioning,
-                int anchorVertical, int anchorHorizontal, int anchorId,
-                int rowCount, int columnCount, int penStyle, int windowStyle) {
-            this.id = id;
-            this.visible = visible;
-            this.rowLock = rowLock;
-            this.columnLock = columnLock;
-            this.priority = priority;
-            this.relativePositioning = relativePositioning;
-            this.anchorVertical = anchorVertical;
-            this.anchorHorizontal = anchorHorizontal;
-            this.anchorId = anchorId;
-            this.rowCount = rowCount;
-            this.columnCount = columnCount;
-            this.penStyle = penStyle;
-            this.windowStyle = windowStyle;
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CaptionRenderer.java b/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CaptionRenderer.java
deleted file mode 100644
index 25c513c..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/Cea708CaptionRenderer.java
+++ /dev/null
@@ -1,1232 +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.media2.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.StyleSpan;
-import android.text.style.SubscriptSpan;
-import android.text.style.SuperscriptSpan;
-import android.text.style.UnderlineSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.CaptioningManager;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-// Note: This is forked from android.media.Cea708CaptionRenderer since P
-class Cea708CaptionRenderer extends SubtitleController.Renderer {
-    private final Context mContext;
-    private Cea708CCWidget mCCWidget;
-
-    Cea708CaptionRenderer(@NonNull Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public boolean supports(@NonNull MediaFormat format) {
-        if (format.containsKey(MediaFormat.KEY_MIME)) {
-            String mimeType = format.getString(MediaFormat.KEY_MIME);
-            return MediaFormat.MIMETYPE_TEXT_CEA_708.equals(mimeType);
-        }
-        return false;
-    }
-
-    @Override
-    public @NonNull SubtitleTrack createTrack(@NonNull MediaFormat format) {
-        String mimeType = format.getString(MediaFormat.KEY_MIME);
-        if (MediaFormat.MIMETYPE_TEXT_CEA_708.equals(mimeType)) {
-            if (mCCWidget == null) {
-                mCCWidget = new Cea708CCWidget(mContext);
-            }
-            return new Cea708CaptionTrack(mCCWidget, format);
-        }
-        throw new RuntimeException("No matching format: " + format.toString());
-    }
-
-    static class Cea708CaptionTrack extends SubtitleTrack {
-        private final Cea708CCParser mCCParser;
-        private final Cea708CCWidget mRenderingWidget;
-
-        Cea708CaptionTrack(Cea708CCWidget renderingWidget, MediaFormat format) {
-            super(format);
-
-            mRenderingWidget = renderingWidget;
-            mCCParser = new Cea708CCParser(mRenderingWidget);
-        }
-
-        @Override
-        public void onData(byte[] data, boolean eos, long runID) {
-            mCCParser.parse(data);
-        }
-
-        @Override
-        public RenderingWidget getRenderingWidget() {
-            return mRenderingWidget;
-        }
-
-        @Override
-        public void updateView(ArrayList<Cue> activeCues) {
-            // Overriding with NO-OP, CC rendering by-passes this
-        }
-    }
-
-    /**
-     * Widget capable of rendering CEA-708 closed captions.
-     */
-    class Cea708CCWidget extends ClosedCaptionWidget implements Cea708CCParser.DisplayListener {
-        private final CCHandler mCCHandler;
-
-        Cea708CCWidget(Context context) {
-            this(context, null);
-        }
-
-        Cea708CCWidget(Context context, AttributeSet attrs) {
-            this(context, attrs, 0);
-        }
-
-        Cea708CCWidget(Context context, AttributeSet attrs, int defStyleAttr) {
-            super(context, attrs, defStyleAttr);
-
-            mCCHandler = new CCHandler((CCLayout) mClosedCaptionLayout);
-        }
-
-        @Override
-        public ClosedCaptionLayout createCaptionLayout(Context context) {
-            return new CCLayout(context);
-        }
-
-        @Override
-        public void emitEvent(Cea708CCParser.CaptionEvent event) {
-            mCCHandler.processCaptionEvent(event);
-
-            setSize(getWidth(), getHeight());
-
-            if (mListener != null) {
-                mListener.onChanged(this);
-            }
-        }
-
-        @Override
-        public void onDraw(@NonNull Canvas canvas) {
-            super.onDraw(canvas);
-            ((ViewGroup) mClosedCaptionLayout).draw(canvas);
-        }
-
-        /**
-         * A layout that scales its children using the given percentage value.
-         */
-        class ScaledLayout extends ViewGroup {
-            private static final String TAG = "ScaledLayout";
-            private static final boolean DEBUG = false;
-            private final Comparator<Rect> mRectTopLeftSorter = new Comparator<Rect>() {
-                @Override
-                public int compare(Rect lhs, Rect rhs) {
-                    if (lhs.top != rhs.top) {
-                        return lhs.top - rhs.top;
-                    } else {
-                        return lhs.left - rhs.left;
-                    }
-                }
-            };
-
-            private Rect[] mRectArray;
-
-            ScaledLayout(Context context) {
-                super(context);
-            }
-
-            /**
-             * ScaledLayoutParams stores the four scale factors.
-             * <br>
-             * Vertical coordinate system:   (scaleStartRow * 100) % ~ (scaleEndRow * 100) %
-             * Horizontal coordinate system: (scaleStartCol * 100) % ~ (scaleEndCol * 100) %
-             * <br>
-             * In XML, for example,
-             * <pre>
-             * {@code
-             * <View
-             *     app:layout_scaleStartRow="0.1"
-             *     app:layout_scaleEndRow="0.5"
-             *     app:layout_scaleStartCol="0.4"
-             *     app:layout_scaleEndCol="1" />
-             * }
-             * </pre>
-             */
-            class ScaledLayoutParams extends ViewGroup.LayoutParams {
-                public static final float SCALE_UNSPECIFIED = -1;
-                public float scaleStartRow;
-                public float scaleEndRow;
-                public float scaleStartCol;
-                public float scaleEndCol;
-
-                ScaledLayoutParams(float scaleStartRow, float scaleEndRow,
-                        float scaleStartCol, float scaleEndCol) {
-                    super(MATCH_PARENT, MATCH_PARENT);
-                    this.scaleStartRow = scaleStartRow;
-                    this.scaleEndRow = scaleEndRow;
-                    this.scaleStartCol = scaleStartCol;
-                    this.scaleEndCol = scaleEndCol;
-                }
-
-                ScaledLayoutParams(Context context, AttributeSet attrs) {
-                    super(MATCH_PARENT, MATCH_PARENT);
-                }
-            }
-
-            @Override
-            public LayoutParams generateLayoutParams(AttributeSet attrs) {
-                return new ScaledLayoutParams(getContext(), attrs);
-            }
-
-            @Override
-            protected boolean checkLayoutParams(LayoutParams p) {
-                return (p instanceof ScaledLayoutParams);
-            }
-
-            @Override
-            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-                int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-                int width = widthSpecSize - getPaddingLeft() - getPaddingRight();
-                int height = heightSpecSize - getPaddingTop() - getPaddingBottom();
-                if (DEBUG) {
-                    Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height));
-                }
-                int count = getChildCount();
-                mRectArray = new Rect[count];
-                for (int i = 0; i < count; ++i) {
-                    View child = getChildAt(i);
-                    ViewGroup.LayoutParams params = child.getLayoutParams();
-                    float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol;
-                    if (!(params instanceof ScaledLayoutParams)) {
-                        throw new RuntimeException("A child of ScaledLayout cannot have the "
-                                + "UNSPECIFIED scale factors");
-                    }
-                    scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow;
-                    scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow;
-                    scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol;
-                    scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol;
-                    if (scaleStartRow < 0 || scaleStartRow > 1) {
-                        throw new RuntimeException("A child of ScaledLayout should have a range of "
-                                + "scaleStartRow between 0 and 1");
-                    }
-                    if (scaleEndRow < scaleStartRow || scaleStartRow > 1) {
-                        throw new RuntimeException("A child of ScaledLayout should have a range of "
-                                + "scaleEndRow between scaleStartRow and 1");
-                    }
-                    if (scaleEndCol < 0 || scaleEndCol > 1) {
-                        throw new RuntimeException("A child of ScaledLayout should have a range of "
-                                + "scaleStartCol between 0 and 1");
-                    }
-                    if (scaleEndCol < scaleStartCol || scaleEndCol > 1) {
-                        throw new RuntimeException("A child of ScaledLayout should have a range of "
-                                + "scaleEndCol between scaleStartCol and 1");
-                    }
-                    if (DEBUG) {
-                        Log.d(TAG, String.format("onMeasure child scaleStartRow: %f scaleEndRow: "
-                                        + "%f scaleStartCol: %f scaleEndCol: %f",
-                                scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
-                    }
-                    mRectArray[i] = new Rect((int) (scaleStartCol * width), (int) (scaleStartRow
-                            * height), (int) (scaleEndCol * width), (int) (scaleEndRow * height));
-                    int childWidthSpec = MeasureSpec.makeMeasureSpec(
-                            (int) (width * (scaleEndCol - scaleStartCol)), MeasureSpec.EXACTLY);
-                    int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-                    child.measure(childWidthSpec, childHeightSpec);
-
-                    // If the height of the measured child view is bigger than the height of the
-                    // calculated region by the given ScaleLayoutParams, the height of the region
-                    // should be increased to fit the size of the child view.
-                    if (child.getMeasuredHeight() > mRectArray[i].height()) {
-                        int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height();
-                        overflowedHeight = (overflowedHeight + 1) / 2;
-                        mRectArray[i].bottom += overflowedHeight;
-                        mRectArray[i].top -= overflowedHeight;
-                        if (mRectArray[i].top < 0) {
-                            mRectArray[i].bottom -= mRectArray[i].top;
-                            mRectArray[i].top = 0;
-                        }
-                        if (mRectArray[i].bottom > height) {
-                            mRectArray[i].top -= mRectArray[i].bottom - height;
-                            mRectArray[i].bottom = height;
-                        }
-                    }
-                    childHeightSpec = MeasureSpec.makeMeasureSpec(
-                            (int) (height * (scaleEndRow - scaleStartRow)), MeasureSpec.EXACTLY);
-                    child.measure(childWidthSpec, childHeightSpec);
-                }
-
-                // Avoid overlapping rectangles.
-                // Step 1. Sort rectangles by position (top-left).
-                int visibleRectCount = 0;
-                int[] visibleRectGroup = new int[count];
-                Rect[] visibleRectArray = new Rect[count];
-                for (int i = 0; i < count; ++i) {
-                    if (getChildAt(i).getVisibility() == View.VISIBLE) {
-                        visibleRectGroup[visibleRectCount] = visibleRectCount;
-                        visibleRectArray[visibleRectCount] = mRectArray[i];
-                        ++visibleRectCount;
-                    }
-                }
-                Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter);
-
-                // Step 2. Move down if there are overlapping rectangles.
-                for (int i = 0; i < visibleRectCount - 1; ++i) {
-                    for (int j = i + 1; j < visibleRectCount; ++j) {
-                        if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) {
-                            visibleRectGroup[j] = visibleRectGroup[i];
-                            visibleRectArray[j].set(visibleRectArray[j].left,
-                                    visibleRectArray[i].bottom,
-                                    visibleRectArray[j].right,
-                                    visibleRectArray[i].bottom + visibleRectArray[j].height());
-                        }
-                    }
-                }
-
-                // Step 3. Move up if there is any overflowed rectangle.
-                for (int i = visibleRectCount - 1; i >= 0; --i) {
-                    if (visibleRectArray[i].bottom > height) {
-                        int overflowedHeight = visibleRectArray[i].bottom - height;
-                        for (int j = 0; j <= i; ++j) {
-                            if (visibleRectGroup[i] == visibleRectGroup[j]) {
-                                visibleRectArray[j].set(visibleRectArray[j].left,
-                                        visibleRectArray[j].top - overflowedHeight,
-                                        visibleRectArray[j].right,
-                                        visibleRectArray[j].bottom - overflowedHeight);
-                            }
-                        }
-                    }
-                }
-                setMeasuredDimension(widthSpecSize, heightSpecSize);
-            }
-
-            @Override
-            protected void onLayout(boolean changed, int l, int t, int r, int b) {
-                int paddingLeft = getPaddingLeft();
-                int paddingTop = getPaddingTop();
-                int count = getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    View child = getChildAt(i);
-                    if (child.getVisibility() != GONE) {
-                        int childLeft = paddingLeft + mRectArray[i].left;
-                        int childTop = paddingTop + mRectArray[i].top;
-                        int childBottom = paddingLeft + mRectArray[i].bottom;
-                        int childRight = paddingTop + mRectArray[i].right;
-                        if (DEBUG) {
-                            Log.d(TAG, String.format(
-                                    "child layout bottom: %d left: %d right: %d top: %d",
-                                    childBottom, childLeft, childRight, childTop));
-                        }
-                        child.layout(childLeft, childTop, childRight, childBottom);
-                    }
-                }
-            }
-
-            @Override
-            public void dispatchDraw(Canvas canvas) {
-                int paddingLeft = getPaddingLeft();
-                int paddingTop = getPaddingTop();
-                int count = getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    View child = getChildAt(i);
-                    if (child.getVisibility() != GONE) {
-                        if (i >= mRectArray.length) {
-                            break;
-                        }
-                        int childLeft = paddingLeft + mRectArray[i].left;
-                        int childTop = paddingTop + mRectArray[i].top;
-                        final int saveCount = canvas.save();
-                        canvas.translate(childLeft, childTop);
-                        child.draw(canvas);
-                        canvas.restoreToCount(saveCount);
-                    }
-                }
-            }
-        }
-
-        /**
-         * Layout containing the safe title area that helps the closed captions look more prominent.
-         *
-         * <p>This is required by CEA-708B.
-         */
-        class CCLayout extends ScaledLayout implements ClosedCaptionLayout {
-            private static final float SAFE_TITLE_AREA_SCALE_START_X = 0.1f;
-            private static final float SAFE_TITLE_AREA_SCALE_END_X = 0.9f;
-            private static final float SAFE_TITLE_AREA_SCALE_START_Y = 0.1f;
-            private static final float SAFE_TITLE_AREA_SCALE_END_Y = 0.9f;
-
-            private final ScaledLayout mSafeTitleAreaLayout;
-
-            CCLayout(Context context) {
-                super(context);
-
-                mSafeTitleAreaLayout = new ScaledLayout(context);
-                addView(mSafeTitleAreaLayout, new ScaledLayout.ScaledLayoutParams(
-                        SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X,
-                        SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y));
-            }
-
-            public void addOrUpdateViewToSafeTitleArea(CCWindowLayout captionWindowLayout,
-                    ScaledLayoutParams scaledLayoutParams) {
-                int index = mSafeTitleAreaLayout.indexOfChild(captionWindowLayout);
-                if (index < 0) {
-                    mSafeTitleAreaLayout.addView(captionWindowLayout, scaledLayoutParams);
-                    return;
-                }
-                mSafeTitleAreaLayout.updateViewLayout(captionWindowLayout, scaledLayoutParams);
-            }
-
-            public void removeViewFromSafeTitleArea(CCWindowLayout captionWindowLayout) {
-                mSafeTitleAreaLayout.removeView(captionWindowLayout);
-            }
-
-            @Override
-            public void setCaptionStyle(CaptionStyle style) {
-                final int count = mSafeTitleAreaLayout.getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    final CCWindowLayout windowLayout =
-                            (CCWindowLayout) mSafeTitleAreaLayout.getChildAt(i);
-                    windowLayout.setCaptionStyle(style);
-                }
-            }
-
-            @Override
-            public void setFontScale(float fontScale) {
-                final int count = mSafeTitleAreaLayout.getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    final CCWindowLayout windowLayout =
-                            (CCWindowLayout) mSafeTitleAreaLayout.getChildAt(i);
-                    windowLayout.setFontScale(fontScale);
-                }
-            }
-        }
-
-        /**
-         * Renders the selected CC track.
-         */
-        class CCHandler implements Handler.Callback {
-            // TODO: Remaining works
-            // CaptionTrackRenderer does not support the full spec of CEA-708.
-            // The remaining works are described in the follows.
-            // C0 Table: Backspace, FF, and HCR are not supported. The rule for P16 is not
-            //           standardized but it is handled as EUC-KR charset for Korea broadcasting.
-            // C1 Table: All the styles of windows and pens except underline, italic, pen size,
-            //           and pen offset specified in CEA-708 are ignored and this follows system
-            //           wide CC preferences for look and feel. SetPenLocation is not implemented.
-            // G2 Table: TSP, NBTSP and BLK are not supported.
-            // Text/commands: Word wrapping, fonts, row and column locking are not supported.
-
-            private static final String TAG = "CCHandler";
-            private static final boolean DEBUG = false;
-
-            private static final int TENTHS_OF_SECOND_IN_MILLIS = 100;
-
-            // According to CEA-708B, there can exist up to 8 caption windows.
-            private static final int CAPTION_WINDOWS_MAX = 8;
-            private static final int CAPTION_ALL_WINDOWS_BITMAP = 255;
-
-            private static final int MSG_DELAY_CANCEL = 1;
-            private static final int MSG_CAPTION_CLEAR = 2;
-
-            private static final long CAPTION_CLEAR_INTERVAL_MS = 60000;
-
-            private final CCLayout mCCLayout;
-            private boolean mIsDelayed = false;
-            private CCWindowLayout mCurrentWindowLayout;
-            private final CCWindowLayout[] mCaptionWindowLayouts =
-                    new CCWindowLayout[CAPTION_WINDOWS_MAX];
-            private final ArrayList<Cea708CCParser.CaptionEvent> mPendingCaptionEvents =
-                    new ArrayList<>();
-            private final Handler mHandler;
-
-            @SuppressWarnings("deprecation")
-            CCHandler(CCLayout ccLayout) {
-                mCCLayout = ccLayout;
-                mHandler = new Handler(this);
-            }
-
-            @Override
-            public boolean handleMessage(Message msg) {
-                switch (msg.what) {
-                    case MSG_DELAY_CANCEL:
-                        delayCancel();
-                        return true;
-                    case MSG_CAPTION_CLEAR:
-                        clearWindows(CAPTION_ALL_WINDOWS_BITMAP);
-                        return true;
-                }
-                return false;
-            }
-
-            public void processCaptionEvent(Cea708CCParser.CaptionEvent event) {
-                if (mIsDelayed) {
-                    mPendingCaptionEvents.add(event);
-                    return;
-                }
-                switch (event.type) {
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_BUFFER:
-                        sendBufferToCurrentWindow((String) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_CONTROL:
-                        sendControlToCurrentWindow((char) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_CWX:
-                        setCurrentWindowLayout((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_CLW:
-                        clearWindows((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_DSW:
-                        displayWindows((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_HDW:
-                        hideWindows((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_TGW:
-                        toggleWindows((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_DLW:
-                        deleteWindows((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_DLY:
-                        delay((int) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_DLC:
-                        delayCancel();
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_RST:
-                        reset();
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_SPA:
-                        setPenAttr((Cea708CCParser.CaptionPenAttr) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_SPC:
-                        setPenColor((Cea708CCParser.CaptionPenColor) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_SPL:
-                        setPenLocation((Cea708CCParser.CaptionPenLocation) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_SWA:
-                        setWindowAttr((Cea708CCParser.CaptionWindowAttr) event.obj);
-                        break;
-                    case Cea708CCParser.CAPTION_EMIT_TYPE_COMMAND_DFX:
-                        defineWindow((Cea708CCParser.CaptionWindow) event.obj);
-                        break;
-                }
-            }
-
-            // The window related caption commands
-            private void setCurrentWindowLayout(int windowId) {
-                if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
-                    return;
-                }
-                CCWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
-                if (windowLayout == null) {
-                    return;
-                }
-                if (DEBUG) {
-                    Log.d(TAG, "setCurrentWindowLayout to " + windowId);
-                }
-                mCurrentWindowLayout = windowLayout;
-            }
-
-            // Each bit of windowBitmap indicates a window.
-            // If a bit is set, the window id is the same as the number of the trailing zeros of the
-            // bit.
-            private ArrayList<CCWindowLayout> getWindowsFromBitmap(int windowBitmap) {
-                ArrayList<CCWindowLayout> windows = new ArrayList<>();
-                for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
-                    if ((windowBitmap & (1 << i)) != 0) {
-                        CCWindowLayout windowLayout = mCaptionWindowLayouts[i];
-                        if (windowLayout != null) {
-                            windows.add(windowLayout);
-                        }
-                    }
-                }
-                return windows;
-            }
-
-            private void clearWindows(int windowBitmap) {
-                if (windowBitmap == 0) {
-                    return;
-                }
-                for (CCWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
-                    windowLayout.clear();
-                }
-            }
-
-            private void displayWindows(int windowBitmap) {
-                if (windowBitmap == 0) {
-                    return;
-                }
-                for (CCWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
-                    windowLayout.show();
-                }
-            }
-
-            private void hideWindows(int windowBitmap) {
-                if (windowBitmap == 0) {
-                    return;
-                }
-                for (CCWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
-                    windowLayout.hide();
-                }
-            }
-
-            private void toggleWindows(int windowBitmap) {
-                if (windowBitmap == 0) {
-                    return;
-                }
-                for (CCWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
-                    if (windowLayout.isShown()) {
-                        windowLayout.hide();
-                    } else {
-                        windowLayout.show();
-                    }
-                }
-            }
-
-            private void deleteWindows(int windowBitmap) {
-                if (windowBitmap == 0) {
-                    return;
-                }
-                for (CCWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
-                    windowLayout.removeFromCaptionView();
-                    mCaptionWindowLayouts[windowLayout.getCaptionWindowId()] = null;
-                }
-            }
-
-            public void reset() {
-                mCurrentWindowLayout = null;
-                mIsDelayed = false;
-                mPendingCaptionEvents.clear();
-                for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
-                    if (mCaptionWindowLayouts[i] != null) {
-                        mCaptionWindowLayouts[i].removeFromCaptionView();
-                    }
-                    mCaptionWindowLayouts[i] = null;
-                }
-                mCCLayout.setVisibility(View.INVISIBLE);
-                mHandler.removeMessages(MSG_CAPTION_CLEAR);
-            }
-
-            private void setWindowAttr(Cea708CCParser.CaptionWindowAttr windowAttr) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.setWindowAttr(windowAttr);
-                }
-            }
-
-            private void defineWindow(Cea708CCParser.CaptionWindow window) {
-                if (window == null) {
-                    return;
-                }
-                int windowId = window.id;
-                if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
-                    return;
-                }
-                CCWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
-                if (windowLayout == null) {
-                    windowLayout = new CCWindowLayout(mCCLayout.getContext());
-                }
-                windowLayout.initWindow(mCCLayout, window);
-                mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout;
-            }
-
-            // The job related caption commands
-            private void delay(int tenthsOfSeconds) {
-                if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) {
-                    return;
-                }
-                mIsDelayed = true;
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DELAY_CANCEL),
-                        tenthsOfSeconds * TENTHS_OF_SECOND_IN_MILLIS);
-            }
-
-            private void delayCancel() {
-                mIsDelayed = false;
-                processPendingBuffer();
-            }
-
-            private void processPendingBuffer() {
-                for (Cea708CCParser.CaptionEvent event : mPendingCaptionEvents) {
-                    processCaptionEvent(event);
-                }
-                mPendingCaptionEvents.clear();
-            }
-
-            // The implicit write caption commands
-            private void sendControlToCurrentWindow(char control) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.sendControl(control);
-                }
-            }
-
-            private void sendBufferToCurrentWindow(String buffer) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.sendBuffer(buffer);
-                    mHandler.removeMessages(MSG_CAPTION_CLEAR);
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CAPTION_CLEAR),
-                            CAPTION_CLEAR_INTERVAL_MS);
-                }
-            }
-
-            // The pen related caption commands
-            private void setPenAttr(Cea708CCParser.CaptionPenAttr attr) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.setPenAttr(attr);
-                }
-            }
-
-            private void setPenColor(Cea708CCParser.CaptionPenColor color) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.setPenColor(color);
-                }
-            }
-
-            private void setPenLocation(Cea708CCParser.CaptionPenLocation location) {
-                if (mCurrentWindowLayout != null) {
-                    mCurrentWindowLayout.setPenLocation(location.row, location.column);
-                }
-            }
-        }
-
-        /**
-         * Layout which renders a caption window of CEA-708B. It contains a {@link TextView} that
-         * takes care of displaying the actual CC text.
-         */
-        private class CCWindowLayout extends RelativeLayout implements View.OnLayoutChangeListener {
-            private static final String TAG = "CCWindowLayout";
-
-            private static final float PROPORTION_PEN_SIZE_SMALL = .75f;
-            private static final float PROPORTION_PEN_SIZE_LARGE = 1.25f;
-
-            // The following values indicates the maximum cell number of a window.
-            private static final int ANCHOR_RELATIVE_POSITIONING_MAX = 99;
-            private static final int ANCHOR_VERTICAL_MAX = 74;
-            private static final int ANCHOR_HORIZONTAL_16_9_MAX = 209;
-            private static final int MAX_COLUMN_COUNT_16_9 = 42;
-
-            // The following values indicates a gravity of a window.
-            private static final int ANCHOR_MODE_DIVIDER = 3;
-            private static final int ANCHOR_HORIZONTAL_MODE_LEFT = 0;
-            private static final int ANCHOR_HORIZONTAL_MODE_CENTER = 1;
-            private static final int ANCHOR_HORIZONTAL_MODE_RIGHT = 2;
-            private static final int ANCHOR_VERTICAL_MODE_TOP = 0;
-            private static final int ANCHOR_VERTICAL_MODE_CENTER = 1;
-            private static final int ANCHOR_VERTICAL_MODE_BOTTOM = 2;
-
-            private CCLayout mCCLayout;
-
-            private CCView mCCView;
-            private CaptionStyle mCaptionStyle;
-            private int mRowLimit = 0;
-            private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
-            private final List<CharacterStyle> mCharacterStyles = new ArrayList<>();
-            private int mCaptionWindowId;
-            private int mRow = -1;
-            private float mFontScale;
-            private float mTextSize;
-            private String mWidestChar;
-            private int mLastCaptionLayoutWidth;
-            private int mLastCaptionLayoutHeight;
-
-            CCWindowLayout(Context context) {
-                this(context, null);
-            }
-
-            CCWindowLayout(Context context, AttributeSet attrs) {
-                this(context, attrs, 0);
-            }
-
-            CCWindowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-                super(context, attrs, defStyleAttr);
-
-                // Add a subtitle view to the layout.
-                mCCView = new CCView(context);
-                LayoutParams params = new RelativeLayout.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-                addView(mCCView, params);
-
-                // Set the system wide CC preferences to the subtitle view.
-                CaptioningManager captioningManager =
-                        (CaptioningManager) context.getSystemService(
-                                Context.CAPTIONING_SERVICE);
-                mFontScale = captioningManager.getFontScale();
-                setCaptionStyle(new CaptionStyle(
-                        captioningManager.getUserStyle()));
-
-                mCCView.setText("");
-                updateWidestChar();
-            }
-
-            public void setCaptionStyle(CaptionStyle style) {
-                mCaptionStyle = style;
-                mCCView.setCaptionStyle(style);
-            }
-
-            public void setFontScale(float fontScale) {
-                mFontScale = fontScale;
-                updateTextSize();
-            }
-
-            public int getCaptionWindowId() {
-                return mCaptionWindowId;
-            }
-
-            public void setCaptionWindowId(int captionWindowId) {
-                mCaptionWindowId = captionWindowId;
-            }
-
-            public void clear() {
-                clearText();
-                hide();
-            }
-
-            public void show() {
-                setVisibility(View.VISIBLE);
-                requestLayout();
-            }
-
-            public void hide() {
-                setVisibility(View.INVISIBLE);
-                requestLayout();
-            }
-
-            public void setPenAttr(Cea708CCParser.CaptionPenAttr penAttr) {
-                mCharacterStyles.clear();
-                if (penAttr.italic) {
-                    mCharacterStyles.add(new StyleSpan(Typeface.ITALIC));
-                }
-                if (penAttr.underline) {
-                    mCharacterStyles.add(new UnderlineSpan());
-                }
-                switch (penAttr.penSize) {
-                    case Cea708CCParser.CaptionPenAttr.PEN_SIZE_SMALL:
-                        mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_SMALL));
-                        break;
-                    case Cea708CCParser.CaptionPenAttr.PEN_SIZE_LARGE:
-                        mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_LARGE));
-                        break;
-                }
-                switch (penAttr.penOffset) {
-                    case Cea708CCParser.CaptionPenAttr.OFFSET_SUBSCRIPT:
-                        mCharacterStyles.add(new SubscriptSpan());
-                        break;
-                    case Cea708CCParser.CaptionPenAttr.OFFSET_SUPERSCRIPT:
-                        mCharacterStyles.add(new SuperscriptSpan());
-                        break;
-                }
-            }
-
-            public void setPenColor(Cea708CCParser.CaptionPenColor penColor) {
-                // TODO: apply pen colors or skip this and use the style of system wide CC style
-                // as is.
-            }
-
-            public void setPenLocation(int row, int column) {
-                // TODO: change the location of pen based on row and column both.
-                if (mRow >= 0) {
-                    for (int r = mRow; r < row; ++r) {
-                        appendText("\n");
-                    }
-                }
-                mRow = row;
-            }
-
-            public void setWindowAttr(Cea708CCParser.CaptionWindowAttr windowAttr) {
-                // TODO: apply window attrs or skip this and use the style of system wide CC style
-                // as is.
-            }
-
-            public void sendBuffer(String buffer) {
-                appendText(buffer);
-            }
-
-            public void sendControl(char control) {
-                // TODO: there are a bunch of ASCII-style control codes.
-            }
-
-            /**
-             * This method places the window on a given CaptionLayout along with the anchor of the
-             * window.
-             * <p>
-             * According to CEA-708B, the anchor id indicates the gravity of the window as the
-             * follows.
-             * For example, A value 7 of a anchor id says that a window is align with its parent
-             * bottom and is located at the center horizontally of its parent.
-             * </p>
-             * <h4>Anchor id and the gravity of a window</h4>
-             * <table>
-             *     <tr>
-             *         <th>GRAVITY</th>
-             *         <th>LEFT</th>
-             *         <th>CENTER_HORIZONTAL</th>
-             *         <th>RIGHT</th>
-             *     </tr>
-             *     <tr>
-             *         <th>TOP</th>
-             *         <td>0</td>
-             *         <td>1</td>
-             *         <td>2</td>
-             *     </tr>
-             *     <tr>
-             *         <th>CENTER_VERTICAL</th>
-             *         <td>3</td>
-             *         <td>4</td>
-             *         <td>5</td>
-             *     </tr>
-             *     <tr>
-             *         <th>BOTTOM</th>
-             *         <td>6</td>
-             *         <td>7</td>
-             *         <td>8</td>
-             *     </tr>
-             * </table>
-             * <p>
-             * In order to handle the gravity of a window, there are two steps.
-             * First, set the size of the window. Since the window will be positioned at
-             * ScaledLayout, the size factors are determined in a ratio.
-             * Second, set the gravity of the window. CaptionWindowLayout is inherited from
-             * RelativeLayout. Hence, we could set the gravity of its child view, SubtitleView.
-             * </p>
-             * <p>
-             * The gravity of the window is also related to its size. When it should be pushed to
-             * one of the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point
-             * should be a boundary of the window. When it should be pushed
-             * in the horizontal/vertical center of its container, the horizontal/vertical center
-             * point of the window should be the same as the anchor point.
-             * </p>
-             *
-             * @param ccLayout a given CaptionLayout, which contains a safe title area.
-             * @param captionWindow a given CaptionWindow, which stores the construction info of the
-             *                      window.
-             */
-            public void initWindow(CCLayout ccLayout, Cea708CCParser.CaptionWindow captionWindow) {
-                if (mCCLayout != ccLayout) {
-                    if (mCCLayout != null) {
-                        mCCLayout.removeOnLayoutChangeListener(this);
-                    }
-                    mCCLayout = ccLayout;
-                    mCCLayout.addOnLayoutChangeListener(this);
-                    updateWidestChar();
-                }
-
-                // Both anchor vertical and horizontal indicates the position cell number of
-                // the window.
-                float scaleRow = (float) captionWindow.anchorVertical
-                        / (captionWindow.relativePositioning
-                                ? ANCHOR_RELATIVE_POSITIONING_MAX : ANCHOR_VERTICAL_MAX);
-
-                // Assumes it has a wide aspect ratio track.
-                float scaleCol = (float) captionWindow.anchorHorizontal
-                        / (captionWindow.relativePositioning ? ANCHOR_RELATIVE_POSITIONING_MAX
-                                : ANCHOR_HORIZONTAL_16_9_MAX);
-
-                // The range of scaleRow/Col need to be verified to be in [0, 1].
-                // Otherwise a RuntimeException will be raised in ScaledLayout.
-                if (scaleRow < 0 || scaleRow > 1) {
-                    Log.i(TAG, "The vertical position of the anchor point should be at the "
-                            + "range of 0 and 1 but " + scaleRow);
-                    scaleRow = Math.max(0, Math.min(scaleRow, 1));
-                }
-                if (scaleCol < 0 || scaleCol > 1) {
-                    Log.i(TAG, "The horizontal position of the anchor point should be at the "
-                            + "range of 0 and 1 but " + scaleCol);
-                    scaleCol = Math.max(0, Math.min(scaleCol, 1));
-                }
-                int gravity = Gravity.CENTER;
-                int horizontalMode = captionWindow.anchorId % ANCHOR_MODE_DIVIDER;
-                int verticalMode = captionWindow.anchorId / ANCHOR_MODE_DIVIDER;
-                float scaleStartRow = 0;
-                float scaleEndRow = 1;
-                float scaleStartCol = 0;
-                float scaleEndCol = 1;
-                switch (horizontalMode) {
-                    case ANCHOR_HORIZONTAL_MODE_LEFT:
-                        gravity = Gravity.LEFT;
-                        mCCView.setAlignment(Alignment.ALIGN_NORMAL);
-                        scaleStartCol = scaleCol;
-                        break;
-                    case ANCHOR_HORIZONTAL_MODE_CENTER:
-                        float gap = Math.min(1 - scaleCol, scaleCol);
-
-                        // Since all TV sets use left text alignment instead of center text
-                        // alignment for this case, we follow the industry convention if possible.
-                        int columnCount = captionWindow.columnCount + 1;
-                        columnCount = Math.min(getScreenColumnCount(), columnCount);
-                        StringBuilder widestTextBuilder = new StringBuilder();
-                        for (int i = 0; i < columnCount; ++i) {
-                            widestTextBuilder.append(mWidestChar);
-                        }
-                        Paint paint = new Paint();
-                        paint.setTypeface(mCaptionStyle.getTypeface());
-                        paint.setTextSize(mTextSize);
-                        float maxWindowWidth = paint.measureText(widestTextBuilder.toString());
-                        float halfMaxWidthScale = mCCLayout.getWidth() > 0
-                                ? maxWindowWidth / 2.0f / (mCCLayout.getWidth() * 0.8f) : 0.0f;
-                        if (halfMaxWidthScale > 0f && halfMaxWidthScale < scaleCol) {
-                            // Calculate the expected max window size based on the column count of
-                            // the caption window multiplied by average alphabets char width,
-                            // then align the left side of the window with the left side of
-                            // the expected max window.
-                            gravity = Gravity.LEFT;
-                            mCCView.setAlignment(Alignment.ALIGN_NORMAL);
-                            scaleStartCol = scaleCol - halfMaxWidthScale;
-                            scaleEndCol = 1.0f;
-                        } else {
-                            // The gap will be the minimum distance value of the distances from both
-                            // horizontal end points to the anchor point.
-                            // If scaleCol <= 0.5, the range of scaleCol is
-                            // [0, the anchor point * 2].
-                            // If scaleCol > 0.5, the range of scaleCol is
-                            // [(1 - the anchor point) * 2, 1].
-                            // The anchor point is located at the horizontal center of the window in
-                            // both cases.
-                            gravity = Gravity.CENTER_HORIZONTAL;
-                            mCCView.setAlignment(Alignment.ALIGN_CENTER);
-                            scaleStartCol = scaleCol - gap;
-                            scaleEndCol = scaleCol + gap;
-                        }
-                        break;
-                    case ANCHOR_HORIZONTAL_MODE_RIGHT:
-                        gravity = Gravity.RIGHT;
-                        // TODO: Alignment.ALIGN_RIGHT is hidden. Implement setAlignment()
-                        // in a different way.
-                        // mCCView.setAlignment(Alignment.ALIGN_RIGHT);
-                        scaleEndCol = scaleCol;
-                        break;
-                }
-                switch (verticalMode) {
-                    case ANCHOR_VERTICAL_MODE_TOP:
-                        gravity |= Gravity.TOP;
-                        scaleStartRow = scaleRow;
-                        break;
-                    case ANCHOR_VERTICAL_MODE_CENTER:
-                        gravity |= Gravity.CENTER_VERTICAL;
-
-                        // See the above comment.
-                        float gap = Math.min(1 - scaleRow, scaleRow);
-                        scaleStartRow = scaleRow - gap;
-                        scaleEndRow = scaleRow + gap;
-                        break;
-                    case ANCHOR_VERTICAL_MODE_BOTTOM:
-                        gravity |= Gravity.BOTTOM;
-                        scaleEndRow = scaleRow;
-                        break;
-                }
-                mCCLayout.addOrUpdateViewToSafeTitleArea(this,
-                        mCCLayout.new ScaledLayoutParams(
-                                scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
-                setCaptionWindowId(captionWindow.id);
-                setRowLimit(captionWindow.rowCount);
-                setGravity(gravity);
-                if (captionWindow.visible) {
-                    show();
-                } else {
-                    hide();
-                }
-            }
-
-            @Override
-            public void onLayoutChange(
-                    View v, int left, int top, int right, int bottom, int oldLeft,
-                    int oldTop, int oldRight, int oldBottom) {
-                int width = right - left;
-                int height = bottom - top;
-                if (width != mLastCaptionLayoutWidth || height != mLastCaptionLayoutHeight) {
-                    mLastCaptionLayoutWidth = width;
-                    mLastCaptionLayoutHeight = height;
-                    updateTextSize();
-                }
-            }
-
-            private void updateWidestChar() {
-                Paint paint = new Paint();
-                paint.setTypeface(mCaptionStyle.getTypeface());
-                Charset latin1 = Charset.forName("ISO-8859-1");
-                float widestCharWidth = 0f;
-                for (int i = 0; i < 256; ++i) {
-                    String ch = new String(new byte[]{(byte) i}, latin1);
-                    float charWidth = paint.measureText(ch);
-                    if (widestCharWidth < charWidth) {
-                        widestCharWidth = charWidth;
-                        mWidestChar = ch;
-                    }
-                }
-                updateTextSize();
-            }
-
-            private void updateTextSize() {
-                if (mCCLayout == null) return;
-
-                // Calculate text size based on the max window size.
-                StringBuilder widestTextBuilder = new StringBuilder();
-                int screenColumnCount = getScreenColumnCount();
-                for (int i = 0; i < screenColumnCount; ++i) {
-                    widestTextBuilder.append(mWidestChar);
-                }
-                String widestText = widestTextBuilder.toString();
-                Paint paint = new Paint();
-                paint.setTypeface(mCaptionStyle.getTypeface());
-                float startFontSize = 0f;
-                float endFontSize = 255f;
-                while (startFontSize < endFontSize) {
-                    float testTextSize = (startFontSize + endFontSize) / 2f;
-                    paint.setTextSize(testTextSize);
-                    float width = paint.measureText(widestText);
-                    if (mCCLayout.getWidth() * 0.8f > width) {
-                        startFontSize = testTextSize + 0.01f;
-                    } else {
-                        endFontSize = testTextSize - 0.01f;
-                    }
-                }
-                mTextSize = endFontSize * mFontScale;
-                mCCView.setTextSize(mTextSize);
-            }
-
-            private int getScreenColumnCount() {
-                // Assume it has a wide aspect ratio track.
-                return MAX_COLUMN_COUNT_16_9;
-            }
-
-            public void removeFromCaptionView() {
-                if (mCCLayout != null) {
-                    mCCLayout.removeViewFromSafeTitleArea(this);
-                    mCCLayout.removeOnLayoutChangeListener(this);
-                    mCCLayout = null;
-                }
-            }
-
-            public void appendText(String text) {
-                updateText(text, true);
-            }
-
-            public void clearText() {
-                mBuilder.clear();
-                mCCView.setText("");
-            }
-
-            private void updateText(String text, boolean appended) {
-                if (!appended) {
-                    mBuilder.clear();
-                }
-                if (text != null && text.length() > 0) {
-                    int length = mBuilder.length();
-                    mBuilder.append(text);
-                    for (CharacterStyle characterStyle : mCharacterStyles) {
-                        mBuilder.setSpan(characterStyle, length, mBuilder.length(),
-                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                }
-                String[] lines = TextUtils.split(mBuilder.toString(), "\n");
-
-                // Truncate text not to exceed the row limit.
-                // Plus one here since the range of the rows is [0, mRowLimit].
-                String truncatedText = TextUtils.join("\n", Arrays.copyOfRange(
-                        lines, Math.max(0, lines.length - (mRowLimit + 1)), lines.length));
-                mBuilder.delete(0, mBuilder.length() - truncatedText.length());
-
-                // Trim the buffer first then set text to CCView.
-                int start = 0, last = mBuilder.length() - 1;
-                int end = last;
-                while ((start <= end) && (mBuilder.charAt(start) <= ' ')) {
-                    ++start;
-                }
-                while ((end >= start) && (mBuilder.charAt(end) <= ' ')) {
-                    --end;
-                }
-                if (start == 0 && end == last) {
-                    mCCView.setText(mBuilder);
-                } else {
-                    SpannableStringBuilder trim = new SpannableStringBuilder();
-                    trim.append(mBuilder);
-                    if (end < last) {
-                        trim.delete(end + 1, last + 1);
-                    }
-                    if (start > 0) {
-                        trim.delete(0, start);
-                    }
-                    mCCView.setText(trim);
-                }
-            }
-
-            public void setRowLimit(int rowLimit) {
-                if (rowLimit < 0) {
-                    throw new IllegalArgumentException("A rowLimit should have a positive number");
-                }
-                mRowLimit = rowLimit;
-            }
-        }
-
-        class CCView extends SubtitleView {
-            CCView(Context context) {
-                this(context, null);
-            }
-
-            CCView(Context context, AttributeSet attrs) {
-                this(context, attrs, 0);
-            }
-
-            CCView(Context context, AttributeSet attrs, int defStyleAttr) {
-                super(context, attrs, defStyleAttr);
-            }
-
-            void setCaptionStyle(CaptionStyle style) {
-                if (Build.VERSION.SDK_INT >= 21) {
-                    if (style.hasForegroundColor()) {
-                        setForegroundColor(style.foregroundColor);
-                    }
-                    if (style.hasBackgroundColor()) {
-                        setBackgroundColor(style.backgroundColor);
-                    }
-                    if (style.hasEdgeType()) {
-                        setEdgeType(style.edgeType);
-                    }
-                    if (style.hasEdgeColor()) {
-                        setEdgeColor(style.edgeColor);
-                    }
-                }
-                setTypeface(style.getTypeface());
-            }
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java b/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java
deleted file mode 100644
index c6435af..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java
+++ /dev/null
@@ -1,160 +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.media2.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.CaptioningManager;
-import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
-
-/**
- * Abstract widget class to render a closed caption track.
- */
-abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
-
-    interface ClosedCaptionLayout {
-        void setCaptionStyle(CaptionStyle captionStyle);
-        void setFontScale(float scale);
-    }
-
-    /** Captioning manager, used to obtain and track caption properties. */
-    private CaptioningManager mManager;
-    private CaptioningChangeListener mCaptioningListener;
-
-    /** Current caption style. */
-    protected CaptionStyle mCaptionStyle;
-
-    /** Callback for rendering changes. */
-    protected OnChangedListener mListener;
-
-    /** Concrete layout of CC. */
-    protected ClosedCaptionLayout mClosedCaptionLayout;
-
-    /** Whether a caption style change listener is registered. */
-    private boolean mHasChangeListener;
-
-    ClosedCaptionWidget(Context context) {
-        this(context, null);
-    }
-
-    ClosedCaptionWidget(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        // Cannot render text over video when layer type is hardware.
-        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
-
-        mCaptioningListener = new CaptioningChangeListener() {
-            @Override
-            public void onUserStyleChanged(CaptioningManager.CaptionStyle userStyle) {
-                mCaptionStyle = new CaptionStyle(userStyle);
-                mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
-            }
-
-            @Override
-            public void onFontScaleChanged(float fontScale) {
-                mClosedCaptionLayout.setFontScale(fontScale);
-            }
-        };
-        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
-        mCaptionStyle = new CaptionStyle(mManager.getUserStyle());
-        float fontScale = mManager.getFontScale();
-
-        mClosedCaptionLayout = createCaptionLayout(context);
-        mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
-        mClosedCaptionLayout.setFontScale(fontScale);
-        addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
-                LayoutParams.MATCH_PARENT);
-
-        requestLayout();
-    }
-
-    public abstract ClosedCaptionLayout createCaptionLayout(Context context);
-
-    @Override
-    public void setOnChangedListener(OnChangedListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public void setSize(int width, int height) {
-        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-
-        measure(widthSpec, heightSpec);
-        layout(0, 0, width, height);
-    }
-
-    @Override
-    public void setVisible(boolean visible) {
-        if (visible) {
-            setVisibility(View.VISIBLE);
-        } else {
-            setVisibility(View.GONE);
-        }
-
-        manageChangeListener();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        manageChangeListener();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        manageChangeListener();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
-    }
-
-    /**
-     * Manages whether this renderer is listening for caption style changes.
-     */
-    private void manageChangeListener() {
-        final boolean needsListener =
-                this.isAttachedToWindow() && getVisibility() == View.VISIBLE;
-        if (mHasChangeListener != needsListener) {
-            mHasChangeListener = needsListener;
-
-            if (needsListener) {
-                mManager.addCaptioningChangeListener(mCaptioningListener);
-            } else {
-                mManager.removeCaptioningChangeListener(mCaptioningListener);
-            }
-        }
-    }
-}
-
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
deleted file mode 100644
index 2cf20b4..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
+++ /dev/null
@@ -1,2362 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.PopupWindow;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A View that contains the controls for {@link androidx.media2.session.MediaController} or {@link
- * androidx.media2.common.SessionPlayer}. It provides a wide range of buttons that serve the
- * following functions: play/pause, rewind/fast-forward, skip to next/previous, select subtitle
- * track, enter/exit full screen mode, select audio track, and adjust playback speed.
- *
- * <p>For simple use cases not requiring communication with {@link
- * androidx.media2.session.MediaSession}, apps need to create a {@link
- * androidx.media2.common.SessionPlayer} (e.g. {@link androidx.media2.player.MediaPlayer}) and set
- * it to this view by calling {@link #setPlayer}. For more advanced use cases that require {@link
- * androidx.media2.session.MediaSession} (e.g. handling media key events, integrating with other
- * androidx.media2.session.MediaSession apps as Assistant), apps need to create a {@link
- * androidx.media2.session.MediaController} attached to the {@link
- * androidx.media2.session.MediaSession} and set it to this view by calling {@link
- * #setMediaController}.
- *
- * <p>The easiest way to use a MediaControlView is by creating a {@link VideoView}, which internally
- * creates a MediaControlView instance and handles all the commands from buttons inside
- * MediaControlView. It is also possible to create a MediaControlView programmatically and add it to
- * a custom video view. For more information, refer to {@link VideoView}.
- *
- * <p>By default, each button in the MediaControlView is visible only when its corresponding {@link
- * androidx.media2.session.SessionCommand} is included in the active {@link
- * androidx.media2.session.SessionCommandGroup}. For more details, refer to {@link
- * androidx.media2.session.MediaSession#setAllowedCommands}.
- *
- * <p>
- *
- * <h3>UI transitions</h3>
- *
- * The UI of an app can be in one of three modes:
- *
- * <ul>
- *   <li>In <b>full</b> mode all the views, such as progress bar, title, transport controls, and
- *       other icons are visible.
- *   <li>In <b>progress-bar only</b> mode the progress bar is the only visible element. The title,
- *       transport controls, and other icons are hidden.
- *   <li>In <b>None</b> mode all the views are hidden.
- * </ul>
- *
- * When the UI mode changes, MediaControlView animates the transition. The animation does not start
- * immediately, there is a default delay interval of 2000ms before the animation begins. You can
- * change this interval by calling {@link VideoView#setMediaControlView(MediaControlView, long)}.
- *
- * <p>User actions can change the scheduled transition during the delay interval according to the
- * following logic:
- *
- * <ol>
- *   <li>In Full mode
- *       <ul>
- *         <li>If a touch/trackball event is received during the interval, the UI changes to None
- *             mode.
- *         <li>If no touch/trackball event is received during the interval, the UI changes to
- *             progress-bar only mode.
- *       </ul>
- *   <li>In Progress-bar only mode
- *       <ul>
- *         <li>If a touch/trackball event is received, the UI changes to Full mode.
- *         <li>If no touch/trackball event is received, the UI changes to None mode.
- *       </ul>
- *   <li>In None mode, if a touch/trackball event is received, the UI changes to Full mode.
- * </ol>
- *
- * All touch/trackballs events are ignored while the system is animating the change between modes.
- *
- * <p>
- *
- * <h3>Customization</h3>
- *
- * The following customizations are supported:
- *
- * <ul>
- *   <li>Set focus to the play/pause button by calling {@link #requestPlayButtonFocus()}.
- *   <li>Set full screen behavior by calling {@link #setOnFullScreenListener(OnFullScreenListener)}.
- *       Calling this method will also show the full screen button.
- * </ul>
- *
- * <p>
- *
- * <h3>Displaying metadata</h3>
- *
- * MediaControlView supports displaying metadata by calling {@link
- * androidx.media2.common.MediaItem#setMetadata(androidx.media2.common.MediaMetadata)}. Metadata
- * display is different for two different media types: video (with or without sound) and audio(sound
- * only, no video)
- *
- * <p>The following table shows the metadata displayed on VideoView and the default values assigned
- * if the keys are not set:
- *
- * <table>
- *     <tr><th>Key</th><th>Default</th></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_TITLE}</td>
- *     <td>{@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text}</td></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_ARTIST}</td>
- *     <td>{@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text}</td></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_ALBUM_ART}</td>
- *     <td>{@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image}</td></tr>
- *     </table>
- *
- * <p>For video media, {@link androidx.media2.common.MediaMetadata#METADATA_KEY_TITLE} metadata is
- * supported. If the value is not set, the following default value will be shown: {@link
- * androidx.media2.widget.R.string#mcv2_non_music_title_unknown_text}
- *
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class MediaControlView extends MediaViewGroup {
-    private static final String TAG = "MediaControlView";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
-    private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
-    private static final int SETTINGS_MODE_SUBTITLE_TRACK = 2;
-    private static final int SETTINGS_MODE_MAIN = 3;
-    private static final int PLAYBACK_SPEED_1x_INDEX = 3;
-
-    private static final int SIZE_TYPE_UNDEFINED = -1;
-    private static final int SIZE_TYPE_EMBEDDED = 0;
-    private static final int SIZE_TYPE_FULL = 1;
-    private static final int SIZE_TYPE_MINIMAL = 2;
-
-    private static final int PLAY_BUTTON_PAUSE = 0;
-    private static final int PLAY_BUTTON_PLAY = 1;
-    private static final int PLAY_BUTTON_REPLAY = 2;
-
-    // Int for defining the UX state where all the views (TitleBar, ProgressBar, BottomBar) are
-    // all visible.
-    private static final int UX_STATE_ALL_VISIBLE = 0;
-    // Int for defining the UX state where only the ProgressBar view is visible.
-    private static final int UX_STATE_ONLY_PROGRESS_VISIBLE = 1;
-    // Int for defining the UX state where none of the views are visible.
-    private static final int UX_STATE_NONE_VISIBLE = 2;
-    // Int for defining the UX state where the views are being animated (shown or hidden).
-    private static final int UX_STATE_ANIMATING = 3;
-
-    private static final long DISABLE_DELAYED_ANIMATION = -1;
-    private static final long DEFAULT_DELAYED_ANIMATION_INTERVAL_MS = 2000;
-    private static final long DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
-    private static final long REWIND_TIME_MS = 10000;
-    private static final long FORWARD_TIME_MS = 30000;
-    private static final long AD_SKIP_WAIT_TIME_MS = 5000;
-    private static final long HIDE_TIME_MS = 250;
-    private static final long SHOW_TIME_MS = 250;
-    private static final int MAX_PROGRESS = 1000;
-    private static final int MAX_SCALE_LEVEL = 10000;
-    private static final int RESOURCE_NON_EXISTENT = -1;
-    private static final int SEEK_POSITION_NOT_SET = -1;
-    private static final String RESOURCE_EMPTY = "";
-
-    private boolean mIsAttachedToVideoView = false;
-
-    Resources mResources;
-    PlayerWrapper mPlayer;
-    OnFullScreenListener mOnFullScreenListener;
-    private AccessibilityManager mAccessibilityManager;
-    private int mEmbeddedSettingsItemWidth;
-    private int mFullSettingsItemWidth;
-    private int mSettingsItemHeight;
-    private int mSettingsWindowMargin;
-    int mSettingsMode;
-    int mSelectedSubtitleTrackIndex;
-    int mSelectedAudioTrackIndex;
-    int mSelectedSpeedIndex;
-    int mSizeType = SIZE_TYPE_UNDEFINED;
-    int mUxState;
-    long mDuration;
-    long mDelayedAnimationIntervalMs;
-    long mCurrentSeekPosition;
-    long mNextSeekPosition;
-    boolean mDragging;
-    boolean mIsFullScreen;
-    boolean mIsShowingReplayButton;
-    boolean mOverflowIsShowing;
-    boolean mSeekAvailable;
-    boolean mIsAdvertisement;
-    boolean mNeedToHideBars;
-    boolean mNeedToShowBars;
-    boolean mWasPlaying;
-
-    private SparseArray<View> mTransportControlsMap = new SparseArray<>();
-
-    // Relating to Title Bar View
-    private View mTitleBar;
-    private TextView mTitleView;
-    private View mAdExternalLink;
-
-    // Relating to Center View
-    ViewGroup mCenterView;
-    private View mCenterViewBackground;
-    private View mEmbeddedTransportControls;
-    private View mMinimalTransportControls;
-
-    // Relating to Minimal Fullscreen View
-    ViewGroup mMinimalFullScreenView;
-    ImageButton mMinimalFullScreenButton;
-
-    // Relating to Progress Bar View
-    private ViewGroup mProgressBar;
-    SeekBar mProgress;
-
-    // Relating to Bottom Bar View
-    private View mBottomBarBackground;
-
-    // Relating to Bottom Bar Left View
-    private ViewGroup mBottomBarLeft;
-    private View mFullTransportControls;
-    private ViewGroup mTimeView;
-    private TextView mEndTime;
-    TextView mCurrentTime;
-    private TextView mAdSkipView;
-    private StringBuilder mFormatBuilder;
-    private Formatter mFormatter;
-
-    // Relating to Bottom Bar Right View
-    ViewGroup mBasicControls;
-    ViewGroup mExtraControls;
-    ImageButton mSubtitleButton;
-    ImageButton mFullScreenButton;
-    private TextView mAdRemainingView;
-
-    // Relating to Settings List View
-    private ListView mSettingsListView;
-    private PopupWindow mSettingsWindow;
-    SettingsAdapter mSettingsAdapter;
-    SubSettingsAdapter mSubSettingsAdapter;
-    private List<String> mSettingsMainTextsList;
-    List<String> mSettingsSubTextsList;
-    private List<Integer> mSettingsIconIdsList;
-    List<String> mSubtitleDescriptionsList;
-    int mVideoTrackCount;
-    List<androidx.media2.common.SessionPlayer.TrackInfo> mAudioTracks = new ArrayList<>();
-    List<androidx.media2.common.SessionPlayer.TrackInfo> mSubtitleTracks = new ArrayList<>();
-    List<String> mAudioTrackDescriptionList;
-    List<String> mPlaybackSpeedTextList;
-    List<Integer> mPlaybackSpeedMultBy100List;
-    int mCustomPlaybackSpeedIndex;
-
-    AnimatorSet mHideMainBarsAnimator;
-    AnimatorSet mHideProgressBarAnimator;
-    AnimatorSet mHideAllBarsAnimator;
-    AnimatorSet mShowMainBarsAnimator;
-    AnimatorSet mShowAllBarsAnimator;
-    ValueAnimator mOverflowShowAnimator;
-    ValueAnimator mOverflowHideAnimator;
-
-    public MediaControlView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public MediaControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public MediaControlView(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        mResources = context.getResources();
-        inflate(context, R.layout.media2_widget_media_controller, this);
-        initControllerView();
-        mDelayedAnimationIntervalMs = DEFAULT_DELAYED_ANIMATION_INTERVAL_MS;
-        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
-    }
-
-    /**
-     * Sets {@link androidx.media2.session.MediaController} to control playback with this view.
-     * Setting a androidx.media2.session.MediaController will unset any
-     * androidx.media2.session.MediaController or androidx.media2.common.SessionPlayer that was
-     * previously set.
-     *
-     * <p>It will throw {@link IllegalStateException} if this MediaControlView belongs to a {@link
-     * VideoView} by {@link androidx.media2.widget.R.attr#enableControlView} or by {@link
-     * VideoView#setMediaControlView}. Use {@link VideoView#setMediaController} instead.
-     *
-     * <p>Note that MediaControlView allows controlling playback through its UI components, but
-     * calling the corresponding methods (e.g. {@link
-     * androidx.media2.session.MediaController#play()}, {@link
-     * androidx.media2.session.MediaController#pause()}) will work as well.
-     *
-     * @param controller the controller
-     * @see #setPlayer
-     */
-    public void setMediaController(@NonNull androidx.media2.session.MediaController controller) {
-        if (controller == null) {
-            throw new NullPointerException("controller must not be null");
-        }
-        if (mIsAttachedToVideoView) {
-            throw new IllegalStateException("It's attached to VideoView. Use VideoView's method.");
-        }
-        setMediaControllerInternal(controller);
-    }
-
-    void setMediaControllerInternal(@NonNull androidx.media2.session.MediaController controller) {
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-        mPlayer = new PlayerWrapper(controller, ContextCompat.getMainExecutor(getContext()),
-                new PlayerCallback());
-        if (this.isAttachedToWindow()) {
-            mPlayer.attachCallback();
-        }
-    }
-
-    /**
-     * Sets {@link androidx.media2.common.SessionPlayer} to control playback with this view. Setting
-     * a androidx.media2.common.SessionPlayer will unset any androidx.media2.session.MediaController
-     * or androidx.media2.common.SessionPlayer that was previously set.
-     *
-     * <p>It will throw {@link IllegalStateException} if this MediaControlView belongs to a {@link
-     * VideoView} by {@link androidx.media2.widget.R.attr#enableControlView} or by {@link
-     * VideoView#setMediaControlView}. Use {@link VideoView#setPlayer} instead.
-     *
-     * <p>Note that MediaControlView allows controlling playback through its UI components, but
-     * calling the corresponding methods (e.g. {@link androidx.media2.common.SessionPlayer#play()},
-     * {@link androidx.media2.common.SessionPlayer#pause()}) will work as well.
-     *
-     * @param player the player
-     * @see #setMediaController
-     */
-    public void setPlayer(@NonNull androidx.media2.common.SessionPlayer player) {
-        if (player == null) {
-            throw new NullPointerException("player must not be null");
-        }
-        if (mIsAttachedToVideoView) {
-            throw new IllegalStateException("It's attached to VideoView. Use VideoView's method.");
-        }
-        setPlayerInternal(player);
-    }
-
-    void setPlayerInternal(@NonNull androidx.media2.common.SessionPlayer player) {
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-        mPlayer = new PlayerWrapper(player, ContextCompat.getMainExecutor(getContext()),
-                new PlayerCallback());
-        if (this.isAttachedToWindow()) {
-            mPlayer.attachCallback();
-        }
-    }
-
-    /**
-     * Sets a listener to be called when the fullscreen mode should be changed.
-     * A non-null listener needs to be set in order to display the fullscreen button.
-     *
-     * @param listener The listener to be called. A value of <code>null</code> removes any
-     * existing listener and hides the fullscreen button.
-     */
-    public void setOnFullScreenListener(@Nullable OnFullScreenListener listener) {
-        if (listener == null) {
-            mOnFullScreenListener = null;
-            mFullScreenButton.setVisibility(View.GONE);
-        } else {
-            mOnFullScreenListener = listener;
-            mFullScreenButton.setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     *  Requests focus for the play/pause button.
-     */
-    public void requestPlayButtonFocus() {
-        ImageButton button = findControlButton(mSizeType, R.id.pause);
-        if (button != null) {
-            button.requestFocus();
-        }
-    }
-
-    /**
-     * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
-     * Application should handle the fullscreen mode accordingly.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public interface OnFullScreenListener {
-        /**
-         * Called to indicate a fullscreen mode change.
-         */
-        void onFullScreen(@NonNull View view, boolean fullScreen);
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        // Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage.
-        return "androidx.media2.widget.MediaControlView";
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (mPlayer == null) {
-            return super.onTouchEvent(ev);
-        }
-        if (ev.getAction() == MotionEvent.ACTION_UP) {
-            if (!isCurrentItemMusic() || mSizeType != SIZE_TYPE_FULL) {
-                if (mUxState == UX_STATE_ALL_VISIBLE) {
-                    hideMediaControlView();
-                } else {
-                    showMediaControlView();
-                }
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent ev) {
-        if (mPlayer == null) {
-            return super.onTrackballEvent(ev);
-        }
-        if (ev.getAction() == MotionEvent.ACTION_UP) {
-            if (!isCurrentItemMusic() || mSizeType != SIZE_TYPE_FULL) {
-                if (mUxState == UX_STATE_ALL_VISIBLE) {
-                    hideMediaControlView();
-                } else {
-                    showMediaControlView();
-                }
-            }
-        }
-        return true;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
-        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
-
-        int childWidth = width - getPaddingLeft() - getPaddingRight();
-        int childHeight = height - getPaddingTop() - getPaddingBottom();
-        int childState = 0;
-
-        if (childWidth < 0) {
-            childWidth = 0;
-            childState |= View.MEASURED_STATE_TOO_SMALL;
-        }
-        if (childHeight < 0) {
-            childHeight = 0;
-            childState |= (View.MEASURED_STATE_TOO_SMALL >> View.MEASURED_HEIGHT_STATE_SHIFT);
-        }
-
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
-            LayoutParams lp = child.getLayoutParams();
-
-            int childWidthSpec;
-            if (lp.width == LayoutParams.MATCH_PARENT) {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
-            } else if (lp.width == LayoutParams.WRAP_CONTENT) {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.UNSPECIFIED);
-            } else {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
-            }
-
-            int childHeightSpec;
-            if (lp.height == LayoutParams.MATCH_PARENT) {
-                childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
-            } else if (lp.height == LayoutParams.WRAP_CONTENT) {
-                childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.UNSPECIFIED);
-            } else {
-                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
-            }
-
-            child.measure(childWidthSpec, childHeightSpec);
-            childState |= child.getMeasuredState();
-        }
-
-        setMeasuredDimension(
-                resolveSizeAndState(width, widthMeasureSpec, childState),
-                resolveSizeAndState(height, heightMeasureSpec,
-                        childState << View.MEASURED_HEIGHT_STATE_SHIFT));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final int width = right - left - getPaddingLeft() - getPaddingRight();
-        final int height = bottom - top - getPaddingTop() - getPaddingBottom();
-
-        final int fullWidth = mBottomBarLeft.getMeasuredWidth()
-                + mTimeView.getMeasuredWidth()
-                + mBasicControls.getMeasuredWidth();
-        final int fullHeight = mTitleBar.getMeasuredHeight()
-                + mProgressBar.getMeasuredHeight()
-                + mBottomBarBackground.getMeasuredHeight();
-
-        final int embeddedWidth = mTimeView.getMeasuredWidth()
-                + mBasicControls.getMeasuredWidth();
-        final int embeddedHeight = mTitleBar.getMeasuredHeight()
-                + mEmbeddedTransportControls.getMeasuredHeight()
-                + mProgressBar.getMeasuredHeight()
-                + mBottomBarBackground.getMeasuredHeight();
-
-        int sizeType;
-        if (mIsAdvertisement || (fullWidth <= width && fullHeight <= height)) {
-            sizeType = SIZE_TYPE_FULL;
-        } else if (embeddedWidth <= width && embeddedHeight <= height) {
-            sizeType = SIZE_TYPE_EMBEDDED;
-        } else {
-            sizeType = SIZE_TYPE_MINIMAL;
-        }
-
-        if (mSizeType != sizeType) {
-            mSizeType = sizeType;
-            updateLayoutForSizeChange(sizeType);
-        }
-
-        mTitleBar.setVisibility(
-                sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-        mCenterViewBackground.setVisibility(
-                sizeType != SIZE_TYPE_FULL ? View.VISIBLE : View.INVISIBLE);
-        mEmbeddedTransportControls.setVisibility(
-                sizeType == SIZE_TYPE_EMBEDDED ? View.VISIBLE : View.INVISIBLE);
-        mMinimalTransportControls.setVisibility(
-                sizeType == SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-        mBottomBarBackground.setVisibility(
-                sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-        mBottomBarLeft.setVisibility(
-                sizeType == SIZE_TYPE_FULL ? View.VISIBLE : View.INVISIBLE);
-        mTimeView.setVisibility(
-                sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-        mBasicControls.setVisibility(
-                sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-        mMinimalFullScreenButton.setVisibility(
-                sizeType == SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
-
-        final int childLeft = getPaddingLeft();
-        final int childRight = childLeft + width;
-        final int childTop = getPaddingTop();
-        final int childBottom = childTop + height;
-
-        layoutChild(mTitleBar,
-                childLeft,
-                childTop);
-        layoutChild(mCenterView,
-                childLeft,
-                childTop);
-        layoutChild(mBottomBarBackground,
-                childLeft,
-                childBottom - mBottomBarBackground.getMeasuredHeight());
-        layoutChild(mBottomBarLeft,
-                childLeft,
-                childBottom - mBottomBarLeft.getMeasuredHeight());
-        layoutChild(mTimeView,
-                sizeType == SIZE_TYPE_FULL
-                        ? childRight - mBasicControls.getMeasuredWidth()
-                                - mTimeView.getMeasuredWidth()
-                        : childLeft,
-                childBottom - mTimeView.getMeasuredHeight());
-        layoutChild(mBasicControls,
-                childRight - mBasicControls.getMeasuredWidth(),
-                childBottom - mBasicControls.getMeasuredHeight());
-        layoutChild(mExtraControls,
-                childRight,
-                childBottom - mExtraControls.getMeasuredHeight());
-        layoutChild(mProgressBar,
-                childLeft,
-                sizeType == SIZE_TYPE_MINIMAL
-                        ? childBottom - mProgressBar.getMeasuredHeight()
-                        : childBottom - mProgressBar.getMeasuredHeight()
-                                - mResources.getDimensionPixelSize(
-                                        R.dimen.media2_widget_custom_progress_margin_bottom));
-        layoutChild(mMinimalFullScreenView,
-                childLeft,
-                childBottom - mMinimalFullScreenView.getMeasuredHeight());
-    }
-
-    private void layoutChild(View child, int left, int top) {
-        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
-    }
-
-    @Override
-    void onVisibilityAggregatedCompat(boolean isVisible) {
-        super.onVisibilityAggregatedCompat(isVisible);
-        if (mPlayer == null) return;
-        if (isVisible) {
-            removeCallbacks(mUpdateProgress);
-            post(mUpdateProgress);
-        } else {
-            removeCallbacks(mUpdateProgress);
-        }
-    }
-
-    void setDelayedAnimationInterval(long interval) {
-        mDelayedAnimationIntervalMs = interval;
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        if (mPlayer != null) {
-            mPlayer.attachCallback();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-    }
-
-    void setAttachedToVideoView(boolean attached) {
-        mIsAttachedToVideoView = attached;
-    }
-
-    ///////////////////////////////////////////////////
-    // Protected or private methods
-    ///////////////////////////////////////////////////
-
-    static View inflateLayout(Context context, int resId) {
-        LayoutInflater inflater = (LayoutInflater) context
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        return inflater.inflate(resId, null);
-    }
-
-    private void initControllerView() {
-        // Relating to Title Bar View
-        mTitleBar = findViewById(R.id.title_bar);
-        mTitleView = findViewById(R.id.title_text);
-        mAdExternalLink = findViewById(R.id.ad_external_link);
-
-        // Relating to Center View
-        mCenterView = findViewById(R.id.center_view);
-        mCenterViewBackground = findViewById(R.id.center_view_background);
-        mEmbeddedTransportControls = initTransportControls(R.id.embedded_transport_controls);
-        mMinimalTransportControls = initTransportControls(R.id.minimal_transport_controls);
-
-        // Relating to Minimal Size FullScreen View
-        mMinimalFullScreenView = findViewById(R.id.minimal_fullscreen_view);
-        mMinimalFullScreenButton = findViewById(R.id.minimal_fullscreen);
-        mMinimalFullScreenButton.setOnClickListener(mFullScreenListener);
-
-        // Relating to Progress Bar View
-        mProgressBar = findViewById(R.id.progress_bar);
-        mProgress = findViewById(R.id.progress);
-        mProgress.setOnSeekBarChangeListener(mSeekListener);
-        mProgress.setMax(MAX_PROGRESS);
-        mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
-        mNextSeekPosition = SEEK_POSITION_NOT_SET;
-
-        // Relating to Bottom Bar View
-        mBottomBarBackground = findViewById(R.id.bottom_bar_background);
-
-        // Relating to Bottom Bar Left View
-        mBottomBarLeft = findViewById(R.id.bottom_bar_left);
-        mFullTransportControls = initTransportControls(R.id.full_transport_controls);
-        mTimeView = findViewById(R.id.time);
-        mEndTime = findViewById(R.id.time_end);
-        mCurrentTime = findViewById(R.id.time_current);
-        mAdSkipView = findViewById(R.id.ad_skip_time);
-        mFormatBuilder = new StringBuilder();
-        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
-
-        // Relating to Bottom Bar Right View
-        mBasicControls = findViewById(R.id.basic_controls);
-        mExtraControls = findViewById(R.id.extra_controls);
-        mSubtitleButton = findViewById(R.id.subtitle);
-        mSubtitleButton.setOnClickListener(mSubtitleListener);
-        mFullScreenButton = findViewById(R.id.fullscreen);
-        mFullScreenButton.setOnClickListener(mFullScreenListener);
-        ImageButton overflowShowButton = findViewById(R.id.overflow_show);
-        overflowShowButton.setOnClickListener(mOverflowShowListener);
-        ImageButton overflowHideButton = findViewById(R.id.overflow_hide);
-        overflowHideButton.setOnClickListener(mOverflowHideListener);
-        ImageButton settingsButton = findViewById(R.id.settings);
-        settingsButton.setOnClickListener(mSettingsButtonListener);
-        mAdRemainingView = findViewById(R.id.ad_remaining);
-
-        // Relating to Settings List View
-        initializeSettingsLists();
-        mSettingsListView = (ListView) inflateLayout(getContext(),
-                R.layout.media2_widget_settings_list);
-        mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
-                mSettingsIconIdsList);
-        mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
-        mSettingsListView.setAdapter(mSettingsAdapter);
-        mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
-        mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
-
-        // TransportControlsMap
-        mTransportControlsMap.append(SIZE_TYPE_EMBEDDED, mEmbeddedTransportControls);
-        mTransportControlsMap.append(SIZE_TYPE_FULL, mFullTransportControls);
-        mTransportControlsMap.append(SIZE_TYPE_MINIMAL, mMinimalTransportControls);
-
-        mEmbeddedSettingsItemWidth = mResources.getDimensionPixelSize(
-                R.dimen.media2_widget_embedded_settings_width);
-        mFullSettingsItemWidth = mResources.getDimensionPixelSize(
-                R.dimen.media2_widget_full_settings_width);
-        mSettingsItemHeight = mResources.getDimensionPixelSize(
-                R.dimen.media2_widget_settings_height);
-        mSettingsWindowMargin = mResources.getDimensionPixelSize(
-                R.dimen.media2_widget_settings_offset);
-        mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
-                LayoutParams.WRAP_CONTENT, true);
-        mSettingsWindow.setBackgroundDrawable(new ColorDrawable());
-        mSettingsWindow.setOnDismissListener(mSettingsDismissListener);
-
-        float titleBarHeight = mResources.getDimension(R.dimen.media2_widget_title_bar_height);
-        float progressBarHeight = mResources.getDimension(
-                R.dimen.media2_widget_custom_progress_thumb_size);
-        float bottomBarHeight = mResources.getDimension(R.dimen.media2_widget_bottom_bar_height);
-
-        View[] bottomBarGroup = { mBottomBarBackground, mBottomBarLeft, mTimeView, mBasicControls,
-                mExtraControls, mProgressBar };
-
-        ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
-        fadeOutAnimator.setInterpolator(new LinearInterpolator());
-        fadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float alpha = (float) animation.getAnimatedValue();
-                int scaleLevel = mSizeType == SIZE_TYPE_MINIMAL ? 0 : MAX_SCALE_LEVEL;
-                mProgress.getThumb().setLevel((int) (scaleLevel * alpha));
-
-                mCenterView.setAlpha(alpha);
-                mMinimalFullScreenView.setAlpha(alpha);
-            }
-        });
-        fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mCenterView.setVisibility(View.INVISIBLE);
-                mMinimalFullScreenView.setVisibility(View.INVISIBLE);
-            }
-        });
-
-        ValueAnimator fadeInAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
-        fadeInAnimator.setInterpolator(new LinearInterpolator());
-        fadeInAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float alpha = (float) animation.getAnimatedValue();
-                int scaleLevel = mSizeType == SIZE_TYPE_MINIMAL ? 0 : MAX_SCALE_LEVEL;
-                mProgress.getThumb().setLevel((int) (scaleLevel * alpha));
-
-                mCenterView.setAlpha(alpha);
-                mMinimalFullScreenView.setAlpha(alpha);
-            }
-        });
-        fadeInAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mCenterView.setVisibility(View.VISIBLE);
-                mMinimalFullScreenView.setVisibility(View.VISIBLE);
-            }
-        });
-
-        mHideMainBarsAnimator = new AnimatorSet();
-        mHideMainBarsAnimator.play(fadeOutAnimator)
-                .with(AnimatorUtil.ofTranslationY(0, -titleBarHeight, mTitleBar))
-                .with(AnimatorUtil.ofTranslationYTogether(0, bottomBarHeight, bottomBarGroup));
-        mHideMainBarsAnimator.setDuration(HIDE_TIME_MS);
-        mHideMainBarsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mUxState = UX_STATE_ANIMATING;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mUxState = UX_STATE_ONLY_PROGRESS_VISIBLE;
-                if (mNeedToShowBars) {
-                    post(mShowAllBars);
-                    mNeedToShowBars = false;
-                }
-            }
-        });
-
-        mHideProgressBarAnimator = AnimatorUtil.ofTranslationYTogether(
-                bottomBarHeight, bottomBarHeight + progressBarHeight, bottomBarGroup);
-        mHideProgressBarAnimator.setDuration(HIDE_TIME_MS);
-        mHideProgressBarAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mUxState = UX_STATE_ANIMATING;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mUxState = UX_STATE_NONE_VISIBLE;
-                if (mNeedToShowBars) {
-                    post(mShowAllBars);
-                    mNeedToShowBars = false;
-                }
-            }
-        });
-
-        mHideAllBarsAnimator = new AnimatorSet();
-        mHideAllBarsAnimator.play(fadeOutAnimator)
-                .with(AnimatorUtil.ofTranslationY(0, -titleBarHeight, mTitleBar))
-                .with(AnimatorUtil.ofTranslationYTogether(
-                        0, bottomBarHeight + progressBarHeight, bottomBarGroup));
-        mHideAllBarsAnimator.setDuration(HIDE_TIME_MS);
-        mHideAllBarsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mUxState = UX_STATE_ANIMATING;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mUxState = UX_STATE_NONE_VISIBLE;
-                if (mNeedToShowBars) {
-                    post(mShowAllBars);
-                    mNeedToShowBars = false;
-                }
-            }
-        });
-
-        mShowMainBarsAnimator = new AnimatorSet();
-        mShowMainBarsAnimator.play(fadeInAnimator)
-                .with(AnimatorUtil.ofTranslationY(-titleBarHeight, 0, mTitleBar))
-                .with(AnimatorUtil.ofTranslationYTogether(bottomBarHeight, 0, bottomBarGroup));
-        mShowMainBarsAnimator.setDuration(SHOW_TIME_MS);
-        mShowMainBarsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mUxState = UX_STATE_ANIMATING;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mUxState = UX_STATE_ALL_VISIBLE;
-            }
-        });
-
-        mShowAllBarsAnimator = new AnimatorSet();
-        mShowAllBarsAnimator.play(fadeInAnimator)
-                .with(AnimatorUtil.ofTranslationY(-titleBarHeight, 0, mTitleBar))
-                .with(AnimatorUtil.ofTranslationYTogether(
-                        bottomBarHeight + progressBarHeight, 0, bottomBarGroup));
-        mShowAllBarsAnimator.setDuration(SHOW_TIME_MS);
-        mShowAllBarsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mUxState = UX_STATE_ANIMATING;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mUxState = UX_STATE_ALL_VISIBLE;
-            }
-        });
-
-        mOverflowShowAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
-        mOverflowShowAnimator.setDuration(SHOW_TIME_MS);
-        mOverflowShowAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                animateOverflow((float) animation.getAnimatedValue());
-            }
-        });
-        mOverflowShowAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mExtraControls.setVisibility(View.VISIBLE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBasicControls.setVisibility(View.INVISIBLE);
-
-                findFullSizedControlButton(R.id.ffwd).setVisibility(
-                        mPlayer != null && mPlayer.canSeekForward() ? View.INVISIBLE : View.GONE);
-            }
-        });
-
-        mOverflowHideAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
-        mOverflowHideAnimator.setDuration(SHOW_TIME_MS);
-        mOverflowHideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                animateOverflow((float) animation.getAnimatedValue());
-            }
-        });
-        mOverflowHideAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBasicControls.setVisibility(View.VISIBLE);
-
-                findFullSizedControlButton(R.id.ffwd).setVisibility(
-                        mPlayer != null && mPlayer.canSeekForward() ? View.VISIBLE : View.GONE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mExtraControls.setVisibility(View.GONE);
-            }
-        });
-    }
-
-    final Runnable mUpdateProgress = new Runnable() {
-        @Override
-        public void run() {
-            boolean isShowing = getVisibility() == View.VISIBLE;
-            if (!mDragging && isShowing && mPlayer != null && mPlayer.isPlaying()) {
-                long pos = setProgress();
-                postDelayedRunnable(mUpdateProgress,
-                        DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
-            }
-        }
-    };
-
-    String stringForTime(long timeMs) {
-        long totalSeconds = timeMs / 1000;
-
-        long seconds = totalSeconds % 60;
-        long minutes = (totalSeconds / 60) % 60;
-        long hours = totalSeconds / 3600;
-
-        mFormatBuilder.setLength(0);
-        if (hours > 0) {
-            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
-        } else {
-            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
-        }
-    }
-
-    long setProgress() {
-        ensurePlayerIsNotNull();
-
-        int positionOnProgressBar = 0;
-        long currentPosition = mPlayer.getCurrentPosition();
-        if (currentPosition > mDuration) {
-            currentPosition = mDuration;
-        }
-        if (mDuration > 0) {
-            positionOnProgressBar = (int) (MAX_PROGRESS * currentPosition / mDuration);
-        }
-        if (mProgress != null && currentPosition != mDuration) {
-            mProgress.setProgress(positionOnProgressBar);
-            // If the media is a local file, there is no need to set a buffer, so set secondary
-            // progress to maximum.
-            if (mPlayer.getBufferPercentage() < 0) {
-                mProgress.setSecondaryProgress(MAX_PROGRESS);
-            } else {
-                mProgress.setSecondaryProgress((int) mPlayer.getBufferPercentage() * 10);
-            }
-        }
-
-        if (mEndTime != null) {
-            mEndTime.setText(stringForTime(mDuration));
-        }
-        if (mCurrentTime != null) {
-            mCurrentTime.setText(stringForTime(currentPosition));
-        }
-
-        if (mIsAdvertisement) {
-            // Update the remaining number of seconds until the first 5 seconds of the
-            // advertisement.
-            if (mAdSkipView != null) {
-                if (currentPosition <= AD_SKIP_WAIT_TIME_MS) {
-                    if (mAdSkipView.getVisibility() == View.GONE) {
-                        mAdSkipView.setVisibility(View.VISIBLE);
-                    }
-                    String skipTimeText = mResources.getString(
-                            R.string.MediaControlView_ad_skip_wait_time,
-                            ((AD_SKIP_WAIT_TIME_MS - currentPosition) / 1000 + 1));
-                    mAdSkipView.setText(skipTimeText);
-                } else {
-                    if (mAdSkipView.getVisibility() == View.VISIBLE) {
-                        mAdSkipView.setVisibility(View.GONE);
-                        findFullSizedControlButton(R.id.next).setEnabled(true);
-                        findFullSizedControlButton(R.id.next).clearColorFilter();
-                    }
-                }
-            }
-            // Update the remaining number of seconds of the advertisement.
-            if (mAdRemainingView != null) {
-                long remainingTime =
-                        (mDuration - currentPosition < 0) ? 0 : (mDuration - currentPosition);
-                String remainingTimeText = mResources.getString(
-                        R.string.MediaControlView_ad_remaining_time,
-                        stringForTime(remainingTime));
-                mAdRemainingView.setText(remainingTimeText);
-            }
-        }
-        return currentPosition;
-    }
-
-    void togglePausePlayState() {
-        ensurePlayerIsNotNull();
-
-        if (mPlayer.isPlaying()) {
-            mPlayer.pause();
-            updatePlayButton(PLAY_BUTTON_PLAY);
-        } else {
-            if (mIsShowingReplayButton) {
-                mPlayer.seekTo(0);
-            }
-            mPlayer.play();
-            updatePlayButton(PLAY_BUTTON_PAUSE);
-        }
-    }
-
-    private void showMediaControlView() {
-        if (mUxState == UX_STATE_ANIMATING) {
-            return;
-        }
-        removeCallbacks(mHideMainBars);
-        removeCallbacks(mHideProgressBar);
-        post(mShowAllBars);
-    }
-
-    private void hideMediaControlView() {
-        if (shouldNotHideBars() || mUxState == UX_STATE_ANIMATING) {
-            return;
-        }
-        removeCallbacks(mHideMainBars);
-        removeCallbacks(mHideProgressBar);
-        post(mHideAllBars);
-    }
-
-    final Runnable mShowAllBars = new Runnable() {
-        @Override
-        public void run() {
-            switch (mUxState) {
-                case UX_STATE_NONE_VISIBLE:
-                    mShowAllBarsAnimator.start();
-                    break;
-                case UX_STATE_ONLY_PROGRESS_VISIBLE:
-                    mShowMainBarsAnimator.start();
-                    break;
-                case UX_STATE_ANIMATING:
-                    mNeedToShowBars = true;
-            }
-
-            if (mPlayer.isPlaying()) {
-                postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
-            }
-        }
-    };
-
-    private final Runnable mHideAllBars = new Runnable() {
-        @Override
-        public void run() {
-            if (shouldNotHideBars()) {
-                return;
-            }
-            mHideAllBarsAnimator.start();
-        }
-    };
-
-    Runnable mHideMainBars = new Runnable() {
-        @Override
-        public void run() {
-            if (!mPlayer.isPlaying() || shouldNotHideBars()) {
-                return;
-            }
-            mHideMainBarsAnimator.start();
-            postDelayedRunnable(mHideProgressBar, mDelayedAnimationIntervalMs);
-        }
-    };
-
-    final Runnable mHideProgressBar = new Runnable() {
-        @Override
-        public void run() {
-            if (!mPlayer.isPlaying() || shouldNotHideBars()) {
-                return;
-            }
-            mHideProgressBarAnimator.start();
-        }
-    };
-
-    // There are two scenarios that can trigger the seekbar listener to trigger:
-    //
-    // The first is the user using the touchpad to adjust the position of the
-    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
-    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
-    // We're setting the field "mDragging" to true for the duration of the dragging
-    // session to avoid jumps in the position in case of ongoing playback.
-    //
-    // The second scenario involves the user operating the scroll ball, in this
-    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
-    // we will simply apply the updated position without suspending regular updates.
-    private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
-        @Override
-        public void onStartTrackingTouch(SeekBar bar) {
-            if (mPlayer == null || !mSeekAvailable) {
-                return;
-            }
-
-            mDragging = true;
-
-            // By removing these pending progress messages we make sure
-            // that a) we won't update the progress while the user adjusts
-            // the seekbar and b) once the user is done dragging the thumb
-            // we will post one of these messages to the queue again and
-            // this ensures that there will be exactly one message queued up.
-            removeCallbacks(mUpdateProgress);
-            removeCallbacks(mHideMainBars);
-            removeCallbacks(mHideProgressBar);
-
-            // Check if playback is currently stopped. In this case, update the pause button to
-            // show the play image instead of the replay image.
-            if (mIsShowingReplayButton) {
-                updateReplayButton(false);
-            }
-
-            if (isCurrentMediaItemFromNetwork() && mPlayer.isPlaying()) {
-                mWasPlaying = true;
-                mPlayer.pause();
-            }
-        }
-
-        @Override
-        public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
-            if (mPlayer == null || !mSeekAvailable) {
-                return;
-            }
-            if (!fromUser) {
-                // We're not interested in programmatically generated changes to
-                // the progress bar's position.
-                return;
-            }
-            // Check if progress bar is being dragged since this method may be called after
-            // onStopTrackingTouch() is called.
-            if (mDragging && mDuration > 0) {
-                long newPosition = ((mDuration * progress) / MAX_PROGRESS);
-                // Do not seek if the current media item has a http scheme URL to improve seek
-                // performance.
-                boolean shouldSeekNow = !isCurrentMediaItemFromNetwork();
-                seekTo(newPosition, shouldSeekNow);
-            }
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar bar) {
-            if (mPlayer == null || !mSeekAvailable) {
-                return;
-            }
-            mDragging = false;
-
-            long latestSeekPosition = getLatestSeekPosition();
-            // Reset existing seek positions since we only need to seek to the latest position.
-            if (isCurrentMediaItemFromNetwork()) {
-                mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
-                mNextSeekPosition = SEEK_POSITION_NOT_SET;
-            }
-            seekTo(latestSeekPosition, true);
-
-            if (mWasPlaying) {
-                mWasPlaying = false;
-                mPlayer.play();
-            }
-        }
-    };
-
-    private final OnClickListener mPlayPauseListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-            togglePausePlayState();
-        }
-    };
-
-    private final OnClickListener mRewListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-            removeCallbacks(mUpdateProgress);
-
-            // If replay button is shown, seek to 10 seconds before the end of the media.
-            boolean stoppedWithDuration = mIsShowingReplayButton && mDuration != 0;
-            long currentPosition = stoppedWithDuration ? mDuration : getLatestSeekPosition();
-            long seekPosition = Math.max(currentPosition - REWIND_TIME_MS, 0);
-            seekTo(seekPosition, /* shouldSeekNow= */ true);
-            if (stoppedWithDuration) {
-                updateReplayButton(/* toBeShown */ false);
-            }
-        }
-    };
-
-    private final OnClickListener mFfwdListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-            removeCallbacks(mUpdateProgress);
-
-            long latestSeekPosition = getLatestSeekPosition();
-            seekTo(Math.min(latestSeekPosition + FORWARD_TIME_MS, mDuration), true);
-
-            // Note: In some edge cases, mDuration might be less than actual duration of
-            // the stream. If controller is in playing state, it should not show replay
-            // button even when the seekPosition >= mDuration.
-            if (latestSeekPosition + FORWARD_TIME_MS >= mDuration && !mPlayer.isPlaying()) {
-                updateReplayButton(/* toBeShown */ true);
-            }
-        }
-    };
-
-    private final OnClickListener mNextListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-            mPlayer.skipToNextItem();
-        }
-    };
-
-    private final OnClickListener mPrevListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-            mPlayer.skipToPreviousItem();
-        }
-    };
-
-    private final OnClickListener mSubtitleListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            removeCallbacks(mHideMainBars);
-            removeCallbacks(mHideProgressBar);
-
-            mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
-            mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
-            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex + 1);
-            displaySettingsWindow(mSubSettingsAdapter);
-        }
-    };
-
-    private final OnClickListener mFullScreenListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mOnFullScreenListener == null) {
-                return;
-            }
-
-            final boolean isEnteringFullScreen = !mIsFullScreen;
-            if (isEnteringFullScreen) {
-                mFullScreenButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                        R.drawable.media2_widget_ic_fullscreen_exit));
-                mMinimalFullScreenButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                        R.drawable.media2_widget_ic_fullscreen_exit));
-            } else {
-                mFullScreenButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                        R.drawable.media2_widget_ic_fullscreen));
-                mMinimalFullScreenButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                        R.drawable.media2_widget_ic_fullscreen));
-            }
-            mIsFullScreen = isEnteringFullScreen;
-            mOnFullScreenListener.onFullScreen(MediaControlView.this,
-                    mIsFullScreen);
-        }
-    };
-
-    private final OnClickListener mOverflowShowListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-
-            mOverflowIsShowing = true;
-            mOverflowShowAnimator.start();
-        }
-    };
-
-    private final OnClickListener mOverflowHideListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            resetHideCallbacks();
-
-            mOverflowIsShowing = false;
-            mOverflowHideAnimator.start();
-        }
-    };
-
-    private final OnClickListener mSettingsButtonListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mPlayer == null) return;
-            removeCallbacks(mHideMainBars);
-            removeCallbacks(mHideProgressBar);
-
-            mSettingsMode = SETTINGS_MODE_MAIN;
-            mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
-            displaySettingsWindow(mSettingsAdapter);
-        }
-    };
-
-    private final AdapterView.OnItemClickListener mSettingsItemClickListener =
-            new AdapterView.OnItemClickListener() {
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            switch (mSettingsMode) {
-                case SETTINGS_MODE_MAIN:
-                    if (position == SETTINGS_MODE_AUDIO_TRACK) {
-                        mSubSettingsAdapter.setTexts(mAudioTrackDescriptionList);
-                        mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
-                        mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
-                    } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
-                        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
-                        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
-                        mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
-                    }
-                    displaySettingsWindow(mSubSettingsAdapter);
-                    break;
-                case SETTINGS_MODE_AUDIO_TRACK:
-                    if (position != mSelectedAudioTrackIndex) {
-                        if (mAudioTracks.size() > 0) {
-                            mPlayer.selectTrack(mAudioTracks.get(position));
-                        }
-                    }
-                    dismissSettingsWindow();
-                    break;
-                case SETTINGS_MODE_PLAYBACK_SPEED:
-                    if (position != mSelectedSpeedIndex) {
-                        float speed = mPlaybackSpeedMultBy100List.get(position) / 100.0f;
-                        mPlayer.setPlaybackSpeed(speed);
-                    }
-                    dismissSettingsWindow();
-                    break;
-                case SETTINGS_MODE_SUBTITLE_TRACK:
-                    if (position != mSelectedSubtitleTrackIndex + 1) {
-                        if (position > 0) {
-                            mPlayer.selectTrack(mSubtitleTracks.get(position - 1));
-                        } else {
-                            mPlayer.deselectTrack(mSubtitleTracks.get(mSelectedSubtitleTrackIndex));
-                        }
-                    }
-                    dismissSettingsWindow();
-                    break;
-            }
-        }
-    };
-
-    private PopupWindow.OnDismissListener mSettingsDismissListener =
-            new PopupWindow.OnDismissListener() {
-                @Override
-                public void onDismiss() {
-                    if (mNeedToHideBars) {
-                        postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
-                    }
-                }
-            };
-
-    void updateTimeViews(androidx.media2.common.MediaItem item) {
-        if (item == null) {
-            mProgress.setProgress(0);
-            mCurrentTime.setText(mResources.getString(R.string.MediaControlView_time_placeholder));
-            mEndTime.setText(mResources.getString(R.string.MediaControlView_time_placeholder));
-            return;
-        }
-
-        ensurePlayerIsNotNull();
-
-        long duration = mPlayer.getDurationMs();
-        if (duration > 0) {
-            mDuration = duration;
-            setProgress();
-        }
-    }
-
-    void updateTitleView(androidx.media2.common.MediaItem item) {
-        if (item == null) {
-            mTitleView.setText(null);
-            return;
-        }
-
-        if (!isCurrentItemMusic()) {
-            CharSequence title = mPlayer.getTitle();
-            if (title == null) {
-                title = mResources.getString(R.string.mcv2_non_music_title_unknown_text);
-            }
-            mTitleView.setText(title.toString());
-        } else {
-            CharSequence title = mPlayer.getTitle();
-            if (title == null) {
-                title = mResources.getString(R.string.mcv2_music_title_unknown_text);
-            }
-            CharSequence artist = mPlayer.getArtistText();
-            if (artist == null) {
-                artist = mResources.getString(R.string.mcv2_music_artist_unknown_text);
-            }
-            // Update title for Embedded size type
-            mTitleView.setText(title.toString() + " - " + artist.toString());
-        }
-    }
-
-    void updateLayoutForAd() {
-        ensurePlayerIsNotNull();
-
-        if (mIsAdvertisement) {
-            findFullSizedControlButton(R.id.rew).setVisibility(View.GONE);
-            findFullSizedControlButton(R.id.ffwd).setVisibility(View.GONE);
-            findFullSizedControlButton(R.id.prev).setVisibility(View.GONE);
-
-            findFullSizedControlButton(R.id.next).setVisibility(View.VISIBLE);
-            findFullSizedControlButton(R.id.next).setEnabled(false);
-            findFullSizedControlButton(R.id.next).setColorFilter(R.color.media2_widget_gray);
-
-            mTimeView.setVisibility(View.GONE);
-            mAdSkipView.setVisibility(View.VISIBLE);
-            mAdRemainingView.setVisibility(View.VISIBLE);
-            mAdExternalLink.setVisibility(View.VISIBLE);
-
-            mProgress.setEnabled(false);
-        } else {
-            findFullSizedControlButton(R.id.rew).setVisibility(
-                    mPlayer.canSeekBackward() ? View.VISIBLE : View.GONE);
-            findFullSizedControlButton(R.id.ffwd).setVisibility(
-                    mPlayer.canSeekForward() ? View.VISIBLE : View.GONE);
-            findFullSizedControlButton(R.id.prev).setVisibility(
-                    mPlayer.canSkipToPrevious() ? View.VISIBLE : View.GONE);
-
-            findFullSizedControlButton(R.id.next).setVisibility(
-                    mPlayer.canSkipToNext() ? View.VISIBLE : View.GONE);
-            findFullSizedControlButton(R.id.next).setEnabled(true);
-            findFullSizedControlButton(R.id.next).clearColorFilter();
-
-            mTimeView.setVisibility(View.VISIBLE);
-            mAdSkipView.setVisibility(View.GONE);
-            mAdRemainingView.setVisibility(View.GONE);
-            mAdExternalLink.setVisibility(View.GONE);
-
-            mProgress.setEnabled(mSeekAvailable);
-        }
-    }
-
-    private void updateLayoutForSizeChange(int sizeType) {
-        switch (sizeType) {
-            case SIZE_TYPE_FULL:
-            case SIZE_TYPE_EMBEDDED:
-                // Relating to Progress Bar
-                mProgress.getThumb().setLevel(MAX_SCALE_LEVEL);
-                break;
-            case SIZE_TYPE_MINIMAL:
-                // Relating to Progress Bar
-                mProgress.getThumb().setLevel(0);
-                break;
-        }
-
-        // Update play/pause and ffwd buttons based on whether currently the replay button is shown
-        // or not.
-        updateReplayButton(mIsShowingReplayButton);
-    }
-
-    private View initTransportControls(int id) {
-        View v = findViewById(id);
-        ImageButton playPauseButton = v.findViewById(R.id.pause);
-        if (playPauseButton != null) {
-            playPauseButton.setOnClickListener(mPlayPauseListener);
-        }
-        ImageButton ffwdButton = v.findViewById(R.id.ffwd);
-        if (ffwdButton != null) {
-            ffwdButton.setOnClickListener(mFfwdListener);
-        }
-        ImageButton rewButton = v.findViewById(R.id.rew);
-        if (rewButton != null) {
-            rewButton.setOnClickListener(mRewListener);
-        }
-        ImageButton nextButton = v.findViewById(R.id.next);
-        if (nextButton != null) {
-            nextButton.setOnClickListener(mNextListener);
-        }
-        ImageButton prevButton = v.findViewById(R.id.prev);
-        if (prevButton != null) {
-            prevButton.setOnClickListener(mPrevListener);
-        }
-        return v;
-    }
-
-    private void initializeSettingsLists() {
-        mSettingsMainTextsList = new ArrayList<String>();
-        mSettingsMainTextsList.add(
-                mResources.getString(R.string.MediaControlView_audio_track_text));
-        mSettingsMainTextsList.add(
-                mResources.getString(R.string.MediaControlView_playback_speed_text));
-
-        mSettingsSubTextsList = new ArrayList<String>();
-        mSettingsSubTextsList.add(
-                mResources.getString(R.string.MediaControlView_audio_track_none_text));
-        String normalSpeed = mResources.getString(R.string.MediaControlView_playback_speed_normal);
-        mSettingsSubTextsList.add(normalSpeed);
-        mSettingsSubTextsList.add(RESOURCE_EMPTY);
-
-        mSettingsIconIdsList = new ArrayList<Integer>();
-        mSettingsIconIdsList.add(R.drawable.media2_widget_ic_audiotrack);
-        mSettingsIconIdsList.add(R.drawable.media2_widget_ic_speed);
-
-        mAudioTrackDescriptionList = new ArrayList<String>();
-        mAudioTrackDescriptionList.add(
-                mResources.getString(R.string.MediaControlView_audio_track_none_text));
-
-        mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
-                mResources.getStringArray(R.array.MediaControlView_playback_speeds)));
-        // Select the normal speed (1x) as the default value.
-        mPlaybackSpeedTextList.add(PLAYBACK_SPEED_1x_INDEX, normalSpeed);
-        mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
-
-        mPlaybackSpeedMultBy100List = new ArrayList<Integer>();
-        int[] speeds = mResources.getIntArray(R.array.media2_widget_speed_multiplied_by_100);
-        for (int i = 0; i < speeds.length; i++) {
-            mPlaybackSpeedMultBy100List.add(speeds[i]);
-        }
-        mCustomPlaybackSpeedIndex = -1;
-    }
-
-    @Nullable
-    ImageButton findControlButton(int sizeType, @IdRes int id) {
-        View transportControl = mTransportControlsMap.get(sizeType);
-        if (transportControl == null) {
-            return null;
-        }
-        return transportControl.findViewById(id);
-    }
-
-    @NonNull
-    ImageButton findFullSizedControlButton(@IdRes int id) {
-        ImageButton button = findControlButton(SIZE_TYPE_FULL, id);
-        if (button == null) {
-            throw new IllegalArgumentException("Couldn't find a view that has the given id");
-        }
-        return button;
-    }
-
-    /**
-     * @return true iff the current media item is from network.
-     */
-    boolean isCurrentMediaItemFromNetwork() {
-        ensurePlayerIsNotNull();
-
-        androidx.media2.common.MediaItem currentMediaItem = mPlayer.getCurrentMediaItem();
-
-        if (!(currentMediaItem instanceof androidx.media2.common.UriMediaItem)) {
-            return false;
-        }
-
-        Uri uri = ((androidx.media2.common.UriMediaItem) currentMediaItem).getUri();
-        return UriUtil.isFromNetwork(uri);
-    }
-
-    void displaySettingsWindow(BaseAdapter adapter) {
-        // Set Adapter
-        mSettingsListView.setAdapter(adapter);
-
-        // Set width of window
-        int itemWidth = (mSizeType == SIZE_TYPE_EMBEDDED)
-                ? mEmbeddedSettingsItemWidth : mFullSettingsItemWidth;
-        mSettingsWindow.setWidth(itemWidth);
-
-        // Calculate height of window
-        int maxHeight = getHeight() - mSettingsWindowMargin * 2;
-        int totalHeight = adapter.getCount() * mSettingsItemHeight;
-        int height = (totalHeight < maxHeight) ? totalHeight : maxHeight;
-        mSettingsWindow.setHeight(height);
-
-        // Show window
-        mNeedToHideBars = false;
-        mSettingsWindow.dismiss();
-        // Workaround for b/123271636.
-        if (height > 0) {
-            int xoff = getWidth() - mSettingsWindow.getWidth() - mSettingsWindowMargin;
-            int yoff = -mSettingsWindow.getHeight() - mSettingsWindowMargin;
-            mSettingsWindow.showAsDropDown(this, xoff, yoff);
-            mNeedToHideBars = true;
-        }
-    }
-
-    void dismissSettingsWindow() {
-        mNeedToHideBars = true;
-        mSettingsWindow.dismiss();
-    }
-
-    void animateOverflow(float animatedValue) {
-        int extraControlWidth = mExtraControls.getWidth();
-        int extraControlTranslationX = -1 * (int) (extraControlWidth * animatedValue);
-        mExtraControls.setTranslationX(extraControlTranslationX);
-
-        mTimeView.setAlpha(1 - animatedValue);
-        mBasicControls.setAlpha(1 - animatedValue);
-
-        int transportControlLeftWidth = findFullSizedControlButton(R.id.pause).getLeft();
-        int transportControlTranslationX = -1 * (int) (transportControlLeftWidth * animatedValue);
-        mFullTransportControls.setTranslationX(transportControlTranslationX);
-        findFullSizedControlButton(R.id.ffwd).setAlpha(1 - animatedValue);
-    }
-
-    void resetHideCallbacks() {
-        removeCallbacks(mHideMainBars);
-        removeCallbacks(mHideProgressBar);
-        postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
-    }
-
-    void updateAllowedCommands() {
-        ensurePlayerIsNotNull();
-
-        boolean canPause = mPlayer.canPause();
-        boolean canRew = mPlayer.canSeekBackward();
-        boolean canFfwd = mPlayer.canSeekForward();
-        boolean canPrev = mPlayer.canSkipToPrevious();
-        boolean canNext = mPlayer.canSkipToNext();
-        boolean canSeekTo = mPlayer.canSeekTo();
-
-        int n = mTransportControlsMap.size();
-        for (int i = 0; i < n; i++) {
-            int sizeType = mTransportControlsMap.keyAt(i);
-
-            View playPauseButton = findControlButton(sizeType, R.id.pause);
-            if (playPauseButton != null) {
-                playPauseButton.setVisibility(canPause ? View.VISIBLE : View.GONE);
-            }
-            View rewButton = findControlButton(sizeType, R.id.rew);
-            if (rewButton != null) {
-                rewButton.setVisibility(canRew ? View.VISIBLE : View.GONE);
-            }
-            View ffwdButton = findControlButton(sizeType, R.id.ffwd);
-            if (ffwdButton != null) {
-                ffwdButton.setVisibility(canFfwd ? View.VISIBLE : View.GONE);
-            }
-            View prevButton = findControlButton(sizeType, R.id.prev);
-            if (prevButton != null) {
-                prevButton.setVisibility(canPrev ? View.VISIBLE : View.GONE);
-            }
-            View nextButton = findControlButton(sizeType, R.id.next);
-            if (nextButton != null) {
-                nextButton.setVisibility(canNext ? View.VISIBLE : View.GONE);
-            }
-        }
-        mSeekAvailable = canSeekTo;
-        mProgress.setEnabled(canSeekTo);
-        updateSubtitleButtonVisibility();
-    }
-
-    void updateSubtitleButtonVisibility() {
-        // 1. If player doesn't support select/deselect track, subtitle button will not be shown.
-        // 2. If there's no valid track information, subtitle button will not be shown.
-        // The second criteria prevents the case that "cc" button is shortly shown and disappears
-        // when the media item is a music without subtitle tracks.
-        if (!mPlayer.canSelectDeselectTrack()
-                || (mVideoTrackCount == 0 && mAudioTracks.isEmpty() && mSubtitleTracks.isEmpty())) {
-            mSubtitleButton.setVisibility(View.GONE);
-            mSubtitleButton.setEnabled(false);
-            return;
-        }
-
-        if (mSubtitleTracks.isEmpty()) {
-            // For Audio only media item, CC button will not be shown when there's
-            // no subtitle tracks.
-            if (isCurrentItemMusic()) {
-                mSubtitleButton.setVisibility(View.GONE);
-                mSubtitleButton.setEnabled(false);
-            } else {
-                mSubtitleButton.setVisibility(View.VISIBLE);
-                mSubtitleButton.setAlpha(0.5f);
-                mSubtitleButton.setEnabled(false);
-            }
-        } else {
-            mSubtitleButton.setVisibility(View.VISIBLE);
-            mSubtitleButton.setAlpha(1.0f);
-            mSubtitleButton.setEnabled(true);
-        }
-    }
-
-    void updatePrevNextButtons(int prevIndex, int nextIndex) {
-        int n = mTransportControlsMap.size();
-        for (int i = 0; i < n; i++) {
-            int sizeType = mTransportControlsMap.keyAt(i);
-            View prevButton = findControlButton(sizeType, R.id.prev);
-            if (prevButton != null) {
-                if (prevIndex > androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) {
-                    prevButton.setAlpha(1.0f);
-                    prevButton.setEnabled(true);
-                } else {
-                    prevButton.setAlpha(0.5f);
-                    prevButton.setEnabled(false);
-                }
-            }
-            View nextButton = findControlButton(sizeType, R.id.next);
-            if (nextButton != null) {
-                if (nextIndex > androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) {
-                    nextButton.setAlpha(1.0f);
-                    nextButton.setEnabled(true);
-                } else {
-                    nextButton.setAlpha(0.5f);
-                    nextButton.setEnabled(false);
-                }
-            }
-        }
-    }
-
-    boolean shouldNotHideBars() {
-        return (isCurrentItemMusic() && mSizeType == SIZE_TYPE_FULL)
-                || mAccessibilityManager.isTouchExplorationEnabled()
-                || mPlayer.getPlayerState()
-                        == androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR
-                || mPlayer.getPlayerState()
-                        == androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-    }
-
-    void seekTo(long newPosition, boolean shouldSeekNow) {
-        ensurePlayerIsNotNull();
-
-        int positionOnProgressBar = (mDuration <= 0)
-                ? 0 : (int) (MAX_PROGRESS * newPosition / mDuration);
-        mProgress.setProgress(positionOnProgressBar);
-        mCurrentTime.setText(stringForTime(newPosition));
-
-        if (mCurrentSeekPosition == SEEK_POSITION_NOT_SET) {
-            // If current seek position is not set, update its value and seek now if necessary.
-            mCurrentSeekPosition = newPosition;
-
-            if (shouldSeekNow) {
-                mPlayer.seekTo(mCurrentSeekPosition);
-            }
-        } else {
-            // If current seek position is already set, update the next seek position.
-            mNextSeekPosition = newPosition;
-        }
-    }
-
-    long getLatestSeekPosition() {
-        ensurePlayerIsNotNull();
-
-        if (mNextSeekPosition != SEEK_POSITION_NOT_SET) {
-            return mNextSeekPosition;
-        } else if (mCurrentSeekPosition != SEEK_POSITION_NOT_SET) {
-            return mCurrentSeekPosition;
-        }
-        return mPlayer.getCurrentPosition();
-    }
-
-    void removeCustomSpeedFromList() {
-        mPlaybackSpeedMultBy100List.remove(mCustomPlaybackSpeedIndex);
-        mPlaybackSpeedTextList.remove(mCustomPlaybackSpeedIndex);
-        mCustomPlaybackSpeedIndex = -1;
-    }
-
-    void updateSelectedSpeed(int selectedSpeedIndex, String selectedSpeedText) {
-        mSelectedSpeedIndex = selectedSpeedIndex;
-        mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED, selectedSpeedText);
-        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
-        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
-    }
-
-    void updateReplayButton(boolean toBeShown) {
-        ImageButton ffwdButton = findControlButton(mSizeType, R.id.ffwd);
-        if (toBeShown) {
-            mIsShowingReplayButton = true;
-            updatePlayButton(PLAY_BUTTON_REPLAY);
-            if (ffwdButton != null) {
-                ffwdButton.setAlpha(0.5f);
-                ffwdButton.setEnabled(false);
-            }
-        } else {
-            mIsShowingReplayButton = false;
-            if (mPlayer != null && mPlayer.isPlaying()) {
-                updatePlayButton(PLAY_BUTTON_PAUSE);
-            } else {
-                updatePlayButton(PLAY_BUTTON_PLAY);
-            }
-            if (ffwdButton != null) {
-                ffwdButton.setAlpha(1.0f);
-                ffwdButton.setEnabled(true);
-            }
-        }
-    }
-
-    void updatePlayButton(int type) {
-        ImageButton playButton = findControlButton(mSizeType, R.id.pause);
-        if (playButton == null) {
-            return;
-        }
-        Drawable drawable;
-        String description;
-        if (type == PLAY_BUTTON_PAUSE) {
-            drawable = ContextCompat.getDrawable(getContext(),
-                    R.drawable.media2_widget_ic_pause_circle_filled);
-            description = mResources.getString(R.string.mcv2_pause_button_desc);
-        } else if (type == PLAY_BUTTON_PLAY) {
-            drawable = ContextCompat.getDrawable(getContext(),
-                    R.drawable.media2_widget_ic_play_circle_filled);
-            description = mResources.getString(R.string.mcv2_play_button_desc);
-        } else if (type == PLAY_BUTTON_REPLAY) {
-            drawable = ContextCompat.getDrawable(getContext(),
-                    R.drawable.media2_widget_ic_replay_circle_filled);
-            description = mResources.getString(R.string.mcv2_replay_button_desc);
-        } else {
-            throw new IllegalArgumentException("unknown type " + type);
-        }
-        playButton.setImageDrawable(drawable);
-        playButton.setContentDescription(description);
-    }
-
-    void postDelayedRunnable(Runnable runnable, long interval) {
-        if (interval != DISABLE_DELAYED_ANIMATION) {
-            postDelayed(runnable, interval);
-        }
-    }
-
-    void ensurePlayerIsNotNull() {
-        if (mPlayer == null) {
-            throw new IllegalStateException("mPlayer must not be null");
-        }
-    }
-
-    void updateTracks(
-            PlayerWrapper player, List<androidx.media2.common.SessionPlayer.TrackInfo> trackInfos) {
-        // Update video track count, audio & subtitle track lists.
-        mVideoTrackCount = 0;
-        mAudioTracks = new ArrayList<>();
-        mSubtitleTracks = new ArrayList<>();
-        mSelectedAudioTrackIndex = 0;
-        // Default is -1 since subtitle selection always includes "Off" item
-        mSelectedSubtitleTrackIndex = -1;
-        androidx.media2.common.SessionPlayer.TrackInfo audioTrack =
-                player.getSelectedTrack(
-                        androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO);
-        androidx.media2.common.SessionPlayer.TrackInfo subtitleTrack =
-                player.getSelectedTrack(
-                        androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-        for (int i = 0; i < trackInfos.size(); i++) {
-            int trackType = trackInfos.get(i).getTrackType();
-            if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
-                mVideoTrackCount++;
-            } else if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
-                if (trackInfos.get(i).equals(audioTrack)) {
-                    mSelectedAudioTrackIndex = mAudioTracks.size();
-                }
-                mAudioTracks.add(trackInfos.get(i));
-            } else if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                if (trackInfos.get(i).equals(subtitleTrack)) {
-                    mSelectedSubtitleTrackIndex = mSubtitleTracks.size();
-                }
-                mSubtitleTracks.add(trackInfos.get(i));
-            }
-        }
-
-        // Update audio description list.
-        mAudioTrackDescriptionList = new ArrayList<>();
-        if (mAudioTracks.isEmpty()) {
-            mAudioTrackDescriptionList.add(
-                    mResources.getString(R.string.MediaControlView_audio_track_none_text));
-        } else {
-            for (int i = 0; i < mAudioTracks.size(); i++) {
-                mAudioTrackDescriptionList.add(mResources.getString(
-                        R.string.MediaControlView_audio_track_number_text, i + 1));
-            }
-        }
-
-        // Update text for audio displayed inside the Settings window.
-        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
-                mAudioTrackDescriptionList.get(mSelectedAudioTrackIndex));
-
-        // Update subtitle description list and subtitle button visibility.
-        mSubtitleDescriptionsList = new ArrayList<>();
-        if (!mSubtitleTracks.isEmpty()) {
-            mSubtitleDescriptionsList.add(mResources.getString(
-                    R.string.MediaControlView_subtitle_off_text));
-            for (int i = 0; i < mSubtitleTracks.size(); i++) {
-                String lang = mSubtitleTracks.get(i).getLanguage().getISO3Language();
-                String trackDescription;
-                if (lang.equals("und")) {
-                    trackDescription = mResources.getString(
-                            R.string.MediaControlView_subtitle_track_number_text, i + 1);
-                } else {
-                    trackDescription = mResources.getString(
-                            R.string.MediaControlView_subtitle_track_number_and_lang_text,
-                            i + 1, lang);
-                }
-                mSubtitleDescriptionsList.add(trackDescription);
-            }
-        }
-        updateSubtitleButtonVisibility();
-    }
-
-    private boolean hasActualVideo() {
-        if (mVideoTrackCount > 0) {
-            return true;
-        }
-        androidx.media2.common.VideoSize videoSize = mPlayer.getVideoSize();
-        if (videoSize.getHeight() > 0 && videoSize.getWidth() > 0) {
-            Log.w(TAG, "video track count is zero, but it renders video. size: " + videoSize);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isCurrentItemMusic() {
-        return !hasActualVideo() && mAudioTracks.size() > 0;
-    }
-
-    private class SettingsAdapter extends BaseAdapter {
-        private List<Integer> mIconIds;
-        private List<String> mMainTexts;
-        private List<String> mSubTexts;
-
-        SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
-                @Nullable List<Integer> iconIds) {
-            mMainTexts = mainTexts;
-            mSubTexts = subTexts;
-            mIconIds = iconIds;
-        }
-
-        @Override
-        public int getCount() {
-            return (mMainTexts == null) ? 0 : mMainTexts.size();
-        }
-
-        @Override
-        public long getItemId(int position) {
-            // Auto-generated method stub--does not have any purpose here
-            return 0;
-        }
-
-        @Override
-        public Object getItem(int position) {
-            // Auto-generated method stub--does not have any purpose here
-            return null;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup container) {
-            View row = inflateLayout(getContext(), R.layout.media2_widget_settings_list_item);
-            TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
-            TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
-            ImageView iconView = (ImageView) row.findViewById(R.id.icon);
-
-            // Set main text
-            mainTextView.setText(mMainTexts.get(position));
-
-            // Remove sub text and center the main text if sub texts do not exist at all or the sub
-            // text at this particular position is empty.
-            if (mSubTexts == null || RESOURCE_EMPTY.equals(mSubTexts.get(position))) {
-                subTextView.setVisibility(View.GONE);
-            } else {
-                // Otherwise, set sub text.
-                subTextView.setText(mSubTexts.get(position));
-            }
-
-            // Remove main icon and set visibility to gone if icons are set to null or the icon at
-            // this particular position is set to RESOURCE_NON_EXISTENT.
-            if (mIconIds == null || mIconIds.get(position) == RESOURCE_NON_EXISTENT) {
-                iconView.setVisibility(View.GONE);
-            } else {
-                // Otherwise, set main icon.
-                iconView.setImageDrawable(
-                        ContextCompat.getDrawable(getContext(), mIconIds.get(position)));
-            }
-            return row;
-        }
-
-        public void setSubTexts(List<String> subTexts) {
-            mSubTexts = subTexts;
-        }
-    }
-
-    private class SubSettingsAdapter extends BaseAdapter {
-        private List<String> mTexts;
-        private int mCheckPosition;
-
-        SubSettingsAdapter(List<String> texts, int checkPosition) {
-            mTexts = texts;
-            mCheckPosition = checkPosition;
-        }
-
-        public String getMainText(int position) {
-            if (mTexts != null) {
-                if (position < mTexts.size()) {
-                    return mTexts.get(position);
-                }
-            }
-            return RESOURCE_EMPTY;
-        }
-
-        @Override
-        public int getCount() {
-            return (mTexts == null) ? 0 : mTexts.size();
-        }
-
-        @Override
-        public long getItemId(int position) {
-            // Auto-generated method stub--does not have any purpose here
-            return 0;
-        }
-
-        @Override
-        public Object getItem(int position) {
-            // Auto-generated method stub--does not have any purpose here
-            return null;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup container) {
-            View row = inflateLayout(getContext(), R.layout.media2_widget_sub_settings_list_item);
-            TextView textView = (TextView) row.findViewById(R.id.text);
-            ImageView checkView = (ImageView) row.findViewById(R.id.check);
-
-            textView.setText(mTexts.get(position));
-            if (position != mCheckPosition) {
-                checkView.setVisibility(View.INVISIBLE);
-            }
-            return row;
-        }
-
-        public void setTexts(List<String> texts) {
-            mTexts = texts;
-        }
-
-        public void setCheckPosition(int checkPosition) {
-            mCheckPosition = checkPosition;
-        }
-    }
-
-    // TODO (b/122440911): Enable advertisement mode
-    class PlayerCallback extends PlayerWrapper.PlayerCallback {
-        @Override
-        public void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onPlayerStateChanged(state: " + state + ")");
-            }
-
-            updateTimeViews(player.getCurrentMediaItem());
-
-            // Update pause button depending on playback state for the following two reasons:
-            //   1) Need to handle case where app customizes playback state behavior when app
-            //      activity is resumed.
-            //   2) Need to handle case where the media file reaches end of duration.
-            switch (state) {
-                case androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING:
-                    removeCallbacks(mUpdateProgress);
-                    post(mUpdateProgress);
-                    resetHideCallbacks();
-                    updateReplayButton(false);
-                    break;
-                case androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED:
-                    updatePlayButton(PLAY_BUTTON_PLAY);
-                    removeCallbacks(mUpdateProgress);
-                    removeCallbacks(mHideMainBars);
-                    removeCallbacks(mHideProgressBar);
-                    post(mShowAllBars);
-                    break;
-                case androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR:
-                    updatePlayButton(PLAY_BUTTON_PLAY);
-                    removeCallbacks(mUpdateProgress);
-                    if (getWindowToken() != null) {
-                        new AlertDialog.Builder(getContext())
-                                .setMessage(R.string.mcv2_playback_error_text)
-                                .setPositiveButton(R.string.mcv2_error_dialog_button,
-                                        new DialogInterface.OnClickListener() {
-                                            @Override
-                                            public void onClick(
-                                                    DialogInterface dialogInterface,
-                                                    int i) {
-                                                dialogInterface.dismiss();
-                                            }
-                                        })
-                                .setCancelable(true)
-                                .show();
-                    }
-                    break;
-            }
-        }
-
-        @Override
-        public void onSeekCompleted(@NonNull PlayerWrapper player, long position) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onSeekCompleted(): " + position);
-            }
-            // Update progress bar and time text.
-            int positionOnProgressBar = (mDuration <= 0)
-                    ? 0 : (int) (MAX_PROGRESS * position / mDuration);
-            mProgress.setProgress(positionOnProgressBar);
-            mCurrentTime.setText(stringForTime(position));
-
-            if (mNextSeekPosition != SEEK_POSITION_NOT_SET) {
-                mCurrentSeekPosition = mNextSeekPosition;
-
-                // If the next seek position is set, seek to that position.
-                player.seekTo(mNextSeekPosition);
-                mNextSeekPosition = SEEK_POSITION_NOT_SET;
-            } else {
-                mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
-
-                // If the next seek position is not set and the progress bar thumb is not being
-                // dragged, start to update progress.
-                if (!mDragging) {
-                    removeCallbacks(mUpdateProgress);
-                    removeCallbacks(mHideMainBars);
-                    post(mUpdateProgress);
-                    postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
-                }
-            }
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(
-                @NonNull PlayerWrapper player,
-                @Nullable androidx.media2.common.MediaItem mediaItem) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onCurrentMediaItemChanged(): " + mediaItem);
-            }
-            updateTimeViews(mediaItem);
-            updateTitleView(mediaItem);
-            updatePrevNextButtons(player.getPreviousMediaItemIndex(),
-                    player.getNextMediaItemIndex());
-        }
-
-        @Override
-        void onPlaylistChanged(
-                @NonNull PlayerWrapper player,
-                @Nullable List<androidx.media2.common.MediaItem> list,
-                @Nullable androidx.media2.common.MediaMetadata metadata) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onPlaylistChanged(): list: " + list + ", metadata: " + metadata);
-            }
-            updatePrevNextButtons(player.getPreviousMediaItemIndex(),
-                    player.getNextMediaItemIndex());
-        }
-
-        @Override
-        public void onPlaybackCompleted(@NonNull PlayerWrapper player) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onPlaybackCompleted()");
-            }
-            updateReplayButton(true);
-            // The progress bar and current time text may not have been updated.
-            mProgress.setProgress(MAX_PROGRESS);
-            mCurrentTime.setText(stringForTime(mDuration));
-        }
-
-        @Override
-        public void onAllowedCommandsChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.session.SessionCommandGroup commands) {
-            if (player != mPlayer) return;
-
-            updateAllowedCommands();
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(@NonNull PlayerWrapper player, float speed) {
-            if (player != mPlayer) return;
-
-            int customSpeedMultBy100 = Math.round(speed * 100);
-            // An application may set a custom playback speed that is not included in the
-            // default playback speed list. The code below handles adding/removing the custom
-            // playback speed to the default list.
-            if (mCustomPlaybackSpeedIndex != -1) {
-                // Remove existing custom playback speed
-                removeCustomSpeedFromList();
-            }
-
-            if (mPlaybackSpeedMultBy100List.contains(customSpeedMultBy100)) {
-                for (int i = 0; i < mPlaybackSpeedMultBy100List.size(); i++) {
-                    if (customSpeedMultBy100 == mPlaybackSpeedMultBy100List.get(i)) {
-                        updateSelectedSpeed(i, mPlaybackSpeedTextList.get(i));
-                        break;
-                    }
-                }
-            } else {
-                String customSpeedText = mResources.getString(
-                        R.string.MediaControlView_custom_playback_speed_text,
-                        customSpeedMultBy100 / 100.0f);
-
-                for (int i = 0; i < mPlaybackSpeedMultBy100List.size(); i++) {
-                    if (customSpeedMultBy100 < mPlaybackSpeedMultBy100List.get(i)) {
-                        mPlaybackSpeedMultBy100List.add(i, customSpeedMultBy100);
-                        mPlaybackSpeedTextList.add(i, customSpeedText);
-                        updateSelectedSpeed(i, customSpeedText);
-                        break;
-                    }
-                    // Add to end of list if the custom speed value is greater than all the
-                    // value in the default speed list.
-                    if (i == mPlaybackSpeedMultBy100List.size() - 1
-                            && customSpeedMultBy100 > mPlaybackSpeedMultBy100List.get(i)) {
-                        mPlaybackSpeedMultBy100List.add(customSpeedMultBy100);
-                        mPlaybackSpeedTextList.add(customSpeedText);
-                        updateSelectedSpeed(i + 1, customSpeedText);
-                    }
-                }
-                mCustomPlaybackSpeedIndex = mSelectedSpeedIndex;
-            }
-        }
-
-        @Override
-        void onTracksChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull List<androidx.media2.common.SessionPlayer.TrackInfo> tracks) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onandroidx.media2.common.SessionPlayer.TrackInfoChanged(): " + tracks);
-            }
-
-            updateTracks(player, tracks);
-            updateTimeViews(player.getCurrentMediaItem());
-            updateTitleView(player.getCurrentMediaItem());
-        }
-
-        @Override
-        void onTrackSelected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onTrackSelected(): " + trackInfo);
-            }
-            if (trackInfo.getTrackType()
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                for (int i = 0; i < mSubtitleTracks.size(); i++) {
-                    if (mSubtitleTracks.get(i).equals(trackInfo)) {
-                        mSelectedSubtitleTrackIndex = i;
-
-                        if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
-                            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex + 1);
-                        }
-                        mSubtitleButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                                R.drawable.media2_widget_ic_subtitle_on));
-                        mSubtitleButton.setContentDescription(
-                                mResources.getString(R.string.mcv2_cc_is_on));
-                        break;
-                    }
-                }
-            } else if (trackInfo.getTrackType()
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
-                for (int i = 0; i < mAudioTracks.size(); i++) {
-                    if (mAudioTracks.get(i).equals(trackInfo)) {
-                        mSelectedAudioTrackIndex = i;
-
-                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
-                                mSubSettingsAdapter.getMainText(mSelectedAudioTrackIndex));
-                        break;
-                    }
-                }
-            }
-        }
-
-        @Override
-        void onTrackDeselected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onTrackDeselected(): " + trackInfo);
-            }
-            if (trackInfo.getTrackType()
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                for (int i = 0; i < mSubtitleTracks.size(); i++) {
-                    if (mSubtitleTracks.get(i).equals(trackInfo)) {
-                        mSelectedSubtitleTrackIndex = -1;
-
-                        if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
-                            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex + 1);
-                        }
-                        mSubtitleButton.setImageDrawable(ContextCompat.getDrawable(getContext(),
-                                R.drawable.media2_widget_ic_subtitle_off));
-                        mSubtitleButton.setContentDescription(
-                                mResources.getString(R.string.mcv2_cc_is_off));
-                        break;
-                    }
-                }
-            }
-        }
-
-        @Override
-        void onVideoSizeChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.VideoSize videoSize) {
-            if (player != mPlayer) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onVideoSizeChanged(): " + videoSize);
-            }
-            if (mVideoTrackCount == 0 && videoSize.getHeight() > 0 && videoSize.getWidth() > 0) {
-                List<androidx.media2.common.SessionPlayer.TrackInfo> tracks = player.getTracks();
-                if (tracks != null) {
-                    updateTracks(player, tracks);
-                }
-            }
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaTimeProvider.java b/media2/media2-widget/src/main/java/androidx/media2/widget/MediaTimeProvider.java
deleted file mode 100644
index 94b291c..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaTimeProvider.java
+++ /dev/null
@@ -1,94 +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.media2.widget;
-
-import androidx.annotation.NonNull;
-
-// Note: This is just copied from android.media.MediaTimeProvider.
-interface MediaTimeProvider {
-    // we do not allow negative media time
-    /**
-     * Presentation time value if no timed event notification is requested.
-     */
-    long NO_TIME = -1;
-
-    /**
-     * Cancels all previous notification request from this listener if any.  It
-     * registers the listener to get seek and stop notifications.  If timeUs is
-     * not negative, it also registers the listener for a timed event
-     * notification when the presentation time reaches (becomes greater) than
-     * the value specified.  This happens immediately if the current media time
-     * is larger than or equal to timeUs.
-     *
-     * @param timeUs presentation time to get timed event callback at (or
-     *               {@link #NO_TIME})
-     */
-    void notifyAt(long timeUs, @NonNull OnMediaTimeListener listener);
-
-    /**
-     * Cancels all previous notification request from this listener if any.  It
-     * registers the listener to get seek and stop notifications.  If the media
-     * is stopped, the listener will immediately receive a stop notification.
-     * Otherwise, it will receive a timed event notificaton.
-     */
-    void scheduleUpdate(@NonNull OnMediaTimeListener listener);
-
-    /**
-     * Cancels all previous notification request from this listener if any.
-     */
-    void cancelNotifications(@NonNull OnMediaTimeListener listener);
-
-    /**
-     * Get the current presentation time.
-     *
-     * @param precise   Whether getting a precise time is important. This is
-     *                  more costly.
-     * @param monotonic Whether returned time should be monotonic: that is,
-     *                  greater than or equal to the last returned time.  Don't
-     *                  always set this to true.  E.g. this has undesired
-     *                  consequences if the media is seeked between calls.
-     * @throws IllegalStateException if the media is not initialized
-     */
-    long getCurrentTimeUs(boolean precise, boolean monotonic)
-            throws IllegalStateException;
-
-    /**
-     * Mediatime listener
-     */
-    interface OnMediaTimeListener {
-        /**
-         * Called when the registered time was reached naturally.
-         *
-         * @param timeUs current media time
-         */
-        void onTimedEvent(long timeUs);
-
-        /**
-         * Called when the media time changed due to seeking.
-         *
-         * @param timeUs current media time
-         */
-        void onSeek(long timeUs);
-
-        /**
-         * Called when the playback stopped.  This is not called on pause, only
-         * on full stop, at which point there is no further current media time.
-         */
-        void onStop();
-    }
-}
-
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaViewGroup.java b/media2/media2-widget/src/main/java/androidx/media2/widget/MediaViewGroup.java
deleted file mode 100644
index 43e946f..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/MediaViewGroup.java
+++ /dev/null
@@ -1,86 +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.media2.widget;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.AttrRes;
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-abstract class MediaViewGroup extends ViewGroup {
-    private boolean mAggregatedIsVisible = false;
-    MediaViewGroup(@NonNull Context context) {
-        super(context);
-    }
-
-    MediaViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    MediaViewGroup(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onVisibilityChanged(@NonNull View view, int visibility) {
-        if (Build.VERSION.SDK_INT < 24) {
-            // onVisibilityAggregated() is introduced at API 24.
-            // This is added to make the behavior compatible on < 24 devices.
-            if (getWindowVisibility() == VISIBLE) {
-                boolean newIsVisible = isShown();
-                if (mAggregatedIsVisible != newIsVisible) {
-                    onVisibilityAggregatedCompat(newIsVisible);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        if (Build.VERSION.SDK_INT < 24) {
-            // onVisibilityAggregated() is introduced at API 24.
-            // This is added to make the behavior compatible on < 24 devices.
-            if (isShown()) {
-                onVisibilityAggregatedCompat(visibility == VISIBLE);
-            }
-        }
-    }
-
-    @Override
-    @RequiresApi(24)
-    public void onVisibilityAggregated(boolean isVisible) {
-        super.onVisibilityAggregated(isVisible);
-        onVisibilityAggregatedCompat(isVisible);
-    }
-
-    @CallSuper
-    void onVisibilityAggregatedCompat(boolean isVisible) {
-        mAggregatedIsVisible = isVisible;
-    }
-
-    boolean isAggregatedVisible() {
-        return mAggregatedIsVisible;
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/MusicView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/MusicView.java
deleted file mode 100644
index 3ff3d67..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/MusicView.java
+++ /dev/null
@@ -1,161 +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.media2.widget;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-class MusicView extends ViewGroup {
-    private MusicViewType mType = MusicViewType.WITHOUT_TITLE;
-    private View mWithTitleLandscape;
-    private View mWithTitlePortrait;
-    private View mWithoutTitle;
-
-    MusicView(@NonNull Context context) {
-        super(context);
-
-        inflateLayout();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (!(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
-                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY)) {
-            throw new AssertionError("MusicView should be measured in MeasureSpec.EXACTLY");
-        }
-
-        final int width = MeasureSpec.getSize(widthMeasureSpec);
-        final int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (width > height) {
-            mType = MusicViewType.WITH_TITLE_LANDSCAPE;
-            mWithTitleLandscape.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED),
-                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            if (hasTooSmallMeasuredState(mWithTitleLandscape)
-                    || mWithTitleLandscape.getMeasuredWidth() > width) {
-                mType = MusicViewType.WITHOUT_TITLE;
-            }
-        } else {
-            mType = MusicViewType.WITH_TITLE_PORTRAIT;
-            mWithTitlePortrait.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
-            if (hasTooSmallMeasuredState(mWithTitlePortrait)
-                    || mWithTitlePortrait.getMeasuredHeight() > height) {
-                mType = MusicViewType.WITHOUT_TITLE;
-            }
-        }
-
-        if (mType == MusicViewType.WITHOUT_TITLE) {
-            mWithoutTitle.measure(MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(height / 2, MeasureSpec.EXACTLY));
-        }
-
-        setMeasuredDimension(width, height);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        View view;
-        if (mType == MusicViewType.WITH_TITLE_LANDSCAPE) {
-            view = mWithTitleLandscape;
-        } else if (mType == MusicViewType.WITH_TITLE_PORTRAIT) {
-            view = mWithTitlePortrait;
-        } else {
-            view = mWithoutTitle;
-        }
-
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            if (child == view) {
-                child.setVisibility(View.VISIBLE);
-            } else {
-                child.setVisibility(View.INVISIBLE);
-            }
-        }
-
-        final int parentWidth = right - left;
-        final int parentHeight = bottom - top;
-
-        final int width = view.getMeasuredWidth();
-        final int height = view.getMeasuredHeight();
-
-        final int childLeft = (parentWidth - width) / 2;
-        final int childTop = (parentHeight - height) / 2;
-
-        view.layout(childLeft, childTop, childLeft + width, childTop + height);
-    }
-
-    void setAlbumDrawable(Drawable album) {
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            ImageView albumView = getChildAt(i).findViewById(R.id.album);
-            if (albumView != null) {
-                albumView.setImageDrawable(album);
-            }
-        }
-    }
-
-    void setTitleText(String title) {
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            TextView titleView = getChildAt(i).findViewById(R.id.title);
-            if (titleView != null) {
-                titleView.setText(title);
-            }
-        }
-    }
-
-    void setArtistText(String artist) {
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            TextView artistView = getChildAt(i).findViewById(R.id.artist);
-            if (artistView != null) {
-                artistView.setText(artist);
-            }
-        }
-    }
-
-    private void inflateLayout() {
-        LayoutInflater inflater = (LayoutInflater) getContext()
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        mWithTitleLandscape = inflater.inflate(R.layout.media2_widget_music_with_title_landscape,
-                null);
-        mWithTitlePortrait = inflater.inflate(R.layout.media2_widget_music_with_title_portrait,
-                null);
-        mWithoutTitle = inflater.inflate(R.layout.media2_widget_music_without_title, null);
-
-        addView(mWithTitleLandscape);
-        addView(mWithTitlePortrait);
-        addView(mWithoutTitle);
-    }
-
-    private static boolean hasTooSmallMeasuredState(@NonNull View view) {
-        return ((view.getMeasuredWidthAndState() & MEASURED_STATE_TOO_SMALL)
-                | (view.getMeasuredHeightAndState() & MEASURED_STATE_TOO_SMALL)) != 0;
-    }
-
-    private enum MusicViewType { WITH_TITLE_LANDSCAPE, WITH_TITLE_PORTRAIT, WITHOUT_TITLE }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/PlayerWrapper.java b/media2/media2-widget/src/main/java/androidx/media2/widget/PlayerWrapper.java
deleted file mode 100644
index 575f96f..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/PlayerWrapper.java
+++ /dev/null
@@ -1,688 +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.media2.widget;
-
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.ObjectsCompat;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/** Wrapper for androidx.media2.session.MediaController and androidx.media2.common.SessionPlayer */
-@SuppressWarnings("deprecation")
-class PlayerWrapper {
-    final androidx.media2.session.MediaController mController;
-    final androidx.media2.common.SessionPlayer mPlayer;
-
-    private final Executor mCallbackExecutor;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final PlayerCallback mWrapperCallback;
-    private final MediaControllerCallback mControllerCallback;
-    private final SessionPlayerCallback mPlayerCallback;
-
-    private boolean mCallbackAttached;
-
-    // cached states
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    int mSavedPlayerState = androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    androidx.media2.session.SessionCommandGroup mAllowedCommands;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    androidx.media2.common.MediaMetadata mMediaMetadata;
-
-    private final androidx.media2.session.SessionCommandGroup mAllCommands;
-
-    PlayerWrapper(
-            @NonNull androidx.media2.session.MediaController controller,
-            @NonNull Executor executor,
-            @NonNull PlayerCallback callback) {
-        if (controller == null) throw new NullPointerException("controller must not be null");
-        if (executor == null) throw new NullPointerException("executor must not be null");
-        if (callback == null) throw new NullPointerException("callback must not be null");
-        mController = controller;
-        mCallbackExecutor = executor;
-        mWrapperCallback = callback;
-        mControllerCallback = new MediaControllerCallback();
-
-        mPlayer = null;
-        mPlayerCallback = null;
-
-        mAllCommands = null;
-    }
-
-    PlayerWrapper(
-            @NonNull androidx.media2.common.SessionPlayer player,
-            @NonNull Executor executor,
-            @NonNull PlayerCallback callback) {
-        if (player == null) throw new NullPointerException("player must not be null");
-        if (executor == null) throw new NullPointerException("executor must not be null");
-        if (callback == null) throw new NullPointerException("callback must not be null");
-        mPlayer = player;
-        mCallbackExecutor = executor;
-        mWrapperCallback = callback;
-        mPlayerCallback = new SessionPlayerCallback();
-
-        mController = null;
-        mControllerCallback = null;
-
-        mAllCommands =
-                new androidx.media2.session.SessionCommandGroup.Builder()
-                        .addAllPredefinedCommands(
-                                androidx.media2.session.SessionCommand.COMMAND_VERSION_1)
-                        .build();
-    }
-
-    boolean hasDisconnectedController() {
-        return mController != null && !mController.isConnected();
-    }
-
-    void attachCallback() {
-        if (mCallbackAttached) return;
-        if (mController != null) {
-            mController.registerExtraCallback(mCallbackExecutor, mControllerCallback);
-        } else if (mPlayer != null) {
-            mPlayer.registerPlayerCallback(mCallbackExecutor, mPlayerCallback);
-        }
-        updateAndNotifyCachedStates();
-        mCallbackAttached = true;
-    }
-
-    void detachCallback() {
-        if (!mCallbackAttached) return;
-        if (mController != null) {
-            mController.unregisterExtraCallback(mControllerCallback);
-        } else if (mPlayer != null) {
-            mPlayer.unregisterPlayerCallback(mPlayerCallback);
-        }
-        mCallbackAttached = false;
-    }
-
-    boolean isPlaying() {
-        return mSavedPlayerState == androidx.media2.common.SessionPlayer.PLAYER_STATE_PLAYING;
-    }
-
-    long getCurrentPosition() {
-        if (mSavedPlayerState == androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE) {
-            return 0;
-        }
-        long position = 0;
-        if (mController != null) {
-            position = mController.getCurrentPosition();
-        } else if (mPlayer != null) {
-            position = mPlayer.getCurrentPosition();
-        }
-        return (position < 0) ? 0 : position;
-    }
-
-    long getBufferPercentage() {
-        if (mSavedPlayerState == androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE) {
-            return 0;
-        }
-        long duration = getDurationMs();
-        if (duration == 0) return 0;
-        long bufferedPos = 0;
-        if (mController != null) {
-            bufferedPos = mController.getBufferedPosition();
-        } else if (mPlayer != null) {
-            bufferedPos = mPlayer.getBufferedPosition();
-        }
-        return (bufferedPos < 0) ? 0 : (bufferedPos * 100 / duration);
-    }
-
-    int getPlayerState() {
-        if (mController != null) {
-            return mController.getPlayerState();
-        } else if (mPlayer != null) {
-            return mPlayer.getPlayerState();
-        }
-        return androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-    }
-
-    boolean canPause() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_PAUSE);
-    }
-
-    boolean canSeekBackward() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_REWIND);
-    }
-
-    boolean canSeekForward() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD);
-    }
-
-    boolean canSkipToNext() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand
-                                .COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM);
-    }
-
-    boolean canSkipToPrevious() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand
-                                .COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM);
-    }
-
-    boolean canSeekTo() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO);
-    }
-
-    boolean canSelectDeselectTrack() {
-        return mAllowedCommands != null
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK)
-                && mAllowedCommands.hasCommand(
-                        androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK);
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void pause() {
-        if (mController != null) {
-            mController.pause();
-        } else if (mPlayer != null) {
-            mPlayer.pause();
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void play() {
-        if (mController != null) {
-            mController.play();
-        } else if (mPlayer != null) {
-            mPlayer.play();
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void seekTo(long posMs) {
-        if (mController != null) {
-            mController.seekTo(posMs);
-        } else if (mPlayer != null) {
-            mPlayer.seekTo(posMs);
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void skipToNextItem() {
-        if (mController != null) {
-            mController.skipToNextPlaylistItem();
-        } else if (mPlayer != null) {
-            mPlayer.skipToNextPlaylistItem();
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void skipToPreviousItem() {
-        if (mController != null) {
-            mController.skipToPreviousPlaylistItem();
-        } else if (mPlayer != null) {
-            mPlayer.skipToPreviousPlaylistItem();
-        }
-    }
-
-    private float getPlaybackSpeed() {
-        if (mController != null) {
-            return mController.getPlaybackSpeed();
-        } else if (mPlayer != null) {
-            return mPlayer.getPlaybackSpeed();
-        }
-        return 1f;
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void setPlaybackSpeed(float speed) {
-        if (mController != null) {
-            mController.setPlaybackSpeed(speed);
-        } else if (mPlayer != null) {
-            mPlayer.setPlaybackSpeed(speed);
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void selectTrack(androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-        if (mController != null) {
-            mController.selectTrack(trackInfo);
-        } else if (mPlayer != null) {
-            mPlayer.selectTrack(trackInfo);
-        }
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-        if (mController != null) {
-            mController.deselectTrack(trackInfo);
-        } else if (mPlayer != null) {
-            mPlayer.deselectTrack(trackInfo);
-        }
-    }
-
-    long getDurationMs() {
-        if (mSavedPlayerState == androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE) {
-            return 0;
-        }
-        long duration = 0;
-        if (mController != null) {
-            duration = mController.getDuration();
-        } else if (mPlayer != null) {
-            duration = mPlayer.getDuration();
-        }
-        return (duration < 0) ? 0 : duration;
-    }
-
-    CharSequence getTitle() {
-        if (mMediaMetadata != null) {
-            if (mMediaMetadata.containsKey(
-                    androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE)) {
-                return mMediaMetadata.getText(
-                        androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE);
-            }
-        }
-        return null;
-    }
-
-    CharSequence getArtistText() {
-        if (mMediaMetadata != null) {
-            if (mMediaMetadata.containsKey(
-                    androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST)) {
-                return mMediaMetadata.getText(
-                        androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST);
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    androidx.media2.common.MediaItem getCurrentMediaItem() {
-        if (mController != null) {
-            return mController.getCurrentMediaItem();
-        } else if (mPlayer != null) {
-            return mPlayer.getCurrentMediaItem();
-        }
-        return null;
-    }
-
-    @Nullable
-    private androidx.media2.session.SessionCommandGroup getAllowedCommands() {
-        if (mController != null) {
-            return mController.getAllowedCommands();
-        } else if (mPlayer != null) {
-            // We can assume direct players allow all commands since no MediaSession is involved.
-            return mAllCommands;
-        }
-        return null;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void updateAndNotifyCachedStates() {
-        boolean playerStateChanged = false;
-        int playerState = getPlayerState();
-        if (mSavedPlayerState != playerState) {
-            mSavedPlayerState = playerState;
-            playerStateChanged = true;
-        }
-
-        boolean allowedCommandsChanged = false;
-        androidx.media2.session.SessionCommandGroup allowedCommands = getAllowedCommands();
-        if (!ObjectsCompat.equals(mAllowedCommands, allowedCommands)) {
-            mAllowedCommands = allowedCommands;
-            allowedCommandsChanged = true;
-        }
-
-        androidx.media2.common.MediaItem item = getCurrentMediaItem();
-        mMediaMetadata = item == null ? null : item.getMetadata();
-
-        if (playerStateChanged) {
-            mWrapperCallback.onPlayerStateChanged(this, playerState);
-        }
-        if (allowedCommands != null && allowedCommandsChanged) {
-            mWrapperCallback.onAllowedCommandsChanged(this, allowedCommands);
-        }
-        mWrapperCallback.onCurrentMediaItemChanged(this, item);
-        notifyNonCachedStates();
-    }
-
-    @NonNull
-    androidx.media2.common.VideoSize getVideoSize() {
-        if (mController != null) {
-            return mController.getVideoSize();
-        } else if (mPlayer != null) {
-            return mPlayer.getVideoSize();
-        }
-        return new androidx.media2.common.VideoSize(0, 0);
-    }
-
-    @NonNull
-    List<androidx.media2.common.SessionPlayer.TrackInfo> getTracks() {
-        if (mController != null) {
-            return mController.getTracks();
-        } else if (mPlayer != null) {
-            return mPlayer.getTracks();
-        }
-        return Collections.emptyList();
-    }
-
-    @Nullable
-    androidx.media2.common.SessionPlayer.TrackInfo getSelectedTrack(int trackType) {
-        if (mController != null) {
-            return mController.getSelectedTrack(trackType);
-        } else if (mPlayer != null) {
-            return mPlayer.getSelectedTrack(trackType);
-        }
-        return null;
-    }
-
-    ListenableFuture<? extends androidx.media2.common.BaseResult> setSurface(Surface surface) {
-        if (mController != null) {
-            return mController.setSurface(surface);
-        } else if (mPlayer != null) {
-            return mPlayer.setSurface(surface);
-        }
-        return null;
-    }
-
-    int getCurrentMediaItemIndex() {
-        if (mController != null) {
-            return mController.getCurrentMediaItemIndex();
-        } else if (mPlayer != null) {
-            return mPlayer.getCurrentMediaItemIndex();
-        }
-        return androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX;
-    }
-
-    int getPreviousMediaItemIndex() {
-        if (mController != null) {
-            return mController.getPreviousMediaItemIndex();
-        } else if (mPlayer != null) {
-            return mPlayer.getPreviousMediaItemIndex();
-        }
-        return androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX;
-    }
-
-    int getNextMediaItemIndex() {
-        if (mController != null) {
-            return mController.getNextMediaItemIndex();
-        } else if (mPlayer != null) {
-            return mPlayer.getNextMediaItemIndex();
-        }
-        return androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX;
-    }
-
-    private class MediaControllerCallback
-            extends androidx.media2.session.MediaController.ControllerCallback {
-        MediaControllerCallback() {
-        }
-
-        @Override
-        public void onConnected(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.session.SessionCommandGroup allowedCommands) {
-            mWrapperCallback.onConnected(PlayerWrapper.this);
-            updateAndNotifyCachedStates();
-        }
-
-        @Override
-        public void onAllowedCommandsChanged(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.session.SessionCommandGroup commands) {
-            if (ObjectsCompat.equals(mAllowedCommands, commands)) return;
-            mAllowedCommands = commands;
-            mWrapperCallback.onAllowedCommandsChanged(PlayerWrapper.this, commands);
-        }
-
-        @Override
-        public void onPlayerStateChanged(
-                @NonNull androidx.media2.session.MediaController controller, int state) {
-            if (mSavedPlayerState == state) return;
-            mSavedPlayerState = state;
-            mWrapperCallback.onPlayerStateChanged(PlayerWrapper.this, state);
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(
-                @NonNull androidx.media2.session.MediaController controller, float speed) {
-            mWrapperCallback.onPlaybackSpeedChanged(PlayerWrapper.this, speed);
-        }
-
-        @Override
-        public void onSeekCompleted(
-                @NonNull androidx.media2.session.MediaController controller, long position) {
-            mWrapperCallback.onSeekCompleted(PlayerWrapper.this, position);
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(
-                @NonNull androidx.media2.session.MediaController controller,
-                @Nullable androidx.media2.common.MediaItem item) {
-            mMediaMetadata = item == null ? null : item.getMetadata();
-            mWrapperCallback.onCurrentMediaItemChanged(PlayerWrapper.this, item);
-        }
-
-        @Override
-        public void onPlaylistChanged(
-                @NonNull androidx.media2.session.MediaController controller,
-                @Nullable List<androidx.media2.common.MediaItem> list,
-                @Nullable androidx.media2.common.MediaMetadata metadata) {
-            mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
-        }
-
-        @Override
-        public void onPlaybackCompleted(
-                @NonNull androidx.media2.session.MediaController controller) {
-            mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
-        }
-
-        @Override
-        public void onVideoSizeChanged(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.common.VideoSize videoSize) {
-            mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, videoSize);
-        }
-
-        @Override
-        public void onSubtitleData(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.common.MediaItem item,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo track,
-                @NonNull androidx.media2.common.SubtitleData data) {
-            mWrapperCallback.onSubtitleData(PlayerWrapper.this, item, track, data);
-        }
-
-        @Override
-        public void onTracksChanged(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull List<androidx.media2.common.SessionPlayer.TrackInfo> tracks) {
-            mWrapperCallback.onTracksChanged(PlayerWrapper.this, tracks);
-        }
-
-        @Override
-        public void onTrackSelected(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            mWrapperCallback.onTrackSelected(PlayerWrapper.this, trackInfo);
-        }
-
-        @Override
-        public void onTrackDeselected(
-                @NonNull androidx.media2.session.MediaController controller,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            mWrapperCallback.onTrackDeselected(PlayerWrapper.this, trackInfo);
-        }
-    }
-
-    private void notifyNonCachedStates() {
-        mWrapperCallback.onPlaybackSpeedChanged(this, getPlaybackSpeed());
-
-        List<androidx.media2.common.SessionPlayer.TrackInfo> trackInfos = getTracks();
-        if (trackInfos != null) {
-            mWrapperCallback.onTracksChanged(PlayerWrapper.this, trackInfos);
-        }
-        androidx.media2.common.MediaItem item = getCurrentMediaItem();
-        if (item != null) {
-            mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, getVideoSize());
-        }
-    }
-
-    private class SessionPlayerCallback
-            extends androidx.media2.common.SessionPlayer.PlayerCallback {
-        SessionPlayerCallback() {
-        }
-
-        @Override
-        public void onPlayerStateChanged(
-                @NonNull androidx.media2.common.SessionPlayer player, int playerState) {
-            if (mSavedPlayerState == playerState) return;
-            mSavedPlayerState = playerState;
-            mWrapperCallback.onPlayerStateChanged(PlayerWrapper.this, playerState);
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(
-                @NonNull androidx.media2.common.SessionPlayer player, float playbackSpeed) {
-            mWrapperCallback.onPlaybackSpeedChanged(PlayerWrapper.this, playbackSpeed);
-        }
-
-        @Override
-        public void onSeekCompleted(
-                @NonNull androidx.media2.common.SessionPlayer player, long position) {
-            mWrapperCallback.onSeekCompleted(PlayerWrapper.this, position);
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull androidx.media2.common.MediaItem item) {
-            mMediaMetadata = item == null ? null : item.getMetadata();
-            mWrapperCallback.onCurrentMediaItemChanged(PlayerWrapper.this, item);
-        }
-
-        @Override
-        public void onPlaylistChanged(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @Nullable List<androidx.media2.common.MediaItem> list,
-                @Nullable androidx.media2.common.MediaMetadata metadata) {
-            mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
-        }
-
-        @Override
-        public void onPlaybackCompleted(@NonNull androidx.media2.common.SessionPlayer player) {
-            mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
-        }
-
-        @Override
-        public void onVideoSizeChanged(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull androidx.media2.common.VideoSize size) {
-            mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, size);
-        }
-
-        @Override
-        public void onSubtitleData(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull androidx.media2.common.MediaItem item,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo track,
-                @NonNull androidx.media2.common.SubtitleData data) {
-            mWrapperCallback.onSubtitleData(PlayerWrapper.this, item, track, data);
-        }
-
-        @Override
-        public void onTracksChanged(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull List<androidx.media2.common.SessionPlayer.TrackInfo> tracks) {
-            mWrapperCallback.onTracksChanged(PlayerWrapper.this, tracks);
-        }
-
-        @Override
-        public void onTrackSelected(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            mWrapperCallback.onTrackSelected(PlayerWrapper.this, trackInfo);
-        }
-
-        @Override
-        public void onTrackDeselected(
-                @NonNull androidx.media2.common.SessionPlayer player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            mWrapperCallback.onTrackDeselected(PlayerWrapper.this, trackInfo);
-        }
-    }
-
-    abstract static class PlayerCallback {
-        void onConnected(@NonNull PlayerWrapper player) {
-        }
-
-        void onAllowedCommandsChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.session.SessionCommandGroup commands) {}
-
-        void onCurrentMediaItemChanged(
-                @NonNull PlayerWrapper player, @Nullable androidx.media2.common.MediaItem item) {}
-
-        void onPlaylistChanged(
-                @NonNull PlayerWrapper player,
-                @Nullable List<androidx.media2.common.MediaItem> list,
-                @Nullable androidx.media2.common.MediaMetadata metadata) {}
-
-        void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
-        }
-        void onPlaybackSpeedChanged(@NonNull PlayerWrapper player, float speed) {
-        }
-        void onSeekCompleted(@NonNull PlayerWrapper player, long position) {
-        }
-        void onPlaybackCompleted(@NonNull PlayerWrapper player) {
-        }
-
-        void onVideoSizeChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.VideoSize videoSize) {}
-
-        void onTracksChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull List<androidx.media2.common.SessionPlayer.TrackInfo> tracks) {}
-
-        void onTrackSelected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {}
-
-        void onTrackDeselected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {}
-
-        void onSubtitleData(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.MediaItem item,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo track,
-                @NonNull androidx.media2.common.SubtitleData data) {}
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/SelectiveLayout.java b/media2/media2-widget/src/main/java/androidx/media2/widget/SelectiveLayout.java
deleted file mode 100644
index 9f21376..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/SelectiveLayout.java
+++ /dev/null
@@ -1,207 +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.media2.widget;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.AttrRes;
-import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-class SelectiveLayout extends MediaViewGroup {
-    SelectiveLayout(@NonNull Context context) {
-        super(context);
-    }
-
-    SelectiveLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    SelectiveLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams;
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-    }
-
-    @Override
-    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
-        if (lp instanceof LayoutParams) {
-            return lp;
-        }
-        return new LayoutParams(lp);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int count = getChildCount();
-
-        int maxWidth = 0;
-        int maxHeight = 0;
-        int childState = 0;
-
-        // Measure its children of which forceMatchParent is false
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (child.getVisibility() != View.GONE && !lp.forceMatchParent) {
-                measureChild(child, widthMeasureSpec, heightMeasureSpec);
-                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
-                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
-                childState = childState | child.getMeasuredState();
-            }
-        }
-
-        // Account for padding too
-        maxWidth += getPositivePaddingLeft() + getPositivePaddingRight();
-        maxHeight += getPositivePaddingTop() + getPositivePaddingBottom();
-
-        // Check against our minimum height and width
-        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
-
-        if (Build.VERSION.SDK_INT >= 23) {
-            // Check against our foreground's minimum height and width
-            final Drawable drawable = Api23Impl.getForeground(this);
-            if (drawable != null) {
-                maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
-                maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
-            }
-        }
-
-        setMeasuredDimension(
-                resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-                resolveSizeAndState(maxHeight, heightMeasureSpec,
-                        childState << View.MEASURED_HEIGHT_STATE_SHIFT));
-
-        // Measure its children of which forceMatchParent is true
-        final int widthMeasureSpecForChild = MeasureSpec.makeMeasureSpec(
-                getMeasuredWidth() - (getPositivePaddingLeft() + getPositivePaddingRight()),
-                MeasureSpec.EXACTLY);
-        final int heightMeasureSpecForChild = MeasureSpec.makeMeasureSpec(
-                getMeasuredHeight() - (getPositivePaddingTop() + getPositivePaddingBottom()),
-                MeasureSpec.EXACTLY);
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (child.getVisibility() != View.GONE && lp.forceMatchParent) {
-                child.measure(widthMeasureSpecForChild, heightMeasureSpecForChild);
-            }
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final int count = getChildCount();
-
-        final int parentLeft = getPositivePaddingLeft();
-        final int parentRight = right - left - getPositivePaddingRight();
-
-        final int parentTop = getPositivePaddingTop();
-        final int parentBottom = bottom - top - getPositivePaddingBottom();
-
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE) {
-                final int width = child.getMeasuredWidth();
-                final int height = child.getMeasuredHeight();
-
-                final int childLeft = parentLeft + (parentRight - parentLeft - width) / 2;
-                final int childTop = parentTop + (parentBottom - parentTop - height) / 2;
-
-                child.layout(childLeft, childTop, childLeft + width, childTop + height);
-            }
-        }
-    }
-
-    @Override
-    public boolean shouldDelayChildPressedState() {
-        return false;
-    }
-
-    private int getPositivePaddingLeft() {
-        return Math.max(getPaddingLeft(), 0);
-    }
-
-    private int getPositivePaddingRight() {
-        return Math.max(getPaddingRight(), 0);
-    }
-
-    private int getPositivePaddingTop() {
-        return Math.max(getPaddingTop(), 0);
-    }
-
-    private int getPositivePaddingBottom() {
-        return Math.max(getPaddingBottom(), 0);
-    }
-
-    static class LayoutParams extends ViewGroup.LayoutParams {
-        /**
-         * If set, the measured size of the child will not be counted to determine the size of its
-         * parent, and it will be measured after its parent has been measured so that its size will
-         * be matched to its parent.
-         */
-        public boolean forceMatchParent;
-
-        LayoutParams() {
-            this(MATCH_PARENT, MATCH_PARENT);
-        }
-
-        LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-        }
-
-        LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-        }
-    }
-
-    @RequiresApi(23)
-    static final class Api23Impl {
-
-        @DoNotInline
-        static Drawable getForeground(View view) {
-            return view.getForeground();
-        }
-
-        private Api23Impl() {}
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleAnchorView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleAnchorView.java
deleted file mode 100644
index 33c6be7..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleAnchorView.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.widget.SubtitleController.Anchor;
-import androidx.media2.widget.SubtitleTrack.RenderingWidget;
-
-class SubtitleAnchorView extends View implements Anchor {
-    private static final String TAG = "SubtitleAnchorView";
-
-    private RenderingWidget mSubtitleWidget;
-    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
-
-    SubtitleAnchorView(Context context) {
-        this(context, null);
-    }
-
-    SubtitleAnchorView(Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    SubtitleAnchorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
-        if (mSubtitleWidget == subtitleWidget) {
-            return;
-        }
-
-        final boolean attachedToWindow = this.isAttachedToWindow();
-        if (mSubtitleWidget != null) {
-            if (attachedToWindow) {
-                mSubtitleWidget.onDetachedFromWindow();
-            }
-
-            mSubtitleWidget.setOnChangedListener(null);
-        }
-        mSubtitleWidget = subtitleWidget;
-
-        if (subtitleWidget != null) {
-            if (mSubtitlesChangedListener == null) {
-                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
-                    @Override
-                    public void onChanged(@NonNull RenderingWidget renderingWidget) {
-                        invalidate();
-                    }
-                };
-            }
-
-            setWillNotDraw(false);
-            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
-
-            if (attachedToWindow) {
-                subtitleWidget.onAttachedToWindow();
-                requestLayout();
-            }
-        } else {
-            setWillNotDraw(true);
-        }
-
-        invalidate();
-    }
-
-    @Override
-    public Looper getSubtitleLooper() {
-        return Looper.getMainLooper();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        if (mSubtitleWidget != null) {
-            mSubtitleWidget.onAttachedToWindow();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        if (mSubtitleWidget != null) {
-            mSubtitleWidget.onDetachedFromWindow();
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        if (mSubtitleWidget != null) {
-            final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-            final int height = getHeight() - getPaddingTop() - getPaddingBottom();
-
-            mSubtitleWidget.setSize(width, height);
-        }
-    }
-
-    @Override
-    protected void onDraw(@NonNull Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (mSubtitleWidget != null) {
-            final int saveCount = canvas.save();
-            canvas.translate(getPaddingLeft(), getPaddingTop());
-            mSubtitleWidget.draw(canvas);
-            canvas.restoreToCount(saveCount);
-        }
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        // Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage.
-        return "androidx.media2.widget.SubtitleAnchorView";
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleController.java b/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleController.java
deleted file mode 100644
index 6db85c7..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleController.java
+++ /dev/null
@@ -1,520 +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.media2.widget;
-
-import android.content.Context;
-import android.media.MediaFormat;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.TrackInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.view.accessibility.CaptioningManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.media2.widget.SubtitleTrack.RenderingWidget;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-// Note: This is forked from android.media.SubtitleController since P
-/**
- * The subtitle controller provides the architecture to display subtitles for a
- * media source.  It allows specifying which tracks to display, on which anchor
- * to display them, and also allows adding external, out-of-band subtitle tracks.
- */
-class SubtitleController {
-    private MediaTimeProvider mTimeProvider;
-    private ArrayList<Renderer> mRenderers;
-    private ArrayList<SubtitleTrack> mTracks;
-    private final Object mRenderersLock = new Object();
-    private final Object mTracksLock = new Object();
-    private SubtitleTrack mSelectedTrack;
-    private CaptioningManager mCaptioningManager;
-    private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener;
-
-    private Handler mHandler;
-
-    private static final int WHAT_SHOW = 1;
-    private static final int WHAT_HIDE = 2;
-    private static final int WHAT_SELECT_TRACK = 3;
-    private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
-
-    private final Handler.Callback mCallback = new Handler.Callback() {
-        @Override
-        public boolean handleMessage(Message msg) {
-            switch (msg.what) {
-                case WHAT_SHOW:
-                    doShow();
-                    return true;
-                case WHAT_HIDE:
-                    doHide();
-                    return true;
-                case WHAT_SELECT_TRACK:
-                    doSelectTrack((SubtitleTrack) msg.obj);
-                    return true;
-                case WHAT_SELECT_DEFAULT_TRACK:
-                    doSelectDefaultTrack();
-                    return true;
-                default:
-                    return false;
-            }
-        }
-    };
-
-    SubtitleController(@NonNull Context context) {
-        this(context, null, null);
-    }
-
-    /**
-     * Creates a subtitle controller for a media playback object that implements
-     * the MediaTimeProvider interface.
-     *
-     * @param timeProvider
-     */
-    SubtitleController(
-            @NonNull Context context,
-            @Nullable MediaTimeProvider timeProvider,
-            @Nullable Listener listener) {
-        mTimeProvider = timeProvider;
-        mListener = listener;
-
-        mRenderers = new ArrayList<Renderer>();
-        mTracks = new ArrayList<SubtitleTrack>();
-        mCaptioningManager =
-                (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
-        mCaptioningChangeListener = new CaptioningManager.CaptioningChangeListener() {
-                @Override
-                public void onEnabledChanged(boolean enabled) {
-                    selectDefaultTrack();
-                }
-
-                @Override
-                public void onLocaleChanged(Locale locale) {
-                    selectDefaultTrack();
-                }
-        };
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        mCaptioningManager.removeCaptioningChangeListener(mCaptioningChangeListener);
-        super.finalize();
-    }
-
-    /**
-     * @return the available subtitle tracks for this media. These include
-     * the tracks found by {@link MediaPlayer} as well as any tracks added
-     * manually via {@link #addTrack}.
-     */
-    public SubtitleTrack[] getTracks() {
-        synchronized (mTracksLock) {
-            SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
-            mTracks.toArray(tracks);
-            return tracks;
-        }
-    }
-
-    /**
-     * @return the currently selected subtitle track
-     */
-    public SubtitleTrack getSelectedTrack() {
-        return mSelectedTrack;
-    }
-
-    private RenderingWidget getRenderingWidget() {
-        if (mSelectedTrack == null) {
-            return null;
-        }
-        return mSelectedTrack.getRenderingWidget();
-    }
-
-    /**
-     * Selects a subtitle track.  As a result, this track will receive
-     * in-band data from the {@link MediaPlayer}.  However, this does
-     * not change the subtitle visibility.
-     *
-     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
-     *
-     * @param track The subtitle track to select.  This must be one of the
-     *              tracks in {@link #getTracks}.
-     * @return true if the track was successfully selected.
-     */
-    public boolean selectTrack(SubtitleTrack track) {
-        if (track != null && !mTracks.contains(track)) {
-            return false;
-        }
-
-        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
-        return true;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void doSelectTrack(SubtitleTrack track) {
-        mTrackIsExplicit = true;
-        if (mSelectedTrack == track) {
-            return;
-        }
-
-        if (mSelectedTrack != null) {
-            mSelectedTrack.hide();
-            mSelectedTrack.setTimeProvider(null);
-        }
-
-        mSelectedTrack = track;
-        if (mAnchor != null) {
-            mAnchor.setSubtitleWidget(getRenderingWidget());
-        }
-
-        if (mSelectedTrack != null) {
-            mSelectedTrack.setTimeProvider(mTimeProvider);
-            mSelectedTrack.show();
-        }
-
-        if (mListener != null) {
-            mListener.onSubtitleTrackSelected(track);
-        }
-    }
-
-    /**
-     * @return the default subtitle track based on system preferences, or null,
-     * if no such track exists in this manager.
-     *
-     * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT.
-     *
-     * 1. If captioning is disabled, only consider FORCED tracks. Otherwise,
-     * consider all tracks, but prefer non-FORCED ones.
-     * 2. If user selected "Default" caption language:
-     *   a. If there is a considered track with DEFAULT=yes, returns that track
-     *      (favor the first one in the current language if there are more than
-     *      one default tracks, or the first in general if none of them are in
-     *      the current language).
-     *   b. Otherwise, if there is a track with AUTOSELECT=yes in the current
-     *      language, return that one.
-     *   c. If there are no default tracks, and no autoselectable tracks in the
-     *      current language, return null.
-     * 3. If there is a track with the caption language, select that one.  Prefer
-     * the one with AUTOSELECT=no.
-     *
-     * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
-     * and FORCED=no.
-     */
-    public SubtitleTrack getDefaultTrack() {
-        SubtitleTrack bestTrack = null;
-        int bestScore = -1;
-
-        Locale selectedLocale = mCaptioningManager.getLocale();
-        Locale locale = selectedLocale;
-        if (locale == null) {
-            locale = Locale.getDefault();
-        }
-        boolean selectForced = !mCaptioningManager.isEnabled();
-
-        synchronized (mTracksLock) {
-            for (SubtitleTrack track: mTracks) {
-                MediaFormat format = track.getFormat();
-                String language = format.getString(MediaFormat.KEY_LANGUAGE);
-                boolean forced = MediaFormatUtil
-                        .getInteger(format, MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0;
-                boolean autoselect = MediaFormatUtil
-                        .getInteger(format, MediaFormat.KEY_IS_AUTOSELECT, 1) != 0;
-                boolean is_default = MediaFormatUtil
-                        .getInteger(format, MediaFormat.KEY_IS_DEFAULT, 0) != 0;
-
-                boolean languageMatches = locale == null
-                        || locale.getLanguage().equals("")
-                        || locale.getISO3Language().equals(language)
-                        || locale.getLanguage().equals(language);
-                // is_default is meaningless unless caption language is 'default'
-                int score = (forced ? 0 : 8)
-                        + (((selectedLocale == null) && is_default) ? 4 : 0)
-                        + (autoselect ? 0 : 2) + (languageMatches ? 1 : 0);
-
-                if (selectForced && !forced) {
-                    continue;
-                }
-
-                // we treat null locale/language as matching any language
-                if ((selectedLocale == null && is_default)
-                        || (languageMatches && (autoselect || forced || selectedLocale != null))) {
-                    if (score > bestScore) {
-                        bestScore = score;
-                        bestTrack = track;
-                    }
-                }
-            }
-        }
-        return bestTrack;
-    }
-
-    static class MediaFormatUtil {
-        MediaFormatUtil() { }
-        static int getInteger(MediaFormat format, String name, int defaultValue) {
-            try {
-                return format.getInteger(name);
-            } catch (NullPointerException | ClassCastException e) {
-                /* no such field or field of different type */
-            }
-            return defaultValue;
-        }
-    }
-
-    private boolean mTrackIsExplicit = false;
-    private boolean mVisibilityIsExplicit = false;
-
-    /** should be called from anchor thread */
-    public void selectDefaultTrack() {
-        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void doSelectDefaultTrack() {
-        if (mTrackIsExplicit) {
-            if (mVisibilityIsExplicit) {
-                return;
-            }
-            // If track selection is explicit, but visibility
-            // is not, it falls back to the captioning setting
-            boolean captionIsEnabledOnSystem = mCaptioningManager.isEnabled();
-            if (captionIsEnabledOnSystem
-                    || (mSelectedTrack != null && MediaFormatUtil.getInteger(
-                            mSelectedTrack.getFormat(),
-                            MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
-                show();
-            } else if (mSelectedTrack != null
-                    && mSelectedTrack.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                hide();
-            }
-            mVisibilityIsExplicit = false;
-        }
-
-        // We can have a default (forced) track even if captioning
-        // is not enabled.  This is handled by getDefaultTrack().
-        // Show this track unless subtitles were explicitly hidden.
-        SubtitleTrack track = getDefaultTrack();
-        if (track != null) {
-            selectTrack(track);
-            mTrackIsExplicit = false;
-            if (!mVisibilityIsExplicit) {
-                show();
-                mVisibilityIsExplicit = false;
-            }
-        }
-    }
-
-    /** must be called from anchor thread */
-    public void reset() {
-        hide();
-        selectTrack(null);
-        mTracks.clear();
-        mTrackIsExplicit = false;
-        mVisibilityIsExplicit = false;
-        mCaptioningManager.removeCaptioningChangeListener(mCaptioningChangeListener);
-    }
-
-    /**
-     * Adds a new, external subtitle track to the manager.
-     *
-     * @param format the format of the track that will include at least
-     *               the MIME type {@link MediaFormat@KEY_MIME}.
-     * @return the created {@link SubtitleTrack} object
-     */
-    public SubtitleTrack addTrack(MediaFormat format) {
-        synchronized (mRenderersLock) {
-            for (Renderer renderer: mRenderers) {
-                if (renderer.supports(format)) {
-                    SubtitleTrack track = renderer.createTrack(format);
-                    if (track != null) {
-                        synchronized (mTracksLock) {
-                            if (mTracks.size() == 0) {
-                                mCaptioningManager.addCaptioningChangeListener(
-                                        mCaptioningChangeListener);
-                            }
-                            mTracks.add(track);
-                        }
-                        return track;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Show the selected (or default) subtitle track.
-     *
-     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
-     */
-    public void show() {
-        processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void doShow() {
-        mVisibilityIsExplicit = true;
-        if (mSelectedTrack != null) {
-            mSelectedTrack.show();
-        }
-    }
-
-    /**
-     * Hide the selected (or default) subtitle track.
-     *
-     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
-     */
-    public void hide() {
-        processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void doHide() {
-        mVisibilityIsExplicit = true;
-        if (mSelectedTrack != null) {
-            mSelectedTrack.hide();
-        }
-    }
-
-    /**
-     * Interface for supporting a single or multiple subtitle types in {@link MediaPlayer}.
-     */
-    public abstract static class Renderer {
-        /**
-         * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new
-         * subtitle track is detected, to see if it should use this object to
-         * parse and display this subtitle track.
-         *
-         * @param format the format of the track that will include at least
-         *               the MIME type {@link MediaFormat@KEY_MIME}.
-         *
-         * @return true if and only if the track format is supported by this
-         * renderer
-         */
-        public abstract boolean supports(@NonNull MediaFormat format);
-
-        /**
-         * Called by {@link MediaPlayer}'s {@link SubtitleController} for each
-         * subtitle track that was detected and is supported by this object to
-         * create a {@link SubtitleTrack} object.  This object will be created
-         * for each track that was found.  If the track is selected for display,
-         * this object will be used to parse and display the track data.
-         *
-         * @param format the format of the track that will include at least
-         *               the MIME type {@link MediaFormat@KEY_MIME}.
-         * @return a {@link SubtitleTrack} object that will be used to parse
-         * and render the subtitle track.
-         */
-        public abstract @NonNull SubtitleTrack createTrack(@NonNull MediaFormat format);
-    }
-
-    /**
-     * Add support for a subtitle format in {@link MediaPlayer}.
-     *
-     * @param renderer a {@link SubtitleController.Renderer} object that adds
-     *                 support for a subtitle format.
-     */
-    public void registerRenderer(Renderer renderer) {
-        synchronized (mRenderersLock) {
-            // TODO how to get available renderers in the system
-            if (!mRenderers.contains(renderer)) {
-                // TODO should added renderers override existing ones (to allow replacing?)
-                mRenderers.add(renderer);
-            }
-        }
-    }
-
-    /**
-     * Returns true if one of the registered renders supports given media format.
-     *
-     * @param format a {@link MediaFormat} object
-     * @return true if this SubtitleController has a renderer that supports
-     * the media format.
-     */
-    public boolean hasRendererFor(MediaFormat format) {
-        synchronized (mRenderersLock) {
-            // TODO how to get available renderers in the system
-            for (Renderer renderer: mRenderers) {
-                if (renderer.supports(format)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Subtitle anchor, an object that is able to display a subtitle renderer,
-     * e.g. a VideoView.
-     */
-    interface Anchor {
-        /**
-         * Anchor should use the supplied subtitle rendering widget, or
-         * none if it is null.
-         */
-        void setSubtitleWidget(RenderingWidget subtitleWidget);
-
-        /**
-         * Anchors provide the looper on which all track visibility changes
-         * (track.show/hide, setSubtitleWidget) will take place.
-         */
-        Looper getSubtitleLooper();
-    }
-
-    private Anchor mAnchor;
-
-    /**
-     *  called from anchor's looper (if any, both when unsetting and
-     *  setting)
-     */
-    public void setAnchor(Anchor anchor) {
-        if (mAnchor == anchor) {
-            return;
-        }
-
-        if (mAnchor != null) {
-            mAnchor.setSubtitleWidget(null);
-        }
-        mAnchor = anchor;
-        mHandler = null;
-        if (mAnchor != null) {
-            mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
-            mAnchor.setSubtitleWidget(getRenderingWidget());
-        }
-    }
-
-    private void processOnAnchor(Message m) {
-        if (Looper.myLooper() == mHandler.getLooper()) {
-            mHandler.dispatchMessage(m);
-        } else {
-            mHandler.sendMessage(m);
-        }
-    }
-
-    /**
-     * Listener for when subtitle track has been selected.
-     */
-    interface Listener {
-        /**
-         * Called when a subtitle track has been selected.
-         *
-         * @param track selected subtitle track or null
-         */
-        void onSubtitleTrackSelected(SubtitleTrack track);
-    }
-
-    private Listener mListener;
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleTrack.java b/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleTrack.java
deleted file mode 100644
index 377e7003..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleTrack.java
+++ /dev/null
@@ -1,714 +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.media2.widget;
-
-import android.graphics.Canvas;
-import android.media.MediaFormat;
-import android.media.MediaPlayer.TrackInfo;
-import android.os.Handler;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-// Note: This is forked from android.media.SubtitleTrack since P
-/**
- * A subtitle track abstract base class that is responsible for parsing and displaying an instance
- * of a particular type of subtitle.
- */
-@SuppressWarnings("deprecation")
-abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener {
-    private static final String TAG = "SubtitleTrack";
-    private long mLastUpdateTimeMs;
-    private long mLastTimeMs;
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    Runnable mRunnable;
-
-    private final LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>();
-    private final LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>();
-
-    private CueList mCues;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final ArrayList<Cue> mActiveCues = new ArrayList<Cue>();
-    protected boolean mVisible;
-
-    public boolean DEBUG = false;
-
-    @SuppressWarnings("deprecation")
-    protected Handler mHandler = new Handler();
-
-    private MediaFormat mFormat;
-
-    SubtitleTrack(MediaFormat format) {
-        mFormat = format;
-        mCues = new CueList();
-        clearActiveCues();
-        mLastTimeMs = -1;
-    }
-
-    public final MediaFormat getFormat() {
-        return mFormat;
-    }
-
-    private long mNextScheduledTimeMs = -1;
-
-    /** Called when there is input data for the subtitle track. */
-    public void onData(androidx.media2.common.SubtitleData data) {
-        long runID = data.getStartTimeUs() + 1;
-        onData(data.getData(), true /* eos */, runID);
-        setRunDiscardTimeMs(
-                runID,
-                (data.getStartTimeUs() + data.getDurationUs()) / 1000);
-    }
-
-    /**
-     * Called when there is input data for the subtitle track.  The
-     * complete subtitle for a track can include multiple whole units
-     * (runs).  Each of these units can have multiple sections.  The
-     * contents of a run are submitted in sequential order, with eos
-     * indicating the last section of the run.  Calls from different
-     * runs must not be intermixed.
-     *
-     * @param data subtitle data byte buffer
-     * @param eos true if this is the last section of the run.
-     * @param runID mostly-unique ID for this run of data.  Subtitle cues
-     *              with runID of 0 are discarded immediately after
-     *              display.  Cues with runID of ~0 are discarded
-     *              only at the deletion of the track object.  Cues
-     *              with other runID-s are discarded at the end of the
-     *              run, which defaults to the latest timestamp of
-     *              any of its cues (with this runID).
-     */
-    protected abstract void onData(byte[] data, boolean eos, long runID);
-
-    /**
-     * Called when adding the subtitle rendering widget to the view hierarchy,
-     * as well as when showing or hiding the subtitle track, or when the video
-     * surface position has changed.
-     *
-     * @return the widget that renders this subtitle track. For most renderers
-     *         there should be a single shared instance that is used for all
-     *         tracks supported by that renderer, as at most one subtitle track
-     *         is visible at one time.
-     */
-    public abstract RenderingWidget getRenderingWidget();
-
-    /**
-     * Called when the active cues have changed, and the contents of the subtitle
-     * view should be updated.
-     */
-    public abstract void updateView(ArrayList<Cue> activeCues);
-
-    protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {
-        // out-of-order times mean seeking or new active cues being added
-        // (during their own timespan)
-        if (rebuild || mLastUpdateTimeMs > timeMs) {
-            clearActiveCues();
-        }
-
-        for (Iterator<Pair<Long, Cue>> it =
-                mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
-            Pair<Long, Cue> event = it.next();
-            Cue cue = event.second;
-
-            if (cue.mEndTimeMs == event.first) {
-                // remove past cues
-                if (DEBUG) Log.v(TAG, "Removing " + cue);
-                mActiveCues.remove(cue);
-                if (cue.mRunID == 0) {
-                    it.remove();
-                }
-            } else if (cue.mStartTimeMs == event.first) {
-                // add new cues
-                // TRICKY: this will happen in start order
-                if (DEBUG) Log.v(TAG, "Adding " + cue);
-                if (cue.mInnerTimesMs != null) {
-                    cue.onTime(timeMs);
-                }
-                mActiveCues.add(cue);
-            } else if (cue.mInnerTimesMs != null) {
-                // cue is modified
-                cue.onTime(timeMs);
-            }
-        }
-
-        /* complete any runs */
-        while (mRunsByEndTime.size() > 0 && mRunsByEndTime.keyAt(0) <= timeMs) {
-            removeRunsByEndTimeIndex(0); // removes element
-        }
-        mLastUpdateTimeMs = timeMs;
-    }
-
-    private void removeRunsByEndTimeIndex(int ix) {
-        Run run = mRunsByEndTime.valueAt(ix);
-        while (run != null) {
-            Cue cue = run.mFirstCue;
-            while (cue != null) {
-                mCues.remove(cue);
-                Cue nextCue = cue.mNextInRun;
-                cue.mNextInRun = null;
-                cue = nextCue;
-            }
-            mRunsByID.remove(run.mRunID);
-            Run nextRun = run.mNextRunAtEndTimeMs;
-            run.mPrevRunAtEndTimeMs = null;
-            run.mNextRunAtEndTimeMs = null;
-            run = nextRun;
-        }
-        mRunsByEndTime.removeAt(ix);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        /* remove all cues (untangle all cross-links) */
-        int size = mRunsByEndTime.size();
-        for (int ix = size - 1; ix >= 0; ix--) {
-            removeRunsByEndTimeIndex(ix);
-        }
-
-        super.finalize();
-    }
-
-    private synchronized void takeTime(long timeMs) {
-        mLastTimeMs = timeMs;
-    }
-
-    protected synchronized void clearActiveCues() {
-        if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
-        mActiveCues.clear();
-        mLastUpdateTimeMs = -1;
-    }
-
-    protected void scheduleTimedEvents() {
-        /* get times for the next event */
-        if (mTimeProvider != null) {
-            mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
-            if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
-            mTimeProvider.notifyAt(mNextScheduledTimeMs >= 0
-                    ? (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME, this);
-        }
-    }
-
-    @Override
-    public void onTimedEvent(long timeUs) {
-        if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
-        synchronized (this) {
-            long timeMs = timeUs / 1000;
-            updateActiveCues(false, timeMs);
-            takeTime(timeMs);
-        }
-        updateView(mActiveCues);
-        scheduleTimedEvents();
-    }
-
-    @Override
-    public void onSeek(long timeUs) {
-        if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
-        synchronized (this) {
-            long timeMs = timeUs / 1000;
-            updateActiveCues(true, timeMs);
-            takeTime(timeMs);
-        }
-        updateView(mActiveCues);
-        scheduleTimedEvents();
-    }
-
-    @Override
-    public void onStop() {
-        synchronized (this) {
-            if (DEBUG) Log.d(TAG, "onStop");
-            clearActiveCues();
-            mLastTimeMs = -1;
-        }
-        updateView(mActiveCues);
-        mNextScheduledTimeMs = -1;
-        mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
-    }
-
-    protected MediaTimeProvider mTimeProvider;
-
-    /**
-     * Shows subtitle rendering widget
-     */
-    public void show() {
-        if (mVisible) {
-            return;
-        }
-
-        mVisible = true;
-        RenderingWidget renderingWidget = getRenderingWidget();
-        if (renderingWidget != null) {
-            renderingWidget.setVisible(true);
-        }
-        if (mTimeProvider != null) {
-            mTimeProvider.scheduleUpdate(this);
-        }
-    }
-
-    /**
-     * Hides subtitle rendering widget
-     */
-    public void hide() {
-        if (!mVisible) {
-            return;
-        }
-
-        if (mTimeProvider != null) {
-            mTimeProvider.cancelNotifications(this);
-        }
-        RenderingWidget renderingWidget = getRenderingWidget();
-        if (renderingWidget != null) {
-            renderingWidget.setVisible(false);
-        }
-        mVisible = false;
-    }
-
-    protected synchronized boolean addCue(Cue cue) {
-        mCues.add(cue);
-
-        if (cue.mRunID != 0) {
-            Run run = mRunsByID.get(cue.mRunID);
-            if (run == null) {
-                run = new Run();
-                mRunsByID.put(cue.mRunID, run);
-                run.mEndTimeMs = cue.mEndTimeMs;
-            } else if (run.mEndTimeMs < cue.mEndTimeMs) {
-                run.mEndTimeMs = cue.mEndTimeMs;
-            }
-
-            // link-up cues in the same run
-            cue.mNextInRun = run.mFirstCue;
-            run.mFirstCue = cue;
-        }
-
-        // if a cue is added that should be visible, need to refresh view
-        long nowMs = -1;
-        if (mTimeProvider != null) {
-            try {
-                nowMs = mTimeProvider.getCurrentTimeUs(
-                        false /* precise */, true /* monotonic */) / 1000;
-            } catch (IllegalStateException e) {
-                // handle as it we are not playing
-            }
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, "mVisible=" + mVisible + ", "
-                    + cue.mStartTimeMs + " <= " + nowMs + ", "
-                    + cue.mEndTimeMs + " >= " + mLastTimeMs);
-        }
-
-        if (mVisible && cue.mStartTimeMs <= nowMs
-                // we don't trust nowMs, so check any cue since last callback
-                && cue.mEndTimeMs >= mLastTimeMs) {
-            if (mRunnable != null) {
-                mHandler.removeCallbacks(mRunnable);
-            }
-            final SubtitleTrack track = this;
-            final long thenMs = nowMs;
-            mRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    // even with synchronized, it is possible that we are going
-                    // to do multiple updates as the runnable could be already
-                    // running.
-                    synchronized (track) {
-                        mRunnable = null;
-                        updateActiveCues(true, thenMs);
-                        updateView(mActiveCues);
-                    }
-                }
-            };
-            // delay update so we don't update view on every cue.  TODO why 10?
-            if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
-                if (DEBUG) Log.v(TAG, "scheduling update");
-            } else {
-                if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
-            }
-            return true;
-        }
-
-        if (mVisible && cue.mEndTimeMs >= mLastTimeMs
-                && (cue.mStartTimeMs < mNextScheduledTimeMs || mNextScheduledTimeMs < 0)) {
-            scheduleTimedEvents();
-        }
-
-        return false;
-    }
-
-    /**
-     * Sets MediaTimeProvider
-     */
-    public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
-        if (mTimeProvider == timeProvider) {
-            return;
-        }
-        if (mTimeProvider != null) {
-            mTimeProvider.cancelNotifications(this);
-        }
-        mTimeProvider = timeProvider;
-        if (mTimeProvider != null) {
-            mTimeProvider.scheduleUpdate(this);
-        }
-    }
-
-
-    static class CueList {
-        private static final String TAG = "CueList";
-        // simplistic, inefficient implementation
-        SortedMap<Long, ArrayList<Cue>> mCues;
-        public boolean DEBUG = false;
-
-        private boolean addEvent(Cue cue, long timeMs) {
-            ArrayList<Cue> cues = mCues.get(timeMs);
-            if (cues == null) {
-                cues = new ArrayList<Cue>(2);
-                mCues.put(timeMs, cues);
-            } else if (cues.contains(cue)) {
-                // do not duplicate cues
-                return false;
-            }
-
-            cues.add(cue);
-            return true;
-        }
-
-        void removeEvent(Cue cue, long timeMs) {
-            ArrayList<Cue> cues = mCues.get(timeMs);
-            if (cues != null) {
-                cues.remove(cue);
-                if (cues.size() == 0) {
-                    mCues.remove(timeMs);
-                }
-            }
-        }
-
-        public void add(Cue cue) {
-            // ignore non-positive-duration cues
-            if (cue.mStartTimeMs >= cue.mEndTimeMs) return;
-
-            if (!addEvent(cue, cue.mStartTimeMs)) {
-                return;
-            }
-
-            long lastTimeMs = cue.mStartTimeMs;
-            if (cue.mInnerTimesMs != null) {
-                for (long timeMs: cue.mInnerTimesMs) {
-                    if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) {
-                        addEvent(cue, timeMs);
-                        lastTimeMs = timeMs;
-                    }
-                }
-            }
-
-            addEvent(cue, cue.mEndTimeMs);
-        }
-
-        public void remove(Cue cue) {
-            removeEvent(cue, cue.mStartTimeMs);
-            if (cue.mInnerTimesMs != null) {
-                for (long timeMs: cue.mInnerTimesMs) {
-                    removeEvent(cue, timeMs);
-                }
-            }
-            removeEvent(cue, cue.mEndTimeMs);
-        }
-
-        public Iterable<Pair<Long, Cue>> entriesBetween(
-                final long lastTimeMs, final long timeMs) {
-            return new Iterable<Pair<Long, Cue>>() {
-                @Override
-                public Iterator<Pair<Long, Cue>> iterator() {
-                    if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]=");
-                    try {
-                        return new EntryIterator(
-                                mCues.subMap(lastTimeMs + 1, timeMs + 1));
-                    } catch (IllegalArgumentException e) {
-                        return new EntryIterator(null);
-                    }
-                }
-            };
-        }
-
-        public long nextTimeAfter(long timeMs) {
-            SortedMap<Long, ArrayList<Cue>> tail = null;
-            try {
-                tail = mCues.tailMap(timeMs + 1);
-                if (tail != null) {
-                    return tail.firstKey();
-                } else {
-                    return -1;
-                }
-            } catch (IllegalArgumentException e) {
-                return -1;
-            } catch (NoSuchElementException e) {
-                return -1;
-            }
-        }
-
-        class EntryIterator implements Iterator<Pair<Long, Cue>> {
-            @Override
-            public boolean hasNext() {
-                return !mDone;
-            }
-
-            @Override
-            public Pair<Long, Cue> next() {
-                if (mDone) {
-                    throw new NoSuchElementException("");
-                }
-                mLastEntry = new Pair<Long, Cue>(
-                        mCurrentTimeMs, mListIterator.next());
-                mLastListIterator = mListIterator;
-                if (!mListIterator.hasNext()) {
-                    nextKey();
-                }
-                return mLastEntry;
-            }
-
-            @Override
-            public void remove() {
-                // only allow removing end tags
-                if (mLastListIterator == null
-                        || mLastEntry.second.mEndTimeMs != mLastEntry.first) {
-                    throw new IllegalStateException("");
-                }
-
-                // remove end-cue
-                mLastListIterator.remove();
-                mLastListIterator = null;
-                if (mCues.get(mLastEntry.first).size() == 0) {
-                    mCues.remove(mLastEntry.first);
-                }
-
-                // remove rest of the cues
-                Cue cue = mLastEntry.second;
-                removeEvent(cue, cue.mStartTimeMs);
-                if (cue.mInnerTimesMs != null) {
-                    for (long timeMs: cue.mInnerTimesMs) {
-                        removeEvent(cue, timeMs);
-                    }
-                }
-            }
-
-            EntryIterator(SortedMap<Long, ArrayList<Cue>> cues) {
-                if (DEBUG) Log.v(TAG, cues + "");
-                mRemainingCues = cues;
-                mLastListIterator = null;
-                nextKey();
-            }
-
-            private void nextKey() {
-                do {
-                    try {
-                        if (mRemainingCues == null) {
-                            throw new NoSuchElementException("");
-                        }
-                        mCurrentTimeMs = mRemainingCues.firstKey();
-                        mListIterator =
-                            mRemainingCues.get(mCurrentTimeMs).iterator();
-                        try {
-                            mRemainingCues =
-                                mRemainingCues.tailMap(mCurrentTimeMs + 1);
-                        } catch (IllegalArgumentException e) {
-                            mRemainingCues = null;
-                        }
-                        mDone = false;
-                    } catch (NoSuchElementException e) {
-                        mDone = true;
-                        mRemainingCues = null;
-                        mListIterator = null;
-                        return;
-                    }
-                } while (!mListIterator.hasNext());
-            }
-
-            private long mCurrentTimeMs;
-            private Iterator<Cue> mListIterator;
-            private boolean mDone;
-            private SortedMap<Long, ArrayList<Cue>> mRemainingCues;
-            private Iterator<Cue> mLastListIterator;
-            private Pair<Long, Cue> mLastEntry;
-        }
-
-        CueList() {
-            mCues = new TreeMap<Long, ArrayList<Cue>>();
-        }
-    }
-
-    /** Cue has timing information
-     */
-    public static class Cue {
-        public long mStartTimeMs;
-        public long mEndTimeMs;
-        public @Nullable long[] mInnerTimesMs;
-        public long mRunID;
-
-        public @Nullable Cue mNextInRun;
-
-        /**
-         * Called to inform current timeMs to the cue
-         */
-        public void onTime(long timeMs) { }
-    }
-
-    /** update mRunsByEndTime (with default end time) */
-    protected void finishedRun(long runID) {
-        if (runID != 0 && runID != ~0) {
-            Run run = mRunsByID.get(runID);
-            if (run != null) {
-                run.storeByEndTimeMs(mRunsByEndTime);
-            }
-        }
-    }
-
-    /** update mRunsByEndTime with given end time */
-    public void setRunDiscardTimeMs(long runID, long timeMs) {
-        if (runID != 0 && runID != ~0) {
-            Run run = mRunsByID.get(runID);
-            if (run != null) {
-                run.mEndTimeMs = timeMs;
-                run.storeByEndTimeMs(mRunsByEndTime);
-            }
-        }
-    }
-
-    /** whether this is a text track who fires events instead getting rendered */
-    public int getTrackType() {
-        return getRenderingWidget() == null
-                ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
-                : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
-    }
-
-
-    private static class Run {
-        public Cue mFirstCue;
-        public Run mNextRunAtEndTimeMs;
-        public Run mPrevRunAtEndTimeMs;
-        public long mEndTimeMs = -1;
-        public long mRunID = 0;
-        private long mStoredEndTimeMs = -1;
-
-        Run() {
-        }
-
-        public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) {
-            // remove old value if any
-            int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs);
-            if (ix >= 0) {
-                if (mPrevRunAtEndTimeMs == null) {
-                    if (mNextRunAtEndTimeMs == null) {
-                        runsByEndTime.removeAt(ix);
-                    } else {
-                        runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs);
-                    }
-                }
-                removeAtEndTimeMs();
-            }
-
-            // add new value
-            if (mEndTimeMs >= 0) {
-                mPrevRunAtEndTimeMs = null;
-                mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs);
-                if (mNextRunAtEndTimeMs != null) {
-                    mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this;
-                }
-                runsByEndTime.put(mEndTimeMs, this);
-                mStoredEndTimeMs = mEndTimeMs;
-            }
-        }
-
-        public void removeAtEndTimeMs() {
-            Run prev = mPrevRunAtEndTimeMs;
-
-            if (mPrevRunAtEndTimeMs != null) {
-                mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs;
-                mPrevRunAtEndTimeMs = null;
-            }
-            if (mNextRunAtEndTimeMs != null) {
-                mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev;
-                mNextRunAtEndTimeMs = null;
-            }
-        }
-    }
-
-    /**
-     * Interface for rendering subtitles onto a Canvas.
-     */
-    interface RenderingWidget {
-        /**
-         * Sets the widget's callback, which is used to send updates when the
-         * rendered data has changed.
-         *
-         * @param callback update callback
-         */
-        void setOnChangedListener(OnChangedListener callback);
-
-        /**
-         * Sets the widget's size.
-         *
-         * @param width width in pixels
-         * @param height height in pixels
-         */
-        void setSize(int width, int height);
-
-        /**
-         * Sets whether the widget should draw subtitles.
-         *
-         * @param visible true if subtitles should be drawn, false otherwise
-         */
-        void setVisible(boolean visible);
-
-        /**
-         * Renders subtitles onto a {@link Canvas}.
-         *
-         * @param c canvas on which to render subtitles
-         */
-        void draw(Canvas c);
-
-        /**
-         * Called when the widget is attached to a window.
-         */
-        void onAttachedToWindow();
-
-        /**
-         * Called when the widget is detached from a window.
-         */
-        void onDetachedFromWindow();
-
-        /**
-         * Callback used to send updates about changes to rendering data.
-         */
-        interface OnChangedListener {
-            /**
-             * Called when the rendering data has changed.
-             *
-             * @param renderingWidget the widget whose data has changed
-             */
-            void onChanged(@NonNull RenderingWidget renderingWidget);
-        }
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleView.java
deleted file mode 100644
index 44dd438..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/SubtitleView.java
+++ /dev/null
@@ -1,355 +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.media2.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Join;
-import android.graphics.Paint.Style;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.os.Build;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.CaptioningManager.CaptionStyle;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-/** Copied from frameworks/base/core/java/com/android/internal/widget/SubtitleView.java */
-class SubtitleView extends View {
-    // Ratio of inner padding to font size.
-    private static final float INNER_PADDING_RATIO = 0.125f;
-
-    // Styled dimensions.
-    private final float mCornerRadius;
-    private final float mOutlineWidth;
-    private final float mShadowRadius;
-    private final float mShadowOffsetX;
-    private final float mShadowOffsetY;
-
-    /** Temporary rectangle used for computing line bounds. */
-    private final RectF mLineBounds = new RectF();
-
-    /** Reusable spannable string builder used for holding text. */
-    private final SpannableStringBuilder mText = new SpannableStringBuilder();
-
-    private Alignment mAlignment;
-    private TextPaint mTextPaint;
-    private Paint mPaint;
-
-    private int mForegroundColor;
-    private int mBackgroundColor;
-    private int mEdgeColor;
-    private int mEdgeType;
-
-    private boolean mHasMeasurements;
-    private int mLastMeasuredWidth;
-    private StaticLayout mLayout;
-
-    private float mSpacingMult = 1;
-    private float mSpacingAdd = 0;
-    private int mInnerPaddingX = 0;
-
-    SubtitleView(Context context) {
-        this(context, null);
-    }
-
-    SubtitleView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        // Set up density-dependent properties.
-        // TODO: Move these to a default style.
-        final Resources res = getContext().getResources();
-        mCornerRadius = res
-                .getDimensionPixelSize(R.dimen.media2_widget_subtitle_corner_radius);
-        mOutlineWidth = res
-                .getDimensionPixelSize(R.dimen.media2_widget_subtitle_outline_width);
-        mShadowRadius = res
-                .getDimensionPixelSize(R.dimen.media2_widget_subtitle_shadow_radius);
-        mShadowOffsetX = res
-                .getDimensionPixelSize(R.dimen.media2_widget_subtitle_shadow_offset);
-        mShadowOffsetY = mShadowOffsetX;
-
-        mTextPaint = new TextPaint();
-        mTextPaint.setAntiAlias(true);
-        mTextPaint.setSubpixelText(true);
-
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-    }
-
-    public void setText(int resId) {
-        final CharSequence text = getContext().getText(resId);
-        setText(text);
-    }
-
-    public void setText(CharSequence text) {
-        mText.clear();
-        mText.append(text);
-
-        mHasMeasurements = false;
-
-        requestLayout();
-        invalidate();
-    }
-
-    public void setForegroundColor(int color) {
-        mForegroundColor = color;
-
-        invalidate();
-    }
-
-    @Override
-    public void setBackgroundColor(int color) {
-        mBackgroundColor = color;
-
-        invalidate();
-    }
-
-    public void setEdgeType(int edgeType) {
-        mEdgeType = edgeType;
-
-        invalidate();
-    }
-
-    public void setEdgeColor(int color) {
-        mEdgeColor = color;
-
-        invalidate();
-    }
-
-    /**
-     * Sets the text size in pixels.
-     *
-     * @param size the text size in pixels
-     */
-    public void setTextSize(float size) {
-        if (mTextPaint.getTextSize() != size) {
-            mTextPaint.setTextSize(size);
-            mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
-
-            mHasMeasurements = false;
-
-            requestLayout();
-            invalidate();
-        }
-    }
-
-    public void setTypeface(Typeface typeface) {
-        if (typeface != null && !typeface.equals(mTextPaint.getTypeface())) {
-            mTextPaint.setTypeface(typeface);
-
-            mHasMeasurements = false;
-
-            requestLayout();
-            invalidate();
-        }
-    }
-
-    public void setAlignment(Alignment textAlignment) {
-        if (mAlignment != textAlignment) {
-            mAlignment = textAlignment;
-
-            mHasMeasurements = false;
-
-            requestLayout();
-            invalidate();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
-
-        if (computeMeasurements(widthSpec)) {
-            final StaticLayout layout = mLayout;
-
-            // Account for padding.
-            final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
-            final int width = layout.getWidth() + paddingX;
-            final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
-            setMeasuredDimension(width, height);
-        } else {
-            setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
-        }
-    }
-
-    @Override
-    public void onLayout(boolean changed, int l, int t, int r, int b) {
-        final int width = r - l;
-
-        computeMeasurements(width);
-    }
-
-    @SuppressWarnings("deprecation")  // The constructor of StaticLayout is deprecated.
-    private boolean computeMeasurements(int maxWidth) {
-        if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
-            return true;
-        }
-
-        // Account for padding.
-        final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
-        maxWidth -= paddingX;
-        if (maxWidth <= 0) {
-            return false;
-        }
-
-        // TODO: Implement minimum-difference line wrapping. Adding the results
-        // of Paint.getTextWidths() seems to return different values than
-        // StaticLayout.getWidth(), so this is non-trivial.
-        mHasMeasurements = true;
-        mLastMeasuredWidth = maxWidth;
-        if (Build.VERSION.SDK_INT >= 23) {
-            StaticLayout.Builder builder = Api23Impl.obtainStaticLayoutBuilder(mText, 0,
-                    mText.length(), mTextPaint, maxWidth);
-            Api23Impl.setAlignment(builder, mAlignment);
-            Api23Impl.setLineSpacing(builder, mSpacingAdd, mSpacingMult);
-            if (Build.VERSION.SDK_INT >= 28) {
-                Api28Impl.setUseLineSpacingFromFallbacks(builder, true);
-            }
-            mLayout = Api23Impl.build(builder);
-        } else {
-            mLayout = new StaticLayout(mText, 0, mText.length(), mTextPaint, maxWidth, mAlignment,
-                    mSpacingMult, mSpacingAdd, true);
-        }
-        return true;
-    }
-
-    @Override
-    protected void onDraw(@NonNull Canvas c) {
-        final StaticLayout layout = mLayout;
-        if (layout == null) {
-            return;
-        }
-
-        final int saveCount = c.save();
-        final int innerPaddingX = mInnerPaddingX;
-        c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop());
-
-        final int lineCount = layout.getLineCount();
-        final Paint textPaint = mTextPaint;
-        final Paint paint = mPaint;
-        final RectF bounds = mLineBounds;
-
-        if (Color.alpha(mBackgroundColor) > 0) {
-            final float cornerRadius = mCornerRadius;
-            float previousBottom = layout.getLineTop(0);
-
-            paint.setColor(mBackgroundColor);
-            paint.setStyle(Style.FILL);
-
-            for (int i = 0; i < lineCount; i++) {
-                bounds.left = layout.getLineLeft(i) - innerPaddingX;
-                bounds.right = layout.getLineRight(i) + innerPaddingX;
-                bounds.top = previousBottom;
-                bounds.bottom = layout.getLineBottom(i);
-                previousBottom = bounds.bottom;
-
-                c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
-            }
-        }
-
-        final int edgeType = mEdgeType;
-        if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
-            textPaint.setStrokeJoin(Join.ROUND);
-            textPaint.setStrokeWidth(mOutlineWidth);
-            textPaint.setColor(mEdgeColor);
-            textPaint.setStyle(Style.FILL_AND_STROKE);
-
-            layout.draw(c);
-
-        } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
-            textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
-        } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
-                || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
-            final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
-            final int colorUp = raised ? Color.WHITE : mEdgeColor;
-            final int colorDown = raised ? mEdgeColor : Color.WHITE;
-            final float offset = mShadowRadius / 2f;
-
-            textPaint.setColor(mForegroundColor);
-            textPaint.setStyle(Style.FILL);
-            textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
-
-            layout.draw(c);
-
-            textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
-        }
-
-        textPaint.setColor(mForegroundColor);
-        textPaint.setStyle(Style.FILL);
-
-        layout.draw(c);
-
-        textPaint.setShadowLayer(0, 0, 0, 0);
-        c.restoreToCount(saveCount);
-    }
-
-    @RequiresApi(23)
-    static class Api23Impl {
-
-        @DoNotInline
-        static StaticLayout build(StaticLayout.Builder builder) {
-            return builder.build();
-        }
-
-        @DoNotInline
-        static StaticLayout.Builder obtainStaticLayoutBuilder(CharSequence source, int start,
-                int end, TextPaint paint, int width) {
-            return StaticLayout.Builder.obtain(source, start, end, paint, width);
-        }
-
-        @DoNotInline
-        static void setAlignment(StaticLayout.Builder builder,
-                Alignment alignment) {
-            builder.setAlignment(alignment);
-        }
-
-        @DoNotInline
-        static void setLineSpacing(StaticLayout.Builder builder, float spacingAdd,
-                float spacingMult) {
-            builder.setLineSpacing(spacingAdd, spacingMult);
-        }
-
-        private Api23Impl() {}
-    }
-
-    @RequiresApi(28)
-    static class Api28Impl {
-
-        @DoNotInline
-        static void setUseLineSpacingFromFallbacks(StaticLayout.Builder builder,
-                boolean useLineSpacingFromFallbacks) {
-            builder.setUseLineSpacingFromFallbacks(useLineSpacingFromFallbacks);
-        }
-
-        private Api28Impl() {}
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/UriUtil.java b/media2/media2-widget/src/main/java/androidx/media2/widget/UriUtil.java
deleted file mode 100644
index e2a1aa0..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/UriUtil.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-
-class UriUtil {
-
-    static boolean isFromNetwork(@NonNull Uri uri) {
-        String scheme = uri.getScheme();
-        if (scheme == null) {
-            return false;
-        }
-        return scheme.equals("http") || scheme.equals("https") || scheme.equals("rtsp");
-    }
-
-    private UriUtil() {
-    }
-
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoSurfaceView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/VideoSurfaceView.java
deleted file mode 100644
index ecc08de..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoSurfaceView.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import androidx.core.content.ContextCompat;
-
-@SuppressWarnings("deprecation")
-class VideoSurfaceView extends SurfaceView implements VideoViewInterface, SurfaceHolder.Callback {
-    private Surface mSurface = null;
-    SurfaceListener mSurfaceListener = null;
-    private PlayerWrapper mPlayer;
-
-    VideoSurfaceView(Context context) {
-        super(context, null);
-        getHolder().addCallback(this);
-    }
-
-    ////////////////////////////////////////////////////
-    // implements VideoViewInterface
-    ////////////////////////////////////////////////////
-
-    @Override
-    public boolean assignSurfaceToPlayerWrapper(PlayerWrapper player) {
-        mPlayer = player;
-        if (player == null || !hasAvailableSurface()) {
-            return false;
-        }
-        player.setSurface(mSurface).addListener(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mSurfaceListener != null) {
-                            mSurfaceListener.onSurfaceTakeOverDone(VideoSurfaceView.this);
-                        }
-                    }
-                }, ContextCompat.getMainExecutor(getContext())
-        );
-        return true;
-    }
-
-    @Override
-    public void setSurfaceListener(SurfaceListener l) {
-        mSurfaceListener = l;
-    }
-
-    @Override
-    public int getViewType() {
-        return androidx.media2.widget.VideoView.VIEW_TYPE_SURFACEVIEW;
-    }
-
-    @Override
-    public boolean hasAvailableSurface() {
-        return mSurface != null && mSurface.isValid();
-    }
-
-    ////////////////////////////////////////////////////
-    // implements SurfaceHolder.Callback
-    ////////////////////////////////////////////////////
-
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        mSurface = holder.getSurface();
-        if (mSurfaceListener != null) {
-            Rect rect = holder.getSurfaceFrame();
-            mSurfaceListener.onSurfaceCreated(this, rect.width(), rect.height());
-        }
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        if (mSurfaceListener != null) {
-            mSurfaceListener.onSurfaceChanged(this, width, height);
-        }
-    }
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        // After we return from this we can't use the surface any more
-        mSurface = null;
-        if (mSurfaceListener != null) {
-            mSurfaceListener.onSurfaceDestroyed(this);
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int videoWidth = mPlayer != null ? mPlayer.getVideoSize().getWidth() : 0;
-        final int videoHeight = mPlayer != null ? mPlayer.getVideoSize().getHeight() : 0;
-
-        int width;
-        int height;
-
-        if (videoWidth == 0 || videoHeight == 0) {
-            width = getDefaultSize(videoWidth, widthMeasureSpec);
-            height = getDefaultSize(videoHeight, heightMeasureSpec);
-            setMeasuredDimension(width, height);
-            return;
-        }
-
-        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
-        final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
-        final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
-            // the size is fixed
-            width = widthSpecSize;
-            height = heightSpecSize;
-
-            // for compatibility, we adjust size based on aspect ratio
-            if (videoWidth * height  < width * videoHeight) {
-                width = height * videoWidth / videoHeight;
-            } else if (videoWidth * height  > width * videoHeight) {
-                height = width * videoHeight / videoWidth;
-            }
-        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
-            // only the width is fixed, adjust the height to match aspect ratio if possible
-            width = widthSpecSize;
-            height = width * videoHeight / videoWidth;
-            if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
-                // couldn't match aspect ratio within the constraints
-                height = heightSpecSize | MEASURED_STATE_TOO_SMALL;
-            }
-        } else if (heightSpecMode == MeasureSpec.EXACTLY) {
-            // only the height is fixed, adjust the width to match aspect ratio if possible
-            height = heightSpecSize;
-            width = height * videoWidth / videoHeight;
-            if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                // couldn't match aspect ratio within the constraints
-                width = widthSpecSize | MEASURED_STATE_TOO_SMALL;
-            }
-        } else {
-            // neither the width nor the height are fixed, try to use actual video size
-            width = videoWidth;
-            height = videoHeight;
-            if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
-                // too tall, decrease both width and height
-                height = heightSpecSize;
-                width = height * videoWidth / videoHeight;
-            }
-            if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                // too wide, decrease both width and height
-                width = widthSpecSize;
-                height = width * videoHeight / videoWidth;
-            }
-        }
-
-        setMeasuredDimension(width, height);
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoTextureView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/VideoTextureView.java
deleted file mode 100644
index da63d3f..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoTextureView.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.view.Surface;
-import android.view.TextureView;
-
-import androidx.core.content.ContextCompat;
-
-@SuppressWarnings("deprecation")
-class VideoTextureView extends TextureView
-        implements VideoViewInterface, TextureView.SurfaceTextureListener {
-    private Surface mSurface;
-    SurfaceListener mSurfaceListener;
-    private PlayerWrapper mPlayer;
-    // A flag to indicate taking over other view should be proceed.
-
-    VideoTextureView(Context context) {
-        super(context, null);
-        setSurfaceTextureListener(this);
-    }
-
-    ////////////////////////////////////////////////////
-    // implements VideoViewInterface
-    ////////////////////////////////////////////////////
-
-    @Override
-    public boolean assignSurfaceToPlayerWrapper(PlayerWrapper player) {
-        mPlayer = player;
-        if (player == null || !hasAvailableSurface()) {
-            // Surface is not ready.
-            return false;
-        }
-        player.setSurface(mSurface).addListener(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mSurfaceListener != null) {
-                            mSurfaceListener.onSurfaceTakeOverDone(VideoTextureView.this);
-                        }
-                    }
-                }, ContextCompat.getMainExecutor(getContext())
-        );
-        return true;
-    }
-
-    @Override
-    public void setSurfaceListener(SurfaceListener l) {
-        mSurfaceListener = l;
-    }
-
-    @Override
-    public int getViewType() {
-        return androidx.media2.widget.VideoView.VIEW_TYPE_TEXTUREVIEW;
-    }
-
-    @Override
-    public boolean hasAvailableSurface() {
-        return mSurface != null && mSurface.isValid();
-    }
-
-    ////////////////////////////////////////////////////
-    // implements TextureView.SurfaceTextureListener
-    ////////////////////////////////////////////////////
-
-    @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
-        mSurface = new Surface(surfaceTexture);
-        if (mSurfaceListener != null) {
-            mSurfaceListener.onSurfaceCreated(this, width, height);
-        }
-    }
-
-    @Override
-    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
-        if (mSurfaceListener != null) {
-            mSurfaceListener.onSurfaceChanged(this, width, height);
-        }
-        // requestLayout();  // TODO: figure out if it should be called here?
-    }
-
-    @Override
-    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-        // no-op
-    }
-
-    @Override
-    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
-        if (mSurfaceListener != null) {
-            mSurfaceListener.onSurfaceDestroyed(this);
-        }
-        mSurface = null;
-        return true;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int videoWidth = mPlayer != null ? mPlayer.getVideoSize().getWidth() : 0;
-        final int videoHeight = mPlayer != null ? mPlayer.getVideoSize().getHeight() : 0;
-
-        int width;
-        int height;
-
-        if (videoWidth == 0 || videoHeight == 0) {
-            width = getDefaultSize(videoWidth, widthMeasureSpec);
-            height = getDefaultSize(videoHeight, heightMeasureSpec);
-            setMeasuredDimension(width, height);
-            return;
-        }
-
-        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
-        final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
-        final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
-            // the size is fixed
-            width = widthSpecSize;
-            height = heightSpecSize;
-
-            // for compatibility, we adjust size based on aspect ratio
-            if (videoWidth * height  < width * videoHeight) {
-                width = height * videoWidth / videoHeight;
-            } else if (videoWidth * height  > width * videoHeight) {
-                height = width * videoHeight / videoWidth;
-            }
-        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
-            // only the width is fixed, adjust the height to match aspect ratio if possible
-            width = widthSpecSize;
-            height = width * videoHeight / videoWidth;
-            if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
-                // couldn't match aspect ratio within the constraints
-                height = heightSpecSize | MEASURED_STATE_TOO_SMALL;
-            }
-        } else if (heightSpecMode == MeasureSpec.EXACTLY) {
-            // only the height is fixed, adjust the width to match aspect ratio if possible
-            height = heightSpecSize;
-            width = height * videoWidth / videoHeight;
-            if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                // couldn't match aspect ratio within the constraints
-                width = widthSpecSize | MEASURED_STATE_TOO_SMALL;
-            }
-        } else {
-            // neither the width nor the height are fixed, try to use actual video size
-            width = videoWidth;
-            height = videoHeight;
-            if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
-                // too tall, decrease both width and height
-                height = heightSpecSize;
-                width = height * videoWidth / videoHeight;
-            }
-            if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                // too wide, decrease both width and height
-                width = widthSpecSize;
-                height = width * videoHeight / videoWidth;
-            }
-        }
-
-        setMeasuredDimension(width, height);
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoView.java b/media2/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
deleted file mode 100644
index 7027695..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
+++ /dev/null
@@ -1,930 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.View;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.palette.graphics.Palette;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A high level view for media playback that can be integrated with either a {@link
- * androidx.media2.common.SessionPlayer} or a {@link androidx.media2.session.MediaController}.
- * Developers can easily implement a video rendering application using this class. By default, a
- * {@link MediaControlView} is attached so the playback control buttons are displayed on top of
- * VideoView.
- *
- * <p>Contents:
- *
- * <ol>
- *   <li><a href="UseCases">Using VideoView with androidx.media2.common.SessionPlayer or
- *       androidx.media2.session.MediaController</a>
- *   <li><a href="UseWithMCV">Using VideoView with MediaControlView</a>
- *   <li><a href="ViewType">Choosing a view type</a>
- *   <li><a href="LegacyVideoView">Comparison with android.widget.VideoView</a>
- *   <li><a href="DisplayMetadata">Displaying Metadata</a>
- * </ol>
- *
- * <h3 id="UseCases">Using VideoView with androidx.media2.common.SessionPlayer or
- * androidx.media2.session.MediaController</h3>
- *
- * <ul>
- *   <li>For simple use cases that do not require communication with a {@link
- *       androidx.media2.session.MediaSession}, apps need to create a player instance that extends
- *       {@link androidx.media2.common.SessionPlayer} (e.g. {@link
- *       androidx.media2.player.MediaPlayer}) and link it to this view by calling {@link
- *       #setPlayer}.
- *   <li>For more advanced use cases that require a {@link androidx.media2.session.MediaSession}
- *       (e.g. handling media key events, integrating with other
- *       androidx.media2.session.MediaSession apps as Assistant), apps need to create a {@link
- *       androidx.media2.session.MediaController} that's attached to the {@link
- *       androidx.media2.session.MediaSession} and link it to this view by calling {@link
- *       #setMediaController}.
- * </ul>
- *
- * <h3 id="UseWithMCV">Using VideoView with MediaControlView</h3>
- *
- * {@link VideoView} is working with {@link MediaControlView} and a MediaControlView instance is
- * attached to VideoView by default.
- *
- * <p>If you want to attach a custom {@link MediaControlView}, assign the custom media control
- * widget using {@link #setMediaControlView}.
- *
- * <p>If you don't want to use {@link MediaControlView}, set the VideoView attribute {@link
- * androidx.media2.widget.R.attr#enableControlView} to false.
- *
- * <h3 id="ViewType">Choosing a view type</h3>
- *
- * VideoView can render videos on a TextureView or SurfaceView. The default is SurfaceView which can
- * be changed by using the {@link #setViewType(int)} method or by setting the {@link
- * androidx.media2.widget.R.attr#viewType} attribute in the layout file.
- *
- * <p>SurfaceView is recommended in most cases for saving battery life. TextureView might be
- * preferred for supporting various UIs such as animation and translucency.
- *
- * <h3 id="LegacyVideoView">Comparison with android.widget.VideoView</h3>
- *
- * These are the main differences between the media2 VideoView widget and the older android widget:
- *
- * <ul>
- *   <li>{@link android.widget.VideoView android.widget.VideoView} creates a {@link
- *       android.media.MediaPlayer} instance internally and wraps playback APIs around it.
- *       <p>{@link VideoView androidx.media2.widget.VideoView} does not create a player instance
- *       internally. Instead, either a {@link androidx.media2.common.SessionPlayer} or a {@link
- *       androidx.media2.session.MediaController} instance should be created externally and link to
- *       {@link VideoView} using {@link #setPlayer(androidx.media2.common.SessionPlayer)} or {@link
- *       #setMediaController(androidx.media2.session.MediaController)}, respectively.
- *   <li>{@link android.widget.VideoView android.widget.VideoView} inherits from the SurfaceView
- *       class.
- *       <p>{@link VideoView androidx.media2.widget.VideoView} inherits from ViewGroup and can
- *       render videos using SurfaceView or TextureView, depending on your choice.
- *   <li>A {@link VideoView} can respond to media key events if you call {@link #setMediaController}
- *       to link it to a {@link androidx.media2.session.MediaController} that's connected to an
- *       active {@link androidx.media2.session.MediaSession}.
- * </ul>
- *
- * <h3 id="DisplayMetadata">Displaying Metadata</h3>
- *
- * When you play music only (sound with no video), VideoView can display album art and other
- * metadata by calling {@link
- * androidx.media2.common.MediaItem#setMetadata(androidx.media2.common.MediaMetadata)}. The
- * following table shows the metadata displayed by the VideoView, and the default values assigned if
- * the keys are not set:
- *
- * <table>
- *     <tr><th>Key</th><th>Default</th></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_TITLE}</td>
- *     <td>{@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text}</td></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_ARTIST}</td>
- *     <td>{@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text}</td></tr>
- *     <tr><td>{@link androidx.media2.common.MediaMetadata#METADATA_KEY_ALBUM_ART}</td>
- *     <td>{@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image}</td></tr>
- *     </table>
- *
- * <p>Note: VideoView does not retain its full state when going into the background. In particular,
- * it does not save, and does not restore the current play state, play position, selected tracks.
- * Applications should save and restore these on their own in {@link
- * android.app.Activity#onSaveInstanceState} and {@link
- * android.app.Activity#onRestoreInstanceState}.
- *
- * <p>Attributes :
- *
- * <ul>
- *   <li>{@link androidx.media2.widget.R.attr#enableControlView}
- *   <li>{@link androidx.media2.widget.R.attr#viewType}
- * </ul>
- *
- * <p>Example of attributes for a VideoView with TextureView and no attached control view:
- *
- * <pre>{@code
- * <androidx.media2.widget.VideoView
- *     android:id="@+id/video_view"
- *     widget:enableControlView="false"
- *     widget:viewType="textureView"
- * />
- * }</pre>
- *
- * @see MediaControlView
- * @see androidx.media2.common.SessionPlayer
- * @see androidx.media2.session.MediaController
- * @deprecated androidx.media2 is deprecated. Please migrate to <a
- *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
- */
-@Deprecated
-public class VideoView extends SelectiveLayout {
-    @IntDef({
-            VIEW_TYPE_TEXTUREVIEW,
-            VIEW_TYPE_SURFACEVIEW
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    /* package */ @interface ViewType {}
-
-    /**
-     * Indicates video is rendering on SurfaceView.
-     *
-     * @see #setViewType
-     */
-    public static final int VIEW_TYPE_SURFACEVIEW = 0;
-
-    /**
-     * Indicates video is rendering on TextureView.
-     *
-     * @see #setViewType
-     */
-    public static final int VIEW_TYPE_TEXTUREVIEW = 1;
-
-    private static final String TAG = "VideoView";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    VideoView.OnViewTypeChangedListener mViewTypeChangedListener;
-
-    VideoViewInterface mCurrentView;
-    VideoViewInterface mTargetView;
-    VideoTextureView mTextureView;
-    VideoSurfaceView mSurfaceView;
-
-    PlayerWrapper mPlayer;
-    MediaControlView mMediaControlView;
-
-    MusicView mMusicView;
-
-    SelectiveLayout.LayoutParams mSelectiveLayoutParams;
-
-    int mVideoTrackCount;
-    int mAudioTrackCount;
-    Map<androidx.media2.common.SessionPlayer.TrackInfo, SubtitleTrack> mSubtitleTracks;
-    SubtitleController mSubtitleController;
-
-    // selected subtitle track info as MediaPlayer returns
-    androidx.media2.common.SessionPlayer.TrackInfo mSelectedSubtitleTrackInfo;
-
-    SubtitleAnchorView mSubtitleAnchorView;
-
-    private final VideoViewInterface.SurfaceListener mSurfaceListener =
-            new VideoViewInterface.SurfaceListener() {
-        @Override
-        public void onSurfaceCreated(@NonNull View view, int width, int height) {
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceCreated()"
-                        + ", width/height: " + width + "/" + height
-                        + ", " + view.toString());
-            }
-            if (view == mTargetView && VideoView.this.isAggregatedVisible()) {
-                mTargetView.assignSurfaceToPlayerWrapper(mPlayer);
-            }
-        }
-
-        @Override
-        public void onSurfaceDestroyed(@NonNull View view) {
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceDestroyed(). " + view.toString());
-            }
-        }
-
-        @Override
-        public void onSurfaceChanged(@NonNull View view, int width, int height) {
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
-                        + ", " + view.toString());
-            }
-        }
-
-        @Override
-        public void onSurfaceTakeOverDone(@NonNull VideoViewInterface view) {
-            if (view != mTargetView) {
-                Log.w(TAG, "onSurfaceTakeOverDone(). view is not targetView. ignore.: " + view);
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
-            }
-            if (view != mCurrentView) {
-                ((View) mCurrentView).setVisibility(View.GONE);
-                mCurrentView = view;
-                if (mViewTypeChangedListener != null) {
-                    mViewTypeChangedListener.onViewTypeChanged(VideoView.this, view.getViewType());
-                }
-            }
-        }
-    };
-
-    public VideoView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public VideoView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public VideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        initialize(context, attrs);
-    }
-
-    private void initialize(Context context, @Nullable AttributeSet attrs) {
-        mSelectedSubtitleTrackInfo = null;
-
-        setFocusable(true);
-        setFocusableInTouchMode(true);
-        requestFocus();
-
-        mTextureView = new VideoTextureView(context);
-        mSurfaceView = new VideoSurfaceView(context);
-        mTextureView.setSurfaceListener(mSurfaceListener);
-        mSurfaceView.setSurfaceListener(mSurfaceListener);
-
-        addView(mTextureView);
-        addView(mSurfaceView);
-
-        mSelectiveLayoutParams = new SelectiveLayout.LayoutParams();
-        mSelectiveLayoutParams.forceMatchParent = true;
-
-        mSubtitleAnchorView = new SubtitleAnchorView(context);
-        mSubtitleAnchorView.setBackgroundColor(0);
-        addView(mSubtitleAnchorView, mSelectiveLayoutParams);
-
-        SubtitleController.Listener listener =
-                new SubtitleController.Listener() {
-                    @Override
-                    public void onSubtitleTrackSelected(SubtitleTrack track) {
-                        // Track deselected
-                        if (track == null) {
-                            mSelectedSubtitleTrackInfo = null;
-                            mSubtitleAnchorView.setVisibility(View.GONE);
-                            return;
-                        }
-
-                        // Track selected
-                        androidx.media2.common.SessionPlayer.TrackInfo info = null;
-                        for (Map.Entry<
-                                        androidx.media2.common.SessionPlayer.TrackInfo,
-                                        SubtitleTrack>
-                                pair : mSubtitleTracks.entrySet()) {
-                            if (pair.getValue() == track) {
-                                info = pair.getKey();
-                                break;
-                            }
-                        }
-                        if (info != null) {
-                            mSelectedSubtitleTrackInfo = info;
-                            mSubtitleAnchorView.setVisibility(View.VISIBLE);
-                        }
-                    }
-                };
-        mSubtitleController = new SubtitleController(context, null, listener);
-        mSubtitleController.registerRenderer(new Cea608CaptionRenderer(context));
-        mSubtitleController.registerRenderer(new Cea708CaptionRenderer(context));
-        mSubtitleController.setAnchor(mSubtitleAnchorView);
-
-        mMusicView = new MusicView(context);
-        mMusicView.setVisibility(View.GONE);
-        addView(mMusicView, mSelectiveLayoutParams);
-
-        boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
-                "http://schemas.android.com/apk/res-auto",
-                "enableControlView", true);
-        if (enableControlView) {
-            mMediaControlView = new MediaControlView(context);
-            mMediaControlView.setAttachedToVideoView(true);
-            addView(mMediaControlView, mSelectiveLayoutParams);
-        }
-
-        // Choose surface view by default
-        int viewType = (attrs == null) ? VideoView.VIEW_TYPE_SURFACEVIEW
-                : attrs.getAttributeIntValue(
-                        "http://schemas.android.com/apk/res-auto",
-                        "viewType", VideoView.VIEW_TYPE_SURFACEVIEW);
-        if (viewType == VideoView.VIEW_TYPE_SURFACEVIEW) {
-            if (DEBUG) {
-                Log.d(TAG, "viewType attribute is surfaceView.");
-            }
-            mTextureView.setVisibility(View.GONE);
-            mSurfaceView.setVisibility(View.VISIBLE);
-            mCurrentView = mSurfaceView;
-        } else if (viewType == VideoView.VIEW_TYPE_TEXTUREVIEW) {
-            if (DEBUG) {
-                Log.d(TAG, "viewType attribute is textureView.");
-            }
-            mTextureView.setVisibility(View.VISIBLE);
-            mSurfaceView.setVisibility(View.GONE);
-            mCurrentView = mTextureView;
-        }
-        mTargetView = mCurrentView;
-    }
-
-    /**
-     * Sets {@link androidx.media2.session.MediaController} to display media content. Setting a
-     * {@link androidx.media2.session.MediaController} will unset any {@link
-     * androidx.media2.session.MediaController} or {@link androidx.media2.common.SessionPlayer} that
-     * was previously set.
-     *
-     * <p>If VideoView has a {@link MediaControlView} instance, this controller will also be set to
-     * it.
-     *
-     * <p>Calling this method will automatically set VideoView's surface to {@link
-     * androidx.media2.session.MediaController} by calling {@link
-     * androidx.media2.session.MediaController#setSurface(Surface)}. If the {@link
-     * androidx.media2.session.MediaController} is connected to a {@link
-     * androidx.media2.session.MediaSession} and that {@link androidx.media2.session.MediaSession}
-     * is associated with a {@link androidx.media2.common.SessionPlayer}, VideoView's surface will
-     * be set to that {@link androidx.media2.common.SessionPlayer}.
-     *
-     * @param controller the controller
-     * @see #setPlayer
-     */
-    public void setMediaController(@NonNull androidx.media2.session.MediaController controller) {
-        if (controller == null) {
-            throw new NullPointerException("controller must not be null");
-        }
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-        mPlayer = new PlayerWrapper(controller, ContextCompat.getMainExecutor(getContext()),
-                new PlayerCallback());
-        if (this.isAttachedToWindow()) {
-            mPlayer.attachCallback();
-        }
-        if (this.isAggregatedVisible()) {
-            mTargetView.assignSurfaceToPlayerWrapper(mPlayer);
-        } else {
-            resetPlayerSurfaceWithNullAsync();
-        }
-
-        if (mMediaControlView != null) {
-            mMediaControlView.setMediaControllerInternal(controller);
-        }
-    }
-
-    /**
-     * Sets {@link androidx.media2.common.SessionPlayer} to display media content. Setting a
-     * androidx.media2.common.SessionPlayer will unset any androidx.media2.session.MediaController
-     * or androidx.media2.common.SessionPlayer that was previously set.
-     *
-     * <p>If VideoView has a {@link MediaControlView} instance, this player will also be set to it.
-     *
-     * <p>Calling this method will automatically set VideoView's surface to {@link
-     * androidx.media2.common.SessionPlayer} by calling {@link
-     * androidx.media2.common.SessionPlayer#setSurface(Surface)}.
-     *
-     * @param player the player
-     * @see #setMediaController
-     */
-    public void setPlayer(@NonNull androidx.media2.common.SessionPlayer player) {
-        if (player == null) {
-            throw new NullPointerException("player must not be null");
-        }
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-        mPlayer = new PlayerWrapper(player, ContextCompat.getMainExecutor(getContext()),
-                new PlayerCallback());
-        if (this.isAttachedToWindow()) {
-            mPlayer.attachCallback();
-        }
-        if (this.isAggregatedVisible()) {
-            mTargetView.assignSurfaceToPlayerWrapper(mPlayer);
-        } else {
-            resetPlayerSurfaceWithNullAsync();
-        }
-
-        if (mMediaControlView != null) {
-            mMediaControlView.setPlayerInternal(player);
-        }
-    }
-
-    /**
-     * Sets {@link MediaControlView} instance. It will replace the previously assigned {@link
-     * MediaControlView} instance if any.
-     *
-     * <p>If a {@link androidx.media2.session.MediaController} or a {@link
-     * androidx.media2.common.SessionPlayer} instance has been set to {@link VideoView}, the same
-     * instance will be set to {@link MediaControlView}.
-     *
-     * @param mediaControlView a {@link MediaControlView} instance.
-     * @param intervalMs time interval in milliseconds until {@link MediaControlView} transitions
-     *     into a different mode. -1 can be set to disable all UI transitions. See {@link
-     *     MediaControlView} Javadoc Section "UI transitions" for details.
-     */
-    public void setMediaControlView(@NonNull MediaControlView mediaControlView, long intervalMs) {
-        if (mMediaControlView != null) {
-            removeView(mMediaControlView);
-            mMediaControlView.setAttachedToVideoView(false);
-        }
-        addView(mediaControlView, mSelectiveLayoutParams);
-        mediaControlView.setAttachedToVideoView(true);
-
-        mMediaControlView = mediaControlView;
-        mMediaControlView.setDelayedAnimationInterval(intervalMs);
-
-        if (mPlayer != null) {
-            if (mPlayer.mController != null) {
-                mMediaControlView.setMediaControllerInternal(mPlayer.mController);
-            } else if (mPlayer.mPlayer != null) {
-                mMediaControlView.setPlayerInternal(mPlayer.mPlayer);
-            }
-        }
-    }
-
-    /**
-     * Returns {@link MediaControlView} instance which is currently attached to VideoView by default
-     * or by {@link #setMediaControlView} method.
-     */
-    @Nullable
-    public MediaControlView getMediaControlView() {
-        return mMediaControlView;
-    }
-
-    /**
-     * Selects which view will be used to render video between SurfaceView and TextureView.
-     * <p>
-     * Note: There are two known issues on API level 28+ devices.
-     * <ul>
-     * <li> When changing view type to SurfaceView from TextureView in "paused" playback state,
-     * a blank screen can be shown.
-     * <li> When changing view type to TextureView from SurfaceView repeatedly in "paused" playback
-     * state, the lastly rendered frame on TextureView can be shown.
-     * </ul>
-     * @param viewType the view type to render video
-     * <ul>
-     * <li>{@link #VIEW_TYPE_SURFACEVIEW}
-     * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
-     * </ul>
-     */
-    public void setViewType(@ViewType int viewType) {
-        if (viewType == mTargetView.getViewType()) {
-            Log.d(TAG, "setViewType with the same type (" + viewType + ") is ignored.");
-            return;
-        }
-        VideoViewInterface targetView;
-        if (viewType == VideoView.VIEW_TYPE_TEXTUREVIEW) {
-            Log.d(TAG, "switching to TextureView");
-            targetView = mTextureView;
-        } else if (viewType == VideoView.VIEW_TYPE_SURFACEVIEW) {
-            Log.d(TAG, "switching to SurfaceView");
-            targetView = mSurfaceView;
-        } else {
-            throw new IllegalArgumentException("Unknown view type: " + viewType);
-        }
-
-        mTargetView = targetView;
-        if (this.isAggregatedVisible()) {
-            targetView.assignSurfaceToPlayerWrapper(mPlayer);
-        }
-        ((View) targetView).setVisibility(View.VISIBLE);
-        requestLayout();
-    }
-
-    /**
-     * Returns view type.
-     *
-     * @return view type. See {@see setViewType}.
-     */
-    @ViewType
-    public int getViewType() {
-        return mCurrentView.getViewType();
-    }
-
-    /**
-     * Sets a listener to be called when a view type change is done.
-     *
-     * @see #setViewType(int)
-     *
-     * @param listener The listener to be called. A value of <code>null</code> removes any existing
-     * listener.
-     */
-    public void setOnViewTypeChangedListener(@Nullable OnViewTypeChangedListener listener) {
-        mViewTypeChangedListener = listener;
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mPlayer != null) {
-            mPlayer.attachCallback();
-        }
-    }
-
-    @Override
-    void onVisibilityAggregatedCompat(boolean isVisible) {
-        super.onVisibilityAggregatedCompat(isVisible);
-        if (mPlayer == null) {
-            return;
-        }
-
-        if (isVisible) {
-            mTargetView.assignSurfaceToPlayerWrapper(mPlayer);
-        } else {
-            if (mPlayer == null || mPlayer.hasDisconnectedController()) {
-                Log.w(TAG, "Surface is being destroyed, but player will not be informed "
-                        + "as the associated media controller is disconnected.");
-                return;
-            }
-            resetPlayerSurfaceWithNull();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (mPlayer != null) {
-            mPlayer.detachCallback();
-        }
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        // Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage.
-        return "androidx.media2.widget.VideoView";
-    }
-
-    ///////////////////////////////////////////////////
-    // Protected or private methods
-    ///////////////////////////////////////////////////
-    boolean isMediaPrepared() {
-        return mPlayer != null
-                && mPlayer.getPlayerState()
-                        != androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR
-                && mPlayer.getPlayerState()
-                        != androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE;
-    }
-
-    boolean hasActualVideo() {
-        if (mVideoTrackCount > 0) {
-            return true;
-        }
-        androidx.media2.common.VideoSize videoSize = mPlayer.getVideoSize();
-        if (videoSize.getHeight() > 0 && videoSize.getWidth() > 0) {
-            Log.w(TAG, "video track count is zero, but it renders video. size: "
-                    + videoSize.getWidth() + "/" + videoSize.getHeight());
-            return true;
-        }
-        return false;
-    }
-
-    boolean isCurrentItemMusic() {
-        return !hasActualVideo() && mAudioTrackCount > 0;
-    }
-
-    void updateTracks(
-            PlayerWrapper player, List<androidx.media2.common.SessionPlayer.TrackInfo> trackInfos) {
-        mSubtitleTracks = new LinkedHashMap<>();
-        mVideoTrackCount = 0;
-        mAudioTrackCount = 0;
-        for (int i = 0; i < trackInfos.size(); i++) {
-            androidx.media2.common.SessionPlayer.TrackInfo trackInfo = trackInfos.get(i);
-            int trackType = trackInfos.get(i).getTrackType();
-            if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
-                mVideoTrackCount++;
-            } else if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
-                mAudioTrackCount++;
-            } else if (trackType
-                    == androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                SubtitleTrack track = mSubtitleController.addTrack(trackInfo.getFormat());
-                if (track != null) {
-                    mSubtitleTracks.put(trackInfo, track);
-                }
-            }
-        }
-        mSelectedSubtitleTrackInfo =
-                player.getSelectedTrack(
-                        androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
-    }
-
-    void updateMusicView(androidx.media2.common.MediaItem item) {
-        boolean shouldShowMusicView = item != null && isCurrentItemMusic();
-        if (shouldShowMusicView) {
-            mMusicView.setVisibility(View.VISIBLE);
-
-            androidx.media2.common.MediaMetadata metadata = item.getMetadata();
-            Resources resources = getResources();
-
-            Drawable albumDrawable = getAlbumArt(metadata,
-                    ContextCompat.getDrawable(
-                            getContext(), R.drawable.media2_widget_ic_default_album_image));
-            String title =
-                    getString(
-                            metadata,
-                            androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE,
-                            resources.getString(R.string.mcv2_music_title_unknown_text));
-            String artist =
-                    getString(
-                            metadata,
-                            androidx.media2.common.MediaMetadata.METADATA_KEY_ARTIST,
-                            resources.getString(R.string.mcv2_music_artist_unknown_text));
-
-            mMusicView.setAlbumDrawable(albumDrawable);
-            mMusicView.setTitleText(title);
-            mMusicView.setArtistText(artist);
-        } else {
-            mMusicView.setVisibility(View.GONE);
-            mMusicView.setAlbumDrawable(null);
-            mMusicView.setTitleText(null);
-            mMusicView.setArtistText(null);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void resetPlayerSurfaceWithNull() {
-        try {
-            int resultCode = mPlayer.setSurface(null).get(100, TimeUnit.MILLISECONDS)
-                    .getResultCode();
-            if (resultCode != androidx.media2.common.BaseResult.RESULT_SUCCESS) {
-                Log.e(TAG, "calling setSurface(null) was not "
-                        + "successful. ResultCode: " + resultCode);
-            }
-        } catch (ExecutionException | InterruptedException | TimeoutException e) {
-            Log.e(TAG, "calling setSurface(null) was not successful.", e);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void resetPlayerSurfaceWithNullAsync() {
-        ListenableFuture<? extends androidx.media2.common.BaseResult> future =
-                mPlayer.setSurface(null);
-        future.addListener(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            int resultCode = future.get().getResultCode();
-                            if (resultCode != androidx.media2.common.BaseResult.RESULT_SUCCESS) {
-                                Log.e(
-                                        TAG,
-                                        "calling setSurface(null) was not "
-                                                + "successful. ResultCode: "
-                                                + resultCode);
-                            }
-                        } catch (ExecutionException | InterruptedException e) {
-                            Log.e(TAG, "calling setSurface(null) was not successful.", e);
-                        }
-                    }
-                },
-                ContextCompat.getMainExecutor(getContext()));
-    }
-
-    private Drawable getAlbumArt(
-            @NonNull androidx.media2.common.MediaMetadata metadata, Drawable defaultDrawable) {
-        Drawable drawable = defaultDrawable;
-        Bitmap bitmap = null;
-
-        if (metadata != null
-                && metadata.containsKey(
-                        androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART)) {
-            bitmap =
-                    metadata.getBitmap(androidx.media2.common.MediaMetadata.METADATA_KEY_ALBUM_ART);
-        }
-        if (bitmap != null) {
-            Palette.Builder builder = Palette.from(bitmap);
-            builder.generate(new Palette.PaletteAsyncListener() {
-                @Override
-                public void onGenerated(Palette palette) {
-                    int dominantColor = palette.getDominantColor(0);
-                    mMusicView.setBackgroundColor(dominantColor);
-                }
-            });
-            drawable = new BitmapDrawable(getResources(), bitmap);
-        } else {
-            mMusicView.setBackgroundColor(ContextCompat.getColor(getContext(),
-                    R.color.media2_widget_music_view_default_background));
-        }
-        return drawable;
-    }
-
-    private String getString(
-            @NonNull androidx.media2.common.MediaMetadata metadata,
-            String stringKey,
-            String defaultValue) {
-        String value = (metadata == null) ? defaultValue : metadata.getString(stringKey);
-        return value == null ? defaultValue : value;
-    }
-
-    class PlayerCallback extends PlayerWrapper.PlayerCallback {
-        @Override
-        void onConnected(@NonNull PlayerWrapper player) {
-            if (DEBUG) {
-                Log.d(TAG, "onConnected()");
-            }
-            if (shouldIgnoreCallback(player)) return;
-            if (VideoView.this.isAggregatedVisible()) {
-                mTargetView.assignSurfaceToPlayerWrapper(mPlayer);
-            }
-        }
-
-        @Override
-        void onVideoSizeChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.VideoSize videoSize) {
-            if (DEBUG) {
-                Log.d(TAG, "onandroidx.media2.common.VideoSizeChanged(): size: " + videoSize);
-            }
-            if (shouldIgnoreCallback(player)) return;
-            if (mVideoTrackCount == 0 && videoSize.getHeight() > 0 && videoSize.getWidth() > 0) {
-                if (isMediaPrepared()) {
-                    List<androidx.media2.common.SessionPlayer.TrackInfo> trackInfos =
-                            player.getTracks();
-                    if (trackInfos != null) {
-                        updateTracks(player, trackInfos);
-                    }
-                }
-            }
-            mTextureView.forceLayout();
-            mSurfaceView.forceLayout();
-            requestLayout();
-        }
-
-        @Override
-        void onSubtitleData(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.MediaItem item,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo track,
-                @NonNull androidx.media2.common.SubtitleData data) {
-            if (DEBUG) {
-                Log.d(
-                        TAG,
-                        "onandroidx.media2.common.SubtitleData():"
-                                + " androidx.media2.common.SessionPlayer.TrackInfo: "
-                                + track
-                                + ", getCurrentPosition: "
-                                + player.getCurrentPosition()
-                                + ", getStartTimeUs(): "
-                                + data.getStartTimeUs()
-                                + ", diff: "
-                                + (data.getStartTimeUs() / 1000 - player.getCurrentPosition())
-                                + "ms, getDurationUs(): "
-                                + data.getDurationUs());
-            }
-            if (shouldIgnoreCallback(player)) return;
-            if (!track.equals(mSelectedSubtitleTrackInfo)) {
-                return;
-            }
-            SubtitleTrack subtitleTrack = mSubtitleTracks.get(track);
-            if (subtitleTrack != null) {
-                subtitleTrack.onData(data);
-            }
-        }
-
-        @Override
-        void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
-            if (DEBUG) {
-                Log.d(TAG, "onPlayerStateChanged(): state: " + state);
-            }
-            if (shouldIgnoreCallback(player)) return;
-            if (state == androidx.media2.common.SessionPlayer.PLAYER_STATE_ERROR) {
-                // TODO: Show error state (b/123498635)
-            }
-        }
-
-        @Override
-        void onCurrentMediaItemChanged(
-                @NonNull PlayerWrapper player, @Nullable androidx.media2.common.MediaItem item) {
-            if (DEBUG) {
-                Log.d(
-                        TAG,
-                        "onCurrentMediaItemChanged():"
-                                + " androidx.media2.common.MediaItem: "
-                                + item);
-            }
-            if (shouldIgnoreCallback(player)) return;
-
-            updateMusicView(item);
-        }
-
-        @Override
-        void onTracksChanged(
-                @NonNull PlayerWrapper player,
-                @NonNull List<androidx.media2.common.SessionPlayer.TrackInfo> tracks) {
-            if (DEBUG) {
-                Log.d(
-                        TAG,
-                        "onandroidx.media2.common.SessionPlayer.TrackInfoChanged(): tracks: "
-                                + tracks);
-            }
-            if (shouldIgnoreCallback(player)) return;
-            updateTracks(player, tracks);
-            updateMusicView(player.getCurrentMediaItem());
-        }
-
-        @Override
-        void onTrackSelected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            if (DEBUG) {
-                Log.d(TAG, "onTrackSelected(): selected track: " + trackInfo);
-            }
-            if (shouldIgnoreCallback(player)) return;
-            SubtitleTrack subtitleTrack = mSubtitleTracks.get(trackInfo);
-            if (subtitleTrack != null) {
-                mSubtitleController.selectTrack(subtitleTrack);
-            }
-        }
-
-        @Override
-        void onTrackDeselected(
-                @NonNull PlayerWrapper player,
-                @NonNull androidx.media2.common.SessionPlayer.TrackInfo trackInfo) {
-            if (DEBUG) {
-                Log.d(TAG, "onTrackDeselected(): deselected track: " + trackInfo);
-            }
-            if (shouldIgnoreCallback(player)) return;
-            SubtitleTrack subtitleTrack = mSubtitleTracks.get(trackInfo);
-            if (subtitleTrack != null) {
-                mSubtitleController.selectTrack(null);
-            }
-        }
-
-        private boolean shouldIgnoreCallback(@NonNull PlayerWrapper player) {
-            if (player != mPlayer) {
-                if (DEBUG) {
-                    try {
-                        final String methodName =
-                                new Throwable().getStackTrace()[1].getMethodName();
-                        Log.w(TAG, methodName + " should be ignored. player is already gone.");
-                    } catch (IndexOutOfBoundsException e) {
-                        Log.w(TAG, "A PlayerCallback should be ignored. player is already gone.");
-                    }
-                }
-                return true;
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Interface definition of a callback to be invoked when the view type has been changed.
-     *
-     * @deprecated androidx.media2 is deprecated. Please migrate to <a
-     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
-     */
-    @Deprecated
-    public interface OnViewTypeChangedListener {
-        /**
-         * Called when the view type has been changed.
-         * @see #setViewType(int)
-         * @param view the View whose view type is changed
-         * @param viewType
-         * <ul>
-         * <li>{@link #VIEW_TYPE_SURFACEVIEW}
-         * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
-         * </ul>
-         */
-        void onViewTypeChanged(@NonNull View view, @ViewType int viewType);
-    }
-}
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoViewInterface.java b/media2/media2-widget/src/main/java/androidx/media2/widget/VideoViewInterface.java
deleted file mode 100644
index c6857a4..0000000
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/VideoViewInterface.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2018 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.media2.widget;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-interface VideoViewInterface {
-    /**
-     * Assigns the view's surface to the given PlayerWrapper instance.
-     *
-     * @param player PlayerWrapper
-     * @return true if the surface is successfully assigned, false if not. It will fail to assign
-     *         if any of PlayerWrapper or surface is unavailable.
-     */
-    boolean assignSurfaceToPlayerWrapper(PlayerWrapper player);
-    void setSurfaceListener(SurfaceListener l);
-    int getViewType();
-
-    /**
-     * Indicates if the view's surface is available.
-     *
-     * @return true if the surface is available.
-     */
-    boolean hasAvailableSurface();
-
-    /**
-     * An instance of VideoViewInterface calls these surface notification methods accordingly if
-     * a listener has been registered via {@link #setSurfaceListener(SurfaceListener)}.
-     */
-    interface SurfaceListener {
-        void onSurfaceCreated(@NonNull View view, int width, int height);
-        void onSurfaceDestroyed(@NonNull View view);
-        void onSurfaceChanged(@NonNull View view, int width, int height);
-        void onSurfaceTakeOverDone(@NonNull VideoViewInterface view);
-    }
-}
diff --git a/media2/media2-widget/src/main/res-public/values/public_attrs.xml b/media2/media2-widget/src/main/res-public/values/public_attrs.xml
deleted file mode 100644
index c0bcefd..0000000
--- a/media2/media2-widget/src/main/res-public/values/public_attrs.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<!-- Definitions of attributes to be exposed as public -->
-<resources>
-    <public type="attr" name="enableControlView" />
-    <public type="attr" name="viewType" />
-</resources>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress.xml
deleted file mode 100644
index c43b4b4..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright 2018 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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:id="@android:id/background">
-        <shape android:shape="rectangle" >
-            <solid android:color="#26000000" />
-        </shape>
-    </item>
-    <item android:id="@android:id/secondaryProgress">
-        <clip>
-            <shape android:shape="rectangle" >
-                <solid android:color="#5Cffffff" />
-            </shape>
-        </clip>
-    </item>
-    <item android:id="@android:id/progress">
-        <clip>
-            <shape android:shape="rectangle" >
-                <solid android:color="#ffffff" />
-            </shape>
-        </clip>
-    </item>
-</layer-list>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress_thumb.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress_thumb.xml
deleted file mode 100644
index c52e00c..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_custom_progress_thumb.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright 2018 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.
--->
-<scale xmlns:android="http://schemas.android.com/apk/res/android"
-       android:scaleWidth="100%"
-       android:scaleHeight="100%"
-       android:scaleGravity="center"
-       android:level="10000">
-    <shape android:shape="oval" >
-        <solid android:color="#ffffff" />
-        <size android:height="@dimen/media2_widget_custom_progress_thumb_size"
-              android:width="@dimen/media2_widget_custom_progress_thumb_size" />
-    </shape>
-</scale>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_audiotrack.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_audiotrack.xml
deleted file mode 100644
index 4a5cd33..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_audiotrack.xml
+++ /dev/null
@@ -1,31 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
-      android:fillColor="#FFFFFF"/>
-  <path
-      android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
-      android:fillColor="#FFFFFF"/>
-  <path
-      android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
-      android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_check.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_check.xml
deleted file mode 100644
index bd7595c..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_check.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-
-    <path
-        android:pathData="M0 0h24v24H0z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_left.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_left.xml
deleted file mode 100644
index 8336d17..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_left.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_right.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_right.xml
deleted file mode 100644
index fb2ce09..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_chevron_right.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_default_album_image.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_default_album_image.xml
deleted file mode 100644
index 1cee6432..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_default_album_image.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="512dp"
-    android:height="512dp"
-    android:viewportWidth="512"
-    android:viewportHeight="512">
-
-    <path
-        android:fillColor="#616161"
-        android:pathData="M 0 0 H 512 V 512 H 0 V 0 Z" />
-    <path
-        android:fillColor="#525252"
-        android:pathData="M256,151v123.14c-6.88-4.02-14.82-6.48-23.33-6.48 c-25.78,0-46.67,20.88-46.67,46.67c0,25.78,20.88,46.67,46.67,46.67s46.67-20.88,46.67-46.67V197.67H326V151H256z" />
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_forward_30.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_forward_30.xml
deleted file mode 100644
index 187d8f8..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_forward_30.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:width="24dp"
-    android:height="24dp">
-    <group>
-        <clip-path
-            android:pathData="M24 24L0 24 0 0 24 0 24 24Z" />
-        <path
-            android:pathData="M9.6 13.5l0.4 0c0.2 0 0.4 -0.1 0.5 -0.2 0.1 -0.1 0.2 -0.2 0.2 -0.4l0 -0.2c0 0 -0.1 -0.1 -0.1 -0.2 0 -0.1 -0.1 -0.1 -0.2 -0.1l-0.5 0c0 0 -0.1 0.1 -0.2 0.1 -0.1 0 -0.1 0.1 -0.1 0.2l0 0.2 -1 0c0 -0.2 0 -0.3 0.1 -0.5 0.1 -0.2 0.2 -0.3 0.3 -0.4 0.1 -0.1 0.3 -0.2 0.4 -0.2 0.1 0 0.4 -0.1 0.5 -0.1 0.2 0 0.4 0 0.6 0.1 0.2 0.1 0.3 0.1 0.5 0.2 0.2 0.1 0.2 0.2 0.3 0.4 0.1 0.2 0.1 0.3 0.1 0.5l0 0.3c0 0 -0.1 0.2 -0.1 0.3 0 0.1 -0.1 0.2 -0.2 0.2 -0.1 0 -0.2 0.1 -0.3 0.2 0.2 0.1 0.4 0.2 0.5 0.4 0.1 0.2 0.2 0.4 0.2 0.6 0 0.2 0 0.4 -0.1 0.5 -0.1 0.1 -0.2 0.3 -0.3 0.4C11 15.9 10.8 16 10.6 16 10.4 16 10.2 16.1 10 16.1 9.8 16.1 9.6 16.1 9.5 16 9.4 15.9 9.2 15.9 9 15.8 8.8 15.7 8.8 15.6 8.7 15.4 8.6 15.2 8.6 15 8.6 14.8l0.8 0 0 0.2c0 0 0.1 0.1 0.1 0.2 0 0.1 0.1 0.1 0.2 0.1l0.5 0c0 0 0.1 -0.1 0.2 -0.1 0.1 0 0.1 -0.1 0.1 -0.2l0 -0.5c0 0 -0.1 -0.1 -0.1 -0.2 0 -0.1 -0.1 -0.1 -0.2 -0.1l-0.6 0 0 -0.7zm5.7 0.7c0 0.3 0 0.6 -0.1 0.8l-0.3 0.6c0 0 -0.3 0.3 -0.5 0.3 -0.2 0 -0.4 0.1 -0.6 0.1 -0.2 0 -0.4 0 -0.6 -0.1C13 15.8 12.9 15.7 12.7 15.6 12.5 15.5 12.5 15.3 12.4 15 12.3 14.7 12.3 14.5 12.3 14.2l0 -0.7c0 -0.3 0 -0.6 0.1 -0.8l0.3 -0.6c0 0 0.3 -0.3 0.5 -0.3 0.2 0 0.4 -0.1 0.6 -0.1 0.2 0 0.4 0 0.6 0.1 0.2 0.1 0.3 0.2 0.5 0.3 0.2 0.1 0.2 0.3 0.3 0.6 0.1 0.3 0.1 0.5 0.1 0.8l0 0.7zm-0.9 -0.8l0 -0.5c0 0 -0.1 -0.2 -0.1 -0.3 0 -0.1 -0.1 -0.1 -0.2 -0.2 -0.1 -0.1 -0.2 -0.1 -0.3 -0.1 -0.1 0 -0.2 0 -0.3 0.1l-0.2 0.2c0 0 -0.1 0.2 -0.1 0.3l0 2c0 0 0.1 0.2 0.1 0.3 0 0.1 0.1 0.1 0.2 0.2 0.1 0.1 0.2 0.1 0.3 0.1 0.1 0 0.2 0 0.3 -0.1l0.2 -0.2c0 0 0.1 -0.2 0.1 -0.3l0 -1.5zM4 13c0 4.4 3.6 8 8 8 4.4 0 8 -3.6 8 -8l-2 0c0 3.3 -2.7 6 -6 6C8.7 19 6 16.3 6 13 6 9.7 8.7 7 12 7l0 4 5 -5 -5 -5 0 4C7.6 5 4 8.6 4 13Z"
-            android:fillColor="#FFFFFF" />
-    </group>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen.xml
deleted file mode 100644
index 4b4f6bc..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen_exit.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen_exit.xml
deleted file mode 100644
index bc204e2..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_fullscreen_exit.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_launch.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_launch.xml
deleted file mode 100644
index 2cd8472..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_launch.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_pause_circle_filled.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_pause_circle_filled.xml
deleted file mode 100644
index 73be228..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_pause_circle_filled.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="40dp"
-        android:height="40dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_play_circle_filled.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_play_circle_filled.xml
deleted file mode 100644
index 9d39def..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_play_circle_filled.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="40dp"
-        android:height="40dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_replay_circle_filled.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_replay_circle_filled.xml
deleted file mode 100644
index 7f323f8..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_replay_circle_filled.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="40dp"
-    android:height="40dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-
-    <path
-        android:pathData="M0,0h24v24H0V0z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="evenOdd"
-        android:pathData="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10c5.52,0,10-4.48,10-10
-C22,6.48,17.52,2,12,2z
-M18,12c0,3.31-2.69,6-6,6c-3.31,0-6-2.69-6-6h2c0,2.21,1.79,4,4,4s4-1.79,4-4s-1.79-4-4-4v3L8,7l4-4v3
-C15.31,6,18,8.69,18,12z" />
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_rewind_10.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_rewind_10.xml
deleted file mode 100644
index c2935c4..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_rewind_10.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:width="24dp"
-    android:height="24dp">
-    <group>
-        <clip-path
-            android:pathData="M0 0L24 0 24 24 0 24 0 0Z" />
-        <path
-            android:pathData="M12 5l0 -4 -5 5 5 5 0 -4c3.3 0 6 2.7 6 6 0 3.3 -2.7 6 -6 6 -3.3 0 -6 -2.7 -6 -6l-2 0c0 4.4 3.6 8 8 8 4.4 0 8 -3.6 8 -8 0 -4.4 -3.6 -8 -8 -8zm-1.1 11l-0.9 0 0 -3.3 -1 0.3 0 -0.7 1.8 -0.6 0.1 0 0 4.3zm4.3 -1.8c0 0.3 0 0.6 -0.1 0.8l-0.3 0.6c0 0 -0.3 0.3 -0.5 0.3 -0.2 0 -0.4 0.1 -0.6 0.1 -0.2 0 -0.4 0 -0.6 -0.1 -0.2 -0.1 -0.3 -0.2 -0.5 -0.3 -0.2 -0.1 -0.2 -0.3 -0.3 -0.6 -0.1 -0.3 -0.1 -0.5 -0.1 -0.8l0 -0.7c0 -0.3 0 -0.6 0.1 -0.8l0.3 -0.6c0 0 0.3 -0.3 0.5 -0.3 0.2 0 0.4 -0.1 0.6 -0.1 0.2 0 0.4 0 0.6 0.1 0.2 0.1 0.3 0.2 0.5 0.3 0.2 0.1 0.2 0.3 0.3 0.6 0.1 0.3 0.1 0.5 0.1 0.8l0 0.7zm-0.9 -0.8l0 -0.5c0 0 -0.1 -0.2 -0.1 -0.3 0 -0.1 -0.1 -0.1 -0.2 -0.2 -0.1 -0.1 -0.2 -0.1 -0.3 -0.1 -0.1 0 -0.2 0 -0.3 0.1l-0.2 0.2c0 0 -0.1 0.2 -0.1 0.3l0 2c0 0 0.1 0.2 0.1 0.3 0 0.1 0.1 0.1 0.2 0.2 0.1 0.1 0.2 0.1 0.3 0.1 0.1 0 0.2 0 0.3 -0.1l0.2 -0.2c0 0 0.1 -0.2 0.1 -0.3l0 -1.5z"
-            android:fillColor="#FFFFFF" />
-    </group>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_settings.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_settings.xml
deleted file mode 100644
index a59ecc1..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_settings.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_next.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_next.xml
deleted file mode 100644
index b1f2812..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_next.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="40dp"
-        android:height="40dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_previous.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_previous.xml
deleted file mode 100644
index 81da314..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_skip_previous.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="40dp"
-        android:height="40dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_speed.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_speed.xml
deleted file mode 100644
index 7c066bf..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_speed.xml
+++ /dev/null
@@ -1,25 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:pathData="M13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM11,4.07L11,2.05c-2.01,0.2 -3.84,1 -5.32,2.21L7.1,5.69c1.11,-0.86 2.44,-1.44 3.9,-1.62zM5.69,7.1L4.26,5.68C3.05,7.16 2.25,8.99 2.05,11h2.02c0.18,-1.46 0.76,-2.79 1.62,-3.9zM4.07,13L2.05,13c0.2,2.01 1,3.84 2.21,5.32l1.43,-1.43c-0.86,-1.1 -1.44,-2.43 -1.62,-3.89zM5.68,19.74C7.16,20.95 9,21.75 11,21.95v-2.02c-1.46,-0.18 -2.79,-0.76 -3.9,-1.62l-1.42,1.43zM22,12c0,5.16 -3.92,9.42 -8.95,9.95v-2.02C16.97,19.41 20,16.05 20,12s-3.03,-7.41 -6.95,-7.93L13.05,2.05C18.08,2.58 22,6.84 22,12z"
-      android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_off.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_off.xml
deleted file mode 100644
index e1d080d..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_off.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-
-    <path
-        android:pathData="M0,0h24v24H0V0z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M18,11h-1.5v-0.5h-2v3h2V13H18v1c0,0.55-0.45,1-1,1h-3c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_on.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_on.xml
deleted file mode 100644
index 0e86369..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_ic_subtitle_on.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-
-    <path
-        android:pathData="M0 0h24v24H0z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
-</vector>
diff --git a/media2/media2-widget/src/main/res/drawable/media2_widget_title_bar_gradient.xml b/media2/media2-widget/src/main/res/drawable/media2_widget_title_bar_gradient.xml
deleted file mode 100644
index 0f35b16..0000000
--- a/media2/media2-widget/src/main/res/drawable/media2_widget_title_bar_gradient.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent">
-    <gradient
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        android:startColor="@color/media2_widget_title_bar_gradient_start"
-        android:endColor="@color/media2_widget_title_bar_gradient_end"
-        android:angle="270" />
-</shape>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_embedded_transport_controls.xml b/media2/media2-widget/src/main/res/layout/media2_widget_embedded_transport_controls.xml
deleted file mode 100644
index 86221f3..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_embedded_transport_controls.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:orientation="horizontal"
-    android:visibility="visible">
-
-    <ImageButton android:id="@+id/prev" style="@style/Media2_Widget_EmbeddedTransportControlsButton.Previous" />
-    <ImageButton android:id="@+id/rew" style="@style/Media2_Widget_EmbeddedTransportControlsButton.Rew" />
-    <ImageButton android:id="@+id/pause" style="@style/Media2_Widget_EmbeddedTransportControlsButton.Pause" />
-    <ImageButton android:id="@+id/ffwd" style="@style/Media2_Widget_EmbeddedTransportControlsButton.Ffwd" />
-    <ImageButton android:id="@+id/next" style="@style/Media2_Widget_EmbeddedTransportControlsButton.Next" />
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_full_transport_controls.xml b/media2/media2-widget/src/main/res/layout/media2_widget_full_transport_controls.xml
deleted file mode 100644
index 1a04f5a..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_full_transport_controls.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:orientation="horizontal"
-    android:visibility="visible">
-
-    <ImageButton android:id="@+id/prev" style="@style/Media2_Widget_FullTransportControlsButton.Previous" />
-    <ImageButton android:id="@+id/rew" style="@style/Media2_Widget_FullTransportControlsButton.Rew" />
-    <ImageButton android:id="@+id/pause" style="@style/Media2_Widget_FullTransportControlsButton.Pause" />
-    <ImageButton android:id="@+id/ffwd" style="@style/Media2_Widget_FullTransportControlsButton.Ffwd" />
-    <ImageButton android:id="@+id/next" style="@style/Media2_Widget_FullTransportControlsButton.Next" />
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_media_controller.xml b/media2/media2-widget/src/main/res/layout/media2_widget_media_controller.xml
deleted file mode 100644
index 03b1417..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_media_controller.xml
+++ /dev/null
@@ -1,243 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <FrameLayout
-        android:id="@+id/center_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layoutDirection="ltr">
-
-        <View
-            android:id="@+id/center_view_background"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@color/media2_widget_center_view_background" />
-
-        <include
-            android:id="@+id/embedded_transport_controls"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            layout="@layout/media2_widget_embedded_transport_controls" />
-
-        <include
-            android:id="@+id/minimal_transport_controls"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            layout="@layout/media2_widget_minimal_transport_controls" />
-    </FrameLayout>
-
-    <LinearLayout
-        android:id="@+id/title_bar"
-        android:background="@drawable/media2_widget_title_bar_gradient"
-        android:baselineAligned="false"
-        style="@style/Media2_Widget_TitleBar">
-
-        <LinearLayout
-            android:id="@+id/title_bar_left"
-            android:gravity="center_vertical"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:orientation="horizontal">
-
-            <TextView
-                android:id="@+id/title_text"
-                android:gravity="center_vertical"
-                android:ellipsize="middle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:paddingStart="@dimen/media2_widget_embedded_icon_padding"
-                android:paddingEnd="@dimen/media2_widget_embedded_icon_padding"
-                android:textSize="15sp"
-                android:textColor="#FFFFFFFF"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:id="@+id/title_bar_right"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <LinearLayout
-                android:id="@+id/ad_external_link"
-                android:clickable="true"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingStart="5dip"
-                android:paddingLeft="5dip"
-                android:paddingEnd="10dip"
-                android:paddingRight="10dip"
-                android:orientation="horizontal"
-                android:visibility="gone">
-
-                <TextView
-                    android:id="@+id/ad_text"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_vertical"
-                    android:paddingEnd="5dip"
-                    android:paddingRight="5dip"
-                    android:text="@string/MediaControlView_ad_text"
-                    android:textSize="10sp"
-                    android:textColor="#FFFFFFFF" />
-
-                <ImageButton
-                    android:id="@+id/ad_launch"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    style="@style/Media2_Widget_TitleBarButton.Launch" />
-            </LinearLayout>
-        </LinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/minimal_fullscreen_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="end"
-        android:layoutDirection="ltr">
-
-        <ImageButton
-            android:id="@+id/minimal_fullscreen"
-            style="@style/Media2_Widget_BottomBarButton.FullScreen" />
-    </LinearLayout>
-
-    <View
-        android:id="@+id/bottom_bar_background"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/media2_widget_bottom_bar_height"
-        android:background="@color/media2_widget_bottom_bar_background"
-        android:layoutDirection="ltr" />
-
-    <LinearLayout
-        android:id="@+id/bottom_bar_left"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/media2_widget_bottom_bar_height"
-        android:layoutDirection="ltr">
-
-        <include
-            android:id="@+id/full_transport_controls"
-            layout="@layout/media2_widget_full_transport_controls" />
-
-        <TextView
-            android:id="@+id/ad_skip_time"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="4dp"
-            android:layout_marginLeft="4dp"
-            android:gravity="center"
-            android:textColor="#FFFFFF"
-            android:textSize="12sp"
-            android:visibility="gone" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/time"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/media2_widget_bottom_bar_height"
-        android:paddingStart="10dp"
-        android:paddingEnd="10dp"
-        android:layoutDirection="ltr">
-
-        <TextView
-            android:id="@+id/time_current"
-            style="@style/Media2_Widget_TimeText.Current" />
-
-        <TextView
-            android:id="@+id/time_interpunct"
-            style="@style/Media2_Widget_TimeText.Interpunct" />
-
-        <TextView
-            android:id="@+id/time_end"
-            style="@style/Media2_Widget_TimeText.End" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/basic_controls"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:layoutDirection="ltr">
-
-        <TextView
-            android:id="@+id/ad_remaining"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="#FFFFFF"
-            android:textSize="12sp"
-            android:visibility="gone" />
-
-        <ImageButton
-            android:id="@+id/subtitle"
-            style="@style/Media2_Widget_BottomBarButton.CC"
-            android:alpha="0.5"
-            android:scaleType="fitCenter"
-            android:visibility="gone" />
-
-        <ImageButton
-            android:id="@+id/fullscreen"
-            style="@style/Media2_Widget_BottomBarButton.FullScreen"
-            android:visibility="gone" />
-
-        <ImageButton
-                android:id="@+id/overflow_show"
-                style="@style/Media2_Widget_BottomBarButton.OverflowShow" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/extra_controls"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:visibility="gone"
-        android:layoutDirection="ltr">
-
-        <ImageButton
-            android:id="@+id/settings"
-            style="@style/Media2_Widget_BottomBarButton.Settings" />
-
-        <ImageButton
-                android:id="@+id/overflow_hide"
-                style="@style/Media2_Widget_BottomBarButton.OverflowHide" />
-    </LinearLayout>
-
-    <FrameLayout
-        android:id="@+id/progress_bar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/media2_widget_custom_progress_thumb_size"
-        android:layoutDirection="ltr">
-
-        <SeekBar
-            android:id="@+id/progress"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:contentDescription="@string/mcv2_seek_bar_desc"
-            android:paddingStart="0dp"
-            android:paddingEnd="0dp"
-            android:maxHeight="@dimen/media2_widget_custom_progress_max_size"
-            android:minHeight="@dimen/media2_widget_custom_progress_max_size"
-            android:progressDrawable="@drawable/media2_widget_custom_progress"
-            android:thumb="@drawable/media2_widget_custom_progress_thumb"
-            android:thumbOffset="0dp"
-            android:splitTrack="false"
-            android:elevation="10dp" />
-    </FrameLayout>
-</merge>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_minimal_transport_controls.xml b/media2/media2-widget/src/main/res/layout/media2_widget_minimal_transport_controls.xml
deleted file mode 100644
index 44b4a29..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_minimal_transport_controls.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:orientation="horizontal">
-
-    <ImageButton android:id="@+id/pause" style="@style/Media2_Widget_MinimalTransportControlsButton" />
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_landscape.xml b/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_landscape.xml
deleted file mode 100644
index 72e9ef5..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_landscape.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:padding="@dimen/media2_widget_music_view_full_padding"
-    android:background="@color/media2_widget_music_view_default_background"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center">
-
-    <ImageView
-        android:id="@+id/album"
-        android:layout_width="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_height="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_gravity="center_vertical"
-        android:layout_marginEnd="@dimen/media2_widget_music_view_image_title_gap"
-        android:layout_marginRight="@dimen/media2_widget_music_view_image_title_gap"
-        android:src="@drawable/media2_widget_ic_default_album_image" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="@dimen/media2_widget_music_view_landscape_text_minimum_width"
-        android:layout_gravity="center_vertical"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/mcv2_music_title_unknown_text"
-            android:textSize="@dimen/media2_widget_music_view_title_text_size"
-            android:textStyle="bold"
-            android:textColor="#FFFFFF" />
-
-        <TextView
-            android:id="@+id/artist"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/mcv2_music_artist_unknown_text"
-            android:textSize="@dimen/media2_widget_music_view_artist_text_size"
-            android:textColor="#BBBBBB" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_portrait.xml b/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_portrait.xml
deleted file mode 100644
index ba726b6..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_music_with_title_portrait.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:padding="@dimen/media2_widget_music_view_full_padding"
-    android:background="@color/media2_widget_music_view_default_background"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:orientation="vertical">
-
-    <ImageView
-        android:id="@+id/album"
-        android:layout_width="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_height="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_marginBottom="@dimen/media2_widget_music_view_image_title_gap"
-        android:src="@drawable/media2_widget_ic_default_album_image"/>
-
-    <TextView
-        android:id="@+id/title"
-        android:maxWidth="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/mcv2_music_title_unknown_text"
-        android:textSize="@dimen/media2_widget_music_view_title_text_size"
-        android:textStyle="bold"
-        android:textColor="#FFFFFF" />
-
-    <TextView
-        android:id="@+id/artist"
-        android:maxWidth="@dimen/media2_widget_music_view_full_image_size"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/mcv2_music_artist_unknown_text"
-        android:textSize="@dimen/media2_widget_music_view_artist_text_size"
-        android:textColor="#BBBBBB" />
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_music_without_title.xml b/media2/media2-widget/src/main/res/layout/media2_widget_music_without_title.xml
deleted file mode 100644
index 6fed130..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_music_without_title.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:background="@color/media2_widget_music_view_default_background"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ImageView
-        android:id="@+id/album"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:scaleType="fitCenter"
-        android:src="@drawable/media2_widget_ic_default_album_image" />
-</FrameLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_settings_list.xml b/media2/media2-widget/src/main/res/layout/media2_widget_settings_list.xml
deleted file mode 100644
index fd26c24..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_settings_list.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/media2_widget_embedded_settings_width"
-    android:layout_height="@dimen/media2_widget_settings_height"
-    android:layout_gravity="center_vertical"
-    android:divider="@null"
-    android:dividerHeight="0dp">
-</ListView>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_settings_list_item.xml b/media2/media2-widget/src/main/res/layout/media2_widget_settings_list_item.xml
deleted file mode 100644
index f56113bb..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_settings_list_item.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="@dimen/media2_widget_settings_height"
-    android:orientation="horizontal"
-    android:background="@color/media2_widget_black_opacity_70">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/media2_widget_settings_height"
-        android:gravity="center|start"
-        android:orientation="horizontal">
-
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="@dimen/media2_widget_settings_icon_size"
-            android:layout_height="@dimen/media2_widget_settings_icon_size"
-            android:layout_margin="8dp" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center|start">
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-
-            <TextView
-                android:id="@+id/main_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingStart="2dp"
-                android:paddingLeft="2dp"
-                android:textColor="@color/media2_widget_white"
-                android:textSize="@dimen/media2_widget_settings_main_text_size"/>
-
-            <TextView
-                android:id="@+id/sub_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingStart="2dp"
-                android:paddingLeft="2dp"
-                android:textColor="@color/media2_widget_white_opacity_70"
-                android:textSize="@dimen/media2_widget_settings_sub_text_size"/>
-        </LinearLayout>
-    </LinearLayout>
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/layout/media2_widget_sub_settings_list_item.xml b/media2/media2-widget/src/main/res/layout/media2_widget_sub_settings_list_item.xml
deleted file mode 100644
index b43ad6f..0000000
--- a/media2/media2-widget/src/main/res/layout/media2_widget_sub_settings_list_item.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="@dimen/media2_widget_settings_height"
-    android:orientation="horizontal"
-    android:background="@color/media2_widget_black_opacity_70">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/media2_widget_settings_height"
-        android:gravity="center|start"
-        android:orientation="horizontal">
-
-        <ImageView
-            android:id="@+id/check"
-            android:layout_width="@dimen/media2_widget_settings_icon_size"
-            android:layout_height="@dimen/media2_widget_settings_icon_size"
-            android:layout_margin="8dp"
-            android:src="@drawable/media2_widget_ic_check"/>
-    </LinearLayout>
-
-    <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/media2_widget_settings_height"
-        android:gravity="center|start"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/text"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/media2_widget_settings_text_height"
-            android:paddingStart="2dp"
-            android:paddingLeft="2dp"
-            android:textColor="@color/media2_widget_white"
-            android:textSize="@dimen/media2_widget_settings_main_text_size"/>
-    </RelativeLayout>
-</LinearLayout>
diff --git a/media2/media2-widget/src/main/res/values-af/strings.xml b/media2/media2-widget/src/main/res/values-af/strings.xml
deleted file mode 100644
index 8988b11..0000000
--- a/media2/media2-widget/src/main/res/values-af/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Af"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Oudiosnit"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Geen"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Terugspeelspoed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normaal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Snit <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Snit <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Snit <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video se titel is onbekend"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Liedjie se titel is onbekend"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Kunstenaar is onbekend"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Kon nie die item speel wat jy versoek het nie"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Terug"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"\"Terug na vorige\"-knoppielys"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Sien meer knoppies"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Terugspeelvordering"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Instellings"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Onderskrif is aan. Klik om dit te versteek."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Onderskrif is af. Klik om dit te wys."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Herspeel"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Speel"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Onderbreek"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Vorige media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Volgende media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Spoel 10 sekondes terug"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Gaan 30 sekondes vorentoe"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Volskerm"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-am/strings.xml b/media2/media2-widget/src/main/res/values-am/strings.xml
deleted file mode 100644
index e95ccd2..0000000
--- a/media2/media2-widget/src/main/res/values-am/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"አጥፋ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"የድምጽ ትራክ"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ምንም"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"የመልሶ ማጫወት ፍጥነት"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"መደበኛ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ትራክ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ትራክ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ትራክ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ያልታወቀ የቪዲዮ አርዕስት"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ያልታወቀ የዘፈን ርዕስ"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ያልታወቀ አርቲስት"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"የጠየቁትን ንጥል ማጫወት አልተቻለም"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"እሺ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ተመለስ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ወደ ቀዳሚው የአዝራር ዝርዝር ተመለስ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ተጨማሪ አዝራሮችን ይመልከቱ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"የመልሶ ማጫወት ሂደት"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ቅንብሮች"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"የግርጌ ጽሁፍ በርቷል። ለመደበቅ ጠቅ ያድርጉ።"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"የግርጌ ጽሁፍ ጠፍቷል። ለማሳየት ጠቅ ያድርጉ።"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ዳግም አጫውት"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"አጫውት"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ላፍታ አቁም"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ቀዳሚ ማህደረ መረጃ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ቀጣይ ማህደረ መረጃ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"በ10 ሰከንዶች አጠንጥን"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"ወደፊት በ30 ሰከንዶች ሂድ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ሙሉ ማያ ገፅ"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ar/strings.xml b/media2/media2-widget/src/main/res/values-ar/strings.xml
deleted file mode 100644
index 1883e1e..0000000
--- a/media2/media2-widget/src/main/res/values-ar/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"الترجمة غير مفعّلة"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"المقطع الصوتي"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ما مِن مقاطع صوتية"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"سرعة التشغيل"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"عادية"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"المقطع الصوتي <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"المقطع الصوتي <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"المقطع الصوتي <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"عنوان الفيديو غير معروف"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"عنوان الأغنية غير معروف"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"الفنان غير معروف"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"تعذّر تشغيل الفيديو الذي طلبته."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"حسنًا"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"الرجوع"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"الرجوع إلى قائمة الازرار السابقة"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"عرض مزيد من الأزرار"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"مستوى تقدُّم التشغيل"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"الإعدادات"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"الترجمة مفعّلة. انقر لإخفائها."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"الترجمة غير مفعّلة. انقر لعرضها."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"إعادة التشغيل"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"تشغيل"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"إيقاف مؤقت"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"الوسائط السابقة"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"الوسائط التالية"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"ترجيع الفيديو بمقدار 10 ثوانٍ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"تقديم بمقدار 30 ثانية"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"وضع ملء الشاشة"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-as/strings.xml b/media2/media2-widget/src/main/res/values-as/strings.xml
deleted file mode 100644
index ab43897..0000000
--- a/media2/media2-widget/src/main/res/values-as/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"অফ কৰক"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"অডিঅ’ ট্ৰেক"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"একো নাই"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"প্লে’বেকৰ গতিবেগ"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"সাধাৰণ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ট্ৰেক নম্বৰ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ট্ৰেক নম্বৰ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ট্ৰেক নম্বৰ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ভিডিঅ’ৰ শিৰোনাম অজ্ঞাত"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"গীতৰ শিৰোনাম অজ্ঞাত"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"অজ্ঞাত শিল্পী"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"আপুনি অনুৰোধ জনোৱা বস্তুটো প্লে’ কৰিব পৰা নগ’ল"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ঠিক আছে"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"উভতি যাওক"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"পূৰ্বৱৰ্তী বুটামৰ সূচীলৈ উভতি যাওক"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"অধিক বুটাম চাওক"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"প্লে’বেকৰ অগ্ৰগতি"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ছেটিং"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"ছাবটাইটেল অন কৰা আছে। লুকুৱাবলৈ ক্লিক কৰক।"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"ছাবটাইটেল অফ কৰা আছে। দেখুৱাবলৈ ক্লিক কৰক।"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"পুনৰ প্লে’ কৰক"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"প্লে’ কৰক"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"পজ কৰক"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"পূৰ্বৱৰ্তী মিডিয়া"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"পৰৱৰ্তী মিডিয়া"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"১০ ছেকেণ্ডকৈ ৰিৱাইণ্ড কৰক"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"৩০ ছেকেণ্ডকৈ ফৰৱাৰ্ড কৰক"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"পূৰ্ণ স্ক্ৰীন"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-az/strings.xml b/media2/media2-widget/src/main/res/values-az/strings.xml
deleted file mode 100644
index cb3d949..0000000
--- a/media2/media2-widget/src/main/res/values-az/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Deaktiv"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio trek"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Heç biri"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Oxutma sürəti"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Trek <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Videonun başlığı bilinmir"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Mahnının adı bilinmir"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"İfaçı bilinmir"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Təklif etdiyiniz videonu oxutmaq alınmadı"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Geriyə"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Əvvəlki düymə siyahısına geri qayıdın"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Daha çox düyməyə baxın"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Oxutmanın gedişatı"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ayarlar"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitr aktivdir. Gizlətmək üçün toxunun."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitr deaktivdir. Göstərmək üçün toxunun."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Yenidən oxudun"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Oxudun"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Durdurun"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Əvvəlki media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Növbəti media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 saniyə geri çəkin"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 saniyə irəli çəkin"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Tam ekran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-b+sr+Latn/strings.xml b/media2/media2-widget/src/main/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index bcf7c55..0000000
--- a/media2/media2-widget/src/main/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Isključeno"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio snimak"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nema"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Brzina reprodukcije"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normalno"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. pesma"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. pesma – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. pesma"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Nepoznat naziv videa"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nepoznat naziv pesme"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nepoznat izvođač"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nismo uspeli da pustimo stavku koju ste zahtevali"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Važi"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Nazad"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Nazad na prethodnu listu dugmadi"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Prikaži još dugmadi"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Napredovanje reprodukcije"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Podešavanja"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titl je uključen. Kliknite da biste ga sakrili."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titl je isključen. Kliknite da bi se prikazivao."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Pusti opet"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Pusti"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pauziraj"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Prethodni medijski fajl"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Sledeći medijski fajl"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Premotaj unazad 10 sekundi"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Premotaj 30 sekundi unapred"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Ceo ekran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-be/strings.xml b/media2/media2-widget/src/main/res/values-be/strings.xml
deleted file mode 100644
index ee229a7..0000000
--- a/media2/media2-widget/src/main/res/values-be/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Выключана"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Гукавая дарожка"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Няма"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Хуткасць прайгравання"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Звычайная"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Трэк <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Трэк <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Трэк <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Невядомая назва відэа"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Невядомая назва кампазіцыі"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Невядомы выканаўца"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Не ўдалося прайграць гэта відэа"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ОК"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Да папярэдняга спіса кнопак"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Паказаць дадатковыя кнопкі"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Ход прайгравання"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Налады"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Субцітры ўключаны. Націсніце, каб схаваць."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Субцітры выключаны. Націсніце, каб паказаць."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Паўтарыць"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Прайграць"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Прыпыніць"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Папярэдні файл мультымедыя"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Наступны файл мультымедыя"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Перайсці на 10 секунд назад"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Перайсці на 30 секунд уперад"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Поўнаэкранны рэжым"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-bg/strings.xml b/media2/media2-widget/src/main/res/values-bg/strings.xml
deleted file mode 100644
index bec4fa1..0000000
--- a/media2/media2-widget/src/main/res/values-bg/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Изключено"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Аудиозапис"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Няма"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Скорост на възпроизвеждане"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Нормално"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Запис <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Запис <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Запис <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Неизвестно заглавие на видеоклипа"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Неизвестно заглавие на песента"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Неизвестен изпълнител"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Възпроизвеждането на заявения от вас елемент не бе успешно"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Към предишния списък с бутони"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Вижте още бутони"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Напредък на възпроизвеждането"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Настройки"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Субтитрите са включени. Кликнете, за да се скрият."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Субтитрите са изключени. Кликнете, за да се покажат."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Повторно пускане"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Пускане"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Поставяне на пауза"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Предишен мултимедиен елемент"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Следващ мултимедиен елемент"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Превъртане назад с 10 секунди"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Превъртане напред с 30 секунди"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Цял екран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-bn/strings.xml b/media2/media2-widget/src/main/res/values-bn/strings.xml
deleted file mode 100644
index 84d67f6..0000000
--- a/media2/media2-widget/src/main/res/values-bn/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"বন্ধ আছে"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"অডিও ট্র্যাক"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"কোনও অডিও ট্র্যাক নেই"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"প্লেব্যাক স্পিড"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"স্বাভাবিক"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"০০:০০:০০"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ট্র্যাক <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ট্র্যাক <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ট্র্যাক <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ভিডিওর নাম অজানা"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"গানের নাম অজানা"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"অজানা শিল্পী"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"আপনার অনুরোধ করা আইটেমটি চালানো যায়নি"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ঠিক আছে"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ফিরুন"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"পূর্ববর্তী বোতামের তালিকাতে ফিরে যান"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"আরও বোতাম দেখুন"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"কতটা চালানো হয়েছে"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"সেটিংস"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"সাবটাইটেল চালু আছে। এটি লুকাতে ক্লিক করুন।"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"সাবটাইটেল বন্ধ আছে। এটি দেখতে ক্লিক করুন।"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"রিপ্লে করুন"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"চালান"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"পজ করুন"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"পূর্ববর্তী মিডিয়া"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"পরবর্তী মিডিয়া"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"১০ সেকেন্ড রিওয়াইন্ড করুন"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"৩০ সেকেন্ড ফরোয়ার্ড করুন"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ফুল-স্ক্রিন"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-bs/strings.xml b/media2/media2-widget/src/main/res/values-bs/strings.xml
deleted file mode 100644
index 9fe2c38..0000000
--- a/media2/media2-widget/src/main/res/values-bs/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Isključeno"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Zvučni zapis"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ništa"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Brzina reprodukcije"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normalno"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Numera <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Numera <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Numera <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Nepoznat naziv videozapisa"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nepoznat naziv pjesme"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nepoznat izvođač"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nije moguće reproducirati stavku koju ste zatražili"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Uredu"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Nazad"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Nazad na prethodnu listu dugmadi"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Prikaz više dugmadi"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Napredak reprodukcije"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Postavke"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titlovi su uključeni. Kliknite da ih sakrijete."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titlovi su isključeni. Kliknite da se prikažu."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Ponovna reprodukcija"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproduciranje"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pauza"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Prethodna medijska stavka"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Sljedeća medijska stavka"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Vraćanje nazad 10 sekundi"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Pomjeranje naprijed 30 sekundi"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Prikaz preko cijelog ekrana"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ca/strings.xml b/media2/media2-widget/src/main/res/values-ca/strings.xml
deleted file mode 100644
index 75d1e5a..0000000
--- a/media2/media2-widget/src/main/res/values-ca/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desactivat"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Pista d\'àudio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Cap"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocitat de reproducció"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>: <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Pista <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Títol de vídeo desconegut"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Títol de cançó desconegut"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconegut"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"No s\'ha pogut reproduir l\'element que has sol·licitat"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"D\'acord"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Enrere"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Torna a la llista de botons anterior"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Mostra més botons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progrés de la reproducció"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Configuració"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtítols activats. Fes clic per amagar."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtítols desactivats. Fes clic per veure."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Torna a reproduir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reprodueix"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Posa en pausa"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Element multimèdia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Element multimèdia següent"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rebobina 10 segons"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avança 30 segons"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pantalla completa"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-cs/strings.xml b/media2/media2-widget/src/main/res/values-cs/strings.xml
deleted file mode 100644
index 61a0480..0000000
--- a/media2/media2-widget/src/main/res/values-cs/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Vypnuto"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Zvuková stopa"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Žádné"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Rychlost přehrávání"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normální"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Stopa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Stopa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Stopa <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Neznámý název videa"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Neznámý název skladby"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Neznámý interpret"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Požadovanou položku se nepodařilo přehrát"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Zpět"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Zpět na předchozí seznam tlačítek"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Zobrazit další tlačítka"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Průběh přehrávání"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Nastavení"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titulky jsou zapnuté. Kliknutím je skryjete."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titulky jsou vypnuté. Kliknutím je zobrazíte."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Přehrát znovu"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Přehrát"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pozastavit"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Předchozí mediální objekt"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Další mediální objekt"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rychlý posun zpět po 10 sekundách"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Přejít o 30 sekund vpřed"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Celá obrazovka"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-da/strings.xml b/media2/media2-widget/src/main/res/values-da/strings.xml
deleted file mode 100644
index e01ec56..0000000
--- a/media2/media2-widget/src/main/res/values-da/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Deaktiveret"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Lydspor"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ingen"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Afspilningshastighed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Spor <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Spor <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Spor <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Ukendt videotitel"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Ukendt sangtitel"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Ukendt kunstner"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Den video, du anmodede om, kunne ikke afspilles"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Tilbage"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Tilbage til forrige liste over knapper"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Se flere knapper"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Afspilningsstatus"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Indstillinger"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Undertekster er aktiveret. Klik for at skjule dem."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Undertekster er deaktiveret. Klik for at se dem."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Afspil igen"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Afspil"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Sæt på pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Forrige medie"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Næste medie"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Spol 10 sekunder tilbage"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Spol 30 sekunder frem"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Fuld skærm"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-de/strings.xml b/media2/media2-widget/src/main/res/values-de/strings.xml
deleted file mode 100644
index 776662e..0000000
--- a/media2/media2-widget/src/main/res/values-de/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Aus"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audiotrack"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Keine"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Wiedergabegeschwindigkeit"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Spur <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Spur <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Unbekannter Videotitel"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Unbekannter Musiktitel"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Unbekannter Interpret"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Das angeforderte Video konnte nicht wiedergegeben werden"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Zurück"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Zurück zur vorherigen Schaltflächenliste"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Weitere Schaltflächen zeigen"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Wiedergabefortschritt"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Einstellungen"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Untertitel sind aktiviert. Zum Ausblenden klicken."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Untertitel sind deaktiviert. Zum Einblenden klicken."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Noch mal"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Wiedergeben"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausieren"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Vorheriges Medium"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Nächstes Medium"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 Sekunden zurück"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 Sekunden vor"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Vollbild"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-el/strings.xml b/media2/media2-widget/src/main/res/values-el/strings.xml
deleted file mode 100644
index 2a3d8cf..0000000
--- a/media2/media2-widget/src/main/res/values-el/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Ανενεργό"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Κομμάτι ήχου"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Κανένα"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Ταχύτητα αναπαραγωγής"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Κανονική"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Κομμάτι <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Κομμάτι <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Κομμάτι <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Άγνωστος τίτλος βίντεο"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Άγνωστος τίτλος τραγουδιού"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Άγνωστος καλλιτέχνης"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Δεν ήταν δυνατή η αναπαραγωγή του στοιχείου που ζητήσατε"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ΟΚ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Πίσω"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Επιστροφή στην προηγούμενη λίστα κουμπιών"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Εμφάνιση περισσότερων κουμπιών"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Πρόοδος αναπαραγωγής"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ρυθμίσεις"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Ο υπότιτλος είναι ενεργός. Κάντε κλικ για να τον αποκρύψετε."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Ο υπότιτλος είναι ανενεργός. Κάντε κλικ για να τον εμφανίσετε."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Επανάληψη"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Αναπαραγωγή"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Παύση"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Προηγούμενο μέσο"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Επόμενο μέσο"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Μετάβαση προς τα πίσω κατά 10 δευτερόλεπτα"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Μετάβαση προς τα εμπρός κατά 30 δευτερόλεπτα"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Πλήρης οθόνη"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-en-rAU/strings.xml b/media2/media2-widget/src/main/res/values-en-rAU/strings.xml
deleted file mode 100644
index a15ebb7..0000000
--- a/media2/media2-widget/src/main/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio track"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"None"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Playback speed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video title unknown"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Song title unknown"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist unknown"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item that you requested"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Back"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Back to previous button list"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"See more buttons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Playback progress"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Settings"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitle is on. Click to hide it."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitle is off. Click to show it."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Replay"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Play"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Previous media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Next media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rewind by 10 seconds"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forwards 30 seconds"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-en-rCA/strings.xml b/media2/media2-widget/src/main/res/values-en-rCA/strings.xml
deleted file mode 100644
index bf17433..0000000
--- a/media2/media2-widget/src/main/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio track"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"None"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Playback speed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video title unknown"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Song title unknown"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist unknown"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item you requested"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Back"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Back to previous button list"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"See more buttons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Playback progress"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Settings"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitle is on. Click to hide it."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitle is off. Click to show it."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Replay"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Play"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Previous media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Next media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rewind by 10 seconds"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forward by 30 seconds"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-en-rGB/strings.xml b/media2/media2-widget/src/main/res/values-en-rGB/strings.xml
deleted file mode 100644
index a15ebb7..0000000
--- a/media2/media2-widget/src/main/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio track"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"None"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Playback speed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video title unknown"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Song title unknown"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist unknown"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item that you requested"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Back"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Back to previous button list"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"See more buttons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Playback progress"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Settings"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitle is on. Click to hide it."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitle is off. Click to show it."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Replay"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Play"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Previous media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Next media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rewind by 10 seconds"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forwards 30 seconds"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-en-rIN/strings.xml b/media2/media2-widget/src/main/res/values-en-rIN/strings.xml
deleted file mode 100644
index a15ebb7..0000000
--- a/media2/media2-widget/src/main/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio track"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"None"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Playback speed"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video title unknown"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Song title unknown"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist unknown"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item that you requested"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Back"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Back to previous button list"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"See more buttons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Playback progress"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Settings"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitle is on. Click to hide it."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitle is off. Click to show it."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Replay"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Play"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Previous media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Next media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rewind by 10 seconds"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forwards 30 seconds"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-en-rXC/strings.xml b/media2/media2-widget/src/main/res/values-en-rXC/strings.xml
deleted file mode 100644
index 7b5584e..0000000
--- a/media2/media2-widget/src/main/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎Off‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎Audio track‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎None‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎Playback speed‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎Normal‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎00:00:00‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎Track ‎‏‎‎‏‏‎<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎Track ‎‏‎‎‏‏‎<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="LANG">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎Track ‎‏‎‎‏‏‎<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎Video title unknown‎‏‎‎‏‎"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎Song title unknown‎‏‎‎‏‎"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎Artist unknown‎‏‎‎‏‎"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎Couldn\'t play the item you requested‎‏‎‎‏‎"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎OK‎‏‎‎‏‎"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎Back‎‏‎‎‏‎"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎Back to previous button list‎‏‎‎‏‎"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‎‏‎‏‏‏‎See more buttons‎‏‎‎‏‎"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎Playback progress‎‏‎‎‏‎"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎Settings‎‏‎‎‏‎"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎Subtitle is on. Click to hide it.‎‏‎‎‏‎"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎Subtitle is off. Click to show it.‎‏‎‎‏‎"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎Replay‎‏‎‎‏‎"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎Play‎‏‎‎‏‎"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎Pause‎‏‎‎‏‎"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‏‏‎‏‎Previous media‎‏‎‎‏‎"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎Next media‎‏‎‎‏‎"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎Rewind by 10 seconds‎‏‎‎‏‎"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎Go forward by 30 seconds‎‏‎‎‏‎"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎Full screen‎‏‎‎‏‎"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-es-rUS/strings.xml b/media2/media2-widget/src/main/res/values-es-rUS/strings.xml
deleted file mode 100644
index a98eb4e..0000000
--- a/media2/media2-widget/src/main/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desactivados"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Pista de audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Sin contenido"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidad de reproducción"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Pista <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título de video desconocido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título de canción desconocido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconocido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"No se pudo reproducir el elemento que solicitaste"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Aceptar"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atrás"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Volver a la lista anterior de botones"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ver más botones"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Reproducción en curso"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Configuración"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Los subtítulos están activados. Haz clic para ocultarlos."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Los subtítulos están desactivados. Haz clic para mostrarlos."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Volver a reproducir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproducir"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausar"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Archivo multimedia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Siguiente archivo multimedia"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Retroceder 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avanzar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pantalla completa"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-es/strings.xml b/media2/media2-widget/src/main/res/values-es/strings.xml
deleted file mode 100644
index 2bc8eca..0000000
--- a/media2/media2-widget/src/main/res/values-es/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desactivados"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Pista de audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ninguna"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidad de reproducción"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> (<xliff:g id="LANG">%2$s</xliff:g>)"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Pista <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título de vídeo desconocido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título de canción desconocido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconocido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"No se ha podido reproducir el elemento que has solicitado"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Aceptar"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atrás"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Ir a la lista anterior de botones"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ver más botones"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progreso de reproducción"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ajustes"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Con subtítulos; ocúltalos con un clic."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Sin subtítulos; haz clic para verlos."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Volver a reproducir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproducir"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausar"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Anterior archivo multimedia"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Siguiente archivo multimedia"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rebobinar 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avanzar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pantalla completa"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-et/strings.xml b/media2/media2-widget/src/main/res/values-et/strings.xml
deleted file mode 100644
index d7ccaed..0000000
--- a/media2/media2-widget/src/main/res/values-et/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Väljas"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Helirada"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Pole"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Taasesituskiirus"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Tavaline"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00.00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. lugu"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. lugu – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. rada"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Tundmatu video pealkiri"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Tundmatu loo pealkiri"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Tundmatu esitaja"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Teie soovitud üksust ei saanud esitada"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Tagasi"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Tagasi eelmise nupuloendi juurde"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Rohkemate nuppude kuvamine"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Taasesitus on pooleli"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Seaded"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtiitrid on sees. Klõpsake nende peitmiseks."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtiitrid on väljas. Klõpsake nende kuvamiseks."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Uuesti esitamine"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Esitamine"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Peatamine"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Eelmine meediaüksus"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Järgmine meediaüksus"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 sekundit tagasi kerimine"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 sekundit edasi kerimine"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Täisekraan"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-eu/strings.xml b/media2/media2-widget/src/main/res/values-eu/strings.xml
deleted file mode 100644
index 5999909..0000000
--- a/media2/media2-widget/src/main/res/values-eu/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desaktibatuta"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio-pista"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Bat ere ez"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Erreprodukzio-abiadura"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normala"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. pista"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. pista (<xliff:g id="LANG">%2$s</xliff:g>)"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. pista"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Bideoaren izen ezezaguna"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Abestiaren izen ezezaguna"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista ezezaguna"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Ezin izan da erreproduzitu eskatu duzun bideoa"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Ados"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atzera"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Itzuli aurreko botoi-zerrendara"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ikusi botoi gehiago"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Erreprodukzioaren garapena"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ezarpenak"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Azpitituluak aktibatuta daude. Sakatu ezkutatzeko."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Azpitituluak desaktibatuta daude. Sakatu erakusteko."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Erreproduzitu berriro"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Erreproduzitu"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausatu"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Aurreko multimedia-elementua"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Hurrengo multimedia-elementua"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Atzeratu 10 segundo"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Aurreratu 30 segundo"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pantaila osoa"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-fa/strings.xml b/media2/media2-widget/src/main/res/values-fa/strings.xml
deleted file mode 100644
index 058ed11..0000000
--- a/media2/media2-widget/src/main/res/values-fa/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"خاموش"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"قطعه"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"هیچ‌کدام"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"سرعت بازپخش"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"عادی"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"۰۰:۰۰:۰۰"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"آهنگ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"آهنگ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"آهنگ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"عنوان ویدیو نامشخص"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"عنوان آهنگ نامشخص"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"هنرمند ناشناس"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"مورد درخواستی پخش نشد"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"تأیید"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"برگشت"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"فهرست دکمه برگشتن به‌عقب"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"مشاهده دکمه‌های بیشتر"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"پیشرفت بازپخش"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"تنظیمات"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"زیرنویس روشن است. برای پنهان کردن آن کلیک کنید."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"زیرنویس خاموش است. برای نمایش آن کلیک کنید."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"بازپخش"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"پخش"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"مکث"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"رسانه قبلی"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"رسانه بعدی"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"۱۰ ثانیه به عقب رفتن"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"۳۰ ثانیه رفتن به جلو"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"تمام‌صفحه"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-fi/strings.xml b/media2/media2-widget/src/main/res/values-fi/strings.xml
deleted file mode 100644
index 16e5bc3..0000000
--- a/media2/media2-widget/src/main/res/values-fi/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Pois päältä"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Ääniraita"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ei mitään"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Toistonopeus"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normaali"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Raita <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Raita <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Raita <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Videon nimi tuntematon"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Kappaleen nimi tuntematon"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artisti tuntematon"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Valitsemaasi kohdetta ei voitu toistaa"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Takaisin"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Palaa edellisiin painikkeisiin"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Näytä lisää painikkeita"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Toiston edistyminen"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Asetukset"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Tekstitys päällä. Piilota klikkaamalla."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Tekstitys ei päällä. Näytä klikkaamalla."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Toista uudelleen"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Toista"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Keskeytä"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Edellinen mediatiedosto"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Seuraava mediatiedosto"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Kelaa 10 sekuntia taaksepäin"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Siirry 30 sekuntia eteenpäin"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Koko näyttö"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-fr-rCA/strings.xml b/media2/media2-widget/src/main/res/values-fr-rCA/strings.xml
deleted file mode 100644
index d05323a..0000000
--- a/media2/media2-widget/src/main/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Désactivés"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Piste audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Aucune"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Vitesse de lecture"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normale"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Piste <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Piste <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> (<xliff:g id="LANG">%2$s</xliff:g>)"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Piste <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titre de vidéo inconnu"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titre de chanson inconnu"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artiste inconnu"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Impossible de lire l\'élément demandé"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Retour"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Retour à la liste de boutons précédente"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Afficher plus de boutons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progression de la lecture"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Paramètres"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Les sous-titres sont activés. Cliquez ici pour les masquer."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Les sous-titres sont désactivés. Cliquez ici pour les afficher."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Revoir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Lire"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Élément précédent"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Élément suivant"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Reculer de 10 secondes"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avancer de 30 secondes"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Plein écran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-fr/strings.xml b/media2/media2-widget/src/main/res/values-fr/strings.xml
deleted file mode 100644
index 3febcde..0000000
--- a/media2/media2-widget/src/main/res/values-fr/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Désactivés"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Piste audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Aucune"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Vitesse de lecture"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normale"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Piste <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Piste <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Piste <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titre de la vidéo inconnu"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titre du morceau inconnu"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artiste inconnu"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Impossible de lire l\'élément demandé"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Retour"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Retour à la liste de boutons précédente"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Afficher plus de boutons"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progression de la lecture"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Paramètres"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Sous-titres activés. Cliquez pour les masquer."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Sous-titres désactivés. Cliquez pour les afficher."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Revoir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Lire"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Contenu multimédia précédent"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Contenu multimédia suivant"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Revenir en arrière de 10 secondes"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avancer de 30 secondes"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Plein écran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-gl/strings.xml b/media2/media2-widget/src/main/res/values-gl/strings.xml
deleted file mode 100644
index 2768ea8..0000000
--- a/media2/media2-widget/src/main/res/values-gl/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desactivado"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Pista de audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ningunha pista de audio"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidade de reprodución"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Pista <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> (<xliff:g id="LANG">%2$s</xliff:g>)"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Pista <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título do vídeo descoñecido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título da canción descoñecido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista descoñecido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Non se puido reproducir o elemento que solicitaches"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Aceptar"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atrás"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Volve á lista anterior de botóns"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Mostra máis botóns"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progreso da reprodución"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Configuración"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Os subtítulos están activados. Fai clic para ocultalos."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Os subtítulos están desactivados. Fai clic para mostralos."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Reproducir de novo"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproducir"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pór en pausa"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Ficheiro multimedia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Seguinte ficheiro multimedia"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Retroceder 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avanzar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pantalla completa"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-gu/strings.xml b/media2/media2-widget/src/main/res/values-gu/strings.xml
deleted file mode 100644
index 86e6521..0000000
--- a/media2/media2-widget/src/main/res/values-gu/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"બંધ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ઑડિયો ટ્રૅક"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"એકપણ નહીં"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"પ્લેબૅકની ઝડપ"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"સામાન્ય"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ટ્રૅક <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ટ્રૅક <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ટ્રૅક <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"વીડિયોનું શીર્ષક અજાણ્યું"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ગીતનું શીર્ષક અજાણ્યું"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"અજાણ્યા કલાકાર"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"તમે વિનંતી કરેલી આઇટમ ચલાવી શક્યાં નથી"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ઓકે"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"પાછળ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"પહેલાંની બટન સૂચિ પર પાછા જાઓ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"વધુ બટન જુઓ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"પ્લેબૅક ચાલુ છે"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"સેટિંગ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"સબટાઇટલ ચાલુ છે. તે છુપાવવા ક્લિક કરો."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"સબટાઇટલ બંધ છે. તે બતાવવા ક્લિક કરો."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ફરીથી ચલાવો"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ચલાવો"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"થોભાવો"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"પાછલું મીડિયા"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"આગલું મીડિયા"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 સેકન્ડ રિવાઇન્ડ કરો"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 સેકન્ડ આગળ જાઓ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"પૂર્ણ સ્ક્રીન"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-hi/strings.xml b/media2/media2-widget/src/main/res/values-hi/strings.xml
deleted file mode 100644
index 19c4278..0000000
--- a/media2/media2-widget/src/main/res/values-hi/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"बंद है"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ऑडियो ट्रैक"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"कोई ऑडियो ट्रैक नहीं है"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"प्लेबैक की रफ़्तार"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"प्लेबैक की रफ़्तार सामान्य है"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ट्रैक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ट्रैक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ट्रैक <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"वीडियो का टाइटल नहीं पता"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"गाने का टाइटल नहीं पता"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"कलाकार का नाम नहीं पता"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"वह वीडियाे नहीं चलाया जा सका जिसका आपने अनुरोध किया था"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ठीक है"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"वापस जाएं"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"बटन की पिछली सूची पर वापस जाएं"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ज़्यादा बटन देखें"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"कितना चला है"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"सेटिंग"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"सबटाइटल चालू है. इसे छिपाने के लिए क्लिक करें."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"सबटाइटल बंद है. इसे दिखाने के लिए क्लिक करें."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"फिर से चलाएं"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"चलाएं"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"रोकें"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"पिछला मीडिया"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"अगला मीडिया"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 सेकंड पीछे ले जाएं"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 सेकंड आगे ले जाएं"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"फ़ुल-स्क्रीन"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-hr/strings.xml b/media2/media2-widget/src/main/res/values-hr/strings.xml
deleted file mode 100644
index 801e734..0000000
--- a/media2/media2-widget/src/main/res/values-hr/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Isključeno"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Zvučni zapis"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nema"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Brzina reprodukcije"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Uobičajeno"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Zapis <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Zapis <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Zapis <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Naslov videozapisa nije poznat"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nepoznat naziv pjesme"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nepoznati izvođač"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Zatražena stavka ne može se reproducirati"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"U redu"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Natrag"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Natrag na prethodni popis gumba"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Pogledajte više gumba"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Napredak reprodukcije"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Postavke"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titl je uključen. Kliknite da biste sakrili."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titl je isključen. Kliknite za prikaz."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Ponovi"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproduciraj"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pauziraj"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Prethodni medij"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Sljedeći medij"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 sekundi unatrag"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 sekundi unaprijed"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Cijeli zaslon"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-hu/strings.xml b/media2/media2-widget/src/main/res/values-hu/strings.xml
deleted file mode 100644
index 54a32dc..0000000
--- a/media2/media2-widget/src/main/res/values-hu/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Ki"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Hangsáv"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nincs audio"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Lejátszási sebesség"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normál"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. szám"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. szám – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. szám"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Ismeretlen videócím"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Ismeretlen dalcím"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Ismeretlen előadó"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nem sikerült lejátszani a kért médiaelemet"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Vissza"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Vissza az előző gomblistára"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"További gombok megjelenítése"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Lejátszási folyamatjelző sáv"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Beállítások"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"A feliratozás be van kapcsolva. Kattintson a feliratok elrejtéséhez."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"A feliratozás ki van kapcsolva. Kattintson a feliratok megjelenítéséhez."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Újrajátszás"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Lejátszás"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Szünet"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Előző médiaelem"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Következő médiaelem"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Visszatekerés 10 másodperccel"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Előretekerés 30 másodperccel"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Teljes képernyő"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-hy/strings.xml b/media2/media2-widget/src/main/res/values-hy/strings.xml
deleted file mode 100644
index b61f6b5..0000000
--- a/media2/media2-widget/src/main/res/values-hy/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Անջատված է"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Աուդիո"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Չկա"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Նվագարկման արագությունը"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Սովորական"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00։00։00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Կատարում <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Կատարում <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Կատարում <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Տեսանյութի անվանումն անհայտ է"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Երգի անվանումն անհայտ է"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Կատարողն անհայտ է"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Չհաջողվեց նվագարկել տեսանյութը"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Եղավ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Հետ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Անցնել կոճակների նախորդ ցանկին"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Այլ կոճակներ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Նվագարկման ընթացքը"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Կարգավորումներ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Սեղմեք՝ ենթագրերը թաքցնելու համար։"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Սեղմեք՝ ենթագրերը ցուցադրելու համար։"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Նորից նվագարկել"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Նվագարկել"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Դադարեցնել"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Նախորդ մեդիա ֆայլը"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Հաջորդ մեդիա ֆայլը"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 վայրկյանով հետ գնալ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 վայրկյանով առաջ գնալ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Լիաէկրան"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-in/strings.xml b/media2/media2-widget/src/main/res/values-in/strings.xml
deleted file mode 100644
index af1a062..0000000
--- a/media2/media2-widget/src/main/res/values-in/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Nonaktif"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Trek audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Tidak ada"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Kecepatan pemutaran"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Trek <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Judul video tidak dikenal"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Judul lagu tidak dikenal"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artis tidak dikenal"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Tidak dapat memutar item yang diminta"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Oke"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Kembali"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Kembali ke daftar tombol sebelumnya"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Lihat tombol lainnya"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progres pemutaran"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Setelan"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitel aktif. Klik agar tersembunyi."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitel nonaktif. Klik agar terlihat."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Putar ulang"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Putar"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Jeda"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Media sebelumnya"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Media berikutnya"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Mundur 10 detik"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Maju 30 detik"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Layar penuh"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-is/strings.xml b/media2/media2-widget/src/main/res/values-is/strings.xml
deleted file mode 100644
index 6c7cc03..0000000
--- a/media2/media2-widget/src/main/res/values-is/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Slökkt"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Hljóðrás"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Engin"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Spilunarhraði"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Venjulegur"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Lag <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Lag <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Lag <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Óþekktur titill myndskeiðs"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Óþekkt heiti lags"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Óþekktur flytjandi"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Ekki tókst að spila það sem þú baðst um"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Í lagi"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Til baka"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Aftur á fyrri hnappalista"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Sjá fleiri hnappa"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Framvinda spilunar"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Stillingar"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Kveikt er á skjátexta. Smelltu til að fela hann."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Slökkt er á skjátexta. Smelltu til að birta hann."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Spila aftur"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Spila"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Gera hlé"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Fyrra efni"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Næsta efni"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Spóla til baka um 10 sekúndur"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Spóla áfram um 30 sekúndur"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Allur skjárinn"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-it/strings.xml b/media2/media2-widget/src/main/res/values-it/strings.xml
deleted file mode 100644
index 6d28bf5..0000000
--- a/media2/media2-widget/src/main/res/values-it/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Traccia audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nessuna traccia"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocità di riproduzione"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normale"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Traccia <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Traccia <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Traccia <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titolo del video sconosciuto"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titolo del brano sconosciuto"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista sconosciuto"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Impossibile riprodurre l\'elemento richiesto"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Indietro"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Torna all\'elenco dei pulsanti precedente"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Visualizza altri pulsanti"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Avanzamento della riproduzione"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Impostazioni"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Sottotitoli attivi. Fai clic per nasconderli."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Sottotitoli non attivi. Fai clic per mostrarli."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Ripeti"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Riproduci"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Metti in pausa"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Contenuti multimediali precedenti"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Contenuti multimediali successivi"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Torna indietro di 10 secondi"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Vai avanti di 30 secondi"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Schermo intero"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-iw/strings.xml b/media2/media2-widget/src/main/res/values-iw/strings.xml
deleted file mode 100644
index 33bf37f..0000000
--- a/media2/media2-widget/src/main/res/values-iw/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"הכתוביות כבויות"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"טראק של אודיו"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ללא"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"מהירות הפעלה"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"רגילה"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"טראק <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"טראק <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"טראק <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"שם הסרטון לא ידוע"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"שם השיר לא ידוע"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"אומן לא ידוע"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"לא ניתן להפעיל את הפריט שביקשת"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"אישור"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"חזרה"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"חזרה לרשימת הלחצנים הקודמת"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"הצגת לחצנים נוספים"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"התקדמות ההפעלה"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"הגדרות"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"הכתוביות מופעלות. צריך ללחוץ כדי להסתיר אותן."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"הכתוביות מושבתות. צריך ללחוץ כדי להציג אותן."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"הפעלה מחדש"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"הפעלה"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"השהיה"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"המדיה הקודמת"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"המדיה הבאה"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"הרצה אחורה של 10 שניות"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"דילוג קדימה של 30 שניות"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"מסך מלא"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ja/strings.xml b/media2/media2-widget/src/main/res/values-ja/strings.xml
deleted file mode 100644
index aea5f2b..0000000
--- a/media2/media2-widget/src/main/res/values-ja/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"OFF"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"音声トラック"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"なし"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"再生速度"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"標準"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"トラック <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"トラック <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"トラック <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"動画タイトルが不明です"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"曲名が不明です"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"アーティストが不明です"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"リクエストしたアイテムを再生できませんでした"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"戻る"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"前のボタンリストに戻る"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"他のボタンを見る"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"再生の進行状況"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"設定"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"字幕: ON。クリックで非表示。"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"字幕: OFF。クリックで表示。"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"最初から再生"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"再生"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"一時停止"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"前のメディア"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"次のメディア"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 秒ずつ巻き戻す"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 秒早送り"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"全画面表示"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ka/strings.xml b/media2/media2-widget/src/main/res/values-ka/strings.xml
deleted file mode 100644
index 48e2c94..0000000
--- a/media2/media2-widget/src/main/res/values-ka/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"გამორთვა"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"აუდიო ჩანაწერი"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"არცერთი"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"დაკვრის სიჩქარე"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ჩვეულებრივი"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ჩანაწერი <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ჩანაწერი <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> — <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ჩანაწერი <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ვიდეოს სათაური უცნობია"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"სიმღერის სახელი უცნობია"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"მუსიკოსი უცნობია"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"მოთხოვნილი ერთეულის დაკვრა ვერ მოხერხდა"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"კარგი"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"უკან"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ღილაკების წინა სიაზე გადასვლა"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"სხვა ღილაკების ნახვა"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"დაკვრის პროგრესი"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"პარამეტრები"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"სუბტიტრი ჩართულია. დასამალად შეეხეთ."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"სუბტიტრი გამორთულია. საჩვენებლად შეეხეთ."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ხელახლა დაკვრა"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"დაკვრა"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"პაუზა"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"წინა მედიაფაილი"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"შემდეგი მედიაფაილი"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 წამით უკან გადახვევა"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 წამით წინ გადასვლა"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"სრული ეკრანი"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-kk/strings.xml b/media2/media2-widget/src/main/res/values-kk/strings.xml
deleted file mode 100644
index 95a6c81..0000000
--- a/media2/media2-widget/src/main/res/values-kk/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Өшіру"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Аудиотрек"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Жоқ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Ойнату жылдамдығы"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Қалыпты"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>-аудиотрек"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>-аудиотрек (<xliff:g id="LANG">%2$s</xliff:g>)"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>-аудиотрек"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Белгісіз бейне атауы"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Белгісіз ән атауы"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Белгісіз орындаушы"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Сіз сұраған элемент ойнатылмады."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Жарайды"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Артқа"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Алдыңғы түймелер тізіміне оралу"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Басқа түймелерді көру"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Ойнату барысы"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Параметрлер"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Субтитр қосулы. Оны жасыру үшін басыңыз."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Субтитр өшірулі. Оны көрсету үшін басыңыз."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Қайта ойнату"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Ойнату"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Кідірту"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Алдыңғы медиафайл"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Келесі медиафайл"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 секунд артқа айналдыру"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 секунд алға айналдыру"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Толық экран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-km/strings.xml b/media2/media2-widget/src/main/res/values-km/strings.xml
deleted file mode 100644
index 11dac02..0000000
--- a/media2/media2-widget/src/main/res/values-km/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"បិទ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ភ្លេង"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"គ្មាន"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ល្បឿន​ចាក់"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ធម្មតា"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"សំនៀង​លេខ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"សំនៀងលេខ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"សំនៀង​លេខ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"មិនស្គាល់​ចំណងជើងវីដេអូ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"មិនស្គាល់ចំណងជើងចម្រៀង"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"មិនស្គាល់សិល្បករ"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"មិន​អាច​ចាក់​មេឌៀ ដែលអ្នក​បាន​ស្នើទេ"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"យល់ព្រម"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ថយក្រោយ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ត្រឡប់ទៅ​បញ្ជី​ប៊ូតុង​មុនវិញ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"មើលប៊ូតុងច្រើនទៀត"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ការចាក់​កំពុង​ដំណើរការ"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ការ​កំណត់"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"អក្សររត់ត្រូវបានបើក។ សូម​ចុច​ដើម្បីលាក់។"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"អក្សររត់ត្រូវបានបិទ។ សូម​ចុច​ដើម្បីបង្ហាញ។"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ចាក់​ឡើងវិញ"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ចាក់"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ផ្អាក"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"មេឌៀមុន"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"មេឌៀបន្ទាប់"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"ខាថយក្រោយ 10 វិនាទី"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"ទៅមុខ​ 30 វិនាទី"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ពេញអេក្រង់"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-kn/strings.xml b/media2/media2-widget/src/main/res/values-kn/strings.xml
deleted file mode 100644
index 2f5b53b..0000000
--- a/media2/media2-widget/src/main/res/values-kn/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ಆಫ್ ಮಾಡಿ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ಆಡಿಯೋ ಟ್ರ್ಯಾಕ್"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ಯಾವುದೂ ಇಲ್ಲ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ಪ್ಲೇಬ್ಯಾಕ್ ವೇಗ"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ಸಾಮಾನ್ಯ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ಟ್ರ್ಯಾಕ್ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ಟ್ರ್ಯಾಕ್ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ಟ್ರ್ಯಾಕ್ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ವೀಡಿಯೊ ಶೀರ್ಷಿಕೆ ಏನೆಂದು ಗೊತ್ತಿಲ್ಲ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ಹಾಡಿನ ಶೀರ್ಷಿಕೆ ಏನೆಂದು ಗೊತ್ತಿಲ್ಲ"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ಕಲಾವಿದರು ಯಾರೆಂದು ಗೊತ್ತಿಲ್ಲ"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"ನೀವು ವಿನಂತಿಸಿದ ಐಟಂ ಅನ್ನು ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ಸರಿ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ಹಿಂದಕ್ಕೆ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ಹಿಂದಿನ ಬಟನ್ ಪಟ್ಟಿಗೆ ಹಿಂದಿರುಗಿ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ಇನ್ನಷ್ಟು ಬಟನ್‌ಗಳನ್ನು ನೋಡಿ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ಪ್ಲೇಬ್ಯಾಕ್‌ನ ಪ್ರಗತಿ"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"ಸಬ್‌ಟೈಟಲ್ ಆನ್ ಆಗಿದೆ. ಅದನ್ನು ಮರೆಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"ಸಬ್‌ಟೈಟಲ್ ಆಫ್ ಆಗಿದೆ. ಅದನ್ನು ತೋರಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ಮರುಪ್ಲೇ ಮಾಡಿ"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ಪ್ಲೇ ಮಾಡಿ"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ವಿರಾಮಗೊಳಿಸಿ"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ಹಿಂದಿನ ಮಾಧ್ಯಮ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ಮುಂದಿನ ಮಾಧ್ಯಮ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 ಸೆಕೆಂಡ್‌ಗಳಷ್ಟು ಹಿಂದಕ್ಕೆ ಹೋಗಿ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 ಸೆಕೆಂಡ್‌ಗಳಷ್ಟು ಮುಂದಕ್ಕೆ ಹೋಗಿ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ko/strings.xml b/media2/media2-widget/src/main/res/values-ko/strings.xml
deleted file mode 100644
index 7d2562e..0000000
--- a/media2/media2-widget/src/main/res/values-ko/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"꺼짐"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"오디오 트랙"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"없음"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"재생 속도"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"일반"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>번 트랙"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>번 트랙 - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>번 트랙"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"동영상 제목 알 수 없음"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"노래 제목 알 수 없음"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"아티스트 알 수 없음"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"요청한 항목을 재생할 수 없습니다."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"확인"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"뒤로"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"이전 버튼 목록으로 돌아가기"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"버튼 더보기"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"재생 진행률"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"설정"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"자막이 켜져 있습니다. 자막을 숨기려면 클릭하세요."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"자막이 꺼져 있습니다. 자막을 표시하려면 클릭하세요."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"다시 재생"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"재생"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"일시중지"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"이전 미디어"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"다음 미디어"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10초 되감기"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30초 앞으로 이동"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"전체 화면"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ky/strings.xml b/media2/media2-widget/src/main/res/values-ky/strings.xml
deleted file mode 100644
index c53b918..0000000
--- a/media2/media2-widget/src/main/res/values-ky/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Өчүк"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Композиция"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Жок"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Ойнотуу ылдамдыгы"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Орточо"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>-композиция"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>-композиция - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>-композиция"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Видеонун аталышы белгисиз"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Ырдын аталышы белгисиз"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Аткаруучу белгисиз"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Сиз сураган нерсе ойнотулбай койду"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Макул"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Артка"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Мурунку баскыч тизмесине кайтуу"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Дагы баскычтарды көрүү"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Ойнотуу көрсөткүчү"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Параметрлер"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Коштомо жазуу күйүк. Жашыруу үчүн чыкылдатыңыз."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Коштомо жазуу өчүк. Аны көрсөтүү үчүн чыкылдатыңыз."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Кайрадан ойнотуу"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Ойнотуу"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Тындыруу"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Мурунку медиа"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Кийинки медиа"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 секунд артка түрдүрүү"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 секунд алдыга түрдүрүү"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Толук экран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-lo/strings.xml b/media2/media2-widget/src/main/res/values-lo/strings.xml
deleted file mode 100644
index 0d5bd99..0000000
--- a/media2/media2-widget/src/main/res/values-lo/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ປິດ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ແທຣັກສຽງ"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ບໍ່ມີ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ຄວາມໄວການຫຼິ້ນ"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ທຳມະດາ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ແທຣກັ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ແທຣັກ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ແທຣັກ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ບໍ່ຮູ້ຈັກຊື່ວິດີໂອ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ບໍ່ຮູ້ຈັກຊື່ເພງ"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ບໍ່ຮູ້ຈັກສິນລະປິນ"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"ບໍ່ສາມາດຫຼິ້ນລາຍການທີ່ທ່ານຮ້ອງຂໍໄວ້"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ຕົກລົງ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ກັບຄືນ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ກັບໄປທີ່ລາຍຊື່ປຸ່ມກ່ອນໜ້າ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ເບິ່ງປຸ່ມເພີ່ມເຕີມ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ສະຖານະການຫຼິ້ນ"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ການຕັ້ງຄ່າ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"ເປີດຄຳແປແລ້ວ. ຄລິກເພື່ອເຊື່ອງມັນ."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"ຄຳແປປິດຢູ່. ຄລິກເພື່ອສະແດງມັນ."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ຫຼິ້ນຄືນໃໝ່"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ຫຼິ້ນ"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ຢຸດຊົ່ວຄາວ"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ມີເດຍຜ່ານມາ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ມີເດຍຕໍ່ໄປ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"ກັບຫຼັງ 10 ວິນາທີ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"ເລື່ອນໄປໜ້າ 30 ວິນາທີ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ເຕັມຈໍ"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-lt/strings.xml b/media2/media2-widget/src/main/res/values-lt/strings.xml
deleted file mode 100644
index f3b54df..0000000
--- a/media2/media2-widget/src/main/res/values-lt/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Išjungta"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Garso takelis"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nėra"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Atkūrimo sparta"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Įprasta"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g> takelis"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g> takelis – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g> takelis"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Nežinomas vaizdo įrašo pavadinimas"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nežinomas dainos pavadinimas"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nežinomas atlikėjas"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nepavyko paleisti norimo elemento"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Gerai"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atgal"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Atgal į ankstesnių mygtukų sąrašą"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Žr. daugiau mygtukų"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Atkūrimo eiga"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Nustatymai"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitrai įjungti. Spustelėkite, kad jie būtų slepiami."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitrai išjungti. Spustelėkite, kad jie būtų rodomi."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Pakartoti"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Leisti"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pristabdyti"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Ankstesnis medijos elementas"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Kitas medijos elementas"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Atsukti atgal 10 sek."</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Persukti pirmyn 30 sek."</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Viso ekrano režimas"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-lv/strings.xml b/media2/media2-widget/src/main/res/values-lv/strings.xml
deleted file mode 100644
index 051ca1c..0000000
--- a/media2/media2-widget/src/main/res/values-lv/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Izslēgti"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio ieraksts"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nav"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Atskaņošanas ātrums"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normāls"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. ieraksts"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. ieraksts — <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. ieraksts"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Nezināms video nosaukums"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nezināms dziesmas nosaukums"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nezināms izpildītājs"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nevarēja atskaņot pieprasīto vienumu."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Labi"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Atpakaļ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Uz iepriekšējo pogu sarakstu"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Skatīt citas pogas"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Atskaņošanas norise"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Iestatījumi"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitri ir ieslēgti. Noklikšķiniet, lai tos paslēptu."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitri ir izslēgti. Noklikšķiniet, lai tos parādītu."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Atskaņot vēlreiz"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Atskaņot"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Apturēt"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Iepriekšējais multivides vienums"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Nākamais multivides vienums"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Attīt atpakaļ par 10 sekundēm"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Pārtīt 30 sekundes uz priekšu"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pilnekrāna režīms"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-mk/strings.xml b/media2/media2-widget/src/main/res/values-mk/strings.xml
deleted file mode 100644
index 3a74f38..0000000
--- a/media2/media2-widget/src/main/res/values-mk/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Исклучено"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Аудиозапис"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Нема"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Брзина на репродукцијата"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Нормална"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Запис <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Запис <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Запис <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Непознат наслов на видео"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Непознат наслов на песна"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Непознат изведувач"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Не можеше да се пушти ставката што ја побаравте"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Во ред"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Назад кон списокот со претходното копче"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Прикажи повеќе копчиња"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Напредок на репродукцијата"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Поставки"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Титлот е вклучен. Кликнете за да го сокриете."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Титлот е исклучен. Кликнете за да го прикажете."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Пушти повторно"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Пушти"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Паузирај"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Претходен запис"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Следен запис"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Премотајте 10 секунди наназад"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Премотајте 30 секунди нанапред"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Цел екран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ml/strings.xml b/media2/media2-widget/src/main/res/values-ml/strings.xml
deleted file mode 100644
index bb69796..0000000
--- a/media2/media2-widget/src/main/res/values-ml/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ഓഫാണ്"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ഓഡിയോ ട്രാക്ക്"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ഒന്നുമില്ല"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"പ്ലേബാക്ക് വേഗത"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"സാധാരണ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ട്രാക്ക് <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ട്രാക്ക് <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ട്രാക്ക് <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"വീഡിയോയുടെ പേര് അജ്ഞാതം"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"പാട്ടിൻ്റെ പേര് അജ്ഞാതം"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"അജ്ഞാത ആർട്ടിസ്‌റ്റ്"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"നിങ്ങൾ അഭ്യർത്ഥിച്ച ഇനം പ്ലേ ചെയ്യാനായില്ല"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ശരി"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"മടങ്ങുക"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"മുമ്പത്തെ ബട്ടൺ ലിസ്‌റ്റിലേക്ക് മടങ്ങുക"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"കൂടുതൽ ബട്ടണുകൾ കാണുക"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"പ്ലേബാക്ക് പുരോഗതി"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ക്രമീകരണം"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"സബ്ടൈറ്റിൽ ഓണാണ്. അത് മറയ്‌ക്കാൻ ക്ലിക്ക് ചെയ്യുക."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"സബ്ടൈറ്റിൽ ഓഫാണ്. അത് കാണിക്കാൻ ക്ലിക്ക് ചെയ്യുക."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"വീണ്ടും പ്ലേ ചെയ്യുക"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"പ്ലേ ചെയ്യുക"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"താൽക്കാലികമായി നിർത്തുക"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"മുമ്പത്തെ മീഡിയ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"അടുത്ത മീഡിയ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 സെക്കൻഡ് പിന്നോട്ട് പോകുക"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 സെക്കൻഡ് മുന്നോട്ട് പോകുക"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"പൂർണ്ണ സ്ക്രീൻ"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-mn/strings.xml b/media2/media2-widget/src/main/res/values-mn/strings.xml
deleted file mode 100644
index 65a8207..0000000
--- a/media2/media2-widget/src/main/res/values-mn/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Унтраах"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Аудио зам"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Байхгүй"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Дахин тоглуулах хурд"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Энгийн"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Бичлэг <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Бичлэг <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Бичлэг <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Видеоны нэр тодорхойгүй"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Дууны нэр тодорхойгүй"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Уран бүтээлч тодорхойгүй"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Таны хүсэлт тавьсан зүйлийг тоглуулж чадсангүй"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Буцах"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Өмнөх товчлуурын жагсаалт руу буцах"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Бусад товчлуурыг харах"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Дахин тоглуулах явц"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Тохиргоо"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Хадмал асаалттай байна. Үүнийг нуухын тулд товшино уу."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Хадмал унтраалттай байна. Үүнийг харуулахын тулд товшино уу."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Дахин тоглуулах"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Тоглуулах"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Түр зогсоох"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Өмнөх медиа"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Дараагийн медиа"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 секундээр ухраах"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 секундээр урагшлуулах"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Бүтэн дэлгэц"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-mr/strings.xml b/media2/media2-widget/src/main/res/values-mr/strings.xml
deleted file mode 100644
index 845623764..0000000
--- a/media2/media2-widget/src/main/res/values-mr/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"बंद करा"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ऑडिओ ट्रॅक"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"काहीही नाही"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"प्लेबॅकचा वेग"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"सामान्य"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"गाणे क्रमांक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"गाणे क्रमांक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"गाणे क्रमांक <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"व्हिडिओचे शीर्षक अज्ञात आहे"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"गाण्याचे शीर्षक अज्ञात आहे"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"कलाकार अज्ञात आहे"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"तुम्ही विनंती केलेला आयटम प्ले करता आला नाही"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ओके"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"मागे जा"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"बटणांच्या मागील सूचीवर परत जा"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"आणखी बटणे पहा"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"प्लेबॅकची प्रगती"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"सेटिंग्ज"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"सबटायटल सुरू आहे. लपवण्यासाठी क्लिक करा."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"सबटायटल बंद आहे. दाखवण्यासाठी क्लिक करा."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"रीप्ले"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"प्ले करा"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"थांबवा"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"मागील मीडिया"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"पुढील मीडिया"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"१० सेकंद मागे जा"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"३० सेकंद पुढे जा"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"फुल स्क्रीन"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ms/strings.xml b/media2/media2-widget/src/main/res/values-ms/strings.xml
deleted file mode 100644
index 488e6fe..0000000
--- a/media2/media2-widget/src/main/res/values-ms/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Mati"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Runut audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Tiada"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Kelajuan main balik"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Biasa"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Runut <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Runut <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Runut <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Tajuk video tidak diketahui"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Tajuk lagu tidak diketahui"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artis tidak diketahui"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Tidak dapat memainkan item yang anda minta"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Kembali"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Kembali ke senarai butang terdahulu"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Lihat lagi butang"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Kemajuan main balik"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Tetapan"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Sari kata dihidupkan. Klik untuk menyembunyikan sari kata."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Sari kata dimatikan. Klik untuk menunjukkan sari kata."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Main semula"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Main"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Jeda"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Media sebelumnya"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Media seterusnya"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Mandir 10 saat"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Maju ke hadapan sebanyak 30 saat"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Skrin penuh"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-my/strings.xml b/media2/media2-widget/src/main/res/values-my/strings.xml
deleted file mode 100644
index 7f66e02..0000000
--- a/media2/media2-widget/src/main/res/values-my/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ပိတ်မည်"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"သီချင်း"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"မရှိ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ဖွင့်ရန် အမြန်နှုန်း"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ပုံမှန်"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"အပုဒ် <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"အပုဒ် <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"အပုဒ် <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"အမည်မသိ ဗီဒီယိုခေါင်းစဉ်"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"အမည်မသိ သီချင်းခေါင်းစဉ်"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"အမည်မသိ အနုပညာရှင်"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"သင်တောင်းဆိုထားသည့်အရာကို ဖွင့်၍မရပါ"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"နောက်သို့"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ယခင်ခလုတ်စာရင်းသို့ ပြန်သွားရန်"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"နောက်ထပ်ခလုတ်များကို ကြည့်ရန်"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ဖွင့်သည့် အခြေအနေ"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ဆက်တင်များ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"စာတန်းထိုး ဖွင့်ထားသည်။ ၎င်းကိုဝှက်ရန် နှိပ်ပါ။"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"စာတန်းထိုး ပိတ်ထားသည်။ ၎င်းကိုပြရန် နှိပ်ပါ။"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ပြန်ဖွင့်ရန်"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ဖွင့်ရန်"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ခဏရပ်ရန်"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ယခင်မီဒီယာ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"နောက်မီဒီယာ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"နောက်သို့ ၁၀ စက္ကန့် ပြန်ရစ်ရန်"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"ရှေ့သို့ စက္ကန့် ၃၀ ရစ်ရန်"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ဖန်သားပြင်အပြည့်"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-nb/strings.xml b/media2/media2-widget/src/main/res/values-nb/strings.xml
deleted file mode 100644
index 8e460b8..0000000
--- a/media2/media2-widget/src/main/res/values-nb/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Av"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Lydspor"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Ingen"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Avspillingshastighet"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Spor <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Spor <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Spor <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Ukjent videotittel"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Ukjent sangtittel"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Ukjent artist"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Kunne ikke spille av elementet du har bedt om"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Tilbake"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Tilbake til forrige knappeliste"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Se flere knapper"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Avspillingsfremdrift"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Innstillinger"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Undertekster er på. Klikk for å skjule."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Undertekster er av. Klikk for å vise."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Spill av på nytt"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Spill av"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Sett på pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Forrige media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Neste media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Spol tilbake 10 sekunder"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Gå 30 sekunder fremover"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Fullskjerm"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ne/strings.xml b/media2/media2-widget/src/main/res/values-ne/strings.xml
deleted file mode 100644
index 9d03eb4..0000000
--- a/media2/media2-widget/src/main/res/values-ne/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"अफ छ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"अडियो ट्र्याक"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"एउटा पनि अडियो ट्रयाक छैन"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"प्लेब्याकको गति"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"सामान्य"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ट्र्याक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ट्र्याक <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ट्र्याक <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"भिडियोको शीर्षक थाहा छैन"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"गीतको शीर्षक थाहा छैन"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"कलाकारको नाम थाहा छैन"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"तपाईंले अनुरोध गर्नुभएको भिडियो प्ले गर्न सकिएन"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ठिक छ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"पछाडि जानुहोस्"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"बटनको अघिल्लो सूचीमा फर्कनुहोस्"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"थप बटनहरू हेर्नुहोस्"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"हालसम्म भिडियो प्ले भएको अवधि"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"सेटिङ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"सबटाइटल देखाउने सुविधा अन छ। सबटाइटल नदेखिने पार्न क्लिक गर्नुहोस्।"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"सबटाइटल देखाउने सुविधा अफ छ। सबटाइटल देखाउन क्लिक गर्नुहोस्।"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"रिप्ले गर्नुहोस्"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"प्ले गर्नुहोस्"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"पज गर्नुहोस्"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"अघिल्लो मिडिया"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"अर्को मिडिया"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"१० सेकेन्ड पछाडि जानुहोस्"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"३० सेकेन्ड अगाडि जानुहोस्"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"फुल स्क्रिन"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-nl/strings.xml b/media2/media2-widget/src/main/res/values-nl/strings.xml
deleted file mode 100644
index 19b7ae8..0000000
--- a/media2/media2-widget/src/main/res/values-nl/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Uit"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audiotrack"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Geen"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Afspeelsnelheid"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normaal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titel van video onbekend"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titel van nummer onbekend"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artiest onbekend"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Kan het gevraagde item niet afspelen"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Terug"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Terug naar vorige knoppenlijst"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Meer knoppen bekijken"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Afspeelvoortgang"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Instellingen"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Ondertiteling aan. Klik om te verbergen."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Ondertiteling uit. Klik om te tonen."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Opnieuw afspelen"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Afspelen"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pauzeren"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Vorige media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Volgende media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 seconden terugspoelen"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 seconden vooruitgaan"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Volledig scherm"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-or/strings.xml b/media2/media2-widget/src/main/res/values-or/strings.xml
deleted file mode 100644
index d527d032..0000000
--- a/media2/media2-widget/src/main/res/values-or/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ବନ୍ଦ ଅଛି"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ଅଡିଓ ଟ୍ରାକ୍"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"କିଛି ନାହିଁ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ପ୍ଲେବ୍ୟାକର ସ୍ପିଡ୍"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ସାଧାରଣ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ଟ୍ରାକ୍ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ଟ୍ରାକ୍ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ଟ୍ରାକ୍ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ଭିଡିଓର ଟାଇଟେଲ୍ ଅଜଣା ଅଟେ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ଗୀତର ଟାଇଟେଲ୍ ଅଜଣା ଅଟେ"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"କଳାକାର ଅଜଣା ଅଟନ୍ତି"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"ଆପଣ ଅନୁରୋଧ କରିଥିବା ଆଇଟମ୍ ଚଲାଯାଇପାରିଲା ନାହିଁ"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ଠିକ ଅଛି"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ପଛକୁ ଫେରନ୍ତୁ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ପୂର୍ବବର୍ତ୍ତୀ ବଟନ୍ ତାଲିକାକୁ ଫେରନ୍ତୁ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ଅଧିକ ବଟନ୍ ଦେଖନ୍ତୁ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ପ୍ଲେବ୍ୟାକର ପ୍ରଗତି"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ସେଟିଂସ୍"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"ସବଟାଇଟେଲ୍ ଚାଲୁ ଅଛି। ଏହାକୁ ଲୁଚାଇବା ପାଇଁ କ୍ଲିକ୍ କରନ୍ତୁ।"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"ସବଟାଇଟେଲ୍ ବନ୍ଦ ଅଛି। ଏହାକୁ ଦେଖାଇବା ପାଇଁ କ୍ଲିକ୍ କରନ୍ତୁ।"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ପୁଣି ଚଲାନ୍ତୁ"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ଚଲାନ୍ତୁ"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ବିରତ କରନ୍ତୁ"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ପୂର୍ବବର୍ତ୍ତୀ ମିଡିଆ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ପରବର୍ତ୍ତୀ ମିଡିଆ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 ସେକେଣ୍ଡ ରିୱାଇଣ୍ଡ କରନ୍ତୁ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 ସେକେଣ୍ଡ ଆଗକୁ ଯାଆନ୍ତୁ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-pa/strings.xml b/media2/media2-widget/src/main/res/values-pa/strings.xml
deleted file mode 100644
index 0b1a6df..0000000
--- a/media2/media2-widget/src/main/res/values-pa/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ਬੰਦ ਹੈ"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ਆਡੀਓ ਟਰੈਕ"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ਕੋਈ ਨਹੀਂ"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ਪਲੇਬੈਕ ਦੀ ਗਤੀ"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ਸਧਾਰਨ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ਟਰੈਕ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ਟਰੈਕ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ਟਰੈਕ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ਅਗਿਆਤ ਵੀਡੀਓ ਸਿਰਲੇਖ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ਅਗਿਆਤ ਗੀਤ ਸਿਰਲੇਖ"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ਕਲਾਕਾਰ ਅਗਿਆਤ ਹੈ"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਬੇਨਤੀ ਕੀਤੀ ਗਈ ਆਈਟਮ ਨਹੀਂ ਚਲਾਈ ਜਾ ਸਕੀ"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ਠੀਕ ਹੈ"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ਪਿੱਛੇ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"ਪਿਛਲੀ ਬਟਨ ਸੂਚੀ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ਹੋਰ ਬਟਨ ਦੇਖੋ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ਪਲੇਬੈਕ ਪ੍ਰਗਤੀ"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ਸੈਟਿੰਗਾਂ"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"ਉਪਸਿਰਲੇਖ ਚਾਲੂ ਹੈ। ਲੁਕਾਉਣ ਲਈ ਕਲਿੱਕ ਕਰੋ।"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"ਉਪਸਿਰਲੇਖ ਬੰਦ ਹੈ। ਇਹ ਦਿਖਾਉਣ ਲਈ ਕਲਿੱਕ ਕਰੋ।"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"ਮੁੜ ਚਲਾਓ"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ਚਲਾਓ"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"ਰੋਕੋ"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"ਪਿਛਲਾ ਮੀਡੀਆ"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ਅਗਲਾ ਮੀਡੀਆ"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 ਸਕਿੰਟ ਪਿੱਛੇ ਕਰੋ"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 ਸਕਿੰਟ ਅੱਗੇ ਜਾਓ"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-pl/strings.xml b/media2/media2-widget/src/main/res/values-pl/strings.xml
deleted file mode 100644
index 8815f25..0000000
--- a/media2/media2-widget/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Wyłączono"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Ścieżka audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Brak"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Szybkość odtwarzania"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normalna"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Utwór <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Utwór <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Utwór <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Nieznany tytuł filmu"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nieznany tytuł utworu"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nieznany wykonawca"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Nie można odtworzyć wybranego elementu"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Wstecz"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Wróć do poprzedniej listy przycisków"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Pokaż więcej przycisków"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Postęp odtwarzania"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ustawienia"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Napisy są włączone. Kliknij, by je ukryć."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Napisy są wyłączone. Kliknij, by je wyświetlać."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Odtwórz ponownie"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Odtwórz"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Wstrzymaj"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Poprzedni plik multimedialny"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Następny plik multimedialny"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Przewiń do tyłu o 10 sekund"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Przejdź o 30 sekund do przodu"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Pełny ekran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-pt-rBR/strings.xml b/media2/media2-widget/src/main/res/values-pt-rBR/strings.xml
deleted file mode 100644
index cb1140a..0000000
--- a/media2/media2-widget/src/main/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desativada"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Faixa de áudio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nenhuma"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidade da reprodução"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>: <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Faixa <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título do vídeo desconhecido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título da música desconhecido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconhecido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Não foi possível abrir o item solicitado"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Voltar"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Voltar à lista de botões anterior"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ver mais botões"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Andamento da reprodução"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Configurações"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"As legendas estão ativadas. Clique para ocultá-las."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"As legendas estão desativadas. Clique para exibi-las."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Repetir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Iniciar"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausar"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Mídia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Próxima mídia"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Voltar 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avançar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Tela cheia"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-pt-rPT/strings.xml b/media2/media2-widget/src/main/res/values-pt-rPT/strings.xml
deleted file mode 100644
index 0461228..0000000
--- a/media2/media2-widget/src/main/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desativadas"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Faixa de áudio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nenhuma"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidade de reprodução"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Faixa <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título do vídeo desconhecido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título da música desconhecido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconhecido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Não foi possível reproduzir o item solicitado."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Anterior"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Voltar à lista de botões anterior"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ver mais botões"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progresso da reprodução"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Definições"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"As legendas estão ativadas. Clique para as ocultar."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"As legendas estão desativadas. Clique para as mostrar."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Repetir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Reproduzir"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausar"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Conteúdo multimédia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Conteúdo multimédia seguinte"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Recuar 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avançar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Ecrã inteiro"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-pt/strings.xml b/media2/media2-widget/src/main/res/values-pt/strings.xml
deleted file mode 100644
index cb1140a..0000000
--- a/media2/media2-widget/src/main/res/values-pt/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Desativada"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Faixa de áudio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nenhuma"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Velocidade da reprodução"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Faixa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>: <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Faixa <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Título do vídeo desconhecido"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Título da música desconhecido"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artista desconhecido"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Não foi possível abrir o item solicitado"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Voltar"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Voltar à lista de botões anterior"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Ver mais botões"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Andamento da reprodução"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Configurações"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"As legendas estão ativadas. Clique para ocultá-las."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"As legendas estão desativadas. Clique para exibi-las."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Repetir"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Iniciar"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausar"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Mídia anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Próxima mídia"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Voltar 10 segundos"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Avançar 30 segundos"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Tela cheia"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ro/strings.xml b/media2/media2-widget/src/main/res/values-ro/strings.xml
deleted file mode 100644
index 8cd5906..0000000
--- a/media2/media2-widget/src/main/res/values-ro/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Dezactivată"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Înregistrare audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Nu există"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Viteza de redare"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normală"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Melodia <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Melodia <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Melodia <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titlul videoclipului este necunoscut"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titlul melodiei este necunoscut"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist necunoscut"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Elementul solicitat nu a putut fi redat"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Înapoi"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Lista anterioară de butoane"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Vezi mai multe butoane"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progresul redării"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Setări"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Subtitrarea este activată. Ascunde-o cu un clic."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Subtitrarea este dezactivată. Afișează-o cu un clic."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Redă din nou"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Redă"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Întrerupe"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Conținutul media anterior"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Conținutul media următor"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Derulează înapoi zece secunde"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Derulează înainte 30 de secunde"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Ecran complet"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ru/strings.xml b/media2/media2-widget/src/main/res/values-ru/strings.xml
deleted file mode 100644
index 40b5e54..0000000
--- a/media2/media2-widget/src/main/res/values-ru/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Отключено"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Звуковая дорожка"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Нет"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Скорость воспроизведения"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Обычная"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Дорожка с субтитрами <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Дорожка с субтитрами <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. Язык: <xliff:g id="LANG">%2$s</xliff:g>."</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Звуковая дорожка <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Без названия"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Без названия"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Неизвестный исполнитель"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Не удалось воспроизвести выбранное видео."</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ОК"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Вернуться к предыдущему списку кнопок"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Открыть список других кнопок"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Панель перемотки воспроизведения"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Настройки"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Субтитры включены. Нажмите, чтобы скрыть их."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Субтитры отключены. Нажмите, чтобы включить их."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Повторить"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Воспроизвести"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Приостановить"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Перейти к предыдущему медиафайлу"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Перейти к следующему медиафайлу"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Перемотать на 10 секунд назад"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Перемотать на 30 секунд вперед"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Полноэкранный режим"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-si/strings.xml b/media2/media2-widget/src/main/res/values-si/strings.xml
deleted file mode 100644
index 01a49b3..0000000
--- a/media2/media2-widget/src/main/res/values-si/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ක්‍රියාවිරහිතයි"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ශ්‍රව්‍ය ඛණ්ඩය"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"කිසිවක් නැත"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"පසුධාවන වේගය"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"සාමාන්‍ය"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g> ඛණ්ඩය"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g> ඛණ්ඩය - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g> ඛණ්ඩය"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"වීඩියෝ මාතෘකාව නොදනී"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ගීතයේ මාතෘකාව නොදනී"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"කලාකරු නොදනී"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"ඔබ ඉල්ලූ අයිතමය වාදනය කළ නොහැකි විය"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"හරි"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"ආපසු"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"පෙර බොත්තම් ලැයිස්තුවට ආපසු"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"තව බොත්තම් බලන්න"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"පසුධාවන ප්‍රගතිය"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"සැකසීම්"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"උපසිරැසි ක්‍රියාත්මකයි. එය සැඟවීමට ක්ලික් කරන්න."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"උපසිරැසි ක්‍රියාවිරහිතයි. එය පෙන්වීමට ක්ලික් කරන්න."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"යළි වාදනය කරන්න"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"වාදනය කරන්න"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"විරාම ගන්වන්න"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"පෙර මාධ්‍යය"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"ඊලඟ මාධ්‍යය"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"තත්පර 10කින් ආපස්සට"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"තත්පර 30කින් ඉදිරියට යන්න"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"පූර්ණ තිරය"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sk/strings.xml b/media2/media2-widget/src/main/res/values-sk/strings.xml
deleted file mode 100644
index 6a66759..0000000
--- a/media2/media2-widget/src/main/res/values-sk/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Vypnuté"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Zvuková stopa"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Žiadne"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Rýchlosť prehrávania"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normálne"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. stopa"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. stopa – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. stopa"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Neznámy názov videa"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Neznámy názov skladby"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Neznámy interpret"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Požadovanú položku sa nepodarilo prehrať"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Späť"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Späť na predchádzajúci zoznam tlačidiel"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Zobraziť ďalšie tlačidlá"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Priebeh prehrávania"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Nastavenia"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titulky sú zapnuté. Skryjete ich kliknutím."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titulky sú vypnuté. Zobrazíte ich kliknutím."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Prehrať znova"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Prehrať"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pozastaviť"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Predchádzajúce médiá"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Ďalšie médiá"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Pretočiť o 10 sekúnd dozadu"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Prejsť o 30 sekúnd vpred"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Celá obrazovka"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sl/strings.xml b/media2/media2-widget/src/main/res/values-sl/strings.xml
deleted file mode 100644
index a0cd1ad..0000000
--- a/media2/media2-widget/src/main/res/values-sl/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Izklopljeno"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Zvočni posnetek"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Brez"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Hitrost predvajanja"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Običajna"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Skladba <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Skladba <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Skladba <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Neznan naslov videoposnetka"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Neznan naslov skladbe"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Neznan izvajalec"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Želene vsebine ni bilo mogoče predvajati"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"V redu"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Nazaj"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Nazaj na prejšnji seznam gumbov"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Prikaz več gumbov"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Potek predvajanja"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Nastavitve"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Podnapisi so vklopljeni. Kliknite za izklop."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Podnapisi so izklopljeni. Kliknite za vklop."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Znova predvajaj"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Predvajaj"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Začasno zaustavi"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Prejšnja predstavnostna vsebina"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Naslednja predstavnostna vsebina"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Pomik nazaj za 10 sekund"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Pomik naprej za 30 sekund"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Celozaslonski način"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sq/strings.xml b/media2/media2-widget/src/main/res/values-sq/strings.xml
deleted file mode 100644
index bbac7b6..0000000
--- a/media2/media2-widget/src/main/res/values-sq/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Joaktiv"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Pjesë audio"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Asnjë"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Shpejtësia e luajtjes"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Kënga <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Pjesa muzikore <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Kënga <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Titulli i videos i panjohur"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Titulli i këngës i panjohur"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artisti i panjohur"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Artikulli i kërkuar nuk mund të luhej"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Në rregull"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Pas"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Kthehu te lista e butonave të mëparshëm"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Shiko më shumë butona"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progresi i luajtjes"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Cilësimet"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Titrat janë aktive. Kliko për t\'i fshehur."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Titrat janë joaktive. Kliko për t\'i shfaqur."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Riluaj"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Luaj"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Vendos në pauzë"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Media e mëparshme"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Media tjetër"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rikthe me 10 sekonda"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Shko përpara me 30 sekonda"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Ekrani i plotë"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sr/strings.xml b/media2/media2-widget/src/main/res/values-sr/strings.xml
deleted file mode 100644
index 2d80fe0..0000000
--- a/media2/media2-widget/src/main/res/values-sr/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Искључено"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Аудио снимак"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Нема"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Брзина репродукције"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Нормално"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. песма"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. песма – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. песма"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Непознат назив видеа"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Непознат назив песме"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Непознат извођач"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Нисмо успели да пустимо ставку коју сте захтевали"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Важи"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Назад на претходну листу дугмади"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Прикажи још дугмади"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Напредовање репродукције"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Подешавања"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Титл је укључен. Кликните да бисте га сакрили."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Титл је искључен. Кликните да би се приказивао."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Пусти опет"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Пусти"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Паузирај"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Претходни медијски фајл"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Следећи медијски фајл"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Премотај уназад 10 секунди"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Премотај 30 секунди унапред"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Цео екран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sv/strings.xml b/media2/media2-widget/src/main/res/values-sv/strings.xml
deleted file mode 100644
index 4cc9933..0000000
--- a/media2/media2-widget/src/main/res/values-sv/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Av"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Ljudspår"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Inga"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Uppspelningshastighet"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Spår <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Spår <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Spår <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Okänd videotitel"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Okänd låttitel"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Okänd artist"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Det gick inte att spela upp det valda objektet"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Tillbaka"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Öppna föregående knapplista"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Visa fler knappar"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Uppspelningsförlopp"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Inställningar"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Undertexter är på. Klicka för att dölja."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Undertexter är av. Klicka för att visa."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Spela upp igen"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Spela upp"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pausa"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Föregående media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Nästa media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Spola bakåt tio sekunder"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Spola framåt 30 sekunder"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Helskärm"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-sw/strings.xml b/media2/media2-widget/src/main/res/values-sw/strings.xml
deleted file mode 100644
index f7515bf..0000000
--- a/media2/media2-widget/src/main/res/values-sw/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Imezimwa"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Wimbo wa sauti"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Hamna"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Kasi ya kucheza"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Kawaida"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Wimbo wa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Wimbo wa <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Wimbo wa <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Jina la video halijulikani"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Jina la wimbo halijulikani"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Msanii hajulikani"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Imeshindwa kucheza video uliyoomba"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Sawa"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Nyuma"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Rudi kwenye orodha ya vitufe vya awali"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Angalia vitufe vingine"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Kiasi cha uchezaji"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Mipangilio"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Umewasha manukuu. Bofya ili uyafiche."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Umezima manukuu. Bofya ili uyaonyeshe."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Cheza tena"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Cheza"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Simamisha"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Maudhui yaliyotangulia"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Maudhui yanayofuata"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rudi nyuma kwa sekunde 10"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Sogeza mbele kwa sekunde 30"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Skrini nzima"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ta/strings.xml b/media2/media2-widget/src/main/res/values-ta/strings.xml
deleted file mode 100644
index 1229524..0000000
--- a/media2/media2-widget/src/main/res/values-ta/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ஆஃப்"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ஆடியோ டிராக்"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"எதுவுமில்லை"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"வீடியோ இயக்க வேகம்"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"இயல்பு"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"டிராக் <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"டிராக் <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"டிராக் <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"வீடியோ தலைப்பு தெரியவில்லை"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"பாடல் தலைப்பு தெரியவில்லை"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"கலைஞர் பெயர் தெரியவில்லை"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"நீங்கள் கோரிய வீடியோவைப் பிளே செய்ய இயலவில்லை"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"சரி"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"பின்செல்லும்"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"முந்தைய பட்டன்களின் பட்டியலுக்குச் செல்லும்"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"மேலும் பட்டன்களைக் காட்டும்"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"வீடியோவின் இயக்க நிலை"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"அமைப்புகள்"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"வசனம் ஆனில் உள்ளது. மறைப்பதற்குக் கிளிக் செய்யவும்."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"வசனம் ஆஃபில் உள்ளது. காட்டுவதற்குக் கிளிக் செய்யவும்."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"மீண்டும் பிளே செய்யும்"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"பிளே செய்யும்"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"இடைநிறுத்தும்"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"முந்தைய மீடியாவைப் பிளே செய்யும்"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"அடுத்த மீடியாவைப் பிளே செய்யும்"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 வினாடிகள் பின்செல்லும்"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 வினாடிகள் முன்செல்லும்"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"முழுத்திரையில் காட்டும்"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-te/strings.xml b/media2/media2-widget/src/main/res/values-te/strings.xml
deleted file mode 100644
index 3048d65..0000000
--- a/media2/media2-widget/src/main/res/values-te/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ఆఫ్"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"ఆడియో ట్రాక్"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ఏదీ లేదు"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ప్లేబ్యాక్ వేగం"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"సాధారణం"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ట్రాక్ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ట్రాక్ <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ట్రాక్ <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"వీడియో పేరు తెలియదు"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"పాట పేరు తెలియదు"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ఆర్టిస్ట్ ఎవరో తెలియదు"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"మీరు రిక్వెస్ట్ చేసిన ఐటెమ్‌ను ప్లే చేయడం సాధ్యపడలేదు"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"సరే"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"వెనుకకు"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"మునుపటి బటన్ లిస్ట్‌కు వెళ్లండి"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"మరిన్ని బటన్‌లను చూడండి"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ప్లేబ్యాక్ ప్రోగ్రెస్"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"సెట్టింగ్‌లు"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"సబ్‌టైటిల్ ఆన్‌లో ఉంది. దాన్ని దాచడానికి క్లిక్ చేయండి."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"సబ్‌టైటిల్ ఆఫ్‌లో ఉంది. దాన్ని చూపడానికి క్లిక్ చేయండి."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"రీప్లే చేయి"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"ప్లే చేయి"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"పాజ్ చేయి"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"మునుపటి మీడియా"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"తర్వాతి మీడియా"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 సెకన్లు రివైండ్ చేయి"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 సెకన్లు ఫార్వర్డ్ చేయి"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"ఫుల్ స్క్రీన్"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-th/strings.xml b/media2/media2-widget/src/main/res/values-th/strings.xml
deleted file mode 100644
index 7674239..0000000
--- a/media2/media2-widget/src/main/res/values-th/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"ปิด"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"แทร็กเสียง"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"ไม่มี"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"ความเร็วในการเล่น"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"ปกติ"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"แทร็ก <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"แทร็ก <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"แทร็ก <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ไม่ทราบชื่อวิดีโอ"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"ไม่ทราบชื่อเพลง"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"ไม่ทราบศิลปิน"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"เล่นวิดีโอที่คุณขอไม่ได้"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ตกลง"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"กลับ"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"กลับไปที่รายการปุ่มก่อนหน้า"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"ดูปุ่มอื่นๆ"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"ความคืบหน้าในการเล่น"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"การตั้งค่า"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"คำบรรยายเปิดอยู่ คลิกเพื่อซ่อน"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"คำบรรยายปิดอยู่ คลิกเพื่อแสดง"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"เล่นซ้ำ"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"เล่น"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"หยุดชั่วคราว"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"สื่อก่อนหน้า"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"สื่อถัดไป"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"กรอกลับ 10 วินาที"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"ข้ามไปข้างหน้า 30 วินาที"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"เต็มหน้าจอ"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-tl/strings.xml b/media2/media2-widget/src/main/res/values-tl/strings.xml
deleted file mode 100644
index d34bce7..0000000
--- a/media2/media2-widget/src/main/res/values-tl/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Naka-off"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio track"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Wala"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Bilis ng pag-playback"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Hindi alam ang pamagat ng video"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Hindi alam ang pamagat ng kanta"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Hindi alam ang artist"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Hindi ma-play ang item na hiniling mo"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Bumalik"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Bumalik sa nakaraang listahan ng button"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Tumingin pa ng mga button"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Progreso ng pag-playback"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Mga Setting"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Naka-on ang subtitle. I-click para itago ito."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Naka-off ang subtitle. I-click para ipakita ito."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"I-replay"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"I-play"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"I-pause"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Nakaraang media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Susunod na media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"I-rewind nang 10 segundo"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"I-forward nang 30 segundo"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-tr/strings.xml b/media2/media2-widget/src/main/res/values-tr/strings.xml
deleted file mode 100644
index a907596..0000000
--- a/media2/media2-widget/src/main/res/values-tr/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Kapalı"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Ses parçası"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Yok"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Çalma hızı"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. parça"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"<xliff:g id="TRACK_NUMBER">%1$d</xliff:g>. parça - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"<xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>. parça"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video başlığı bilinmiyor"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Şarkı adı bilinmiyor"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Sanatçı bilinmiyor"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"İstekte bulunduğunuz öğe oynatılamadı"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"Tamam"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Geri"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Önceki düğme listesine dön"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Diğer düğmeleri göster"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Çalma ilerleme durumu"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Ayarlar"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Altyazı açık. Gizlemek için tıklayın."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Altyazı kapalı. Göstermek için tıklayın."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Tekrar oynat"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Oynat"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Duraklat"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Önceki medya"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Sonraki medya"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 saniye geri sar"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 saniye ileri git"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Tam ekran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-uk/strings.xml b/media2/media2-widget/src/main/res/values-uk/strings.xml
deleted file mode 100644
index 5dbcd76..0000000
--- a/media2/media2-widget/src/main/res/values-uk/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Вимкнено"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Звукова доріжка"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Немає"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Швидкість відтворення"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Звичайна"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Доріжка <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Доріжка <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Доріжка <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Невідома назва відео"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Невідома назва пісні"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Невідомий виконавець"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Не вдалося відтворити відео"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Назад"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Повернутися до списку попередніх кнопок"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Показати інші кнопки"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Перебіг відтворення"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Налаштування"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Субтитри ввімкнено. Натисніть, щоб сховати."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Субтитри вимкнено. Натисніть, щоб показати."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Повторити"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Відтворити"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Призупинити"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Попередній медіафайл"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Наступний медіафайл"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Перемотати назад на 10 секунд"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Перемотати на 30 секунд уперед"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"На весь екран"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-ur/strings.xml b/media2/media2-widget/src/main/res/values-ur/strings.xml
deleted file mode 100644
index b440fd8..0000000
--- a/media2/media2-widget/src/main/res/values-ur/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"آف"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"آڈیو ٹریک"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"کوئی نہیں"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"پلے بیک کی رفتار"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"عام"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"ٹریک <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"ٹریک <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"ٹریک <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"ویڈیو کا عنوان نامعلوم ہے"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"گانے کا عنوان نامعلوم ہے"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"فنکار نامعلوم ہے"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"آپ کا مطلوبہ آئٹم نہیں چلایا جا سکا"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"ٹھیک ہے"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"پیچھے"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"بٹنز کی پچھلی فہرست پر واپس جائیں"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"مزید بٹنز دیکھیں"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"پلے بیک کی پیش رفت"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"ترتیبات"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"سب ٹائٹل آن ہے۔ اسے چھپانے کے لیے کلک کریں۔"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"سب ٹائٹل آف ہے۔ اسے دکھانے کے لیے کلک کریں۔"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"دوبارہ چلائیں"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"چلائیں"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"موقوف کریں"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"پچھلا میڈیا"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"اگلا میڈیا"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 سیکنڈ تک پیچھے جائیں"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 سیکنڈ تک آگے جائیں"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"پوری اسکرین"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-uz/strings.xml b/media2/media2-widget/src/main/res/values-uz/strings.xml
deleted file mode 100644
index 0047ff5..0000000
--- a/media2/media2-widget/src/main/res/values-uz/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Oʻchiq"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Audio trek"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Hech qanday"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Ijro tezligi"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Trek <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Trek <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video nomi notanish"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Nomsiz"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Notanish ijrochi"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Tanlangan video ijro qilinmadi"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Orqaga"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Avvalgi roʻyxatga qaytish"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Boshqa tugmalar"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Ijro rivoji"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Sozlamalar"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Taglavha yoniq. Berkitish uchun bosing."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Taglavha yopiq. Uni ochish uchun bosing."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Qaytadan"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Ijro"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Pauza"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Avvalgi media"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Keyingi media"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"10 soniya orqaga"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"30 soniya oldinga"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Butun ekran"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-vi/strings.xml b/media2/media2-widget/src/main/res/values-vi/strings.xml
deleted file mode 100644
index 52349d1..0000000
--- a/media2/media2-widget/src/main/res/values-vi/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Tắt"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Bản âm thanh"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Không có"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Tốc độ phát"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Chuẩn"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Bản nhạc <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Bản nhạc <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Bản nhạc <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Tiêu đề video không xác định"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Tên bài hát không xác định"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Nghệ sĩ không xác định"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Không thể phát video bạn yêu cầu"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Quay lại"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Quay lại danh sách nút trước"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Xem các nút khác"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Tiến trình phát"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Cài đặt"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Phụ đề đang bật. Nhấp để ẩn."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Phụ đề đang tắt. Nhấp để hiển thị."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Phát lại"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Phát"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Tạm dừng"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Nội dung nghe nhìn trước đó"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Nội dung nghe nhìn tiếp theo"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Tua lại 10 giây"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Tua đi 30 giây"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Toàn màn hình"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-zh-rCN/strings.xml b/media2/media2-widget/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 04fe574..0000000
--- a/media2/media2-widget/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"关闭"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"音轨"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"无"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"播放速度"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"正常"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"字幕轨 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"字幕轨 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"音轨 <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"未知视频标题"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"未知歌曲名称"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"未知音乐人"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"无法播放您请求的内容"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"知道了"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"返回"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"返回上一个按钮列表"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"查看更多按钮"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"播放进度"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"设置"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"字幕已开启。点击即可隐藏。"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"字幕已关闭。点击即可显示。"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"重放"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"播放"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"暂停"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"上一项媒体内容"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"下一项媒体内容"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"快退 10 秒"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"快进 30 秒"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"全屏"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-zh-rHK/strings.xml b/media2/media2-widget/src/main/res/values-zh-rHK/strings.xml
deleted file mode 100644
index 05ec6bb..0000000
--- a/media2/media2-widget/src/main/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"關閉"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"音軌"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"無"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"播放速度"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"正常"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"曲目 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"曲目 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"曲目 <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"影片標題不明"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"歌名不明"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"歌手不明"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"無法播放你要求的影片"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"好"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"返回"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"返回上一個按鈕清單"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"查看更多按鈕"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"播放進度"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"設定"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"字幕已開啟,按一下即可隱藏。"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"字幕已關閉,按一下即可顯示。"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"重播"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"播放"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"暫停"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"上一個媒體"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"下一個媒體"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"倒帶 10 秒"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"前轉 30 秒"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"全螢幕"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-zh-rTW/strings.xml b/media2/media2-widget/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 0da5d80..0000000
--- a/media2/media2-widget/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"關閉"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"曲目"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"無"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"播放速度"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"正常"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"曲目 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"曲目 <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"曲目 <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"影片標題不明"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"歌曲名稱不明"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"演出者不明"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"無法播放你要求的項目"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"確定"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"返回"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"返回先前的按鈕清單"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"顯示更多按鈕"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"播放進度"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"設定"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"字幕已開啟。按一下即可隱藏。"</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"字幕已關閉。按一下即可顯示。"</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"重播"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"播放"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"暫停"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"上一個媒體"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"下一個媒體"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"倒轉 10 秒"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"快轉 30 秒"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"全螢幕"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values-zu/strings.xml b/media2/media2-widget/src/main/res/values-zu/strings.xml
deleted file mode 100644
index 36147c8..0000000
--- a/media2/media2-widget/src/main/res/values-zu/strings.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright 2018 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="MediaControlView_subtitle_off_text" msgid="3464220590351304587">"Valiwe"</string>
-    <string name="MediaControlView_audio_track_text" msgid="3309135445007366582">"Ithrekhi yomsindo"</string>
-    <string name="MediaControlView_audio_track_none_text" msgid="2659752099246305694">"Lutho"</string>
-    <string name="MediaControlView_playback_speed_text" msgid="1481072528142380025">"Isivinini sokudlala"</string>
-    <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Okujwayelekile"</string>
-    <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
-    <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Ithrekhi <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Ithrekhi <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
-    <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Ithrekhi <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
-    <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Isihloko sevidiyo asaziwa"</string>
-    <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Isihloko sengoma asaziwa"</string>
-    <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Umculi akaziwa"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Ayikwazi ukudlala into oyicelile"</string>
-    <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"KULUNGILE"</string>
-    <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Emuva"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Buyela emuva kuhlu lwenkinobho yangaphambilini"</string>
-    <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"Bona imiphumela eminingi"</string>
-    <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"Inqubekela phambili yokudlala"</string>
-    <string name="mcv2_settings_button_desc" msgid="811917224044739656">"Amasethingi"</string>
-    <string name="mcv2_cc_is_on" msgid="5427119422911561783">"Umbhalo ongezansi uvuliwe. Chofoza ukuze uwufihle."</string>
-    <string name="mcv2_cc_is_off" msgid="2380791179816122456">"Umbhalo ongezansi uvaliwe. Chofoza ukuze uwubonise."</string>
-    <string name="mcv2_replay_button_desc" msgid="3128622733570179596">"Dlala futhi"</string>
-    <string name="mcv2_play_button_desc" msgid="4881308324856085359">"Dlala"</string>
-    <string name="mcv2_pause_button_desc" msgid="7391720675120766278">"Phumula"</string>
-    <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Imidiya yangaphambilini"</string>
-    <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Imidiya elandelayo"</string>
-    <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Buyisela emuva ngamasekhondi ayi-10"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Hamba phambili ngamasekhondi angu-30"</string>
-    <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Iskrini esigcwele"</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/arrays.xml b/media2/media2-widget/src/main/res/values/arrays.xml
deleted file mode 100644
index 90cd130..0000000
--- a/media2/media2-widget/src/main/res/values/arrays.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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.
--->
-
-<resources>
-    <integer-array name="media2_widget_speed_multiplied_by_100">
-        <item>25</item>
-        <item>50</item>
-        <item>75</item>
-        <item>100</item>
-        <item>125</item>
-        <item>150</item>
-        <item>200</item>
-    </integer-array>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/attrs.xml b/media2/media2-widget/src/main/res/values/attrs.xml
deleted file mode 100644
index 51e8a0e4a..0000000
--- a/media2/media2-widget/src/main/res/values/attrs.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<resources>
-    <!-- Attributes that are read when parsing a <VideoView> tag. -->
-    <declare-styleable name="VideoView">
-        <!-- Default : true -->
-        <attr name="enableControlView" format="boolean" />
-        <!-- To choose underlying view type to render a video. Default : surfaceView -->
-        <attr name="viewType" format="enum">
-            <enum name="surfaceView" value="0" />
-            <enum name="textureView" value="1" />
-        </attr>
-    </declare-styleable>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/colors.xml b/media2/media2-widget/src/main/res/values/colors.xml
deleted file mode 100644
index 8412011..0000000
--- a/media2/media2-widget/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2018 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.
-  -->
-<resources>
-
-    <!-- The color of the material notification background for media notifications when no custom
-     color is specified -->
-    <color name="notification_material_background_media_default_color">#ff424242</color>
-
-    <color name="media2_widget_gray">#808080</color>
-    <color name="media2_widget_white">#ffffff</color>
-    <color name="media2_widget_white_opacity_70">#B3ffffff</color>
-    <color name="media2_widget_black_opacity_70">#B3000000</color>
-    <color name="media2_widget_title_bar_gradient_start">#d0000000</color>
-    <color name="media2_widget_title_bar_gradient_end">#00000000</color>
-    <color name="media2_widget_center_view_background">#90000000</color>
-    <color name="media2_widget_bottom_bar_background">#b0000000</color>
-    <color name="media2_widget_music_view_default_background">#424242</color>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/dimens.xml b/media2/media2-widget/src/main/res/values/dimens.xml
deleted file mode 100644
index 6a0d53c..0000000
--- a/media2/media2-widget/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2018 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.
-  -->
-
-<resources>
-    <dimen name="media2_widget_embedded_settings_width">150dp</dimen>
-    <dimen name="media2_widget_full_settings_width">225dp</dimen>
-    <dimen name="media2_widget_settings_height">48dp</dimen>
-    <dimen name="media2_widget_settings_icon_size">24dp</dimen>
-    <dimen name="media2_widget_settings_text_height">24dp</dimen>
-    <dimen name="media2_widget_settings_main_text_size">14sp</dimen>
-    <dimen name="media2_widget_settings_sub_text_size">12sp</dimen>
-    <dimen name="media2_widget_settings_offset">8dp</dimen>
-
-    <dimen name="media2_widget_icon_size">48dp</dimen>
-    <dimen name="media2_widget_icon_margin">10dp</dimen>
-    <dimen name="media2_widget_pause_icon_padding">6dp</dimen>
-    <dimen name="media2_widget_full_icon_padding">10dp</dimen>
-    <dimen name="media2_widget_embedded_icon_padding">12dp</dimen>
-
-    <dimen name="media2_widget_custom_progress_max_size">2dp</dimen>
-    <dimen name="media2_widget_custom_progress_thumb_size">12dp</dimen>
-    <dimen name="media2_widget_custom_progress_margin_bottom">43dp</dimen>
-
-    <dimen name="media2_widget_title_bar_height">48dp</dimen>
-    <dimen name="media2_widget_bottom_bar_height">48dp</dimen>
-
-    <dimen name="media2_widget_music_view_full_padding">52dp</dimen>
-    <dimen name="media2_widget_music_view_full_image_size">200dp</dimen>
-    <dimen name="media2_widget_music_view_image_title_gap">24dp</dimen>
-    <dimen name="media2_widget_music_view_landscape_text_minimum_width">200dp</dimen>
-    <dimen name="media2_widget_music_view_title_text_size">20sp</dimen>
-    <dimen name="media2_widget_music_view_artist_text_size">16sp</dimen>
-
-    <!-- Rounded corner radius for video subtitles. -->
-    <dimen name="media2_widget_subtitle_corner_radius">2dp</dimen>
-
-    <!-- Shadow radius for video subtitles. -->
-    <dimen name="media2_widget_subtitle_shadow_radius">2dp</dimen>
-
-    <!-- Shadow offset for video subtitles. -->
-    <dimen name="media2_widget_subtitle_shadow_offset">2dp</dimen>
-
-    <!-- Outline width for video subtitles. -->
-    <dimen name="media2_widget_subtitle_outline_width">2dp</dimen>
-
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/strings.xml b/media2/media2-widget/src/main/res/values/strings.xml
deleted file mode 100644
index 9ad1f0c..0000000
--- a/media2/media2-widget/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,135 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 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.
-  -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- TODO: remove translatable=false, when "Ads" mode is ready to release publicly. -->
-    <!-- Text for displaying ad skip wait time. -->
-    <string translatable="false" name="MediaControlView_ad_skip_wait_time">
-        You can skip Ad in <xliff:g id="wait_time" example="5">%1$d</xliff:g>s
-    </string>
-    <!-- TODO: remove translatable=false, when "Ads" mode is ready to release publicly. -->
-    <!-- Text for displaying ad total remaining time. -->
-    <string translatable="false" name="MediaControlView_ad_remaining_time">
-        Ad · <xliff:g id="remaining_time" example="1:15">%1$s</xliff:g> remaining
-    </string>
-    <!-- TODO: remove translatable=false, when "Ads" mode is ready to release publicly. -->
-    <!-- Placeholder text indicating that the user can press the button to go to an external website. -->
-    <string translatable="false" name="MediaControlView_ad_text">Visit Advertiser</string>
-
-    <!-- It's shown in the list of subtitle options. If the "off" option is selected, then
-         subtitle will not be shown. -->
-    <string name="MediaControlView_subtitle_off_text">Off</string>
-    <!-- The title of audio track selection. It is shown with the currently selected audio track's
-         information (such as language). If a user clicks it, the list of possible audio tracks
-         will be shown. -->
-    <string name="MediaControlView_audio_track_text">Audio track</string>
-    <!-- It is shown when no audio track exists in this media. -->
-    <string name="MediaControlView_audio_track_none_text">None</string>
-    <!-- The title of playback speed selection. It is shown with the current playback speed.
-         If a user clicks it, the list of possible playback speeds will be shown. -->
-    <string name="MediaControlView_playback_speed_text">Playback speed</string>
-    <!-- It implies that the playback speed is normal (1.0x). -->
-    <string name="MediaControlView_playback_speed_normal">Normal</string>
-    <string-array translatable="false" name="MediaControlView_playback_speeds">
-        <item>0.25x</item>
-        <item>0.5x</item>
-        <item>0.75x</item>
-        <item>1.25x</item>
-        <item>1.5x</item>
-        <item>2x</item>
-    </string-array>
-    <!-- Text for displaying custom playback speed. -->
-    <string translatable="false" name="MediaControlView_custom_playback_speed_text">
-        <xliff:g id="playback_speed" example="1.05">%1$.2f</xliff:g>x
-    </string>
-    <!-- Placeholder text for displaying time. Used to calculate which size layout to use. -->
-    <string name="MediaControlView_time_placeholder">00:00:00</string>
-
-    <!-- Text for displaying subtitle track number without language info. -->
-    <string name="MediaControlView_subtitle_track_number_text">
-        Track <xliff:g id="track_number" example="1">%1$d</xliff:g>
-    </string>
-    <!-- Text for displaying subtitle track number with language info. -->
-    <string name="MediaControlView_subtitle_track_number_and_lang_text">
-        Track <xliff:g id="track_number" example="1">%1$d</xliff:g> - <xliff:g id="lang" example="eng">%2$s</xliff:g>
-    </string>
-    <!-- Text for displaying audio track number. -->
-    <string name="MediaControlView_audio_track_number_text">
-        Track <xliff:g id="audio_number" example="1">%1$d</xliff:g>
-    </string>
-    <!-- Text for displaying unknown media title. -->
-    <string name="mcv2_non_music_title_unknown_text">Video title unknown</string>
-    <!-- Text for displaying unknown song title. -->
-    <string name="mcv2_music_title_unknown_text">Song title unknown</string>
-    <!-- Text for displaying unknown artist name. -->
-    <string name="mcv2_music_artist_unknown_text">Artist unknown</string>
-    <!-- Text for error alert when a video cannot be played. It can be used by any app. -->
-    <string name="mcv2_playback_error_text">Couldn\'t play the item you requested</string>
-    <!-- Button to close error alert when a video cannot be played. -->
-    <string name="mcv2_error_dialog_button">OK</string>
-
-    <!-- Content description of the back button. -->
-    <string name="mcv2_back_button_desc">Back</string>
-    <!-- Content description of the left arrow button to navigate a list of buttons.
-         If a user clicks the arrow, it goes back to the previous button list. -->
-    <string name="mcv2_overflow_left_button_desc">Back to previous button list</string>
-    <!-- Content description of the right arrow button to navigate a list of buttons.
-         If a user clicks the arrow, it shows a list of other buttons. -->
-    <string name="mcv2_overflow_right_button_desc">See more buttons</string>
-    <!-- Content description of the seek bar, which indicates the playback progress. -->
-    <string name="mcv2_seek_bar_desc">Playback progress</string>
-    <!-- Content description of the settings button. -->
-    <string name="mcv2_settings_button_desc">Settings</string>
-    <!-- Content description of the close caption (subtitle) button, when subtitle is turned on.
-         If a user clicks the button, it will turned off. -->
-    <string name="mcv2_cc_is_on">Subtitle is on. Click to hide it.</string>
-    <!-- Content description of the close caption (subtitle) button, when subtitle is turned off.
-         If a user clicks the button, it will turned on. -->
-    <string name="mcv2_cc_is_off">Subtitle is off. Click to show it.</string>
-    <!-- Content description of the replay button.
-         If a user clicks the button, the playback will be restarted from the beginning. -->
-    <string name="mcv2_replay_button_desc">Replay</string>
-    <!-- Content description of the play button.
-         If a user clicks the button, the playback will be started or resumed. -->
-    <string name="mcv2_play_button_desc">Play</string>
-    <!-- Content description of the pause button.
-         If a user clicks the button, the playback will be paused. -->
-    <string name="mcv2_pause_button_desc">Pause</string>
-    <!-- Content description of the "previous" button.
-         If a user clicks the button, it goes to the previous media to play. -->
-    <string name="mcv2_previous_button_desc">Previous media</string>
-    <!-- Content description of the "next" button.
-         If a user clicks the button, it goes to the next media to play. -->
-    <string name="mcv2_next_button_desc">Next media</string>
-    <!-- Content description of the "rewind" button.
-         If a user clicks the button, it rewinds back by 10 seconds. -->
-    <string name="mcv2_rewind_button_desc">Rewind by 10 seconds</string>
-    <!-- Content description of the "fast-forward" button.
-         If a user clicks the button, it fast-forwards by 30 seconds. -->
-    <string name="mcv2_ffwd_button_desc">Go forward by 30 seconds</string>
-    <!-- TODO: remove translatable=false, when "Ads" mode is ready to release publicly. -->
-    <!-- Content description of the "Ads" button.
-         If a user clicks the button, it launches external website. -->
-    <string translatable="false" name="mcv2_launch_button_desc">Launch Link</string>
-    <!-- Content description of the full screen button.
-         If a user clicks the button, the widget goes into full-screen mode. -->
-    <string name="mcv2_full_screen_button_desc">Full screen</string>
-    <!-- TODO: cleanup these video quality strings from all the branches. -->
-    <string translatable="false" name="mcv2_video_quality_button_desc">Video Quality Selection</string>
-    <string translatable="false" name="MediaControlView_video_quality_auto_text">Auto</string>
-</resources>
diff --git a/media2/media2-widget/src/main/res/values/styles.xml b/media2/media2-widget/src/main/res/values/styles.xml
deleted file mode 100644
index 0904206..0000000
--- a/media2/media2-widget/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,180 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <style name="Media2_Widget_FullTransportControlsButton">
-        <item name="android:background">@null</item>
-        <item name="android:scaleType">fitXY</item>
-    </style>
-
-    <style name="Media2_Widget_FullTransportControlsButton.Previous">
-        <item name="android:src">@drawable/media2_widget_ic_skip_previous</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_previous_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_full_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_FullTransportControlsButton.Next">
-        <item name="android:src">@drawable/media2_widget_ic_skip_next</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_next_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_full_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_FullTransportControlsButton.Pause">
-        <item name="android:src">@drawable/media2_widget_ic_pause_circle_filled</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_pause_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_pause_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_FullTransportControlsButton.Ffwd">
-        <item name="android:src">@drawable/media2_widget_ic_forward_30</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_ffwd_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_full_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_FullTransportControlsButton.Rew">
-        <item name="android:src">@drawable/media2_widget_ic_rewind_10</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_rewind_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_full_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton">
-        <item name="android:background">@null</item>
-        <item name="android:scaleType">fitXY</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton.Previous">
-        <item name="android:src">@drawable/media2_widget_ic_skip_previous</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_previous_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton.Next">
-        <item name="android:src">@drawable/media2_widget_ic_skip_next</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_next_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton.Pause">
-        <item name="android:src">@drawable/media2_widget_ic_pause_circle_filled</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_pause_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_pause_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton.Ffwd">
-        <item name="android:src">@drawable/media2_widget_ic_forward_30</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_ffwd_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_EmbeddedTransportControlsButton.Rew">
-        <item name="android:src">@drawable/media2_widget_ic_rewind_10</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:contentDescription">@string/mcv2_rewind_button_desc</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-    </style>
-
-    <style name="Media2_Widget_MinimalTransportControlsButton">
-        <item name="android:background">@null</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:padding">@dimen/media2_widget_pause_icon_padding</item>
-        <item name="android:scaleType">fitXY</item>
-        <item name="android:src">@drawable/media2_widget_ic_pause_circle_filled</item>
-        <item name="android:contentDescription">@string/mcv2_pause_button_desc</item>
-    </style>
-
-    <style name="Media2_Widget_TitleBar">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">@dimen/media2_widget_title_bar_height</item>
-    </style>
-
-    <style name="Media2_Widget_TitleBarButton">
-        <item name="android:background">@null</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-        <item name="android:scaleType">fitXY</item>
-    </style>
-
-    <style name="Media2_Widget_TitleBarButton.Launch">
-        <item name="android:src">@drawable/media2_widget_ic_launch</item>
-        <item name="android:contentDescription">@string/mcv2_launch_button_desc</item>
-    </style>
-
-    <style name="Media2_Widget_TimeText">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:paddingLeft">4dp</item>
-        <item name="android:paddingRight">4dp</item>
-        <item name="android:textStyle">bold</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:gravity">center</item>
-    </style>
-
-    <style name="Media2_Widget_TimeText.Current">
-        <item name="android:textColor">@color/media2_widget_white</item>
-        <item name="android:text">@string/MediaControlView_time_placeholder</item>
-    </style>
-
-    <style name="Media2_Widget_TimeText.Interpunct">
-        <item name="android:textColor">@color/media2_widget_white_opacity_70</item>
-        <item name="android:text">·</item>
-    </style>
-
-    <style name="Media2_Widget_TimeText.End">
-        <item name="android:textColor">@color/media2_widget_white_opacity_70</item>
-        <item name="android:text">@string/MediaControlView_time_placeholder</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton">
-        <item name="android:background">@null</item>
-        <item name="android:layout_width">@dimen/media2_widget_icon_size</item>
-        <item name="android:layout_height">@dimen/media2_widget_icon_size</item>
-        <item name="android:gravity">center_horizontal</item>
-        <item name="android:padding">@dimen/media2_widget_embedded_icon_padding</item>
-        <item name="android:scaleType">fitXY</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton.CC">
-        <item name="android:src">@drawable/media2_widget_ic_subtitle_off</item>
-        <item name="android:contentDescription">@string/mcv2_cc_is_off</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton.FullScreen">
-        <item name="android:src">@drawable/media2_widget_ic_fullscreen</item>
-        <item name="android:contentDescription">@string/mcv2_full_screen_button_desc</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton.OverflowShow">
-        <item name="android:src">@drawable/media2_widget_ic_chevron_right</item>
-        <item name="android:contentDescription">@string/mcv2_overflow_right_button_desc</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton.OverflowHide">
-        <item name="android:src">@drawable/media2_widget_ic_chevron_left</item>
-        <item name="android:contentDescription">@string/mcv2_overflow_left_button_desc</item>
-    </style>
-
-    <style name="Media2_Widget_BottomBarButton.Settings">
-        <item name="android:src">@drawable/media2_widget_ic_settings</item>
-        <item name="android:contentDescription">@string/mcv2_settings_button_desc</item>
-    </style>
-</resources>
diff --git a/mediarouter/mediarouter-testing/api/1.7.0-beta01.txt b/mediarouter/mediarouter-testing/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..14e1df6
--- /dev/null
+++ b/mediarouter/mediarouter-testing/api/1.7.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.mediarouter.testing {
+
+  public class MediaRouterTestHelper {
+    method @MainThread public static void resetMediaRouter();
+  }
+
+}
+
diff --git a/media2/media2-common/api/res-1.0.0-beta01.txt b/mediarouter/mediarouter-testing/api/res-1.7.0-beta01.txt
similarity index 100%
rename from media2/media2-common/api/res-1.0.0-beta01.txt
rename to mediarouter/mediarouter-testing/api/res-1.7.0-beta01.txt
diff --git a/mediarouter/mediarouter-testing/api/restricted_1.7.0-beta01.txt b/mediarouter/mediarouter-testing/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..14e1df6
--- /dev/null
+++ b/mediarouter/mediarouter-testing/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.mediarouter.testing {
+
+  public class MediaRouterTestHelper {
+    method @MainThread public static void resetMediaRouter();
+  }
+
+}
+
diff --git a/mediarouter/mediarouter/api/1.7.0-beta01.txt b/mediarouter/mediarouter/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..e67bdb6
--- /dev/null
+++ b/mediarouter/mediarouter/api/1.7.0-beta01.txt
@@ -0,0 +1,638 @@
+// Signature format: 4.0
+package androidx.mediarouter.app {
+
+  public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+    ctor public MediaRouteActionProvider(android.content.Context);
+    method @Deprecated public void enableDynamicGroup();
+    method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+    method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public android.view.View onCreateActionView();
+    method public androidx.mediarouter.app.MediaRouteButton onCreateMediaRouteButton();
+    method @Deprecated public void setAlwaysVisible(boolean);
+    method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteButton extends android.view.View {
+    ctor public MediaRouteButton(android.content.Context);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?, int);
+    method @Deprecated public void enableDynamicGroup();
+    method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public void onAttachedToWindow();
+    method public void onDetachedFromWindow();
+    method @Deprecated public void setAlwaysVisible(boolean);
+    method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+    method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable?);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method public boolean showDialog();
+  }
+
+  public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+    ctor public MediaRouteChooserDialog(android.content.Context);
+    ctor public MediaRouteChooserDialog(android.content.Context, int);
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+    method public void refreshRoutes();
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+    ctor public MediaRouteChooserDialogFragment();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public androidx.mediarouter.app.MediaRouteChooserDialog onCreateChooserDialog(android.content.Context, android.os.Bundle?);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+    ctor public MediaRouteControllerDialog(android.content.Context);
+    ctor public MediaRouteControllerDialog(android.content.Context, int);
+    method public android.view.View? getMediaControlView();
+    method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSession();
+    method public androidx.mediarouter.media.MediaRouter.RouteInfo getRoute();
+    method public boolean isVolumeControlEnabled();
+    method public android.view.View? onCreateMediaControlView(android.os.Bundle?);
+    method public void setVolumeControlEnabled(boolean);
+  }
+
+  public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+    ctor public MediaRouteControllerDialogFragment();
+    method public androidx.mediarouter.app.MediaRouteControllerDialog onCreateControllerDialog(android.content.Context, android.os.Bundle?);
+  }
+
+  public class MediaRouteDialogFactory {
+    ctor public MediaRouteDialogFactory();
+    method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+    method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+    method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+  }
+
+  public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+    ctor public MediaRouteDiscoveryFragment();
+    method public androidx.mediarouter.media.MediaRouter getMediaRouter();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public androidx.mediarouter.media.MediaRouter.Callback? onCreateCallback();
+    method public int onPrepareCallbackFlags();
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public final class SystemOutputSwitcherDialogController {
+    method public static boolean showDialog(android.content.Context);
+  }
+
+}
+
+package androidx.mediarouter.media {
+
+  public final class MediaControlIntent {
+    field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+    field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+    field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+    field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+    field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+    field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+    field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+    field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+    field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+    field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+    field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+    field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+    field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+    field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+    field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+    field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+    field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+    field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+    field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+    field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+    field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+    field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+    field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+    field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+    field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+    field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+    field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+  }
+
+  public final class MediaItemMetadata {
+    field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+    field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+    field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+  }
+
+  public final class MediaItemStatus {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaItemStatus? fromBundle(android.os.Bundle?);
+    method public long getContentDuration();
+    method public long getContentPosition();
+    method public android.os.Bundle? getExtras();
+    method public int getPlaybackState();
+    method public long getTimestamp();
+    field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+    field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+    field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+    field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+    field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+    field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+    field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+    field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+    field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+    field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+  }
+
+  public static final class MediaItemStatus.Builder {
+    ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus);
+    ctor public MediaItemStatus.Builder(int);
+    method public androidx.mediarouter.media.MediaItemStatus build();
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setContentDuration(long);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setContentPosition(long);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setPlaybackState(int);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setTimestamp(long);
+  }
+
+  public final class MediaRouteDescriptor {
+    method public android.os.Bundle asBundle();
+    method public boolean canDisconnectAndKeepPlaying();
+    method public static androidx.mediarouter.media.MediaRouteDescriptor? fromBundle(android.os.Bundle?);
+    method public java.util.Set<java.lang.String!> getAllowedPackages();
+    method public int getConnectionState();
+    method public java.util.List<android.content.IntentFilter!> getControlFilters();
+    method public java.util.Set<java.lang.String!> getDeduplicationIds();
+    method public String? getDescription();
+    method public int getDeviceType();
+    method public android.os.Bundle? getExtras();
+    method public android.net.Uri? getIconUri();
+    method public String getId();
+    method public String getName();
+    method public int getPlaybackStream();
+    method public int getPlaybackType();
+    method public int getPresentationDisplayId();
+    method public android.content.IntentSender? getSettingsActivity();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method @Deprecated public boolean isConnecting();
+    method public boolean isDynamicGroupRoute();
+    method public boolean isEnabled();
+    method public boolean isSystemRoute();
+    method public boolean isValid();
+    method public boolean isVisibilityPublic();
+  }
+
+  public static final class MediaRouteDescriptor.Builder {
+    ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+    ctor public MediaRouteDescriptor.Builder(String, String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilter(android.content.IntentFilter);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilters(java.util.Collection<android.content.IntentFilter!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder clearControlFilters();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setCanDisconnect(boolean);
+    method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnecting(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnectionState(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeduplicationIds(java.util.Set<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDescription(String?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeviceType(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIconUri(android.net.Uri);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setId(String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIsDynamicGroupRoute(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setName(String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackStream(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackType(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPresentationDisplayId(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setSettingsActivity(android.content.IntentSender?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityPublic();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityRestricted(java.util.Set<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolume(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeHandling(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeMax(int);
+  }
+
+  public final class MediaRouteDiscoveryRequest {
+    ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector, boolean);
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest? fromBundle(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaRouteSelector getSelector();
+    method public boolean isActiveScan();
+    method public boolean isValid();
+  }
+
+  public abstract class MediaRouteProvider {
+    ctor public MediaRouteProvider(android.content.Context);
+    method public final android.content.Context getContext();
+    method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+    method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+    method public final android.os.Handler getHandler();
+    method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata getMetadata();
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+    method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+    method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+    method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+    method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+    method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+  }
+
+  public abstract static class MediaRouteProvider.Callback {
+    ctor public MediaRouteProvider.Callback();
+    method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+  }
+
+  public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+    ctor public MediaRouteProvider.DynamicGroupRouteController();
+    method public String? getGroupableSelectionTitle();
+    method public String? getTransferableSectionTitle();
+    method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+    method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+    method public abstract void onAddMemberRoute(String);
+    method public abstract void onRemoveMemberRoute(String);
+    method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+  }
+
+  public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+    method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+    method public int getSelectionState();
+    method public boolean isGroupable();
+    method public boolean isTransferable();
+    method public boolean isUnselectable();
+    field public static final int SELECTED = 3; // 0x3
+    field public static final int SELECTING = 2; // 0x2
+    field public static final int UNSELECTED = 1; // 0x1
+    field public static final int UNSELECTING = 0; // 0x0
+  }
+
+  public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+    ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+    ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsGroupable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsTransferable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsUnselectable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setSelectionState(int);
+  }
+
+  public static final class MediaRouteProvider.ProviderMetadata {
+    method public android.content.ComponentName getComponentName();
+    method public String getPackageName();
+  }
+
+  public abstract static class MediaRouteProvider.RouteController {
+    ctor public MediaRouteProvider.RouteController();
+    method public boolean onControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+    method public void onRelease();
+    method public void onSelect();
+    method public void onSetVolume(int);
+    method @Deprecated public void onUnselect();
+    method public void onUnselect(int);
+    method public void onUpdateVolume(int);
+  }
+
+  public final class MediaRouteProviderDescriptor {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaRouteProviderDescriptor? fromBundle(android.os.Bundle?);
+    method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+    method public boolean isValid();
+    method public boolean supportsDynamicGroupRoute();
+  }
+
+  public static final class MediaRouteProviderDescriptor.Builder {
+    ctor public MediaRouteProviderDescriptor.Builder();
+    ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoute(androidx.mediarouter.media.MediaRouteDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder setSupportsDynamicGroupRoute(boolean);
+  }
+
+  public abstract class MediaRouteProviderService extends android.app.Service {
+    ctor public MediaRouteProviderService();
+    method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
+    method public android.os.IBinder? onBind(android.content.Intent);
+    method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+    field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+  }
+
+  public final class MediaRouteSelector {
+    method public android.os.Bundle asBundle();
+    method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
+    method public static androidx.mediarouter.media.MediaRouteSelector? fromBundle(android.os.Bundle?);
+    method public java.util.List<java.lang.String!> getControlCategories();
+    method public boolean hasControlCategory(String?);
+    method public boolean isEmpty();
+    method public boolean isValid();
+    method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>?);
+    field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+  }
+
+  public static final class MediaRouteSelector.Builder {
+    ctor public MediaRouteSelector.Builder();
+    ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method public androidx.mediarouter.media.MediaRouteSelector build();
+  }
+
+  public final class MediaRouter {
+    method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback);
+    method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+    method @MainThread public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+    method @Deprecated @MainThread public void addRemoteControlClient(Object);
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo? getBluetoothRoute();
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+    method @MainThread public static androidx.mediarouter.media.MediaRouter getInstance(android.content.Context);
+    method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSessionToken();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!> getProviders();
+    method @MainThread public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+    method @MainThread public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+    method @MainThread public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+    method @MainThread public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+    method @Deprecated @MainThread public void removeRemoteControlClient(Object);
+    method @MainThread public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @MainThread public void setMediaSession(Object?);
+    method @MainThread public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
+    method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+    method @MainThread public void setRouteListingPreference(androidx.mediarouter.media.RouteListingPreference?);
+    method @MainThread public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+    method @MainThread public void unselect(int);
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+    field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+    field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+    field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+    field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+    field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+    field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+    field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+    field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+    field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+    field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+  }
+
+  public abstract static class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
+    method public void onProviderAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onProviderChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onRouteAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+    method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+    method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+  }
+
+  public abstract static class MediaRouter.ControlRequestCallback {
+    ctor public MediaRouter.ControlRequestCallback();
+    method public void onError(String?, android.os.Bundle?);
+    method public void onResult(android.os.Bundle?);
+  }
+
+  public static interface MediaRouter.OnPrepareTransferListener {
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+  }
+
+  public static final class MediaRouter.ProviderInfo {
+    method public android.content.ComponentName getComponentName();
+    method public String getPackageName();
+    method @MainThread public androidx.mediarouter.media.MediaRouteProvider getProviderInstance();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+  }
+
+  public static class MediaRouter.RouteInfo {
+    method public boolean canDisconnect();
+    method public int getConnectionState();
+    method public java.util.List<android.content.IntentFilter!> getControlFilters();
+    method public String? getDescription();
+    method public int getDeviceType();
+    method public android.os.Bundle? getExtras();
+    method public android.net.Uri? getIconUri();
+    method public String getId();
+    method public String getName();
+    method public int getPlaybackStream();
+    method public int getPlaybackType();
+    method @MainThread public android.view.Display? getPresentationDisplay();
+    method public androidx.mediarouter.media.MediaRouter.ProviderInfo getProvider();
+    method public android.content.IntentSender? getSettingsIntent();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method @MainThread public boolean isBluetooth();
+    method @Deprecated public boolean isConnecting();
+    method @MainThread public boolean isDefault();
+    method public boolean isDeviceSpeaker();
+    method public boolean isEnabled();
+    method @MainThread public boolean isSelected();
+    method public boolean isSystemRoute();
+    method @MainThread public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method @MainThread public void requestSetVolume(int);
+    method @MainThread public void requestUpdateVolume(int);
+    method @MainThread public void select();
+    method @MainThread public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+    method @MainThread public boolean supportsControlAction(String, String);
+    method @MainThread public boolean supportsControlCategory(String);
+    method @MainThread public boolean supportsControlRequest(android.content.Intent);
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+    field public static final int DEVICE_TYPE_BLE_HEADSET = 22; // 0x16
+    field public static final int DEVICE_TYPE_BLUETOOTH_A2DP = 3; // 0x3
+    field public static final int DEVICE_TYPE_BUILTIN_SPEAKER = 12; // 0xc
+    field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+    field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+    field public static final int DEVICE_TYPE_DOCK = 19; // 0x13
+    field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+    field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+    field public static final int DEVICE_TYPE_HDMI = 16; // 0x10
+    field public static final int DEVICE_TYPE_HDMI_ARC = 23; // 0x17
+    field public static final int DEVICE_TYPE_HDMI_EARC = 24; // 0x18
+    field public static final int DEVICE_TYPE_HEARING_AID = 21; // 0x15
+    field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_SMARTPHONE = 11; // 0xb
+    field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
+    field @Deprecated public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+    field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
+    field public static final int DEVICE_TYPE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_USB_ACCESSORY = 18; // 0x12
+    field public static final int DEVICE_TYPE_USB_DEVICE = 17; // 0x11
+    field public static final int DEVICE_TYPE_USB_HEADSET = 20; // 0x14
+    field public static final int DEVICE_TYPE_WIRED_HEADPHONES = 14; // 0xe
+    field public static final int DEVICE_TYPE_WIRED_HEADSET = 13; // 0xd
+    field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+    field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+    field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+    field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+  }
+
+  public class MediaRouterParams {
+    method public int getDialogType();
+    method public boolean isMediaTransferReceiverEnabled();
+    method public boolean isOutputSwitcherEnabled();
+    method public boolean isTransferToLocalEnabled();
+    field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+    field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+    field public static final String ENABLE_GROUP_VOLUME_UX = "androidx.mediarouter.media.MediaRouterParams.ENABLE_GROUP_VOLUME_UX";
+  }
+
+  public static final class MediaRouterParams.Builder {
+    ctor public MediaRouterParams.Builder();
+    ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+    method public androidx.mediarouter.media.MediaRouterParams build();
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+  }
+
+  public final class MediaSessionStatus {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaSessionStatus? fromBundle(android.os.Bundle?);
+    method public android.os.Bundle? getExtras();
+    method public int getSessionState();
+    method public long getTimestamp();
+    method public boolean isQueuePaused();
+    field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+    field public static final int SESSION_STATE_ENDED = 1; // 0x1
+    field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+  }
+
+  public static final class MediaSessionStatus.Builder {
+    ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus);
+    ctor public MediaSessionStatus.Builder(int);
+    method public androidx.mediarouter.media.MediaSessionStatus build();
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setQueuePaused(boolean);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setSessionState(int);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setTimestamp(long);
+  }
+
+  public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+    ctor public MediaTransferReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent);
+  }
+
+  public class RemotePlaybackClient {
+    ctor public RemotePlaybackClient(android.content.Context, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void endSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void enqueue(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public String? getSessionId();
+    method public void getSessionStatus(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void getStatus(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public boolean hasSession();
+    method public boolean isMessagingSupported();
+    method public boolean isQueuingSupported();
+    method public boolean isRemotePlaybackSupported();
+    method public boolean isSessionManagementSupported();
+    method public void pause(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void play(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void release();
+    method public void remove(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void resume(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void seek(String, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void sendMessage(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener?);
+    method public void setSessionId(String?);
+    method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback?);
+    method public void startSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void stop(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+  }
+
+  public abstract static class RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.ActionCallback();
+    method public void onError(String?, int, android.os.Bundle?);
+  }
+
+  public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.ItemActionCallback();
+    method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+  }
+
+  public static interface RemotePlaybackClient.OnMessageReceivedListener {
+    method public void onMessageReceived(String, android.os.Bundle?);
+  }
+
+  public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.SessionActionCallback();
+    method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?);
+  }
+
+  public abstract static class RemotePlaybackClient.StatusCallback {
+    ctor public RemotePlaybackClient.StatusCallback();
+    method public void onItemStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+    method public void onSessionChanged(String?);
+    method public void onSessionStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?);
+  }
+
+  public final class RouteListingPreference {
+    method public java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!> getItems();
+    method public android.content.ComponentName? getLinkedItemComponentName();
+    method public boolean isSystemOrderingEnabled();
+    field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+    field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+  }
+
+  public static final class RouteListingPreference.Builder {
+    ctor public RouteListingPreference.Builder();
+    method public androidx.mediarouter.media.RouteListingPreference build();
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setItems(java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!>);
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setLinkedItemComponentName(android.content.ComponentName?);
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setSystemOrderingEnabled(boolean);
+  }
+
+  public static final class RouteListingPreference.Item {
+    method public CharSequence? getCustomSubtextMessage();
+    method public int getFlags();
+    method public String getRouteId();
+    method public int getSelectionBehavior();
+    method public int getSubText();
+    field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
+    field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
+    field public static final int FLAG_SUGGESTED = 4; // 0x4
+    field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+    field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+    field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+    field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4; // 0x4
+    field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+    field public static final int SUBTEXT_DEVICE_LOW_POWER = 5; // 0x5
+    field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3; // 0x3
+    field public static final int SUBTEXT_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int SUBTEXT_NONE = 0; // 0x0
+    field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2; // 0x2
+    field public static final int SUBTEXT_TRACK_UNSUPPORTED = 7; // 0x7
+    field public static final int SUBTEXT_UNAUTHORIZED = 6; // 0x6
+  }
+
+  public static final class RouteListingPreference.Item.Builder {
+    ctor public RouteListingPreference.Item.Builder(String);
+    method public androidx.mediarouter.media.RouteListingPreference.Item build();
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(CharSequence?);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setFlags(int);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSubText(int);
+  }
+
+}
+
diff --git a/mediarouter/mediarouter/api/res-1.7.0-beta01.txt b/mediarouter/mediarouter/api/res-1.7.0-beta01.txt
new file mode 100644
index 0000000..620c3fe
--- /dev/null
+++ b/mediarouter/mediarouter/api/res-1.7.0-beta01.txt
@@ -0,0 +1,4 @@
+dimen mediarouter_chooser_list_item_padding_bottom
+dimen mediarouter_chooser_list_item_padding_end
+dimen mediarouter_chooser_list_item_padding_start
+dimen mediarouter_chooser_list_item_padding_top
diff --git a/mediarouter/mediarouter/api/restricted_1.7.0-beta01.txt b/mediarouter/mediarouter/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..e67bdb6
--- /dev/null
+++ b/mediarouter/mediarouter/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,638 @@
+// Signature format: 4.0
+package androidx.mediarouter.app {
+
+  public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+    ctor public MediaRouteActionProvider(android.content.Context);
+    method @Deprecated public void enableDynamicGroup();
+    method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+    method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public android.view.View onCreateActionView();
+    method public androidx.mediarouter.app.MediaRouteButton onCreateMediaRouteButton();
+    method @Deprecated public void setAlwaysVisible(boolean);
+    method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteButton extends android.view.View {
+    ctor public MediaRouteButton(android.content.Context);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?, int);
+    method @Deprecated public void enableDynamicGroup();
+    method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public void onAttachedToWindow();
+    method public void onDetachedFromWindow();
+    method @Deprecated public void setAlwaysVisible(boolean);
+    method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+    method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable?);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method public boolean showDialog();
+  }
+
+  public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+    ctor public MediaRouteChooserDialog(android.content.Context);
+    ctor public MediaRouteChooserDialog(android.content.Context, int);
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+    method public void refreshRoutes();
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+    ctor public MediaRouteChooserDialogFragment();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public androidx.mediarouter.app.MediaRouteChooserDialog onCreateChooserDialog(android.content.Context, android.os.Bundle?);
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+    ctor public MediaRouteControllerDialog(android.content.Context);
+    ctor public MediaRouteControllerDialog(android.content.Context, int);
+    method public android.view.View? getMediaControlView();
+    method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSession();
+    method public androidx.mediarouter.media.MediaRouter.RouteInfo getRoute();
+    method public boolean isVolumeControlEnabled();
+    method public android.view.View? onCreateMediaControlView(android.os.Bundle?);
+    method public void setVolumeControlEnabled(boolean);
+  }
+
+  public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+    ctor public MediaRouteControllerDialogFragment();
+    method public androidx.mediarouter.app.MediaRouteControllerDialog onCreateControllerDialog(android.content.Context, android.os.Bundle?);
+  }
+
+  public class MediaRouteDialogFactory {
+    ctor public MediaRouteDialogFactory();
+    method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+    method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+    method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+  }
+
+  public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+    ctor public MediaRouteDiscoveryFragment();
+    method public androidx.mediarouter.media.MediaRouter getMediaRouter();
+    method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+    method public androidx.mediarouter.media.MediaRouter.Callback? onCreateCallback();
+    method public int onPrepareCallbackFlags();
+    method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+  }
+
+  public final class SystemOutputSwitcherDialogController {
+    method public static boolean showDialog(android.content.Context);
+  }
+
+}
+
+package androidx.mediarouter.media {
+
+  public final class MediaControlIntent {
+    field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+    field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+    field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+    field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+    field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+    field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+    field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+    field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+    field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+    field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+    field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+    field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+    field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+    field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+    field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+    field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+    field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+    field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+    field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+    field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+    field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+    field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+    field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+    field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+    field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+    field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+    field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+  }
+
+  public final class MediaItemMetadata {
+    field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+    field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+    field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+  }
+
+  public final class MediaItemStatus {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaItemStatus? fromBundle(android.os.Bundle?);
+    method public long getContentDuration();
+    method public long getContentPosition();
+    method public android.os.Bundle? getExtras();
+    method public int getPlaybackState();
+    method public long getTimestamp();
+    field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+    field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+    field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+    field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+    field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+    field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+    field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+    field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+    field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+    field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+  }
+
+  public static final class MediaItemStatus.Builder {
+    ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus);
+    ctor public MediaItemStatus.Builder(int);
+    method public androidx.mediarouter.media.MediaItemStatus build();
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setContentDuration(long);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setContentPosition(long);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setPlaybackState(int);
+    method public androidx.mediarouter.media.MediaItemStatus.Builder setTimestamp(long);
+  }
+
+  public final class MediaRouteDescriptor {
+    method public android.os.Bundle asBundle();
+    method public boolean canDisconnectAndKeepPlaying();
+    method public static androidx.mediarouter.media.MediaRouteDescriptor? fromBundle(android.os.Bundle?);
+    method public java.util.Set<java.lang.String!> getAllowedPackages();
+    method public int getConnectionState();
+    method public java.util.List<android.content.IntentFilter!> getControlFilters();
+    method public java.util.Set<java.lang.String!> getDeduplicationIds();
+    method public String? getDescription();
+    method public int getDeviceType();
+    method public android.os.Bundle? getExtras();
+    method public android.net.Uri? getIconUri();
+    method public String getId();
+    method public String getName();
+    method public int getPlaybackStream();
+    method public int getPlaybackType();
+    method public int getPresentationDisplayId();
+    method public android.content.IntentSender? getSettingsActivity();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method @Deprecated public boolean isConnecting();
+    method public boolean isDynamicGroupRoute();
+    method public boolean isEnabled();
+    method public boolean isSystemRoute();
+    method public boolean isValid();
+    method public boolean isVisibilityPublic();
+  }
+
+  public static final class MediaRouteDescriptor.Builder {
+    ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+    ctor public MediaRouteDescriptor.Builder(String, String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilter(android.content.IntentFilter);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilters(java.util.Collection<android.content.IntentFilter!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder clearControlFilters();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setCanDisconnect(boolean);
+    method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnecting(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnectionState(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeduplicationIds(java.util.Set<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDescription(String?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeviceType(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIconUri(android.net.Uri);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setId(String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIsDynamicGroupRoute(boolean);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setName(String);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackStream(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackType(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPresentationDisplayId(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setSettingsActivity(android.content.IntentSender?);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityPublic();
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityRestricted(java.util.Set<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolume(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeHandling(int);
+    method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeMax(int);
+  }
+
+  public final class MediaRouteDiscoveryRequest {
+    ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector, boolean);
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest? fromBundle(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaRouteSelector getSelector();
+    method public boolean isActiveScan();
+    method public boolean isValid();
+  }
+
+  public abstract class MediaRouteProvider {
+    ctor public MediaRouteProvider(android.content.Context);
+    method public final android.content.Context getContext();
+    method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+    method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+    method public final android.os.Handler getHandler();
+    method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata getMetadata();
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+    method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+    method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+    method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+    method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+    method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+  }
+
+  public abstract static class MediaRouteProvider.Callback {
+    ctor public MediaRouteProvider.Callback();
+    method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+  }
+
+  public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+    ctor public MediaRouteProvider.DynamicGroupRouteController();
+    method public String? getGroupableSelectionTitle();
+    method public String? getTransferableSectionTitle();
+    method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+    method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+    method public abstract void onAddMemberRoute(String);
+    method public abstract void onRemoveMemberRoute(String);
+    method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+  }
+
+  public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+    method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+    method public int getSelectionState();
+    method public boolean isGroupable();
+    method public boolean isTransferable();
+    method public boolean isUnselectable();
+    field public static final int SELECTED = 3; // 0x3
+    field public static final int SELECTING = 2; // 0x2
+    field public static final int UNSELECTED = 1; // 0x1
+    field public static final int UNSELECTING = 0; // 0x0
+  }
+
+  public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+    ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+    ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsGroupable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsTransferable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsUnselectable(boolean);
+    method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setSelectionState(int);
+  }
+
+  public static final class MediaRouteProvider.ProviderMetadata {
+    method public android.content.ComponentName getComponentName();
+    method public String getPackageName();
+  }
+
+  public abstract static class MediaRouteProvider.RouteController {
+    ctor public MediaRouteProvider.RouteController();
+    method public boolean onControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+    method public void onRelease();
+    method public void onSelect();
+    method public void onSetVolume(int);
+    method @Deprecated public void onUnselect();
+    method public void onUnselect(int);
+    method public void onUpdateVolume(int);
+  }
+
+  public final class MediaRouteProviderDescriptor {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaRouteProviderDescriptor? fromBundle(android.os.Bundle?);
+    method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+    method public boolean isValid();
+    method public boolean supportsDynamicGroupRoute();
+  }
+
+  public static final class MediaRouteProviderDescriptor.Builder {
+    ctor public MediaRouteProviderDescriptor.Builder();
+    ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoute(androidx.mediarouter.media.MediaRouteDescriptor);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>);
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor build();
+    method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder setSupportsDynamicGroupRoute(boolean);
+  }
+
+  public abstract class MediaRouteProviderService extends android.app.Service {
+    ctor public MediaRouteProviderService();
+    method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
+    method public android.os.IBinder? onBind(android.content.Intent);
+    method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+    field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+  }
+
+  public final class MediaRouteSelector {
+    method public android.os.Bundle asBundle();
+    method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
+    method public static androidx.mediarouter.media.MediaRouteSelector? fromBundle(android.os.Bundle?);
+    method public java.util.List<java.lang.String!> getControlCategories();
+    method public boolean hasControlCategory(String?);
+    method public boolean isEmpty();
+    method public boolean isValid();
+    method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>?);
+    field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+  }
+
+  public static final class MediaRouteSelector.Builder {
+    ctor public MediaRouteSelector.Builder();
+    ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+    method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method public androidx.mediarouter.media.MediaRouteSelector build();
+  }
+
+  public final class MediaRouter {
+    method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback);
+    method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+    method @MainThread public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+    method @Deprecated @MainThread public void addRemoteControlClient(Object);
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo? getBluetoothRoute();
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+    method @MainThread public static androidx.mediarouter.media.MediaRouter getInstance(android.content.Context);
+    method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSessionToken();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!> getProviders();
+    method @MainThread public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+    method @MainThread public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+    method @MainThread public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+    method @MainThread public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+    method @Deprecated @MainThread public void removeRemoteControlClient(Object);
+    method @MainThread public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @MainThread public void setMediaSession(Object?);
+    method @MainThread public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
+    method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+    method @MainThread public void setRouteListingPreference(androidx.mediarouter.media.RouteListingPreference?);
+    method @MainThread public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+    method @MainThread public void unselect(int);
+    method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+    field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+    field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+    field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+    field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+    field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+    field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+    field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+    field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+    field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+    field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+  }
+
+  public abstract static class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
+    method public void onProviderAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onProviderChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+    method public void onRouteAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+    method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+    method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+  }
+
+  public abstract static class MediaRouter.ControlRequestCallback {
+    ctor public MediaRouter.ControlRequestCallback();
+    method public void onError(String?, android.os.Bundle?);
+    method public void onResult(android.os.Bundle?);
+  }
+
+  public static interface MediaRouter.OnPrepareTransferListener {
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+  }
+
+  public static final class MediaRouter.ProviderInfo {
+    method public android.content.ComponentName getComponentName();
+    method public String getPackageName();
+    method @MainThread public androidx.mediarouter.media.MediaRouteProvider getProviderInstance();
+    method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+  }
+
+  public static class MediaRouter.RouteInfo {
+    method public boolean canDisconnect();
+    method public int getConnectionState();
+    method public java.util.List<android.content.IntentFilter!> getControlFilters();
+    method public String? getDescription();
+    method public int getDeviceType();
+    method public android.os.Bundle? getExtras();
+    method public android.net.Uri? getIconUri();
+    method public String getId();
+    method public String getName();
+    method public int getPlaybackStream();
+    method public int getPlaybackType();
+    method @MainThread public android.view.Display? getPresentationDisplay();
+    method public androidx.mediarouter.media.MediaRouter.ProviderInfo getProvider();
+    method public android.content.IntentSender? getSettingsIntent();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method @MainThread public boolean isBluetooth();
+    method @Deprecated public boolean isConnecting();
+    method @MainThread public boolean isDefault();
+    method public boolean isDeviceSpeaker();
+    method public boolean isEnabled();
+    method @MainThread public boolean isSelected();
+    method public boolean isSystemRoute();
+    method @MainThread public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+    method @MainThread public void requestSetVolume(int);
+    method @MainThread public void requestUpdateVolume(int);
+    method @MainThread public void select();
+    method @MainThread public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+    method @MainThread public boolean supportsControlAction(String, String);
+    method @MainThread public boolean supportsControlCategory(String);
+    method @MainThread public boolean supportsControlRequest(android.content.Intent);
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+    field public static final int DEVICE_TYPE_BLE_HEADSET = 22; // 0x16
+    field public static final int DEVICE_TYPE_BLUETOOTH_A2DP = 3; // 0x3
+    field public static final int DEVICE_TYPE_BUILTIN_SPEAKER = 12; // 0xc
+    field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+    field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+    field public static final int DEVICE_TYPE_DOCK = 19; // 0x13
+    field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+    field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+    field public static final int DEVICE_TYPE_HDMI = 16; // 0x10
+    field public static final int DEVICE_TYPE_HDMI_ARC = 23; // 0x17
+    field public static final int DEVICE_TYPE_HDMI_EARC = 24; // 0x18
+    field public static final int DEVICE_TYPE_HEARING_AID = 21; // 0x15
+    field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_SMARTPHONE = 11; // 0xb
+    field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
+    field @Deprecated public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+    field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
+    field public static final int DEVICE_TYPE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_USB_ACCESSORY = 18; // 0x12
+    field public static final int DEVICE_TYPE_USB_DEVICE = 17; // 0x11
+    field public static final int DEVICE_TYPE_USB_HEADSET = 20; // 0x14
+    field public static final int DEVICE_TYPE_WIRED_HEADPHONES = 14; // 0xe
+    field public static final int DEVICE_TYPE_WIRED_HEADSET = 13; // 0xd
+    field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+    field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+    field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+    field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+  }
+
+  public class MediaRouterParams {
+    method public int getDialogType();
+    method public boolean isMediaTransferReceiverEnabled();
+    method public boolean isOutputSwitcherEnabled();
+    method public boolean isTransferToLocalEnabled();
+    field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+    field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+    field public static final String ENABLE_GROUP_VOLUME_UX = "androidx.mediarouter.media.MediaRouterParams.ENABLE_GROUP_VOLUME_UX";
+  }
+
+  public static final class MediaRouterParams.Builder {
+    ctor public MediaRouterParams.Builder();
+    ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+    method public androidx.mediarouter.media.MediaRouterParams build();
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+  }
+
+  public final class MediaSessionStatus {
+    method public android.os.Bundle asBundle();
+    method public static androidx.mediarouter.media.MediaSessionStatus? fromBundle(android.os.Bundle?);
+    method public android.os.Bundle? getExtras();
+    method public int getSessionState();
+    method public long getTimestamp();
+    method public boolean isQueuePaused();
+    field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+    field public static final int SESSION_STATE_ENDED = 1; // 0x1
+    field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+  }
+
+  public static final class MediaSessionStatus.Builder {
+    ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus);
+    ctor public MediaSessionStatus.Builder(int);
+    method public androidx.mediarouter.media.MediaSessionStatus build();
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setExtras(android.os.Bundle?);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setQueuePaused(boolean);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setSessionState(int);
+    method public androidx.mediarouter.media.MediaSessionStatus.Builder setTimestamp(long);
+  }
+
+  public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+    ctor public MediaTransferReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent);
+  }
+
+  public class RemotePlaybackClient {
+    ctor public RemotePlaybackClient(android.content.Context, androidx.mediarouter.media.MediaRouter.RouteInfo);
+    method public void endSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void enqueue(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public String? getSessionId();
+    method public void getSessionStatus(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void getStatus(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public boolean hasSession();
+    method public boolean isMessagingSupported();
+    method public boolean isQueuingSupported();
+    method public boolean isRemotePlaybackSupported();
+    method public boolean isSessionManagementSupported();
+    method public void pause(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void play(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void release();
+    method public void remove(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void resume(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void seek(String, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+    method public void sendMessage(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener?);
+    method public void setSessionId(String?);
+    method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback?);
+    method public void startSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+    method public void stop(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+  }
+
+  public abstract static class RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.ActionCallback();
+    method public void onError(String?, int, android.os.Bundle?);
+  }
+
+  public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.ItemActionCallback();
+    method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+  }
+
+  public static interface RemotePlaybackClient.OnMessageReceivedListener {
+    method public void onMessageReceived(String, android.os.Bundle?);
+  }
+
+  public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+    ctor public RemotePlaybackClient.SessionActionCallback();
+    method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?);
+  }
+
+  public abstract static class RemotePlaybackClient.StatusCallback {
+    ctor public RemotePlaybackClient.StatusCallback();
+    method public void onItemStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+    method public void onSessionChanged(String?);
+    method public void onSessionStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?);
+  }
+
+  public final class RouteListingPreference {
+    method public java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!> getItems();
+    method public android.content.ComponentName? getLinkedItemComponentName();
+    method public boolean isSystemOrderingEnabled();
+    field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+    field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+  }
+
+  public static final class RouteListingPreference.Builder {
+    ctor public RouteListingPreference.Builder();
+    method public androidx.mediarouter.media.RouteListingPreference build();
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setItems(java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!>);
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setLinkedItemComponentName(android.content.ComponentName?);
+    method public androidx.mediarouter.media.RouteListingPreference.Builder setSystemOrderingEnabled(boolean);
+  }
+
+  public static final class RouteListingPreference.Item {
+    method public CharSequence? getCustomSubtextMessage();
+    method public int getFlags();
+    method public String getRouteId();
+    method public int getSelectionBehavior();
+    method public int getSubText();
+    field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
+    field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
+    field public static final int FLAG_SUGGESTED = 4; // 0x4
+    field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+    field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+    field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+    field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4; // 0x4
+    field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+    field public static final int SUBTEXT_DEVICE_LOW_POWER = 5; // 0x5
+    field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3; // 0x3
+    field public static final int SUBTEXT_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int SUBTEXT_NONE = 0; // 0x0
+    field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2; // 0x2
+    field public static final int SUBTEXT_TRACK_UNSUPPORTED = 7; // 0x7
+    field public static final int SUBTEXT_UNAUTHORIZED = 6; // 0x6
+  }
+
+  public static final class RouteListingPreference.Item.Builder {
+    ctor public RouteListingPreference.Item.Builder(String);
+    method public androidx.mediarouter.media.RouteListingPreference.Item build();
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(CharSequence?);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setFlags(int);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+    method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSubText(int);
+  }
+
+}
+
diff --git a/mediarouter/mediarouter/lint-baseline.xml b/mediarouter/mediarouter/lint-baseline.xml
index 5c1cb4f..d338482 100644
--- a/mediarouter/mediarouter/lint-baseline.xml
+++ b/mediarouter/mediarouter/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanThreadSleep"
@@ -39,7 +39,7 @@
 
     <issue
         id="UnspecifiedRegisterReceiverFlag"
-        message="`mActionReceiver` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for androidx.mediarouter.media.actions.ACTION_ITEM_STATUS_CHANGED, androidx.mediarouter.media.actions.ACTION_SESSION_STATUS_CHANGED, androidx.mediarouter.media.actions.ACTION_MESSAGE_RECEIVED"
+        message="`mActionReceiver` is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected broadcasts registered for androidx.mediarouter.media.actions.ACTION_ITEM_STATUS_CHANGED, androidx.mediarouter.media.actions.ACTION_SESSION_STATUS_CHANGED, androidx.mediarouter.media.actions.ACTION_MESSAGE_RECEIVED"
         errorLine1="            context.registerReceiver(mActionReceiver, actionFilter);"
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
index 5765475..e9127f7 100644
--- a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
+++ b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
+<issues format="6" by="lint 8.4.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha07)" variant="all" version="8.4.0-alpha07">
 
     <issue
         id="BanThreadSleep"
@@ -46,4 +46,13 @@
             file="src/test/kotlin/androidx/navigation/safeargs/gradle/IncrementalPluginTest.kt"/>
     </issue>
 
+    <issue
+        id="EagerGradleConfiguration"
+        message="Avoid using eager method get"
+        errorLine1="            variant.registerJavaGeneratingTask(task, task.get().outputDir.asFile.get())"
+        errorLine2="                                                          ~~~">
+        <location
+            file="src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt"/>
+    </issue>
+
 </issues>
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
index 8aeb85a..abc5785 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
@@ -55,15 +55,15 @@
      * Holder object for inputs to [loadInitial].
      *
      * @param Key Type of data used to query [Value] types out of the [DataSource].
-     * @property requestedInitialKey Load items around this key, or at the beginning of the data set
+     * @param requestedInitialKey Load items around this key, or at the beginning of the data set
      * if `null` is passed.
      *
      * Note that this key is generally a hint, and may be ignored if you want to always load from
      * the beginning.
-     * @property requestedLoadSize Requested number of items to load.
+     * @param requestedLoadSize Requested number of items to load.
      *
      * Note that this may be larger than available data.
-     * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+     * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the
      * loaded total count will be ignored.
      */
     public open class LoadInitialParams<Key : Any>(
@@ -79,10 +79,10 @@
      * Holder object for inputs to [loadBefore] and [loadAfter].
      *
      * @param Key Type of data used to query [Value] types out of the [DataSource].
-     * @property key Load items before/after this key.
+     * @param key Load items before/after this key.
      *
      * Returned data must begin directly adjacent to this position.
-     * @property requestedLoadSize Requested number of items to load.
+     * @param requestedLoadSize Requested number of items to load.
      *
      * Returned page can be of this size, but it may be altered if that is easier, e.g. a network
      * data source where the backend defines page size.
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
index e0d393bc..5f2296507 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
@@ -54,10 +54,10 @@
      * Holder object for inputs to [loadInitial].
      *
      * @param Key Type of data used to query pages.
-     * @property requestedLoadSize Requested number of items to load.
+     * @param requestedLoadSize Requested number of items to load.
      *
      * Note that this may be larger than available data.
-     * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+     * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the
      * loaded total count will be ignored.
      */
     public open class LoadInitialParams<Key : Any>(
@@ -69,10 +69,10 @@
      * Holder object for inputs to [loadBefore] and [loadAfter].
      *
      * @param Key Type of data used to query pages.
-     * @property key Load items before/after this key.
+     * @param key Load items before/after this key.
      *
      * Returned data must begin directly adjacent to this position.
-     * @property requestedLoadSize Requested number of items to load.
+     * @param requestedLoadSize Requested number of items to load.
      *
      * Returned page can be of this size, but it may be altered if that is easier, e.g. a network
      * data source where the backend defines page size.
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
index 9509e2f..f489ffa 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
@@ -153,6 +153,7 @@
         scope.cancel()
     }
 
+    @Ignore // 324193178
     @Test
     public fun dontLeakNonCachedFlow_finished() = runTest {
         collectPages(
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
index d554721..2841b9f 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
@@ -89,7 +89,7 @@
      *
      * @param error [Throwable] that caused the load operation to generate this error state.
      *
-     * @see androidx.paging.PagedList.retry
+     * @see androidx.paging.PagedList#retry
      */
     public class Error(
         public val error: Throwable
diff --git a/playground-common/gradle/wrapper/gradle-wrapper.properties b/playground-common/gradle/wrapper/gradle-wrapper.properties
index 3919b6f..6e3d090 100644
--- a/playground-common/gradle/wrapper/gradle-wrapper.properties
+++ b/playground-common/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-rc-1-bin.zip
-distributionSha256Sum=a2da4ba435f6728b43554c5845f6f88f79589c3e0018c29ab33eb23bd781255b
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 676e017..5970697 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
 # Disable docs
 androidx.enableDocumentation=false
 androidx.playground.snapshotBuildId=11349412
-androidx.playground.metalavaBuildId=11328314
-androidx.studio.type=playground
+androidx.playground.metalavaBuildId=11427892
+androidx.studio.type=playground
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
index c7e868f..7d3b798 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
@@ -41,6 +41,7 @@
 import com.google.common.truth.Truth.assertThat
 import dalvik.system.BaseDexClassLoader
 import java.io.File
+import java.util.concurrent.Executor
 import org.junit.Assert.assertThrows
 import org.junit.Assume.assumeTrue
 import org.junit.Before
@@ -319,8 +320,20 @@
         var sdkActivityHandlers: MutableMap<IBinder, SdkSandboxActivityHandlerCompat> =
             mutableMapOf()
 
-        override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-            throw UnsupportedOperationException("Shouldn't be called")
+        override fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        ) {
+            executor.execute {
+                callback.onError(
+                    LoadSdkCompatException(
+                        LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+                        "Shouldn't be called"
+                    )
+                )
+            }
         }
 
         override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
index ee66f50..49aaf76 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
@@ -34,6 +34,7 @@
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import java.io.File
+import java.util.concurrent.Executor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -160,8 +161,20 @@
 
     private class NoOpImpl : SdkSandboxControllerCompat.SandboxControllerImpl {
 
-        override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-            throw UnsupportedOperationException("NoOp")
+        override fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        ) {
+            executor.execute {
+                callback.onError(
+                    LoadSdkCompatException(
+                        LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+                        "NoOp"
+                    )
+                )
+            }
         }
 
         override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
index eb195d6..a57674c 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
@@ -20,9 +20,11 @@
 import android.os.IBinder
 import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import java.util.concurrent.Executor
 
 /**
  * Local implementation that will be injected to locally loaded SDKs.
@@ -32,8 +34,21 @@
     private val locallyLoadedSdks: LocallyLoadedSdks,
     private val appOwnedSdkRegistry: AppOwnedSdkRegistry
 ) : SdkSandboxControllerCompat.SandboxControllerImpl {
-    override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-        throw UnsupportedOperationException("Shouldn't be called")
+
+    override fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        executor: Executor,
+        callback: SdkSandboxControllerCompat.LoadSdkCallback
+    ) {
+        executor.execute {
+            callback.onError(
+                LoadSdkCompatException(
+                    LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+                    "Shouldn't be called"
+                )
+            )
+        }
     }
 
     override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
index d815735..c3a7b6d 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
@@ -30,6 +30,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Assert
@@ -220,8 +221,20 @@
     ) : SdkSandboxControllerCompat.SandboxControllerImpl {
         var token: IBinder? = null
 
-        override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-            throw UnsupportedOperationException("Shouldn't be called")
+        override fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        ) {
+            executor.execute {
+                callback.onError(
+                    LoadSdkCompatException(
+                        LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+                        "Shouldn't be called"
+                    )
+                )
+            }
         }
 
         override fun getSandboxedSdks() = sandboxedSdks
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
index 3d1a408..518a230 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
@@ -34,6 +34,12 @@
 import androidx.privacysandbox.sdkruntime.core.controller.impl.LocalImpl
 import androidx.privacysandbox.sdkruntime.core.controller.impl.NoOpImpl
 import androidx.privacysandbox.sdkruntime.core.controller.impl.PlatformUDCImpl
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
 import org.jetbrains.annotations.TestOnly
 
 /**
@@ -65,8 +71,15 @@
      * @return [SandboxedSdkCompat] from SDK on a successful run.
      * @throws [LoadSdkCompatException] on fail.
      */
-    suspend fun loadSdk(sdkName: String, params: Bundle) =
-        controllerImpl.loadSdk(sdkName, params)
+    suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat =
+        suspendCancellableCoroutine { continuation ->
+            controllerImpl.loadSdk(
+                sdkName,
+                params,
+                Runnable::run,
+                ContinuationLoadSdkCallback(continuation)
+            )
+        }
 
     /**
      * Fetches information about Sdks that are loaded in the sandbox or locally.
@@ -118,7 +131,7 @@
     @RestrictTo(LIBRARY_GROUP)
     interface SandboxControllerImpl {
 
-        suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat
+        fun loadSdk(sdkName: String, params: Bundle, executor: Executor, callback: LoadSdkCallback)
 
         fun getSandboxedSdks(): List<SandboxedSdkCompat>
 
@@ -132,6 +145,12 @@
         )
     }
 
+    @RestrictTo(LIBRARY_GROUP)
+    interface LoadSdkCallback {
+        fun onResult(result: SandboxedSdkCompat)
+        fun onError(error: LoadSdkCompatException)
+    }
+
     companion object {
 
         private var localImpl: SandboxControllerImpl? = null
@@ -186,4 +205,24 @@
             throw UnsupportedOperationException("SDK should be loaded locally on API below 34")
         }
     }
+
+    private class ContinuationLoadSdkCallback(
+        private val continuation: Continuation<SandboxedSdkCompat>
+    ) : LoadSdkCallback, AtomicBoolean(false) {
+        override fun onResult(result: SandboxedSdkCompat) {
+            // Do not attempt to resume more than once, even if the caller is buggy.
+            if (compareAndSet(false, true)) {
+                continuation.resume(result)
+            }
+        }
+
+        override fun onError(error: LoadSdkCompatException) {
+            // Do not attempt to resume more than once, even if the caller is buggy.
+            if (compareAndSet(false, true)) {
+                continuation.resumeWithException(error)
+            }
+        }
+
+        override fun toString() = "ContinuationLoadSdkCallback(outcomeReceived = ${get()})"
+    }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
index f7c1f54..0b9bcf4 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
@@ -24,6 +24,7 @@
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import java.util.concurrent.Executor
 
 /**
  * Wrapper for client provided implementation of [SdkSandboxControllerCompat].
@@ -34,11 +35,20 @@
     private val clientVersion: Int
 ) : SdkSandboxControllerCompat.SandboxControllerImpl {
 
-    override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-        throw LoadSdkCompatException(
-            LOAD_SDK_NOT_FOUND,
-            "Not supported for locally loaded SDKs yet"
-        )
+    override fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        executor: Executor,
+        callback: SdkSandboxControllerCompat.LoadSdkCallback
+    ) {
+        executor.execute {
+            callback.onError(
+                LoadSdkCompatException(
+                    LOAD_SDK_NOT_FOUND,
+                    "Not supported for locally loaded SDKs yet"
+                )
+            )
+        }
     }
 
     override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt
index 6e0727d..102d2e3 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt
@@ -23,17 +23,27 @@
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import java.util.concurrent.Executor
 
 /**
  * NoOp implementation for cases when [SdkSandboxControllerCompat] not supported.
  */
 internal class NoOpImpl : SdkSandboxControllerCompat.SandboxControllerImpl {
 
-    override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-        throw LoadSdkCompatException(
-            LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
-            "Loading SDK not supported on this device"
-        )
+    override fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        executor: Executor,
+        callback: SdkSandboxControllerCompat.LoadSdkCallback
+    ) {
+        executor.execute {
+            callback.onError(
+                LoadSdkCompatException(
+                    LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
+                    "Loading SDK not supported on this device"
+                )
+            )
+        }
     }
 
     override fun getSandboxedSdks(): List<SandboxedSdkCompat> = emptyList()
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformSdkLoader.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformSdkLoader.kt
index 72e7850..138b2b3 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformSdkLoader.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformSdkLoader.kt
@@ -17,18 +17,20 @@
 package androidx.privacysandbox.sdkruntime.core.controller.impl
 
 import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
 import android.app.sdksandbox.sdkprovider.SdkSandboxController
 import android.os.Bundle
+import android.os.OutcomeReceiver
 import android.os.ext.SdkExtensions
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresExtension
 import androidx.core.os.BuildCompat
-import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
-import kotlinx.coroutines.suspendCancellableCoroutine
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import java.util.concurrent.Executor
 
 /**
  * Trying to load SDK using [SdkSandboxController].
@@ -39,47 +41,74 @@
     private val loaderImpl: LoaderImpl
 ) {
 
-    suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat =
-        loaderImpl.loadSdk(sdkName, params)
+    fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        executor: Executor,
+        receiver: SdkSandboxControllerCompat.LoadSdkCallback
+    ) {
+        loaderImpl.loadSdk(sdkName, params, executor, receiver)
+    }
 
     private interface LoaderImpl {
-        suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat
+        fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        )
     }
 
     /**
      * Implementation for cases when API not supported by [SdkSandboxController]
      */
     private object FailImpl : LoaderImpl {
-        override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-            throw LoadSdkCompatException(
-                LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
-                "Loading SDK not supported on this device"
-            )
+        override fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        ) {
+            executor.execute {
+                callback.onError(
+                    LoadSdkCompatException(
+                        LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
+                        "Loading SDK not supported on this device"
+                    )
+                )
+            }
         }
     }
 
     /**
      * Implementation for AdServices V10.
      */
+    @RequiresApi(34)
     @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
     private class ApiAdServicesV10Impl(
         private val controller: SdkSandboxController
     ) : LoaderImpl {
         @DoNotInline
-        override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
-            try {
-                val sandboxedSdk = suspendCancellableCoroutine { continuation ->
-                    controller.loadSdk(
-                        sdkName,
-                        params,
-                        Runnable::run,
-                        continuation.asOutcomeReceiver()
-                    )
+        override fun loadSdk(
+            sdkName: String,
+            params: Bundle,
+            executor: Executor,
+            callback: SdkSandboxControllerCompat.LoadSdkCallback
+        ) {
+            controller.loadSdk(
+                sdkName,
+                params,
+                executor,
+                object : OutcomeReceiver<SandboxedSdk, LoadSdkException> {
+                    override fun onResult(result: SandboxedSdk) {
+                        callback.onResult(SandboxedSdkCompat(result))
+                    }
+
+                    override fun onError(error: LoadSdkException) {
+                        callback.onError(toLoadCompatSdkException(error))
+                    }
                 }
-                return SandboxedSdkCompat(sandboxedSdk)
-            } catch (ex: LoadSdkException) {
-                throw toLoadCompatSdkException(ex)
-            }
+            )
         }
     }
 
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
index 401fcf9..7b1dc7f 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
@@ -32,6 +32,7 @@
 import androidx.privacysandbox.sdkruntime.core.activity.ActivityHolder
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import java.util.concurrent.Executor
 
 /**
  * Implementation that delegates to platform [SdkSandboxController] for Android U.
@@ -47,8 +48,14 @@
     private val compatToPlatformMap =
         hashMapOf<SdkSandboxActivityHandlerCompat, SdkSandboxActivityHandler>()
 
-    override suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat =
-        sdkLoader.loadSdk(sdkName, params)
+    override fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        executor: Executor,
+        callback: SdkSandboxControllerCompat.LoadSdkCallback
+    ) {
+        sdkLoader.loadSdk(sdkName, params, executor, callback)
+    }
 
     override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
         return controller
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
index 1e3d017..2f90309 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -29,7 +29,6 @@
 import androidx.privacysandbox.tools.core.generator.ThrowableParcelConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.TransportCancellationGenerator
 import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
-import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.model.containsSdkActivityLauncher
 import androidx.privacysandbox.tools.core.model.getOnlyService
@@ -121,9 +120,7 @@
     private fun generateValueConverters() {
         val valueConverterFileGenerator =
             ValueConverterFileGenerator(binderCodeConverter, target)
-        // TODO(b/323369085): Generate value converters for enum classes
-        api.values.filterIsInstance<AnnotatedDataClass>().map(valueConverterFileGenerator::generate)
-            .forEach(::write)
+        api.values.map(valueConverterFileGenerator::generate).forEach(::write)
         api.interfaces.filter { it.inheritsSandboxedUiAdapter }.map {
             CoreLibInfoAndBinderWrapperConverterGenerator.generate(it).also(::write)
         }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkApiCompilerDiffTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkApiCompilerDiffTest.kt
index 88c2b52..437f221 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkApiCompilerDiffTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkApiCompilerDiffTest.kt
@@ -51,6 +51,7 @@
         "com/mysdk/ParcelableResponse.java",
         "com/mysdk/ParcelableStackFrame.java",
         "com/mysdk/ParcelableInnerValue.java",
+        "com/mysdk/ParcelableRequestFlag.java",
         "com/mysdk/PrivacySandboxThrowableParcel.java",
     )
 }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
index 154583e..7e422f7 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
@@ -83,12 +83,19 @@
     val myInterface: MyInterface,
     val myUiInterface: MyUiInterface,
     val activityLauncher: SdkActivityLauncher,
+    val flag: RequestFlag,
 )
 
 @PrivacySandboxValue
 data class InnerValue(val numbers: List<Int>, val maybeNumber: Int?)
 
 @PrivacySandboxValue
+enum class RequestFlag {
+    SLOW,
+    FAST,
+}
+
+@PrivacySandboxValue
 data class Response(
     val response: String,
     val mySecondInterface: MySecondInterface,
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestConverter.kt
index 1070100..7bc3b48 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestConverter.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestConverter.kt
@@ -16,7 +16,8 @@
                 myInterface = (parcelable.myInterface as MyInterfaceStubDelegate).delegate,
                 myUiInterface = (parcelable.myUiInterface.binder as
                         MyUiInterfaceStubDelegate).delegate,
-                activityLauncher = SdkActivityLauncherAndBinderWrapper(parcelable.activityLauncher))
+                activityLauncher = SdkActivityLauncherAndBinderWrapper(parcelable.activityLauncher),
+                flag = RequestFlagConverter(context).fromParcelable(parcelable.flag))
         return annotatedValue
     }
 
@@ -33,6 +34,7 @@
                 MyUiInterfaceStubDelegate(annotatedValue.myUiInterface, context))
         parcelable.activityLauncher =
                 SdkActivityLauncherAndBinderWrapper.getLauncherInfo(annotatedValue.activityLauncher)
+        parcelable.flag = RequestFlagConverter(context).toParcelable(annotatedValue.flag)
         return parcelable
     }
 }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt
new file mode 100644
index 0000000..c905fff
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt
@@ -0,0 +1,16 @@
+package com.mysdk
+
+import android.content.Context
+
+public class RequestFlagConverter(
+    public val context: Context,
+) {
+    public fun fromParcelable(parcelable: ParcelableRequestFlag): RequestFlag =
+            RequestFlag.entries[parcelable.variant_ordinal]
+
+    public fun toParcelable(annotatedValue: RequestFlag): ParcelableRequestFlag {
+        val parcelable = ParcelableRequestFlag()
+        parcelable.variant_ordinal = annotatedValue.ordinal
+        return parcelable
+    }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
index 746878b..096aed8 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
@@ -33,7 +33,6 @@
 import androidx.privacysandbox.tools.core.generator.ThrowableParcelConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.ValueFileGenerator
-import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.model.containsSdkActivityLauncher
 import androidx.privacysandbox.tools.core.model.getOnlyService
@@ -180,13 +179,10 @@
         val valueFileGenerator = ValueFileGenerator()
         val valueConverterFileGenerator =
             ValueConverterFileGenerator(binderCodeConverter, GenerationTarget.CLIENT)
-        api.values
-            // TODO(b/323369085): Generate value converters for enum classes
-            .filterIsInstance<AnnotatedDataClass>()
-            .forEach {
-                valueFileGenerator.generate(it).writeTo(output)
-                valueConverterFileGenerator.generate(it).writeTo(output)
-            }
+        api.values.forEach {
+            valueFileGenerator.generate(it).writeTo(output)
+            valueConverterFileGenerator.generate(it).writeTo(output)
+        }
     }
 
     private fun generateCoreLibInfoConverters(api: ParsedApi, output: File) {
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
index 229f25e..75d7929 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
@@ -52,6 +52,7 @@
             if (classNode.isAnnotatedWith<PrivacySandboxService>()) {
                 services.add(parseKotlinMetadata(classNode))
             }
+            // TODO(b/323369085): Validate that enum variants don't have methods
             if (classNode.isAnnotatedWith<PrivacySandboxValue>()) {
                 values.add(parseKotlinMetadata(classNode))
             }
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
index ebfb05b..2375bfd 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
@@ -17,6 +17,7 @@
 package androidx.privacysandbox.tools.apigenerator.parser
 
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import androidx.privacysandbox.tools.core.model.Method
@@ -80,14 +81,53 @@
 
     private fun parseValue(value: KmClass): AnnotatedValue {
         val type = parseClassName(value.name)
+        val isEnum = value.kind == ClassKind.ENUM_CLASS
 
-        if (!value.isData) {
+        if (!value.isData && !isEnum) {
             throw PrivacySandboxParsingException(
-                "${type.qualifiedName} is not a Kotlin data class but it's annotated with " +
-                    "@PrivacySandboxValue."
+                "${type.qualifiedName} is not a Kotlin data class or enum class but it's " +
+                    "annotated with @PrivacySandboxValue."
             )
         }
-        return AnnotatedDataClass(type, parseProperties(type, value))
+        val superTypes = value.supertypes.asSequence().map { it.classifier }
+            .filterIsInstance<KmClassifier.Class>()
+            .map { it.name }
+            .filter { it !in listOf("kotlin/Enum", "kotlin/Any") }
+            .map { parseClassName(it) }.toList()
+        if (superTypes.isNotEmpty()) {
+            throw PrivacySandboxParsingException(
+                "Error in ${type.qualifiedName}: values annotated with @PrivacySandboxValue may " +
+                    "not inherit other types (${
+                        superTypes.joinToString(limit = 3) { it.simpleName }
+                    })"
+            )
+        }
+
+        return if (value.isData) {
+            AnnotatedDataClass(type, parseProperties(type, value))
+        } else {
+            validateEnum(value, type)
+            AnnotatedEnumClass(type, value.enumEntries.toList())
+        }
+    }
+
+    private fun validateEnum(value: KmClass, type: Type) {
+        if (value.properties.isNotEmpty()) {
+            throw PrivacySandboxParsingException(
+                "Error in ${type.qualifiedName}: enum classes annotated with " +
+                    "@PrivacySandboxValue may not declare properties (${
+                        value.properties.joinToString(limit = 3) { it.name }
+                    })"
+            )
+        }
+        if (value.functions.isNotEmpty()) {
+            throw PrivacySandboxParsingException(
+                "Error in ${type.qualifiedName}: enum classes annotated with " +
+                    "@PrivacySandboxValue may not declare methods (${
+                        value.functions.joinToString(limit = 3) { it.name }
+                    })"
+            )
+        }
     }
 
     /** Parses properties and sorts them based on the order of constructor parameters. */
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
index 7907500..2398d20 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
@@ -27,6 +27,7 @@
         "com/sdkwithcallbacks/ISdkCallback.java",
         "com/sdkwithcallbacks/ISdkService.java",
         "com/sdkwithcallbacks/ParcelableResponse.java",
+        "com/sdkwithcallbacks/ParcelableMyEnum.java",
         "com/sdkwithcallbacks/IMyUiInterface.java",
         "com/sdkwithcallbacks/IMyUiInterfaceCoreLibInfoAndBinderWrapper.java"
     )
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/ValuesApiGeneratorDiffTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/ValuesApiGeneratorDiffTest.kt
index 03f4de0..d800ba71 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/ValuesApiGeneratorDiffTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/ValuesApiGeneratorDiffTest.kt
@@ -31,6 +31,7 @@
         "com/sdkwithvalues/ParcelableInnerSdkValue.java",
         "com/sdkwithvalues/ParcelableSdkRequest.java",
         "com/sdkwithvalues/ParcelableSdkResponse.java",
+        "com/sdkwithvalues/ParcelableRequestFlag.java",
         "com/sdkwithvalues/ICancellationSignal.java",
         "com/sdkwithvalues/ParcelableStackFrame.java",
         "com/sdkwithvalues/PrivacySandboxThrowableParcel.java",
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
index 7901798..0a9fc51 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.privacysandbox.tools.apigenerator.mergedClasspath
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.Method
 import androidx.privacysandbox.tools.core.model.Parameter
@@ -74,7 +75,10 @@
                     |data class PayloadResponse(val url: String)
                     |
                     |@PrivacySandboxValue
-                    |data class PayloadRequest(val type: PayloadType)
+                    |data class PayloadRequest(val type: PayloadType, val option: PayloadOption)
+                    |
+                    |@PrivacySandboxValue
+                    |enum class PayloadOption { SHAKEN, STIRRED }
                     |
                     |@PrivacySandboxCallback
                     |interface CustomCallback {
@@ -83,6 +87,10 @@
                 """.trimMargin(),
         )
 
+        val expectedPayloadOption = AnnotatedEnumClass(
+            type = Type("com.mysdk", "PayloadOption"),
+            variants = listOf("SHAKEN", "STIRRED")
+        )
         val expectedPayloadType = AnnotatedDataClass(
             type = Type("com.mysdk", "PayloadType"),
             properties = listOf(
@@ -94,6 +102,7 @@
             type = Type("com.mysdk", "PayloadRequest"),
             properties = listOf(
                 ValueProperty("type", expectedPayloadType.type),
+                ValueProperty("option", expectedPayloadOption.type)
             )
         )
         val expectedPayloadResponse = AnnotatedDataClass(
@@ -179,19 +188,19 @@
         )
         val expectedCallback = AnnotatedInterface(
             type = Type(packageName = "com.mysdk", simpleName = "CustomCallback"),
-                methods = listOf(
-                    Method(
-                        name = "onComplete",
-                        parameters = listOf(
-                            Parameter(
-                                "status",
-                                Types.int
-                            ),
+            methods = listOf(
+                Method(
+                    name = "onComplete",
+                    parameters = listOf(
+                        Parameter(
+                            "status",
+                            Types.int
                         ),
-                        returnType = Types.unit,
-                        isSuspend = false,
                     ),
-                )
+                    returnType = Types.unit,
+                    isSuspend = false,
+                ),
+            )
         )
 
         val actualApi = compileAndParseApi(source)
@@ -200,6 +209,7 @@
             expectedPayloadType,
             expectedPayloadRequest,
             expectedPayloadResponse,
+            expectedPayloadOption,
         )
         assertThat(actualApi.callbacks).containsExactly(expectedCallback)
         assertThat(actualApi.interfaces).containsExactlyElementsIn(expectedInterfaces)
@@ -408,7 +418,7 @@
         assertThrows<PrivacySandboxParsingException> {
             compileAndParseApi(source)
         }.hasMessageThat().contains(
-            "com.mysdk.Value is not a Kotlin data class but it's annotated with " +
+            "com.mysdk.Value is not a Kotlin data class or enum class but it's annotated with " +
                 "@PrivacySandboxValue"
         )
     }
@@ -430,7 +440,7 @@
         assertThrows<PrivacySandboxParsingException> {
             compileAndParseApi(source)
         }.hasMessageThat().contains(
-            "com.mysdk.Value is not a Kotlin data class but it's annotated with " +
+            "com.mysdk.Value is not a Kotlin data class or enum class but it's annotated with " +
                 "@PrivacySandboxValue"
         )
     }
@@ -494,6 +504,83 @@
         )
     }
 
+    @Test
+    fun enumClassWithMethods_throws() {
+        val source = Source.kotlin(
+            "com/mysdk/TestSandboxSdk.kt", """
+                    package com.mysdk
+                    import androidx.privacysandbox.tools.PrivacySandboxService
+                    import androidx.privacysandbox.tools.PrivacySandboxValue
+                    @PrivacySandboxService
+                    interface MySdk
+                    @PrivacySandboxValue
+                    enum class MyEnum {
+                       FOO,
+                       BAR;
+                       fun enumMethod() = "should throw"
+                    }
+                """
+        )
+
+        assertThrows<PrivacySandboxParsingException> {
+            compileAndParseApi(source)
+        }.hasMessageThat().contains(
+            "Error in com.mysdk.MyEnum: enum classes annotated with " +
+                "@PrivacySandboxValue may not declare methods (enumMethod)"
+        )
+    }
+
+    @Test
+    fun enumClassWithFields_throws() {
+        val source = Source.kotlin(
+            "com/mysdk/TestSandboxSdk.kt", """
+                    package com.mysdk
+                    import androidx.privacysandbox.tools.PrivacySandboxService
+                    import androidx.privacysandbox.tools.PrivacySandboxValue
+                    @PrivacySandboxService
+                    interface MySdk
+                    @PrivacySandboxValue
+                    enum class MyEnum(val enumField: Int) {
+                       FOO(123),
+                       BAR(456),
+                    }
+                """
+        )
+
+        assertThrows<PrivacySandboxParsingException> {
+            compileAndParseApi(source)
+        }.hasMessageThat().contains(
+            "Error in com.mysdk.MyEnum: enum classes annotated with @PrivacySandboxValue may not " +
+                "declare properties (enumField)"
+        )
+    }
+
+    @Test
+    fun enumClassImplementingInterface_throws() {
+        val source = Source.kotlin(
+            "com/mysdk/TestSandboxSdk.kt", """
+                    package com.mysdk
+                    import androidx.privacysandbox.tools.PrivacySandboxService
+                    import androidx.privacysandbox.tools.PrivacySandboxValue
+                    @PrivacySandboxService
+                    interface MySdk
+                    interface MyCustomInterface
+                    @PrivacySandboxValue
+                    enum class MyEnum : MyCustomInterface {
+                       FOO,
+                       BAR,
+                    }
+                """
+        )
+
+        assertThrows<PrivacySandboxParsingException> {
+            compileAndParseApi(source)
+        }.hasMessageThat().contains(
+            "Error in com.mysdk.MyEnum: values annotated with @PrivacySandboxValue " +
+                "may not inherit other types (MyCustomInterface)"
+        )
+    }
+
     private fun compileAndParseApi(vararg sources: Source): ParsedApi {
         val classpath = mergedClasspath(assertCompiles(sources.toList()))
         return ApiStubParser.parse(classpath)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
index 5c45597..5c2718b 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
@@ -26,7 +26,10 @@
 }
 
 @PrivacySandboxValue
-data class Response(val response: String, val uiInterface: MyUiInterface)
+data class Response(val response: String, val uiInterface: MyUiInterface, val myEnum: MyEnum)
+
+@PrivacySandboxValue
+enum class MyEnum { FLIP, FLOP }
 
 @PrivacySandboxInterface
 interface MyInterface {
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnum.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnum.kt
new file mode 100644
index 0000000..1e0af70
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnum.kt
@@ -0,0 +1,6 @@
+package com.sdkwithcallbacks
+
+public enum class MyEnum {
+    FLIP,
+    FLOP,
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt
new file mode 100644
index 0000000..9554eaf
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt
@@ -0,0 +1,12 @@
+package com.sdkwithcallbacks
+
+public object MyEnumConverter {
+    public fun fromParcelable(parcelable: ParcelableMyEnum): MyEnum =
+            MyEnum.entries[parcelable.variant_ordinal]
+
+    public fun toParcelable(annotatedValue: MyEnum): ParcelableMyEnum {
+        val parcelable = ParcelableMyEnum()
+        parcelable.variant_ordinal = annotatedValue.ordinal
+        return parcelable
+    }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt
index a449083..752bbeb 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt
@@ -3,4 +3,5 @@
 public data class Response(
     public val response: String,
     public val uiInterface: MyUiInterface,
+    public val myEnum: MyEnum,
 )
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt
index e5105f8..4a42a82 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt
@@ -5,7 +5,8 @@
         val annotatedValue = Response(
                 response = parcelable.response,
                 uiInterface = MyUiInterfaceClientProxy(parcelable.uiInterface.binder,
-                        parcelable.uiInterface.coreLibInfo))
+                        parcelable.uiInterface.coreLibInfo),
+                myEnum = com.sdkwithcallbacks.MyEnumConverter.fromParcelable(parcelable.myEnum))
         return annotatedValue
     }
 
@@ -15,6 +16,7 @@
         parcelable.uiInterface =
                 IMyUiInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable((annotatedValue.uiInterface
                 as MyUiInterfaceClientProxy).coreLibInfo, annotatedValue.uiInterface.remote)
+        parcelable.myEnum = com.sdkwithcallbacks.MyEnumConverter.toParcelable(annotatedValue.myEnum)
         return parcelable
     }
 }
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
index ad46445..2b80648 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
@@ -32,12 +32,19 @@
 )
 
 @PrivacySandboxValue
+enum class RequestFlag {
+    UP,
+    DOWN,
+}
+
+@PrivacySandboxValue
 data class SdkRequest(
     val id: Long,
     val innerValue: InnerSdkValue,
     val maybeInnerValue: InnerSdkValue?,
     val moreValues: List<InnerSdkValue>,
     val activityLauncher: SdkActivityLauncher,
+    val requestFlag: RequestFlag,
 )
 
 @PrivacySandboxValue
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt
new file mode 100644
index 0000000..9a969f5
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt
@@ -0,0 +1,6 @@
+package com.sdkwithvalues
+
+public enum class RequestFlag {
+    UP,
+    DOWN,
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt
new file mode 100644
index 0000000..adcb755
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt
@@ -0,0 +1,12 @@
+package com.sdkwithvalues
+
+public object RequestFlagConverter {
+    public fun fromParcelable(parcelable: ParcelableRequestFlag): RequestFlag =
+            RequestFlag.entries[parcelable.variant_ordinal]
+
+    public fun toParcelable(annotatedValue: RequestFlag): ParcelableRequestFlag {
+        val parcelable = ParcelableRequestFlag()
+        parcelable.variant_ordinal = annotatedValue.ordinal
+        return parcelable
+    }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequest.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequest.kt
index 65b2b06..a73c428 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequest.kt
@@ -8,4 +8,5 @@
     public val maybeInnerValue: InnerSdkValue?,
     public val moreValues: List<InnerSdkValue>,
     public val activityLauncher: SdkActivityLauncher,
+    public val requestFlag: RequestFlag,
 )
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequestConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequestConverter.kt
index 297867f..63401e6 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequestConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkRequestConverter.kt
@@ -13,7 +13,9 @@
                         com.sdkwithvalues.InnerSdkValueConverter.fromParcelable(notNullValue) },
                 moreValues = parcelable.moreValues.map {
                         com.sdkwithvalues.InnerSdkValueConverter.fromParcelable(it) }.toList(),
-                activityLauncher = getLocalOrProxyLauncher(parcelable.activityLauncher))
+                activityLauncher = getLocalOrProxyLauncher(parcelable.activityLauncher),
+                requestFlag =
+                        com.sdkwithvalues.RequestFlagConverter.fromParcelable(parcelable.requestFlag))
         return annotatedValue
     }
 
@@ -27,6 +29,8 @@
         parcelable.moreValues = annotatedValue.moreValues.map {
                 com.sdkwithvalues.InnerSdkValueConverter.toParcelable(it) }.toTypedArray()
         parcelable.activityLauncher = toBinder(annotatedValue.activityLauncher)
+        parcelable.requestFlag =
+                com.sdkwithvalues.RequestFlagConverter.toParcelable(annotatedValue.requestFlag)
         return parcelable
     }
 }
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
index f95621c..e516dcf 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
@@ -24,6 +24,7 @@
 import androidx.privacysandbox.tools.core.generator.poet.AidlTypeKind
 import androidx.privacysandbox.tools.core.generator.poet.AidlTypeSpec
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import androidx.privacysandbox.tools.core.model.Method
@@ -91,8 +92,7 @@
 
     private fun generateAidlContent(): List<AidlFileSpec> {
         // TODO(b/323369085): Generate AIDL content for enum classes
-        val values = api.values.filterIsInstance<AnnotatedDataClass>()
-            .map(::generateValue)
+        val values = api.values.map(::generateValue)
         val service = aidlInterface(api.getOnlyService())
         val customCallbacks = api.callbacks.flatMap(::aidlInterface)
         val interfaces = api.interfaces.flatMap(::aidlInterface)
@@ -219,13 +219,18 @@
         }
     }
 
-    private fun generateValue(value: AnnotatedDataClass): AidlFileSpec {
-        return aidlParcelable(value.aidlType().innerType) {
-            for (property in value.properties) {
-                addProperty(property.name, getAidlTypeDeclaration(property.type))
+    private fun generateValue(value: AnnotatedValue) =
+        aidlParcelable(value.aidlType().innerType) {
+            when (value) {
+                is AnnotatedEnumClass ->
+                    addProperty("variant_ordinal", getAidlTypeDeclaration(Types.int))
+
+                is AnnotatedDataClass ->
+                    for (property in value.properties) {
+                        addProperty(property.name, getAidlTypeDeclaration(property.type))
+                    }
             }
         }
-    }
 
     private fun getAidlFile(rootPath: Path, aidlSource: AidlFileSpec) = Paths.get(
         rootPath.toString(),
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
index 3fd3c73..e326d56 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
@@ -20,6 +20,8 @@
 import androidx.privacysandbox.tools.core.generator.SpecNames.contextClass
 import androidx.privacysandbox.tools.core.generator.SpecNames.contextPropertyName
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
+import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import androidx.privacysandbox.tools.core.model.ValueProperty
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FileSpec
@@ -42,7 +44,7 @@
         const val fromParcelableMethodName = "fromParcelable"
     }
 
-    fun generate(value: AnnotatedDataClass) =
+    fun generate(value: AnnotatedValue) =
         FileSpec.builder(
             value.converterNameSpec().packageName,
             value.converterNameSpec().simpleName
@@ -51,7 +53,7 @@
             addType(generateConverter(value))
         }
 
-    private fun generateConverter(value: AnnotatedDataClass): TypeSpec {
+    private fun generateConverter(value: AnnotatedValue): TypeSpec {
         if (target == SERVER) {
             return TypeSpec.classBuilder(value.converterNameSpec()).build {
                 primaryConstructor(
@@ -70,12 +72,19 @@
         }
     }
 
-    private fun generateToParcelable(value: AnnotatedDataClass) =
+    private fun generateToParcelable(value: AnnotatedValue) =
         FunSpec.builder(toParcelableMethodName).build {
             addParameter("annotatedValue", value.type.poetTypeName())
             returns(value.parcelableNameSpec())
             addStatement("val parcelable = %T()", value.parcelableNameSpec())
-            value.properties.map(::generateToParcelablePropertyConversion).forEach(::addCode)
+            when (value) {
+                is AnnotatedDataClass ->
+                    value.properties.map(::generateToParcelablePropertyConversion)
+                        .forEach(::addCode)
+
+                is AnnotatedEnumClass ->
+                    addStatement("parcelable.variant_ordinal = annotatedValue.ordinal")
+            }
             addStatement("return parcelable")
         }
 
@@ -90,17 +99,31 @@
             )
         }
 
-    private fun generateFromParcelable(value: AnnotatedDataClass) =
-        FunSpec.builder(fromParcelableMethodName).build {
-            addParameter("parcelable", value.parcelableNameSpec())
-            returns(value.type.poetTypeName())
-            val parameters = value.properties.map(::generateFromParcelablePropertyConversion)
-            addStatement {
-                add("val annotatedValue = %T(\n", value.type.poetTypeName())
-                add(parameters.joinToCode(separator = ",\n"))
-                add(")")
-            }
-            addStatement("return annotatedValue")
+    private fun generateFromParcelable(value: AnnotatedValue) =
+        when (value) {
+            is AnnotatedDataClass ->
+                FunSpec.builder(fromParcelableMethodName).build {
+                    addParameter("parcelable", value.parcelableNameSpec())
+                    returns(value.type.poetTypeName())
+                    val parameters =
+                        value.properties.map(::generateFromParcelablePropertyConversion)
+                    addStatement {
+                        add("val annotatedValue = %T(\n", value.type.poetTypeName())
+                        add(parameters.joinToCode(separator = ",\n"))
+                        add(")")
+                    }
+                    addStatement("return annotatedValue")
+                }
+
+            is AnnotatedEnumClass ->
+                FunSpec.builder(fromParcelableMethodName).build {
+                    addParameter("parcelable", value.parcelableNameSpec())
+                    returns(value.type.poetTypeName())
+                    addStatement(
+                        "return %T.entries[parcelable.variant_ordinal]",
+                        value.type.poetTypeName()
+                    )
+                }
         }
 
     private fun generateFromParcelablePropertyConversion(property: ValueProperty) =
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
index 400849c..62ed768 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
@@ -17,6 +17,8 @@
 package androidx.privacysandbox.tools.core.generator
 
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
+import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.PropertySpec
@@ -26,19 +28,27 @@
  * Generates a file that defines a previously declared SDK value.
  */
 class ValueFileGenerator {
-    fun generate(value: AnnotatedDataClass) =
+    fun generate(value: AnnotatedValue) =
         FileSpec.builder(value.type.packageName, value.type.simpleName).build {
             addCommonSettings()
             addType(generateValue(value))
         }
 
-    private fun generateValue(value: AnnotatedDataClass) =
-        TypeSpec.classBuilder(value.type.poetClassName()).build {
-            addModifiers(KModifier.DATA)
-            primaryConstructor(value.properties.map {
-                PropertySpec.builder(it.name, it.type.poetTypeName())
-                    .mutable(false)
-                    .build()
-            })
+    private fun generateValue(value: AnnotatedValue) =
+        when (value) {
+            is AnnotatedDataClass ->
+                TypeSpec.classBuilder(value.type.poetClassName()).build {
+                    addModifiers(KModifier.DATA)
+                    primaryConstructor(value.properties.map {
+                        PropertySpec.builder(it.name, it.type.poetTypeName())
+                            .mutable(false)
+                            .build()
+                    })
+                }
+
+            is AnnotatedEnumClass ->
+                TypeSpec.enumBuilder(value.type.poetClassName()).build {
+                    value.variants.forEach(::addEnumConstant)
+                }
         }
 }
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
index 4dd08cb..cf22612 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
@@ -17,6 +17,7 @@
 package androidx.privacysandbox.tools.core.generator
 
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.Method
 import androidx.privacysandbox.tools.core.model.Parameter
@@ -36,6 +37,10 @@
 class AidlValueGeneratorTest {
     @Test
     fun generate() {
+        val innerEnum = AnnotatedEnumClass(
+            Type(packageName = "com.mysdk", simpleName = "InnerEnum"),
+            listOf("ONE, TWO, THREE")
+        )
         val innerValue = AnnotatedDataClass(
             Type(packageName = "com.mysdk", simpleName = "InnerValue"),
             listOf(
@@ -43,6 +48,7 @@
                 ValueProperty("booleanProperty", Types.boolean),
                 ValueProperty("longProperty", Types.long),
                 ValueProperty("maybeFloatProperty", Types.float.asNullable()),
+                ValueProperty("enumProperty", innerEnum.type)
             )
         )
         val outerValue = AnnotatedDataClass(
@@ -103,7 +109,7 @@
                     )
                 )
             ),
-            values = setOf(innerValue, outerValue)
+            values = setOf(innerEnum, innerValue, outerValue)
         )
 
         val (aidlGeneratedSources, javaGeneratedSources) = AidlTestHelper.runGenerator(api)
@@ -112,6 +118,7 @@
                 "com.mysdk" to "IMySdk",
                 "com.mysdk" to "ParcelableOuterValue",
                 "com.mysdk" to "ParcelableInnerValue",
+                "com.mysdk" to "ParcelableInnerEnum",
                 "com.mysdk" to "IUnitTransactionCallback",
                 "com.mysdk" to "IOuterValueTransactionCallback",
                 "com.mysdk" to "IListOuterValueTransactionCallback",
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerEnum.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerEnum.aidl
new file mode 100644
index 0000000..923fa5f
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerEnum.aidl
@@ -0,0 +1,5 @@
+package com.mysdk;
+
+parcelable ParcelableInnerEnum {
+    int variant_ordinal;
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerValue.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerValue.aidl
index c8487616..cf49d62 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerValue.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ParcelableInnerValue.aidl
@@ -1,6 +1,9 @@
 package com.mysdk;
 
+import com.mysdk.ParcelableInnerEnum;
+
 parcelable ParcelableInnerValue {
+    ParcelableInnerEnum enumProperty;
     boolean booleanProperty;
     float[] maybeFloatProperty;
     int intProperty;
diff --git a/privacysandbox/ui/integration-tests/mediateesdkprovider/src/main/java/androidx/privacysandbox/ui/integration/mediateesdkprovider/MediateeSdkApi.kt b/privacysandbox/ui/integration-tests/mediateesdkprovider/src/main/java/androidx/privacysandbox/ui/integration/mediateesdkprovider/MediateeSdkApi.kt
index 91010f2..13776ed 100644
--- a/privacysandbox/ui/integration-tests/mediateesdkprovider/src/main/java/androidx/privacysandbox/ui/integration/mediateesdkprovider/MediateeSdkApi.kt
+++ b/privacysandbox/ui/integration-tests/mediateesdkprovider/src/main/java/androidx/privacysandbox/ui/integration/mediateesdkprovider/MediateeSdkApi.kt
@@ -28,46 +28,25 @@
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
-import android.provider.Settings
 import android.util.Log
 import android.view.View
-import android.view.ViewGroup
-import android.webkit.WebView
 import androidx.privacysandbox.ui.core.SandboxedUiAdapter
-import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+import androidx.privacysandbox.ui.integration.testaidl.IMediateeSdkApi
 import androidx.privacysandbox.ui.provider.toCoreLibInfo
 import java.util.concurrent.Executor
 
-class MediateeSdkApi(val sdkContext: Context) : ISdkApi.Stub() {
+class MediateeSdkApi(val sdkContext: Context) : IMediateeSdkApi.Stub() {
     private val handler = Handler(Looper.getMainLooper())
-    private lateinit var bannerAd: BannerAd
 
-    override fun loadAd(
-        isWebView: Boolean,
-        text: String,
-        withSlowDraw: Boolean,
-        isViewMediated: Boolean
-    ): Bundle {
-        bannerAd = BannerAd(isWebView, withSlowDraw, text)
+    override fun loadTestAdWithWaitInsideOnDraw(count: Int): Bundle {
+        var bannerAd = BannerAd(count)
         return bannerAd.toCoreLibInfo(sdkContext)
     }
 
-    override fun requestResize(width: Int, height: Int) {
-        bannerAd.requestResize(width, height)
-    }
-
-    private fun isAirplaneModeOn(): Boolean {
-        return Settings.Global.getInt(
-            sdkContext.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
-    }
-
     // TODO(b/321830843) : Move logic to a helper file
     private inner class BannerAd(
-        private val isWebView: Boolean,
-        private val withSlowDraw: Boolean,
-        private val text: String
-    ) :
-        SandboxedUiAdapter {
+        private val count: Int
+    ) : SandboxedUiAdapter {
         lateinit var sessionClientExecutor: Executor
         lateinit var sessionClient: SandboxedUiAdapter.SessionClient
         override fun openSession(
@@ -83,39 +62,16 @@
             sessionClient = client
             handler.post(Runnable lambda@{
                 Log.d(TAG, "Session requested")
-                lateinit var adView: View
-                if (isWebView) {
-                    // To test error cases.
-                    if (isAirplaneModeOn()) {
-                        clientExecutor.execute {
-                            client.onSessionError(
-                                Throwable("Cannot load WebView in airplane mode.")
-                            )
-                        }
-                        return@lambda
-                    }
-                    val webView = WebView(context)
-                    webView.loadUrl(AD_URL)
-                    webView.layoutParams = ViewGroup.LayoutParams(
-                        initialWidth, initialHeight
-                    )
-                    adView = webView
-                } else {
-                    adView = TestView(context, withSlowDraw, text)
-                }
+                var adView = TestView(context, count)
                 clientExecutor.execute {
                     client.onSessionOpened(BannerAdSession(adView))
                 }
             })
         }
 
-        fun requestResize(width: Int, height: Int) {
-            sessionClientExecutor.execute {
-                sessionClient.onResizeRequested(width, height)
-            }
-        }
-
-        private inner class BannerAdSession(private val adView: View) : SandboxedUiAdapter.Session {
+        private inner class BannerAdSession(
+            private val adView: View
+        ) : SandboxedUiAdapter.Session {
             override val view: View
                 get() = adView
 
@@ -142,16 +98,14 @@
     // TODO(b/321830843) : Move logic to a helper file
     private inner class TestView(
         context: Context,
-        private val withSlowDraw: Boolean,
-        private val text: String
+        private val count: Int
     ) : View(context) {
 
         @SuppressLint("BanThreadSleep")
         override fun onDraw(canvas: Canvas) {
             // We are adding sleep to test the synchronization of the app and the sandbox view's
             // size changes.
-            if (withSlowDraw)
-                Thread.sleep(500)
+            Thread.sleep(500)
             super.onDraw(canvas)
 
             val paint = Paint()
@@ -160,6 +114,7 @@
                 Color.rgb((0..255).random(), (0..255).random(), (0..255).random())
             )
 
+            val text = "Mediated Ad #$count"
             canvas.drawText(text, 75F, 75F, paint)
 
             setOnClickListener {
diff --git a/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/IMediateeSdkApi.aidl b/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/IMediateeSdkApi.aidl
new file mode 100644
index 0000000..30a2384
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/IMediateeSdkApi.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.privacysandbox.ui.integration.testaidl;
+
+import android.os.Bundle;
+
+interface IMediateeSdkApi {
+    Bundle loadTestAdWithWaitInsideOnDraw(int count);
+}
diff --git a/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/ISdkApi.aidl b/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/ISdkApi.aidl
index d72bd62..04d34ab 100644
--- a/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/ISdkApi.aidl
+++ b/privacysandbox/ui/integration-tests/testaidl/src/main/aidl/androidx/privacysandbox/ui/integration/testaidl/ISdkApi.aidl
@@ -19,6 +19,10 @@
 import android.os.Bundle;
 
 interface ISdkApi {
-    Bundle loadAd(boolean isWebView, String text, boolean shouldWaitInsideOnDraw, boolean isViewMediated);
+    Bundle loadWebViewAd();
+    Bundle loadLocalWebViewAd();
+    Bundle loadTestAd(String text);
+    Bundle loadTestAdWithWaitInsideOnDraw(String text);
+    Bundle loadMediatedTestAd(int count);
     void requestResize(int width, int height);
 }
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index 2f8c8b7..d8e1f47 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import android.os.ext.SdkExtensions
 import android.util.Log
+import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
 import android.widget.LinearLayout
@@ -33,6 +34,7 @@
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
 import androidx.privacysandbox.ui.client.view.SandboxedSdkView
 import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+import com.google.android.material.switchmaterial.SwitchMaterial
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -41,14 +43,16 @@
     private lateinit var mSdkSandboxManager: SdkSandboxManagerCompat
 
     private var mSdkLoaded = false
+    private lateinit var sdkApi: ISdkApi
 
-    private lateinit var mSandboxedSdkView1: SandboxedSdkView
-    private lateinit var mSandboxedSdkView2: SandboxedSdkView
-    private lateinit var resizableSandboxedSdkView: SandboxedSdkView
-    private lateinit var mNewAdButton: Button
-    private lateinit var mResizeButton: Button
-    private lateinit var mResizeSdkButton: Button
-    private lateinit var mLoadAdButton: Button
+    private lateinit var webViewBannerView: SandboxedSdkView
+    private lateinit var bottomBannerView: SandboxedSdkView
+    private lateinit var resizableBannerView: SandboxedSdkView
+    private lateinit var newAdButton: Button
+    private lateinit var resizeButton: Button
+    private lateinit var resizeSdkButton: Button
+    private lateinit var mediationSwitch: SwitchMaterial
+    private lateinit var localWebViewToggle: SwitchMaterial
 
     // TODO(b/257429573): Remove this line once fixed.
     @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
@@ -72,34 +76,77 @@
             }
         }
     }
+
     private fun onLoadedSdk(sandboxedSdk: SandboxedSdkCompat) {
         Log.i(TAG, "Loaded successfully")
         mSdkLoaded = true
-        val sdkApi = ISdkApi.Stub.asInterface(sandboxedSdk.getInterface())
+        sdkApi = ISdkApi.Stub.asInterface(sandboxedSdk.getInterface())
 
-        mSandboxedSdkView1 = findViewById(R.id.rendered_view)
-        mSandboxedSdkView1.addStateChangedListener(StateChangeListener(mSandboxedSdkView1))
+        webViewBannerView = findViewById(R.id.webview_ad_view)
+        bottomBannerView = SandboxedSdkView(this@MainActivity)
+        resizableBannerView = findViewById(R.id.resizable_ad_view)
+        newAdButton = findViewById(R.id.new_ad_button)
+        resizeButton = findViewById(R.id.resize_button)
+        resizeSdkButton = findViewById(R.id.resize_sdk_button)
+        mediationSwitch = findViewById(R.id.mediation_switch)
+        localWebViewToggle = findViewById(R.id.local_to_internet_switch)
 
-        mSandboxedSdkView2 = SandboxedSdkView(this@MainActivity)
-        mSandboxedSdkView2.addStateChangedListener(StateChangeListener(mSandboxedSdkView2))
-        mSandboxedSdkView2.layoutParams = findViewById<LinearLayout>(
+        loadWebViewBannerAd()
+        loadBottomBannerAd()
+        loadResizableBannerAd()
+    }
+
+    private fun loadWebViewBannerAd() {
+        webViewBannerView.addStateChangedListener(StateChangeListener(webViewBannerView))
+        webViewBannerView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
+            sdkApi.loadLocalWebViewAd()
+        ))
+
+        localWebViewToggle.setOnCheckedChangeListener { _: View, isChecked: Boolean ->
+            if (isChecked) {
+                webViewBannerView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
+                    sdkApi.loadLocalWebViewAd()
+                ))
+            } else {
+                webViewBannerView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
+                    sdkApi.loadWebViewAd()
+                ))
+            }
+        }
+    }
+
+    private fun loadBottomBannerAd() {
+        bottomBannerView.addStateChangedListener(StateChangeListener(bottomBannerView))
+        bottomBannerView.layoutParams = findViewById<LinearLayout>(
             R.id.bottom_banner_container).layoutParams
         runOnUiThread {
-            findViewById<LinearLayout>(R.id.bottom_banner_container).addView(mSandboxedSdkView2)
+            findViewById<LinearLayout>(R.id.bottom_banner_container).addView(bottomBannerView)
         }
+        bottomBannerView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
+            sdkApi.loadTestAd(/*text=*/ "Hey!")
+        ))
+    }
 
-        resizableSandboxedSdkView = findViewById(R.id.new_ad_view)
-        resizableSandboxedSdkView.addStateChangedListener(
-            StateChangeListener(resizableSandboxedSdkView))
-
-        mNewAdButton = findViewById(R.id.new_ad_button)
+    private fun loadResizableBannerAd() {
+        resizableBannerView.addStateChangedListener(
+            StateChangeListener(resizableBannerView))
+        resizableBannerView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
+            sdkApi.loadTestAdWithWaitInsideOnDraw(/*text=*/ "Resizable View")
+        ))
 
         var count = 1
-        var loadMediatedAd = false
-        mNewAdButton.setOnClickListener {
-            resizableSandboxedSdkView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
-                sdkApi.loadAd(/*isWebView=*/ false, /*text=*/ "Ad #$count",
-                    /*withSlowDraw*/ true, loadMediatedAd)))
+        newAdButton.setOnClickListener {
+            if (mediationSwitch.isChecked) {
+                resizableBannerView.setAdapter(
+                    SandboxedUiAdapterFactory.createFromCoreLibInfo(
+                        sdkApi.loadMediatedTestAd(count)
+                ))
+            } else {
+                resizableBannerView.setAdapter(
+                    SandboxedUiAdapterFactory.createFromCoreLibInfo(
+                        sdkApi.loadTestAdWithWaitInsideOnDraw(/*text=*/ "Ad #$count")
+                ))
+            }
             count++
         }
 
@@ -109,51 +156,21 @@
             (currentSize + (100..200).random()) % maxSize
         }
 
-        mResizeButton = findViewById(R.id.resize_button)
-        mResizeButton.setOnClickListener {
-            val newWidth = newSize(resizableSandboxedSdkView.width, maxWidthPixels)
-            val newHeight = newSize(resizableSandboxedSdkView.height, maxHeightPixels)
-            resizableSandboxedSdkView.layoutParams = resizableSandboxedSdkView.layoutParams.apply {
-                width = newWidth
-                height = newHeight
+        resizeButton.setOnClickListener {
+            val newWidth = newSize(resizableBannerView.width, maxWidthPixels)
+            val newHeight = newSize(resizableBannerView.height, maxHeightPixels)
+            resizableBannerView.layoutParams =
+                resizableBannerView.layoutParams.apply {
+                    width = newWidth
+                    height = newHeight
             }
         }
 
-        mResizeSdkButton = findViewById(R.id.resize_sdk_button)
-        mResizeSdkButton.setOnClickListener {
-            val newWidth = newSize(resizableSandboxedSdkView.width, maxWidthPixels)
-            val newHeight = newSize(resizableSandboxedSdkView.height, maxHeightPixels)
+        resizeSdkButton.setOnClickListener {
+            val newWidth = newSize(resizableBannerView.width, maxWidthPixels)
+            val newHeight = newSize(resizableBannerView.height, maxHeightPixels)
             sdkApi.requestResize(newWidth, newHeight)
         }
-
-        mLoadAdButton = findViewById(R.id.load_ad_button)
-        loadAllAds(sdkApi, loadMediatedAd)
-        // TODO(b/323888187): use new ad button with toggling for loading Ads.
-        mLoadAdButton.setOnClickListener {
-            if (loadMediatedAd) {
-                loadMediatedAd = false
-                mLoadAdButton.post { mLoadAdButton.setText("load mediated Ad") }
-            } else {
-                loadMediatedAd = true
-                mLoadAdButton.post { mLoadAdButton.setText("load non-mediated Ad") }
-            }
-            loadAllAds(sdkApi, loadMediatedAd)
-        }
-    }
-
-    private fun loadAllAds(sdkApi: ISdkApi, isViewMediated: Boolean) {
-        mSandboxedSdkView1.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
-            sdkApi.loadAd(/*isWebView=*/ true, /*text=*/ "", /*withSlowDraw*/ false, isViewMediated)
-        ))
-
-        mSandboxedSdkView2.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
-            sdkApi.loadAd(/*isWebView=*/ false, /*text=*/ "Hey!",
-                /*withSlowDraw*/ false, isViewMediated)
-        ))
-
-        resizableSandboxedSdkView.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
-            sdkApi.loadAd(/*isWebView=*/ false, /*text=*/ "Resize view",
-                /*withSlowDraw*/ true, isViewMediated)))
     }
 
     private inner class StateChangeListener(val view: SandboxedSdkView) :
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 0991560..70d6b1c 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -45,8 +45,18 @@
                 android:layout_marginEnd="16dp"
                 android:text="@string/app_name"/>
 
+            <com.google.android.material.switchmaterial.SwitchMaterial
+                android:id="@+id/local_to_internet_switch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/local_to_internet_switch"
+                android:checked="true"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
             <androidx.privacysandbox.ui.client.view.SandboxedSdkView
-                android:id="@+id/rendered_view"
+                android:id="@+id/webview_ad_view"
                 android:background="#FF0000"
                 android:layout_width="match_parent"
                 android:layout_marginStart="16dp"
@@ -56,7 +66,7 @@
                 android:layout_height="400dp" />
 
             <androidx.privacysandbox.ui.client.view.SandboxedSdkView
-                android:id="@+id/new_ad_view"
+                android:id="@+id/resizable_ad_view"
                 android:layout_width="wrap_content"
                 android:layout_height="100dp"
                 android:layout_marginBottom="16dp"
@@ -71,10 +81,11 @@
                 android:orientation="horizontal">
 
                 <com.google.android.material.switchmaterial.SwitchMaterial
-                    android:id="@+id/material_switch"
+                    android:id="@+id/mediation_switch"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="material_switch"
+                    android:text="@string/mediation_switch"
+                    android:checked="false"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent" />
@@ -102,19 +113,6 @@
                     android:text="@string/resizeFromSdk"
                     android:textAllCaps="false" />
             </LinearLayout>
-            <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <Button
-                    android:id="@+id/load_ad_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dp"
-                    android:text="@string/loadMediatedAd"
-                    android:textAllCaps="false" />
-            </LinearLayout>
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
index ee04bed..a961c28 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
@@ -18,5 +18,6 @@
     <string name="app_name">PrivacySandboxUiTestApp</string>
     <string name="long_text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sit amet lacus dignissim, sollicitudin nisl sed, egestas leo. Sed congue vitae nulla vel mattis. Sed porttitor lobortis felis id sollicitudin. Maecenas a venenatis mi. Etiam sapien ipsum, bibendum at congue eget, venenatis laoreet erat. Integer dapibus varius lectus, eu gravida arcu pharetra in. Suspendisse volutpat sit amet ex non rutrum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed quis dui eros. Ut sed magna sit amet nulla iaculis ultrices. Pellentesque fermentum, nibh vel fermentum lacinia, urna nibh dictum risus, id feugiat sapien dolor nec erat. Maecenas augue nibh, sodales eu iaculis ut, volutpat non magna. Donec eget laoreet odio.Sed eget purus id mauris euismod lobortis. Vestibulum suscipit hendrerit rhoncus. Etiam et porttitor justo. Vivamus sodales velit in risus convallis tempor vitae non nunc. Integer lacinia consectetur ipsum, sit amet semper mi posuere eget. Etiam non quam nec sem malesuada viverra. Donec sollicitudin quam metus, at suscipit nisi mattis nec. Nam eu vehicula mauris. Cras nibh massa, interdum eget ante ut, molestie efficitur leo. In aliquet sodales mi vel bibendum. In iaculis neque in laoreet vestibulum. Nullam non interdum lectus. Etiam non ante elit. Vivamus luctus nisi ex, quis facilisis dui pellentesque porttitor. Etiam arcu nisl, porta eu hendrerit vel, porttitor vel turpis. Vestibulum in venenatis elit. Nunc in nisl congue, suscipit massa eu, luctus enim. Donec a fermentum magna, nec commodo purus. Quisque ac nisi et mi pretium porta ut eget nibh. Nulla consequat enim a congue porta. Donec odio magna, elementum in felis sit amet, posuere laoreet urna. Suspendisse ultricies in libero nec volutpat. Vivamus at magna lectus. Sed id metus et tellus suscipit aliquam in at lacus. Ut gravida ultrices augue, quis ultrices lacus ullamcorper ac. Ut fringilla ac quam sit amet pharetra. In non ante consectetur, dapibus ante eu, interdum risus. Nam lobortis blandit nisl ac dapibus. Maecenas vitae est ac odio sollicitudin varius eget quis orci. Mauris vitae ex eget neque tempor faucibus eget vel orci. Morbi eu feugiat lorem. Donec id sem et magna ullamcorper congue. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed laoreet ultrices quam, quis eleifend libero malesuada id. Sed ac sollicitudin diam. Integer sit amet ex ac purus malesuada iaculis at in mauris. Vestibulum egestas velit et sapien volutpat, vel varius augue fringilla. Duis efficitur blandit arcu in suscipit. Maecenas neque purus, finibus vel rhoncus at, pretium ut ipsum.</string>
     <string name="resizeFromSdk">resize from sdk</string>
-    <string name="loadMediatedAd">load mediated Ad</string>
+    <string name="local_to_internet_switch">local webview</string>
+    <string name="mediation_switch">Mediation</string>
 </resources>
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle b/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
index eef7cee..248162d 100644
--- a/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
@@ -55,4 +55,5 @@
     implementation project(':privacysandbox:ui:ui-provider')
     implementation project(':privacysandbox:sdkruntime:sdkruntime-client')
     implementation project(':privacysandbox:ui:ui-client')
+    implementation project(':webkit:webkit')
 }
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/gifs/coloured_rectangle.gif b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/gifs/coloured_rectangle.gif
new file mode 100644
index 0000000..5801e4f
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/gifs/coloured_rectangle.gif
Binary files differ
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/www/webview-test.html b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/www/webview-test.html
new file mode 100644
index 0000000..54cbbd3
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/assets/www/webview-test.html
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 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.
+  -->
+
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        div {
+            white-space: nowrap;
+        }
+
+        iframe {
+            width: 90%;
+        }
+    </style>
+</head>
+<body>
+
+<div>
+    <input type="text" id="input_test" name="input_text">
+</div>
+
+<div>
+    <img src="../gifs/coloured_rectangle.gif" alt="Coloured rectangle repeatable gif">
+</div>
+
+<iframe src="https://www.youtube.com/embed/PzzNuCk-e0Y"></iframe>
+
+<div>
+    This is a long piece of text to allow horizontal scrolling. This is a long piece of text to
+    allow horizontal scrolling. This is a long piece of text to allow horizontal scrolling.
+    This is a long piece of text to allow horizontal scrolling.
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
index 2771a42..05b9468 100644
--- a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
@@ -24,7 +24,6 @@
 import android.graphics.Color
 import android.graphics.Paint
 import android.net.Uri
-import android.os.Build.VERSION.SDK_INT
 import android.os.Bundle
 import android.os.Handler
 import android.os.IBinder
@@ -33,51 +32,44 @@
 import android.util.Log
 import android.view.View
 import android.view.ViewGroup
+import android.webkit.WebResourceRequest
+import android.webkit.WebResourceResponse
+import android.webkit.WebSettings
 import android.webkit.WebView
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
 import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
 import androidx.privacysandbox.ui.client.view.SandboxedSdkView
 import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.privacysandbox.ui.integration.testaidl.IMediateeSdkApi
 import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
 import androidx.privacysandbox.ui.provider.toCoreLibInfo
+import androidx.webkit.WebViewAssetLoader
+import androidx.webkit.WebViewClientCompat
 import java.util.concurrent.Executor
 
 class SdkApi(val sdkContext: Context) : ISdkApi.Stub() {
     private val handler = Handler(Looper.getMainLooper())
     private lateinit var bannerAd: BannerAd
 
-    override fun loadAd(
-        isWebView: Boolean,
-        text: String,
-        withSlowDraw: Boolean,
-        isViewMediated: Boolean
-    ): Bundle {
-        if (!isViewMediated || SDK_INT < UPSIDE_DOWN_CAKE) {
-            bannerAd = BannerAd(isWebView, withSlowDraw, text)
-            return BannerAd(isWebView, withSlowDraw, text).toCoreLibInfo(sdkContext)
-        }
-        val sdkSandboxControllerCompat = SdkSandboxControllerCompat.from(sdkContext)
-        val sandboxedSdks = sdkSandboxControllerCompat.getSandboxedSdks()
-        var mediateeSandboxedSdkCompat: SandboxedSdkCompat? = null
-        sandboxedSdks.forEach {
-            sandboxedSdkCompat ->
-            if (sandboxedSdkCompat.getSdkInfo()?.name.equals(MEDIATEE_SDK)) {
-                mediateeSandboxedSdkCompat = sandboxedSdkCompat
-            }
-        }
-        if (mediateeSandboxedSdkCompat == null) {
-            return BannerAd(isWebView,
-                withSlowDraw,
-                text).toCoreLibInfo(sdkContext)
-        }
-        val mediateeSdkApi = asInterface(mediateeSandboxedSdkCompat!!.getInterface())
-        val bundle = mediateeSdkApi.loadAd(isWebView, text, withSlowDraw, /*isViewMediated=*/true)
-        val view = SandboxedSdkView(sdkContext)
-        val adapter = SandboxedUiAdapterFactory.createFromCoreLibInfo(bundle)
-        view.setAdapter(adapter)
-        bannerAd = BannerAd(isWebView, withSlowDraw, text, view)
-        return bannerAd.toCoreLibInfo(sdkContext)
+    override fun loadWebViewAd(): Bundle {
+        return WebViewBannerAd().toCoreLibInfo(sdkContext)
+    }
+
+    override fun loadLocalWebViewAd(): Bundle {
+        return LocalViewBannerAd().toCoreLibInfo(sdkContext)
+    }
+
+    override fun loadTestAd(text: String): Bundle {
+        return TestBannerAd(text).toCoreLibInfo(sdkContext)
+    }
+
+    override fun loadTestAdWithWaitInsideOnDraw(text: String): Bundle {
+        return TestBannerAdWithWaitInsideOnDraw(text).toCoreLibInfo(sdkContext)
+    }
+
+    override fun loadMediatedTestAd(count: Int): Bundle {
+        return MediatedBannerAd(count).toCoreLibInfo(sdkContext)
     }
 
     override fun requestResize(width: Int, height: Int) {
@@ -89,15 +81,12 @@
             sdkContext.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
     }
 
-    private inner class BannerAd(
-        private val isWebView: Boolean,
-        private val withSlowDraw: Boolean,
-        private val text: String,
-        private val mediatedView: View? = null
-    ) :
-        SandboxedUiAdapter {
+    private abstract inner class BannerAd() : SandboxedUiAdapter {
         lateinit var sessionClientExecutor: Executor
         lateinit var sessionClient: SandboxedUiAdapter.SessionClient
+
+        abstract fun buildAdView(sessionContext: Context): View?
+
         override fun openSession(
             context: Context,
             windowInputToken: IBinder,
@@ -111,36 +100,10 @@
             sessionClient = client
             handler.post(Runnable lambda@{
                 Log.d(TAG, "Session requested")
-                if (mediatedView == null) {
-                    lateinit var adView: View
-                    if (isWebView) {
-                        // To test error cases.
-                        if (isAirplaneModeOn()) {
-                            clientExecutor.execute {
-                                client.onSessionError(
-                                    Throwable("Cannot load WebView in airplane mode.")
-                                )
-                            }
-                            return@lambda
-                        }
-                        val webView = WebView(context)
-                        webView.loadUrl(AD_URL)
-                        webView.layoutParams = ViewGroup.LayoutParams(
-                            initialWidth, initialHeight
-                        )
-                        adView = webView
-                    } else {
-                        adView = TestView(context, withSlowDraw, text)
-                    }
-                    clientExecutor.execute {
-                        Log.i(TAG, "Ad shown without mediation")
-                        client.onSessionOpened(BannerAdSession(adView))
-                    }
-                } else {
-                    clientExecutor.execute {
-                        Log.i(TAG, "Mediated Ad shown")
-                        client.onSessionOpened(BannerAdSession(mediatedView))
-                    }
+                val adView: View = buildAdView(context) ?: return@lambda
+                adView.layoutParams = ViewGroup.LayoutParams(initialWidth, initialHeight)
+                clientExecutor.execute {
+                    client.onSessionOpened(BannerAdSession(adView))
                 }
             })
         }
@@ -175,6 +138,79 @@
         }
     }
 
+    private inner class WebViewBannerAd() : BannerAd() {
+        override fun buildAdView(sessionContext: Context): View? {
+            if (isAirplaneModeOn()) {
+                sessionClientExecutor.execute {
+                    sessionClient.onSessionError(
+                        Throwable("Cannot load WebView in airplane mode.")
+                    )
+                }
+                return null
+            }
+            val webView = WebView(sessionContext)
+            customizeWebViewSettings(webView.settings)
+            webView.loadUrl(GOOGLE_URL)
+            return webView
+        }
+    }
+
+    private inner class LocalViewBannerAd() : BannerAd() {
+        override fun buildAdView(sessionContext: Context): View {
+            val webView = WebView(sessionContext)
+            val assetLoader = WebViewAssetLoader.Builder()
+                .addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(sdkContext))
+                .addPathHandler("/res/", WebViewAssetLoader.ResourcesPathHandler(sdkContext))
+                .build()
+            webView.webViewClient = LocalContentWebViewClient(assetLoader)
+            customizeWebViewSettings(webView.settings)
+            webView.loadUrl(LOCAL_WEB_VIEW_URL)
+            return webView
+        }
+    }
+
+    private inner class TestBannerAd(private val text: String) : BannerAd() {
+        override fun buildAdView(sessionContext: Context): View {
+            return TestView(sessionContext, false, text)
+        }
+    }
+
+    private inner class TestBannerAdWithWaitInsideOnDraw(private val text: String) : BannerAd() {
+        override fun buildAdView(sessionContext: Context): View {
+            return TestView(sessionContext, true, text)
+        }
+    }
+
+    private inner class MediatedBannerAd(private val count: Int) : BannerAd() {
+        val mMediateeSandboxedSdkCompat: SandboxedSdkCompat?
+
+        init {
+            val sdkSandboxControllerCompat = SdkSandboxControllerCompat.from(sdkContext)
+            val sandboxedSdks = sdkSandboxControllerCompat.getSandboxedSdks()
+            mMediateeSandboxedSdkCompat =
+                sandboxedSdks.find {
+                    sandboxedSdkCompat ->
+                    sandboxedSdkCompat.getSdkInfo()?.name.equals(MEDIATEE_SDK)
+                }
+        }
+
+        override fun buildAdView(sessionContext: Context): View {
+            if (mMediateeSandboxedSdkCompat == null) {
+                return TestBannerAdWithWaitInsideOnDraw(
+                    "Mediated SDK is not loaded, this is a mediator Ad!"
+                ).buildAdView(sdkContext)
+            }
+
+            val mediateeSdkApi: IMediateeSdkApi = IMediateeSdkApi.Stub.asInterface(
+                mMediateeSandboxedSdkCompat.getInterface())
+            val bundle = mediateeSdkApi.loadTestAdWithWaitInsideOnDraw(count)
+            val view = SandboxedSdkView(sdkContext)
+            val adapter = SandboxedUiAdapterFactory.createFromCoreLibInfo(bundle)
+            view.setAdapter(adapter)
+            return view
+        }
+    }
+
     private inner class TestView(
         context: Context,
         private val withSlowDraw: Boolean,
@@ -200,7 +236,7 @@
             setOnClickListener {
                 Log.i(TAG, "Click on ad detected")
                 val visitUrl = Intent(Intent.ACTION_VIEW)
-                visitUrl.data = Uri.parse(AD_URL)
+                visitUrl.data = Uri.parse(GOOGLE_URL)
                 visitUrl.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 sdkContext.startActivity(visitUrl)
             }
@@ -211,9 +247,44 @@
         }
     }
 
+    private inner class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) :
+        WebViewClientCompat() {
+        override fun shouldInterceptRequest(
+            view: WebView,
+            request: WebResourceRequest
+        ): WebResourceResponse? {
+            return assetLoader.shouldInterceptRequest(request.url)
+        }
+
+        @Deprecated("Deprecated in Java")
+        override fun shouldInterceptRequest(
+            view: WebView,
+            url: String
+        ): WebResourceResponse? {
+            return assetLoader.shouldInterceptRequest(Uri.parse(url))
+        }
+    }
+
+    private fun customizeWebViewSettings(settings: WebSettings) {
+        settings.javaScriptEnabled = true
+        settings.setGeolocationEnabled(true)
+        settings.setSupportZoom(true)
+        settings.databaseEnabled = true
+        settings.domStorageEnabled = true
+        settings.allowFileAccess = true
+        settings.allowContentAccess = true
+
+        // Default layout behavior for chrome on android.
+        settings.useWideViewPort = true
+        settings.loadWithOverviewMode = true
+        settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
+    }
+
     companion object {
         private const val TAG = "TestSandboxSdk"
-        private const val AD_URL = "https://www.google.com/"
+        private const val GOOGLE_URL = "https://www.google.com/"
+        private const val LOCAL_WEB_VIEW_URL =
+            "https://appassets.androidplatform.net/assets/www/webview-test.html"
         private const val MEDIATEE_SDK =
             "androidx.privacysandbox.ui.integration.mediateesdkprovider"
         private const val UPSIDE_DOWN_CAKE = 34
diff --git a/privacysandbox/ui/ui-client/build.gradle b/privacysandbox/ui/ui-client/build.gradle
index fc455a5..a217c36 100644
--- a/privacysandbox/ui/ui-client/build.gradle
+++ b/privacysandbox/ui/ui-client/build.gradle
@@ -37,8 +37,8 @@
 
     implementation("androidx.lifecycle:lifecycle-common:2.6.2")
     implementation("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
-    implementation("androidx.customview:customview-poolingcontainer:1.0.0-alpha01")
-    implementation project(path: ':privacysandbox:ui:ui-core')
+    implementation("androidx.customview:customview-poolingcontainer:1.0.0")
+    implementation(project(":privacysandbox:ui:ui-core"))
 
     androidTestImplementation(project(":internal-testutils-runtime"))
     androidTestImplementation(libs.junit)
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index 3d98913c..aad4cc0 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -120,6 +120,8 @@
         }
     }
 
+    // TODO(324609478): broken test
+    @Ignore
     @Test
     public void invalidateInAnotherInstance() throws Exception {
         final SampleDatabase db1 = openDatabase(true);
@@ -134,6 +136,8 @@
         assertTrue(changed1.await(3, TimeUnit.SECONDS));
     }
 
+    // TODO(324274513): broken test
+    @Ignore
     @Test
     public void invalidateInAnotherInstanceFts() throws Exception {
         final SampleFtsDatabase db1 = openFtsDatabase(true);
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
index f212956..1aee9857 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
@@ -71,10 +71,10 @@
         path: String
     ): Source? {
         val file = File(path).canonicalFile
-        if (!file.path.startsWith(root.path)) {
+        if (!file.startsWith(root)) {
             return null
         }
-        val relativePath = path.substringAfter(root.canonicalPath + File.separator).let {
+        val relativePath = file.relativeTo(root).path.let {
             val matcher = BY_ROUNDS_PATH_PATTERN.matcher(it)
             if (matcher.find()) {
                 matcher.group(2)
diff --git a/room/room-runtime/build.gradle b/room/room-runtime/build.gradle
index 7972af6..88190e2 100644
--- a/room/room-runtime/build.gradle
+++ b/room/room-runtime/build.gradle
@@ -33,6 +33,7 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("androidx.stableaidl")
+    id("com.google.devtools.ksp")
 }
 
 def nativeEnabled = KmpPlatformsKt.enableNative(project)
@@ -196,6 +197,11 @@
 dependencies {
     lintChecks(project(":room:room-runtime-lint"))
     sqliteSharedArchive(project(":sqlite:sqlite-bundled"))
+    add("kspAndroidAndroidTest", project(path: ":room:room-compiler", configuration: "shadowAndImplementation"))
+}
+
+ksp {
+    arg("room.generateKotlin", "true")
 }
 
 androidx {
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
new file mode 100644
index 0000000..36c22ac
--- /dev/null
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.room
+
+import android.app.ActivityManager
+import android.content.Context
+import android.os.Build
+import androidx.kruth.assertThat
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SdkSuppress
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.test.Test
+import org.junit.Before
+
+class MultiInstanceInvalidationTest {
+    @Entity
+    data class SampleEntity(
+        @PrimaryKey
+        val pk: Int
+    )
+
+    @Database(
+        entities = [SampleEntity::class],
+        version = 1,
+        exportSchema = false
+    )
+    abstract class SampleDatabase : RoomDatabase()
+
+    private lateinit var autoCloseDb: SampleDatabase
+
+    @Suppress("DEPRECATION") // For `getRunningServices()`
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    fun invalidateInAnotherInstanceAutoCloser() {
+        val latch = CountDownLatch(1)
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val manager = context.getSystemService(
+            ActivityManager::class.java
+        )
+        val autoCloseHelper = autoCloseDb.openHelper as AutoClosingRoomOpenHelper
+        val autoCloser = autoCloseHelper.autoCloser
+        autoCloseHelper.writableDatabase
+        // Make sure the service is running.
+        assertThat(manager.getRunningServices(100)).isNotEmpty()
+
+        // Let Room call setAutoCloseCallback
+        val trackerCallback = autoCloser.onAutoCloseCallback
+        autoCloser.setAutoCloseCallback {
+            trackerCallback?.run()
+            // At this point in time InvalidationTracker's callback has run and unbind should have
+            // been invoked.
+            latch.countDown()
+        }
+        latch.await()
+
+        // Make sure the service is no longer running.
+        assertThat(manager.getRunningServices(100)).isEmpty()
+        autoCloseDb.close()
+    }
+
+    @OptIn(ExperimentalRoomApi::class)
+    @Before
+    fun initDb() {
+        val context: Context = ApplicationProvider.getApplicationContext()
+        autoCloseDb = Room.databaseBuilder(
+            context,
+            SampleDatabase::class.java,
+            "MyDb"
+        )
+            .enableMultiInstanceInvalidation()
+            .setAutoCloseTimeout(200, TimeUnit.MILLISECONDS)
+            .build()
+    }
+}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index c1192db..be39682 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -88,6 +88,9 @@
 
     private val trackerLock = Any()
 
+    /** The initialization state for restarting invalidation after auto-close. */
+    private var multiInstanceClientInitState: MultiInstanceClientInitState? = null
+
     init {
         tableIdLookup = mutableMapOf()
         tablesNames = Array(tableNames.size) { id ->
@@ -161,6 +164,11 @@
                 return
             }
 
+            multiInstanceClientInitState?.let {
+                // Start multi-instance invalidation, based in info from the saved initState.
+                startMultiInstanceInvalidation()
+            }
+
             // These actions are not in a transaction because temp_store is not allowed to be
             // performed on a transaction, and recursive_triggers is not affected by transactions.
             database.execSQL("PRAGMA temp_store = MEMORY;")
@@ -174,27 +182,28 @@
 
     private fun onAutoCloseCallback() {
         synchronized(trackerLock) {
+            val isObserverMapEmpty = observerMap.filterNot { it.key.isRemote }.isEmpty()
+            if (multiInstanceInvalidationClient != null && isObserverMapEmpty) {
+                stopMultiInstanceInvalidation()
+            }
             initialized = false
             observedTableTracker.resetTriggerState()
             cleanupStatement?.close()
         }
     }
 
-    internal fun startMultiInstanceInvalidation(
-        context: Context,
-        name: String,
-        serviceIntent: Intent
-    ) {
+    private fun startMultiInstanceInvalidation() {
+        val state = checkNotNull(multiInstanceClientInitState)
         multiInstanceInvalidationClient = MultiInstanceInvalidationClient(
-            context = context,
-            name = name,
-            serviceIntent = serviceIntent,
+            context = state.context,
+            name = state.name,
+            serviceIntent = state.serviceIntent,
             invalidationTracker = this,
             executor = database.queryExecutor
         )
     }
 
-    internal fun stopMultiInstanceInvalidation() {
+    private fun stopMultiInstanceInvalidation() {
         multiInstanceInvalidationClient?.stop()
         multiInstanceInvalidationClient = null
     }
@@ -582,6 +591,22 @@
         )
     }
 
+    internal fun initMultiInstanceInvalidation(
+        context: Context,
+        name: String,
+        serviceIntent: Intent
+    ) {
+        multiInstanceClientInitState = MultiInstanceClientInitState(
+            context = context,
+            name = name,
+            serviceIntent = serviceIntent
+        )
+    }
+
+    internal fun stop() {
+        stopMultiInstanceInvalidation()
+    }
+
     /**
      * Wraps an observer and keeps the table information.
      *
@@ -844,3 +869,12 @@
         }
     }
 }
+
+/**
+ * Stores needed info to restart the invalidation after it was auto-closed.
+ */
+internal data class MultiInstanceClientInitState(
+    val context: Context,
+    val name: String,
+    val serviceIntent: Intent
+)
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index 6d22589..450ae01 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -250,7 +250,7 @@
         // Configure multi-instance invalidation, if enabled
         if (configuration.multiInstanceInvalidationServiceIntent != null) {
             requireNotNull(configuration.name)
-            invalidationTracker.startMultiInstanceInvalidation(
+            invalidationTracker.initMultiInstanceInvalidation(
                 configuration.context,
                 configuration.name,
                 configuration.multiInstanceInvalidationServiceIntent
@@ -517,7 +517,7 @@
             val closeLock: Lock = readWriteLock.writeLock()
             closeLock.lock()
             try {
-                invalidationTracker.stopMultiInstanceInvalidation()
+                invalidationTracker.stop()
                 connectionManager.close()
             } finally {
                 closeLock.unlock()
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
index 13ba545..bb3ec23 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
@@ -181,7 +181,7 @@
     /**
      * Journal modes for SQLite database.
      *
-     * @see Builder.setJournalMode
+     * @see Builder#setJournalMode
      */
     enum class JournalMode {
         /**
diff --git a/samples/AndroidXDemos/lint-baseline.xml b/samples/AndroidXDemos/lint-baseline.xml
index 21633f4..ffcc193 100644
--- a/samples/AndroidXDemos/lint-baseline.xml
+++ b/samples/AndroidXDemos/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha07)" variant="all" version="8.4.0-alpha07">
 
     <issue
         id="OnClick"
@@ -60,7 +60,7 @@
 
     <issue
         id="NewApi"
-        message="`&lt;class>` requires API level 24 (current min is 19)"
+        message="Custom drawables requires API level 24 (current min is 19)"
         errorLine1="    class=&quot;com.example.androidx.drawable.MyDrawable&quot;"
         errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/samples/MediaRoutingDemo/lint-baseline.xml b/samples/MediaRoutingDemo/lint-baseline.xml
index 020dd5f..b32d7d5 100644
--- a/samples/MediaRoutingDemo/lint-baseline.xml
+++ b/samples/MediaRoutingDemo/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="RestrictedApiAndroidX"
@@ -12,15 +12,6 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="RouteInfo.DEVICE_TYPE_BLUETOOTH can only be accessed from within the same library (androidx.mediarouter:mediarouter)"
-        errorLine1="        BLUETOOTH(MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH),"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/androidx/mediarouting/data/RouteItem.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
         message="RouteInfo.DEVICE_TYPE_UNKNOWN can only be accessed from within the same library (androidx.mediarouter:mediarouter)"
         errorLine1="        UNKNOWN(MediaRouter.RouteInfo.DEVICE_TYPE_UNKNOWN);"
         errorLine2="                                      ~~~~~~~~~~~~~~~~~~~">
diff --git a/samples/Support4Demos/lint-baseline.xml b/samples/Support4Demos/lint-baseline.xml
index 8e8e334..13a47b0 100644
--- a/samples/Support4Demos/lint-baseline.xml
+++ b/samples/Support4Demos/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="MissingPermission"
@@ -183,7 +183,7 @@
 
     <issue
         id="UnspecifiedRegisterReceiverFlag"
-        message="`this` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for com.example.android.supportv4.media.next, com.example.android.supportv4.media.pause, com.example.android.supportv4.media.play, com.example.android.supportv4.media.prev"
+        message="`this` is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected broadcasts registered for com.example.android.supportv4.media.next, com.example.android.supportv4.media.pause, com.example.android.supportv4.media.play, com.example.android.supportv4.media.prev"
         errorLine1="                mService.registerReceiver(this, filter);"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/security/security-mls/OWNERS b/security/security-mls/OWNERS
new file mode 100644
index 0000000..f8a2d84
--- /dev/null
+++ b/security/security-mls/OWNERS
@@ -0,0 +1,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
diff --git a/media2/media2-exoplayer/api/current.txt b/security/security-mls/api/current.txt
similarity index 100%
rename from media2/media2-exoplayer/api/current.txt
rename to security/security-mls/api/current.txt
diff --git a/media2/media2-common/api/res-current.txt b/security/security-mls/api/res-current.txt
similarity index 100%
rename from media2/media2-common/api/res-current.txt
rename to security/security-mls/api/res-current.txt
diff --git a/media2/media2-exoplayer/api/restricted_current.txt b/security/security-mls/api/restricted_current.txt
similarity index 100%
rename from media2/media2-exoplayer/api/restricted_current.txt
rename to security/security-mls/api/restricted_current.txt
diff --git a/security/security-mls/build.gradle b/security/security-mls/build.gradle
new file mode 100644
index 0000000..1d899d2
--- /dev/null
+++ b/security/security-mls/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    // Add dependencies here
+}
+
+android {
+    namespace "androidx.security.mls"
+}
+
+androidx {
+    name = "androidx.security:security-mls"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.SECURITY_MLS
+    inceptionYear = "2024"
+    description = "build apps on top of the MLS (RFC 9420) protocol."
+}
diff --git a/security/security-mls/src/main/java/androidx/security/androidx-security-security-mls-documentation.md b/security/security-mls/src/main/java/androidx/security/androidx-security-security-mls-documentation.md
new file mode 100644
index 0000000..75982a6
--- /dev/null
+++ b/security/security-mls/src/main/java/androidx/security/androidx-security-security-mls-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+android.security security-mls
+
+# Package androidx.security.mls
+
+This library makes it easy for developers to write apps with end to end encryption on top of the Messaging Layer Security (RFC9420) protocol.
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 7cef8ff..7c7abd3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -254,7 +254,7 @@
                         "CAMERA  - camera projects\n" +
                         "MAIN    - androidx projects that are not compose\n" +
                         "FLAN    - fragment, lifecycle, activity, and navigation projects\n" +
-                        "MEDIA   - media, media2, and mediarouter projects\n" +
+                        "MEDIA   - media and mediarouter projects\n" +
                         "WEAR    - Wear OS projects\n" +
                         "NATIVE  - native projects\n" +
                         "WINDOW  - window projects\n" +
@@ -336,6 +336,8 @@
 includeProject(":activity:activity-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":activity:integration-tests:testapp", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":activity:integration-tests:baselineprofile", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":activity:integration-tests:macrobenchmark", [BuildType.MAIN, BuildType.FLAN])
+includeProject(":activity:integration-tests:macrobenchmark-target", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":annotation:annotation")
 includeProject(":annotation:annotation-experimental")
 includeProject(":annotation:annotation-experimental-lint")
@@ -507,11 +509,13 @@
 includeProject(":compose:lint:common", [BuildType.COMPOSE])
 includeProject(":compose:lint:common-test", [BuildType.COMPOSE])
 includeProject(":compose:material", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive-layout", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive-navigation", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive-samples", "compose/material3/adaptive/samples", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive-benchmark", "compose/material3/adaptive/benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3", [BuildType.COMPOSE])
 includeProject(":compose:material3:benchmark", [BuildType.COMPOSE])
-includeProject(":compose:material3:material3-adaptive", [BuildType.COMPOSE])
-includeProject(":compose:material3:material3-adaptive:material3-adaptive-samples", "compose/material3/material3-adaptive/samples", [BuildType.COMPOSE])
-includeProject(":compose:material3:material3-adaptive:material3-adaptive-benchmark", "compose/material3/material3-adaptive/benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples", "compose/material3/material3-adaptive-navigation-suite/samples", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-common", [BuildType.COMPOSE])
@@ -756,12 +760,12 @@
 includeProject(":lifecycle:lifecycle-runtime-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples", "lifecycle/lifecycle-runtime-compose/samples", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-compose:integration-tests:lifecycle-runtime-compose-demos", [BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])
 includeProject(":lifecycle:lifecycle-runtime-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-testing-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-service", [BuildType.MAIN, BuildType.FLAN, BuildType.GLANCE])
-includeProject(":lifecycle:lifecycle-viewmodel", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])
 includeProject(":lifecycle:lifecycle-viewmodel-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples", "lifecycle/lifecycle-viewmodel-compose/samples", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:integration-tests:lifecycle-viewmodel-demos", [BuildType.COMPOSE])
@@ -772,12 +776,6 @@
 includeProject(":lint-checks:integration-tests")
 includeProject(":loader:loader", [BuildType.MAIN])
 includeProject(":loader:loader-ktx", [BuildType.MAIN])
-includeProject(":media2:integration-tests:testapp", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-common", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-exoplayer", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-player", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-session", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-widget", [BuildType.MAIN, BuildType.MEDIA])
 includeProject(":media:media", [BuildType.MAIN, BuildType.MEDIA])
 includeProject(":mediarouter:mediarouter", [BuildType.MAIN, BuildType.MEDIA])
 includeProject(":mediarouter:mediarouter-testing", [BuildType.MAIN, BuildType.MEDIA])
@@ -906,6 +904,7 @@
 includeProject(":security:security-crypto", [BuildType.MAIN])
 includeProject(":security:security-crypto-ktx", [BuildType.MAIN])
 includeProject(":security:security-identity-credential", [BuildType.MAIN])
+includeProject(":security:security-mls", [BuildType.MAIN])
 includeProject(":sharetarget:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":sharetarget:sharetarget", [BuildType.MAIN])
 includeProject(":slice:slice-benchmark", [BuildType.MAIN])
@@ -1114,7 +1113,6 @@
 includeProject(":internal-testutils-macrobenchmark", "testutils/testutils-macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":internal-testutils-paparazzi", "testutils/testutils-paparazzi", [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(":kruth:kruth", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP, BuildType.COMPOSE])
@@ -1134,16 +1132,6 @@
         "media/version-compat-tests/previous/service", [BuildType.MAIN, BuildType.MEDIA])
 includeProject(":media:version-compat-tests:lib", [BuildType.MAIN, BuildType.MEDIA])
 
-includeProject(":media2:media2-session:version-compat-tests:client",
-        "media2/media2-session/version-compat-tests/current/client", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-session:version-compat-tests:client-previous",
-        "media2/media2-session/version-compat-tests/previous/client", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-session:version-compat-tests:service",
-        "media2/media2-session/version-compat-tests/current/service", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-session:version-compat-tests:service-previous",
-        "media2/media2-session/version-compat-tests/previous/service", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":media2:media2-session:version-compat-tests:common", [BuildType.MAIN, BuildType.MEDIA])
-
 /////////////////////////////
 //
 // External
@@ -1157,8 +1145,6 @@
 includeProject(":external:libyuv", [BuildType.CAMERA])
 includeProject(":noto-emoji-compat-font", new File(externalRoot, "noto-fonts/emoji-compat"), [BuildType.MAIN])
 includeProject(":noto-emoji-compat-flatbuffers", new File(externalRoot, "noto-fonts/emoji-compat-flatbuffers"), [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":external:paparazzi:paparazzi", [BuildType.COMPOSE])
-includeProject(":external:paparazzi:paparazzi-agent", [BuildType.COMPOSE])
 
 if (isAllProjects()) {
     includeProject(":docs-tip-of-tree")
diff --git a/slidingpanelayout/slidingpanelayout/build.gradle b/slidingpanelayout/slidingpanelayout/build.gradle
index 14b7661..92187ab 100644
--- a/slidingpanelayout/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/slidingpanelayout/build.gradle
@@ -17,7 +17,7 @@
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.core:core-ktx:1.1.0")
     api("androidx.customview:customview:1.1.0")
-    implementation("androidx.window:window:1.2.0-rc01")
+    implementation("androidx.window:window:1.2.0")
     implementation("androidx.transition:transition:1.4.1")
 
     androidTestImplementation(libs.testExtJunit)
@@ -26,7 +26,7 @@
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(':internal-testutils-runtime'))
-    androidTestImplementation("androidx.window:window-testing:1.2.0-rc01")
+    androidTestImplementation("androidx.window:window-testing:1.2.0")
 }
 
 androidx {
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
index 1e9c22f..754d5e4 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
@@ -218,13 +218,13 @@
  * Returns the AIDL import directories for the given variant of the project.
  */
 internal fun Project.getAidlArtifactsOnCompileClasspath(variant: Variant): List<FileCollection> {
-    val incoming = project.configurations.findByName("${variant.name}CompileClasspath")?.incoming
-    val aidlFiles = incoming?.artifactView { config ->
-            config.attributes(ArtifactType.AIDL)
-        }?.artifacts?.artifactFiles
-    val stableAidlFiles = incoming?.artifactView { config ->
-            config.attributes(ArtifactType.STABLE_AIDL)
-        }?.artifacts?.artifactFiles
+    val incoming = variant.compileConfiguration.incoming
+    val aidlFiles = incoming.artifactView { config ->
+        config.attributes(ArtifactType.AIDL)
+    }.artifacts.artifactFiles
+    val stableAidlFiles = incoming.artifactView { config ->
+        config.attributes(ArtifactType.STABLE_AIDL)
+    }.artifacts.artifactFiles
     return listOfNotNull(aidlFiles, stableAidlFiles)
 }
 
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
index 23ab4ad..18de81a 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
@@ -116,7 +116,7 @@
     }
 
     // Register packaged output for use by AGP's AIDL in other projects.
-    project.configurations.findByName(targetConfig)?.outgoing?.variants { variants ->
+    variant.compileConfiguration.outgoing.variants { variants ->
         variants.allNamed(ARTIFACT_TYPE_AIDL) { variant ->
             variant.artifact(packagedDir) { artifact ->
                 artifact.type = ARTIFACT_TYPE_AIDL
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
index 9d027d5..8ca3a1e 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
@@ -108,10 +108,6 @@
         throw NotImplementedError()
     }
 
-    override fun update(transform: Transformer<out Provider<out T>?, in Provider<T>>) {
-        throw NotImplementedError()
-    }
-
     override fun <U : Any?, R : Any?> zip(
         p0: Provider<U>,
         p1: BiFunction<in T, in U, out R>
diff --git a/testutils/testutils-paparazzi/build.gradle b/testutils/testutils-paparazzi/build.gradle
deleted file mode 100644
index 68d62b0..0000000
--- a/testutils/testutils-paparazzi/build.gradle
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2022 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.BundleInsideHelper
-import androidx.build.LibraryType
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-}
-
-BundleInsideHelper.forInsideJar(
-        project,
-        "com.google.protobuf",
-        // b/268288856 untangle this mess.
-        // Currently this has to match test/screenshot/screenshot/build.gradle because sometimes
-        // both :internal-testutils-paparazzi and :test:screenshot:screenshot are added to the
-        // classpath and picking a different package will cause missing class exceptions due to
-        // class shadowing
-        "androidx.test.screenshot.protobuf",
-        // proto-lite dependency includes .proto files, which are not used and would clash if
-        // users also use proto library directly
-        /* dropResourcesWithSuffix = */ ".proto"
-)
-
-dependencies {
-    api(project(":external:paparazzi:paparazzi"))
-    bundleInside(project(path: ":test:screenshot:screenshot-proto"))
-
-    testImplementation(libs.junit)
-    testImplementation(libs.kotlinTestJunit)
-}
-
-androidx {
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
\ No newline at end of file
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt
deleted file mode 100644
index 510b7f2..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2022 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.paparazzi
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Environment
-import app.cash.paparazzi.Paparazzi
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import java.io.File
-
-/**
- * Creates a [Paparazzi] test rule configured from system properties for AndroidX tests with the
- * `AndroidXPaparazziPlugin` Gradle plugin applied.
- *
- * Golden images used with this framework are expected to have a one-to-one relationship to test
- * functions. This helps ensure isolation between test functions and facilitates updating golden
- * images programmatically via the `:updateGolden` Gradle task or CI.
- *
- * To this end, golden images are named by the qualified name of their test function, instead of
- * a secondary identifier. Additionally, the returned [Paparazzi] instance will enforce a limit of
- * one snapshot per test function.
- */
-fun androidxPaparazzi(
-    deviceConfig: DeviceConfig = DeviceConfig.PIXEL_6.copy(softButtons = false),
-    theme: String = "android:Theme.Material.NoActionBar.Fullscreen",
-    renderingMode: RenderingMode = RenderingMode.SHRINK,
-    imageDiffer: ImageDiffer = ImageDiffer.MSSIMMatcher
-) = Paparazzi(
-    deviceConfig = deviceConfig,
-    theme = theme,
-    renderingMode = renderingMode,
-    environment = Environment(
-        platformDir = systemProperty("platformDir"),
-        resDir = systemProperty("resDir"),
-        assetsDir = systemProperty("assetsDir"),
-        compileSdkVersion = systemProperty("compileSdkVersion").toInt(),
-        resourcePackageNames = systemProperty("resourcePackageNames").split(","),
-        appTestDir = System.getProperty("user.dir")!!
-    ),
-    snapshotHandler = GoldenVerifier(
-        modulePath = systemProperty("modulePath"),
-        goldenRootDirectory = File(systemProperty("goldenRootDir")),
-        reportDirectory = File(systemProperty("reportDir")),
-        imageDiffer = imageDiffer
-    )
-)
-
-/** Package name used for resolving system properties */
-internal const val PACKAGE_NAME = "androidx.testutils.paparazzi"
-
-/** Name of the internal Gradle plugin */
-private const val PLUGIN_NAME = "AndroidXPaparazziPlugin"
-
-/** Name of the module containing this library */
-private const val MODULE_NAME = ":internal-testutils-paparazzi"
-
-/** Read a system property with [PACKAGE_NAME] prefix, throwing an exception if missing */
-private fun systemProperty(name: String) = checkNotNull(System.getProperty("$PACKAGE_NAME.$name")) {
-    if (System.getProperty("$PACKAGE_NAME.gradlePluginApplied").toBoolean()) {
-        "Missing required system property: $PACKAGE_NAME.$name. This is likely due to version " +
-            "mismatch between $PLUGIN_NAME and $MODULE_NAME."
-    } else {
-        "Paparazzi system properties not set. Please ensure $PLUGIN_NAME is applied to your build."
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt
deleted file mode 100644
index 8282bd9d..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2022 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.paparazzi
-
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult.Status
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.SnapshotHandler
-import app.cash.paparazzi.TestName
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-
-/**
- * This [SnapshotHandler] implements AndroidX-specific logic for screenshot testing. Specifically,
- * it writes a report to [reportDirectory] at every comparison just before throwing an exception,
- * even on successful comparison. The report contains text and binary protos containing the status
- * of the comparison and relative paths to actual, expected (copy of golden), and difference
- * (magenta highlights) images as applicable. CI consumes these reports to allow updating golden
- * images from the web UI. Golden images can also be updated by the `:updateGolden` Gradle task,
- * which copies actual images to their expected location in the golden repo.
- *
- * It also knows that the AndroidX golden repo, located at [goldenRootDirectory], is partitioned
- * by Gradle project path or [modulePath] and loads images from it.
- *
- * As noted in documentation on [androidxPaparazzi], the verifier is the component to enforce a
- * one-to-one relationship between test functions and golden images. This isn't strictly necessary,
- * but CI currently expects reports to be generated as a side effect of a regular test invocation.
- * Enforcing a one-to-one relationship avoids a situation where a failing assertion earlier in the
- * test would stop a later assertion from executing and generating its report as a side effect,
- * requiring multiple rounds of updating golden images to get the test passing.
- *
- * Additionally, CI currently only supports one comparison per report (note the [ScreenshotResult]
- * proto is not repeated). If this limit is lifted in the future, we would need to devise a scheme
- * for naming additional golden images such as an index of the order they were invoked and wrap the
- * test rule with something like an `ErrorCollector` to ensure later assertions are always
- * reachable.
- *
- * @property modulePath Unique path for the module, derived from Gradle project path. The verifier
- * will search for golden images in this directory relative to [goldenRootDirectory].
- *
- * @property goldenRootDirectory Location on disk of the golden images repo. Golden images for this
- * module are found in the [modulePath] directory under this directory.
- *
- * @property reportDirectory Directory to write reports, including protos, actual and expected
- * images, and image diffs.
- *
- * @property imageDiffer An [ImageDiffer] for comparing images.
- *
- * @property goldenRepoName Name of the repo containing golden images. Used for CI
- */
-internal class GoldenVerifier(
-    val modulePath: String,
-    val goldenRootDirectory: File,
-    val reportDirectory: File,
-    val imageDiffer: ImageDiffer = ImageDiffer.PixelPerfect,
-    val goldenRepoName: String = ANDROIDX_GOLDEN_REPO_NAME
-) : SnapshotHandler {
-    /** Directory containing golden images for this module. */
-    val goldenDirectory = goldenRootDirectory.resolve(modulePath)
-
-    /** The set of tests seen by this verifier, used to enforce single assertion per test. */
-    private val attemptedTests = mutableSetOf<TestName>()
-
-    /**
-     * Asserts that the [actual] matches the expected golden for the test described in [snapshot].
-     * As a side effect, this writes the report proto, actual, expected, and difference images to
-     * [reportDirectory] as appropriate.
-     */
-    fun assertMatchesGolden(snapshot: Snapshot, actual: BufferedImage) {
-        check(snapshot.testName !in attemptedTests) {
-            "Snapshot already taken for test \"${snapshot.testName.methodName}\". Taking " +
-                "multiple snapshots per test is not currently supported."
-        }
-        attemptedTests += snapshot.testName
-
-        val expected = snapshot.toGoldenFile().takeIf { it.canRead() }?.let { ImageIO.read(it) }
-        val analysis = analyze(expected, actual)
-
-        fun updateMessage() = "To update golden images for this test module, run " +
-            "${updateGoldenGradleCommand()}."
-
-        writeReport(snapshot, analysis)
-
-        when (analysis) {
-            is AnalysisResult.Passed -> { /** Test passed, don't need to throw anything */ }
-            is AnalysisResult.Failed -> throw AssertionError(
-                "Actual image differs from golden image: ${analysis.imageDiff.description}. " +
-                    updateMessage()
-            )
-            is AnalysisResult.SizeMismatch -> throw AssertionError(
-                "Actual image has different dimensions than golden image. " +
-                    "Actual: ${analysis.actual.width}x${analysis.actual.height}. " +
-                    "Golden: ${analysis.expected.width}x${analysis.expected.height}. " +
-                    updateMessage()
-            )
-            is AnalysisResult.MissingGolden -> throw AssertionError(
-                "Expected golden image for test \"${snapshot.testName.methodName}\" does not " +
-                    "exist. Run ${updateGoldenGradleCommand()} " +
-                    "to create it and update all golden images for this test module."
-            )
-        }
-    }
-
-    /** Compare [expected] golden image to [actual] image and return an [AnalysisResult] */
-    fun analyze(expected: BufferedImage?, actual: BufferedImage): AnalysisResult {
-        if (expected == null) {
-            return AnalysisResult.MissingGolden(actual)
-        }
-
-        if (actual.width != expected.width || actual.height != expected.height) {
-            return AnalysisResult.SizeMismatch(actual, expected)
-        }
-
-        return when (val diff = imageDiffer.diff(actual, expected)) {
-            is ImageDiffer.DiffResult.Similar -> AnalysisResult.Passed(actual, expected, diff)
-            is ImageDiffer.DiffResult.Different -> AnalysisResult.Failed(actual, expected, diff)
-        }
-    }
-
-    /**
-     * Write the [analysis] for test described by [snapshot] to [reportDirectory] as both binary
-     * and text proto, including actual, expected, and difference image files as appropriate.
-     */
-    fun writeReport(snapshot: Snapshot, analysis: AnalysisResult) {
-        val actualFile = snapshot.toActualFile().also { ImageIO.write(analysis.actual, "PNG", it) }
-        val goldenFile = snapshot.toGoldenFile()
-
-        val resultProto = ScreenshotResult.newBuilder().apply {
-            currentScreenshotFileName = actualFile.toRelativeString(reportDirectory)
-            repoRootPath = goldenRepoName
-            locationOfGoldenInRepo = goldenFile.toRelativeString(goldenRootDirectory)
-        }
-
-        fun diffFile(diff: BufferedImage) = snapshot.toDiffFile()
-            .also { ImageIO.write(diff, "PNG", it) }
-            .toRelativeString(reportDirectory)
-
-        fun expectedFile() = goldenFile.copyTo(snapshot.toExpectedFile())
-            .toRelativeString(reportDirectory)
-
-        when (analysis) {
-            is AnalysisResult.Passed -> resultProto.apply {
-                result = Status.PASSED
-                expectedImageFileName = expectedFile()
-                analysis.imageDiff.highlights?.let { diffImageFileName = diffFile(it) }
-                comparisonStatistics = analysis.imageDiff.taggedDescription()
-            }
-            is AnalysisResult.Failed -> resultProto.apply {
-                result = Status.FAILED
-                expectedImageFileName = expectedFile()
-                diffImageFileName = diffFile(analysis.imageDiff.highlights)
-                comparisonStatistics = analysis.imageDiff.taggedDescription()
-            }
-            is AnalysisResult.SizeMismatch -> resultProto.apply {
-                result = Status.SIZE_MISMATCH
-                expectedImageFileName = expectedFile()
-            }
-            is AnalysisResult.MissingGolden -> resultProto.apply {
-                result = Status.MISSING_GOLDEN
-            }
-        }
-
-        val result = resultProto.build()
-        snapshot.toResultProtoFile().outputStream().use { result.writeTo(it) }
-
-        // TODO(b/244200590): Remove text proto output, or replace with JSON
-        snapshot.toResultTextProtoFile().writeText(result.toString())
-    }
-
-    /**
-     * Analysis result ADT returned from [analyze], including actual and expected images and
-     * an [ImageDiffer.DiffResult].
-     */
-    sealed interface AnalysisResult {
-        val actual: BufferedImage
-
-        data class Passed(
-            override val actual: BufferedImage,
-            val expected: BufferedImage,
-            val imageDiff: ImageDiffer.DiffResult.Similar
-        ) : AnalysisResult
-
-        data class Failed(
-            override val actual: BufferedImage,
-            val expected: BufferedImage,
-            val imageDiff: ImageDiffer.DiffResult.Different
-        ) : AnalysisResult
-
-        data class SizeMismatch(
-            override val actual: BufferedImage,
-            val expected: BufferedImage
-        ) : AnalysisResult
-
-        data class MissingGolden(
-            override val actual: BufferedImage
-        ) : AnalysisResult
-    }
-
-    override fun newFrameHandler(
-        snapshot: Snapshot,
-        frameCount: Int,
-        fps: Int
-    ): SnapshotHandler.FrameHandler {
-        require(frameCount == 1) { "Videos are not yet supported" }
-
-        return object : SnapshotHandler.FrameHandler {
-            override fun handle(image: BufferedImage) {
-                assertMatchesGolden(snapshot, image)
-            }
-
-            override fun close() = Unit
-        }
-    }
-
-    override fun close() = Unit
-
-    /** Adds [ImageDiffer.name] as a prefix to [ImageDiffer.DiffResult.description]. */
-    private fun ImageDiffer.DiffResult.taggedDescription() =
-        "[${imageDiffer.name}]: $description"
-
-    // Filename templates based for a given snapshot
-    private fun Snapshot.toGoldenFile() = goldenDirectory.resolve("${toFileName()}_paparazzi.png")
-    private fun Snapshot.toExpectedFile() = reportDirectory.resolve("${toFileName()}_expected.png")
-    private fun Snapshot.toActualFile() = reportDirectory.resolve("${toFileName()}_actual.png")
-    private fun Snapshot.toDiffFile() = reportDirectory.resolve("${toFileName()}_diff.png")
-    private fun Snapshot.toResultProtoFile() =
-        reportDirectory.resolve("${toFileName()}_goldResult.pb")
-    private fun Snapshot.toResultTextProtoFile() =
-        reportDirectory.resolve("${toFileName()}_goldResult.textproto")
-
-    companion object {
-        /** Name of the AndroidX golden repo. */
-        const val ANDROIDX_GOLDEN_REPO_NAME = "platform/frameworks/support-golden"
-
-        /** Name of the updateGolden Gradle command for this module, via system properties. */
-        fun updateGoldenGradleCommand() =
-            "./gradlew ${
-                (System.getProperty("$PACKAGE_NAME.updateGoldenTask") ?: ":updateGolden")
-            } -Pandroidx.ignoreTestFailures=true"
-
-        /** Render test function name as a fully qualified string. */
-        fun TestName.toQualifiedName(): String {
-            return if (packageName.isEmpty()) {
-                "$className.$methodName"
-            } else {
-                "$packageName.$className.$methodName"
-            }
-        }
-
-        /** Get a file name with special characters removed from a snapshot. */
-        fun Snapshot.toFileName() = testName.toQualifiedName().replace(Regex("\\W+"), "_")
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt
deleted file mode 100644
index 8902cb4..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Copyright 2022 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.paparazzi
-
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Similar
-import java.awt.image.BufferedImage
-import kotlin.math.pow
-
-@JvmDefaultWithCompatibility
-/**
- *  Functional interface to compare two images and returns a [ImageDiffer.DiffResult] ADT containing
- *  comparison statistics and a difference image, if applicable.
- */
-fun interface ImageDiffer {
-    /**
-     * Compare image [a] to image [b]. Implementations may assume [a] and [b] have the same
-     * dimensions.
-     */
-    fun diff(a: BufferedImage, b: BufferedImage): DiffResult
-
-    /** A name to be used in logs for this differ, defaulting to the class's simple name. */
-    val name
-        get() = requireNotNull(this::class.simpleName) {
-            "Could not determine ImageDiffer.name reflectively. Please override ImageDiffer.name."
-        }
-
-    /**
-     * Result ADT returned from [diff].
-     *
-     * A differ may permit a small amount of difference, even for [Similar] results. Similar results
-     * must include a [description], even if it's trivial, but may omit the [highlights] image if
-     * it would be fully transparent.
-     *
-     * @property description A human-readable description of how the images differed, such as the
-     * count of different pixels or percentage changed. Displayed in test failure messages and in
-     * CI.
-     *
-     * @property highlights An image with a transparent background, highlighting where the compared
-     * images differ, typically in shades of magenta. Displayed in CI.
-     */
-    sealed interface DiffResult {
-        val description: String
-        val highlights: BufferedImage?
-
-        data class Similar(
-            override val description: String,
-            override val highlights: BufferedImage? = null
-        ) : DiffResult
-
-        data class Different(
-            override val description: String,
-            override val highlights: BufferedImage
-        ) : DiffResult
-    }
-
-    /**
-     * Pixel perfect image differ requiring images to be identical.
-     *
-     * The alpha channel is treated as pre-multiplied, meaning RGB channels may differ if the alpha
-     * channel is 0 (fully transparent).
-     */
-    // TODO(b/244752233): Support wide gamut images.
-    object PixelPerfect : ImageDiffer {
-        override fun diff(a: BufferedImage, b: BufferedImage): DiffResult {
-            check(a.width == b.width && a.height == b.height) { "Images are different sizes" }
-            val width = a.width
-            val height = b.height
-            val highlights = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
-            var count = 0
-
-            for (x in 0 until width) {
-                for (y in 0 until height) {
-                    val aPixel = a.getRGB(x, y)
-                    val bPixel = b.getRGB(x, y)
-
-                    // Compare full ARGB pixels, but allow other channels to differ if alpha is 0
-                    if (aPixel == bPixel || (aPixel ushr 24 == 0 && bPixel ushr 24 == 0)) {
-                        highlights.setRGB(x, y, TRANSPARENT.toInt())
-                    } else {
-                        count++
-                        highlights.setRGB(x, y, MAGENTA.toInt())
-                    }
-                }
-            }
-
-            val description = "$count of ${width * height} pixels different"
-            return if (count > 0) {
-                DiffResult.Different(description, highlights)
-            } else {
-                DiffResult.Similar(description)
-            }
-        }
-    }
-
-    /**
-     * Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
-     * Simoncelli. Details can be read in their paper:
-     * https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
-     */
-    object MSSIMMatcher : ImageDiffer {
-        override fun diff(a: BufferedImage, b: BufferedImage): DiffResult {
-            val aIntArray = a.toIntArray()
-            val bIntArray = b.toIntArray()
-            val SSIMTotal = calculateSSIM(aIntArray, bIntArray, a.width, a.height)
-
-            val stats = "[MSSIM] Required SSIM: $SSIM_THRESHOLD, Actual " +
-                "SSIM: " + "%.3f".format(SSIMTotal)
-            if (SSIMTotal >= SSIM_THRESHOLD) {
-                return DiffResult.Similar(stats)
-            }
-            return PixelPerfect.diff(a, b)
-        }
-
-        internal fun calculateSSIM(
-            ideal: IntArray,
-            given: IntArray,
-            width: Int,
-            height: Int
-        ): Double {
-            return calculateSSIM(ideal, given, 0, width, width, height)
-        }
-
-        private fun calculateSSIM(
-            ideal: IntArray,
-            given: IntArray,
-            offset: Int,
-            stride: Int,
-            width: Int,
-            height: Int
-        ): Double {
-            var SSIMTotal = 0.0
-            var windows = 0
-            var currentWindowY = 0
-            while (currentWindowY < height) {
-                val windowHeight = computeWindowSize(currentWindowY, height)
-                var currentWindowX = 0
-                while (currentWindowX < width) {
-                    val windowWidth = computeWindowSize(currentWindowX, width)
-                    val start: Int =
-                        indexFromXAndY(currentWindowX, currentWindowY, stride, offset)
-                    if (isWindowWhite(ideal, start, stride, windowWidth, windowHeight) &&
-                        isWindowWhite(given, start, stride, windowWidth, windowHeight)
-                    ) {
-                        currentWindowX += WINDOW_SIZE
-                        continue
-                    }
-                    windows++
-                    val means =
-                        getMeans(ideal, given, start, stride, windowWidth, windowHeight)
-                    val meanX = means[0]
-                    val meanY = means[1]
-                    val variances = getVariances(
-                        ideal, given, meanX, meanY, start, stride,
-                        windowWidth, windowHeight
-                    )
-                    val varX = variances[0]
-                    val varY = variances[1]
-                    val stdBoth = variances[2]
-                    val SSIM = SSIM(meanX, meanY, varX, varY, stdBoth)
-                    SSIMTotal += SSIM
-                    currentWindowX += WINDOW_SIZE
-                }
-                currentWindowY += WINDOW_SIZE
-            }
-            if (windows == 0) {
-                return 1.0
-            }
-            return SSIMTotal / windows.toDouble()
-        }
-
-        /**
-         * Compute the size of the window. The window defaults to WINDOW_SIZE, but
-         * must be contained within dimension.
-         */
-        private fun computeWindowSize(coordinateStart: Int, dimension: Int): Int {
-            return if (coordinateStart + WINDOW_SIZE <= dimension) {
-                WINDOW_SIZE
-            } else {
-                dimension - coordinateStart
-            }
-        }
-
-        private fun isWindowWhite(
-            colors: IntArray,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): Boolean {
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    if (colors[indexFromXAndY(x, y, stride, start)] != WHITE) {
-                        return false
-                    }
-                }
-            }
-            return true
-        }
-
-        /**
-         * This calculates the position in an array that would represent a bitmap given the parameters.
-         */
-        private fun indexFromXAndY(x: Int, y: Int, stride: Int, offset: Int): Int {
-            return x + y * stride + offset
-        }
-
-        private fun SSIM(
-            muX: Double,
-            muY: Double,
-            sigX: Double,
-            sigY: Double,
-            sigXY: Double
-        ): Double {
-            var SSIM = (2 * muX * muY + CONSTANT_C1) * (2 * sigXY + CONSTANT_C2)
-            val denom = ((muX * muX + muY * muY + CONSTANT_C1) * (sigX + sigY + CONSTANT_C2))
-            SSIM /= denom
-            return SSIM
-        }
-
-        /**
-         * This method will find the mean of a window in both sets of pixels. The return is an array
-         * where the first double is the mean of the first set and the second double is the mean of the
-         * second set.
-         */
-        private fun getMeans(
-            pixels0: IntArray,
-            pixels1: IntArray,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): DoubleArray {
-            var avg0 = 0.0
-            var avg1 = 0.0
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    val index: Int = indexFromXAndY(x, y, stride, start)
-                    avg0 += getIntensity(pixels0[index])
-                    avg1 += getIntensity(pixels1[index])
-                }
-            }
-            avg0 /= windowWidth * windowHeight.toDouble()
-            avg1 /= windowWidth * windowHeight.toDouble()
-            return doubleArrayOf(avg0, avg1)
-        }
-
-        /**
-         * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
-         * return value is an array of doubles, the first is the variance of the first set of pixels,
-         * the second is the variance of the second set of pixels, and the third is the covariance.
-         */
-        private fun getVariances(
-            pixels0: IntArray,
-            pixels1: IntArray,
-            mean0: Double,
-            mean1: Double,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): DoubleArray {
-            if (windowHeight == 1 && windowWidth == 1) {
-                // There is only one item. The variance of a single item would be 0.
-                // Since Bessel's correction is used below, it will return NaN instead of 0.
-                return doubleArrayOf(0.0, 0.0, 0.0)
-            }
-
-            var var0 = 0.0
-            var var1 = 0.0
-            var varBoth = 0.0
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    val index: Int = indexFromXAndY(x, y, stride, start)
-                    val v0 = getIntensity(pixels0[index]) - mean0
-                    val v1 = getIntensity(pixels1[index]) - mean1
-                    var0 += v0 * v0
-                    var1 += v1 * v1
-                    varBoth += v0 * v1
-                }
-            }
-            // Using Bessel's correction. Hence, subtracting one.
-            val denominatorWithBesselsCorrection = windowWidth * windowHeight - 1.0
-            var0 /= denominatorWithBesselsCorrection
-            var1 /= denominatorWithBesselsCorrection
-            varBoth /= denominatorWithBesselsCorrection
-            return doubleArrayOf(var0, var1, varBoth)
-        }
-
-        /**
-         * Gets the intensity of a given pixel in RGB using luminosity formula
-         *
-         * l = 0.21R' + 0.72G' + 0.07B'
-         *
-         * The prime symbols dictate a gamma correction of 1.
-         */
-        private fun getIntensity(pixel: Int): Double {
-            val gamma = 1.0
-            var l = 0.0
-            l += 0.21f * (red(pixel) / 255f.toDouble()).pow(gamma)
-            l += 0.72f * (green(pixel) / 255f.toDouble()).pow(gamma)
-            l += 0.07f * (blue(pixel) / 255f.toDouble()).pow(gamma)
-            return l
-        }
-
-        /**
-         * Return the red component of a color int. This is the same as saying
-         * (color >> 16) & 0xFF
-         */
-        fun red(color: Int): Int {
-            return color shr 16 and 0xFF
-        }
-
-        /**
-         * Return the green component of a color int. This is the same as saying
-         * (color >> 8) & 0xFF
-         */
-        fun green(color: Int): Int {
-            return color shr 8 and 0xFF
-        }
-
-        /**
-         * Return the blue component of a color int. This is the same as saying
-         * color & 0xFF
-         */
-        fun blue(color: Int): Int {
-            return color and 0xFF
-        }
-
-        private fun BufferedImage.toIntArray(): IntArray {
-            val bitmapArray = IntArray(width * height)
-            for (x in 0 until width) {
-                for (y in 0 until height) {
-                    bitmapArray[y * width + x] = getRGB(x, y)
-                }
-            }
-            return bitmapArray
-        }
-    }
-
-    private companion object {
-        const val MAGENTA = 0xFF_FF_00_FFu
-        const val TRANSPARENT = 0x00_FF_FF_FFu
-        const val WHITE = 0xFFFFFF
-
-        // These values were taken from the publication
-        private const val CONSTANT_L = 254.0
-        private const val CONSTANT_K1 = 0.00001
-        private const val CONSTANT_K2 = 0.00003
-        private val CONSTANT_C1 = (CONSTANT_L * CONSTANT_K1).pow(2.0)
-        private val CONSTANT_C2 = (CONSTANT_L * CONSTANT_K2).pow(2.0)
-        private const val WINDOW_SIZE = 10
-
-        private val SSIM_THRESHOLD: Double = 0.98
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt b/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt
deleted file mode 100644
index aee8a81..0000000
--- a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright 2022 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.paparazzi
-
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult.Status
-import app.cash.paparazzi.Snapshot
-import java.awt.image.BufferedImage
-import java.io.File
-import java.util.Date
-import javax.imageio.ImageIO
-import kotlin.test.Test
-import kotlin.test.assertContains
-import kotlin.test.assertEquals
-import kotlin.test.assertFails
-import kotlin.test.assertFailsWith
-import kotlin.test.assertIs
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
-import org.junit.rules.TestName
-
-class GoldenVerifierTest {
-    @get:Rule
-    val testName = TestName()
-
-    @get:Rule
-    val goldenDirectory = TemporaryFolder()
-
-    @get:Rule
-    val reportDirectory = TemporaryFolder()
-
-    private val modulePath = "testutils/testutils-paparazzi"
-
-    @Test
-    fun `snapshot handler success`() {
-        createGolden("circle")
-        goldenVerifier().newFrameHandler(snapshot(), 1, 0).handle(loadTestImage("circle"))
-    }
-
-    @Test
-    fun `snapshot handler failure`() {
-        createGolden("star")
-        assertFails {
-            goldenVerifier().newFrameHandler(snapshot(), 1, 0).handle(loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `removes special characters in file names`() {
-        createGolden("circle")
-        // Test that createGolden/goldenFile match naming in verifier
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-
-        assertEquals(
-            goldenFile().name,
-            "androidx_testutils_paparazzi_GoldenVerifierTest_" +
-                "removes_special_characters_in_file_names_paparazzi.png"
-        )
-    }
-
-    @Test
-    fun `writes report on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-
-        val proto = reportProto()
-        assertEquals(Status.PASSED, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("[PixelPerfect]: 0 of 65536 pixels different", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "PASSED")
-    }
-
-    @Test
-    fun `writes actual image on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertEquals(loadTestImage("circle"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `analysis of success`() {
-        val analysis = goldenVerifier().analyze(loadTestImage("circle"), loadTestImage("circle"))
-        assertIs<GoldenVerifier.AnalysisResult.Passed>(analysis)
-        assertEquals(loadTestImage("circle"), analysis.actual)
-        assertEquals(loadTestImage("circle"), analysis.expected)
-    }
-
-    @Test
-    fun `asserts on failure`() {
-        createGolden("star")
-        val message = "Actual image differs from golden image: 17837 of 65536 pixels different. " +
-            "To update golden images for this test module, run ./gradlew :updateGolden " +
-            "-Pandroidx.ignoreTestFailures=true."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-
-        val proto = reportProto()
-        assertEquals(Status.FAILED, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals(reportFile("diff.png").name, proto.diffImageFileName)
-        assertEquals("[PixelPerfect]: 17837 of 65536 pixels different", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "FAILED")
-    }
-
-    @Test
-    fun `writes actual image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("star"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `writes diff image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("PixelPerfect_diff"), reportFile("diff.png").readImage())
-    }
-
-    @Test
-    fun `analysis of failure`() {
-        val analysis = goldenVerifier().analyze(loadTestImage("circle"), loadTestImage("star"))
-        assertIs<GoldenVerifier.AnalysisResult.Failed>(analysis)
-        assertEquals(loadTestImage("star"), analysis.actual)
-        assertEquals(loadTestImage("circle"), analysis.expected)
-        assertEquals(loadTestImage("PixelPerfect_diff"), analysis.imageDiff.highlights)
-    }
-
-    @Test
-    fun `asserts on size mismatch`() {
-        createGolden("horizontal_rectangle")
-        val message = "Actual image has different dimensions than golden image. Actual: 72x128. " +
-            "Golden: 128x72. To update golden images for this test module, run ./gradlew " +
-            ":updateGolden -Pandroidx.ignoreTestFailures=true."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        val proto = reportProto()
-        assertEquals(Status.SIZE_MISMATCH, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "SIZE_MISMATCH")
-    }
-
-    @Test
-    fun `writes actual image for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        assertEquals(loadTestImage("vertical_rectangle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        assertEquals(loadTestImage("horizontal_rectangle"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `analysis of size mismatch`() {
-        val analysis = goldenVerifier()
-            .analyze(loadTestImage("horizontal_rectangle"), loadTestImage("vertical_rectangle"))
-        assertIs<GoldenVerifier.AnalysisResult.SizeMismatch>(analysis)
-        assertEquals(loadTestImage("vertical_rectangle"), analysis.actual)
-        assertEquals(loadTestImage("horizontal_rectangle"), analysis.expected)
-    }
-
-    @Test
-    fun `asserts on missing golden`() {
-        val message = "Expected golden image for test \"asserts on missing golden\" does not " +
-            "exist. Run ./gradlew :updateGolden -Pandroidx.ignoreTestFailures=true to create it " +
-            "and update all golden images for this test module."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto for missing golden`() {
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-
-        val proto = reportProto()
-        assertEquals(Status.MISSING_GOLDEN, proto.result)
-        assertEquals("", proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "MISSING_GOLDEN")
-    }
-
-    @Test
-    fun `writes actual image for missing golden`() {
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `analysis of missing golden`() {
-        val analysis = goldenVerifier().analyze(null, loadTestImage("circle"))
-        assertIs<GoldenVerifier.AnalysisResult.MissingGolden>(analysis)
-        assertEquals(loadTestImage("circle"), analysis.actual)
-    }
-
-    @Test
-    fun `ensures single snapshot per method`() {
-        val verifier = goldenVerifier()
-        createGolden("circle")
-
-        verifier.assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertFails { verifier.assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-    }
-
-    private fun goldenVerifier() = GoldenVerifier(
-        modulePath = modulePath,
-        goldenRootDirectory = goldenDirectory.root,
-        reportDirectory = reportDirectory.root
-    )
-
-    /** Assert [block] throws an [AssertionError] with supplied [message]. */
-    private inline fun assertFailsWithMessage(message: String, block: () -> Unit) {
-        assertEquals(message, assertFailsWith<AssertionError> { block() }.message)
-    }
-
-    /** Compare two images using [ImageDiffer.PixelPerfect]. */
-    private fun assertEquals(expected: BufferedImage, actual: BufferedImage) {
-        assertIs<ImageDiffer.DiffResult.Similar>(
-            ImageDiffer.PixelPerfect.diff(expected, actual),
-            message = "Expected images to be identical, but they were not."
-        )
-    }
-
-    private fun snapshot() = Snapshot(
-        name = null,
-        testName = app.cash.paparazzi.TestName(
-            packageName = this::class.java.packageName,
-            className = this::class.simpleName!!,
-            methodName = testName.methodName
-        ),
-        timestamp = Date()
-    )
-
-    /** Create a golden image for this test from the supplied test image [name]. */
-    private fun createGolden(name: String) = javaClass.getResourceAsStream("$name.png")!!
-            .copyTo(goldenFile().apply { parentFile!!.mkdirs() }.outputStream())
-
-    /** Relative path to golden image for this test. */
-    private fun goldenPath() = "$modulePath/${testName()}_paparazzi.png"
-
-    /** Resolve the file path for a golden image for this test under [goldenDirectory]. */
-    private fun goldenFile() = goldenDirectory.root.resolve(goldenPath()).canonicalFile
-
-    /** Read the binary result proto under for this test and check common fields. */
-    private fun reportProto() =
-        ScreenshotResult.parseFrom(reportFile("goldResult.pb").inputStream()).also { proto ->
-            assertEquals(reportFile("actual.png").name, proto.currentScreenshotFileName)
-            assertEquals(GoldenVerifier.ANDROIDX_GOLDEN_REPO_NAME, proto.repoRootPath)
-            assertEquals(goldenPath(), proto.locationOfGoldenInRepo)
-        }
-
-    /** Resolve the file path for a report file with provided [suffix] under [reportDirectory]. */
-    private fun reportFile(suffix: String) =
-        reportDirectory.root.resolve("${testName()}_$suffix").canonicalFile
-
-    /** Convenience function to read an image from a file. */
-    private fun File.readImage() = ImageIO.read(this)
-
-    /** Fully qualified test ID with special characters replaced for this test. */
-    private fun testName() = "${this::class.qualifiedName!!}_${testName.methodName}"
-        .replace(Regex("\\W+"), "_")
-
-    /** Load a test image from resources. */
-    private fun loadTestImage(name: String) =
-        ImageIO.read(javaClass.getResourceAsStream("$name.png")!!)
-}
diff --git a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt b/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt
deleted file mode 100644
index 8b67607..0000000
--- a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2022 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.paparazzi
-
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Different
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Similar
-import androidx.testutils.paparazzi.ImageDiffer.PixelPerfect
-import javax.imageio.ImageIO
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertNull
-
-class ImageDifferTest {
-    @Test
-    fun `PixelPerfect similar`() {
-        val result = PixelPerfect.diff(loadTestImage("circle"), loadTestImage("circle"))
-        assertIs<Similar>(result)
-        assertEquals("0 of 65536 pixels different", result.description)
-        assertNull(result.highlights)
-    }
-
-    @Test
-    fun `PixelPerfect different`() {
-        val result = PixelPerfect.diff(loadTestImage("circle"), loadTestImage("star"))
-        assertIs<Different>(result)
-        assertEquals("17837 of 65536 pixels different", result.description)
-        assertIs<Similar>(
-            PixelPerfect.diff(result.highlights, loadTestImage("PixelPerfect_diff"))
-        )
-    }
-
-    @Test
-    fun `PixelPerfect name`() {
-        assertEquals("PixelPerfect", PixelPerfect.name)
-    }
-
-    private fun loadTestImage(name: String) =
-        ImageIO.read(javaClass.getResourceAsStream("$name.png")!!)
-}
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png
deleted file mode 100644
index 8e3b7b8..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png
deleted file mode 100644
index e6d58321..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png
deleted file mode 100644
index a13221a..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png
deleted file mode 100644
index 61b0c64..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png
deleted file mode 100644
index 91a607e..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityTestRule.kt b/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityTestRule.kt
index afc6573..3f167df 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityTestRule.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityTestRule.kt
@@ -18,6 +18,8 @@
 
 import android.app.Activity
 import android.os.Looper
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 /**
  * Wait for execution, by default waiting 2 cycles to ensure that posted transitions are
@@ -38,6 +40,23 @@
     }
 }
 
+/**
+ * Delay execution to future frames. This is important for something like pointer input where there
+ * can be a delayed post to push event to future frame (see AndroidComposeView). We need to be able
+ * to test that without sleeping the thread.
+ */
+@Suppress("DEPRECATION")
+fun androidx.test.rule.ActivityTestRule<out Activity>.waitForFutureFrame(frames: Int = 1) {
+    repeat(frames) {
+        val countDownLatch = CountDownLatch(1)
+        activity.window.decorView.postOnAnimation {
+            countDownLatch.countDown()
+        }
+        countDownLatch.await(1L, TimeUnit.SECONDS)
+        runOnUiThreadRethrow {}
+    }
+}
+
 @Suppress("DEPRECATION")
 fun androidx.test.rule.ActivityTestRule<out Activity>.runOnUiThreadRethrow(block: () -> Unit) {
     if (Looper.getMainLooper() == Looper.myLooper()) {
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
index ab4afe3..8127a9e 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
@@ -174,6 +174,8 @@
         )
     }
 
+    @JvmStatic
+    @DoNotInline
     fun isFallbackLineSpacingEnabled(layout: BoringLayout): Boolean {
         return layout.isFallbackLineSpacingEnabled
     }
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
deleted file mode 100644
index 2fe33ca..0000000
--- a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2022 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 dalvik.annotation.optimization;
-
-import androidx.annotation.RestrictTo;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Applied to native methods to enable an ART runtime built-in optimization:
- * methods that are annotated this way can speed up JNI transitions for methods that contain no
- * objects (in parameters or return values, or as an implicit {@code this}).
- *
- * <p>
- * The native implementation must exclude the {@code JNIEnv} and {@code jclass} parameters from its
- * function signature. As an additional limitation, the method must be explicitly registered with
- * {@code RegisterNatives} instead of relying on the built-in dynamic JNI linking.
- * </p>
- *
- * <p>
- * Performance of JNI transitions:
- * <ul>
- * <li>Regular JNI cost in nanoseconds: 115
- * <li>Fast {@code (!)} JNI cost in nanoseconds: 60
- * <li>{@literal @}{@link FastNative} cost in nanoseconds: 35
- * <li>{@literal @}{@code CriticalNative} cost in nanoseconds: 25
- * </ul>
- * (Measured on angler-userdebug in 07/2016).
- * </p>
- *
- * <p>
- * A similar annotation, {@literal @}{@link FastNative}, exists with similar performance guarantees.
- * However, unlike {@code @CriticalNative} it supports non-statics, object return values, and object
- * parameters. If a method absolutely must have access to a {@code jobject}, then use
- * {@literal @}{@link FastNative} instead of this.
- * </p>
- *
- * <p>
- * This has the side-effect of disabling all garbage collections while executing a critical native
- * method. Use with extreme caution. Any long-running methods must not be marked with
- * {@code @CriticalNative} (including usually-fast but generally unbounded methods)!
- * </p>
- *
- * <p>
- * <b>Deadlock Warning:</b> As a rule of thumb, do not acquire any locks during a critical native
- * call if they aren't also locally released [before returning to managed code].
- * </p>
- *
- * <p>
- * Say some code does:
- *
- * <code>
- * critical_native_call_to_grab_a_lock();
- * does_some_java_work();
- * critical_native_call_to_release_a_lock();
- * </code>
- *
- * <p>
- * This code can lead to deadlocks. Say thread 1 just finishes
- * {@code critical_native_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
- * GC kicks in and suspends thread 1. Thread 2 now is in
- * {@code critical_native_call_to_grab_a_lock()} but is blocked on grabbing the
- * native lock since it's held by thread 1. Now thread suspension can't finish
- * since thread 2 can't be suspended since it's doing CriticalNative JNI.
- * </p>
- *
- * <p>
- * Normal natives don't have the issue since once it's executing in native code,
- * it is considered suspended from the runtime's point of view.
- * CriticalNative natives however don't do the state transition done by the normal natives.
- * </p>
- *
- * <p>
- * This annotation has no effect when used with non-native methods.
- * The runtime must throw a {@code VerifierError} upon class loading if this is used with a native
- * method that contains object parameters, an object return value, or a non-static.
- * </p>
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@Retention(RetentionPolicy.CLASS)  // Save memory, don't instantiate as an object at runtime.
-@Target(ElementType.METHOD)
-public @interface CriticalNative {}
-
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
deleted file mode 100644
index 94a7e9e..0000000
--- a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2022 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 dalvik.annotation.optimization;
-
-import androidx.annotation.RestrictTo;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * An ART runtime built-in optimization for "native" methods to speed up JNI transitions.
- *
- * <p>
- * This has the side-effect of disabling all garbage collections while executing a fast native
- * method. Use with extreme caution. Any long-running methods must not be marked with
- * {@code @FastNative} (including usually-fast but generally unbounded methods)!</p>
- *
- * <p><b>Deadlock Warning:</b>As a rule of thumb, do not acquire any locks during a fast native
- * call if they aren't also locally released [before returning to managed code].</p>
- *
- * <p>
- * Say some code does:
- *
- * <code>
- * fast_jni_call_to_grab_a_lock();
- * does_some_java_work();
- * fast_jni_call_to_release_a_lock();
- * </code>
- *
- * <p>
- * This code can lead to deadlocks. Say thread 1 just finishes
- * {@code fast_jni_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
- * GC kicks in and suspends thread 1. Thread 2 now is in {@code fast_jni_call_to_grab_a_lock()}
- * but is blocked on grabbing the native lock since it's held by thread 1.
- * Now thread suspension can't finish since thread 2 can't be suspended since it's doing
- * FastNative JNI.
- * </p>
- *
- * <p>
- * Normal JNI doesn't have the issue since once it's in native code,
- * it is considered suspended from java's point of view.
- * FastNative JNI however doesn't do the state transition done by JNI.
- * </p>
- *
- * <p>
- * Note that even in FastNative methods you <b>are</b> allowed to
- * allocate objects and make upcalls into Java code. A call from Java to
- * a FastNative function and back to Java is equivalent to a call from one Java
- * method to another. What's forbidden in a FastNative method is blocking
- * the calling thread in some non-Java code and thereby preventing the thread
- * from responding to requests from the garbage collector to enter the suspended
- * state.
- * </p>
- *
- * <p>
- * Has no effect when used with non-native methods.
- * </p>
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@Retention(RetentionPolicy.CLASS)  // Save memory, don't instantiate as an object at runtime.
-@Target(ElementType.METHOD)
-public @interface FastNative {}
-
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
deleted file mode 100644
index e604041..0000000
--- a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Rationale
-
-`FastNative` and `CriticalNative` annotations are duplicated from the platform, so that the platform
-detects their presence, and applies them, even though they're not part of platform public API.
-
-# Next steps
-
-Once the annotations become public in the platform, remove duplicate files and use the platform.
-Tracking item: b/35664282
diff --git a/tv/integration-tests/playground/build.gradle b/tv/integration-tests/playground/build.gradle
index 630c4eb..17edbb4 100644
--- a/tv/integration-tests/playground/build.gradle
+++ b/tv/integration-tests/playground/build.gradle
@@ -86,6 +86,8 @@
                 using project(":lifecycle:lifecycle-livedata-core")
         substitute(module("androidx.lifecycle:lifecycle-runtime:")).
                 using project(":lifecycle:lifecycle-runtime")
+        substitute(module("androidx.lifecycle:lifecycle-runtime-ktx:")).
+                using project(":lifecycle:lifecycle-runtime-ktx")
         substitute(module("androidx.lifecycle:lifecycle-viewmodel:")).
                 using project(":lifecycle:lifecycle-viewmodel")
         substitute(module("androidx.lifecycle:lifecycle-viewmodel-ktx:")).
diff --git a/tv/integration-tests/presentation/build.gradle b/tv/integration-tests/presentation/build.gradle
index ffce50c..c8afcc1 100644
--- a/tv/integration-tests/presentation/build.gradle
+++ b/tv/integration-tests/presentation/build.gradle
@@ -90,6 +90,8 @@
                 using project(":lifecycle:lifecycle-livedata-core")
         substitute(module("androidx.lifecycle:lifecycle-runtime:")).
                 using project(":lifecycle:lifecycle-runtime")
+        substitute(module("androidx.lifecycle:lifecycle-runtime-ktx:")).
+                using project(":lifecycle:lifecycle-runtime-ktx")
         substitute(module("androidx.lifecycle:lifecycle-viewmodel:")).
                 using project(":lifecycle:lifecycle-viewmodel")
         substitute(module("androidx.lifecycle:lifecycle-viewmodel-ktx:")).
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
index 1857244..ff46bd7 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
@@ -53,6 +53,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @ExperimentalTvFoundationApi
+@Suppress("DEPRECATION")
 fun Modifier.scrollableWithPivot(
     state: ScrollableState,
     orientation: Orientation,
diff --git a/vectordrawable/integration-tests/testapp/lint-baseline.xml b/vectordrawable/integration-tests/testapp/lint-baseline.xml
index b35bd65..9b7fbaf 100644
--- a/vectordrawable/integration-tests/testapp/lint-baseline.xml
+++ b/vectordrawable/integration-tests/testapp/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="NewApi"
@@ -121,8 +121,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use -0.001 instead of -.001 to avoid crashes on some devices"
-        errorLine1="                M 3.65, 6.125"
-        errorLine2="          ~~~~~">
+        errorLine1="                m-.001, 0"
+        errorLine2="                 ~~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -130,8 +130,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use 0.001 instead of .001 to avoid crashes on some devices"
-        errorLine1="                m-.001, 0"
-        errorLine2="       ~~~~">
+        errorLine1="                a .001,.001 0 1,0 .002,0"
+        errorLine2="                  ~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -139,8 +139,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use 0.001 instead of .001 to avoid crashes on some devices"
-        errorLine1="                m-.001, 0"
-        errorLine2="            ~~~~">
+        errorLine1="                a .001,.001 0 1,0 .002,0"
+        errorLine2="                       ~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -148,8 +148,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use 0.002 instead of .002 to avoid crashes on some devices"
-        errorLine1="                m-.001, 0"
-        errorLine2="                       ^">
+        errorLine1="                a .001,.001 0 1,0 .002,0"
+        errorLine2="                                  ~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -157,8 +157,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use -0.002 instead of -.002 to avoid crashes on some devices"
-        errorLine1="                a .001,.001 0 1,0 .002,0"
-        errorLine2="                                     ^">
+        errorLine1="                a .001,.001 0 1,0-.002,0z&quot; />"
+        errorLine2="                                 ~~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -166,8 +166,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use 0.001 instead of .001 to avoid crashes on some devices"
-        errorLine1="                a .001,.001 0 1,0 .002,0"
-        errorLine2="                      ~~~~">
+        errorLine1="                a .001,.001 0 1,0-.002,0z&quot; />"
+        errorLine2="                  ~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
@@ -175,8 +175,8 @@
     <issue
         id="InvalidVectorPath"
         message="Use 0.001 instead of .001 to avoid crashes on some devices"
-        errorLine1="                a .001,.001 0 1,0 .002,0"
-        errorLine2="                           ~~~~">
+        errorLine1="                a .001,.001 0 1,0-.002,0z&quot; />"
+        errorLine2="                       ~~~~">
         <location
             file="src/main/res/drawable/vector_drawable04.xml"/>
     </issue>
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Placeholder.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Placeholder.kt
index 4cb7df0..81cba40 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Placeholder.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Placeholder.kt
@@ -394,7 +394,7 @@
  * @param shape the shape to apply to the placeholder
  * @param color the color of the placeholder.
  */
-@Suppress("ComposableModifierFactory")
+@Suppress("ComposableModifierFactory", "DEPRECATION")
 @ExperimentalWearMaterialApi
 @Composable
 public fun Modifier.placeholder(
@@ -445,7 +445,7 @@
  * @param color the color to use in the shimmer.
  */
 
-@Suppress("ComposableModifierFactory")
+@Suppress("ComposableModifierFactory", "DEPRECATION")
 @ExperimentalWearMaterialApi
 @OptIn(ExperimentalWearFoundationApi::class)
 @Composable
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index 63d6d89..c7998e9e 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -32,9 +32,9 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.5.0")
+    api(project(":compose:ui:ui-tooling-preview"))
 
     implementation(libs.kotlinStdlibCommon)
-    implementation(project(":compose:ui:ui-tooling-preview"))
     implementation("androidx.wear:wear-tooling-preview:1.0.0")
 }
 
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index c2f20c3..912149c 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -68,6 +68,7 @@
     androidTestImplementation(project(":activity:activity"))
     androidTestImplementation(project(":activity:activity-compose"))
     androidTestImplementation(project(":activity:activity-ktx"))
+    androidTestImplementation(project(":compose:runtime:runtime"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.7.0")
     androidTestImplementation("androidx.lifecycle:lifecycle-common:2.7.0")
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
index 1d7ce0b..92ec2f7 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
@@ -427,6 +427,11 @@
                         .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_START)
                         .build());
         testCases.put(
+                "overflow_text_center_golden" + goldenSuffix,
+                new Text.Builder(context, longText)
+                        .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_CENTER)
+                        .build());
+        testCases.put(
                 "overflow_ellipsize_maxlines_notreached" + goldenSuffix,
                 new Box.Builder()
                         .setWidth(dp(100))
@@ -437,8 +442,6 @@
                                         // Line height = 20sp
                                         .setTypography(Typography.TYPOGRAPHY_BODY1)
                                         .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE)
-                                        .setMultilineAlignment(
-                                                LayoutElementBuilders.TEXT_ALIGN_START)
                                         .setMaxLines(6)
                                         .build())
                         .build());
@@ -454,8 +457,6 @@
                                         .setTypography(Typography.TYPOGRAPHY_BODY1)
                                         .setOverflow(
                                                 LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
-                                        .setMultilineAlignment(
-                                                LayoutElementBuilders.TEXT_ALIGN_START)
                                         .setMaxLines(6)
                                         .build())
                         .build());
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index 5f95a07..b8482ca 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -970,7 +970,11 @@
             int gravity = UNSPECIFIED_GRAVITY;
             LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
 
-            if (prevInflateParent != null && prevInflateParent.getChildCount() > 0) {
+            if (prevInflateParent != null
+                    && prevInflateParent.getChildCount() > 0
+                    // This is to ensure we are centering the correct parent and that it wasn't
+                    // changed after previous inflation.
+                    && prevRenderedMetadata != null) {
                 View firstChild = prevInflateParent.getChildAt(0);
                 if (firstChild != null) {
                     FrameLayout.LayoutParams childLp =
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpan.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpan.java
new file mode 100644
index 0000000..40db96c
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpan.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.wear.protolayout.renderer.inflater;
+
+import static java.lang.Math.abs;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.StaticLayout;
+import android.text.style.LeadingMarginSpan;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Helper class fixing the indentation for the last broken line by translating the canvas in the
+ * opposite direction.
+ *
+ * <p>Applying letter spacing, center alignment and ellipsis to a text causes incorrect indentation
+ * of the truncated line. For example, the last line is indented in a way where the start of the
+ * line is outside of the boundaries of text.
+ *
+ * <p>It should be applied to a text only when those three attributes are set.
+ */
+// Branched from androidx.compose.ui.text.android.style.IndentationFixSpan
+class IndentationFixSpan implements LeadingMarginSpan {
+    @VisibleForTesting static final String ELLIPSIS_CHAR = "…";
+    @Nullable private Layout mOverrideLayoutForMeasuring = null;
+
+    @Override
+    public int getLeadingMargin(boolean first) {
+        return 0;
+    }
+
+    /**
+     * Creates an instance of {@link IndentationFixSpan} used for fixing the text in {@link
+     * android.widget.TextView} when ellipsize, letter spacing and alignment are set.
+     */
+    IndentationFixSpan() {}
+
+    /**
+     * Creates an instance of {@link IndentationFixSpan} used for fixing the text in {@link
+     * android.widget.TextView} when ellipsize, letter spacing and alignment are set.
+     *
+     * @param layout The {@link StaticLayout} used for measuring how much Canvas should be rotated
+     *               in {@link #drawLeadingMargin}.
+     */
+    IndentationFixSpan(@NonNull StaticLayout layout) {
+        this.mOverrideLayoutForMeasuring = layout;
+    }
+
+    /**
+     * See {@link LeadingMarginSpan#drawLeadingMargin}.
+     *
+     * <p>If {@code IndentationFixSpan(StaticLayout)} has been used, the given {@code layout} would
+     * be ignored when doing measurements.
+     */
+    @Override
+    public void drawLeadingMargin(
+            @NonNull Canvas canvas,
+            @Nullable Paint paint,
+            int x,
+            int dir,
+            int top,
+            int baseline,
+            int bottom,
+            @Nullable CharSequence text,
+            int start,
+            int end,
+            boolean first,
+            @Nullable Layout layout) {
+        // If StaticLayout has been provided, we should use that one for measuring instead of the
+        // passed in one.
+        if (mOverrideLayoutForMeasuring != null) {
+            layout = mOverrideLayoutForMeasuring;
+        }
+
+        if (layout == null || paint == null) {
+            return;
+        }
+
+        float padding = calculatePadding(paint, start, layout);
+
+        if (padding != 0f) {
+            canvas.translate(padding, 0f);
+        }
+    }
+
+    /** Calculates the extra padding on ellipsized last line. Otherwise, returns 0. */
+    @VisibleForTesting
+    static float calculatePadding(@NonNull Paint paint, int start, @NonNull Layout layout) {
+        int lineIndex = layout.getLineForOffset(start);
+
+        // No action needed if line is not ellipsized and that is not the last line.
+        if (lineIndex != layout.getLineCount() - 1 || !isLineEllipsized(layout, lineIndex)) {
+            return 0f;
+        }
+
+        return layout.getParagraphDirection(lineIndex) == Layout.DIR_LEFT_TO_RIGHT
+                ? getEllipsizedPaddingForLtr(layout, lineIndex, paint)
+                : getEllipsizedPaddingForRtl(layout, lineIndex, paint);
+    }
+
+    /** Returns whether the given line is ellipsized. */
+    private static boolean isLineEllipsized(@NonNull Layout layout, int lineIndex) {
+        return layout.getEllipsisCount(lineIndex) > 0;
+    }
+
+    /**
+     * Gets the extra padding that is on the left when line is ellipsized on left-to-right layout
+     * direction. Otherwise, returns 0.
+     */
+    private static float getEllipsizedPaddingForLtr(
+            @NonNull Layout layout, int lineIndex, @NonNull Paint paint) {
+        float lineLeft = layout.getLineLeft(lineIndex);
+
+        if (lineLeft >= 0) {
+            return 0;
+        }
+
+        int ellipsisIndex = getEllipsisIndex(layout, lineIndex);
+        float horizontal = getHorizontalPosition(layout, ellipsisIndex);
+        float length = (horizontal - lineLeft) + paint.measureText(ELLIPSIS_CHAR);
+        float divideFactor = getDivideFactor(layout, lineIndex);
+
+        return abs(lineLeft) + ((layout.getWidth() - length) / divideFactor);
+    }
+
+    /**
+     * Gets the extra padding that is on the right when line is ellipsized on right-to-left layout
+     * direction. Otherwise, returns 0.
+     */
+    // TODO: b/323180070 - Investigate how to improve this so that text doesn't get clipped on large
+    // sizes as there is a bug in platform with letter spacing on formatting characters.
+    private static float getEllipsizedPaddingForRtl(
+            @NonNull Layout layout, int lineIndex, @NonNull Paint paint) {
+        float width = layout.getWidth();
+
+        if (width >= layout.getLineRight(lineIndex)) {
+            return 0;
+        }
+
+        int ellipsisIndex = getEllipsisIndex(layout, lineIndex);
+        float horizontal = getHorizontalPosition(layout, ellipsisIndex);
+        float length = (layout.getLineRight(lineIndex) - horizontal)
+                + paint.measureText(ELLIPSIS_CHAR);
+        float divideFactor = getDivideFactor(layout, lineIndex);
+
+        return width - layout.getLineRight(lineIndex) - ((width - length) / divideFactor);
+    }
+
+    private static float getHorizontalPosition(@NonNull Layout layout, int ellipsisIndex) {
+        return layout.getPrimaryHorizontal(ellipsisIndex);
+    }
+
+    private static int getEllipsisIndex(@NonNull Layout layout, int lineIndex) {
+        return layout.getLineStart(lineIndex) + layout.getEllipsisStart(lineIndex);
+    }
+
+    private static float getDivideFactor(@NonNull Layout layout, int lineIndex) {
+        return layout.getParagraphAlignment(lineIndex) == Alignment.ALIGN_CENTER ? 2f : 1f;
+    }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 0b982b4..4b8fc59 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -53,6 +53,7 @@
 import android.graphics.drawable.GradientDrawable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
+import android.text.StaticLayout;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
@@ -2542,13 +2543,10 @@
                 text.getText(),
                 t -> {
                     // Underlines are applied using a Spannable here, rather than setting paint bits
-                    // (or
-                    // using Paint#setTextUnderline). When multiple fonts are mixed on the same line
-                    // (especially when mixing anything with NotoSans-CJK), multiple underlines can
-                    // appear. Using UnderlineSpan instead though causes the correct behaviour to
-                    // happen
-                    // (only a
-                    // single underline).
+                    // (or using Paint#setTextUnderline). When multiple fonts are mixed on the same
+                    // line (especially when mixing anything with NotoSans-CJK), multiple
+                    // underlines can appear. Using UnderlineSpan instead though causes the
+                    // correct behaviour to happen (only a single underline).
                     SpannableStringBuilder ssb = new SpannableStringBuilder();
                     ssb.append(t);
 
@@ -2556,6 +2554,13 @@
                         ssb.setSpan(new UnderlineSpan(), 0, ssb.length(), Spanned.SPAN_MARK_MARK);
                     }
 
+                    // When letter spacing, align and ellipsize are applied to text, the ellipsized
+                    // line is indented wrong. This adds the IndentationFixSpan in order to fix
+                    // the issue.
+                    if (shouldAttachIndentationFixSpan(text)) {
+                        attachIndentationFixSpan(ssb, /* layoutForMeasuring= */ null);
+                    }
+
                     textView.setText(ssb);
                 },
                 posId,
@@ -2578,7 +2583,7 @@
 
         if (overflow.getValue() == TextOverflow.TEXT_OVERFLOW_ELLIPSIZE
                 && !text.getText().hasDynamicValue()) {
-            adjustMaxLinesForEllipsize(textView);
+            adjustMaxLinesForEllipsize(textView, shouldAttachIndentationFixSpan(text));
         }
 
         // Text auto size is not supported for dynamic text.
@@ -2669,6 +2674,78 @@
     }
 
     /**
+     * Checks whether the {@link IndentationFixSpan} needs to be attached to fix the alignment on
+     * text.
+     */
+    private static boolean shouldAttachIndentationFixSpan(@NonNull Text text) {
+        boolean hasLetterSpacing =
+                text.hasFontStyle()
+                        && text.getFontStyle().hasLetterSpacing()
+                        && text.getFontStyle().getLetterSpacing().getValue() != 0;
+        boolean hasEllipsize =
+                text.hasOverflow()
+                        && (text.getOverflow().getValue() == TextOverflow.TEXT_OVERFLOW_ELLIPSIZE
+                        || text.getOverflow().getValue()
+                        == TextOverflow.TEXT_OVERFLOW_ELLIPSIZE_END);
+        // Since default align is center, we need fix when either alignment is not set or it's set
+        // to center.
+        boolean isCenterAligned =
+                !text.hasMultilineAlignment()
+                        || text.getMultilineAlignment().getValue()
+                        == TextAlignment.TEXT_ALIGN_CENTER;
+        return hasLetterSpacing && hasEllipsize && isCenterAligned;
+    }
+
+    /**
+     * This fixes that issue by correctly indenting the ellipsized line by translating the canvas on
+     * the opposite direction.
+     *
+     * <p>When letter spacing, center alignment and ellipsize are all set to a TextView, depending
+     * on a length of overflow text, the last, ellipsized line starts getting cut of from the
+     * start side.
+     *
+     * <p>It should be applied to a text only when those three attributes are set.
+     */
+    private static void attachIndentationFixSpan(
+            @NonNull SpannableStringBuilder ssb, @Nullable StaticLayout layoutForMeasuring) {
+        if (ssb.length() == 0) {
+            return;
+        }
+
+        // Add additional span that accounts for the extra space that TextView adds when ellipsizing
+        // text.
+        IndentationFixSpan fixSpan =
+                layoutForMeasuring == null
+                        ? new IndentationFixSpan()
+                        : new IndentationFixSpan(layoutForMeasuring);
+        ssb.setSpan(fixSpan, ssb.length() - 1, ssb.length() - 1, /* flags= */ 0);
+    }
+
+    /**
+     * See {@link #attachIndentationFixSpan(SpannableStringBuilder, StaticLayout)}. This method uses
+     * {@link StaticLayout} for measurements.
+     */
+    private static void attachIndentationFixSpan(@NonNull TextView textView) {
+        // This is needed to be passed in as the original Layout would have ellipsize on
+        // a maxLines and only be updated after it's drawn, so we need to calculate
+        // padding based on the StaticLayout.
+        StaticLayout layoutForMeasuring =
+                StaticLayout.Builder.obtain(
+                                /* source= */ textView.getText(),
+                                /* start= */ 0,
+                                /* end= */ textView.getText().length(),
+                                /* paint= */ textView.getPaint(),
+                                /* width= */ textView.getMeasuredWidth())
+                        .setMaxLines(textView.getMaxLines())
+                        .setEllipsize(TruncateAt.END)
+                        .setIncludePad(false)
+                        .build();
+        SpannableStringBuilder ssb = new SpannableStringBuilder(textView.getText());
+        attachIndentationFixSpan(ssb, layoutForMeasuring);
+        textView.setText(ssb);
+    }
+
+    /**
      * Sorts out what maxLines should be if the text could possibly be truncated before maxLines is
      * reached.
      *
@@ -2677,12 +2754,17 @@
      * different than what TEXT_OVERFLOW_ELLIPSIZE_END does, as that option just ellipsizes the last
      * line of text.
      */
-    private void adjustMaxLinesForEllipsize(@NonNull TextView textView) {
+    private void adjustMaxLinesForEllipsize(
+            @NonNull TextView textView, boolean shouldAttachIndentationFixSpan) {
         textView.getViewTreeObserver()
                 .addOnPreDrawListener(
                         new OnPreDrawListener() {
                             @Override
                             public boolean onPreDraw() {
+                                if (textView.getText().length() == 0) {
+                                    return true;
+                                }
+
                                 ViewParent maybeParent = textView.getParent();
                                 if (!(maybeParent instanceof View)) {
                                     Log.d(
@@ -2705,6 +2787,10 @@
                                 // Update only if changed.
                                 if (availableLines < maxMaxLines) {
                                     textView.setMaxLines(availableLines);
+
+                                    if (shouldAttachIndentationFixSpan) {
+                                        attachIndentationFixSpan(textView);
+                                    }
                                 }
 
                                 // Cancel the current drawing pass.
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
index 1571b81..6db2dfb 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
@@ -46,6 +46,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -194,6 +195,57 @@
     }
 
     @Test
+    public void rootContainerChangeChild_beforeLayoutUpdate_layoutReinflates() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+
+        // Render the first layout.
+        Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout1, RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
+        List<View> textView1 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(textView1).hasSize(1);
+        assertThat(findViewsWithText(mRootContainer, TEXT2)).hasSize(1);
+
+        // Change child to a layout that doesn't have FrameLayout.LayoutParams.
+        // This tests that the new layout will be correctly inflated and that there is no exception
+        // thrown when LayoutParams are not as expected (FrameLayout.LayoutParams).
+
+        RelativeLayout newParent = new RelativeLayout(mApplicationContext);
+        newParent.setLayoutParams(new RelativeLayout.LayoutParams(100, 100));
+        // Setting a child is necessary to trigger centering with LayoutParams.
+        newParent.addView(new RelativeLayout(mApplicationContext));
+
+        mRootContainer.removeAllViews();
+        mRootContainer.addView(newParent);
+
+        // Now renderer the new layout. In regular case this would be partial update, but here the
+        // not changed part of the layout was also changed in inflated View.
+        Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
+
+        result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout2, RESOURCES, mRootContainer);
+
+        // Make sure future is computing result.
+        assertThat(result.isDone()).isFalse();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
+        // Everything should be re-inflated.
+        List<View> updatedTextView1 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(updatedTextView1).hasSize(1);
+        assertThat(updatedTextView1.get(0)).isNotEqualTo(textView1.get(0));
+        assertThat(findViewsWithText(mRootContainer, TEXT2)).isEmpty();
+        assertThat(findViewsWithText(mRootContainer, TEXT3)).isNotEmpty();
+    }
+
+    @Test
     public void adaptiveUpdateRatesEnabled_attach_withDynamicValue_appliesDiffOnly()
             throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpanTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpanTest.java
new file mode 100644
index 0000000..e795c5c
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/IndentationFixSpanTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.wear.protolayout.renderer.inflater;
+
+import static androidx.wear.protolayout.renderer.inflater.IndentationFixSpan.ELLIPSIS_CHAR;
+import static androidx.wear.protolayout.renderer.inflater.IndentationFixSpan.calculatePadding;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+
+/**
+ * Tests that the actual padding is correctly calculated. The translation of canvas is tested with
+ * screenshot tests.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IndentationFixSpanTest {
+
+    private static final String TEST_TEXT = "Test";
+    private static final int DEFAULT_ELLIPSIZE_START = 100;
+    private static final TestPaint PAINT = new TestPaint();
+
+    @Rule public final Expect expect = Expect.create();
+
+    @Test
+    public void test_givenLayout_correctObjectIsUsed() {
+        StaticLayout layout = mock(StaticLayout.class);
+        IndentationFixSpan span = new IndentationFixSpan(layout);
+        Layout givenLayout = mock(Layout.class);
+
+        span.drawLeadingMargin(
+                mock(Canvas.class),
+                mock(Paint.class),
+                /* x= */ 0,
+                /* dir= */ 0,
+                /* top= */ 0,
+                /* baseline= */ 0,
+                /* bottom= */ 0,
+                "Text",
+                /* start= */ 0,
+                /* end= */ 0,
+                false,
+                givenLayout);
+
+        verifyNoInteractions(givenLayout);
+
+        verify(layout).getLineCount();
+        verify(layout).getLineForOffset(/* offset= */ 0);
+    }
+
+    @Test
+    public void test_calculatedPadding_onRtl_centerAlign_correctValue() {
+        TestLayoutRtl layout =
+                new TestLayoutRtl(
+                        TEST_TEXT,
+                        /* width= */ 288,
+                        Alignment.ALIGN_CENTER,
+                        /* mainLineIndex= */ 2);
+
+        // On ellipsized line there is padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(-8.5f);
+    }
+
+    @Test
+    public void test_calculatedPadding_onRtl_normalAlign_correctValue() {
+        TestLayoutRtl layout =
+                new TestLayoutRtl(
+                        TEST_TEXT,
+                        /* width= */ 288,
+                        Alignment.ALIGN_NORMAL,
+                        /* mainLineIndex= */ 2);
+
+        // On ellipsized line there is padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(-9f);
+    }
+
+    @Test
+    public void test_calculatedPadding_onLtr_centerAlign_correctValue() {
+        TestLayoutLtr layout =
+                new TestLayoutLtr(
+                        TEST_TEXT,
+                        /* width= */ 300,
+                        Alignment.ALIGN_CENTER,
+                        /* mainLineIndex= */ 2);
+
+        // On ellipsized line there is padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(13f);
+    }
+
+    @Test
+    public void test_calculatedPadding_onLtr_normalAlign_correctValue() {
+        TestLayoutLtr layout =
+                new TestLayoutLtr(
+                        TEST_TEXT,
+                        /* width= */ 300,
+                        Alignment.ALIGN_NORMAL,
+                        /* mainLineIndex= */ 2);
+
+        // On ellipsized line there is padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(19f);
+    }
+
+    @Test
+    public void test_calculatePadding_lastLineNotEllipsize_returnsZero() {
+        // Number of lines so that notEllipsizedLineIndex is the last one.
+        TestLayoutLtr layout =
+                new TestLayoutLtr(
+                        TEST_TEXT,
+                        /* width= */ 300,
+                        Alignment.ALIGN_CENTER,
+                        /* mainLineIndex= */ 2);
+        // But not ellipsized.
+        layout.removeEllipsisCount();
+
+        // On not ellipsized line there is no padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(0);
+    }
+
+    @Test
+    public void test_calculatePadding_notLastLine_returnsZero() {
+        // Number of lines so that notEllipsizedLineIndex is the last one.
+        TestLayoutLtr layout =
+                new TestLayoutLtr(
+                        TEST_TEXT,
+                        /* width= */ 300,
+                        Alignment.ALIGN_CENTER,
+                        /* mainLineIndex= */ 2);
+        // Number of lines so that lineIndex is NOT the last one.
+        layout.increaseLineCount();
+
+        // On not last line there is no padding.
+        expect.that(calculatePadding(PAINT, DEFAULT_ELLIPSIZE_START, layout)).isEqualTo(0);
+    }
+
+    private static class TestPaint extends TextPaint {
+
+        @Override
+        public float measureText(String text) {
+            if (Objects.equals(text, ELLIPSIS_CHAR)) {
+                return 23f;
+            }
+            return super.measureText(text);
+        }
+    }
+
+    /**
+     * Test only implementation of {@link Layout} with numbers so we can test padding correctly.
+     */
+    private abstract static class TestLayout extends Layout {
+
+        private static final int DEFAULT_ELLIPSIS_COUNT = 3;
+        protected final int mMainLineIndex;
+
+        // Overridable values for the mainLineIndex.
+        private int mLineCount;
+        private int mEllipsisCount = DEFAULT_ELLIPSIS_COUNT;
+
+        protected TestLayout(CharSequence text, int width, Alignment align, int mainLineIndex) {
+            super(text, PAINT, width, align, /* spacingMult= */ 0, /* spacingAdd= */ 0);
+            this.mMainLineIndex = mainLineIndex;
+            mLineCount = mainLineIndex + 1;
+        }
+
+        void increaseLineCount() {
+            mLineCount = mLineCount + 3;
+        }
+
+        void removeEllipsisCount() {
+            this.mEllipsisCount = 0;
+        }
+
+        @Override
+        public int getLineCount() {
+            return mLineCount;
+        }
+
+        @Override
+        public int getLineTop(int line) {
+            // N/A
+            return 0;
+        }
+
+        @Override
+        public int getLineDescent(int line) {
+            // N/A
+            return 0;
+        }
+
+        @Override
+        public int getLineStart(int line) {
+            return 0;
+        }
+
+        @Override
+        public boolean getLineContainsTab(int line) {
+            // N/A
+            return false;
+        }
+
+        @Override
+        public Directions getLineDirections(int line) {
+            // N/A
+            return null;
+        }
+
+        @Override
+        public int getTopPadding() {
+            // N/A
+            return 0;
+        }
+
+        @Override
+        public int getBottomPadding() {
+            // N/A
+            return 0;
+        }
+
+        @Override
+        public int getEllipsisCount(int line) {
+            return line == mMainLineIndex ? /* non zero */ mEllipsisCount : 0;
+        }
+
+        @Override
+        public int getLineForOffset(int offset) {
+            return offset == DEFAULT_ELLIPSIZE_START ? mMainLineIndex : 0;
+        }
+    }
+
+    /**
+     * Specific implementation of {@link Layout} that returns numbers for LTR testing of padding.
+     */
+    private static class TestLayoutLtr extends TestLayout {
+
+        protected TestLayoutLtr(CharSequence text, int width, Alignment align, int mainLineIndex) {
+            super(text, width, align, mainLineIndex);
+        }
+
+        @Override
+        public float getPrimaryHorizontal(int offset) {
+            return 258f;
+        }
+
+        @Override
+        public float getLineLeft(int line) {
+            return line == mMainLineIndex ? -7f : super.getLineLeft(line);
+        }
+
+        @Override
+        public int getEllipsisStart(int line) {
+            return 20;
+        }
+
+        @Override
+        public int getParagraphDirection(int line) {
+            return Layout.DIR_LEFT_TO_RIGHT;
+        }
+    }
+
+    /**
+     * Specific implementation of {@link Layout} that returns numbers for RTL testing of padding.
+     */
+    private static class TestLayoutRtl extends TestLayout {
+
+        protected TestLayoutRtl(CharSequence text, int width, Alignment align, int mainLineIndex) {
+            super(text, width, align, mainLineIndex);
+        }
+
+        @Override
+        public float getPrimaryHorizontal(int offset) {
+            return 32f;
+        }
+
+        @Override
+        public float getLineLeft(int line) {
+            return line == mMainLineIndex ? -7f : super.getLineLeft(line);
+        }
+
+        @Override
+        public int getEllipsisStart(int line) {
+            return 20;
+        }
+
+        @Override
+        public int getParagraphDirection(int line) {
+            return Layout.DIR_RIGHT_TO_LEFT;
+        }
+
+        @Override
+        public float getLineRight(int line) {
+            return line == mMainLineIndex ? 296f : super.getLineRight(line);
+        }
+    }
+}
diff --git a/wear/tiles/tiles-renderer/lint-baseline.xml b/wear/tiles/tiles-renderer/lint-baseline.xml
index 95125a4..8294d8d 100644
--- a/wear/tiles/tiles-renderer/lint-baseline.xml
+++ b/wear/tiles/tiles-renderer/lint-baseline.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="UnspecifiedRegisterReceiverFlag"
-        message="`updateReceiver` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for androidx.wear.tiles.action.REQUEST_TILE_UPDATE"
+        message="`updateReceiver` is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected broadcasts registered for androidx.wear.tiles.action.REQUEST_TILE_UPDATE"
         errorLine1="        context.registerReceiver(updateReceiver, i)"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/wear/watchface/watchface-style/lint-baseline.xml b/wear/watchface/watchface-style/lint-baseline.xml
deleted file mode 100644
index 4e453d5..0000000
--- a/wear/watchface/watchface-style/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="cli" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 33 (current min is 26): `LargeCustomValueUserStyleSetting`"
-        errorLine1="                is UserStyleSetting.LargeCustomValueUserStyleSetting ->"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt"/>
-    </issue>
-
-</issues>
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index eb7ff4b..a76fbda 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -821,8 +821,9 @@
                     // init may not have completed.
                     if (initComplete) {
                         onDraw()
+                    } else {
+                        scheduleDraw()
                     }
-                    scheduleDraw()
                 }
             }
         }
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index bf9e5e6..1698827 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -144,7 +144,7 @@
     method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
   }
 
-  public class URLUtilCompat {
+  public final class URLUtilCompat {
     method public static String? getFilenameFromContentDisposition(String);
     method public static String guessFileName(String, String?, String?);
   }
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index bf9e5e6..1698827 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -144,7 +144,7 @@
     method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
   }
 
-  public class URLUtilCompat {
+  public final class URLUtilCompat {
     method public static String? getFilenameFromContentDisposition(String);
     method public static String guessFileName(String, String?, String?);
   }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/URLUtilCompat.java b/webkit/webkit/src/main/java/androidx/webkit/URLUtilCompat.java
index d4b21cf..a00ef4b 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/URLUtilCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/URLUtilCompat.java
@@ -35,7 +35,7 @@
  * @see android.webkit.URLUtil
  */
 @SuppressWarnings("AcronymName") // Compat class for similarly named URLUtil in Android SDK
-public class URLUtilCompat {
+public final class URLUtilCompat {
 
     private URLUtilCompat() {} // Class should not be instantiated
 
diff --git a/window/sidecar/sidecar/build.gradle b/window/sidecar/sidecar/build.gradle
index 6fcdb47..6934064 100644
--- a/window/sidecar/sidecar/build.gradle
+++ b/window/sidecar/sidecar/build.gradle
@@ -41,6 +41,7 @@
     description = "This version of the OEM extension is deprecated. Please use window:extensions:extensions." +
             "module instead."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Should not be published"
 }
 
 android {
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
index 3fdbecb..147d2a9 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
@@ -239,6 +239,7 @@
             0,
             0,
             mConfiguration.executor,
+            mConfiguration.workerCoroutineContext,
             mTaskExecutor,
             mConfiguration.workerFactory,
             progressUpdater,
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
index cdfda32f..5aa72cc 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
@@ -185,6 +185,7 @@
                 mRunAttemptCount,
                 mGeneration,
                 configuration.getExecutor(),
+                configuration.getWorkerCoroutineContext(),
                 taskExecutor,
                 configuration.getWorkerFactory(),
                 progressUpdater,
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 6d1f114..452cc68 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -29,6 +29,7 @@
     method public androidx.work.RunnableScheduler getRunnableScheduler();
     method public androidx.core.util.Consumer<java.lang.Throwable>? getSchedulingExceptionHandler();
     method public java.util.concurrent.Executor getTaskExecutor();
+    method public kotlin.coroutines.CoroutineContext getWorkerCoroutineContext();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerExecutionExceptionHandler();
     method public androidx.work.WorkerFactory getWorkerFactory();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerInitializationExceptionHandler();
@@ -43,6 +44,7 @@
     property public final androidx.work.RunnableScheduler runnableScheduler;
     property public final androidx.core.util.Consumer<java.lang.Throwable>? schedulingExceptionHandler;
     property public final java.util.concurrent.Executor taskExecutor;
+    property public final kotlin.coroutines.CoroutineContext workerCoroutineContext;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerExecutionExceptionHandler;
     property public final androidx.work.WorkerFactory workerFactory;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerInitializationExceptionHandler;
@@ -65,6 +67,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 6d1f114..452cc68 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -29,6 +29,7 @@
     method public androidx.work.RunnableScheduler getRunnableScheduler();
     method public androidx.core.util.Consumer<java.lang.Throwable>? getSchedulingExceptionHandler();
     method public java.util.concurrent.Executor getTaskExecutor();
+    method public kotlin.coroutines.CoroutineContext getWorkerCoroutineContext();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerExecutionExceptionHandler();
     method public androidx.work.WorkerFactory getWorkerFactory();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerInitializationExceptionHandler();
@@ -43,6 +44,7 @@
     property public final androidx.work.RunnableScheduler runnableScheduler;
     property public final androidx.core.util.Consumer<java.lang.Throwable>? schedulingExceptionHandler;
     property public final java.util.concurrent.Executor taskExecutor;
+    property public final kotlin.coroutines.CoroutineContext workerCoroutineContext;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerExecutionExceptionHandler;
     property public final androidx.work.WorkerFactory workerFactory;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerInitializationExceptionHandler;
@@ -65,6 +67,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/lint-baseline.xml b/work/work-runtime/lint-baseline.xml
index e57c7ec..503fac4 100644
--- a/work/work-runtime/lint-baseline.xml
+++ b/work/work-runtime/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
 
     <issue
         id="BanSynchronizedMethods"
@@ -13,42 +13,6 @@
     <issue
         id="BanThreadSleep"
         message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TEST_TIMEOUT_IN_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TEST_TIMEOUT_IN_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TEST_TIMEOUT_IN_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(TEST_TIMEOUT_IN_MS);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
         errorLine1="            Thread.sleep(duration);"
         errorLine2="                   ~~~~~">
         <location
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt
new file mode 100644
index 0000000..40e6d54
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.work
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class ConfigurationExecutorsTest {
+    val workerFactory = TrackingWorkerFactory()
+    val executor = Executors.newSingleThreadExecutor {
+        Thread(it).also { thread -> thread.name = threadTestName }
+    }
+
+    @Test
+    fun testSetExecutor() = runBlocking {
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher?.asExecutor()).isEqualTo(executor)
+    }
+
+    @Test
+    fun testSetWorkerCoroutineDispatcher() = runBlocking {
+        val dispatcher = executor.asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(dispatcher)
+    }
+
+    @Test
+    fun testSetBoth() = runBlocking {
+        val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(dispatcher)
+    }
+
+    @Test
+    fun testSetNeither() = runBlocking {
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(Dispatchers.Default)
+    }
+
+    @Test
+    fun testSetCoroutineContextOverride() = runBlocking {
+        val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineContextOverridingWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id)
+            as CoroutineContextOverridingWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        @Suppress("DEPRECATION")
+        assertThat(coroutineDispatcher).isEqualTo(coroutineWorker.coroutineContext)
+    }
+}
+
+private const val threadTestName = "configuration_test"
+
+class ThreadNameWorker(
+    context: Context,
+    workerParams: WorkerParameters
+) : Worker(context, workerParams) {
+    val threadNameDeferred = CompletableDeferred<String>()
+
+    override fun doWork(): Result {
+        threadNameDeferred.complete(Thread.currentThread().name)
+        return Result.success()
+    }
+}
+
+class CoroutineDispatcherWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : CoroutineWorker(appContext, params) {
+    val coroutineDispatcherDeferred = CompletableDeferred<CoroutineDispatcher?>()
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun doWork(): Result {
+        coroutineDispatcherDeferred.complete(currentCoroutineContext()[CoroutineDispatcher])
+        return Result.success()
+    }
+}
+
+class CoroutineContextOverridingWorker(
+    appContext: Context,
+    params: WorkerParameters,
+) : CoroutineWorker(appContext, params) {
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+
+    @Suppress("OVERRIDE_DEPRECATION")
+    override val coroutineContext: CoroutineDispatcher
+        get() = dispatcher
+
+    val coroutineDispatcherDeferred = CompletableDeferred<CoroutineDispatcher?>()
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun doWork(): Result {
+        coroutineDispatcherDeferred.complete(currentCoroutineContext()[CoroutineDispatcher])
+        return Result.success()
+    }
+}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java b/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
index 20c26d7..cd18321 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
@@ -39,6 +39,8 @@
 
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.Dispatchers;
+
 @RunWith(AndroidJUnit4.class)
 public class DefaultWorkerFactoryTest extends DatabaseTest {
 
@@ -73,6 +75,7 @@
                         1,
                         0,
                         executor,
+                        Dispatchers.getDefault(),
                         new WorkManagerTaskExecutor(executor),
                         mDefaultWorkerFactory,
                         mProgressUpdater,
@@ -101,6 +104,7 @@
                             1,
                             0,
                             executor,
+                            Dispatchers.getDefault(),
                             new WorkManagerTaskExecutor(executor),
                             mDefaultWorkerFactory,
                             mProgressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
index 29ce8c5..bc03195 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
@@ -25,6 +25,7 @@
 import androidx.work.worker.FailureWorker
 import androidx.work.worker.TestWorker
 import java.util.UUID
+import kotlinx.coroutines.Dispatchers
 import org.hamcrest.CoreMatchers.instanceOf
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.MatcherAssert.assertThat
@@ -98,6 +99,7 @@
         1,
         0,
         SynchronousExecutor(),
+        Dispatchers.Default,
         WorkManagerTaskExecutor(SynchronousExecutor()),
         factory,
         progressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
index 3794ddb..9a680b7 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
@@ -34,6 +34,7 @@
 import com.google.common.util.concurrent.ListenableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.fail
 import org.junit.Before
@@ -168,6 +169,7 @@
         1,
         0,
         executor,
+        Dispatchers.Default,
         taskExecutor,
         configuration.workerFactory,
         progressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index ddb9e31..8510b51 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -114,6 +114,8 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
+import kotlinx.coroutines.Dispatchers;
+
 @RunWith(AndroidJUnit4.class)
 public class WorkerWrapperTest extends DatabaseTest {
 
@@ -230,6 +232,7 @@
                                 1,
                                 0,
                                 mSynchronousExecutor,
+                                Dispatchers.getDefault(),
                                 mWorkTaskExecutor,
                                 mConfiguration.getWorkerFactory(),
                                 mMockProgressUpdater,
@@ -1007,6 +1010,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1036,6 +1040,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1056,6 +1061,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1085,6 +1091,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1115,6 +1122,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1149,6 +1157,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1397,6 +1406,7 @@
                         1,
                         0,
                         executorService,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
diff --git a/work/work-runtime/src/main/java/androidx/work/Configuration.kt b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
index ddcbc4d..8f33b1d 100644
--- a/work/work-runtime/src/main/java/androidx/work/Configuration.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
@@ -27,8 +27,14 @@
 import java.util.concurrent.Executors
 import java.util.concurrent.ThreadFactory
 import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.asExecutor
 
 /**
  * The Configuration object used to customize [WorkManager] upon initialization.
@@ -44,6 +50,11 @@
     val executor: Executor
 
     /**
+     * The [CoroutineContext] used by [WorkManager] to execute [CoroutineWorker]s.
+     */
+    val workerCoroutineContext: CoroutineContext
+
+    /**
      * The [Executor] used by [WorkManager] for all its internal business logic
      */
     val taskExecutor: Executor
@@ -150,7 +161,19 @@
     val isUsingDefaultTaskExecutor: Boolean
 
     init {
-        executor = builder.executor ?: createDefaultExecutor(isTaskExecutor = false)
+        val builderWorkerDispatcher = builder.workerContext
+
+        executor = builder.executor ?: builderWorkerDispatcher?.asExecutor()
+            ?: createDefaultExecutor(isTaskExecutor = false)
+
+        workerCoroutineContext = when {
+            builderWorkerDispatcher != null -> builderWorkerDispatcher
+            // we don't want simply always use executor.asCoroutineDispatcher()
+            // as compatibility measure
+            builder.executor != null -> executor.asCoroutineDispatcher()
+            else -> Dispatchers.Default
+        }
+
         isUsingDefaultTaskExecutor = builder.taskExecutor == null
         // This executor is used for *both* WorkManager's tasks and Room's query executor.
         // So this should not be a single threaded executor. Writes will still be serialized
@@ -182,6 +205,7 @@
      */
     class Builder {
         internal var executor: Executor? = null
+        internal var workerContext: CoroutineContext? = null
         internal var workerFactory: WorkerFactory? = null
         internal var inputMergerFactory: InputMergerFactory? = null
         internal var taskExecutor: Executor? = null
@@ -226,7 +250,7 @@
             initializationExceptionHandler = configuration.initializationExceptionHandler
             schedulingExceptionHandler = configuration.schedulingExceptionHandler
             workerInitializationExceptionHandler =
-                    configuration.workerInitializationExceptionHandler
+                configuration.workerInitializationExceptionHandler
             workerExecutionExceptionHandler = configuration.workerExecutionExceptionHandler
             defaultProcessName = configuration.defaultProcessName
         }
@@ -254,7 +278,10 @@
         }
 
         /**
-         * Specifies a custom [Executor] for WorkManager.
+         * Specifies a custom [Executor] to run [Worker.doWork].
+         *
+         * If [setWorkerCoroutineContext] wasn't called then the [executor] will be used as
+         * [CoroutineDispatcher] to run [CoroutineWorker] as well.
          *
          * @param executor An [Executor] for running [Worker]s
          * @return This [Builder] instance
@@ -265,6 +292,20 @@
         }
 
         /**
+         * Specifies a custom [CoroutineDispatcher] to run [CoroutineWorker.doWork].
+         *
+         * If [setExecutor] wasn't called then [dispatcher] will be used as [Executor]
+         * to run [Worker] as well.
+         *
+         * @param dispatcher A [CoroutineDispatcher] for running [CoroutineWorker]s
+         * @return This [Builder] instance
+         */
+        fun setWorkerCoroutineContext(dispatcher: CoroutineDispatcher): Builder {
+            this.workerContext = dispatcher
+            return this
+        }
+
+        /**
          * Specifies a [Executor] which will be used by WorkManager for all its
          * internal book-keeping.
          *
@@ -512,7 +553,7 @@
     }
 }
 
-internal val DEFAULT_CONTENT_URI_TRIGGERS_WORKERS_LIMIT = 8
+internal const val DEFAULT_CONTENT_URI_TRIGGERS_WORKERS_LIMIT = 8
 
 private fun createDefaultExecutor(isTaskExecutor: Boolean): Executor {
     val factory = object : ThreadFactory {
@@ -529,3 +570,6 @@
         factory
     )
 }
+
+private fun CoroutineContext?.asExecutor(): Executor? =
+    (this?.get(ContinuationInterceptor) as? CoroutineDispatcher)?.asExecutor()
diff --git a/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt b/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
index d56aec9..b546f1c 100644
--- a/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
@@ -18,33 +18,52 @@
 
 import android.content.Context
 import com.google.common.util.concurrent.ListenableFuture
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.Runnable
 
 /**
- * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines.  Override
+ * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines. Override
  * the [doWork] function to do your suspending work.
- * <p>
- * By default, CoroutineWorker runs on [Dispatchers.Default]; this can be modified by
- * overriding [coroutineContext].
+ *
+ * By default, CoroutineWorker runs on [Dispatchers.Default] if neither
+ * [Configuration.Builder.setExecutor] or [Configuration.Builder.setWorkerCoroutineContext]
+ * were set.
+ *
  * <p>
  * A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
- * [ListenableWorker.Result].  After this time has expired, the worker will be signalled to stop.
+ * [ListenableWorker.Result]. After this time has expired, the worker will be signalled to stop.
  */
 public abstract class CoroutineWorker(
     appContext: Context,
-    params: WorkerParameters
+    private val params: WorkerParameters
 ) : ListenableWorker(appContext, params) {
 
     /**
-     * The coroutine context on which [doWork] will run. By default, this is [Dispatchers.Default].
+     * The coroutine context on which [doWork] will run.
+     *
+     * If this property is overridden then it takes precedent over [Configuration.executor] or
+     * [Configuration.workerCoroutineContext].
+     *
+     * By default, this is a dispatcher delegating to [Dispatchers.Default]
      */
     @Deprecated(message = "use withContext(...) inside doWork() instead.")
-    public open val coroutineContext: CoroutineDispatcher = Dispatchers.Default
+    public open val coroutineContext: CoroutineDispatcher = DeprecatedDispatcher
 
     @Suppress("DEPRECATION")
     public final override fun startWork(): ListenableFuture<Result> {
+        // if a developer didn't override coroutineContext property, then
+        // we use Dispatchers.Default directly.
+        // We can't fully implement delegating CoroutineDispatcher, because CoroutineDispatcher
+        // has experimental and internal apis.
+        val coroutineContext = if (coroutineContext != DeprecatedDispatcher) {
+            coroutineContext
+        } else {
+            params.workerContext
+        }
+
         return launchFuture(coroutineContext + Job()) { doWork() }
     }
 
@@ -107,4 +126,15 @@
     public final override fun onStopped() {
         super.onStopped()
     }
+
+    private object DeprecatedDispatcher : CoroutineDispatcher() {
+        val dispatcher = Dispatchers.Default
+        override fun dispatch(context: CoroutineContext, block: Runnable) {
+            dispatcher.dispatch(context, block)
+        }
+
+        override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+            return dispatcher.isDispatchNeeded(context)
+        }
+    }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java b/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
index fa1d5dd..fa8f5a2 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
+++ b/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
@@ -26,6 +26,8 @@
 import androidx.annotation.RestrictTo;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
+import kotlin.coroutines.CoroutineContext;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -46,6 +48,7 @@
     private @NonNull RuntimeExtras mRuntimeExtras;
     private int mRunAttemptCount;
     private @NonNull Executor mBackgroundExecutor;
+    private @NonNull CoroutineContext mWorkerContext;
     private @NonNull TaskExecutor mWorkTaskExecutor;
     private @NonNull WorkerFactory mWorkerFactory;
     private @NonNull ProgressUpdater mProgressUpdater;
@@ -63,6 +66,7 @@
             @IntRange(from = 0) int runAttemptCount,
             @IntRange(from = 0) int generation,
             @NonNull Executor backgroundExecutor,
+            @NonNull CoroutineContext workerContext,
             @NonNull TaskExecutor workTaskExecutor,
             @NonNull WorkerFactory workerFactory,
             @NonNull ProgressUpdater progressUpdater,
@@ -74,6 +78,7 @@
         mRunAttemptCount = runAttemptCount;
         mGeneration = generation;
         mBackgroundExecutor = backgroundExecutor;
+        mWorkerContext = workerContext;
         mWorkTaskExecutor = workTaskExecutor;
         mWorkerFactory = workerFactory;
         mProgressUpdater = progressUpdater;
@@ -184,6 +189,13 @@
     /**
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @NonNull CoroutineContext getWorkerContext() {
+        return mWorkerContext;
+    }
+
+    /**
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public @NonNull TaskExecutor getTaskExecutor() {
         return mWorkTaskExecutor;
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
index fd7b5f6..b0115818 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
@@ -29,6 +29,7 @@
 import androidx.work.WorkInfo
 import androidx.work.WorkerExceptionInfo
 import androidx.work.WorkerParameters
+import androidx.work.await
 import androidx.work.impl.background.systemalarm.RescheduleReceiver
 import androidx.work.impl.foreground.ForegroundProcessor
 import androidx.work.impl.model.DependencyDao
@@ -37,7 +38,6 @@
 import androidx.work.impl.model.WorkSpecDao
 import androidx.work.impl.model.generationalId
 import androidx.work.impl.utils.PackageManagerHelper
-import androidx.work.impl.utils.SynchronousExecutor
 import androidx.work.impl.utils.WorkForegroundUpdater
 import androidx.work.impl.utils.WorkProgressUpdater
 import androidx.work.impl.utils.futures.SettableFuture
@@ -53,7 +53,6 @@
 import java.util.concurrent.Callable
 import java.util.concurrent.CancellationException
 import java.util.concurrent.ExecutionException
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asCoroutineDispatcher
 
@@ -173,6 +172,7 @@
             workSpec.runAttemptCount,
             workSpec.generation,
             configuration.executor,
+            configuration.workerCoroutineContext,
             workTaskExecutor,
             configuration.workerFactory,
             WorkProgressUpdater(workDatabase, workTaskExecutor),
@@ -208,33 +208,13 @@
                 return
             }
             val foregroundUpdater = params.foregroundUpdater
-            val dispatcher = workTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher()
-            val runExpedited = launchFuture(dispatcher + Job(), CoroutineStart.UNDISPATCHED) {
+            val mainDispatcher = workTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher()
+            val future = launchFuture(mainDispatcher + Job()) {
                 workForeground(appContext, workSpec, worker, foregroundUpdater, workTaskExecutor)
+                logd(TAG) { "Starting work for ${workSpec.workerClassName}" }
+                worker.startWork().await()
             }
-            // propagate cancellation to runExpedited
-            workerResultFuture.addListener({
-                if (workerResultFuture.isCancelled()) {
-                    runExpedited.cancel(true)
-                }
-            }, SynchronousExecutor())
-            runExpedited.addListener(Runnable {
-                // if mWorkerResultFuture is already cancelled don't even try to do anything.
-                // Naturally, the race between cancellation and mWorker.startWork() still can
-                // happen but we try to avoid doing unnecessary work when it is possible.
-                if (workerResultFuture.isCancelled()) {
-                    return@Runnable
-                }
-                try {
-                    runExpedited.get()
-                    logd(TAG) { "Starting work for ${workSpec.workerClassName}" }
-                    // Call mWorker.startWork() on the main thread.
-                    workerResultFuture.setFuture(worker.startWork())
-                } catch (e: Throwable) {
-                    workerResultFuture.setException(e)
-                }
-            }, workTaskExecutor.getMainThreadExecutor())
-
+            workerResultFuture.setFuture(future)
             // Avoid synthetic accessors.
             val workDescription = workDescription
             workerResultFuture.addListener({
diff --git a/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt b/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
index 99ae5f7..cd6c543 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
@@ -25,6 +25,7 @@
 import io.reactivex.Single
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -94,6 +95,7 @@
     1,
     0,
     executor,
+    EmptyCoroutineContext,
     RxWorkerTest.InstantWorkTaskExecutor(),
     DefaultWorkerFactory,
     progressUpdater,
diff --git a/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt b/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
index 25e93b7..ada365e 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
@@ -26,6 +26,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
+import kotlin.coroutines.EmptyCoroutineContext
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert
@@ -127,6 +128,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt b/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
index fec7eec..e6a60c4 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
@@ -22,6 +22,7 @@
 import androidx.work.impl.utils.futures.SettableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -64,6 +65,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         RxWorkerTest.InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
index 51c70f6..35a6b31 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
@@ -31,6 +31,7 @@
 import io.reactivex.rxjava3.core.Single
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -100,6 +101,7 @@
     1,
     0,
     executor,
+    EmptyCoroutineContext,
     RxWorkerTest.InstantWorkTaskExecutor(),
     DefaultWorkerFactory,
     progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
index 9edd2de..4d497f3 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
@@ -32,6 +32,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
+import kotlin.coroutines.EmptyCoroutineContext
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert
@@ -133,6 +134,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
index 5bc2ab1..b528b562 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
@@ -27,6 +27,7 @@
 import androidx.work.impl.utils.futures.SettableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -69,6 +70,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         RxWorkerTest.InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java b/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
index 8a65620..c06abc9 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
@@ -42,6 +42,8 @@
 import java.util.UUID;
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.Dispatchers;
+
 /**
  * Builds instances of {@link androidx.work.ListenableWorker} which can be used for testing.
  *
@@ -321,6 +323,7 @@
                         mGeneration,
                         // This is unused for ListenableWorker
                         getExecutor(),
+                        Dispatchers.getDefault(),
                         getTaskExecutor(),
                         mWorkerFactory,
                         getProgressUpdater(),